mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-05 08:13:06 +08:00
Let LLVM merge the comparisons of a three-way string comparator
This commit is contained in:
@@ -1601,8 +1601,8 @@ export function helperDefs(emitter: LlEmitter): string[] {
|
||||
// order; other differing words decide by their first differing byte,
|
||||
// and strings shorter than a word by their first byte. Otherwise a
|
||||
// proven-ASCII string on either side orders by bytes (scr_str_cmp), and
|
||||
// other pairs call scr_str_cmp_u16_bytes, which only reads the two
|
||||
// strings, so LLVM can merge repeated comparisons of one pair. Callers
|
||||
// other pairs call scr_str_cmp_u16_bytes. Both only read the two strings,
|
||||
// so LLVM can merge repeated comparisons of one pair. Callers
|
||||
// only test the sign of the result.
|
||||
if (emitter.decls.has(`declare i32 @scr_str_cmp_u16(ptr, ptr)`)) {
|
||||
const fn = "scr_str_cmp_u16";
|
||||
@@ -1732,7 +1732,7 @@ export function helperDefs(emitter: LlEmitter): string[] {
|
||||
` %anyascii = icmp slt i32 %cws, 0`,
|
||||
` br i1 %anyascii, label %memorder, label %units`,
|
||||
`memorder:`,
|
||||
` %m = call i32 @scr_str_cmp(ptr %a, ptr %b)`,
|
||||
` %m = call i32 @scr_str_cmp(ptr %a, ptr %b) nounwind willreturn memory(argmem: read)`,
|
||||
` ret i32 %m`,
|
||||
`units:`,
|
||||
` %c = call i32 @scr_str_cmp_u16_bytes(ptr %a, ptr %b) nounwind willreturn memory(argmem: read)`,
|
||||
|
||||
@@ -478,7 +478,9 @@ test("string comparisons decide identity, lengths and leading bytes before the r
|
||||
expect(units).toContain("%below = icmp ult i8 %bx, %by");
|
||||
expect(units).toContain("%tailable = and i1 %samelen, %short");
|
||||
expect(units).toContain("%anyascii = icmp slt i32 %cws, 0");
|
||||
expect(units).toContain("call i32 @scr_str_cmp(ptr %a, ptr %b)");
|
||||
expect(units).toContain(
|
||||
"call i32 @scr_str_cmp(ptr %a, ptr %b) nounwind willreturn memory(argmem: read)",
|
||||
);
|
||||
expect(units).toContain(
|
||||
"call i32 @scr_str_cmp_u16_bytes(ptr %a, ptr %b) nounwind willreturn memory(argmem: read)",
|
||||
);
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
/* String comparison emission: the runtime calls behind a relational string
|
||||
* operator only read the two strings, so LLVM merges the comparisons of one
|
||||
* pair. A three-way comparator (`a < b ? -1 : a > b ? 1 : 0`) then compares
|
||||
* its strings once, as a comparator that tests one operator does. */
|
||||
import { createHash } from "node:crypto";
|
||||
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { expect, test } from "vitest";
|
||||
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);
|
||||
const outDir = join(cacheDir, `string-compare-${key}`);
|
||||
mkdirSync(outDir, { recursive: true });
|
||||
const file = join(outDir, `${name}.ts`);
|
||||
writeFileSync(file, source);
|
||||
const result = await compile(file, {
|
||||
outPath: join(outDir, name),
|
||||
outDir,
|
||||
backend: "llvm",
|
||||
outputKind: "asm",
|
||||
});
|
||||
if (!result.ok)
|
||||
throw new Error(result.diagnostics.map((d) => `${d.code}: ${d.message}`).join("\n"));
|
||||
return readFileSync(result.artifact.path, "utf8");
|
||||
}
|
||||
|
||||
const program = (body: string): string => `function order(a: string, b: string): number {
|
||||
return ${body};
|
||||
}
|
||||
const words = process.argv.slice(2);
|
||||
words.sort(order);
|
||||
console.log(words.join(","));
|
||||
`;
|
||||
|
||||
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;
|
||||
|
||||
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);
|
||||
});
|
||||
Reference in New Issue
Block a user