diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 20014c81e..a69e74e19 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -131,9 +131,6 @@ jobs: - uses: oven-sh/setup-bun@v2 with: bun-version: "1.3" - - uses: mlugg/setup-zig@v2 - with: - version: 0.15.2 - run: bun install --frozen-lockfile - name: Install cross-compilation toolchain if: matrix.target == 'aarch64-unknown-linux-gnu' @@ -184,9 +181,6 @@ jobs: - uses: taiki-e/install-action@v2 with: tool: nextest - - uses: mlugg/setup-zig@v2 - with: - version: 0.15.2 - name: Cache bun dependencies uses: actions/cache@v4 with: @@ -240,9 +234,6 @@ jobs: cache-on-failure: true save-if: ${{ github.event_name == 'push' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')) }} cache-workspace-crates: true - - uses: mlugg/setup-zig@v2 - with: - version: 0.15.2 - name: Cache bun dependencies uses: actions/cache@v4 with: @@ -373,8 +364,6 @@ jobs: merge-multiple: true run-id: ${{ steps.source.outputs.run-id }} github-token: ${{ secrets.GITHUB_TOKEN }} - - name: Verify native addons - run: bun run ci:release:verify-natives - name: Stage native addons for release run: cp packages/natives/native/*.node packages/coding-agent/binaries/ - name: Build release archives diff --git a/docs/natives-build-release-debugging.md b/docs/natives-build-release-debugging.md index 2d0c4d746..579051d2d 100644 --- a/docs/natives-build-release-debugging.md +++ b/docs/natives-build-release-debugging.md @@ -13,7 +13,6 @@ It follows the architecture terms from `docs/natives-architecture.md`: - `packages/natives/scripts/build-native.ts` - `packages/natives/scripts/embed-native.ts` - `packages/natives/scripts/gen-enums.ts` -- `packages/natives/scripts/zig-safe-wrapper.ts` - `packages/natives/package.json` - `packages/natives/native/index.js` - `packages/natives/native/loader-state.js` @@ -101,8 +100,6 @@ Runtime x64 candidate order also includes the unsuffixed default filename after - baseline: `-C target-cpu=x86-64-v2` - non-x64 / no variant: `-C target-cpu=native` - if already set, script does not override. -- `ZIG`: optional real Zig path used when the host Zig CPU contract wrapper is enabled. -- `PI_NATIVE_REAL_ZIG`, `PI_NATIVE_ZIG_TARGET`, `PI_NATIVE_ZIG_CPU`: set internally for `zig-safe-wrapper.ts` when building local x64 Linux/macOS artifacts with Zig available. ## Build state/lifecycle transitions @@ -114,7 +111,7 @@ Runtime x64 candidate order also includes the unsuffixed default filename after - x64 + `TARGET_VARIANT` → explicit variant; - x64 cross-build without `TARGET_VARIANT` → hard error; - x64 local build without override → detect host AVX2. -3. **CPU policy**: set `RUSTFLAGS` if allowed; optionally route Zig through `zig-safe-wrapper.ts` to avoid leaking newer host CPU instructions into x64 artifacts. +3. **CPU policy**: set `RUSTFLAGS` for the resolved variant unless the caller already provided one. 4. **Compile**: run napi-rs against `crates/pi-natives` into an isolated output directory. 5. **Locate artifact**: accept the canonical filename or a single napi-rs-generated `pi_natives.-*.node` candidate. 6. **Install**: copy/rename addon into `packages/natives/native`. diff --git a/package.json b/package.json index 0eebdf0c0..c7b2c2a69 100644 --- a/package.json +++ b/package.json @@ -111,7 +111,6 @@ "ci:test:full": "bun run test", "ci:test:smoke": "bun packages/coding-agent/src/cli.ts --version && bun packages/coding-agent/src/cli.ts --help && bun packages/coding-agent/src/cli.ts stats --help", "ci:test:install-methods": "bash scripts/install-tests/run-ci.sh", - "ci:release:verify-natives": "bun scripts/ci-release-verify-natives.ts", "ci:release:build-binaries": "bun scripts/ci-release-build-binaries.ts", "ci:release:build-archives": "bun scripts/ci-release-build-archives.ts", "ci:release:publish": "bun scripts/ci-release-publish.ts", diff --git a/packages/natives/CHANGELOG.md b/packages/natives/CHANGELOG.md index c17e843ae..166f10dc8 100644 --- a/packages/natives/CHANGELOG.md +++ b/packages/natives/CHANGELOG.md @@ -7,6 +7,11 @@ - Simplified native build profile suffix formatting without changing `local` and `ci` values - Changed the native build output behavior to avoid setting an isolated Cargo target directory automatically +### Removed + +- Removed the host Zig CPU contract wrapper (`zig-safe-wrapper.ts`) and its `ZIG`/`PI_NATIVE_REAL_ZIG`/`PI_NATIVE_ZIG_TARGET`/`PI_NATIVE_ZIG_CPU` env handling, since the `zlob` Rust dependency that required Zig is gone +- Removed the `ci-release-verify-natives` script and its AVX-512 marker scan from the release pipeline + ## [14.5.12] - 2026-04-30 ### Breaking Changes diff --git a/packages/natives/scripts/build-native.ts b/packages/natives/scripts/build-native.ts index a298c64e9..fdbb515b5 100644 --- a/packages/natives/scripts/build-native.ts +++ b/packages/natives/scripts/build-native.ts @@ -17,13 +17,6 @@ const isCrossCompile = Boolean(crossTarget) || targetPlatform !== process.platfo type X64Variant = "modern" | "baseline"; -interface SafeHostZigBuildConfig { - wrapperPath: string; - realZigPath: string; - target: string; - cpu: string; -} - let configuredVariant: X64Variant | undefined; if (configuredVariantRaw) { if (targetArch !== "x64") { @@ -46,36 +39,8 @@ function resolveEffectiveVariant(): X64Variant | null { const effectiveVariant = resolveEffectiveVariant(); const variantSuffix = effectiveVariant ? `-${effectiveVariant}` : ""; -function resolveLinuxHostZigTarget(): "x86_64-linux-gnu" | "x86_64-linux-musl" { - const report = process.report?.getReport?.() as { header?: { glibcVersionRuntime?: string } } | undefined; - return report?.header?.glibcVersionRuntime ? "x86_64-linux-gnu" : "x86_64-linux-musl"; -} - -function resolveSafeHostZigBuildConfig(): SafeHostZigBuildConfig | null { - if (isCrossCompile || targetArch !== "x64" || !effectiveVariant) { - return null; - } - - if (targetPlatform !== "linux" && targetPlatform !== "darwin") { - return null; - } - - const realZigPath = Bun.env.ZIG ?? Bun.which("zig"); - if (!realZigPath) { - return null; - } - - return { - wrapperPath: path.join(import.meta.dir, "zig-safe-wrapper.ts"), - realZigPath, - target: targetPlatform === "linux" ? resolveLinuxHostZigTarget() : "x86_64-macos", - cpu: effectiveVariant === "modern" ? "x86_64_v3" : "x86_64_v2", - }; -} - -// Keep host-built Zig dependencies on the same ISA floor as the Rust addon. -// zlob's build.rs defaults host builds to `native`, which can leak newer CPU -// instructions into release artifacts even when Rust itself targets x86-64-v2/v3. +// Pin Rust target-cpu so x64 baseline/modern variants get a reproducible ISA floor +// instead of inheriting the host CPU when RUSTFLAGS is unset. if (!isCrossCompile && !Bun.env.RUSTFLAGS) { if (effectiveVariant === "modern") { Bun.env.RUSTFLAGS = "-C target-cpu=x86-64-v3"; @@ -235,17 +200,6 @@ if (!napiBin) { throw new Error("Could not locate @napi-rs/cli `napi` binary in node_modules/.bin"); } -const safeHostZigBuildConfig = resolveSafeHostZigBuildConfig(); -if (safeHostZigBuildConfig) { - Bun.env.ZIG = safeHostZigBuildConfig.wrapperPath; - Bun.env.PI_NATIVE_REAL_ZIG = safeHostZigBuildConfig.realZigPath; - Bun.env.PI_NATIVE_ZIG_TARGET = safeHostZigBuildConfig.target; - Bun.env.PI_NATIVE_ZIG_CPU = safeHostZigBuildConfig.cpu; - console.log( - `Pinning host Zig CPU contract: ${safeHostZigBuildConfig.target} ${safeHostZigBuildConfig.cpu} (${effectiveVariant})`, - ); -} - try { const buildResult = await $`${napiBin} ${napiArgs}`.nothrow(); if (buildResult.exitCode !== 0) { diff --git a/packages/natives/scripts/zig-safe-wrapper.ts b/packages/natives/scripts/zig-safe-wrapper.ts deleted file mode 100755 index 0d33469d5..000000000 --- a/packages/natives/scripts/zig-safe-wrapper.ts +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env bun - -export function buildZigArgs(args: string[], overrides: { target?: string; cpu?: string }): string[] { - const nextArgs = [...args]; - if (nextArgs[0] !== "build") { - return nextArgs; - } - - const target = overrides.target?.trim(); - const cpu = overrides.cpu?.trim(); - - if (target && !nextArgs.some(arg => arg.startsWith("-Dtarget="))) { - nextArgs.push(`-Dtarget=${target}`); - } - if (cpu && !nextArgs.some(arg => arg.startsWith("-Dcpu="))) { - nextArgs.push(`-Dcpu=${cpu}`); - } - - return nextArgs; -} - -export async function main( - argv: string[] = process.argv.slice(2), - env: NodeJS.ProcessEnv = process.env, -): Promise { - const realZigPath = env.PI_NATIVE_REAL_ZIG; - if (!realZigPath) { - throw new Error("PI_NATIVE_REAL_ZIG is required when using zig-safe-wrapper.ts"); - } - - const child = Bun.spawn( - [realZigPath, ...buildZigArgs(argv, { target: env.PI_NATIVE_ZIG_TARGET, cpu: env.PI_NATIVE_ZIG_CPU })], - { - stdin: "inherit", - stdout: "inherit", - stderr: "inherit", - }, - ); - return await child.exited; -} - -if (import.meta.main) { - const exitCode = await main(); - process.exit(exitCode); -} diff --git a/packages/natives/test/build-safety.test.ts b/packages/natives/test/build-safety.test.ts deleted file mode 100644 index 59ff38956..000000000 --- a/packages/natives/test/build-safety.test.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { describe, expect, it } from "bun:test"; -import { hasAvx512Markers } from "../../../scripts/ci-release-verify-natives"; -import { buildZigArgs } from "../scripts/zig-safe-wrapper"; - -describe("native build safety", () => { - describe("buildZigArgs", () => { - it("pins host zig build to the requested cpu contract", () => { - expect( - buildZigArgs(["build", "-Doptimize=ReleaseFast"], { target: "x86_64-linux-gnu", cpu: "x86_64_v2" }), - ).toEqual(["build", "-Doptimize=ReleaseFast", "-Dtarget=x86_64-linux-gnu", "-Dcpu=x86_64_v2"]); - }); - - it("does not override explicit zig target or cpu flags", () => { - expect( - buildZigArgs(["build", "-Dtarget=x86_64-linux-gnu", "-Dcpu=x86_64_v3"], { - target: "x86_64-linux-gnu", - cpu: "x86_64_v2", - }), - ).toEqual(["build", "-Dtarget=x86_64-linux-gnu", "-Dcpu=x86_64_v3"]); - }); - - it("leaves non-build zig commands untouched", () => { - expect(buildZigArgs(["version"], { target: "x86_64-linux-gnu", cpu: "x86_64_v2" })).toEqual(["version"]); - }); - }); - - describe("hasAvx512Markers", () => { - it("flags AVX-512 register markers in disassembly", () => { - expect(hasAvx512Markers("60ba1df:\tc4 c1 78 92 c9\t\tkmovw %r9d,%k1")).toBe(true); - expect(hasAvx512Markers("123456:\t62 f1 7d 48 6f c0\tvmovdqa32 %zmm0,%zmm1")).toBe(true); - }); - - it("flags EVEX-encoded AVX-512 even without zmm or mask registers", () => { - expect(hasAvx512Markers("401000:\t62 f3 75 28 25 c2 96\tvpternlogd $0x96,%ymm2,%ymm1,%ymm0")).toBe(true); - }); - - it("ignores ordinary x86-64 disassembly", () => { - expect(hasAvx512Markers("401000:\t48 89 e5\t\tmov %rsp,%rbp")).toBe(false); - expect(hasAvx512Markers("401004:\tc5 f5 fe c2\tvpaddd %ymm2,%ymm1,%ymm0")).toBe(false); - expect(hasAvx512Markers("58b83d7:\t62 00 00 00 ")).toBe(false); - }); - }); -}); diff --git a/scripts/ci-build-native.ts b/scripts/ci-build-native.ts index 3dc36f251..b732e26ab 100644 --- a/scripts/ci-build-native.ts +++ b/scripts/ci-build-native.ts @@ -2,7 +2,6 @@ import * as path from "node:path"; import { $ } from "bun"; -import { detectHostAvx2Support } from "./host-detect" interface NativeBuildVariant { name: "baseline" | "modern"; @@ -11,8 +10,6 @@ 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 = { baseline: { name: "baseline", @@ -37,18 +34,6 @@ function parseTargetVariants(): NativeBuildVariant[] { }); } -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, label: string): Promise { if (isDryRun) { const variant = env.TARGET_VARIANT ? ` TARGET_VARIANT=${env.TARGET_VARIANT}` : ""; @@ -61,25 +46,10 @@ async function runNativeBuild(env: Record, label: st await $`bun --cwd=packages/natives run build`.cwd(repoRoot).env(env); } -async function verifyBuiltAddons(expectedAddons: string[]): Promise { - 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 { const variants = parseTargetVariants(); if (variants.length === 0) { await runNativeBuild(Bun.env, "default"); - await verifyBuiltAddons(resolveExpectedAddons([])); return; } @@ -93,8 +63,6 @@ async function main(): Promise { variant.name, ); } - - await verifyBuiltAddons(resolveExpectedAddons(variants)); } await main(); diff --git a/scripts/ci-release-verify-natives.ts b/scripts/ci-release-verify-natives.ts deleted file mode 100644 index a72659787..000000000 --- a/scripts/ci-release-verify-natives.ts +++ /dev/null @@ -1,113 +0,0 @@ -#!/usr/bin/env bun - -import * as fs from "node:fs/promises"; -import * as path from "node:path"; - -const repoRoot = path.join(import.meta.dir, ".."); -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", - "darwin-x64-modern", - "darwin-x64-baseline", - "darwin-arm64", - "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 { - const entries = await fs.readdir(nativeDir); - const expectedAddons = resolveExpectedAddons(); - - console.log(`Native addons downloaded from ${nativeDir}:`); - for (const entry of entries.sort((a, b) => a.localeCompare(b))) { - console.log(` ${entry}`); - } - console.log(); - console.log(`Expected addons: ${expectedAddons.join(", ")}`); - - const missingAddons = expectedAddons.filter((platform) => !entries.includes(`pi_natives.${platform}.node`)); - if (missingAddons.length > 0) { - for (const platform of missingAddons) { - console.error(`MISSING pi_natives.${platform}.node`); - } - process.exit(1); - } - - 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); - } -} - -if (import.meta.main) { - await main(); -}