# G-172 Distributed binaries declare a CPU floor - no shipped artifact is tuned to its build host Status: proposed Scope: src/buildsuites/suite_tcl90/build905.zig and src/buildsuites/suite_tcl86/build86.zig (flagless target/cpu resolution defaults); src/tools/punkzip/build.zig and src/tools/punkres/build.zig (distributed build-path tools); src/buildsuites/suite_tcl90/tools/family_artifacts.tcl (artifact record emission - the recorded floor field); punkbin artifact repo (external c:/repo/jn/punkbin - win32-x86_64/*.toml sidecars, defaults.txt curation); bin/ (the shipped zig-built exes); src/scriptapps/bin/punk-runtime.* (floor reporting on fetch, floor-vs-host verdict on use/run); scriptlib/developer/ (cpu-floor audit tool) Goal: Every binary this project distributes runs on any CPU meeting a declared instruction-set floor - the floor is a property of the build recipe rather than of whichever machine happened to run it, it travels with the artifact in its metadata record, and neither a build nor a publication can silently ship host-tuned code. Acceptance: A flagless build of each distributed-artifact recipe (suite_tcl90, suite_tcl86, punkzip, punkres) on an AVX-512-capable host emits zero instructions above the declared floor - verified by an audit tool that disassembles the produced binaries and reports out-of-floor instructions, carrying the current AVX-512 regression as its fixture; `-Dcpu=native` still yields a host-tuned local build, proving the floor is a default and not a restriction; each published artifact's metadata record states the floor it was built to and punk-runtime surfaces it; and selecting or launching a runtime whose recorded floor the local CPU does not meet produces a named diagnosis instead of a silent 0xC000001D, while fetching for any platform stays ungated. ## Context Reported 2026-08-06: on a freshly pulled checkout, `bin/punk-runtime.cmd fetch` with no runtime name retrieved the curated default `tclsh9.0.5-punk-r2.exe`, which exited immediately with no prompt, no error and no output. Two other runtimes fetched onto the same machine (`tclsh902z.exe`, `tclsfe-x64.exe`) started normally. The failure was initially read as a Windows-version difference (25H2 works, 23H2 does not). It is not an OS difference. Disassembling `.text` of the artifacts: | artifact | zmm sites | ymm sites | built by | |---|---|---|---| | `tclsh9.0.5-punk-r2.exe` | 2756 | 617 | zig 0.16.0, ReleaseFast | | `tclsh9.0.5-r2.exe` (plain) | 2756 | 617 | zig 0.16.0, ReleaseFast | | `tclsh902z.exe` | 0 | 0 | BAWT / MSYS2 mingw GCC | | `tclsfe-x64.exe` | 0 | 0 | apnadkarni MSVC | The 9.0.5 binaries carry real EVEX-encoded AVX-512 - `vmovdqu64`, `vpscatterqq`, `vpermt2q`, `vpmovm2q`, `vptestnmq`, `vshufi64x2` - with the first site at RVA 0x2360, the very front of `.text`. Tcl has no runtime CPU dispatch, so none of it is guarded. On a CPU without AVX-512 the first one raises #UD and the process is killed with STATUS_ILLEGAL_INSTRUCTION (`0xC000001D`, errorlevel -1073741795) before Tcl writes a byte. Confirmed against the reporting fleet - the split is exactly the AVX-512 line: | host | CPU | uarch | AVX-512 | 9.0.5 runtime | |---|---|---|---|---| | is-vmhost-1 (Win11 23H2) | TR PRO 3955WX | Zen 2 | no | dies, errorlevel -1073741795 | | jcross1 (Win11) | Ryzen 5 7600X | Zen 4 | yes | works | | build/dev box (Win11 25H2) | TR PRO 9955WX | Zen 5 | yes | works | Cause: the recipes called `b.standardTargetOptions(.{})` and no build path passed `-Dtarget`/`-Dcpu`. With no flag, zig resolves cpu_model `determined_by_arch_os` by NATIVE detection; on a Zen 5 host that is `znver5`, and `ReleaseFast` then auto-vectorises freely. Measured with the bundled 0.16.0-dev zig on an auto-vectorisable C loop: flagless -> `znver5`, zmm=805; `-Dcpu=native` -> `znver5`, zmm=805; `-Dtarget=x86_64-windows` -> `x86_64`, zmm=0; `-Dcpu=baseline` -> `x86_64`, zmm=0; `-Dcpu=x86_64_v2` -> `x86_64_v2`, zmm=0. The class is wider than the runtime family. Every zig-built binary the project distributes inherits its build host's instruction set: - all six published `tclsh9.0.5-*` artifacts (punk + plain, r1 + r2, both `-bi-` variants): 2756 zmm each - the whole family, so there is no 9.0.5 fallback to fetch instead; - `bin/punkzip.exe` 1724 zmm, `bin/punkres.exe` 1423 zmm - both build-path tools, so a `bake` on a non-AVX-512 machine dies too; - `bin/punk905.exe`, `punk9-dev.exe`, `punk9_beta.exe`, `punk9bi_beta.exe`, `punkdeclare.exe`, `punkfiledemo.exe`: 2756 zmm; - an older generation (`punk901*.exe`, `punk9.exe`, `xcritcl.exe`, `tclsh90s*.exe`, `mkzipfix.exe`) carries ~4500 ymm sites and no AVX-512 - AVX2-only, from an earlier build host. Same defect, milder floor. Two things make this worse than a portability nit. The artifact record already carries `toolchain`, `optimize` and six source-checkout digests but says nothing about the instruction set, so the one fact that determines whether the binary can execute is the one fact not recorded. And punkbin `defaults.txt` points `win32-x86_64` at `tclsh9.0.5-punk-r2.exe`, so a flagless fetch on a fresh checkout hands every non-AVX-512 machine a dead default runtime - the project's first contact with a new user fails with no message at all. ## Approach 1. Recipe default, not a build-command convention. `standardTargetOptions` takes `.{ .default_target = .{ .cpu_model = .baseline } }`, so a flagless build is portable and `-Dcpu=native` is the opt-in for a host-tuned local build. "Remember to pass `-Dcpu=` when publishing" is precisely what failed silently for six artifacts and four recipes; the default is what publishes. (Applied 2026-08-06 to all four recipes - see Progress.) 2. Floor choice: `baseline` (x86-64 v1) for the x86_64 family (user decision 2026-08-06). For a Tcl interpreter the codegen delta against v2/v3 is negligible against the cost of the default runtime failing to start. The win32-ix86 lane (G-130) makes the same choice on its own axis. 3. Record the floor in the artifact metadata: `[provenance]` gains the resolved cpu model and the floor the recipe declared, emitted by `family_artifacts.tcl` alongside `toolchain`/`optimize`, and carried in both the embedded record and the sidecar toml. 4. Audit tool in `scriptlib/developer/` (the advisory-tool tier): disassemble a binary, classify instructions against a named floor, report anything above it. The current AVX-512 artifacts are its fixture - the tool must flag them and must pass a rebuilt one. This is what makes the acceptance criterion measurable rather than a promise about build habits. 5. punk-runtime surfaces the floor, gating only where execution is implied. `fetch` stays ungated and reports the floor as information - fetching for another platform (`-platform

