Files
scriptc/packages/compiler/test/bytes-callback-helpers.test.ts
T
Chris Tate feec4bf4df Call typed-array callbacks directly and visit on an integer counter
The helpers of the typed-array callback methods (forEach, map, filter,
some, every, find, findIndex, findLast, findLastIndex, reduce,
reduceRight) called their callback through the closure's code pointer on
every element, and one helper served every call with the same signature,
so the callback was never inlined. A literal callback now gets a helper
of its own, as for the array methods, and the constant-callback census
accepts the typed-array helpers, so the helper calls that function
directly.

The visit is now a loop on its own counter, `for (let i = 0; i < a.length;
i++)` or down from the last index, instead of a loop on a counter
declared before it, so the counter is an integer and element reads need no
index test. reduce and reduceRight without an initial value read their
seed at the first visited index and start one past it.

Adds reduce, some and map callbacks to the typed-array-kernels workload.
2026-10-22 04:44:34 -05:00

69 lines
2.9 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, validateModule } from "../src/index.js";
import type { IrFunction, IrModule, IrStmt } from "../src/ir/ir.js";
async function lower(source: string): Promise<IrModule> {
const dir = await mkdtemp(join(tmpdir(), "scriptc-bytes-callbacks-"));
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"));
const mod = deserializeModule(await readFile(outPath, "utf8"));
expect(validateModule(mod)).toEqual([]);
return mod;
} finally {
await rm(dir, { recursive: true, force: true });
}
}
const helpers = (mod: IrModule, method: string): IrFunction[] =>
mod.functions.filter((f) => f.name.startsWith(`%bytes.${method}.`));
const visitLoop = (fn: IrFunction): IrStmt & { kind: "for" } => {
const loop = fn.body.find((s): s is IrStmt & { kind: "for" } => s.kind === "for");
if (!loop) throw new Error(`no loop in ${fn.name}`);
return loop;
};
test("literal callbacks get helpers of their own; function values share one", async () => {
const mod = await lower(`
const a = new Int32Array([1, 2, 3]);
const double = (v: number): number => v * 2;
console.log(a.map((v) => v + 1).join(","), a.map((v) => v - 1).join(","));
console.log(a.map(double).join(","), new Int32Array([4]).map(double).join(","));
`);
expect(helpers(mod, "map")).toHaveLength(3);
});
test("typed-array callback loops run on their own counter", async () => {
const mod = await lower(`
const a = new Float64Array([1.5, 2.5, 3.5]);
console.log(a.some((v) => v > 2), a.reduce((s, v) => s + v));
console.log(a.reduceRight((s, v) => s - v), a.findLastIndex((v) => v < 3));
`);
for (const method of ["some", "reduce"]) {
const loop = visitLoop(helpers(mod, method)[0]!);
// for (let i = <0 or 1 past reduce's seed>; i < a.length; i = i + 1)
expect(loop.init).toMatchObject({ kind: "varDecl", localId: "i.0", init: { kind: "numLit" } });
expect(loop.cond).toMatchObject({
kind: "bin",
op: "<",
right: { kind: "bytesIntrinsic", method: "length" },
});
}
expect(visitLoop(helpers(mod, "reduce")[0]!).init).toMatchObject({ init: { value: 1 } });
// Backward: from n - 2 past reduceRight's seed, and from n - 1.
expect(visitLoop(helpers(mod, "reduceRight")[0]!).init).toMatchObject({
init: { kind: "bin", op: "-", right: { value: 2 } },
});
expect(visitLoop(helpers(mod, "findLastIndex")[0]!).init).toMatchObject({
init: { kind: "bin", op: "-", right: { value: 1 } },
});
});