diff --git a/examples/1177-diagnostics-addr-of-const-rejected.sx b/examples/1177-diagnostics-addr-of-const-rejected.sx new file mode 100644 index 0000000..2bcd5d2 --- /dev/null +++ b/examples/1177-diagnostics-addr-of-const-rejected.sx @@ -0,0 +1,18 @@ +// Taking the address of a scalar `::` constant is a compile error: a scalar +// constant folds to its value and has NO storage (only array/struct constants +// are immutable globals with a real address — see 0177). Covers a module-scope +// const, a local const, and an inline-asm `-> @const` write-through (the path +// that surfaced the bug). Before the fix, `@N` lowered to `inttoptr (i64 40 to +// ptr)` — a wild pointer that segfaulted on deref and emitted invalid stores +// for asm `-> @const`. Regression (issue 0138). + +takes :: (p: *i64) {} + +N :: 40; + +main :: () { + takes(@N); // module scalar const — no storage + x :: 7; + takes(@x); // local scalar const — no storage + asm volatile { "mov %[c], #99", [c] "=r" -> @N }; // write-through to a const +} diff --git a/examples/expected/1177-diagnostics-addr-of-const-rejected.exit b/examples/expected/1177-diagnostics-addr-of-const-rejected.exit new file mode 100644 index 0000000..d00491f --- /dev/null +++ b/examples/expected/1177-diagnostics-addr-of-const-rejected.exit @@ -0,0 +1 @@ +1 diff --git a/examples/expected/1177-diagnostics-addr-of-const-rejected.stderr b/examples/expected/1177-diagnostics-addr-of-const-rejected.stderr new file mode 100644 index 0000000..031ba26 --- /dev/null +++ b/examples/expected/1177-diagnostics-addr-of-const-rejected.stderr @@ -0,0 +1,17 @@ +error: cannot take the address of constant 'N' — a scalar '::' constant has no storage (use a '=' variable or a local copy for mutable data) + --> examples/1177-diagnostics-addr-of-const-rejected.sx:14:11 + | +14 | takes(@N); // module scalar const — no storage + | ^^ + +error: cannot take the address of constant 'x' — a scalar '::' constant has no storage (use a '=' variable or a local copy for mutable data) + --> examples/1177-diagnostics-addr-of-const-rejected.sx:16:11 + | +16 | takes(@x); // local scalar const — no storage + | ^^ + +error: cannot take the address of constant 'N' — a scalar '::' constant has no storage (use a '=' variable or a local copy for mutable data) + --> examples/1177-diagnostics-addr-of-const-rejected.sx:17:49 + | +17 | asm volatile { "mov %[c], #99", [c] "=r" -> @N }; // write-through to a const + | ^^ diff --git a/examples/expected/1177-diagnostics-addr-of-const-rejected.stdout b/examples/expected/1177-diagnostics-addr-of-const-rejected.stdout new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/examples/expected/1177-diagnostics-addr-of-const-rejected.stdout @@ -0,0 +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 index a81eff4..d42aa37 100644 --- a/issues/0138-address-of-comptime-const-yields-wild-pointer.md +++ b/issues/0138-address-of-comptime-const-yields-wild-pointer.md @@ -1,6 +1,25 @@ # 0138 — `@const` (address-of a `::` comptime constant) yields a wild pointer -**Status:** OPEN +**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 diff --git a/src/ir/lower/expr.zig b/src/ir/lower/expr.zig index 6f4abc1..918d11f 100644 --- a/src/ir/lower/expr.zig +++ b/src/ir/lower/expr.zig @@ -2000,6 +2000,21 @@ pub fn lowerExpr(self: *Lowering, node: *const Node) Ref { const ptr_ty = self.module.types.ptrTo(binding.ty); break :blk self.builder.emit(.{ .addr_of = .{ .operand = binding.ref } }, ptr_ty); } + // A non-storage value binding — a scalar `::` constant + // folded to its value (`is_alloca == false`, not a + // ref-capture pointer or pack-element alias). It has NO + // address: array/struct consts get real storage (reached + // via `resolveGlobalRef` below), but a scalar const does + // not. Taking `@const` would otherwise fall through to the + // generic `addr_of` arm and reinterpret the folded value + // as a pointer — `inttoptr (i64 to ptr)`, a wild + // pointer that segfaults on deref and emits invalid stores + // for asm `-> @const` (issue 0138). Diagnose loudly. + if (!binding.is_ref_capture and binding.pack_elem == null) { + if (self.diagnostics) |d| + d.addFmt(.err, node.span, "cannot take the address of constant '{s}' — a scalar '::' constant has no storage (use a '=' variable or a local copy for mutable data)", .{id_name}); + break :blk self.emitPlaceholder("addr_of_const"); + } } } // address_of(global) → emit global_addr (pointer to global, not load) @@ -2007,6 +2022,15 @@ pub fn lowerExpr(self: *Lowering, node: *const Node) Ref { const ptr_ty = self.module.types.ptrTo(gi.ty); break :blk self.builder.emit(.{ .global_addr = gi.id }, ptr_ty); } + // A module-scope scalar `::` constant (not in lexical `scope`, and + // not a storage-backed array/struct const — those resolve above). + // Same defect as the local case: without storage, `@FORTY` would + // become `inttoptr (i64 to ptr)`. Diagnose (issue 0138). + if (self.program_index.module_const_map.get(id_name) != null) { + if (self.diagnostics) |d| + d.addFmt(.err, node.span, "cannot take the address of constant '{s}' — a scalar '::' constant has no storage (use a '=' variable or a local copy for mutable data)", .{id_name}); + break :blk self.emitPlaceholder("addr_of_const"); + } } // Fold a negated integer literal into one constant: `-128` must // range-check as -128, not as an out-of-range +128 intermediate.