Version while loops only for accesses at their counters

A while loop over typed arrays also versioned when its only checks were
thread-owned receivers or static indices. A worker's job loop such as
`while ((job = Atomics.load(next, 0)) < count) { ...; run(job) }` then
got a second copy whose guard fails whenever the array is shared, and
the doubled call kept the job function from being inlined. A while loop
now versions only when it has an access at one of its counters. Other
while loops compile as they did before they could version.
This commit is contained in:
Chris Tate
2026-10-23 19:42:52 -05:00
parent 4206155ac9
commit 9ab731bf66
2 changed files with 15 additions and 2 deletions
@@ -385,7 +385,10 @@ test("while loop counters below a bound version their accesses", () => {
]);
const declared = (loop: IrStmt): IrFunction =>
fn([{ kind: "varDecl", localId: "i", init: ref("k"), loc }, loop]);
const versionOf = (loop: IrStmt & { kind: "while" }): BytesLoopVersion | null => {
const versionOf = (
loop: IrStmt & { kind: "while" },
workers = false,
): BytesLoopVersion | null => {
const f = declared(loop);
const ranges = analyzeIntegerRanges(f);
return matchBytesLoopVersion(loop, null, {
@@ -396,7 +399,7 @@ test("while loop counters below a bound version their accesses", () => {
initialized: findInitializedByteLoopBindings(f).get(loop) ?? new Set(),
proven: new Set(),
scalarStrings: new Set(),
workers: false,
workers,
owned: new Set(),
});
};
@@ -413,6 +416,11 @@ test("while loop counters below a bound version their accesses", () => {
// An access after the counter advances in the same pass stays checked.
const advanced = whileLoop([step, set(ref("out", f64s), ref("i"), num(0))]);
expect(versionOf(advanced)).toBeNull();
// In a worker program, a while loop versions for its counter's accesses,
// and not for thread-owned receivers alone.
expect(versionOf(copy, true)!.ownedReceivers).toHaveLength(2);
const job = whileLoop([set(ref("out", f64s), ref("k"), get(ref("src", f64s), ref("k"))), step]);
expect(versionOf(job, true)).toBeNull();
const mod: IrModule = {
irVersion: 17,
sourceFile: loc.file,
@@ -581,6 +581,11 @@ export function matchBytesLoopVersion(
pairedKept.length === 0
)
return null;
// A while loop versions only for accesses at its counters. Its other
// checks (thread-owned receivers, static indices) save a test or two per
// pass, while a second copy of its body doubles the calls it makes, which
// can keep a worker's job loop from inlining the function that runs a job.
if (loop.kind === "while" && boundedKept.length === 0) return null;
const bounds = new Set<Node>();
const ownedNodes = new Set<Node>();
const used = new Set<number>();