fix(0138): diagnose @scalar-const address-of (no storage)
A scalar `::` constant folds to its value and has no storage. The unary `.address_of` lowering (src/ir/lower/expr.zig) skipped the alloca path (is_alloca == false) and resolveGlobalRef (scalar consts get no storage global), falling through to the generic addr_of arm, which reinterpreted the folded value as a pointer: `inttoptr (i64 <value> to ptr)`. That wild pointer segfaulted on deref and emitted invalid stores for inline-asm `-> @const`. Diagnose instead, in the address_of(identifier) path: a non-alloca, non-ref-capture, non-pack-elem scope binding (local scalar const) and a module_const_map name not backed by storage (module scalar const) both report "cannot take the address of constant '<name>' — a scalar '::' constant has no storage …" and return a placeholder Ref. Chose diagnose over materializing read-only storage (consistent with the fold-only scalar model). Array/struct consts keep real storage and stay addressable (@K/@LIT unchanged). Also gives the ASM stream's planned output-to-const rejection for free — asm `-> @const` lowers through the same path. Regression: examples/1177-diagnostics-addr-of-const-rejected.sx. Resolves 0138.
This commit is contained in:
18
examples/1177-diagnostics-addr-of-const-rejected.sx
Normal file
18
examples/1177-diagnostics-addr-of-const-rejected.sx
Normal file
@@ -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
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
1
|
||||||
@@ -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
|
||||||
|
| ^^
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
@@ -1,6 +1,25 @@
|
|||||||
# 0138 — `@const` (address-of a `::` comptime constant) yields a wild pointer
|
# 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 <value> 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 '<name>' — 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
|
## Symptom
|
||||||
|
|
||||||
|
|||||||
@@ -2000,6 +2000,21 @@ pub fn lowerExpr(self: *Lowering, node: *const Node) Ref {
|
|||||||
const ptr_ty = self.module.types.ptrTo(binding.ty);
|
const ptr_ty = self.module.types.ptrTo(binding.ty);
|
||||||
break :blk self.builder.emit(.{ .addr_of = .{ .operand = binding.ref } }, ptr_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 <value> 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)
|
// 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);
|
const ptr_ty = self.module.types.ptrTo(gi.ty);
|
||||||
break :blk self.builder.emit(.{ .global_addr = gi.id }, ptr_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 <value> 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
|
// Fold a negated integer literal into one constant: `-128` must
|
||||||
// range-check as -128, not as an out-of-range +128 intermediate.
|
// range-check as -128, not as an out-of-range +128 intermediate.
|
||||||
|
|||||||
Reference in New Issue
Block a user