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.
This commit is contained in:
Chris Tate
2026-10-22 20:53:28 -05:00
parent 5ad562da7e
commit b025254f79
4 changed files with 17 additions and 13 deletions
@@ -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"
? {
@@ -1201,6 +1201,10 @@ export class LlEmitter {
* (findIntegerOrNaNLocals in wrapping-sums.ts). */
integerOrNaNLocals: ReadonlyMap<string, IntegerRange> = new Map();
bytesBounds: ReadonlySet<IrExpr | IrStmt> = 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<IrExpr, IrExpr>();
/** Worker programs: typed-array accesses whose views an enclosing version
* guard proved thread-owned. */
bytesOwned: ReadonlySet<IrExpr | IrStmt> = new Set();
@@ -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<IrExpr, IrExpr>();
/** `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);
}
@@ -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)}`,