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:
@@ -900,14 +900,16 @@ pub const LLVMEmitter = struct {
|
||||
.float => |v| c.LLVMConstReal(llvm_ty, v),
|
||||
.boolean => |v| c.LLVMConstInt(llvm_ty, @intFromBool(v), 0),
|
||||
.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
|
||||
// A top-level null-pointer global (`p : *s64 = null;`) and a
|
||||
// zero-initialized global both emit as the all-zero constant
|
||||
// of the global's type (issue 0081).
|
||||
.null_val, .zeroinit => c.LLVMConstNull(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);
|
||||
} else {
|
||||
@@ -946,10 +948,22 @@ pub const LLVMEmitter = struct {
|
||||
.aggregate => |agg| {
|
||||
// Re-emit. The first pass in `emitGlobals` already ran,
|
||||
// but func_ref leaves resolved to null then (func_map
|
||||
// wasn't populated yet). Now they resolve properly.
|
||||
const init_val = self.emitConstAggregate(agg, llvm_ty);
|
||||
// wasn't populated yet). Now they must resolve — a still-
|
||||
// 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);
|
||||
},
|
||||
.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,
|
||||
}
|
||||
}
|
||||
@@ -1009,7 +1023,17 @@ pub const LLVMEmitter = struct {
|
||||
.boolean => |v| c.LLVMConstInt(llvm_ty, @intFromBool(v), 0),
|
||||
.null_val => c.LLVMConstNull(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),
|
||||
.aggregate => |fields| self.serializeAggregateValue(fields, ty, interp, global_name),
|
||||
// The remaining Value variants cannot become static binary
|
||||
@@ -2420,7 +2444,13 @@ pub const LLVMEmitter = struct {
|
||||
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 is_struct = kind == c.LLVMStructTypeKind;
|
||||
const n: c_uint = @intCast(agg.len);
|
||||
@@ -2436,8 +2466,14 @@ pub const LLVMEmitter = struct {
|
||||
.float => |v| c.LLVMConstReal(elem_ty, v),
|
||||
.boolean => |v| c.LLVMConstInt(elem_ty, @intFromBool(v), 0),
|
||||
.string => |sid| self.emitConstStringGlobal(self.ir_mod.types.getString(sid)),
|
||||
.aggregate => |inner| self.emitConstAggregate(inner, elem_ty),
|
||||
.func_ref => |fid| self.func_map.get(fid.index()) orelse c.LLVMConstNull(elem_ty),
|
||||
.aggregate => |inner| self.emitConstAggregate(inner, elem_ty, require_resolved),
|
||||
.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
|
||||
// the all-zero constant of the leaf type (issue 0081).
|
||||
.null_val, .zeroinit => c.LLVMConstNull(elem_ty),
|
||||
|
||||
Reference in New Issue
Block a user