fix: comptime reg->value bridge for array-in-aggregate + clean abort on comptime-init failure (issue 0167)

(C) regToValue (comptime_vm.zig) gained no array arm, so a #run returning
an aggregate containing an array bailed 'reg->value: aggregate shape not
bridged yet'. Add an .array arm: read N elements at stride
typeSizeBytes(elem) from the array address, bridge each recursively via
regToValue -> an .aggregate Value (serializeAggregateValue already emits
arrays). Composes with struct fields, nested arrays, array-of-structs,
and the ?Arr optional payload; unbridgeable elements bail loudly.

(E) A global failing #run proceeded into LLVM emission and panicked
'unresolved type reached LLVM emission' when the unresolved const was
used. Add 'if (self.comptime_failed) return;' in emit() after Pass 0 so
it aborts cleanly (exit 1, the comptime diagnostic) across run/ir/build.

Regression: examples/comptime/0644-comptime-run-array-aggregate.sx.
Verified by 3 adversarial reviews, suite 793/0. Filed separate bugs found
during review: 0181 (optional-chain ?. to array field + index panics),
0182 (body-local #run unbridged silently miscompiles).
This commit is contained in:
agra
2026-06-23 11:34:22 +03:00
parent 555ccdc024
commit fa7c07faf8
9 changed files with 246 additions and 0 deletions

View File

@@ -2236,6 +2236,24 @@ pub const Vm = struct {
}
return .{ .aggregate = out };
}
if (info == .array) {
// `[N]E` is held by-address as N contiguous `E` slots at
// stride `sizeof(E)`. Bridge each element via `regToValue`
// recursively (so a nested array / array-of-struct / array
// inside a struct all compose), producing an `.aggregate`
// Value whose serializer arm (`serializeAggregateValue`'s
// `.array` case) emits an `LLVMConstArray2`.
const elem_ty = info.array.element;
const len: usize = @intCast(info.array.length);
const stride: Addr = @intCast(table.typeSizeBytes(elem_ty));
const out = alloc.alloc(Value, len) catch return self.failMsg("reg→value: out of memory (array)");
for (0..len) |i| {
const elem_addr = reg + @as(Addr, @intCast(i)) * stride;
const er = try self.readField(table, elem_addr, elem_ty);
out[i] = try self.regToValue(alloc, table, er, elem_ty);
}
return .{ .aggregate = out };
}
if (info == .optional) {
// Only the `{ payload@0, has_value@sizeof(child) }` aggregate
// shape lands here — a pointer-child optional is a word and

View File

@@ -391,6 +391,16 @@ pub const LLVMEmitter = struct {
// Pass 0.5: Run comptime side-effect functions (#run expr; at top level)
self.runComptimeSideEffects();
// A comptime/global init failure (e.g. an unbridgeable `#run` result)
// sets `comptime_failed` AND leaves the failed const's type as the
// `.unresolved` sentinel. The driver converts `comptime_failed` into a
// clean exit-1 *after* emit() returns — but the remaining passes
// (declare/emit function bodies that reference the now-unresolved const)
// would `@panic("unresolved type reached LLVM emission")` first. Abort
// emission here so the failure surfaces as the printed diagnostic +
// clean exit, never the panic.
if (self.comptime_failed) return;
// Pass 1: Declare all functions (so calls can reference them)
for (self.ir_mod.functions.items, 0..) |func, i| {
self.declareFunction(&func, @intCast(i));