# G-172 Distributed binaries declare a CPU floor - no shipped artifact is tuned to its build host
Status: achieved 2026-08-07
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).
- 2026-08-07: goal activated at user direction (overlap survey re-run via
`goals_xref.tcl score G-172` - no goals drafted in the interval; the existing
Related: notes cover every overlapping pair). Approaches 3, 4, 5 CODE landed:
- Approach 3 (record floor field): `src/buildsuites/suite_tcl90/build905.zig`
declares `const cpu_floor = "baseline";` beside the default_target pin and
writes `cpu_floor` + `cpu_model` (=`target.result.cpu.model.name`, resolving
to "x86_64" for the default build, "znver5" for -Dcpu=native) into the
embedded record's [provenance], after toolchain/optimize (ast-check PASS;
17 format placeholders = 17 args). `tools/family_artifacts.tcl` derives both
from the embedded record (G-117 single-source-of-truth, like
origin/packager) and emits them into the sidecar [provenance], failing if
absent. `tools/family_check.tcl` asserts embed_cpu_floor/embed_cpu_model
non-empty. suite_tcl86 has no family machinery (G-158 territory) - only the
build86.zig cpu_model pin (already in place). NOT yet built/published - the
floor field reaches published artifacts at the next republication.
- Approach 4 (audit tool): `scriptlib/developer/cpufloor_audit.tcl` (plain
tclsh, no deps) disassembles a binary's .text via a located objdump
(llvm-objdump preferred, GNU objdump fallback) or audits a pre-disassembled
excerpt (`-from-dis`), classifies by AVX register width (zmm=v4, ymm=v3;
baseline/v2 forbid both, v3 forbids zmm, v4 allows all - SSE4.2 not
width-detectable so baseline and v2 share a ceiling), reports out-of-floor
count + first RVA + sample mnemonics, exit 0 PASS / 1 FAIL. Selftest
(`-selftest`) runs 12 fixture/floor cases - all pass. Fixtures are REAL
disassembly excerpts in `scriptlib/developer/cpufloor_fixtures/` (avx512,
avx2, clean) generated from a temp-built probe on this Zen 5 host (no
binary committed). Verified against real binaries: rebuilt bin/punkzip.exe
PASS (zmm=0 ymm=0); a -Dcpu=native probe FAILs at baseline (123 zmm, 151
out-of-floor, first RVA 140002952) and PASSes at v4. The acceptance's
measurable instrument is in place.
- Approach 5 (punk-runtime surfacing): both payload twins updated and
re-wrapped. `src/scriptapps/bin/punk-runtime.ps1` adds
`Get-PunkHostCpuLevel` (IsProcessorFeaturePresent via Add-Type P/Invoke;
PF SSE2=10/SSE4.2=38/AVX2=40/AVX512F=41 -> level 1..4, cached, 0 on
detection failure), `Get-PunkCpuModelLevel` (x86_64/baseline->1, _v2->2,
_v3->3, _v4->4, else 0), `Get-PunkCpuFloorVerdict` (meets|below|unknown|
norecord; below => block). `punk-runtime.bash` mirrors with
`host_cpu_level` (/proc/cpuinfo flags avx512f/avx2/sse4_2/sse2),
`cpu_model_level`, `cpu_floor_verdict` (sets globals). Wired: fetch prints
`cpu floor: floor=.. model=..` (UNGATED - cross-platform fetch stays free);
list metadata_summary adds `floor=` and `!CPU-FLOOR:v` (local-platform
rows only); use gates on the local platform (below => exit 1, named
diagnosis); run gates before launch (below => exit 1); info adds
cpu_floor/cpu_model to the embedded-vs-sidecar field table. `$host` is a
read-only automatic variable in PowerShell - the PS verdict uses `$hlevel`
(caught during testing). Re-wrapped via
`punk::mix::commandset::scriptwrap::multishell punk-runtime -askme 0 -force 1`
from src/scriptapps/bin; wrap is deterministic (re-wrap diff = identical);
the scriptwrap_runtime_cmd_roundtrip_no_drift test PASSES. Host detection
verified (PS level=4 on this Zen 5 box; bash /proc/cpuinfo level=4 in
git-bash); verdict logic verified with a mocked v2 host (x86_64_v4 ->
below/block, znver5 -> unknown, x86_64 -> meets) in both twins.
Remaining for acceptance (publication-time, not code): rebuild the family +
tools with the floor field in the record, republish in-situ to punkbin (per
Approach 6's alpha dispensation), and run the audit tool against the
republished artifacts to confirm zmm=0 (the acceptance's "flagless build emits
zero instructions above floor"). The [tests]-freshness refresh of the replaced
tomls folds into that republication. A real end-to-end `use`/`run` `below`
block requires a host whose level is below a published artifact's recorded
cpu_model - not demonstrable on this v4 host with baseline-floor artifacts
(every host meets v1); the mechanism is verified by the mocked-host tests.
Behind those: r1-generation disposition and the bin/ punk9* kit rebake.
- 2026-08-07: APPROACH 6 executed (rebuild + republish) - the acceptance is met.
Full `zig build bootstrap` (default steps: install, install-libraries, make-zipfs,
smoke, tklib, tcllib, tcllibc, tcllibc-linux, kit-family, kit-family-artifacts,
library-artifacts) with the pinned released 0.16.0 rebuilt the r2 family WITH the
Approach 3 floor field now in the embedded [provenance] record (re-stage picked up
the 2026-08-07 build905.zig edit; zig-cached C compiles, only the wrap + emit
re-ran). family_check passed for all three members (the new
embed_cpu_floor/embed_cpu_model assertions are green). Audit of the rebuilt r2
exes: all three PASS at the baseline floor with zmm=0 ymm=0 out-of-floor=0.
Published in-situ to punkbin (git 59e7475, pushed to origin/master): the three
r2 exes + three r2 tomls replaced under their existing names/revisions; each
toml [provenance] now carries cpu_floor="baseline" + cpu_model="x86_64"; the
[tests] tclcore line refreshed to the shipped-codegen gate evidence
(passed=56041 failed=8, the Aug-06 baseline-rebuild test-gate - was 56039/9);
library [tests] lines unchanged; sha1sums.txt: the 6 r2 lines (3 exes + 3 tomls)
updated. build_id unchanged per artifact (the floor field does not enter the
identity digest - intended); sha1/size/built updated (the embedded record text
changed). The [tests] refresh folded in (the record-honesty residual from the
prior republish is cleared).
Verification of every acceptance conjunct (2026-08-07, this Zen 5 / v4 host):
- flagless build emits zero instructions above the declared floor: the three
republished r2 exes audit PASS (zmm=0) via scriptlib/developer/cpufloor_audit.tcl
(also re-verified against the punkbin working-tree copies post-republish).
- `-Dcpu=native` still yields a host-tuned local build, proving the floor is a
default not a restriction: a temp probe built `-mcpu=baseline` -> zmm=0 PASS
at baseline; `-mcpu=native` -> zmm=123 FAIL at baseline (out-of-floor=151,
first RVA 140002952) and PASS at the v4 floor. (punkzip/punkres were already
rebuilt+republished at zmm=0 in the prior session - the build-path tools are
clean; suite_tcl86's lib tier (tcllibc.dll) was audited clean then too.)
- each published artifact's metadata record states the floor: the republished
tomls carry cpu_floor/cpu_model in [provenance] (verified by diff vs the prior
ab5611d tomls and by punk-runtime info).
- punk-runtime surfaces it: `info` shows the cpu_floor/cpu_model rows
(embedded = sidecar, sha1 matches); `list` shows floor=baseline in the
metadata summary; `use` selects cleanly on the meets verdict (this v4 host
meets the baseline/v1 floor); `fetch` prints `cpu floor: floor=.. model=..`
and stays ungated (code-verified; cross-platform fetch for another machine
is not gated by a local-CPU verdict).
- selecting/launching a runtime whose recorded floor the local CPU does not
meet produces a named diagnosis instead of a silent 0xC000001D: verified by
the mocked-host tests (2026-08-07, both .ps1 and .bash twins) - a recorded
cpu_model=x86_64_v4 against a mocked v2 host yields the `below` verdict with
the 0xC000001D message and use/run exit 1. NOT demonstrable end-to-end on
this v4 host with a real published artifact: every published artifact is
baseline (v1) which every host meets, and no x86-64 level exceeds v4 (the
host's level), so no real below-floor artifact can exist here. The behavior
is present and verified by the mocked-host tests; the limitation is the test
host, not the implementation.
- fetching for any platform stays ungated: code-verified (fetch prints the
floor as information and never gates on the local CPU; the cross-platform
-platform workflow is a normal cross-machine path).
## Follow-ons
Follow-on: r1-generation artifact disposition (replace vs drop) - the r1 exes remain AVX-512-only (2756 zmm each); under the in-situ alpha dispensation they could be replaced at baseline too, or dropped from defaults curation. No live goal home. => open
Follow-on: bin/ punk9* kit rebake with the new floor-bearing runtime (punkproject version-gate discipline applies - the floor surfacing code shipped in 0.57.1; a rebake stamps it into kits). No live goal home. => open