# 0138 — `@const` (address-of a `::` comptime constant) yields a wild pointer **Status:** RESOLVED > **Resolution.** Root cause was in `src/ir/lower/expr.zig`'s unary `.address_of` > lowering: a scalar `::` constant binds a folded *value* (`is_alloca == false`, > no storage), so it skipped both the alloca path and `resolveGlobalRef`, then > fell through to the generic `addr_of` arm which reinterpreted the value as a > pointer (`inttoptr (i64 to ptr)`). Fixed by diagnosing in the > `address_of(identifier)` path — both the lexical case (a non-alloca, > non-ref-capture, non-pack-elem scope binding) and the module case (a name in > `module_const_map` that `resolveGlobalRef` did not back with storage) now emit > "cannot take the address of constant '' — a scalar '::' constant has no > storage …" and return a placeholder Ref. Chose **diagnose** over materializing > read-only storage (confirmed with the user): consistent with the fold-only > scalar model; array/struct consts keep their real storage and stay addressable > (`@K`/`@LIT` via `global_addr`, unchanged). This also gives the ASM stream's > planned "output-to-`const` rejection" for free — asm `-> @const` lowers `@place` > through the same path, so it now reports the clean diagnostic instead of an LLVM > verifier failure. Regression: `examples/1177-diagnostics-addr-of-const-rejected.sx` > (module const, local const, and asm `-> @const` write-through). `zig build test` > green (659 corpus). ## 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).