mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-01 07:33:12 +08:00
Put program constructors in .init_array on ELF (#2499)
This commit is contained in:
@@ -127,6 +127,9 @@ TargetOptions targetOptions(const Triple &TargetTriple) {
|
||||
// The Android API 26 runtime pack uses the NDK's emulated TLS ABI.
|
||||
// Program globals and runtime thread instances must use the same ABI.
|
||||
Options.EmulatedTLS = TargetTriple.isAndroid();
|
||||
// Program constructors go in .init_array, which the C runtime's startup
|
||||
// files run on ELF, as runtime units' constructors do (runtime-unit.cpp).
|
||||
Options.UseInitArray = TargetTriple.isOSBinFormatELF();
|
||||
if (ProfileLayout && TargetTriple.isOSBinFormatELF()) {
|
||||
Options.FunctionSections = true;
|
||||
Options.EnableMachineFunctionSplitter = true;
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
import { execFile } from "node:child_process";
|
||||
import { createRequire } from "node:module";
|
||||
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { dirname, join } from "node:path";
|
||||
import { promisify } from "node:util";
|
||||
import { afterEach, describe, expect, test } from "vitest";
|
||||
import { nativeCodegenTarget, nativeHelperForTarget } from "../src/backend/targets.js";
|
||||
|
||||
const execFileAsync = promisify(execFile);
|
||||
const target = nativeCodegenTarget();
|
||||
const helperSpec = target === null ? null : nativeHelperForTarget(target);
|
||||
const helper = (() => {
|
||||
if (helperSpec === null || process.platform === "win32") return null;
|
||||
try {
|
||||
const packageJson = createRequire(import.meta.url).resolve(
|
||||
`${helperSpec.packageName}/package.json`,
|
||||
);
|
||||
return join(dirname(packageJson), "bin", "scriptc-llvm-codegen");
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
})();
|
||||
const dirs: string[] = [];
|
||||
|
||||
afterEach(async () => {
|
||||
await Promise.all(dirs.splice(0).map((dir) => rm(dir, { recursive: true, force: true })));
|
||||
});
|
||||
|
||||
/** A module whose constructor sets the value main returns. Functions after
|
||||
* the constructor give split code generation and partitions something to
|
||||
* divide. */
|
||||
function program(): string {
|
||||
const lines = [
|
||||
"@value = internal global i32 0",
|
||||
"@llvm.global_ctors = appending global [1 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 65535, ptr @init, ptr null }]",
|
||||
"define internal void @init() {",
|
||||
// Volatile, so the optimizer cannot evaluate the constructor at compile time.
|
||||
" store volatile i32 42, ptr @value",
|
||||
" ret void",
|
||||
"}",
|
||||
];
|
||||
for (let index = 0; index < 8; index++)
|
||||
lines.push(
|
||||
`define internal i32 @step${index}(i32 %v) noinline {`,
|
||||
` %r = add i32 %v, ${index}`,
|
||||
" ret i32 %r",
|
||||
"}",
|
||||
);
|
||||
lines.push(
|
||||
"define i32 @main() {",
|
||||
" %value = load i32, ptr @value",
|
||||
...Array.from({ length: 8 }, (_, index) => ` %s${index} = call i32 @step${index}(i32 0)`),
|
||||
" ret i32 %value",
|
||||
"}",
|
||||
);
|
||||
return lines.join("\n") + "\n";
|
||||
}
|
||||
|
||||
/** The objects' section names, on ELF targets (null elsewhere). */
|
||||
async function sectionNames(objects: string[]): Promise<string[] | null> {
|
||||
if (process.platform !== "linux") return null;
|
||||
const names: string[] = [];
|
||||
for (const object of objects) {
|
||||
const { stdout } = await execFileAsync("readelf", ["-S", "-W", object]);
|
||||
for (const match of stdout.matchAll(/^\s*\[\s*\d+\]\s+(\S+)/gm)) names.push(match[1]!);
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
/** Links with lld when the C compiler driver can use it, as scriptc's Linux
|
||||
* links do: unlike the BFD linker's default script, lld does not run .ctors
|
||||
* entries from .init_array. */
|
||||
async function link(objects: string[], executable: string): Promise<void> {
|
||||
const cc = process.env["CC"] ?? "cc";
|
||||
try {
|
||||
await execFileAsync(cc, ["-fuse-ld=lld", ...objects, "-o", executable]);
|
||||
} catch {
|
||||
await execFileAsync(cc, [...objects, "-o", executable]);
|
||||
}
|
||||
}
|
||||
|
||||
async function exitCode(options: {
|
||||
outputs: number;
|
||||
split?: boolean;
|
||||
name: string;
|
||||
}): Promise<{ code: number; sections: string[] | null }> {
|
||||
const dir = await mkdtemp(join(tmpdir(), "scriptc-constructors-"));
|
||||
dirs.push(dir);
|
||||
const input = join(dir, `${options.name}.ll`);
|
||||
await writeFile(input, program());
|
||||
const outputs = Array.from({ length: options.outputs }, (_, index) =>
|
||||
join(dir, `${options.name}.${index}.o`),
|
||||
);
|
||||
await execFileAsync(helper!, [
|
||||
"emit",
|
||||
"--input",
|
||||
input,
|
||||
...outputs.flatMap((output) => ["--output", output]),
|
||||
"--filetype",
|
||||
"obj",
|
||||
"--target",
|
||||
target!.llvmTriple,
|
||||
"--opt-level",
|
||||
"2",
|
||||
"--relocation-model",
|
||||
"pic",
|
||||
"--diagnostic-format",
|
||||
"json",
|
||||
"--source-path",
|
||||
"/src/program.ts",
|
||||
...(options.split === true ? ["--split", "codegen"] : []),
|
||||
]);
|
||||
const sections = await sectionNames(outputs);
|
||||
const executable = join(dir, options.name);
|
||||
await link(outputs, executable);
|
||||
try {
|
||||
await execFileAsync(executable, []);
|
||||
return { code: 0, sections };
|
||||
} catch (error) {
|
||||
return { code: (error as { code: number }).code, sections };
|
||||
}
|
||||
}
|
||||
|
||||
/** The constructor ran, and on ELF it is in .init_array, never .ctors. */
|
||||
function expectConstructorRan(result: { code: number; sections: string[] | null }): void {
|
||||
expect(result.code).toBe(42);
|
||||
if (result.sections === null) return;
|
||||
expect(result.sections).toContain(".init_array");
|
||||
expect(result.sections).not.toContain(".ctors");
|
||||
}
|
||||
|
||||
describe.runIf(helper !== null)("program constructors", () => {
|
||||
test("a constructor runs before main in a single object", async () => {
|
||||
expectConstructorRan(await exitCode({ outputs: 1, name: "single" }));
|
||||
});
|
||||
|
||||
test("a constructor runs before main with split code generation", async () => {
|
||||
expectConstructorRan(await exitCode({ outputs: 3, split: true, name: "split" }));
|
||||
});
|
||||
|
||||
test("a constructor runs before main across program partitions", async () => {
|
||||
expectConstructorRan(await exitCode({ outputs: 3, name: "partitions" }));
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user