Count comparator calls in AArch64 and Mach-O assembly

The string comparison emission test matched only x86-64 `call`
instructions to unprefixed symbols, so on AArch64 and macOS hosts it
counted no calls and failed its positive-count check. It now also matches
AArch64 `bl` and Mach-O's leading underscore, and it runs for the host and
for aarch64-linux-gnu and x86_64-macos assembly, so every host checks all
three spellings.
This commit is contained in:
Chris Tate
2026-10-24 16:14:15 -05:00
parent e7bced94fd
commit 8f3c2c28d6
+26 -9
View File
@@ -11,17 +11,23 @@ import { compile } from "@scriptc/compiler";
const repoRoot = join(import.meta.dirname, "../..");
const cacheDir = join(repoRoot, "node_modules/.cache/scriptc-tests");
async function emittedAsm(name: string, source: string): Promise<string> {
const key = createHash("sha256").update(source).digest("hex").slice(0, 16);
async function emittedAsm(name: string, source: string, target: string): Promise<string> {
const key = createHash("sha256").update(target).update(source).digest("hex").slice(0, 16);
const outDir = join(cacheDir, `string-compare-${key}`);
mkdirSync(outDir, { recursive: true });
const file = join(outDir, `${name}.ts`);
writeFileSync(file, source);
const saved = process.env["SCRIPTC_TARGET"];
if (target === "host") delete process.env["SCRIPTC_TARGET"];
else process.env["SCRIPTC_TARGET"] = target;
const result = await compile(file, {
outPath: join(outDir, name),
outDir,
backend: "llvm",
outputKind: "asm",
}).finally(() => {
if (saved === undefined) delete process.env["SCRIPTC_TARGET"];
else process.env["SCRIPTC_TARGET"] = saved;
});
if (!result.ok)
throw new Error(result.diagnostics.map((d) => `${d.code}: ${d.message}`).join("\n"));
@@ -36,12 +42,23 @@ words.sort(order);
console.log(words.join(","));
`;
// Calls to the comparison runtime: `call`/`callq` on x86-64 and `bl` on
// AArch64, with Mach-O's leading underscore on macOS.
const compareCalls = (asm: string): number =>
asm.match(/\bcall[a-z]*\s+(?:sc_str_cmp_u16|scr_str_cmp_u16_bytes|scr_str_cmp)\b/g)?.length ?? 0;
asm.match(
/\b(?:call[a-z]*|bl)\s+_?(?:sc_str_cmp_u16|scr_str_cmp_u16_bytes|scr_str_cmp)\b/g,
)?.length ?? 0;
test("a three-way string comparator compares its pair once", async () => {
const once = compareCalls(await emittedAsm("once", program("a < b ? -1 : 1")));
const both = compareCalls(await emittedAsm("both", program("a < b ? -1 : a > b ? 1 : 0")));
expect(once).toBeGreaterThan(0);
expect(both).toBe(once);
});
// The host's assembly, and two other targets' so that every host checks the
// x86-64, AArch64 and Mach-O spellings.
test.each(["host", "aarch64-linux-gnu", "x86_64-macos"])(
"a three-way string comparator compares its pair once (%s)",
async (target) => {
const once = compareCalls(await emittedAsm("once", program("a < b ? -1 : 1"), target));
const both = compareCalls(
await emittedAsm("both", program("a < b ? -1 : a > b ? 1 : 0"), target),
);
expect(once).toBeGreaterThan(0);
expect(both).toBe(once);
},
);