No runnable build on the review box: target-cpu=x86-64-v3 crashes build scripts on pre-AVX2 CPUs, and CC=clang is absent #16

Closed
opened 2026-08-25 12:34:36 +02:00 by pbwraith · 3 comments
Owner

gopher64-netplay cannot be built on the review box — 7 open PRs are ungateable

All seven open PRs (!9, !10, !11, !12, !13, !14, !15) are currently
unverifiable, because cargo check fails on main before compiling any of
this repo's own code. This is not a defect in any PR — it reproduces on a clean
origin/main checkout — but it means no PR here can be honestly reviewed or
merged, so it is worth fixing as a cause rather than re-discovering per PR.

Two independent blockers, both measured today.

Blocker 1 — target-cpu=x86-64-v3 makes the build crash on pre-2013 CPUs

.cargo/config.toml:

[target.'cfg(target_arch = "x86_64")']
rustflags = ["-C", "target-cpu=x86-64-v3"]

x86-64-v3 requires AVX2/BMI2, i.e. Intel Haswell (2013) or newer. The review
box is an Intel i7-2600K (Sandy Bridge, 2011), which is x86-64-v2.

Observed on origin/main, identically with -j 1 and with default parallelism:

error: failed to run custom build command for `num-traits v0.2.19`
  memory allocation of 648518346341351440 bytes failed
warning: build failed
EXITCODE=101

Minimal reproduction, outside this repo entirely — cargo new + a single
num-traits = "0.2.19" dependency:

(no RUSTFLAGS)                        -> Finished in 10.93s          rc=0
RUSTFLAGS="-C target-cpu=x86-64-v3"   -> exit code 0xc0000409, STATUS_STACK_BUFFER_OVERRUN
                                         memory allocation of 648518346341351440 bytes failed

Byte-for-byte the same bogus allocation size, so this is the cause and not a
coincidence. The 648518346341351440 figure is 0x0900000000000010 — garbage
from executing AVX2 instructions on a CPU that does not implement them, not a
genuine out-of-memory condition (the box had 3.5 GB free).

The subtle part, and the reason this is worth fixing rather than documenting:
this breaks build scripts, not just the shipped binary. For a native build
(host == target) cargo applies [target.<cfg>] rustflags to build scripts and
proc macros as well, so the flag miscompiles num-traits's build script and
the failure happens at dependency-compile time. The project is therefore
unbuildable on such a host — you cannot even get far enough to produce a binary
you would then decline to run.

Verified workaround for the flag (an explicit RUSTFLAGS env var replaces the
config's rustflags rather than merging with it — remember to carry the stack
flag across):

RUSTFLAGS="-C target-cpu=x86-64-v2 -C link-arg=/STACK:8388608" cargo check

That gets past num-traits and every other dependency, straight to blocker 2.

Blocker 2 — [env] CC = "clang" with no clang on the box

.cargo/config.toml sets CC = "clang", CXX = "clang++", AR = "llvm-ar"
unconditionally. With the v3 flag overridden, the build then fails at:

warning: ring@0.17.14: Compiler family detection failed due to error:
  ToolNotFound: failed to find tool "clang": program not found
error: failed to run custom build command for `ring v0.17.14`

clang and cmake are both absent (which clang / which cmake -> not
found). cmake is very likely needed too — sdl3-src and bindgen are
build-dependencies — but I did not get far enough to confirm that, and say so
explicitly rather than guessing.

Scope

Pick whichever is appropriate; this issue is about making the gate runnable, not
about mandating one of them:

  1. Make the microarchitecture opt-in. Move target-cpu=x86-64-v3 behind an
    explicit profile or env opt-in so the default build is portable, keeping the
    optimised flags for release builds on capable hardware. This also fixes the
    real-world case of a user on an older CPU.
  2. Provision the review box: install LLVM/clang and cmake, and record the
    RUSTFLAGS override in a documented gate script.

If (1) is rejected because this is a fork and every changed line is future
rebase cost against upstream, say so on this issue and do (2) — but then the
override must be written down somewhere a reviewer will find it, because the
failure mode (a bogus multi-petabyte allocation inside an unrelated dependency)
gives no hint whatsoever about its cause.

Acceptance

  • cargo check --all-targets exits 0 on a clean origin/main checkout on the
    review box, by a documented command.
  • That command is recorded in the repo (a docs/ note or a script), not only
    in this issue.
  • The seven open PRs can then each be gated against their merge result.

Out of scope

  • Reviewing or merging any of the seven open PRs.
  • Any change to src/ or to the vendored vendor/ggrs tree.
  • Making the shipped release binary run on x86-64-v2 hardware — this issue
    is only about being able to build and test.
