Excuse only reproduced differences as nondeterministic, and read vendor archive symbols

A program is nondeterministic only when every artifact that differs also
differs between two baseline builds; a deterministic difference in another
artifact still fails the gate. The runtime-only check now reads the symbols
of changed vendor archives as well as runtime objects.
This commit is contained in:
Chris Tate
2026-10-27 17:55:44 -05:00
parent 4d5127944e
commit 08fbecb7e7
3 changed files with 42 additions and 8 deletions
+14 -3
View File
@@ -523,6 +523,14 @@ export function runtimePackDiff(baseline, candidate) {
};
}
/** The changed runtime pack files whose symbols executables link: runtime
* objects and vendor archives. */
export function linkedRuntimeFiles(diff) {
return [...diff.changed, ...diff.added, ...diff.removed].filter(
(path) => path.endsWith(".o") || path.endsWith(".a"),
);
}
/** Defined functions of textual LLVM IR (llvm-dis output) as name → hash of
* the body. Attribute group and metadata numbers are dropped first, since
* an unrelated change elsewhere in the unit renumbers them. */
@@ -638,7 +646,9 @@ export function programLines(entry) {
* LLVM, is "runtime-only" when `runtimeOnly` says the changed runtime
* objects explain it (the gate checks the runtime packs and the symbols).
* A difference the baseline also shows when it builds the program again is
* "nondeterministic". Neither fails the gate unless `strict` is set. */
* "nondeterministic": `nondeterministic` lists the artifact kinds whose
* baseline rebuild differs, and every differing artifact must be among
* them. Neither fails the gate unless `strict` is set. */
export function programVerdict({
baseline,
candidate,
@@ -646,7 +656,7 @@ export function programVerdict({
allowed,
required = false,
runtimeOnly = false,
nondeterministic = false,
nondeterministic = [],
strict = false,
}) {
if (!baseline.ok || !candidate.ok) {
@@ -659,7 +669,8 @@ export function programVerdict({
if (allowed) return { verdict: "allowed", fails: false };
if (runtimeOnly && differing.every(([kind]) => kind === "exe"))
return { verdict: "runtime-only", fails: strict };
if (nondeterministic) return { verdict: "nondeterministic", fails: strict };
if (differing.every(([kind]) => nondeterministic.includes(kind)))
return { verdict: "nondeterministic", fails: strict };
return { verdict: "differs", fails: true };
}
+4 -3
View File
@@ -45,6 +45,7 @@ import {
DYNAMIC_WORKLOADS,
definedFunctionNames,
dynamicTierSymbols,
linkedRuntimeFiles,
llvmFunctionHashes,
median,
nmOutput,
@@ -502,7 +503,7 @@ function runtimeChanges() {
const objects = [...diff.changed, ...diff.added, ...diff.removed].filter((path) =>
path.endsWith(".o"),
);
for (const path of objects)
for (const path of linkedRuntimeFiles(diff))
for (const root of [trees.baseline, trees.candidate]) {
const file = join(packDir(root), path);
if (!existsSync(file)) continue;
@@ -808,7 +809,7 @@ await pool(
nondeterministic,
strict: values.strict,
});
let { verdict, fails } = judge(false);
let { verdict, fails } = judge([]);
// Build a difference the change cannot explain once more with the
// baseline: when the baseline's two builds differ in the same
// artifacts, the difference is the build's, not the change's.
@@ -822,7 +823,7 @@ await pool(
);
if (again.dir && !values.keep) rmSync(again.dir, { recursive: true, force: true });
}
if (recheck?.length) ({ verdict, fails } = judge(true));
if (recheck?.length) ({ verdict, fails } = judge(recheck));
}
const entry = {
id: program.id,
+24 -2
View File
@@ -10,6 +10,7 @@ import {
confinedToRuntime,
definedFunctionNames,
dynamicTierSymbols,
linkedRuntimeFiles,
llvmFunctionHashes,
mergeReports,
nmOutput,
@@ -222,14 +223,35 @@ test("the zero-cost gate classifies runtime-only and nondeterministic difference
});
// A changed runtime cannot explain different LLVM.
expect(verdict(llvmToo, { runtimeOnly: true })).toEqual({ verdict: "differs", fails: true });
expect(verdict(llvmToo, { nondeterministic: true })).toEqual({
expect(verdict(llvmToo, { nondeterministic: ["llvm", "exe"] })).toEqual({
verdict: "nondeterministic",
fails: false,
});
expect(verdict(llvmToo, { nondeterministic: true, strict: true })).toEqual({
expect(verdict(llvmToo, { nondeterministic: ["llvm", "exe"], strict: true })).toEqual({
verdict: "nondeterministic",
fails: true,
});
// A nondeterministic executable does not excuse a deterministic LLVM difference.
expect(verdict(llvmToo, { nondeterministic: ["exe"] })).toEqual({
verdict: "differs",
fails: true,
});
});
test("the zero-cost gate reads the symbols of changed runtime objects and vendor archives", () => {
expect(
linkedRuntimeFiles({
changed: [
"artifacts/release/runtime/default/scr_a.o",
"artifacts/release/runtime/default/scr_a.bc",
],
added: ["artifacts/vendor/libz/libscriptc-zlib.a"],
removed: [],
}),
).toEqual([
"artifacts/release/runtime/default/scr_a.o",
"artifacts/vendor/libz/libscriptc-zlib.a",
]);
});
test("the zero-cost gate names the runtime pack files and functions that changed", () => {