mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-01 07:33:12 +08:00
Mark emitted functions sanitize_address only in sanitized builds
The sanitized lane's -fsanitize=address link needs the attribute to instrument the emitted functions. Without a sanitizer pass it is not inert: LLVM does not shrink-wrap functions that carry it, so every call saved and restored all of a function's callee-saved registers on entry, including on paths that return before any call, and it blocks speculative loads. The LLVM helper already dropped it for builds that import runtime bitcode (speed); release builds kept it. - The new sanitizeAttributes option (default true for direct emission) is set from --sanitize wherever objectAudit is. - Without it and without a target CPU level, attribute group #0 holds a string attribute LLVM ignores, since a group cannot be empty.
This commit is contained in:
@@ -50,9 +50,11 @@ test.runIf(process.platform === "linux" && process.arch === "x64")(
|
||||
const expected = `${(await execFileAsync(process.execPath, [entry])).stdout}`;
|
||||
const baseline = await build([]);
|
||||
expect(baseline).not.toContain('"target-cpu"');
|
||||
// Only --sanitize builds mark the functions for ASan instrumentation.
|
||||
expect(baseline).not.toContain("sanitize_address");
|
||||
expect(await build(["--target-cpu=baseline"])).toBe(baseline);
|
||||
const v3 = await build(["--target-cpu=x86-64-v3"]);
|
||||
expect(v3).toMatch(/^attributes #0 = \{ sanitize_address "target-cpu"="x86-64-v3" \}$/m);
|
||||
expect(v3).toMatch(/^attributes #0 = \{ "target-cpu"="x86-64-v3" \}$/m);
|
||||
if (runsV3) expect((await execFileAsync(outPath)).stdout).toBe(expected);
|
||||
// The bootstrap's cached route builds without a level.
|
||||
expect(await build([], true)).toBe(baseline);
|
||||
|
||||
@@ -196,15 +196,17 @@ function readRange(dir: string, range: number): { result: ChunkResult; texts: st
|
||||
|
||||
/** The attribute groups of the emitted functions: #0 for every function,
|
||||
* #1 for coroutine bodies and #2 for out-of-line record clones. Each carries
|
||||
* the build's `--target-cpu` level. */
|
||||
* the build's `--target-cpu` level, and `sanitize_address` in sanitized
|
||||
* builds. A group needs one attribute: without either, #0 holds a string
|
||||
* attribute that LLVM ignores. */
|
||||
function functionAttributes(emitter: LlEmitter, hasNoInlineRecordClone: boolean): string[] {
|
||||
const cpu = targetCpuAttribute(emitter.targetCpu);
|
||||
const asan = emitter.sanitizeAttributes ? " sanitize_address" : "";
|
||||
const base = `${asan}${cpu}` || ` "scriptc-function"`;
|
||||
return [
|
||||
`attributes #0 = { sanitize_address${cpu} }`,
|
||||
...(emitter.usesCoroutines
|
||||
? [`attributes #1 = { sanitize_address presplitcoroutine${cpu} }`]
|
||||
: []),
|
||||
...(hasNoInlineRecordClone ? [`attributes #2 = { noinline sanitize_address${cpu} }`] : []),
|
||||
`attributes #0 = {${base} }`,
|
||||
...(emitter.usesCoroutines ? [`attributes #1 = {${asan} presplitcoroutine${cpu} }`] : []),
|
||||
...(hasNoInlineRecordClone ? [`attributes #2 = { noinline${asan}${cpu} }`] : []),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1179,9 +1181,9 @@ export function emitModuleParts(emitter: LlEmitter): string[] {
|
||||
);
|
||||
for (const line of emitter.inlineRcTail(flushedDecls)) out.push(line);
|
||||
out.push(
|
||||
// sanitize_address is inert under the plain pipeline; the sanitized
|
||||
// lane's -fsanitize=address link activates instrumentation over the
|
||||
// emitted functions too (the runtime TUs get theirs from clang).
|
||||
// The sanitized lane's -fsanitize=address link instruments the
|
||||
// functions marked sanitize_address (the runtime TUs get theirs from
|
||||
// clang).
|
||||
...functionAttributes(emitter, hasNoInlineRecordClone),
|
||||
``,
|
||||
);
|
||||
|
||||
@@ -211,6 +211,19 @@ test("a target CPU level reaches every function attribute group and nothing else
|
||||
expect(emitLlvmModule(module, { targetCpu: "baseline" })).toBe(plain);
|
||||
});
|
||||
|
||||
test("only sanitized builds mark the emitted functions sanitize_address", () => {
|
||||
const module = moduleFor([call()]);
|
||||
const sanitized = emitLlvmModule(module);
|
||||
expect(emitLlvmModule(module, { sanitizeAttributes: true })).toBe(sanitized);
|
||||
expect(sanitized).toMatch(/^attributes #0 = \{ sanitize_address \}$/m);
|
||||
const plain = emitLlvmModule(module, { sanitizeAttributes: false });
|
||||
expect(plain).not.toContain("sanitize_address");
|
||||
// A group needs an attribute: an ignored string attribute stands in.
|
||||
expect(plain).toMatch(/^attributes #0 = \{ "scriptc-function" \}$/m);
|
||||
const v3 = emitLlvmModule(module, { sanitizeAttributes: false, targetCpu: "x86-64-v3" });
|
||||
expect(v3).toMatch(/^attributes #0 = \{ "target-cpu"="x86-64-v3" \}$/m);
|
||||
});
|
||||
|
||||
test("inline RC inlines every fast path at speed and keeps box releases as calls in release", () => {
|
||||
for (const boxed of [false, true]) {
|
||||
const module = moduleFor(Array.from({ length: 4 }, call), { boxed });
|
||||
|
||||
@@ -511,6 +511,12 @@ export interface LlvmTargetOptions {
|
||||
inlineRc?: InlineRcMode;
|
||||
/** Define the inline RC fast-path helpers `noinline` (sanitized builds). */
|
||||
rcHelpersNoinline?: boolean;
|
||||
/** Mark every emitted function `sanitize_address`, which the sanitized
|
||||
* lane's `-fsanitize=address` link needs to instrument it. Without a
|
||||
* sanitizer pass the attribute still turns off shrink-wrapping and
|
||||
* speculative loads, so plain builds pass false. Defaults to true for
|
||||
* direct emission. */
|
||||
sanitizeAttributes?: boolean;
|
||||
/** Define `scr_detached_exit` so the Linux runtime hands the address-space
|
||||
* teardown after `process.exit` to a helper process (`--detached-exit`).
|
||||
* Executables only; the runtime's weak default keeps it off. */
|
||||
@@ -1269,6 +1275,8 @@ export class LlEmitter {
|
||||
readonly runtimeAbiMarker: boolean;
|
||||
readonly detachedExit: boolean;
|
||||
readonly targetCpu: TargetCpu | undefined;
|
||||
/** Emitted functions carry `sanitize_address` (LlvmTargetOptions). */
|
||||
readonly sanitizeAttributes: boolean;
|
||||
readonly wasmBoundsChecks: boolean;
|
||||
/** Interned string literals: UTF-8 text → { symbol, byte length } —
|
||||
* first-use order, the runtime ABI’s determinism discipline. */
|
||||
@@ -1971,6 +1979,7 @@ export class LlEmitter {
|
||||
this.runtimeAbiMarker = options.runtimeAbiMarker === true;
|
||||
this.detachedExit = options.detachedExit === true;
|
||||
this.targetCpu = options.targetCpu;
|
||||
this.sanitizeAttributes = options.sanitizeAttributes !== false;
|
||||
this.wasmBoundsChecks =
|
||||
options.wasmBoundsChecks === true ||
|
||||
wasmBoundsChecks({
|
||||
|
||||
@@ -216,8 +216,9 @@ export interface RegionHooks {
|
||||
classDefinitions(cls: IrClassDef, sizeOf: string): string[];
|
||||
}
|
||||
|
||||
/** Every emitted function/helper carries #0 = { sanitize_address } — see
|
||||
* the emitter header. */
|
||||
/** Every emitted function/helper carries attribute group #0: the build's
|
||||
* target CPU level, and `sanitize_address` in sanitized builds (see
|
||||
* emit-module.ts functionAttributes). */
|
||||
export const FN_ATTRS = "#0";
|
||||
|
||||
export { computeTraced } from "../cycle-analysis.js";
|
||||
|
||||
@@ -854,6 +854,7 @@ async function prepareExecutableInput(
|
||||
// the pending flag, since their link may not add an unwinder.
|
||||
unwindAbi: buildPlatform === "linux" && outputKind !== "obj",
|
||||
objectAudit: opts.sanitize === true,
|
||||
sanitizeAttributes: opts.sanitize === true,
|
||||
lowRegion: lowRegionTarget(buildPlatform, opts.sanitize === true),
|
||||
...(opts.sanitize === true ? { wasmBoundsChecks: true } : {}),
|
||||
...inlineRcOption(opts.optimization, opts.sanitize === true),
|
||||
@@ -925,6 +926,7 @@ async function prepareExecutableInput(
|
||||
runtimeAbiMarker: useRuntimePack,
|
||||
unwindAbi: buildPlatform === "linux",
|
||||
objectAudit: opts.sanitize === true,
|
||||
sanitizeAttributes: opts.sanitize === true,
|
||||
lowRegion: lowRegionTarget(buildPlatform, opts.sanitize === true),
|
||||
...(opts.sanitize === true ? { wasmBoundsChecks: true } : {}),
|
||||
...inlineRcOption(opts.optimization, opts.sanitize === true),
|
||||
@@ -2357,6 +2359,7 @@ async function compileLibraryTracked(
|
||||
pointerBits: buildPlatform === "wasi" ? 32 : 64,
|
||||
wasi: buildPlatform === "wasi",
|
||||
objectAudit: opts.sanitize === true,
|
||||
sanitizeAttributes: opts.sanitize === true,
|
||||
...inlineRcOption(profile.optimization, opts.sanitize === true),
|
||||
});
|
||||
timing("llvm-emit", {
|
||||
|
||||
@@ -431,6 +431,7 @@ export function prepareNativeExecutable(
|
||||
(outputKind === "exe" || outputKind === "llvm" || outputKind === "asm") &&
|
||||
toolchain.target.platform === "linux",
|
||||
objectAudit: options.sanitize === true,
|
||||
sanitizeAttributes: options.sanitize === true,
|
||||
lowRegion: lowRegionTarget(toolchain.target.platform, options.sanitize === true),
|
||||
functionWorkers: emitWorkers(),
|
||||
forkPrimitives: emitForkPrimitives(),
|
||||
@@ -545,6 +546,7 @@ export function prepareNativeLibrary(
|
||||
pointerBits: toolchain.target.pointerBits,
|
||||
wasi: toolchain.target.platform === "wasi",
|
||||
objectAudit: options.sanitize === true,
|
||||
sanitizeAttributes: options.sanitize === true,
|
||||
...inlineRcOption(profile.optimization, options.sanitize === true),
|
||||
});
|
||||
timing("llvm-emit");
|
||||
|
||||
@@ -50,7 +50,7 @@ console.log(sum);
|
||||
expect(definition(llvm, "sc_f_range")).not.toContain("@scr_coro_yield_f64(");
|
||||
expect(llvm).not.toContain("@scr_gen_take_out_f64(");
|
||||
expect(llvm).toContain("%ScrGenHead = type { i64, ptr, ptr, i32, %ScrGenSlot }");
|
||||
expect(llvm).toContain("attributes #1 = { sanitize_address presplitcoroutine }");
|
||||
expect(llvm).toContain("attributes #1 = { presplitcoroutine }");
|
||||
expect(definition(llvm, "sc_gs_range")).toContain("call ptr @scr_gen_new_stackless(");
|
||||
expect(llvm).not.toContain("@scr_gen_yield_f64(");
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user