# verify-baseline-static Static ISA verifier for Bun baseline builds. Disassembles every instruction in `.text` and flags anything requiring CPU features beyond the baseline target. | Target | Baseline | Flags | | ------- | ---------------------------------- | ---------------------------------------------------------------------------- | | x86-64 | Nehalem (`-march=nehalem`) | AVX, AVX2, AVX-512, BMI1/2, FMA, AES-NI, PCLMULQDQ, ADX, SHA-NI, RDRAND, ... | | aarch64 | armv8-a+crc (`-march=armv8-a+crc`) | LSE atomics, SVE, DotProd, RCPC, JSCVT, RDM, non-hint PAC | Architecture and format (ELF/PE) are auto-detected from the binary header. Complements the emulator-based check in `scripts/verify-baseline.ts`: the emulator only catches instructions the test suite _executes_. This catches everything the compiler _emitted_. ## Usage ```sh cargo build --release --manifest-path scripts/verify-baseline-static/Cargo.toml ./scripts/verify-baseline-static/target/release/verify-baseline-static \ --binary path/to/bun-linux-x64-baseline/bun-profile \ --allowlist scripts/verify-baseline-static/allowlist-x64.txt ``` `--allowlist` can be repeated; entries merge (feature ceilings union, blanket-pass wins). Useful for layering an extra allowlist on top of the base one without editing it. Exit `0` = clean, `1` = violations, `2` = tool error. Use the `-profile` artifact. The stripped release binary has no `.symtab` (ELF) and no companion `.pdb` (PE) — every violation becomes `` and nothing in the allowlist matches. Windows auto-discovers `.pdb` if present, or pass `--pdb` explicitly. ## When it fails A new symbol showed up with post-baseline instructions. Two possibilities: 1. **It's runtime-dispatched** (CPUID / HWCAP-gated). Find the gate in the caller, add the symbol to the allowlist with a comment pointing at it. Group with related symbols. 2. **It's not gated.** That's a real bug — a `-march` flag leaked into a subbuild and the function will SIGILL on baseline hardware. Fix the compile flags for that translation unit. If you're not sure which: run the binary under `qemu-x86_64 -cpu Nehalem` (or `qemu-aarch64 -cpu cortex-a53`) and hit that code path. SIGILL = bug. ### Data-in-`.text` false positives The tool linear-sweeps every byte in `.text`. There's no general way to do better for x86: toolchains don't emit "this byte is data" markers the way ARM EABI's `$d` mapping symbols do, and code/data separation in x86 binaries is undecidable in general ([Schwarz & Debray 2002](https://www2.cs.arizona.edu/~debray/Publications/disasm.pdf)). MSVC inlines jump tables and small `static const` arrays into `.text` right after the function that uses them (LLVM puts them in `.rodata`, so ELF builds are typically clean). When those bytes form a valid instruction encoding, the decoder reports it. **Filtered automatically:** 3DNow!, SMM, Cyrix, VIA Padlock, RTM/TSX — ISA extensions no toolchain targeting x86-64 emits without explicit intrinsics. Their encodings (`0f xx` for the defunct extensions, `C7/C6 F8` for XBEGIN/XABORT) surface when a jump table's bytes happen to line up; the MSVC CRT strspn/strcspn/strpbrk switch tables are the observed source of the latter. **Not filtered (very rare):** a table whose bytes form a valid VEX/EVEX-prefixed encoding. Looks like a real AVX hit. Triage the same way: disassemble around the address; if preceded by `ret` + small-int byte soup instead of stack-frame setup, it's a table. Allowlist the symbol. ## What's deliberately ignored **x64:** - **ENDBR64 / CET_IBT** — NOP-compatible on pre-CET CPUs by design. - **TZCNT** — LLVM preloads the destination with operand-width so the REP-BSF fallback on pre-BMI1 CPUs gives identical results. (LZCNT is **not** ignored: `BSR` and `LZCNT` produce different results, and LLVM never emits LZCNT for `-march=nehalem`.) - **XGETBV** — every AVX-dispatch path calls this, always after `if (CPUID.OSXSAVE)`. A stray one would SIGILL at startup; the emulator test catches that trivially. **aarch64:** - **PACIASP / AUTIASP / BTI** — HINT-space; architecturally NOP on older CPUs. - **`$d`-marked data** — literal pools and inline strings in `.text` are skipped via ARM EABI mapping symbols. ## Symbol attribution | Binary | Primary source | Fallback | | ------ | --------------------------------------------------------------- | -------------------------------------------- | | ELF | `.symtab` | `.dynsym` | | PE | PDB DBI module stream (`S_*PROC32`, has real sizes) → `S_PUB32` | PDB section contributions → `` | The Windows fallback handles code with no per-function PDB record (stripped CRT objects, anonymized staticlib helpers). Section contributions are the linker-map data in structured form — they say which `.obj`/`.lib` every byte came from. Attribution is by library basename, which doesn't move when unrelated code shifts the link layout. PDB coverage for the same code can vary across linker versions — a function that gets an `S_LPROC32` record on one toolchain may fall through to `` on another. If a symbol flips between a mangled name and `` across CI runs, allowlist both forms. Rust v0 mangled names carry a crate-hash (`Cs[base62]_`) that changes across target triples and toolchain versions. The allowlist uses `` as a placeholder; the tool canonicalizes both sides before comparing. ## Allowlist format ``` # comment symbol_name # blanket pass — any feature allowed symbol_name [AVX, AVX2] # feature ceiling — only these allowed ``` A feature outside the brackets is a violation even for an allowlisted symbol. Widening the bracket is the explicit checkpoint for "did the gate get updated when the dependency did?" | File | Covers | | --------------------------- | ------------------------------------------------------------- | | `allowlist-x64.txt` | `linux-x64-baseline`, `linux-x64-musl-baseline` | | `allowlist-x64-windows.txt` | `windows-x64-baseline` (separate: MSVC mangling, CRT symbols) | | `allowlist-aarch64.txt` | `linux-aarch64`, `linux-aarch64-musl` | One allowlist covers both glibc and musl because the dispatch surface is architecture-specific, not libc-specific. Symbols that LTO inlined away on one libc show as STALE on the other — informational, not an error.