fix(ir): serialize enum-literal global initializers (issue 0082)
A module-global initialized with an enum literal silently zero-initialized to the first tag (`chosen : Color = .green` read back as `.red`), and an enum tag inside a global array/struct was rejected as non-constant. The constant serializer had no enum-literal arm. Add `Lowering.constEnumLiteral`: serialize an enum literal to a `ConstantValue.int` holding the variant's tag value, resolved against the destination enum type and respecting explicit variant values; the global's type drives the backing width at emit time. Wired into `globalInitValue` (scalar global) and `constExprValue` (array element / struct field / nested aggregate). A non-enum destination or unknown variant is diagnosed loudly, never silently zero-initialized. The compiler-injected OS/ARCH globals now serialize to their real `.unknown` tag (6 / 4); runtime reads are unchanged (they resolve through comptime_constants), so only the static initializer in the pinned .ir snapshots changes. Remove the silent `func_ref => orelse LLVMConstNull` fallbacks in the LLVM constant emitters: aggregate func_ref leaves carry a `require_resolved` flag (transient null in Pass 0, loud diagnostic if still unresolved in the Pass-1.5 re-emit), a top-level func_ref global is resolved in initVtableGlobals, and the comptime (#run) path bails loudly instead of emitting a null function pointer. Regression: examples/0139-types-global-enum-literal-init.sx (scalar, array, struct field, explicit-value enum u16 stride, struct-array with enum field); negative: examples/1127-diagnostics-global-enum-literal-bad-variant.sx. Mark issue 0082 RESOLVED.
This commit is contained in:
51
examples/0139-types-global-enum-literal-init.sx
Normal file
51
examples/0139-types-global-enum-literal-init.sx
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
// A module-global initialized with an enum literal (`.Variant`) reads back the
|
||||||
|
// declared tag — scalar, inside a global array, and as a struct field, for both
|
||||||
|
// a plain enum (tag == declaration index) and an explicit-value enum (`enum u16
|
||||||
|
// { ok :: 200; ... }`, larger backing for element-stride coverage).
|
||||||
|
// Regression (issue 0082): `globalInitValue` had an `.enum_literal => null`
|
||||||
|
// carve-out (kept for the compiler-injected `OS`/`ARCH` globals) that silently
|
||||||
|
// zero-initialized EVERY enum global to the first tag — so `chosen : Color =
|
||||||
|
// .green` read back as `.red` — while a global array/struct of enums was
|
||||||
|
// rejected outright as non-constant. The fix serializes the enum literal to its
|
||||||
|
// tag value (respecting explicit variant values) against the destination enum
|
||||||
|
// type, for the scalar global, the array element, and the nested aggregate
|
||||||
|
// field. (Explicit-value enums print as `.` because the `{}` formatter indexes
|
||||||
|
// variants by position — a separate, pre-existing limitation — so those are
|
||||||
|
// asserted by equality, not by their printed name.)
|
||||||
|
|
||||||
|
#import "modules/std.sx";
|
||||||
|
|
||||||
|
Color :: enum u8 { red; green; blue; }
|
||||||
|
Code :: enum u16 { ok :: 200; not_found :: 404; teapot :: 418; }
|
||||||
|
|
||||||
|
Pair :: struct { a: Color; b: Color; }
|
||||||
|
Row :: struct { status: Code; pad: s64; }
|
||||||
|
|
||||||
|
// scalar enum global
|
||||||
|
chosen : Color = .green;
|
||||||
|
// global array of enum
|
||||||
|
palette : [3]Color = .[ .blue, .green, .red ];
|
||||||
|
// enum field(s) inside a global struct
|
||||||
|
pair : Pair = .{ a = .blue, b = .green };
|
||||||
|
// explicit-value enum: scalar, array (2-byte stride), and inside a struct array
|
||||||
|
status : Code = .teapot;
|
||||||
|
codes : [3]Code = .[ .ok, .not_found, .teapot ];
|
||||||
|
rows : [2]Row = .[ .{ status = .not_found, pad = 11 }, .{ status = .teapot, pad = 22 } ];
|
||||||
|
|
||||||
|
main :: () {
|
||||||
|
print("chosen={}\n", chosen);
|
||||||
|
print("palette={},{},{}\n", palette[0], palette[1], palette[2]);
|
||||||
|
print("pair.a={} pair.b={}\n", pair.a, pair.b);
|
||||||
|
|
||||||
|
if chosen == .green
|
||||||
|
and palette[0] == .blue and palette[1] == .green and palette[2] == .red
|
||||||
|
and pair.a == .blue and pair.b == .green
|
||||||
|
and status == .teapot
|
||||||
|
and codes[0] == .ok and codes[1] == .not_found and codes[2] == .teapot
|
||||||
|
and rows[0].status == .not_found and rows[0].pad == 11
|
||||||
|
and rows[1].status == .teapot and rows[1].pad == 22 {
|
||||||
|
print("PASS\n");
|
||||||
|
} else {
|
||||||
|
print("FAIL: global enum-literal initializer mis-serialized\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
16
examples/1127-diagnostics-global-enum-literal-bad-variant.sx
Normal file
16
examples/1127-diagnostics-global-enum-literal-bad-variant.sx
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
// A module-global enum-literal initializer naming a variant that does not exist
|
||||||
|
// must be rejected loudly — never silently zero-initialized to the first tag.
|
||||||
|
// Regression (issue 0082): the enum-literal global serializer resolves the tag
|
||||||
|
// against the destination enum type; an unknown variant emits a diagnostic and
|
||||||
|
// fails the build instead of falling back to a null (zero-tag) initializer.
|
||||||
|
// Expected: "'.purple' is not a variant of enum 'Color'"; exit 1.
|
||||||
|
|
||||||
|
#import "modules/std.sx";
|
||||||
|
|
||||||
|
Color :: enum u8 { red; green; blue; }
|
||||||
|
bad : Color = .purple;
|
||||||
|
|
||||||
|
main :: () -> s32 {
|
||||||
|
print("{}\n", bad);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
|
|
||||||
@OS = internal global i64 0
|
@OS = internal global i64 6
|
||||||
@ARCH = internal global i64 0
|
@ARCH = internal global i64 4
|
||||||
@POINTER_SIZE = internal global i64 8
|
@POINTER_SIZE = internal global i64 8
|
||||||
@__sx_default_context = internal global { { ptr, ptr, ptr }, ptr } { { ptr, ptr, ptr } { ptr null, ptr @__thunk_CAllocator_Allocator_alloc, ptr @__thunk_CAllocator_Allocator_dealloc }, ptr null }
|
@__sx_default_context = internal global { { ptr, ptr, ptr }, ptr } { { ptr, ptr, ptr } { ptr null, ptr @__thunk_CAllocator_Allocator_alloc, ptr @__thunk_CAllocator_Allocator_dealloc }, ptr null }
|
||||||
@str = private unnamed_addr constant [2 x i8] c"0\00", align 1
|
@str = private unnamed_addr constant [2 x i8] c"0\00", align 1
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
|
|
||||||
@OS = internal global i64 0
|
@OS = internal global i64 6
|
||||||
@ARCH = internal global i64 0
|
@ARCH = internal global i64 4
|
||||||
@POINTER_SIZE = internal global i64 8
|
@POINTER_SIZE = internal global i64 8
|
||||||
@__sx_default_context = internal global { { ptr, ptr, ptr }, ptr } { { ptr, ptr, ptr } { ptr null, ptr @__thunk_CAllocator_Allocator_alloc, ptr @__thunk_CAllocator_Allocator_dealloc }, ptr null }
|
@__sx_default_context = internal global { { ptr, ptr, ptr }, ptr } { { ptr, ptr, ptr } { ptr null, ptr @__thunk_CAllocator_Allocator_alloc, ptr @__thunk_CAllocator_Allocator_dealloc }, ptr null }
|
||||||
@str = private unnamed_addr constant [2 x i8] c"0\00", align 1
|
@str = private unnamed_addr constant [2 x i8] c"0\00", align 1
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
0
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
chosen=.green
|
||||||
|
palette=.blue,.green,.red
|
||||||
|
pair.a=.blue pair.b=.green
|
||||||
|
PASS
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
|
|
||||||
@OS = internal global i64 0
|
@OS = internal global i64 6
|
||||||
@ARCH = internal global i64 0
|
@ARCH = internal global i64 4
|
||||||
@POINTER_SIZE = internal global i64 8
|
@POINTER_SIZE = internal global i64 8
|
||||||
@__sx_default_context = internal global { { ptr, ptr, ptr }, ptr } { { ptr, ptr, ptr } { ptr null, ptr @__thunk_CAllocator_Allocator_alloc, ptr @__thunk_CAllocator_Allocator_dealloc }, ptr null }
|
@__sx_default_context = internal global { { ptr, ptr, ptr }, ptr } { { ptr, ptr, ptr } { ptr null, ptr @__thunk_CAllocator_Allocator_alloc, ptr @__thunk_CAllocator_Allocator_dealloc }, ptr null }
|
||||||
@__Counter__SimpleCounter__vtable = internal constant { ptr, ptr } { ptr @__thunk_SimpleCounter_Counter_inc, ptr @__thunk_SimpleCounter_Counter_get }
|
@__Counter__SimpleCounter__vtable = internal constant { ptr, ptr } { ptr @__thunk_SimpleCounter_Counter_inc, ptr @__thunk_SimpleCounter_Counter_get }
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
1
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
error: '.purple' is not a variant of enum 'Color'
|
||||||
|
--> examples/1127-diagnostics-global-enum-literal-bad-variant.sx:11:15
|
||||||
|
|
|
||||||
|
11 | bad : Color = .purple;
|
||||||
|
| ^^^^^^^
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
|
|
||||||
@__SxFoo_state_ivar = internal global ptr null
|
@__SxFoo_state_ivar = internal global ptr null
|
||||||
@__SxFoo_class = internal global ptr null
|
@__SxFoo_class = internal global ptr null
|
||||||
@OS = internal global i64 0
|
@OS = internal global i64 6
|
||||||
@ARCH = internal global i64 0
|
@ARCH = internal global i64 4
|
||||||
@POINTER_SIZE = internal global i64 8
|
@POINTER_SIZE = internal global i64 8
|
||||||
@__sx_default_context = internal global { { ptr, ptr, ptr }, ptr } { { ptr, ptr, ptr } { ptr null, ptr @__thunk_CAllocator_Allocator_alloc, ptr @__thunk_CAllocator_Allocator_dealloc }, ptr null }
|
@__sx_default_context = internal global { { ptr, ptr, ptr }, ptr } { { ptr, ptr, ptr } { ptr null, ptr @__thunk_CAllocator_Allocator_alloc, ptr @__thunk_CAllocator_Allocator_dealloc }, ptr null }
|
||||||
@__sx_objc_cstr_dealloc = internal global [8 x i8] c"dealloc\00"
|
@__sx_objc_cstr_dealloc = internal global [8 x i8] c"dealloc\00"
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
|
|
||||||
@__SxFoo_state_ivar = internal global ptr null
|
@__SxFoo_state_ivar = internal global ptr null
|
||||||
@__SxFoo_class = internal global ptr null
|
@__SxFoo_class = internal global ptr null
|
||||||
@OS = internal global i64 0
|
@OS = internal global i64 6
|
||||||
@ARCH = internal global i64 0
|
@ARCH = internal global i64 4
|
||||||
@POINTER_SIZE = internal global i64 8
|
@POINTER_SIZE = internal global i64 8
|
||||||
@__sx_default_context = internal global { { ptr, ptr, ptr }, ptr } { { ptr, ptr, ptr } { ptr null, ptr @__thunk_CAllocator_Allocator_alloc, ptr @__thunk_CAllocator_Allocator_dealloc }, ptr null }
|
@__sx_default_context = internal global { { ptr, ptr, ptr }, ptr } { { ptr, ptr, ptr } { ptr null, ptr @__thunk_CAllocator_Allocator_alloc, ptr @__thunk_CAllocator_Allocator_dealloc }, ptr null }
|
||||||
@__sx_objc_cstr_dealloc = internal global [8 x i8] c"dealloc\00"
|
@__sx_objc_cstr_dealloc = internal global [8 x i8] c"dealloc\00"
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
|
|
||||||
@__SxBox_state_ivar = internal global ptr null
|
@__SxBox_state_ivar = internal global ptr null
|
||||||
@__SxBox_class = internal global ptr null
|
@__SxBox_class = internal global ptr null
|
||||||
@OS = internal global i64 0
|
@OS = internal global i64 6
|
||||||
@ARCH = internal global i64 0
|
@ARCH = internal global i64 4
|
||||||
@POINTER_SIZE = internal global i64 8
|
@POINTER_SIZE = internal global i64 8
|
||||||
@__sx_default_context = internal global { { ptr, ptr, ptr }, ptr } { { ptr, ptr, ptr } { ptr null, ptr @__thunk_CAllocator_Allocator_alloc, ptr @__thunk_CAllocator_Allocator_dealloc }, ptr null }
|
@__sx_default_context = internal global { { ptr, ptr, ptr }, ptr } { { ptr, ptr, ptr } { ptr null, ptr @__thunk_CAllocator_Allocator_alloc, ptr @__thunk_CAllocator_Allocator_dealloc }, ptr null }
|
||||||
@OBJC_CLASSLIST_REFERENCES_SxBox = internal global ptr null
|
@OBJC_CLASSLIST_REFERENCES_SxBox = internal global ptr null
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
|
|
||||||
@OS = internal global i64 0
|
@OS = internal global i64 6
|
||||||
@ARCH = internal global i64 0
|
@ARCH = internal global i64 4
|
||||||
@POINTER_SIZE = internal global i64 8
|
@POINTER_SIZE = internal global i64 8
|
||||||
@__sx_default_context = internal global { { ptr, ptr, ptr }, ptr } { { ptr, ptr, ptr } { ptr null, ptr @__thunk_CAllocator_Allocator_alloc, ptr @__thunk_CAllocator_Allocator_dealloc }, ptr null }
|
@__sx_default_context = internal global { { ptr, ptr, ptr }, ptr } { { ptr, ptr, ptr } { ptr null, ptr @__thunk_CAllocator_Allocator_alloc, ptr @__thunk_CAllocator_Allocator_dealloc }, ptr null }
|
||||||
@OBJC_SELECTOR_REFERENCES_init = internal global ptr null
|
@OBJC_SELECTOR_REFERENCES_init = internal global ptr null
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
|
|
||||||
@OS = internal global i64 0
|
@OS = internal global i64 6
|
||||||
@ARCH = internal global i64 0
|
@ARCH = internal global i64 4
|
||||||
@POINTER_SIZE = internal global i64 8
|
@POINTER_SIZE = internal global i64 8
|
||||||
@__sx_default_context = internal global { { ptr, ptr, ptr }, ptr } { { ptr, ptr, ptr } { ptr null, ptr @__thunk_CAllocator_Allocator_alloc, ptr @__thunk_CAllocator_Allocator_dealloc }, ptr null }
|
@__sx_default_context = internal global { { ptr, ptr, ptr }, ptr } { { ptr, ptr, ptr } { ptr null, ptr @__thunk_CAllocator_Allocator_alloc, ptr @__thunk_CAllocator_Allocator_dealloc }, ptr null }
|
||||||
@OBJC_SELECTOR_REFERENCES_tripleValue = internal global ptr null
|
@OBJC_SELECTOR_REFERENCES_tripleValue = internal global ptr null
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
|
|
||||||
@OS = internal global i64 0
|
@OS = internal global i64 6
|
||||||
@ARCH = internal global i64 0
|
@ARCH = internal global i64 4
|
||||||
@POINTER_SIZE = internal global i64 8
|
@POINTER_SIZE = internal global i64 8
|
||||||
@__sx_default_context = internal global { { ptr, ptr, ptr }, ptr } { { ptr, ptr, ptr } { ptr null, ptr @__thunk_CAllocator_Allocator_alloc, ptr @__thunk_CAllocator_Allocator_dealloc }, ptr null }
|
@__sx_default_context = internal global { { ptr, ptr, ptr }, ptr } { { ptr, ptr, ptr } { ptr null, ptr @__thunk_CAllocator_Allocator_alloc, ptr @__thunk_CAllocator_Allocator_dealloc }, ptr null }
|
||||||
@OBJC_SELECTOR_REFERENCES_length = internal global ptr null
|
@OBJC_SELECTOR_REFERENCES_length = internal global ptr null
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
|
|
||||||
@OS = internal global i64 0
|
@OS = internal global i64 6
|
||||||
@ARCH = internal global i64 0
|
@ARCH = internal global i64 4
|
||||||
@POINTER_SIZE = internal global i64 8
|
@POINTER_SIZE = internal global i64 8
|
||||||
@g_held_view = internal global ptr null
|
@g_held_view = internal global ptr null
|
||||||
@__sx_default_context = internal global { { ptr, ptr, ptr }, ptr } { { ptr, ptr, ptr } { ptr null, ptr @__thunk_CAllocator_Allocator_alloc, ptr @__thunk_CAllocator_Allocator_dealloc }, ptr null }
|
@__sx_default_context = internal global { { ptr, ptr, ptr }, ptr } { { ptr, ptr, ptr } { ptr null, ptr @__thunk_CAllocator_Allocator_alloc, ptr @__thunk_CAllocator_Allocator_dealloc }, ptr null }
|
||||||
|
|||||||
110
issues/0082-global-enum-literal-initializer-zeroes.md
Normal file
110
issues/0082-global-enum-literal-initializer-zeroes.md
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
# 0082 - global enum-literal initializer silently zero-initializes
|
||||||
|
|
||||||
|
> **RESOLVED.**
|
||||||
|
> **Root cause:** `Lowering.globalInitValue` (`src/ir/lower.zig`) carried an
|
||||||
|
> `.enum_literal => null` carve-out: any enum-literal global initializer returned
|
||||||
|
> a null payload, which the LLVM/interp emitters turn into a zero-initialized
|
||||||
|
> global — so `chosen : Color = .green` read back as the first tag (`.red`).
|
||||||
|
> `constExprValue` had no enum-literal arm either, so an enum tag inside a global
|
||||||
|
> array (`[2]Color = .[.green, .blue]`) or struct field made the whole aggregate
|
||||||
|
> look non-constant and the global was rejected outright.
|
||||||
|
> **Fix:** a new `Lowering.constEnumLiteral` serializes an enum literal to a
|
||||||
|
> `ConstantValue.int` holding the variant's tag value, resolved against the
|
||||||
|
> destination enum type and respecting explicit variant values (`enum { a; b ::
|
||||||
|
> 5; }`); the global's type drives the backing width at emit time. Wired into both
|
||||||
|
> `globalInitValue` (scalar global) and `constExprValue` (array element / struct
|
||||||
|
> field / nested aggregate). A non-enum destination or an unknown variant is
|
||||||
|
> diagnosed loudly — never silently zero-initialized. The compiler-injected
|
||||||
|
> `OS`/`ARCH` globals now serialize to their real `.unknown` tag (6 / 4) instead
|
||||||
|
> of relying on the null→zero fallback; runtime reads are unchanged because they
|
||||||
|
> resolve through `comptime_constants`. As part of the same exhaustiveness pass,
|
||||||
|
> the silent `func_ref => … orelse LLVMConstNull` fallbacks in the LLVM constant
|
||||||
|
> emitters (`src/ir/emit_llvm.zig`) were removed: aggregate func_ref leaves carry
|
||||||
|
> a `require_resolved` flag (transient null in Pass 0, loud diagnostic if still
|
||||||
|
> unresolved in the Pass-1.5 re-emit), a top-level func_ref global is resolved in
|
||||||
|
> `initVtableGlobals`, and the comptime (`#run`) path bails loudly instead of
|
||||||
|
> emitting a null function pointer.
|
||||||
|
> **Regression:** `examples/0139-types-global-enum-literal-init.sx` (scalar enum
|
||||||
|
> global, global array of enum, enum struct field, explicit-value `enum u16` for
|
||||||
|
> element-stride, struct-array with enum field) — FAILS on the pre-fix compiler
|
||||||
|
> (wrong tag / rejected as non-constant), PASSES after. Negative:
|
||||||
|
> `examples/1127-diagnostics-global-enum-literal-bad-variant.sx` (unknown variant
|
||||||
|
> rejected loudly, exit 1).
|
||||||
|
|
||||||
|
## Symptom
|
||||||
|
|
||||||
|
A module-global enum initialized with a non-zero enum literal silently reads back
|
||||||
|
as the zero tag. Observed: `chosen : Color = .green;` prints `.red` and the
|
||||||
|
program exits 1. Expected: it should print `.green` and exit 0, or the compiler
|
||||||
|
should reject unsupported enum-literal global initializers loudly instead of
|
||||||
|
zero-initializing.
|
||||||
|
|
||||||
|
## Reproduction
|
||||||
|
|
||||||
|
```sx
|
||||||
|
#import "modules/std.sx";
|
||||||
|
|
||||||
|
Color :: enum u8 { red; green; blue; }
|
||||||
|
|
||||||
|
chosen : Color = .green;
|
||||||
|
|
||||||
|
main :: () -> s32 {
|
||||||
|
print("chosen={}\n", chosen);
|
||||||
|
if chosen == .green {
|
||||||
|
print("PASS\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
print("FAIL\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Observed:
|
||||||
|
|
||||||
|
```text
|
||||||
|
chosen=.red
|
||||||
|
FAIL
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected:
|
||||||
|
|
||||||
|
```text
|
||||||
|
chosen=.green
|
||||||
|
PASS
|
||||||
|
```
|
||||||
|
|
||||||
|
## Investigation prompt
|
||||||
|
|
||||||
|
Fix issue 0082: module-global enum literal initializers silently become the zero
|
||||||
|
tag.
|
||||||
|
|
||||||
|
Suspected area:
|
||||||
|
- `src/ir/lower.zig`, `Lowering.globalInitValue`: the `.enum_literal => null`
|
||||||
|
carve-out preserves the stdlib's historical zero-init path for compiler-
|
||||||
|
injected `OS : OperatingSystem = .unknown`, but it also silently drops any
|
||||||
|
user-written non-zero enum literal such as `.green`.
|
||||||
|
- `src/ir/lower.zig`, `Lowering.constExprValue`: aggregate enum-literal fields
|
||||||
|
are currently not serialized either, so audit both top-level and aggregate
|
||||||
|
enum literals.
|
||||||
|
|
||||||
|
Likely fix:
|
||||||
|
- Resolve the destination enum type from `var_ty` / `expected_ty` and serialize
|
||||||
|
the enum tag as a `ConstantValue.int` with the variant index/value.
|
||||||
|
- If a particular enum literal shape cannot be serialized yet (payload variants,
|
||||||
|
unsupported explicit tag values, etc.), emit a diagnostic instead of returning
|
||||||
|
`null`.
|
||||||
|
- Preserve the compiler-injected `OperatingSystem` / `Architecture` behavior by
|
||||||
|
making those globals real constants, not by relying on null initializer
|
||||||
|
fallback.
|
||||||
|
|
||||||
|
Verification:
|
||||||
|
- Run the repro above and expect `chosen=.green` / `PASS` / exit 0.
|
||||||
|
- Add a pinned regression in the `01xx` types block for a non-zero enum global
|
||||||
|
and, if supported by the fix, an enum field inside a global aggregate.
|
||||||
|
- Run:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
zig build
|
||||||
|
zig build test
|
||||||
|
bash tests/run_examples.sh
|
||||||
|
```
|
||||||
@@ -900,14 +900,16 @@ pub const LLVMEmitter = struct {
|
|||||||
.float => |v| c.LLVMConstReal(llvm_ty, v),
|
.float => |v| c.LLVMConstReal(llvm_ty, v),
|
||||||
.boolean => |v| c.LLVMConstInt(llvm_ty, @intFromBool(v), 0),
|
.boolean => |v| c.LLVMConstInt(llvm_ty, @intFromBool(v), 0),
|
||||||
.string => |sid| self.emitConstStringGlobal(self.ir_mod.types.getString(sid)),
|
.string => |sid| self.emitConstStringGlobal(self.ir_mod.types.getString(sid)),
|
||||||
.aggregate => |agg| self.emitConstAggregate(agg, llvm_ty),
|
.aggregate => |agg| self.emitConstAggregate(agg, llvm_ty, false),
|
||||||
.vtable => c.LLVMConstNull(llvm_ty), // placeholder — initialized in initVtableGlobals after function declarations
|
.vtable => c.LLVMConstNull(llvm_ty), // placeholder — initialized in initVtableGlobals after function declarations
|
||||||
// A top-level null-pointer global (`p : *s64 = null;`) and a
|
// A top-level null-pointer global (`p : *s64 = null;`) and a
|
||||||
// zero-initialized global both emit as the all-zero constant
|
// zero-initialized global both emit as the all-zero constant
|
||||||
// of the global's type (issue 0081).
|
// of the global's type (issue 0081).
|
||||||
.null_val, .zeroinit => c.LLVMConstNull(llvm_ty),
|
.null_val, .zeroinit => c.LLVMConstNull(llvm_ty),
|
||||||
.undef => c.LLVMGetUndef(llvm_ty),
|
.undef => c.LLVMGetUndef(llvm_ty),
|
||||||
.func_ref => |fid| self.func_map.get(fid.index()) orelse c.LLVMConstNull(llvm_ty),
|
// func_map is empty in Pass 0 (functions are declared in
|
||||||
|
// Pass 1). Emit a placeholder and resolve in initVtableGlobals.
|
||||||
|
.func_ref => c.LLVMConstNull(llvm_ty),
|
||||||
};
|
};
|
||||||
c.LLVMSetInitializer(llvm_global, init_val);
|
c.LLVMSetInitializer(llvm_global, init_val);
|
||||||
} else {
|
} else {
|
||||||
@@ -946,10 +948,22 @@ pub const LLVMEmitter = struct {
|
|||||||
.aggregate => |agg| {
|
.aggregate => |agg| {
|
||||||
// Re-emit. The first pass in `emitGlobals` already ran,
|
// Re-emit. The first pass in `emitGlobals` already ran,
|
||||||
// but func_ref leaves resolved to null then (func_map
|
// but func_ref leaves resolved to null then (func_map
|
||||||
// wasn't populated yet). Now they resolve properly.
|
// wasn't populated yet). Now they must resolve — a still-
|
||||||
const init_val = self.emitConstAggregate(agg, llvm_ty);
|
// unresolved func_ref here is a loud diagnostic, never a
|
||||||
|
// silent null.
|
||||||
|
const init_val = self.emitConstAggregate(agg, llvm_ty, true);
|
||||||
c.LLVMSetInitializer(llvm_global, init_val);
|
c.LLVMSetInitializer(llvm_global, init_val);
|
||||||
},
|
},
|
||||||
|
.func_ref => |fid| {
|
||||||
|
const llvm_func = self.func_map.get(fid.index()) orelse {
|
||||||
|
std.debug.print(
|
||||||
|
"error: global '{s}' references function #{d} which has no declaration\n",
|
||||||
|
.{ self.ir_mod.types.getString(global.name), fid.index() },
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
c.LLVMSetInitializer(llvm_global, llvm_func);
|
||||||
|
},
|
||||||
else => continue,
|
else => continue,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1009,7 +1023,17 @@ pub const LLVMEmitter = struct {
|
|||||||
.boolean => |v| c.LLVMConstInt(llvm_ty, @intFromBool(v), 0),
|
.boolean => |v| c.LLVMConstInt(llvm_ty, @intFromBool(v), 0),
|
||||||
.null_val => c.LLVMConstNull(llvm_ty),
|
.null_val => c.LLVMConstNull(llvm_ty),
|
||||||
.void_val, .undef => c.LLVMGetUndef(llvm_ty),
|
.void_val, .undef => c.LLVMGetUndef(llvm_ty),
|
||||||
.func_ref => |fid| self.func_map.get(fid.index()) orelse c.LLVMConstNull(llvm_ty),
|
// Comptime globals are serialized here in Pass 0, before functions
|
||||||
|
// are declared (Pass 1) and with no later re-emit. A func_ref can
|
||||||
|
// therefore never resolve to a real function pointer at this point;
|
||||||
|
// bail loudly rather than ship a silently-null function pointer.
|
||||||
|
.func_ref => |fid| blk: {
|
||||||
|
std.debug.print(
|
||||||
|
"error: comptime init of '{s}' produced a reference to function #{d}, which cannot be serialized as a static constant (function declarations are not available at global-init time)\n",
|
||||||
|
.{ global_name, fid.index() },
|
||||||
|
);
|
||||||
|
break :blk c.LLVMGetUndef(llvm_ty);
|
||||||
|
},
|
||||||
.string => |s| self.emitConstStringGlobal(s),
|
.string => |s| self.emitConstStringGlobal(s),
|
||||||
.aggregate => |fields| self.serializeAggregateValue(fields, ty, interp, global_name),
|
.aggregate => |fields| self.serializeAggregateValue(fields, ty, interp, global_name),
|
||||||
// The remaining Value variants cannot become static binary
|
// The remaining Value variants cannot become static binary
|
||||||
@@ -2420,7 +2444,13 @@ pub const LLVMEmitter = struct {
|
|||||||
return c.LLVMConstStructInContext(self.context, &fields, 2, 0);
|
return c.LLVMConstStructInContext(self.context, &fields, 2, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn emitConstAggregate(self: *LLVMEmitter, agg: []const ir_inst.ConstantValue, llvm_ty: c.LLVMTypeRef) c.LLVMValueRef {
|
/// Serialize a constant aggregate to an LLVM constant. `require_resolved`
|
||||||
|
/// governs the func_ref leaves: in Pass 0 (`emitGlobals`) func_map is empty,
|
||||||
|
/// so func_refs are left as a transient null placeholder (`false`) and the
|
||||||
|
/// whole aggregate is re-emitted by `initVtableGlobals` after Pass 1 with
|
||||||
|
/// `true`, where any still-unresolved func_ref is a loud diagnostic — never
|
||||||
|
/// a silently-null function pointer.
|
||||||
|
fn emitConstAggregate(self: *LLVMEmitter, agg: []const ir_inst.ConstantValue, llvm_ty: c.LLVMTypeRef, require_resolved: bool) c.LLVMValueRef {
|
||||||
const kind = c.LLVMGetTypeKind(llvm_ty);
|
const kind = c.LLVMGetTypeKind(llvm_ty);
|
||||||
const is_struct = kind == c.LLVMStructTypeKind;
|
const is_struct = kind == c.LLVMStructTypeKind;
|
||||||
const n: c_uint = @intCast(agg.len);
|
const n: c_uint = @intCast(agg.len);
|
||||||
@@ -2436,8 +2466,14 @@ pub const LLVMEmitter = struct {
|
|||||||
.float => |v| c.LLVMConstReal(elem_ty, v),
|
.float => |v| c.LLVMConstReal(elem_ty, v),
|
||||||
.boolean => |v| c.LLVMConstInt(elem_ty, @intFromBool(v), 0),
|
.boolean => |v| c.LLVMConstInt(elem_ty, @intFromBool(v), 0),
|
||||||
.string => |sid| self.emitConstStringGlobal(self.ir_mod.types.getString(sid)),
|
.string => |sid| self.emitConstStringGlobal(self.ir_mod.types.getString(sid)),
|
||||||
.aggregate => |inner| self.emitConstAggregate(inner, elem_ty),
|
.aggregate => |inner| self.emitConstAggregate(inner, elem_ty, require_resolved),
|
||||||
.func_ref => |fid| self.func_map.get(fid.index()) orelse c.LLVMConstNull(elem_ty),
|
.func_ref => |fid| self.func_map.get(fid.index()) orelse blk: {
|
||||||
|
if (require_resolved) std.debug.print(
|
||||||
|
"error: static initializer references function #{d} which has no declaration\n",
|
||||||
|
.{fid.index()},
|
||||||
|
);
|
||||||
|
break :blk c.LLVMConstNull(elem_ty);
|
||||||
|
},
|
||||||
// A null pointer field and a zero-initialized field both emit as
|
// A null pointer field and a zero-initialized field both emit as
|
||||||
// the all-zero constant of the leaf type (issue 0081).
|
// the all-zero constant of the leaf type (issue 0081).
|
||||||
.null_val, .zeroinit => c.LLVMConstNull(elem_ty),
|
.null_val, .zeroinit => c.LLVMConstNull(elem_ty),
|
||||||
|
|||||||
@@ -940,11 +940,12 @@ pub const Lowering = struct {
|
|||||||
d.addFmt(.err, v.span, "global '{s}' must be initialized by a compile-time constant; '{s}' is not a usable constant here", .{ vd.name, id.name });
|
d.addFmt(.err, v.span, "global '{s}' must be initialized by a compile-time constant; '{s}' is not a usable constant here", .{ vd.name, id.name });
|
||||||
break :blk null;
|
break :blk null;
|
||||||
},
|
},
|
||||||
// Enum-literal shorthand globals (`OS : OperatingSystem = .unknown;`)
|
// An enum-literal global (`chosen : Color = .green;`) serializes to
|
||||||
// keep their established zero-init: it is load-bearing for
|
// the variant's tag value against the destination enum type (issue
|
||||||
// compile-time `inline if OS == .X` in the stdlib (issue 0071 scope
|
// 0082). The compiler-injected `OS`/`ARCH` globals flow through here
|
||||||
// note). Carved out explicitly — not folded into a silent fallthrough.
|
// too; their runtime reads resolve via `comptime_constants`, so the
|
||||||
.enum_literal => null,
|
// serialized tag only affects the static initializer.
|
||||||
|
.enum_literal => |el| self.constEnumLiteral(&el, var_ty, v.span),
|
||||||
// Any other initializer shape (`.field_access` on a const, a call, an
|
// Any other initializer shape (`.field_access` on a const, a call, an
|
||||||
// arithmetic expression, …) is not a static constant the compiler can
|
// arithmetic expression, …) is not a static constant the compiler can
|
||||||
// evaluate here. Diagnose loudly rather than emit a null payload that
|
// evaluate here. Diagnose loudly rather than emit a null payload that
|
||||||
@@ -1047,10 +1048,44 @@ pub const Lowering = struct {
|
|||||||
},
|
},
|
||||||
.array_literal => |al| self.constArrayLiteral(al.elements, expected_ty),
|
.array_literal => |al| self.constArrayLiteral(al.elements, expected_ty),
|
||||||
.struct_literal => |sl| self.constStructLiteral(&sl, expected_ty),
|
.struct_literal => |sl| self.constStructLiteral(&sl, expected_ty),
|
||||||
|
// An enum tag as an aggregate leaf (`[2]Color = .[.green, .blue]`, or
|
||||||
|
// an enum field inside a global struct) serializes to its tag int
|
||||||
|
// against the leaf's declared enum type (issue 0082).
|
||||||
|
.enum_literal => |el| self.constEnumLiteral(&el, expected_ty, expr.span),
|
||||||
else => null,
|
else => null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Serialize an enum-literal initializer (`.Variant`) into a static
|
||||||
|
/// `ConstantValue.int` holding the variant's tag value, resolved against the
|
||||||
|
/// destination enum type `ty`. The tag respects explicit variant values
|
||||||
|
/// (`enum { a; b :: 5; }`); the enum's backing width is applied by the
|
||||||
|
/// const emitters via the destination type's LLVM type. Plain enums only —
|
||||||
|
/// a tagged-union or non-enum destination is diagnosed loudly rather than
|
||||||
|
/// silently zero-initialized (issue 0082).
|
||||||
|
fn constEnumLiteral(self: *Lowering, el: *const ast.EnumLiteral, ty: TypeId, span: ast.Span) ?inst_mod.ConstantValue {
|
||||||
|
if (!ty.isBuiltin()) {
|
||||||
|
const info = self.module.types.get(ty);
|
||||||
|
if (info == .@"enum") {
|
||||||
|
const e = info.@"enum";
|
||||||
|
const name_id = self.module.types.internString(el.name);
|
||||||
|
for (e.variants, 0..) |variant, i| {
|
||||||
|
if (variant != name_id) continue;
|
||||||
|
if (e.explicit_values) |vals| {
|
||||||
|
if (i < vals.len) return .{ .int = vals[i] };
|
||||||
|
}
|
||||||
|
return .{ .int = @intCast(i) };
|
||||||
|
}
|
||||||
|
if (self.diagnostics) |d|
|
||||||
|
d.addFmt(.err, span, "'.{s}' is not a variant of enum '{s}'", .{ el.name, self.module.types.getString(e.name) });
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (self.diagnostics) |d|
|
||||||
|
d.addFmt(.err, span, "enum-literal global initializer '.{s}' is only supported for a plain enum destination type", .{el.name});
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/// Try to convert a struct literal into a compile-time ConstantValue.aggregate of the
|
/// Try to convert a struct literal into a compile-time ConstantValue.aggregate of the
|
||||||
/// struct's fields in declaration order, filling missing fields from the struct's
|
/// struct's fields in declaration order, filling missing fields from the struct's
|
||||||
/// field defaults. Returns null if any value is not constant-foldable.
|
/// field defaults. Returns null if any value is not constant-foldable.
|
||||||
|
|||||||
Reference in New Issue
Block a user