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:
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
|
||||
```
|
||||
Reference in New Issue
Block a user