mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-02 07:43:42 +08:00
With --cpu-dispatch, an x86-64 executable carries two copies of each program function: the baseline code and a copy compiled for x86-64-v3. main reads CPUID and XGETBV once. Calls between the program's functions go to the caller's copy. A function that is also named outside the copies (a function value, a call from main, a runtime callback or a coroutine body) keeps a dispatcher under its own name, which tests the level and tail-calls one copy. Functions without such a use get no dispatcher, so their copies have the callers the original had and LLVM inlines them the same way. The executable still runs on any x86-64 CPU. SCRIPTC_CPU_LEVEL caps the level at run time. The option is off by default, applies to executables without a --target-cpu level, and is part of the executable cache key. bench:runtime takes --baseline-cpu-dispatch and --candidate-cpu-dispatch, and the LLVM differential suite runs each program at levels 0 and 3 with SCRIPTC_CPU_DISPATCH=1.
120 lines
4.2 KiB
TypeScript
120 lines
4.2 KiB
TypeScript
import { describe, expect, test } from "vitest";
|
|
import { resolveOutputOptions, type OutputOptionValues } from "../src/output-options.js";
|
|
|
|
const BASE: OutputOptionValues = {
|
|
emitIr: false,
|
|
keepLlvm: true,
|
|
sanitize: false,
|
|
};
|
|
|
|
describe("output option compatibility", () => {
|
|
test.each([
|
|
[undefined, undefined, "exe", "llvm"],
|
|
["exe", "llvm", "exe", "llvm"],
|
|
["ir", undefined, "ir", undefined],
|
|
["llvm", undefined, "llvm", "llvm"],
|
|
["llvm", "llvm", "llvm", "llvm"],
|
|
["asm", undefined, "asm", "llvm"],
|
|
["obj", "llvm", "obj", "llvm"],
|
|
] as const)("accepts --emit=%s --backend=%s", (emit, backend, outputKind, expectedBackend) => {
|
|
const result = resolveOutputOptions("build", {
|
|
...BASE,
|
|
...(emit === undefined ? {} : { emit }),
|
|
...(backend === undefined ? {} : { backend }),
|
|
});
|
|
expect(result).toMatchObject({
|
|
ok: true,
|
|
outputKind,
|
|
...(expectedBackend === undefined ? {} : { backend: expectedBackend }),
|
|
});
|
|
});
|
|
|
|
test.each([
|
|
[{ emit: "wat" }, /unknown emit kind/],
|
|
[{ emit: "asm", backend: "c" }, /unknown backend/],
|
|
[{ emit: "obj", backend: "c" }, /unknown backend/],
|
|
[{ emit: "c" }, /unknown emit kind/],
|
|
[{ emit: "llvm", backend: "c" }, /unknown backend/],
|
|
[{ emit: "ir", keepLlvm: false }, /no-keep-llvm/],
|
|
[{ emit: "llvm", sanitize: true }, /sanitize/],
|
|
[{ emit: "llvm", optimization: "dev" }, /optimization/],
|
|
[{ emit: "ir", backend: "llvm" }, /before backend selection/],
|
|
[{ emit: "llvm", emitIr: true }, /emit-ir/],
|
|
[{ emit: "ir", emitIr: true }, /same output/],
|
|
[{ backend: "wat" }, /unknown backend/],
|
|
[{ emit: "obj", windowsSubsystem: "gui" }, /windows-subsystem/],
|
|
[{ emit: "llvm", windowsSubsystem: "gui" }, /windows-subsystem/],
|
|
[{ emit: "obj", strip: true }, /strip/],
|
|
[{ emit: "llvm", strip: true }, /strip/],
|
|
[{ emit: "obj", detachedExit: true }, /detached-exit/],
|
|
[{ emit: "llvm", detachedExit: true }, /detached-exit/],
|
|
[{ emit: "asm", cpuDispatch: true }, /cpu-dispatch/],
|
|
[{ emit: "obj", profileGenerate: "/tmp/%p.profraw" }, /profile-generate/],
|
|
[{ emit: "llvm", profileUse: "/tmp/a.profdata" }, /profile-use/],
|
|
[{ profileGenerate: "/tmp/%p.profraw", profileUse: "/tmp/a.profdata" }, /cannot be combined/],
|
|
] as const)("rejects %j", (override, message) => {
|
|
const result = resolveOutputOptions("build", { ...BASE, ...override });
|
|
expect(result).toEqual({ ok: false, message: expect.stringMatching(message) });
|
|
});
|
|
|
|
test.each(["ir", "llvm"])("run rejects --emit=%s", (emit) => {
|
|
expect(resolveOutputOptions("run", { ...BASE, emit })).toEqual({
|
|
ok: false,
|
|
message: "scriptc run requires --emit=exe",
|
|
});
|
|
});
|
|
|
|
test.each(["asm", "obj"])("run rejects --emit=%s", (emit) => {
|
|
expect(resolveOutputOptions("run", { ...BASE, emit })).toEqual({
|
|
ok: false,
|
|
message: "scriptc run requires --emit=exe",
|
|
});
|
|
});
|
|
|
|
test.each(["asm", "obj"])(
|
|
"native outputs accept optimization and sanitizer for compiler-level validation",
|
|
(emit) => {
|
|
expect(
|
|
resolveOutputOptions("build", {
|
|
...BASE,
|
|
emit,
|
|
optimization: "dev",
|
|
sanitize: true,
|
|
}),
|
|
).toMatchObject({ ok: true, outputKind: emit, backend: "llvm" });
|
|
},
|
|
);
|
|
|
|
test("the deprecated alias remains additive for executable builds", () => {
|
|
expect(resolveOutputOptions("build", { ...BASE, emitIr: true })).toMatchObject({
|
|
ok: true,
|
|
outputKind: "exe",
|
|
emitIr: true,
|
|
deprecateEmitIr: true,
|
|
});
|
|
});
|
|
|
|
test("source outputs retain frontend-affecting FFI manifests", () => {
|
|
expect(
|
|
resolveOutputOptions("build", { ...BASE, emit: "llvm", ffi: "native.json" }),
|
|
).toMatchObject({
|
|
ok: true,
|
|
outputKind: "llvm",
|
|
});
|
|
});
|
|
|
|
test("executable outputs accept a Windows subsystem selection", () => {
|
|
expect(resolveOutputOptions("build", { ...BASE, windowsSubsystem: "gui" })).toMatchObject({
|
|
ok: true,
|
|
outputKind: "exe",
|
|
});
|
|
});
|
|
|
|
test("executable outputs accept stripping", () => {
|
|
expect(resolveOutputOptions("build", { ...BASE, strip: true })).toMatchObject({
|
|
ok: true,
|
|
outputKind: "exe",
|
|
});
|
|
});
|
|
});
|