Files
scriptc/scripts/bench-backend-analysis.mjs
Chris Tate c08ab955ae Enforce linting and consistent source formatting
- Enforce oxlint and oxfmt for owned JavaScript and TypeScript with independent CI checks.
- Preserve compiler import boundaries and protected fixtures while cleaning up lint findings.
- Standardize source formatting and validate import, fixture, and embedded-source protections.
2026-10-05 17:51:24 -05:00

397 lines
13 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* Isolate whole-module analysis from frontend/native toolchain timings.
* Call chains, class hierarchies, and wide field layouts expose repeated work;
* every measured run verifies the complete analysis result. */
import assert from "node:assert/strict";
import { parseArgs } from "node:util";
import { computeMayThrow } from "../packages/compiler/dist/backend/may-throw.js";
import { computeTraced } from "../packages/compiler/dist/backend/cycle-analysis.js";
import { validateModule } from "../packages/compiler/dist/ir/validate.js";
import { F64, VOID } from "../packages/compiler/dist/ir/ir.js";
const { values } = parseArgs({
options: {
iterations: { type: "string", default: "5" },
sizes: { type: "string", default: "500,2000,4000,8000" },
workloads: {
type: "string",
default:
"may-throw,cycle-analysis,class-values-wide,class-values-deep,class-validation-wide,virtual-validation-wide,virtual-validation-shared,record-validation-references,record-validation-fields,class-validation-fields,class-validation-layouts",
},
},
});
const iterations = Number(values.iterations);
const sizes = values.sizes.split(",").map(Number);
const selected = values.workloads.split(",");
const names = [
"may-throw",
"cycle-analysis",
"class-values-wide",
"class-values-deep",
"class-validation-wide",
"virtual-validation-wide",
"virtual-validation-shared",
"record-validation-references",
"record-validation-fields",
"class-validation-fields",
"class-validation-layouts",
];
assert.ok(
Number.isInteger(iterations) && iterations >= 1 && iterations <= 100,
"--iterations must be an integer between 1 and 100",
);
assert.ok(
sizes.length >= 1 &&
sizes.length <= 16 &&
sizes.every((n) => Number.isInteger(n) && n >= 1 && n <= 100_000),
"--sizes must contain 1–16 integers between 1 and 100000",
);
assert.ok(
selected.length >= 1 && selected.every((name) => names.includes(name)),
`--workloads must select from ${names.join(",")}`,
);
const loc = { file: "chain.ts", start: 0, end: 1 };
const value = { kind: "numLit", value: 1, type: F64, loc };
const results = [];
for (const size of sizes) {
const functions = Array.from({ length: size }, (_, i) => ({
name: `fn${i}`,
params: [],
locals: [],
returnType: VOID,
loc,
body: [
i === size - 1
? { kind: "throw", value, loc }
: {
kind: "exprStmt",
expr: { kind: "call", callee: `fn${i + 1}`, args: [], type: VOID, loc },
loc,
},
],
}));
const mod = { irVersion: 13, sourceFile: loc.file, entry: "fn0", functions };
const records = Array.from({ length: size }, (_, i) => ({
id: `r${i}`,
fields: [
{ name: "next", type: i === size - 1 ? F64 : { kind: "record", shapeId: `r${i + 1}` } },
],
}));
const graph = { ...mod, records };
const workloads = [
{
name: "may-throw",
run: () => computeMayThrow(mod),
check: (answer) => {
assert.equal(answer.indirect, false);
assert.equal(answer.fns.size, size);
for (const fn of functions) assert.ok(answer.fns.has(fn.name), fn.name);
},
},
{
name: "cycle-analysis",
run: () => computeTraced(graph),
check: (answer) => {
assert.equal(answer.shapes.size, 0);
assert.equal(answer.unions.size, 0);
},
},
];
for (const deep of [false, true]) {
const classes = Array.from({ length: size }, (_, i) => ({
name: `C${i}`,
...(deep && i > 0 ? { base: `C${i - 1}` } : {}),
fields: [],
loc,
})).reverse();
const constructors = Array.from({ length: size }, (_, i) => ({
name: `%C${i}.constructor`,
params: [],
locals: [],
returnType: VOID,
loc,
body: (deep ? i === size - 1 : i % 2 === 0) ? [{ kind: "throw", value, loc }] : [],
}));
const factories = Array.from({ length: size }, (_, i) => ({
name: `factory${i}`,
params: [],
locals: [],
returnType: VOID,
loc,
body: [
{
kind: "exprStmt",
expr: {
kind: "newValue",
callee: {
kind: "classRef",
className: `C${i}`,
type: { kind: "classval", className: `C${i}` },
loc,
},
args: [],
type: { kind: "object", className: `C${i}` },
loc,
},
loc,
},
],
}));
const hierarchy = { ...mod, classes, functions: [...factories, ...constructors] };
workloads.push({
name: deep ? "class-values-deep" : "class-values-wide",
run: () => computeMayThrow(hierarchy),
check: (answer) => {
assert.equal(answer.indirect, false);
assert.equal(answer.fns.size, deep ? size + 1 : 2 * Math.ceil(size / 2));
for (let i = 0; i < size; i++) {
assert.equal(answer.fns.has(`factory${i}`), deep || i % 2 === 0, `factory${i}`);
assert.equal(
answer.fns.has(`%C${i}.constructor`),
deep ? i === size - 1 : i % 2 === 0,
`%C${i}.constructor`,
);
}
},
});
}
const layouts = {
...mod,
classes: Array.from({ length: size }, (_, i) => ({ name: `C${i}`, fields: [], loc })),
functions: functions.map((fn) => ({ ...fn, body: [] })),
};
workloads.push({
name: "class-validation-wide",
run: () => validateModule(layouts),
check: (answer) => assert.deepEqual(answer, []),
});
for (const shared of [false, true]) {
const name = shared ? "virtual-validation-shared" : "virtual-validation-wide";
if (!selected.includes(name)) continue;
const virtual = {
...mod,
classes: [],
functions: [{ name: "main", params: [], locals: [], returnType: VOID, body: [], loc }],
entry: "main",
};
for (let i = 0; i < size; i++) {
const base = `Base${i}`;
const derived = `Derived${i}`;
const method = shared ? "run" : `method${i}`;
virtual.classes.push({
name: base,
fields: [],
methods: [method],
abstractMethods: [method],
loc,
});
virtual.classes.push({ name: derived, base, fields: [], methods: [method], loc });
const object = { kind: "object", className: base };
const self = { localId: "self", name: "self", type: object };
virtual.functions.push({
name: `%${derived}.${method}`,
params: [self],
locals: [{ id: "self", name: "self", type: object, mutable: false }],
returnType: F64,
body: [{ kind: "return", value, loc }],
loc,
});
for (let j = 0; j < 4; j++) {
virtual.functions.push({
name: `caller${i}_${j}`,
params: [self],
locals: [{ id: "self", name: "self", type: object, mutable: false }],
returnType: VOID,
body: [
{
kind: "exprStmt",
expr: {
kind: "virtualCall",
className: base,
method,
args: [{ kind: "varRef", localId: "self", type: object, loc }],
type: F64,
loc,
},
loc,
},
],
loc,
});
}
}
workloads.push({
name,
run: () => validateModule(virtual),
check: (answer) => assert.deepEqual(answer, []),
});
}
if (selected.includes("record-validation-references")) {
const references = { ...layouts, classes: [], records };
workloads.push({
name: "record-validation-references",
run: () => validateModule(references),
check: (answer) => assert.deepEqual(answer, []),
});
}
if (selected.includes("record-validation-fields")) {
const fields = Array.from({ length: size }, (_, i) => ({
name: `field${String(i).padStart(6, "0")}`,
type: F64,
}));
const type = { kind: "record", shapeId: "wide" };
const receiver = { kind: "varRef", localId: "record", type, loc };
const wide = {
...mod,
entry: "main",
records: [{ id: "wide", fields }],
functions: [
{
name: "main",
params: [{ localId: "record", name: "record", type }],
locals: [{ id: "record", name: "record", type, mutable: false }],
returnType: VOID,
loc,
body: fields.flatMap(({ name }) => [
{
kind: "exprStmt",
expr: {
kind: "recordGet",
obj: receiver,
shapeId: "wide",
field: name,
type: F64,
loc,
},
loc,
},
{
kind: "exprStmt",
expr: {
kind: "recordClone",
source: receiver,
overrides: [{ name, value }],
type,
loc,
},
loc,
},
]),
},
],
};
workloads.push({
name: "record-validation-fields",
run: () => validateModule(wide),
check: (answer) => assert.deepEqual(answer, []),
});
}
for (const name of ["class-validation-fields", "class-validation-layouts"]) {
if (!selected.includes(name)) continue;
const fields = Array.from({ length: size }, (_, i) => ({
name: `field${String(i).padStart(6, "0")}`,
type: F64,
}));
const type = { kind: "object", className: "Derived" };
const unionType = { kind: "union", unionId: "variants" };
const receiver = { kind: "varRef", localId: "object", type, loc };
const union = { kind: "varRef", localId: "variant", type: unionType, loc };
const wide = {
...mod,
entry: "main",
classes: [
{ name: "Base", fields, loc },
{ name: "Derived", base: "Base", fields, loc },
],
unions: [{ id: "variants", arms: [{ kind: "object", className: "Base" }, type] }],
functions: [
{
name: "main",
returnType: VOID,
loc,
params: [
{ localId: "object", name: "object", type },
{ localId: "variant", name: "variant", type: unionType },
],
locals: [
{ id: "object", name: "object", type, mutable: false },
{ id: "variant", name: "variant", type: unionType, mutable: false },
],
body:
name === "class-validation-layouts"
? []
: fields.flatMap(({ name: field }) => [
{
kind: "exprStmt",
expr: {
kind: "fieldGet",
obj: receiver,
className: "Derived",
field,
type: F64,
loc,
},
loc,
},
{
kind: "exprStmt",
expr: {
kind: "fieldIncDec",
obj: receiver,
className: "Derived",
field,
op: "+",
prefix: true,
fieldDyn: false,
type: F64,
loc,
},
loc,
},
{ kind: "fieldSet", obj: receiver, className: "Derived", field, value, loc },
{
kind: "exprStmt",
expr: {
kind: "unionDisc",
value: union,
unionId: "variants",
field,
type: F64,
loc,
},
loc,
},
]),
},
],
};
workloads.push({
name,
run: () => validateModule(wide),
check: (answer) => assert.deepEqual(answer, []),
});
}
for (const { name, run, check } of workloads) {
if (!selected.includes(name)) continue;
const samples = [];
for (let i = -2; i < iterations; i++) {
const start = performance.now();
const answer = run();
const ms = performance.now() - start;
check(answer);
if (i >= 0) samples.push(ms);
}
const sorted = [...samples].sort((a, b) => a - b);
const middle = Math.floor(sorted.length / 2);
const median = sorted.length % 2 ? sorted[middle] : (sorted[middle - 1] + sorted[middle]) / 2;
results.push({ analysis: name, nodes: size, median_ms: median, samples_ms: samples });
process.stderr.write(`${name} ${size}: ${median.toFixed(2)} ms\n`);
}
}
console.log(
JSON.stringify(
{ node: process.version, platform: process.platform, arch: process.arch, iterations, results },
null,
2,
),
);