Count loops bounded by a string's length in integers (#1738)

This commit is contained in:
Chris Tate
2026-10-21 21:45:21 -05:00
committed by GitHub
2 changed files with 46 additions and 0 deletions
@@ -209,6 +209,45 @@ test("a string read at the counter is an integer consumer", () => {
expect(matchIntegerCountedForLoop(s, withText)).toBeNull();
});
test("a string length bounds a counted loop while its binding is unchanged", () => {
const withText = new Map(locals);
withText.set("s", { id: "s", name: "s", type: STRING, mutable: true });
const text = (): IrExpr => ({ kind: "varRef", localId: "s", type: STRING, loc });
const length: IrExpr = {
kind: "strIntrinsic",
method: "length",
receiver: text(),
args: [],
type: F64,
loc,
};
const s = counted(length);
s.body = [
{
kind: "exprStmt",
expr: {
kind: "strIntrinsic",
method: "charCodeAt",
receiver: text(),
args: [ref("i")],
type: F64,
loc,
},
loc,
},
];
expect(matchIntegerCountedForLoop(s, withText)?.localId).toBe("i");
// Rebinding the string in the body changes the length the condition reads.
const rebinding = {
...s,
body: [...s.body, { kind: "assign", localId: "s", value: text(), loc } as IrStmt],
};
expect(matchIntegerCountedForLoop(rebinding, withText)).toBeNull();
const boxed = new Map(withText);
boxed.set("s", { ...withText.get("s")!, boxed: true });
expect(matchIntegerCountedForLoop(s, boxed)).toBeNull();
});
test("a stable array length bounds a counted loop; a resizing body keeps it a condition", () => {
const ARR = arrayOf(F64);
const array = (localId: string): IrExpr => ({ kind: "varRef", localId, type: ARR, loc });
@@ -83,6 +83,13 @@ function stableLoopValue(
stableLoopValue(value.left, locals, body, invariant, counter, stableLength) &&
stableLoopValue(value.right, locals, body, invariant, counter, stableLength)
);
case "strIntrinsic":
// A string's length is fixed: stable while its binding is.
return (
value.method === "length" &&
value.receiver.kind === "varRef" &&
stableLoopValue(value.receiver, locals, body, invariant, counter)
);
case "bytesIntrinsic":
case "arrIntrinsic":
return (