From 8f3c2c28d625015a93bb15acbc3cdb5fb1c20d9a Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 16:14:15 -0500 Subject: [PATCH] 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. --- tests/harness/string-compare-emission.test.ts | 35 ++++++++++++++----- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/tests/harness/string-compare-emission.test.ts b/tests/harness/string-compare-emission.test.ts index 2654f56ba..1a8fdddf6 100644 --- a/tests/harness/string-compare-emission.test.ts +++ b/tests/harness/string-compare-emission.test.ts @@ -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 { - const key = createHash("sha256").update(source).digest("hex").slice(0, 16); +async function emittedAsm(name: string, source: string, target: string): Promise { + 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); + }, +);