`) is a normal cross-machine workflow and a local-CPU verdict would be meaningless there (user point 2026-08-06). The actionable moment is selection and launch: `use` and `run` are already the local-only actions (`run` explicitly takes no `-platform` because "foreign binaries are not runnable here"), so that is where a floor the host does not meet becomes a named diagnosis. `list` annotates rows the local CPU cannot run. 6. Republication is IN-SITU replacement (user decision 2026-08-06): rebuilt binaries replace the unsuitable artifacts under their existing names and revisions, with sidecar tomls and `sha1sums.txt` regenerated to match. Artifact immutability remains the long-term intention but is explicitly violable during alpha - every consuming system is in-house, there are no third-party users, and leaving known-dead binaries fetchable is the worse outcome. No `defaults.txt` repoint is needed (it already names `tclsh9.0.5-punk-r2.exe`). Replacement may proceed as soon as a rebuilt binary passes the zmm audit; if the floor field (Approach 3) is not yet in the record schema, a second in-situ record refresh adds it later - cheap under the same dispensation. ## Alternatives considered - Pass `-Dcpu=baseline` at publish time only, leaving recipes native by default - rejected: it is the same unenforced convention that produced the defect, and it makes a developer's local build and the published artifact differ in a way nothing checks. - `x86_64_v2` or `x86_64_v3` floor - deferred, not rejected. v2 (SSE4.2/POPCNT, 2008+) excludes nothing realistically in service; v3 (AVX2/BMI2, 2013+) excludes Intel N-series/Pentium Silver, which have no AVX at all. Revisit per artifact class if a measured win justifies it; the recipe already accepts `-Dcpu=`. - Runtime CPU dispatch in the hot paths - rejected as out of proportion: Tcl upstream has none, and the goal is that a shipped binary starts everywhere, not that it is optimal everywhere. - Detect and report at first run instead of building portably - rejected: the failure is #UD before `main`, so there is no point at which our own code could report anything. ## Notes - Related: G-105 - cross-target builds parameterize the same recipe's target; this goal is the CPU axis of that surface. Whichever lands first, the other inherits the resolved default_target shape. - Related: G-130 - the win32-ix86 lane consumes the floor decision on its own axis (an i686 baseline floor is the same class of choice). - Related: G-116, G-108 - additional products of the same recipe (tcltls bi-family battery, the debug `-dbg` tier); both inherit the pin. - Related: G-142 - curated listing manifests are where a per-artifact floor becomes visible before download. - Related: G-147 - fetch-side sibling; a floor field joins the facts a revision check reads. - Related: G-137 - punkres RT_VERSION stamping consumes `bin/punkres.exe`, itself one of the affected binaries. - Related (archived): G-117 (self-describing runtimes - see goals/archive/G-117-self-describing-runtimes.md) and G-123 (runtime tiers, schema v2 - see goals/archive/G-123-thirdparty-runtime-tiers.md) - the artifact record schema this goal extends with the floor field. - Related (archived): G-126 (punkzip accelerator), G-128 (punkres stamper) - the two distributed tools whose recipes carry the same defect. - Related (archived): G-102 - the suite zig-version pin lives in the same comptime block as the defect. - Overlap survey 2026-08-06 (`goals_xref.tcl paths src/buildsuites/suite_tcl90 bin/punkzip.exe bin/punkres.exe`): surveyed and judged not related in substance - G-110 (shared-lib extraction cache), G-131 (boot payload autodetection), G-141, G-157, G-158 (bin/ sibling-file matches only). - Zig version gate (corrected 2026-08-06 - an earlier note here claimed the gate blocks rebuilding on this box; it does not): the recipes' SemVer gate does reject the stray prerelease copy under `bin/tools/zig-0.16.0-dev.254+6dd0270a1/` (a prerelease sorts below its release), but that copy is not the pinned toolchain. The `zigpin` record (`sources.config`) points at `bin/tools/zig-x86_64-windows-0.16.0/zig.exe`, a released 0.16.0 that is present, reports `0.16.0`, and passes the gate; `suite.tcl` and the README both resolve to it by default. The removed-zig claim arose from missing the `zig-x86_64-windows-*` naming family in `bin/tools/` and back-inferring a removal from the artifact records. Rejecting a 0.16.0-dev snapshot is arguably correct behavior besides - it predates the 0.16 API the recipes are written against - so nothing here blocks or needs fixing. - Verification note: `llvm-objdump -d --section=.text ` counting `zmm[0-9]` operands is the quick manual check used throughout the Context measurements. Linear disassembly of a non-AVX-512 binary yields ~3 false positives from data misparse; real usage is in the hundreds-to-thousands, so the distinction is unambiguous at a glance but the audit tool (Approach 4) should decode properly rather than grep. ## Progress - 2026-08-06: recipe pins applied (Approach 1+2) - `default_target` with `cpu_model = .baseline` in `src/buildsuites/suite_tcl90/build905.zig`, `src/buildsuites/suite_tcl86/build86.zig`, `src/tools/punkzip/build.zig` and `src/tools/punkres/build.zig`, each carrying the finding in-comment. Verified by `zig ast-check` on all four; the mechanism verified separately with the same toolchain and optimize level on an equivalent recipe (flagless -> baseline/zmm=0, `-Dcpu=native` -> znver5/zmm=805). A full suite build was NOT run in that session - recorded at the time as gate-blocked, corrected in the next entry (the pinned toolchain passes the gate). The `b.graph.host` target sites elsewhere in these recipes are build-time helper tools that are never distributed and are correct as native. - 2026-08-06 (correction, later session): the gate-blocker claim is wrong - the pinned toolchain `bin/tools/zig-x86_64-windows-0.16.0/zig.exe` (the `zigpin` record) is a released 0.16.0, present on this box and passing the recipes' gate; only the stray dev copy is rejected. Notes bullet rewritten accordingly. Nothing blocks the rebuild here. - 2026-08-06: user decision - republication is in-situ replacement under the existing artifact names/revisions (Approach 6 rewritten); punkbin immutability is deliberately violable during alpha with zero third-party consumers. Disposition of the r1-generation artifacts (replace vs drop) remains a pending curation call. - 2026-08-06 (later session): Approach 6 executed for the r2 generation + tools. Full `zig build bootstrap` with the pinned released 0.16.0 rebuilt the family at the baseline floor: all three r2 exes audit zmm=0 ymm=0 (was 2756/617), family_check self-contained PASS, punk-r2 build_id unchanged. punkzip 2.4.0 / punkres 0.3.1 rebuilt (zmm 1724/1423 -> 0), own suites pass; local bin/ copies replaced. Core test-gate: two consecutive PASS runs against the dispositioned baseline (69552 run / 8 failed, identical totals both runs; a first attempt failed once on interp-36.7, a bgerror timing race - passed on both reruns; one intermediate run hung 4h inside httpProxy.test "ThreadLevel 1", the socket-flake class - killed and rerun). lib tier audited clean (tcllibc.dll zmm=0; the critcl -target pin already protected it). Published in-situ: punkbin ab5611d (3 exes + 3 schema-2 tomls + 2 versioned tool exes + both sha1sums.txt), pushed to origin/master. Record-honesty residual: the published tomls' [tests] lines carry the emission-time (July) summaries; the fresh shipped-codegen gate evidence lives in _build testreports - fold a [tests] refresh into the Approach 3 record work. - Remaining for acceptance: Approach 3 (floor in the artifact record, folding in a [tests]-freshness refresh of the replaced tomls), 4 (audit tool + fixture), 5 (punk-runtime surfacing and the use/run verdict). Behind those: r1-generation disposition (replace vs drop) and the bin/ punk9* kit rebake with the new runtime (punkproject version-gate discipline applies).