fix(ir): materialize global aggregate struct-literal initializers (issue 0080)
A module-global array of struct literals (`pairs : [2]Pair = .[ .{...}, .{...} ]`)
was emitted as `zeroinitializer`, silently dropping every declared field — reads
returned 0 with no diagnostic. Global struct literals and struct-with-array
already worked; the gap was struct literals used as ARRAY elements.
Root cause: `Lowering.constExprValue` (the const-aggregate serializer for global
initializers) had no `.struct_literal` arm. `constArrayLiteral` serialized each
element through `constExprValue`, so a struct-literal element returned null,
collapsing the whole array initializer to null; `globalInitValue` then emitted no
payload and the LLVM backend zero-initialized the global — the same silent-zero
class as 0071/0072, one level inside an array literal.
Fix: make `constExprValue` type-aware — thread the destination element/field
TypeId so a struct-literal leaf routes through `constStructLiteral` and a nested
array-literal through `constArrayLiteral` with the correct element type.
`constArrayLiteral` derives its element type from the array TypeId;
`constStructLiteral` passes each field's type. A global aggregate initializer that
still does not fully reduce to a compile-time constant is now rejected loudly
(`diagnoseNonConstGlobal`) instead of silently zeroing. `emitConstAggregate`
already recurses over nested aggregates, so `sx run` (JIT) and `sx build` (AOT)
both materialize the declared values.
Regression: examples/0137-types-global-aggregate-literal-init.sx (global
[N]Struct literal, global struct literal, struct-with-array, nested
array-of-struct-with-array; values read back with no prior store, plus a store on
top). Fails on the pre-fix compiler (array-of-struct fields read 0), passes after.
Marks issues 0079 (already resolved) and 0080 RESOLVED.
This commit is contained in:
@@ -926,15 +926,15 @@ pub const Lowering = struct {
|
||||
.bool_literal => |bl| .{ .boolean = bl.value },
|
||||
.float_literal => |fl| .{ .float = fl.value },
|
||||
.string_literal => |sl| .{ .string = self.module.types.internString(sl.raw) },
|
||||
.array_literal => |al| self.constArrayLiteral(al.elements),
|
||||
.struct_literal => |sl| self.constStructLiteral(&sl, var_ty),
|
||||
.array_literal => |al| self.constArrayLiteral(al.elements, var_ty) orelse self.diagnoseNonConstGlobal(vd, v),
|
||||
.struct_literal => |sl| self.constStructLiteral(&sl, var_ty) orelse self.diagnoseNonConstGlobal(vd, v),
|
||||
.identifier => |id| blk: {
|
||||
// A global initialized from a module constant copies the
|
||||
// constant's recorded value (typed module consts land in
|
||||
// `module_const_map` via `registerTypedModuleConst`, run in the
|
||||
// same pass-2 before this).
|
||||
if (self.program_index.module_const_map.get(id.name)) |ci| {
|
||||
if (self.constExprValue(ci.value)) |cv| break :blk cv;
|
||||
if (self.constExprValue(ci.value, var_ty)) |cv| break :blk cv;
|
||||
}
|
||||
if (self.diagnostics) |d|
|
||||
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 });
|
||||
@@ -957,6 +957,16 @@ pub const Lowering = struct {
|
||||
};
|
||||
}
|
||||
|
||||
/// A global aggregate initializer (array/struct literal) that does not fully
|
||||
/// reduce to a compile-time constant is rejected loudly. Without this the
|
||||
/// `null` payload would fall through to a zero-initialized global, silently
|
||||
/// dropping the declared fields (issues 0071/0072/0080).
|
||||
fn diagnoseNonConstGlobal(self: *Lowering, vd: *const ast.VarDecl, v: *const Node) ?inst_mod.ConstantValue {
|
||||
if (self.diagnostics) |d|
|
||||
d.addFmt(.err, v.span, "global '{s}' must be initialized by a compile-time constant", .{vd.name});
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Resolve identifier-RHS type aliases whose target is declared LATER in the
|
||||
/// file. The forward scan above only registers an alias (`A :: B`) when `B`
|
||||
/// is already in `type_alias_map` / the `TypeTable`; a forward target isn't
|
||||
@@ -993,19 +1003,30 @@ pub const Lowering = struct {
|
||||
}
|
||||
}
|
||||
|
||||
/// Try to convert an array literal's elements into a compile-time ConstantValue.aggregate.
|
||||
/// Returns null if any element is not a compile-time constant.
|
||||
fn constArrayLiteral(self: *Lowering, elements: []const *const Node) ?inst_mod.ConstantValue {
|
||||
/// Try to convert an array literal's elements into a compile-time
|
||||
/// ConstantValue.aggregate. `array_ty` is the array's resolved TypeId; its
|
||||
/// element type drives type-aware serialization of struct-literal and
|
||||
/// nested-array elements. Returns null if `array_ty` is not an array type or
|
||||
/// any element is not a compile-time constant.
|
||||
fn constArrayLiteral(self: *Lowering, elements: []const *const Node, array_ty: TypeId) ?inst_mod.ConstantValue {
|
||||
if (array_ty.isBuiltin()) return null;
|
||||
const elem_ty: TypeId = switch (self.module.types.get(array_ty)) {
|
||||
.array => |a| a.element,
|
||||
else => return null,
|
||||
};
|
||||
const vals = self.alloc.alloc(inst_mod.ConstantValue, elements.len) catch return null;
|
||||
for (elements, 0..) |elem, i| {
|
||||
vals[i] = self.constExprValue(elem) orelse return null;
|
||||
vals[i] = self.constExprValue(elem, elem_ty) orelse return null;
|
||||
}
|
||||
return .{ .aggregate = vals };
|
||||
}
|
||||
|
||||
/// Try to convert a single AST expression into a compile-time ConstantValue.
|
||||
/// Returns null if the expression is not constant-foldable here.
|
||||
fn constExprValue(self: *Lowering, expr: *const Node) ?inst_mod.ConstantValue {
|
||||
/// `expected_ty` is the destination element/field type — it lets aggregate
|
||||
/// leaves (struct literals, nested arrays) serialize with the correct shape
|
||||
/// rather than collapsing to null (issue 0080). Returns null if the
|
||||
/// expression is not constant-foldable here.
|
||||
fn constExprValue(self: *Lowering, expr: *const Node, expected_ty: TypeId) ?inst_mod.ConstantValue {
|
||||
return switch (expr.data) {
|
||||
.int_literal => |il| .{ .int = il.value },
|
||||
.bool_literal => |bl| .{ .boolean = bl.value },
|
||||
@@ -1020,7 +1041,8 @@ pub const Lowering = struct {
|
||||
},
|
||||
else => null,
|
||||
},
|
||||
.array_literal => |al| self.constArrayLiteral(al.elements),
|
||||
.array_literal => |al| self.constArrayLiteral(al.elements, expected_ty),
|
||||
.struct_literal => |sl| self.constStructLiteral(&sl, expected_ty),
|
||||
else => null,
|
||||
};
|
||||
}
|
||||
@@ -1055,7 +1077,7 @@ pub const Lowering = struct {
|
||||
break :blk null;
|
||||
};
|
||||
if (init_expr) |e| {
|
||||
vals[fi] = self.constExprValue(e) orelse return null;
|
||||
vals[fi] = self.constExprValue(e, sf.ty) orelse return null;
|
||||
} else {
|
||||
vals[fi] = .zeroinit;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user