commit 9e7fb34dbd5406af47f6f2bc32a89e920548a8a9
parent c67f18b84502a7af20d9486f177d61dfa855dd1a
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Tue, 2 Jun 2026 07:40:19 -0700
cg: fix i128 -O1 null-deref — flow wide16 int constants as VALUEs (Track 7.3 follow-up)
api_make_wide16_int_const returned an lvalue/PLACE, but Track 7.3 makes
i128/f128 scalar VALUEs. As a place, an i128 constant was passed
by-reference at -O1 and the ABI dereferenced a value slot as a 32-bit
pointer -> SIGSEGV on i128->bool compares (test-parse i128_06_shifts_bitwise,
O1/D,J,E). Return api_make_sv to match api_push_call_result.
test-parse 3784/0 (was 3 fail); toy 1355/0; cg-api 173/0+168/0; opt green;
make bootstrap reproduces byte-identical at -O0 AND -O1.
Diffstat:
2 files changed, 15 insertions(+), 12 deletions(-)
diff --git a/doc/plan/CODEGEN.md b/doc/plan/CODEGEN.md
@@ -9,17 +9,15 @@ The **fold-layer isolation + delayed-arith re-enable** (Track 6) is **complete**
remains is the **binop/cmp op-split** (the rest of Track 2), the Track 1c completeness
audit, and the Track 5 multi-value follow-up.
-> **⚠ KNOWN REGRESSION — blocks merge.** `test-parse i128_06_shifts_bitwise` fails at
-> **-O1 on the native + link paths** (`O1/D`, `O1/J`, `O1/E`; `-O1/R` interp and all
-> `-O0` variants pass). Clean on `main` (`dce052f`: 8/8 pass). Bisect: enters at
-> `a0397c6` (Track 7.1/7.2) as `O0/E`+`O1/E` link failures, then **morphs** through
-> `6f48bfd` (Track 7.3 wide16 collapse) into the `-O1` D/J/E failures at branch HEAD —
-> so the i128 `-O1` lowering is broken by the interaction of the PLACE-predicate
-> tightening (7.1/7.2) and the wide16 collapse (7.3). **Every per-stage verify and the
-> final verify missed it: `test-parse` was in no stage's gate** (gates ran
-> toy/cg-api/opt/isa/smoke + bootstrap; `make bootstrap` reproduces byte-identical
-> because it tests stage2==stage3 determinism, not correctness-vs-golden). Root-cause
-> and fix the i128 `-O1` path before merging; add `test-parse` to the gate.
+> **✓ RESOLVED — i128 -O1 regression fixed.** `test-parse i128_06_shifts_bitwise` had
+> crashed (SIGSEGV/SIGABRT) at -O1 on the native/link paths. Root cause: Track 7.3 made
+> i128/f128 flow as scalar VALUEs, but `api_make_wide16_int_const` (`src/cg/wide.c`)
+> still returned an **lvalue/PLACE**, so an i128 constant entered the stack as a place;
+> the O1 ABI lowering then passed it by-reference and dereferenced a value slot as a
+> (mistyped 32-bit) pointer → null deref on i128→`_Bool` compares. Fix: return a VALUE
+> (`api_make_sv`), matching `api_push_call_result`'s i128 representation. Now full
+> `test-parse` is 3784/0 and bootstrap reproduces at -O0 AND -O1. **Lesson: codegen
+> gates must include `test-parse` — bootstrap reproducing ≠ correctness.**
Forward-looking companion to the canonical design in [doc/CODEGEN.md](../CODEGEN.md). Goal:
make the `CfreeCg` public API and the internal `CgTarget` contract carry **one clear
diff --git a/src/cg/wide.c b/src/cg/wide.c
@@ -38,7 +38,12 @@ ApiSValue api_make_wide16_int_const(CfreeCg* g, i64 value, CfreeCgTypeId ty) {
CGLocal local = api_f128_temp_local(g, ty);
api_wide16_sext_imm_bytes(g, value, bytes);
api_store_f128_bytes(g, local, ty, bytes);
- return api_make_lv(api_op_local(local, ty), ty);
+ /* i128/f128 are scalar VALUEs (Track 7.3), not places: the constant lives in
+ * `local` and flows as a value. Returning an lvalue here made the O1 ABI path
+ * pass the constant by-reference and deref a value slot (a null-deref crash on
+ * i128->bool compares); a value backed by the local is the correct form and
+ * matches how api_push_call_result represents an i128 result. */
+ return api_make_sv(api_op_local(local, ty), ty);
}
void api_store_f128_bytes(CfreeCg* g, CGLocal local, CfreeCgTypeId ty,