## gopher64-netplay cannot be built on the review box — 7 open PRs are ungateable All seven open PRs (!9, !10, !11, !12, !13, !14, !15) are currently **unverifiable**, because `cargo check` fails on `main` before compiling any of this repo's own code. This is not a defect in any PR — it reproduces on a clean `origin/main` checkout — but it means no PR here can be honestly reviewed or merged, so it is worth fixing as a cause rather than re-discovering per PR. Two independent blockers, both measured today. ### Blocker 1 — `target-cpu=x86-64-v3` makes the build crash on pre-2013 CPUs `.cargo/config.toml`: ```toml [target.'cfg(target_arch = "x86_64")'] rustflags = ["-C", "target-cpu=x86-64-v3"] ``` `x86-64-v3` requires AVX2/BMI2, i.e. Intel Haswell (2013) or newer. The review box is an **Intel i7-2600K (Sandy Bridge, 2011)**, which is `x86-64-v2`. Observed on `origin/main`, identically with `-j 1` and with default parallelism: ``` error: failed to run custom build command for `num-traits v0.2.19` memory allocation of 648518346341351440 bytes failed warning: build failed EXITCODE=101 ``` Minimal reproduction, outside this repo entirely — `cargo new` + a single `num-traits = "0.2.19"` dependency: ``` (no RUSTFLAGS) -> Finished in 10.93s rc=0 RUSTFLAGS="-C target-cpu=x86-64-v3" -> exit code 0xc0000409, STATUS_STACK_BUFFER_OVERRUN memory allocation of 648518346341351440 bytes failed ``` Byte-for-byte the same bogus allocation size, so this is the cause and not a coincidence. The 648518346341351440 figure is `0x0900000000000010` — garbage from executing AVX2 instructions on a CPU that does not implement them, not a genuine out-of-memory condition (the box had 3.5 GB free). **The subtle part, and the reason this is worth fixing rather than documenting:** this breaks *build scripts*, not just the shipped binary. For a native build (host == target) cargo applies `[target.<cfg>] rustflags` to build scripts and proc macros as well, so the flag miscompiles `num-traits`'s build script and the failure happens at dependency-compile time. The project is therefore unbuildable on such a host — you cannot even get far enough to produce a binary you would then decline to run. Verified workaround for the flag (an explicit `RUSTFLAGS` env var replaces the config's `rustflags` rather than merging with it — remember to carry the stack flag across): ``` RUSTFLAGS="-C target-cpu=x86-64-v2 -C link-arg=/STACK:8388608" cargo check ``` That gets past `num-traits` and every other dependency, straight to blocker 2. ### Blocker 2 — `[env] CC = "clang"` with no clang on the box `.cargo/config.toml` sets `CC = "clang"`, `CXX = "clang++"`, `AR = "llvm-ar"` unconditionally. With the v3 flag overridden, the build then fails at: ``` warning: ring@0.17.14: Compiler family detection failed due to error: ToolNotFound: failed to find tool "clang": program not found error: failed to run custom build command for `ring v0.17.14` ``` `clang` and `cmake` are both absent (`which clang` / `which cmake` -> not found). `cmake` is very likely needed too — `sdl3-src` and `bindgen` are build-dependencies — but I did not get far enough to confirm that, and say so explicitly rather than guessing. ### Scope Pick whichever is appropriate; this issue is about making the gate runnable, not about mandating one of them: 1. **Make the microarchitecture opt-in.** Move `target-cpu=x86-64-v3` behind an explicit profile or env opt-in so the default build is portable, keeping the optimised flags for release builds on capable hardware. This also fixes the real-world case of a user on an older CPU. 2. **Provision the review box**: install LLVM/clang and cmake, and record the `RUSTFLAGS` override in a documented gate script. If (1) is rejected because this is a fork and every changed line is future rebase cost against upstream, say so on this issue and do (2) — but then the override must be written down somewhere a reviewer will find it, because the failure mode (a bogus multi-petabyte allocation inside an unrelated dependency) gives no hint whatsoever about its cause. ### Acceptance - `cargo check --all-targets` exits 0 on a clean `origin/main` checkout on the review box, by a documented command. - That command is recorded in the repo (a `docs/` note or a script), not only in this issue. - The seven open PRs can then each be gated against their merge result. ### Out of scope - Reviewing or merging any of the seven open PRs. - Any change to `src/` or to the vendored `vendor/ggrs` tree. - Making the *shipped release binary* run on `x86-64-v2` hardware — this issue is only about being able to **build and test**.
pbwraith added the
p1
label 2026-08-25 12:34:36 +02:00
Author
Owner

