Browse Source
The published tclsh9.0.5 runtime family is AVX-512-only. A flagless
bin/punk-runtime.cmd fetch hands a fresh checkout the curated win32-x86_64
default (tclsh9.0.5-punk-r2.exe), which on a CPU without AVX-512 raises #UD and
is killed with STATUS_ILLEGAL_INSTRUCTION (0xC000001D, errorlevel -1073741795)
before writing a byte - no message, no prompt, no error. First reported as a
Windows-version difference (25H2 works, 23H2 does not); it is not one.
Cause: the recipes called b.standardTargetOptions(.{}) and no build path passed
-Dtarget/-Dcpu, so zig resolved cpu_model .determined_by_arch_os by NATIVE
detection and ReleaseFast auto-vectorised to the Zen 5 build host. Measured: all
six published tclsh9.0.5-* artifacts (punk + plain, r1 + r2, both -bi- variants)
carry 2756 zmm-operand instructions each - real EVEX encodings, first site at the
front of .text, and Tcl has no runtime CPU dispatch - as do bin/punk905.exe,
punk9-dev.exe, punk9_beta.exe, punk9bi_beta.exe, punkdeclare.exe and
punkfiledemo.exe. bin/punkzip.exe carries 1724 and bin/punkres.exe 1423, so a
bake on such a machine fails too. An older generation (punk901*, punk9.exe,
xcritcl.exe, tclsh90s*) is AVX2-only from an earlier host: same defect, milder
floor. Confirmed across three machines - Zen 2 dies, Zen 4 and Zen 5 run.
Pins applied to all four in-repo recipes (suite_tcl90, suite_tcl86, punkzip,
punkres): standardTargetOptions gains .default_target = .{ .cpu_model =
.baseline }, so a flagless build is portable while -Dcpu=native remains the
developer opt-in and -Dtarget=<triple> is unaffected. Floor chosen as baseline
(x86-64 v1) - for a Tcl interpreter the codegen delta against v2/v3 is
negligible set against the default runtime failing to start. This has to be a
recipe DEFAULT rather than a build-command convention: "remember to pass -Dcpu=
when publishing" is precisely what failed silently for six artifacts.
Verified by zig ast-check on all four recipes, and by reproducing the mechanism
with the same toolchain and optimize level on an equivalent recipe (flagless ->
x86_64 baseline, zmm=0; -Dcpu=native -> znver5, zmm=805). No suite build was
run: all four recipes gate on required_zig = "0.16.0" with a .lt SemVer compare,
which rejects the bundled prerelease bin/tools/zig-0.16.0-dev.254 - a separate
pre-existing defect, recorded in the goal's Notes and deliberately not fixed
here.
Nothing is rebuilt or republished, and no shipped surface changes, so no project
version bump. Recording the floor in the artifact metadata record, an audit tool,
punk-runtime surfacing it on use/run (fetch stays ungated - fetching for another
platform is a normal workflow), and the rebuild/republish with the defaults.txt
repoint all remain open as G-172.
Assisted-by: harness=claude; primary-model=claude-opus-5[1m]; api-location=anthropic.com
master
8 changed files with 273 additions and 7 deletions
@ -0,0 +1,177 @@ |
|||||||
|
# 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 pinned 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 <p>`) 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 of the affected artifacts as a new revision, and repointing |
||||||
|
punkbin `defaults.txt`, is the closing step - sequenced after 1-5 so the |
||||||
|
rebuilt family is audited and self-describing before it becomes the default. |
||||||
|
|
||||||
|
## 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=<model>`. |
||||||
|
- 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<n>` 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). |
||||||
|
- Deliberately outside this contract: all four recipes gate on |
||||||
|
`required_zig = "0.16.0"` with a `.lt` SemanticVersion compare, so a |
||||||
|
prerelease toolchain (`0.16.0-dev.254+6dd0270a1`, the copy under bin/tools) |
||||||
|
sorts below the pin and is rejected by the recipes' own gate. A real defect |
||||||
|
in the same lines, but a different one - it blocks rebuilding here until a |
||||||
|
released 0.16.0 is materialized. |
||||||
|
- Verification note: `llvm-objdump -d --section=.text <exe>` 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 - blocked by the version-gate defect noted above. The `b.graph.host` |
||||||
|
target sites elsewhere in these recipes are build-time helper tools that are |
||||||
|
never distributed and are correct as native. |
||||||
|
- Remaining for acceptance: Approach 3 (floor in the artifact record), 4 (audit |
||||||
|
tool + fixture), 5 (punk-runtime surfacing and the use/run verdict), 6 |
||||||
|
(rebuild, republish, repoint `defaults.txt`). |
||||||
Loading…
Reference in new issue