Resolved at F161 (hwjs-cc 06665f7) — two distinct defects, both pure static-merge artifacts, both root-caused, fixed, and witnessed on the real packages
Root cause 1 — xz lzmadec / apps/openssl: the fpcast devirt's own lowering manufactured its evidence
FPCAST PHASE-ORDER VIOLATION was not a phase-order bug at all — it was evidence pollution by the pass's own devirt arms:
lowerFpcastSite rewrites a triggered indirect dispatch into the equality-guarded compare-chain whose arms are DIRECT calls to every sig-compatible address-taken candidate (the completeness contract).
- The five fpcast evidence walks that cross a call boundary backward (
fpcastRootGlobal / fpcastRootObjects / fpcastResolveThreaded / fpcastResolveActual FollowArgs + buildFpcastRegistry's helper-call scan) treated those synthetic arms as genuine callers.
- At whole-program STATIC vantage, every static main carries glibc's
__run_exit_handlers cxa site (Fvii, registered call_fini Fvi). Lowering it emits arms direct-calling every Fvii-compatible app function with actuals rooted in the exit-handler list. The assertFpcastLoweredBeforeClassify re-collect then walked Argument→caller-actual through an arm, rooted liblzma lzma_next_end's allocator->free field (byte offset 4 — coinciding with glibc's exit_function fn slot) at @__exit_funcs, manufactured call_fini as sig-mismatched evidence, re-TRIGGERED the previously-sound site, and the guard fataled the link. openssl: same class through the msort_with_tmp/qsort sites.
- Why dynamic passed: the per-module split keeps glibc's dispatch sites and the app's address-taken universe in different modules — no arm inside
glibc.so ever calls an app function. The failure only exists when the static merge assembles both in one module and the pass's own lowering bridges them.
Witnessed 1:1 on the real xz-5.4.5 lzmadec merged module: the RED knob re-manufactures exactly
[hwjs-coro] FPCAST-SURVIVOR fn=lzma_next_end site_sig=Fvii cands=16 evidence={call_fini:Fvi } site: tail call void %16(ptr noundef %20, ptr noundef %10)
fatal error: error in backend: [hwjs-coro] FPCAST PHASE-ORDER VIOLATION: ...
Fix (F161): every lowering-emitted call (arms + the F117 consult arm) carries !hwjs.fpcast.arm provenance metadata; the five caller-crossing walks skip arm calls. This is exact PRE-lowering analysis parity — an indirect caller was never followed either — so no site's evidence shrinks below its collect-#1 classification, and a genuinely-unenumerated target still lands on the chain's LOUD abort default (never a silent misdispatch). The violation path now NAMES every survivor (FPCAST-SURVIVOR fn=… evidence={…}) before the fatal. RED knob HWJS_CORO_NO_FPCAST_ARM_PROVENANCE (registered in wasmld's RED-knob guard).
Root cause 2 — coreutils vasnprintf (SJLJ): retired by F159, root-caused and gated here
Reproduced byte-exact (suspends=146) on gnulib vasnprintf.c recompiled with -DHAVE_ALLOCA=1 against this ticket's pin e861f8f: vasnprintf allocas its printf-directive scratch (buf = alloca(buf_neededlength)) and hands the address into glibc as the snprintf FORMAT pointer; the escape walk bottomed in glibc's integer-laundered printf/word-copy internals (ptrtoint both args, raw-inttoptr byte loops, address-integers as int args into defined _wordcopy_* helpers) → REFUSE vla/dynamic-alloca-address-escape-opaque-sink → vasnprintf stays NATIVE → and because the static merge puts __libc_siglongjmp in-module with vasnprintf on a sigsetjmp/siglongjmp cross-frame unwind path, the SJLJ seam fataled the whole link (setjmp-2b-unwind-through-non-coroutinizable-frame). The two refusal lines in this ticket are one causal chain, not two bugs. Dynamic passed because glibc.so keeps __libc_siglongjmp out of the app module — no in-module cross-frame path is ever assembled.
F159's integer-laundered word-copy readdressing (landed for brintos/hwjs-cc#2) follows exactly that chain, so the directive alloca now DS-LOWERs (1/1) to a frame-resident region, vasnprintf coroutinizes, and the SJLJ seam never fires. RED knob HWJS_CORO_NO_WORDCOPY_READDRESS re-manufactures both exact ticket refusals.
(Why the in-repo glibc distro never hit it: its coreutils config takes the malloc arm — HAVE_ALLOCA undef. The yocto arm's HAVE_ALLOCA=1 is the trigger; byte-identical gnulib defines glibc-vs-musl, as the ticket established.)
Verdict: two distinct defects (shared only the vantage)
Both are whole-program-merge phenomena the per-module dynamic vantage never assembles, but mechanically independent: (1) devirt-arm evidence pollution in the fpcast walks; (2) an escape-classification gap whose refusal cascaded into the SJLJ closure. Each has its own fix, its own flipping RED knob, and its own gate.
RED → GREEN evidence (all on the FINAL plugin, provenance commit=06665f7 dirty=0)
Real packages (link + RUN, static, whole-program glibc merge):
- xz-5.4.5
lzmadec static link: GREEN — coroutinized=415 refused=0; RED (HWJS_CORO_NO_FPCAST_ARM_PROVENANCE=1) fatals with the exact ticket message + the new FPCAST-SURVIVOR fn=lzma_next_end attribution. RUN: decodes a real .lzma stream byte-exact under the −517-parking harness (1 park/1 resume) and in a no-park control.
- xz-5.4.5
xz static binary: full compress → decompress round-trip byte-exact across parks.
- openssl-3.0.13
apps/openssl static link: GREEN (pre-F161: the exact same FPCAST PHASE-ORDER fatal). RUN: openssl version prints "OpenSSL 3.0.13" (exit 0 across a park), openssl dgst -sha256 matches host hash byte-exact (028069dd…), openssl list -digest-commands sorts correctly (direct qsort/msort fpcast exercise).
- coreutils
nice + [ static links with gnulib vasnprintf recompiled -DHAVE_ALLOCA=1 (the ticket's package shape): GREEN with DS-LOWER fn=rpl_vasnprintf lowered=1/1, and [ RUNS ([ a = a ] → 0, [ a = b ] → 1).
Committed non-vacuous gates:
pass/coroutinize/fpcast_arm_provenance.mjs + fixtures/fpcast_arm.c (hwjs-cc, in run-coro.sh): GREEN triggered+lowered+no survivor, RUNTIME through the adapted arm, RED flips to the exact fatal. 8/8.
distros/linux-6.12-glibc-bash-coreutils/tools/run-hwjscc4-static-glibc-gate.mjs + tools/hwjscc4-static/{vasn_fx.c,fpcast_glibc_fx.c} (superproject): both shapes static-link (WASMLD_DYNAMIC_MAIN=0/MODULE=0, the yocto posture) AND RUN across parks + siglongjmp; both RED arms flip to the exact pre-fix refusals; plus the rig-conditional real-coreutils leg. 20/20 GREEN.
No-regression: hwjs-cc battery 178 pass / 7 skip / 0 fail; kernel force-recompiled per #84 (genuinely-new-plugin objects) and all three distros rebuilt green on the F161 lineage; substrate/glibc distro gates green (#73 malloc-fatal 8/8, #2 gqsort, #3 gstrerror all GREEN; hardwarejs vitest no regression — the 14 env-gated failures are identical pre/post-F161, needing the absent binaryen fixture-assembler). parked_abi/drive_abi/ds_region_abi unchanged — metadata-only provenance, no wire change, no new import.
Live: F161 is pass-only (no TS/hardwarejs runtime change), so no web/sst deploy; all three live distro machines (brintos/linux-6.12-{glibc-bash-coreutils,musl-bash-coreutils,musl-busybox}) were rebuilt on the F161 lineage and deployed additively (server-durable, no deletions).
SHAs: hwjs-cc 06665f7 (F161, pushed to joel-dev), superproject fb5318e (gitlink advance + the #4 gate, pushed to joel-dev).
Both static-merged-module walls the ticket reported — the coreutils vasnprintf SJLJ refusal and the xz/openssl fpcast phase-order violation — now link AND run correctly on the final artifacts. Closing as completed.