Second finding: the 7 open PRs do not compose — 4 of them independently edit src/netplay.rs

Separate from the build blocker above, and checkable without a compiler. Every
one of the seven PRs reports mergeable=true; that flag is measured against
main in isolation and says nothing about whether they coexist. Merged for real
into throwaway worktrees off origin/main (00f918e), they do not.

Stacking (measured with git merge-base --is-ancestor)

issue-5  <  issue-6  <  issue-7      (!9 < !10 < !11 — a stack; issue-7 contains all three)
issue-1, issue-2, issue-3, issue-8   independent branches off main

So !11 already contains !9 and !10 — merging !11 lands all three, and
reviewing them separately is only worthwhile if they are meant to land
separately.

Best composable set: 3 of 7

order: issue-1 -> issue-2 -> issue-8 -> issue-3 -> issue-7
  clean    : issue-1 (!14), issue-2 (!15), issue-8 (!12)
  conflict : issue-3 (!13) -> src/netplay.rs, src/savestates.rs
  conflict : issue-7 (!11) -> src/device.rs, src/netplay.rs, src/savestates.rs

Other orders are worse, not better:

issue-7 first : clean {issue-7, issue-8}          conflict 1, 2, 3
issue-3 first : clean {issue-3, issue-8}          conflict 2, 1, 7

