From b025254f7995563e58daaf89f19b334d84d0ebb7 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Thu, 22 Oct 2026 20:53:28 -0500 Subject: [PATCH] Keep the charAt unit reads' origins on the emitter The charCodeAt reads that stand for rewritten charAt reads (one-unit tests and switches) recorded the charAt each stands for in a module-level WeakMap. Compiler sources keep such caches on their owners: the map now belongs to the emitter (charAtUnitReads), and charAtUnitRead takes it. --- .../compiler/src/backend/llvm/emit-statements.ts | 9 ++++++--- packages/compiler/src/backend/llvm/emitter.ts | 4 ++++ .../compiler/src/backend/llvm/expr-containers.ts | 13 +++++-------- .../compiler/src/backend/llvm/expr-primitives.ts | 4 ++-- 4 files changed, 17 insertions(+), 13 deletions(-) diff --git a/packages/compiler/src/backend/llvm/emit-statements.ts b/packages/compiler/src/backend/llvm/emit-statements.ts index b530f794d..333715b97 100644 --- a/packages/compiler/src/backend/llvm/emit-statements.ts +++ b/packages/compiler/src/backend/llvm/emit-statements.ts @@ -1826,7 +1826,7 @@ export function emitTryCatch(emitter: LlEmitter, s: IrStmt & { kind: "tryCatch" * case, refcounted/boxed case-body locals are NULL-reset up front and * the scope-exit releases rely on NULL tolerance. */ export function emitSwitch(emitter: LlEmitter, s: IrStmt & { kind: "switch" }): void { - const unitSwitch = charAtUnitSwitch(s); + const unitSwitch = charAtUnitSwitch(emitter, s); if (unitSwitch !== null) return emitSwitch(emitter, unitSwitch); const B = emitter.B; const discKind = s.disc.type.kind; @@ -2518,7 +2518,10 @@ function nullableStringSwitch( * one-unit string literals, as a switch on `s.charCodeAt(i)` with the units * as cases: no character string is built. An index out of range answers * NaN, which matches no case, as "" matches no one-unit literal. */ -function charAtUnitSwitch(s: IrStmt & { kind: "switch" }): (IrStmt & { kind: "switch" }) | null { +function charAtUnitSwitch( + emitter: LlEmitter, + s: IrStmt & { kind: "switch" }, +): (IrStmt & { kind: "switch" }) | null { const disc = s.disc; if (disc.kind !== "strIntrinsic" || disc.method !== "charAt" || disc.args.length !== 1) return null; @@ -2531,7 +2534,7 @@ function charAtUnitSwitch(s: IrStmt & { kind: "switch" }): (IrStmt & { kind: "sw return null; return { ...s, - disc: charAtUnitRead(disc), + disc: charAtUnitRead(emitter, disc), cases: s.cases.map((c) => c.test?.kind === "strLit" ? { diff --git a/packages/compiler/src/backend/llvm/emitter.ts b/packages/compiler/src/backend/llvm/emitter.ts index 9c42920b9..5eecda3ec 100644 --- a/packages/compiler/src/backend/llvm/emitter.ts +++ b/packages/compiler/src/backend/llvm/emitter.ts @@ -1201,6 +1201,10 @@ export class LlEmitter { * (findIntegerOrNaNLocals in wrapping-sums.ts). */ integerOrNaNLocals: ReadonlyMap = new Map(); bytesBounds: ReadonlySet = new Set(); + /** The charCodeAt reads standing for charAt reads the emitter rewrote + * (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(); /** Worker programs: typed-array accesses whose views an enclosing version * guard proved thread-owned. */ bytesOwned: ReadonlySet = new Set(); diff --git a/packages/compiler/src/backend/llvm/expr-containers.ts b/packages/compiler/src/backend/llvm/expr-containers.ts index 2f4cd98e9..b84e3fda5 100644 --- a/packages/compiler/src/backend/llvm/expr-containers.ts +++ b/packages/compiler/src/backend/llvm/expr-containers.ts @@ -286,14 +286,11 @@ export function emitArrayCopyLoop( host.B.line(`call double @scr_arr_push_spread(ptr ${dst}, ptr ${src})`); } -/** The `charCodeAt` reads that stand for `charAt` reads the emitter - * rewrote, such as one-unit tests and switches: a loop version's proof - * for the charAt covers its read. */ -const unitReadOrigins = new WeakMap(); - /** `s.charCodeAt(i)` in place of `s.charAt(i)`, the same index read as a - * code unit. */ + * code unit. The emitter records the charAt it stands for + * (charAtUnitReads), so a loop version's proof for the charAt covers it. */ export function charAtUnitRead( + host: LlvmEmitterContext, charAt: IrExpr & { kind: "strIntrinsic" }, ): IrExpr & { kind: "strIntrinsic" } { const read: IrExpr & { kind: "strIntrinsic" } = { @@ -304,7 +301,7 @@ export function charAtUnitRead( type: F64, loc: charAt.loc, }; - unitReadOrigins.set(read, charAt); + host.charAtUnitReads.set(read, charAt); return read; } @@ -312,7 +309,7 @@ export function charAtUnitRead( * below the length (bytes-loop-versions.ts). */ function provenStringRead(host: LlvmEmitterContext, e: IrExpr): boolean { if (host.bytesBounds.has(e)) return true; - const origin = unitReadOrigins.get(e); + const origin = host.charAtUnitReads.get(e); return origin !== undefined && host.bytesBounds.has(origin); } diff --git a/packages/compiler/src/backend/llvm/expr-primitives.ts b/packages/compiler/src/backend/llvm/expr-primitives.ts index 72392956a..0d57d3f69 100644 --- a/packages/compiler/src/backend/llvm/expr-primitives.ts +++ b/packages/compiler/src/backend/llvm/expr-primitives.ts @@ -648,7 +648,7 @@ export function emitStringExpr( // equals no unit, as "" equals no one-unit literal. const unit = charAtUnitTest(e); if (unit !== null) { - const code = host.emitExpr(charAtUnitRead(unit.charAt)); + const code = host.emitExpr(charAtUnitRead(host, unit.charAt)); const t = B.tmp(); B.line( `${t} = fcmp ${e.negated ? "une" : "oeq"} double ${code.name}, ${f64Lit(unit.code)}`, @@ -682,7 +682,7 @@ export function emitStringExpr( // the code unit, as the equality above does. const unit = charAtUnitTest(e); if (unit !== null) { - const code = host.emitExpr(charAtUnitRead(unit.charAt)); + const code = host.emitExpr(charAtUnitRead(host, unit.charAt)); const t = B.tmp(); B.line( `${t} = fcmp ${charAtUnitPredicate(e.op, unit.left)} double ${code.name}, ${f64Lit(unit.code)}`,