Files
scriptc/tests/harness/zero-cost-gate.test.ts
T
Chris Tate cb75c51eda Fail the zero-cost gate when it compares nothing
A benchmark workload or the self-hosted compiler that fails to build
with both checkouts now fails the gate, and so does a run in which no
program built with both. Before this, a run whose builds all failed the
same way, such as with a helper of another version, reported PASS. A
build error now prints in full under its program; its first line is
often a warning.
2026-10-21 03:45:36 -05:00

194 lines
7.0 KiB
TypeScript

import { spawnSync } from "node:child_process";
import { expect, test } from "vitest";
import {
cachegrindCounts,
compareArtifact,
compareCounts,
compareExecutables,
definedFunctionNames,
dynamicTierSymbols,
nmOutput,
programVerdict,
} from "../../scripts/zero-cost-compare.mjs";
const ir = (functions: object[], globals: object[]) =>
Buffer.from(JSON.stringify({ irVersion: 16, functions, globals, sourceFile: "/p/main.ts" }));
const llvm = (body: string, extra = "") =>
Buffer.from(
[
"; Generated by scriptc (LLVM backend) from /p/main.ts. Do not edit.",
'@.str = private constant [3 x i8] c"hi\\00"',
"define internal i64 @add(i64 %a, i64 %b) {",
` ${body}`,
"}",
'define void @"main.$1"() { ret void }',
extra,
"declare void @scr_print(ptr)",
"",
].join("\n"),
);
test("the zero-cost gate names the first differing IR and LLVM unit", () => {
const f = { name: "f", params: [], body: [{ kind: "return" }] };
const g = { name: "g", params: [], body: [] };
const base = ir([f, g, f], [{ name: "a", type: "i64" }]);
expect(compareArtifact("ir", base, Buffer.from(base))).toEqual({ identical: true });
const changed = compareArtifact(
"ir",
base,
ir([f, { ...g, params: [1] }, f], [{ name: "a", type: "i64" }]),
);
expect(changed).toMatchObject({
identical: false,
first: "changed g",
changed: ["g"],
added: [],
module: null,
});
const header = compareArtifact("ir", base, ir([f, g, f], [{ name: "a", type: "f64" }]));
expect(header).toMatchObject({ first: "module globals[0:a]", changed: [] });
// Repeated names are matched by occurrence, so an insertion shows as added.
expect(compareArtifact("ir", base, ir([f, g, f, f], [{ name: "a", type: "i64" }]))).toMatchObject(
{
first: "added f#3",
},
);
const ll = llvm("%r = add i64 %a, %b\n ret i64 %r");
expect(compareArtifact("llvm", ll, Buffer.from(ll))).toEqual({ identical: true });
expect(compareArtifact("llvm", ll, llvm("%r = sub i64 %a, %b\n ret i64 %r"))).toMatchObject({
first: "changed add",
changed: ["add"],
functions: { baseline: 2, candidate: 2 },
});
expect(
compareArtifact(
"llvm",
ll,
llvm("%r = add i64 %a, %b\n ret i64 %r", "define void @new() { ret void }"),
),
).toMatchObject({ first: "added new", added: ["new"] });
const global = compareArtifact("llvm", ll, Buffer.from(ll.toString().replace("hi", "ho")));
expect(global).toMatchObject({ first: "module line 2", changed: [] });
expect(global.module.candidate).toContain("ho");
});
test("the zero-cost gate compares executables and instruction counts per function", () => {
const nm =
"0000000000001000 0000000000000010 T main\n0000000000001010 0000000000000020 t scr_add\n";
const resized = compareExecutables(
Buffer.from("abcd"),
Buffer.from("abXd"),
nm,
nm.replace("0000000000000020 t", "0000000000000030 t"),
);
expect(resized).toMatchObject({ identical: false, first: "resized scr_add", offset: 2 });
const output = (main: number, prefault: number, loader: number) =>
[
"events: Ir",
"fl=???",
"fn=main",
`0 ${main}`,
"fn=scr_sa_prefault",
`0 ${prefault}`,
"fn=_dl_lookup_symbol_x",
`0 ${loader}`,
"fn=scr_add",
"0 7",
"0 3",
].join("\n");
const own = new Set(["main", "scr_add", "scr_sa_prefault"]);
const base = cachegrindCounts(output(100, 50, 9000), own);
expect([...base]).toEqual([
["main", 100],
["scr_add", 10],
]);
expect(compareCounts(base, cachegrindCounts(output(100, 75, 9300), own)).identical).toBe(true);
expect(compareCounts(base, cachegrindCounts(output(104, 50, 9000), own))).toMatchObject({
identical: false,
total: { baseline: 110, candidate: 114 },
deltas: [{ name: "main", delta: 4 }],
});
});
test("the zero-cost gate throws when nm does not list a binary's symbols", () => {
const ok = { status: 0, signal: null, stdout: "0000000000001000 T main\n", stderr: "" };
expect(nmOutput("prog", ok)).toBe(ok.stdout);
const missing = spawnSync("scriptc-missing-nm", ["prog"], { encoding: "utf8" });
expect(() => nmOutput("prog", missing)).toThrow(/^nm prog: .*ENOENT/);
expect(() =>
nmOutput("prog", { status: null, signal: "SIGKILL", stdout: "", stderr: "" }),
).toThrow("nm prog: killed by SIGKILL");
expect(() =>
nmOutput("prog", { status: 1, signal: null, stdout: "", stderr: "nm: prog: no symbols\n" }),
).toThrow("nm prog: exited with status 1: nm: prog: no symbols");
});
test("the zero-cost gate recognizes dynamic-tier runtime symbols", () => {
expect(
dynamicTierSymbols([
"main",
"scr_dyn_release",
"scr_dyn_obj_drop_keys",
"scr_dyn_this_push_dyn",
"scr_js_load_ic",
"scr_dyn_obj_read",
"scr_dyn_obj_read",
]),
).toEqual(["scr_dyn_obj_read", "scr_js_load_ic"]);
});
test("the zero-cost gate reads function names from ELF and Mach-O nm output", () => {
const elf = [
"0000000000001000 0000000000000010 T scr_dyn_call",
"0000000000001010 0000000000000008 t scr_dyn_obj_get",
"0000000000002000 0000000000000004 D scr_dyn_table",
" U memcpy",
].join("\n");
expect(definedFunctionNames(elf, "linux")).toEqual(["scr_dyn_call", "scr_dyn_obj_get"]);
// Mach-O names carry a leading underscore, which would hide every
// dynamic-tier symbol from the prefix match.
const macho = [
"0000000100001000 T _scr_dyn_call",
"0000000100001010 t _scr_dyn_obj_get",
"0000000100002000 W __main",
"0000000100003000 s _scr_dyn_add.message",
" U _memcpy",
].join("\n");
expect(definedFunctionNames(macho, "darwin")).toEqual([
"scr_dyn_call",
"scr_dyn_obj_get",
"_main",
]);
expect(dynamicTierSymbols(definedFunctionNames(macho, "darwin"))).toEqual([
"scr_dyn_call",
"scr_dyn_obj_get",
]);
});
test("the zero-cost gate fails differing programs unless they match the signature", () => {
const ok = { ok: true };
const same = { ir: { identical: true }, exe: { identical: true } };
const differs = { ir: { identical: true }, exe: { identical: false } };
const verdict = (
artifacts: object,
allowed = false,
baseline: object = ok,
candidate: object = ok,
required = false,
) => programVerdict({ baseline, candidate, artifacts, allowed, required });
expect(verdict(same)).toEqual({ verdict: "identical", fails: false });
expect(verdict(differs)).toEqual({ verdict: "differs", fails: true });
expect(verdict(differs, true)).toEqual({ verdict: "allowed", fails: false });
const failed = { ok: false, error: "SC1090" };
expect(verdict({}, false, failed, failed)).toEqual({ verdict: "both-fail", fails: false });
// A benchmark or the self-hosted compiler that neither checkout builds
// was never compared.
expect(verdict({}, false, failed, failed, true)).toEqual({ verdict: "both-fail", fails: true });
expect(verdict({}, true, ok, failed)).toEqual({ verdict: "build-error", fails: true });
});