From c760b92548d1b99437b597efff4dcb8c78cbcad0 Mon Sep 17 00:00:00 2001 From: agra Date: Mon, 15 Jun 2026 23:18:37 +0300 Subject: [PATCH] issue(0138): @const address-of yields wild pointer; ASM output-to-const BLOCKED MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Filed issues/0138: `@const` (address-of a `::` comptime constant) lowers to `inttoptr (i64 to ptr)` — segfaults on deref, invalid store for asm `-> @const`. Root cause in src/ir/lower/expr.zig .address_of (not asm). Marked CHECKPOINT-ASM Next step BLOCKED on 0138 for the output-to-const rejection item. --- current/CHECKPOINT-ASM.md | 19 ++- ...s-of-comptime-const-yields-wild-pointer.md | 111 ++++++++++++++++++ 2 files changed, 128 insertions(+), 2 deletions(-) create mode 100644 issues/0138-address-of-comptime-const-yields-wild-pointer.md diff --git a/current/CHECKPOINT-ASM.md b/current/CHECKPOINT-ASM.md index f5fa987..dce5ec9 100644 --- a/current/CHECKPOINT-ASM.md +++ b/current/CHECKPOINT-ASM.md @@ -213,8 +213,17 @@ Phase E–F feasibility already confirmed against the live tree `extern`, 60 sites; `--target` a global CLI flag). ## Next step -Inline assembly is **feature-complete for the common surface** plus read-write -(`+`) place outputs. Remaining work, all optional / additive (pick any): +**BLOCKED on issue 0138** for the output-to-`const` rejection item. Starting that +item surfaced a general (non-asm) compiler bug: `@const` (address-of a `::` +comptime constant) reinterprets the const's *value* as a pointer +(`inttoptr (i64 40 to ptr)`) → segfault on deref / invalid store for asm +`-> @const`. Root cause is in `src/ir/lower/expr.zig`'s `.address_of` path, not +the asm code; fixing it gives the asm `-> @place`-to-const rejection for free. +Filed `issues/0138-address-of-comptime-const-yields-wild-pointer.md`. Do NOT +implement an asm-only workaround — wait for the address-of fix. + +Other remaining work is unaffected by 0138 and can proceed independently once +0138 is resolved (all optional / additive): - **Indirect-memory (`"=*m"`) outputs**: pass the place address as an arg, asm writes through it (no return slot). Currently rejected. - **Output-to-`const` rejection** for `-> @place` (the place must be mutable). @@ -281,6 +290,12 @@ Orthogonal: **issue 0137** (no-`main` segfault). aarch64 example (`"=r,0"` IR). `zig build test` green (658 corpus, 446 unit). ## Known issues +- **0138** — `@const` (address-of a `::` comptime constant) yields a wild pointer + (`inttoptr (i64 to ptr)`): segfaults on deref, invalid store for asm + `-> @const`. General address-of bug in `src/ir/lower/expr.zig` `.address_of`, + not asm-specific. **BLOCKS** the ASM "output-to-`const` rejection" item (fixing + 0138 gives that rejection for free). Filed + `issues/0138-address-of-comptime-const-yields-wild-pointer.md`. - **0137** — `sx run` on a program with no `main` segfaults (unguarded JIT entry lookup, `src/target.zig:256-273`). Pre-existing, asm-independent. Filed `issues/0137-jit-run-no-main-segfault.md`. Does not block A.1. diff --git a/issues/0138-address-of-comptime-const-yields-wild-pointer.md b/issues/0138-address-of-comptime-const-yields-wild-pointer.md new file mode 100644 index 0000000..a81eff4 --- /dev/null +++ b/issues/0138-address-of-comptime-const-yields-wild-pointer.md @@ -0,0 +1,111 @@ +# 0138 — `@const` (address-of a `::` comptime constant) yields a wild pointer + +**Status:** OPEN + +## Symptom + +Taking the address of a `::`-bound comptime constant (`@x` where `x :: 40`) +does **not** produce a real address. The address-of lowering falls through to +the generic `addr_of` arm, which takes the *folded constant value* and +reinterprets it as a pointer: + +```llvm +store ptr inttoptr (i64 40 to ptr), ptr %alloca, align 8 +``` + +- **Observed:** `@x` of a const lowers to `inttoptr (i64 to ptr)` — a + pointer whose numeric address IS the constant's value. Dereferencing it + segfaults (`@x` of `x :: 40` → wild pointer `0x28`). Using it as a store + destination (e.g. inline-asm `-> @x` write-through) emits invalid IR that + only the LLVM verifier catches: `Store operand must be a pointer / store i64 + %asm, i64 40`. +- **Expected:** either a clean compile diagnostic ("cannot take the address of + comptime constant `x`") or materialization of read-only backing storage so + the address is real. Never a silent reinterpret-value-as-pointer (a textbook + silent-miscompile per CLAUDE.md). + +This is **not** inline-asm-specific — it was discovered while implementing the +ASM stream's planned "output-to-`const` rejection for `-> @place`", but the +root cause is in the general address-of path. The same `-> @place`-to-const +rejection falls out for free once `@const` is handled correctly (asm lowers +`@place` through the same address-of path). + +## Reproduction + +Segfault on deref (no inline asm needed, no project deps): + +```sx +main :: () -> i64 { + x :: 40; // comptime constant — no runtime storage + p := @x; // lowers to `inttoptr (i64 40 to ptr)` — wild pointer + return p.*; // segfault (deref of 0x28) +} +``` + +The IR for just `p := @x` (no deref) shows the defect directly: + +```sx +main :: () -> i64 { + x :: 40; + p := @x; + return 7; +} +``` +→ +```llvm +%alloca = alloca ptr, align 8 +store ptr inttoptr (i64 40 to ptr), ptr %alloca, align 8 ; <-- bug +ret i32 7 +``` + +Inline-asm write-through to a const (the path that surfaced it) — invalid IR +caught by the verifier instead of a sx diagnostic: + +```sx +FORTY :: 40; +main :: () -> i64 { + asm volatile { "mov %[c], #99", [c] "=r" -> @FORTY }; + return FORTY; +} +``` +→ `LLVM verification failed: Store operand must be a pointer.` + +## Investigation prompt + +> `@const` (address-of a `::`-bound comptime constant) miscompiles: instead of +> a real address it reinterprets the constant's *value* as a pointer +> (`inttoptr (i64 to ptr)`), segfaulting on deref and producing invalid +> stores for inline-asm `-> @place` write-through to a const. +> +> **Suspected area:** `src/ir/lower/expr.zig`, the unary `.address_of` lowering. +> The clean `address_of(identifier)` path (~line 1994) only handles +> `binding.is_alloca` locals and globals (`resolveGlobalRef`). A `::` const is +> neither, so it falls through to the generic `.address_of` arm (~line 2057), +> which does `addr_of(self.lowerExpr(uop.operand))` — and `lowerExpr` of a const +> identifier folds to the constant value, so `addr_of` of an i64 constant emits +> `inttoptr`. +> +> **Fix likely needs to:** detect, in the `address_of(identifier)` path, that +> the resolved binding is a comptime constant with no storage. Then either (a) +> emit a clear diagnostic via `self.diagnostics.addFmt(.err, span, "cannot take +> the address of comptime constant `{s}`", .{name})` and return a dedicated +> sentinel (NOT a folded value) — matches CLAUDE.md's no-silent-default rule; or +> (b) materialize a read-only global/alloca for the const and return its real +> address. Decide which against `specs.md` (does sx intend `::` consts to be +> addressable at all?). Coordinate with PLAN-CONST-AGG's "const-write rejection" +> — a write through `@const` (asm `-> @place`, or a future `p.* = …`) must also +> be rejected; the read-only-storage option (b) still needs the write rejected. +> +> **Verification:** run the three repros above. Expect: repro 1 (`return p.*`) +> either fails to compile with the diagnostic, or returns 40 (if `::` consts +> become addressable); repro 3 (asm `-> @FORTY`) reports a clean sx diagnostic, +> NOT an LLVM verifier failure. Add a pinned regression under `issues/expected/` +> (or migrate to `examples/` once the behavior is decided). + +## Notes + +- Discovered mid-ASM-stream while starting the planned output-to-`const` + rejection step. Read-write `+` place outputs (the prior ASM step) shipped + green before this surfaced. +- Not covered by any existing issue or by `current/PLAN-CONST-AGG.md` (which + addresses const *writes* via assignment, not address-of).