comptime VM: box_any/unbox_any + .any as a 16-byte flat-memory aggregate (Phase 4A.1)

Ported the Any-boxing conversion pair:
- box_any: alloc the 16-byte { type_tag@0, value@8 } box, tag = source TypeId
  index (matches the legacy comptime interp; runtime anyTag also normalizes
  arbitrary-width ints). Value slot holds a word source's scalar bytes (via
  writeField(source_type) so f32 round-trips) or an aggregate source's
  flat-memory ADDR (the runtime pointer-in-value-slot shape).
- unbox_any: read the value slot back (word -> readField; aggregate -> the
  stored ADDR).

Required promoting .any to a first-class flat-memory aggregate (was
kindOf -> .unsupported): kindOf(.any) = .aggregate (16B, by-address) and
fieldOffset special-cases .any to the {@0, @8} layout (shared with
string/slice). Without the latter a struct_get on an Any panicked
(union field 'struct' while 'any' is active) -- caught + fixed, no crash.

Updated two unit tests that used unbox_any as the "unported op" example ->
compiler_call; added a box->unbox round-trip test. 697/0 both gates + all
unit tests. The 6 box_any examples no longer bail at box_any (output matches
legacy) but fall back further at switch_br/type_name/out (later 4A steps).
This commit is contained in:
agra
2026-06-18 16:56:50 +03:00
parent 3283effa97
commit 1526d198e2
3 changed files with 79 additions and 9 deletions

View File

@@ -689,6 +689,26 @@ test "comptime_vm exec: tagged-union enum_init with payload lays out {tag@0, pay
try std.testing.expectEqual(@as(u64, 42), try v.machine.readWord(addr + 8, 8)); // payload
}
test "comptime_vm exec: box_any/unbox_any round-trips a scalar through the {tag, value} box" {
const alloc = std.testing.allocator;
var table = types.TypeTable.init(alloc);
defer table.deinit();
// a := box_any(42, i64); return unbox_any(a) → 42 (exercises both the 16-byte
// {tag@0, value@8} box write and the value-slot read-back).
var fb = Fb.init(alloc, &.{}, .i64);
defer fb.deinit();
const b0 = fb.block(&.{});
const c = fb.add(b0, inst(.{ .const_int = 42 }, .i64));
const a = fb.add(b0, inst(.{ .box_any = .{ .operand = ref(c), .source_type = .i64 } }, .any));
const u = fb.add(b0, inst(.{ .unbox_any = .{ .operand = ref(a) } }, .i64));
_ = fb.add(b0, inst(.{ .ret = .{ .operand = ref(u) } }, .void));
var v = vm.Vm.init(alloc);
v.table = &table;
defer v.deinit();
try std.testing.expectEqual(@as(i64, 42), toI64(try v.run(&fb.func, &.{})));
}
test "comptime_vm exec: const_type yields a Type-value word; regToValue bridges it to .type_tag" {
const alloc = std.testing.allocator;
var table = types.TypeTable.init(alloc);
@@ -1245,11 +1265,12 @@ test "comptime_vm tryEval: pure function → Value; unsupported → null" {
const v = vm.tryEval(alloc, &module, ok_id) orelse return error.VmShouldHaveHandledIt;
try std.testing.expectEqual(@as(i64, 42), v.int);
// fn bad() { unbox_any(1) } → tryEval yields null (caller falls back to legacy)
// fn bad() { compiler_call() } → an unported op → tryEval yields null (caller
// falls back to legacy). (box_any/unbox_any are now VM-native; compiler_call is
// still unported until Phase 4D.)
var fb2 = Fb.init(alloc, &.{}, .void);
const c0 = fb2.block(&.{});
const c = fb2.add(c0, inst(.{ .const_int = 1 }, .i64));
_ = fb2.add(c0, inst(.{ .unbox_any = .{ .operand = ref(c) } }, .i64));
_ = fb2.add(c0, inst(.{ .compiler_call = .{ .name = 0, .args = &.{} } }, .void));
_ = fb2.add(c0, inst(.ret_void, .void));
const bad_id = module.addFunction(fb2.func);
@@ -1271,19 +1292,18 @@ test "comptime_vm exec: division by zero and unsupported op bail loudly" {
try std.testing.expectEqual(@as(i64, 4), toI64(try v.run(&fb.func, &.{ fromI64(12), fromI64(3) })));
try std.testing.expectError(error.DivisionByZero, v.run(&fb.func, &.{ fromI64(12), fromI64(0) }));
}
// A not-yet-ported op (unbox_any) → Unsupported with the op name in `detail`.
// A not-yet-ported op (compiler_call) → Unsupported with the op name in `detail`.
{
var fb = Fb.init(std.testing.allocator, &.{}, .void);
defer fb.deinit();
const b0 = fb.block(&.{});
const c = fb.add(b0, inst(.{ .const_int = 1 }, .i64));
_ = fb.add(b0, inst(.{ .unbox_any = .{ .operand = ref(c) } }, .i64));
_ = fb.add(b0, inst(.{ .compiler_call = .{ .name = 0, .args = &.{} } }, .void));
_ = fb.add(b0, inst(.ret_void, .void));
var v = vm.Vm.init(std.testing.allocator);
defer v.deinit();
try std.testing.expectError(error.Unsupported, v.run(&fb.func, &.{}));
try std.testing.expectEqualStrings("unbox_any", v.detail.?);
try std.testing.expectEqualStrings("compiler_call", v.detail.?);
}
}