issue-8 (!12, the UI PR) is clean in every order — it only touches
src/ui/** plus src/ui/netplay.rs, so it is order-independent.

Recommendation

Land in the order above. After !14, !15 and !12 are in, !13 and the
!9/!10/!11 stack need a real rebase onto the new main
, not a
mergeable recheck — the conflicts are in the message-dispatch region of
src/netplay.rs that issue-1 restructures (+258/-34) and that the resync
work also edits.

Worth doing in that order specifically because issue-1 is the protocol
foundation (state-message types, chunked codec) that the resync PRs are
layering on top of; rebasing them onto it should remove duplicated
message-plumbing rather than just relocating a conflict.

I am not bouncing any of these — the conflicts are a consequence of seven
branches being cut from the same commit in parallel, not a defect in any one of
them, and until the gate above runs none of them can be verified anyway.

## Second finding: the 7 open PRs do not compose — 4 of them independently edit `src/netplay.rs` Separate from the build blocker above, and checkable without a compiler. Every one of the seven PRs reports `mergeable=true`; that flag is measured against `main` in isolation and says nothing about whether they coexist. Merged for real into throwaway worktrees off `origin/main` (`00f918e`), they do not. ### Stacking (measured with `git merge-base --is-ancestor`) ``` issue-5 < issue-6 < issue-7 (!9 < !10 < !11 — a stack; issue-7 contains all three) issue-1, issue-2, issue-3, issue-8 independent branches off main ``` So `!11` already contains `!9` and `!10` — merging `!11` lands all three, and reviewing them separately is only worthwhile if they are meant to land separately. ### Best composable set: 3 of 7 ``` order: issue-1 -> issue-2 -> issue-8 -> issue-3 -> issue-7 clean : issue-1 (!14), issue-2 (!15), issue-8 (!12) conflict : issue-3 (!13) -> src/netplay.rs, src/savestates.rs conflict : issue-7 (!11) -> src/device.rs, src/netplay.rs, src/savestates.rs ``` Other orders are worse, not better: ``` issue-7 first : clean {issue-7, issue-8} conflict 1, 2, 3 issue-3 first : clean {issue-3, issue-8} conflict 2, 1, 7 ``` `issue-8` (!12, the UI PR) is clean in every order — it only touches `src/ui/**` plus `src/ui/netplay.rs`, so it is order-independent. ### Recommendation Land in the order above. After `!14`, `!15` and `!12` are in, **`!13` and the `!9`/`!10`/`!11` stack need a real rebase onto the new `main`**, not a `mergeable` recheck — the conflicts are in the message-dispatch region of `src/netplay.rs` that `issue-1` restructures (+258/-34) and that the resync work also edits. Worth doing in that order specifically because `issue-1` is the protocol foundation (state-message types, chunked codec) that the resync PRs are layering on top of; rebasing them onto it should *remove* duplicated message-plumbing rather than just relocating a conflict. I am not bouncing any of these — the conflicts are a consequence of seven branches being cut from the same commit in parallel, not a defect in any one of them, and until the gate above runs none of them can be verified anyway.
Author
Owner

Correction: options 1 and 2 are not alternatives -- option 1 alone does not make the gate runnable

Re-verified both blockers independently today and confirmed them, but the
"Scope" section above says "Pick whichever is appropriate", and that framing is
wrong in a way that would waste an implementation.

Option 1 (make target-cpu=x86-64-v3 opt-in) does not produce a runnable
gate on its own.
This issue's own text already contains the proof -- the
RUSTFLAGS override "gets past num-traits and every other dependency,
straight to blocker 2". Blocker 2 is independent of the microarch flag, so
a PR that only makes the flag opt-in leaves cargo check still failing, on
ring, for a completely different reason. It is a worthwhile portability fix
on its own merits; it is not a fix for this issue's acceptance criterion.

Only option 2 (provision the box) actually satisfies the acceptance, because
it carries the RUSTFLAGS override and installs the missing compiler.

Sharper finding on blocker 2: it is not merely "clang is absent"

which clang / gcc / cc / cl all return nothing, as recorded. But the
box is closer to buildable than that suggests, and the precise state changes
what the cheapest remediation is:

Visual Studio 2022 BuildTools IS installed:
  C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools
  VC\Tools\MSVC\14.35.32215\   -> contains only: atlmfc\  Auxiliary\
  VC\Tools\MSVC\14.36.32532\   -> contains only: atlmfc\  Auxiliary\
  no bin\, no Hostx64\, no cl.exe, no vcvars64.bat  (recursive search: 0 hits)

So the MSVC toolset directories exist with the compiler payload never
installed
-- a partial BuildTools install. Two rustup MSVC toolchains are
also present (stable-x86_64-pc-windows-msvc, plus 1.97.1 and 1.98.0), while
the active toolchain is stable-x86_64-pc-windows-gnu (rustc -vV ->
host: x86_64-pc-windows-gnu, rustc 1.85.0).

CPU confirmed as stated: Intel64 Family 6 Model 42 = Sandy Bridge, i.e.
x86-64-v2, so the AVX2 analysis in this issue holds.

Why this sweep did not just provision it

I tried the non-interactive route and it is not available:

winget install --id LLVM.LLVM --scope user --accept-package-agreements \
  --accept-source-agreements --disable-interactivity
-> No applicable installer found

LLVM has no user-scope installer, so every remaining path -- LLVM machine-scope,
or completing the BuildTools install with
Microsoft.VisualStudio.Component.VC.Tools.x86.x64, or MSYS2 for a MinGW gcc
matching the active -gnu toolchain -- requires elevation, which would put
a UAC dialog on the owner's desktop from an unattended sweep. That is an owner
action, and it is a small one: a single elevated install, once.

cmake is also absent and will be needed (sdl3-src, bindgen), so whichever
compiler is chosen, install Kitware.CMake in the same pass.

Status of the 7 PRs, unchanged

Still ungateable, and I did not merge or bounce any of them. The composition
analysis in the comment above stands. Recommend this issue keeps its p1 and
is treated as the prerequisite for all seven.

## Correction: options 1 and 2 are not alternatives -- option 1 alone does not make the gate runnable Re-verified both blockers independently today and confirmed them, but the "Scope" section above says "Pick whichever is appropriate", and that framing is wrong in a way that would waste an implementation. **Option 1 (make `target-cpu=x86-64-v3` opt-in) does not produce a runnable gate on its own.** This issue's own text already contains the proof -- the `RUSTFLAGS` override "gets past `num-traits` and every other dependency, **straight to blocker 2**". Blocker 2 is independent of the microarch flag, so a PR that only makes the flag opt-in leaves `cargo check` still failing, on `ring`, for a completely different reason. It is a worthwhile portability fix on its own merits; it is not a fix for this issue's acceptance criterion. Only **option 2 (provision the box)** actually satisfies the acceptance, because it carries the `RUSTFLAGS` override *and* installs the missing compiler. ## Sharper finding on blocker 2: it is not merely "clang is absent" `which clang` / `gcc` / `cc` / `cl` all return nothing, as recorded. But the box is closer to buildable than that suggests, and the precise state changes what the cheapest remediation is: ``` Visual Studio 2022 BuildTools IS installed: C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools VC\Tools\MSVC\14.35.32215\ -> contains only: atlmfc\ Auxiliary\ VC\Tools\MSVC\14.36.32532\ -> contains only: atlmfc\ Auxiliary\ no bin\, no Hostx64\, no cl.exe, no vcvars64.bat (recursive search: 0 hits) ``` So the MSVC **toolset directories exist with the compiler payload never installed** -- a partial BuildTools install. Two rustup MSVC toolchains are also present (`stable-x86_64-pc-windows-msvc`, plus 1.97.1 and 1.98.0), while the *active* toolchain is `stable-x86_64-pc-windows-gnu` (`rustc -vV` -> `host: x86_64-pc-windows-gnu`, rustc 1.85.0). CPU confirmed as stated: `Intel64 Family 6 Model 42` = Sandy Bridge, i.e. `x86-64-v2`, so the AVX2 analysis in this issue holds. ## Why this sweep did not just provision it I tried the non-interactive route and it is not available: ``` winget install --id LLVM.LLVM --scope user --accept-package-agreements \ --accept-source-agreements --disable-interactivity -> No applicable installer found ``` LLVM has no user-scope installer, so every remaining path -- LLVM machine-scope, or completing the BuildTools install with `Microsoft.VisualStudio.Component.VC.Tools.x86.x64`, or MSYS2 for a MinGW `gcc` matching the active `-gnu` toolchain -- requires **elevation**, which would put a UAC dialog on the owner's desktop from an unattended sweep. That is an owner action, and it is a small one: a single elevated install, once. `cmake` is also absent and will be needed (`sdl3-src`, `bindgen`), so whichever compiler is chosen, install `Kitware.CMake` in the same pass. ## Status of the 7 PRs, unchanged Still ungateable, and I did not merge or bounce any of them. The composition analysis in the comment above stands. Recommend this issue keeps its `p1` and is treated as the prerequisite for all seven.
pbwraith added the
in-review
label 2026-08-25 13:51:51 +02:00
Author
Owner

Status: the fix in !17 is verified for the specific crash; the gate still cannot complete

Ran ./scripts/gate.sh from !17 against the merge result on the review box.

The blocker this issue documents is fixed. num-traits v0.2.19 compiles,
and the signature allocation size 648518346341351440 appears zero times
in the log (371 crates processed before any error). That is Blocker 1 closed on
the evidence.

But the gate still does not reach exit 0, for a new and unrelated reason:
the review box's C: drive is full -- 1.13 GB free of 477 GB, hitting zero
mid-build:

rust-lld: error: failed to open .../libsimd_helpers-....so: No space left on device
EXIT=101

A debug cargo check --all-targets on this dependency graph needs several GB
of target/, so this repo cannot be gated on this box until disk is freed.
osrs-farm/ as a whole accounts for only ~3 GB, so the consumer is elsewhere
on the drive; I have escalated it to the owner rather than deleting anything
outside my scope.

Consequence for this issue: it stays open and correctly describes a real
condition, but its remaining blocker has changed identity -- it is no longer
target-cpu=x86-64-v3, it is free space. The seven PRs it names
(!9, !10, !11, !12, !13, !14, !15) remain ungateable, now for that reason.

Note also that bounced is not a defined label in this repo, so any attempt to
bounce a PR here stores nothing (Gitea labels resolve by id per-repo and
silently drop unknown names). Worth adding if this repo is going to take
sweep review traffic.

## Status: the fix in !17 is verified for the specific crash; the gate still cannot complete Ran `./scripts/gate.sh` from !17 against the merge result on the review box. **The blocker this issue documents is fixed.** `num-traits v0.2.19` compiles, and the signature allocation size `648518346341351440` appears **zero** times in the log (371 crates processed before any error). That is Blocker 1 closed on the evidence. **But the gate still does not reach exit 0, for a new and unrelated reason:** the review box's C: drive is full -- **1.13 GB free of 477 GB**, hitting zero mid-build: ``` rust-lld: error: failed to open .../libsimd_helpers-....so: No space left on device EXIT=101 ``` A debug `cargo check --all-targets` on this dependency graph needs several GB of `target/`, so this repo cannot be gated on this box until disk is freed. `osrs-farm/` as a whole accounts for only ~3 GB, so the consumer is elsewhere on the drive; I have escalated it to the owner rather than deleting anything outside my scope. Consequence for this issue: it stays open and correctly describes a real condition, but its remaining blocker has changed identity -- it is no longer `target-cpu=x86-64-v3`, it is free space. The seven PRs it names (!9, !10, !11, !12, !13, !14, !15) remain ungateable, now for that reason. Note also that `bounced` is not a defined label in this repo, so any attempt to bounce a PR here stores nothing (Gitea labels resolve by id per-repo and silently drop unknown names). Worth adding if this repo is going to take sweep review traffic.
pbwraith removed the
in-review
label 2026-08-26 04:44:24 +02:00
This repo is archived. You cannot comment on issues.
No Milestone
No project
No Assignees
1 Participants
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: pbwraith/gopher64-netplay#16
No description provided.