Files
scriptc/packages/compiler/test/worker-shared-loop-version-emission.test.ts
T
Chris Tate b1a6814796 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.
2026-10-25 08:06:24 -05:00

75 lines
2.7 KiB
TypeScript

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<string> {
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/);
});