merge: PR #669
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env bun
|
||||
|
||||
import * as fs from "node:fs";
|
||||
import * as path from "node:path";
|
||||
import { $ } from "bun";
|
||||
|
||||
@@ -10,6 +11,8 @@ interface NativeBuildVariant {
|
||||
|
||||
const repoRoot = path.join(import.meta.dir, "..");
|
||||
const isDryRun = process.argv.includes("--dry-run");
|
||||
const targetPlatform = Bun.env.TARGET_PLATFORM || process.platform;
|
||||
const targetArch = Bun.env.TARGET_ARCH || process.arch;
|
||||
const variantConfigs: Record<NativeBuildVariant["name"], NativeBuildVariant> = {
|
||||
baseline: {
|
||||
name: "baseline",
|
||||
@@ -34,6 +37,60 @@ function parseTargetVariants(): NativeBuildVariant[] {
|
||||
});
|
||||
}
|
||||
|
||||
function runCommand(command: string, args: string[]): string | null {
|
||||
try {
|
||||
const result = Bun.spawnSync([command, ...args], { stdout: "pipe", stderr: "pipe" });
|
||||
if (result.exitCode !== 0) return null;
|
||||
return result.stdout.toString("utf-8").trim();
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function detectHostAvx2Support(): boolean {
|
||||
if (process.arch !== "x64") return false;
|
||||
|
||||
if (process.platform === "linux") {
|
||||
try {
|
||||
const cpuInfo = fs.readFileSync("/proc/cpuinfo", "utf8");
|
||||
return /\bavx2\b/i.test(cpuInfo);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (process.platform === "darwin") {
|
||||
const leaf7 = runCommand("sysctl", ["-n", "machdep.cpu.leaf7_features"]);
|
||||
if (leaf7 && /\bAVX2\b/i.test(leaf7)) return true;
|
||||
const features = runCommand("sysctl", ["-n", "machdep.cpu.features"]);
|
||||
return Boolean(features && /\bAVX2\b/i.test(features));
|
||||
}
|
||||
|
||||
if (process.platform === "win32") {
|
||||
const output = runCommand("powershell.exe", [
|
||||
"-NoProfile",
|
||||
"-NonInteractive",
|
||||
"-Command",
|
||||
"[System.Runtime.Intrinsics.X86.Avx2]::IsSupported",
|
||||
]);
|
||||
return output?.toLowerCase() === "true";
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function resolveExpectedAddons(variants: NativeBuildVariant[]): string[] {
|
||||
if (variants.length > 0) {
|
||||
return variants.map(variant => `${targetPlatform}-${targetArch}-${variant.name}`);
|
||||
}
|
||||
|
||||
if (targetArch === "x64") {
|
||||
return [`${targetPlatform}-${targetArch}-${detectHostAvx2Support() ? "modern" : "baseline"}`];
|
||||
}
|
||||
|
||||
return [`${targetPlatform}-${targetArch}`];
|
||||
}
|
||||
|
||||
async function runNativeBuild(env: Record<string, string | undefined>, label: string): Promise<void> {
|
||||
if (isDryRun) {
|
||||
const variant = env.TARGET_VARIANT ? ` TARGET_VARIANT=${env.TARGET_VARIANT}` : "";
|
||||
@@ -46,10 +103,25 @@ async function runNativeBuild(env: Record<string, string | undefined>, label: st
|
||||
await $`bun --cwd=packages/natives run build`.cwd(repoRoot).env(env);
|
||||
}
|
||||
|
||||
async function verifyBuiltAddons(expectedAddons: string[]): Promise<void> {
|
||||
if (isDryRun) {
|
||||
console.log(`DRY RUN bun scripts/ci-release-verify-natives.ts PI_NATIVE_EXPECTED_ADDONS=${expectedAddons.join(" ")}`);
|
||||
return;
|
||||
}
|
||||
|
||||
await $`bun scripts/ci-release-verify-natives.ts`
|
||||
.cwd(repoRoot)
|
||||
.env({
|
||||
...Bun.env,
|
||||
PI_NATIVE_EXPECTED_ADDONS: expectedAddons.join(" "),
|
||||
});
|
||||
}
|
||||
|
||||
async function main(): Promise<void> {
|
||||
const variants = parseTargetVariants();
|
||||
if (variants.length === 0) {
|
||||
await runNativeBuild(Bun.env, "default");
|
||||
await verifyBuiltAddons(resolveExpectedAddons([]));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -63,6 +135,8 @@ async function main(): Promise<void> {
|
||||
variant.name,
|
||||
);
|
||||
}
|
||||
|
||||
await verifyBuiltAddons(resolveExpectedAddons(variants));
|
||||
}
|
||||
|
||||
await main();
|
||||
|
||||
@@ -4,8 +4,8 @@ import * as fs from "node:fs/promises";
|
||||
import * as path from "node:path";
|
||||
|
||||
const repoRoot = path.join(import.meta.dir, "..");
|
||||
const nativeDir = path.join(repoRoot, "packages", "natives", "native");
|
||||
const expectedAddons = [
|
||||
const nativeDir = path.resolve(Bun.env.PI_NATIVE_VERIFY_DIR ?? path.join(repoRoot, "packages", "natives", "native"));
|
||||
const defaultExpectedAddons = [
|
||||
"linux-x64-modern",
|
||||
"linux-x64-baseline",
|
||||
"linux-arm64",
|
||||
@@ -15,11 +15,58 @@ const expectedAddons = [
|
||||
"win32-x64-modern",
|
||||
"win32-x64-baseline",
|
||||
] as const;
|
||||
const x64LinuxIsaContracts = [
|
||||
{ addon: "linux-x64-baseline", filename: "pi_natives.linux-x64-baseline.node", label: "x86-64-v2" },
|
||||
{ addon: "linux-x64-modern", filename: "pi_natives.linux-x64-modern.node", label: "x86-64-v3" },
|
||||
] as const;
|
||||
const AVX512_REGISTER_PATTERN = /\bzmm\d+\b|\bk[0-7]\b/;
|
||||
|
||||
export function hasAvx512Markers(disassembly: string): boolean {
|
||||
return disassembly.split("\n").some(line => {
|
||||
if (AVX512_REGISTER_PATTERN.test(line)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const columns = line.split("\t");
|
||||
if (columns.length < 3) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const bytes = columns[1]?.trim() ?? "";
|
||||
const instruction = columns[2]?.trim() ?? "";
|
||||
return bytes.startsWith("62 ") && /^[a-z]/i.test(instruction) && !instruction.startsWith(".byte");
|
||||
});
|
||||
}
|
||||
|
||||
function disassemble(binaryPath: string): string {
|
||||
const objdumpPath = Bun.which("objdump");
|
||||
if (!objdumpPath) {
|
||||
throw new Error("objdump is required to verify linux-x64 native ISA contracts.");
|
||||
}
|
||||
|
||||
const result = Bun.spawnSync([objdumpPath, "-d", binaryPath], { stdout: "pipe", stderr: "pipe" });
|
||||
if (result.exitCode !== 0) {
|
||||
const stderr = result.stderr.toString("utf-8").trim();
|
||||
throw new Error(`objdump failed for ${binaryPath}${stderr ? `:\n${stderr}` : ""}`);
|
||||
}
|
||||
|
||||
return result.stdout.toString("utf-8");
|
||||
}
|
||||
|
||||
function resolveExpectedAddons(): string[] {
|
||||
const configured = (Bun.env.PI_NATIVE_EXPECTED_ADDONS ?? "").trim();
|
||||
if (!configured) {
|
||||
return [...defaultExpectedAddons];
|
||||
}
|
||||
|
||||
return configured.split(/[\s,]+/).filter(Boolean);
|
||||
}
|
||||
|
||||
async function main(): Promise<void> {
|
||||
const entries = await fs.readdir(nativeDir);
|
||||
const expectedAddons = resolveExpectedAddons();
|
||||
|
||||
console.log("Native addons downloaded:");
|
||||
console.log(`Native addons downloaded from ${nativeDir}:`);
|
||||
for (const entry of entries.sort((a, b) => a.localeCompare(b))) {
|
||||
console.log(` ${entry}`);
|
||||
}
|
||||
@@ -37,6 +84,30 @@ async function main(): Promise<void> {
|
||||
for (const platform of expectedAddons) {
|
||||
console.log(`OK pi_natives.${platform}.node`);
|
||||
}
|
||||
|
||||
const isaFailures: string[] = [];
|
||||
for (const contract of x64LinuxIsaContracts) {
|
||||
if (!expectedAddons.includes(contract.addon)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const binaryPath = path.join(nativeDir, contract.filename);
|
||||
const disassembly = disassemble(binaryPath);
|
||||
if (hasAvx512Markers(disassembly)) {
|
||||
isaFailures.push(`${contract.filename} contains AVX-512 markers; ${contract.label} artifacts must stay below x86-64-v4.`);
|
||||
continue;
|
||||
}
|
||||
console.log(`OK ${contract.filename} contains no AVX-512 markers`);
|
||||
}
|
||||
|
||||
if (isaFailures.length > 0) {
|
||||
for (const failure of isaFailures) {
|
||||
console.error(failure);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
await main();
|
||||
if (import.meta.main) {
|
||||
await main();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user