fix(338): preserve global.get extended-const exprs — silent miscompile of PIE base+N offsets - #346
Conversation
meld fuse silently TRUNCATED any wasm-2.0 extended-const expr containing a `global.get` — `global.get $base; i32.const N; i32.add` (and the operand-swapped `i32.const N; global.get $base; i32.add`) became just `global.get $base`, dropping the arithmetic. This corrupted global initializers and data/element offsets — the PIE `__memory_base + N` / `__table_base + N` shape. The module still validated → silent miscompile (SR-51). #152/LS-A-11 fixed only the const-first-no-globalget fold; both global.get cases were missed. Fix: preserve the FULL extended-const operator sequence (the value is runtime-dependent, so it cannot fold to a constant) and re-emit it with global indices remapped. New `ParsedConstExpr::ExtendedGlobalGet` + `ExtConstOp` + `read_extended_const_global_get`; the `fold_extended_const_{i32,i64}` arms now return `ExtConstFold::{Value,Extended}` and route an embedded `global.get` into the preserve-and-remap path instead of erroring (which had fallen back to UN-remapped raw bytes). Wired at both sites: segments.rs (parse_const_expr_with_value, data/elem offsets) + merger.rs (convert_init_expr, global inits). Operand order preserved (sub/mul non-commutative). Load-bearing ordering fix (Mythos): the imported-global index-map population was hoisted to run BEFORE the defined-globals loop that calls convert_init_expr — previously the remap silently missed (unwrap_or kept the local index), so a multi-module fuse that shifts __memory_base off import-index 0 read the wrong base. Tests (executed vs wasmtime): global-init `base+100`->1100 and `100+base`->1100 (multi-module base-shift; pre-fix read __stack_pointer=5100); data-offset `base+N` and `N+base` land at base+N (pre-fix truncated to base+0 / hard-error); bare global.get + const-first fold unchanged. 737 lib+integration green; clippy + fmt clean; rivet validate PASS. Refs #338, #152, SR-51, SYS-1, LS-A-11. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Mythos discover pass — done (two passes; one incomplete-fix caught + fixed)Pass 1 on the initial Fix (this PR): routed the embedded- Pass 2 / re-verification: full suite re-run on the rebased branch — 36 test binaries, 0 failures; clippy + fmt clean; |
Mythos delta-pass requiredThis PR modifies one or more Tier-5 source files (per Before merge, run the Mythos discover protocol on the
Why this gate exists: LS-A-10 The gate check on this PR will pass once the label is |
LS-N verification gate✅ 59/59 approved LS entries verified
Approved Failed LS entries(none) Missing regression tests(none) Updated automatically by |
Mythos delta-pass (auto)✅ NO FINDINGS across 2 Tier-5 file(s)
Auto-run via |
meld fusesilently truncated any wasm-2.0 extended-const expression containing aglobal.get— bothglobal.get $base; i32.const N; i32.addand the operand-swappedi32.const N; global.get $base; i32.addbecame justglobal.get $base, dropping the arithmetic. This corrupted global initializers and data/element-segment offsets — the position-independent__memory_base + N/__table_base + Nshape. The module still validated → silent miscompile (SR-51). #152/LS-A-11 fixed only the const-first-no-global.getfold; bothglobal.getcases were missed.Fix
The value is runtime-dependent, so it can't fold to a constant — meld now PRESERVES the full extended-const operator sequence and re-emits it with global indices remapped. New
ParsedConstExpr::ExtendedGlobalGet+ExtConstOp+read_extended_const_global_get; thefold_extended_const_{i32,i64}arms returnExtConstFold::{Value,Extended}and route an embeddedglobal.getinto the preserve-and-remap path (instead of erroring → un-remapped raw-bytes fallback). Wired at both sites:segments.rs(data/elem offsets) +merger.rs convert_init_expr(global inits). Operand order preserved (sub/mulnon-commutative).Load-bearing ordering fix (found by the Mythos pass): the imported-global index-map population was hoisted to run BEFORE the defined-globals loop that calls
convert_init_expr— previously the remap silently missed (unwrap_orkept the local index), so a multi-module fuse that shifts__memory_baseoff import-index 0 read the wrong base.Tests (executed vs wasmtime; 737 green; clippy + fmt clean)
base+100→1100 and multi-module100+base→1100 (base-shift fixture; pre-fix read__stack_pointer=5100).base+NandN+baseland at base+N (pre-fix truncated to base+0 / hard-error).global.get+ const-first fold unchanged.Falsification
If the preserve-and-remap regressed,
test_338_multimodule_const_first_embedded_globalget_remaps(asserts the fused global reads the correct base, not the un-remapped 5100) fails. If truncation returned,test_338_global_initializer_extended_const_preserved(==1100) fails.Refs #338, #152, SR-51, SYS-1, LS-A-11.
🤖 Generated with Claude Code