fix: comptime VM reg→value bridge for optional results (issue 0162)
Add an .optional arm to regToValue in comptime_vm.zig: read the
has_value flag at offset sizeof(child), bridge the payload recursively
into a { payload, i1=true } aggregate when set, yield .null_val (zero
{T,i1}) when clear or the bare null sentinel. Matching serialize arm in
serializeAggregateValue (emit_llvm.zig). Pointer/?Closure/?Protocol-child
optionals and array-payload aggregates bail loudly, not silently.
Regression: examples/comptime/0643-comptime-run-optional-aggregate.sx
(present ?T, present ?i64, null ?i64). Verified by 3 adversarial reviews.
This commit is contained in:
38
examples/comptime/0643-comptime-run-optional-aggregate.sx
Normal file
38
examples/comptime/0643-comptime-run-optional-aggregate.sx
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
// `#run` of a function returning an OPTIONAL value must bridge the comptime
|
||||||
|
// VM register → host Value through the reg→value optional arm.
|
||||||
|
//
|
||||||
|
// Regression (issue 0162): a `#run` whose function returned `?T` / `?i64`
|
||||||
|
// previously failed comptime evaluation with
|
||||||
|
// "reg→value: aggregate shape not bridged yet"
|
||||||
|
// because the VM's regToValue bridge handled scalars/structs/slices/tuples
|
||||||
|
// but bailed on an OPTIONAL-typed result. The fix reads the optional's
|
||||||
|
// has_value flag (at offset sizeof(child)); when set it bridges the payload
|
||||||
|
// recursively into a `{ payload, i1=true }` aggregate (the host serializes
|
||||||
|
// that to `{T, i1}`), and when clear (or the value is the bare null sentinel)
|
||||||
|
// it yields `.null_val` (serialized to a zero `{T, i1}` = absent).
|
||||||
|
//
|
||||||
|
// Exercises: present `?T` (optional of struct), present `?i64` (optional of
|
||||||
|
// scalar), and a NULL-returning `?i64`. The values are read via `!` (unwrap)
|
||||||
|
// and `??` (coalesce), which read the has_value flag correctly.
|
||||||
|
|
||||||
|
#import "modules/std.sx";
|
||||||
|
|
||||||
|
T :: struct { a: i64 = 0; b: i64 = 0; }
|
||||||
|
|
||||||
|
mk_struct :: () -> ?T { t : T = .{ a = 7, b = 11 }; return t; }
|
||||||
|
mk_scalar :: () -> ?i64 { return 5; }
|
||||||
|
mk_null :: () -> ?i64 { return null; }
|
||||||
|
|
||||||
|
X :: #run mk_struct(); // present ?T
|
||||||
|
Y :: #run mk_scalar(); // present ?i64
|
||||||
|
N :: #run mk_null(); // null ?i64
|
||||||
|
|
||||||
|
main :: () {
|
||||||
|
// Present optional-of-struct: payload bridged field-by-field.
|
||||||
|
print("X.a = {}\n", X!.a);
|
||||||
|
print("X.b = {}\n", X!.b);
|
||||||
|
// Present optional-of-scalar.
|
||||||
|
print("Y = {}\n", Y ?? -1);
|
||||||
|
// Null optional: coalesce takes the default.
|
||||||
|
print("N = {}\n", N ?? -1);
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
0
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
X.a = 7
|
||||||
|
X.b = 11
|
||||||
|
Y = 5
|
||||||
|
N = -1
|
||||||
@@ -1,5 +1,17 @@
|
|||||||
# 0162 — `#run` returning an optional aggregate fails the comptime VM reg→value bridge
|
# 0162 — `#run` returning an optional aggregate fails the comptime VM reg→value bridge
|
||||||
|
|
||||||
|
> **RESOLVED.** Root cause: the comptime VM's `regToValue` bridge
|
||||||
|
> (`src/ir/comptime_vm.zig`) had no `.optional` arm, so any OPTIONAL-typed
|
||||||
|
> `#run` result hit the `"aggregate shape not bridged yet"` bail. Fix: added an
|
||||||
|
> `.optional` arm that reads the has_value flag (at offset `sizeof(child)`),
|
||||||
|
> bridges the payload recursively into a `{ payload, i1=true }` aggregate when
|
||||||
|
> set, and yields `.null_val` (→ zero `{T, i1}`) when clear or the bare null
|
||||||
|
> sentinel; plus a matching serialize arm in `serializeAggregateValue`
|
||||||
|
> (`src/ir/emit_llvm.zig`). Pointer/`?Closure`/`?Protocol`-child optionals and
|
||||||
|
> array-payload aggregates bail loudly (out of scope, not silent). Regression
|
||||||
|
> test: `examples/comptime/0643-comptime-run-optional-aggregate.sx` (present
|
||||||
|
> `?T`, present `?i64`, null `?i64`). Verified by 3 adversarial reviews.
|
||||||
|
|
||||||
## Symptom
|
## Symptom
|
||||||
|
|
||||||
A `#run` (or comptime const init) whose function returns an OPTIONAL value
|
A `#run` (or comptime const init) whose function returns an OPTIONAL value
|
||||||
|
|||||||
@@ -2236,6 +2236,37 @@ pub const Vm = struct {
|
|||||||
}
|
}
|
||||||
return .{ .aggregate = out };
|
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
|
||||||
|
// bridges through the `.word` arm. A closure / protocol child
|
||||||
|
// has a different layout (sentinel func-ref / ctx-ptr-as-flag),
|
||||||
|
// so guard against it and bail loudly rather than mis-read.
|
||||||
|
const child = info.optional.child;
|
||||||
|
if (optChildIsPtr(table, child))
|
||||||
|
return self.failMsg("reg→value: pointer optional reached the aggregate arm");
|
||||||
|
if (!child.isBuiltin()) switch (table.get(child)) {
|
||||||
|
.closure => return self.failMsg("reg→value: ?Closure optional not bridged (sentinel layout)"),
|
||||||
|
.@"struct" => |s| if (s.is_protocol)
|
||||||
|
return self.failMsg("reg→value: ?Protocol optional not bridged (ctx-ptr layout)"),
|
||||||
|
else => {},
|
||||||
|
};
|
||||||
|
// has_value flag lives at offset sizeof(child); clear → null.
|
||||||
|
// The `const_null` form is a bare `null_addr` (no allocation);
|
||||||
|
// treat that as none too (mirrors `optHas`).
|
||||||
|
if (reg == null_addr) return .null_val;
|
||||||
|
const has = (try self.machine.readWord(reg + table.typeSizeBytes(child), 1)) != 0;
|
||||||
|
if (!has) return .null_val;
|
||||||
|
// Present: bridge the payload (read from offset 0) as the child
|
||||||
|
// type, and present it as the `{ payload, i1=true }` LLVM-struct
|
||||||
|
// shape the host's optional serializer expects.
|
||||||
|
const payload_reg = try self.readField(table, reg, child);
|
||||||
|
const payload = try self.regToValue(alloc, table, payload_reg, child);
|
||||||
|
const out = alloc.alloc(Value, 2) catch return self.failMsg("reg→value: out of memory (optional)");
|
||||||
|
out[0] = payload;
|
||||||
|
out[1] = .{ .boolean = true };
|
||||||
|
return .{ .aggregate = out };
|
||||||
|
}
|
||||||
return self.failMsg("reg→value: aggregate shape not bridged yet");
|
return self.failMsg("reg→value: aggregate shape not bridged yet");
|
||||||
},
|
},
|
||||||
.unsupported => return self.failMsg("reg→value: unsupported type"),
|
.unsupported => return self.failMsg("reg→value: unsupported type"),
|
||||||
|
|||||||
@@ -1230,6 +1230,25 @@ pub const LLVMEmitter = struct {
|
|||||||
}
|
}
|
||||||
return c.LLVMConstArray2(llvm_elem_ty, elem_vals.items.ptr, @intCast(elem_vals.items.len));
|
return c.LLVMConstArray2(llvm_elem_ty, elem_vals.items.ptr, @intCast(elem_vals.items.len));
|
||||||
}
|
}
|
||||||
|
// Present optional `?T` → `{ <payload>, i1 1 }`, matching the
|
||||||
|
// anonymous-struct layout `toLLVMType` builds for a non-pointer
|
||||||
|
// optional (the absent case is a `.null_val`, serialized at the
|
||||||
|
// `valueToLLVMConst` top level as `LLVMConstNull` of `{T,i1}`).
|
||||||
|
// The VM's reg→value optional arm only produces this 2-field
|
||||||
|
// `[payload, bool]` aggregate for present, non-pointer optionals.
|
||||||
|
if (info == .optional) {
|
||||||
|
if (fields.len != 2) {
|
||||||
|
std.debug.print(
|
||||||
|
"error: comptime init of '{s}' produced an optional aggregate with {} fields (expected 2: payload, has_value)\n",
|
||||||
|
.{ global_name, fields.len },
|
||||||
|
);
|
||||||
|
return self.failGlobalInit(llvm_ty);
|
||||||
|
}
|
||||||
|
const payload = self.valueToLLVMConst(fields[0], info.optional.child, global_name);
|
||||||
|
const has = self.valueToLLVMConst(fields[1], .bool, global_name);
|
||||||
|
var ofields = [_]c.LLVMValueRef{ payload, has };
|
||||||
|
return c.LLVMConstStructInContext(self.context, &ofields, 2, 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std.debug.print(
|
std.debug.print(
|
||||||
|
|||||||
Reference in New Issue
Block a user