comptime VM: real lowering-time Context — allocating + List-building type-fns run on the VM (issue 0141)
The VM can now evaluate a comptime type-fn that allocates at lowering time (the 0141 family) — the legacy interp cannot. Four changes: - runComptimeTypeFunc (lower/comptime.zig): force the CAllocator->Allocator thunks to exist (getOrCreateThunks, idempotent, guarded) BEFORE eval. A type-fn const runs at scanDecls (Pass 1), before Pass 1c builds the default-context global + thunks, so the comptime allocator was otherwise null. - materializeDefaultContext: build a REAL context at lowering time when the global is absent — find the two thunks by name and lay their func-refs into the inline Allocator value at the head of Context, so context.allocator.alloc_bytes dispatches call_indirect -> thunk -> native VM malloc. - aggType: deref a pointer base_type (the List write path emits struct_gep with base_type = *Struct; fieldOffset panicked on the pointer — now derefs, no panic). - subslice: handle a [*]T many-pointer / *T base (a List's items field — the base IS the data pointer). Verified end-to-end (manual probe): a compiler-API type-fn building its []Member in a List(Member) runs HANDLED on the VM and mints (green=7) — the 0141 List-growth pattern. Can't be a corpus test yet (gate-OFF/legacy can't allocate at lowering time — the dual-path bind), so locked in via VM unit tests (many-pointer subslice; struct_gep with a pointer base_type). 697/0 both gates + all unit tests.
This commit is contained in:
@@ -450,6 +450,76 @@ test "comptime_vm exec: subslice of an array" {
|
||||
try std.testing.expectEqual(@as(i64, 43), toI64(try v.run(&fb.func, &.{})));
|
||||
}
|
||||
|
||||
test "comptime_vm exec: subslice of a many-pointer ([*]T) — base IS the data pointer" {
|
||||
const alloc = std.testing.allocator;
|
||||
var table = types.TypeTable.init(alloc);
|
||||
defer table.deinit();
|
||||
const arr = table.intern(.{ .array = .{ .element = .i64, .length = 5 } });
|
||||
const aptr = table.intern(.{ .pointer = .{ .pointee = arr } });
|
||||
const i64ptr = table.intern(.{ .pointer = .{ .pointee = .i64 } });
|
||||
const mptr = table.intern(.{ .many_pointer = .{ .element = .i64 } });
|
||||
const sl = table.intern(.{ .slice = .{ .element = .i64 } });
|
||||
|
||||
// a := {0,10,20,30,40}; s := ([*]i64 a)[1..4]; return len(s) + s[0] + s[2] → 43
|
||||
var fb = Fb.init(alloc, &.{}, .i64);
|
||||
defer fb.deinit();
|
||||
const b0 = fb.block(&.{});
|
||||
const a = fb.add(b0, inst(.{ .alloca = arr }, aptr));
|
||||
inline for (0..5) |k| {
|
||||
const ik = fb.add(b0, inst(.{ .const_int = @intCast(k) }, .i64));
|
||||
const g = fb.add(b0, inst(.{ .index_gep = .{ .lhs = ref(a), .rhs = ref(ik) } }, i64ptr));
|
||||
const cv = fb.add(b0, inst(.{ .const_int = @as(i64, @intCast(k)) * 10 }, .i64));
|
||||
_ = fb.add(b0, inst(.{ .store = .{ .ptr = ref(g), .val = ref(cv), .val_ty = .i64 } }, .void));
|
||||
}
|
||||
// The alloca result IS the array's base address — subslice it as a `[*]i64`.
|
||||
const lo = fb.add(b0, inst(.{ .const_int = 1 }, .i64));
|
||||
const hi = fb.add(b0, inst(.{ .const_int = 4 }, .i64));
|
||||
const s = fb.add(b0, inst(.{ .subslice = .{ .base = ref(a), .lo = ref(lo), .hi = ref(hi), .base_ty = mptr } }, sl));
|
||||
const slen = fb.add(b0, inst(.{ .length = .{ .operand = ref(s) } }, .i64));
|
||||
const z = fb.add(b0, inst(.{ .const_int = 0 }, .i64));
|
||||
const e0 = fb.add(b0, inst(.{ .index_get = .{ .lhs = ref(s), .rhs = ref(z) } }, .i64));
|
||||
const two = fb.add(b0, inst(.{ .const_int = 2 }, .i64));
|
||||
const e2 = fb.add(b0, inst(.{ .index_get = .{ .lhs = ref(s), .rhs = ref(two) } }, .i64));
|
||||
const t = fb.add(b0, inst(.{ .add = .{ .lhs = ref(slen), .rhs = ref(e0) } }, .i64));
|
||||
const sum = fb.add(b0, inst(.{ .add = .{ .lhs = ref(t), .rhs = ref(e2) } }, .i64));
|
||||
_ = fb.add(b0, inst(.{ .ret = .{ .operand = ref(sum) } }, .void));
|
||||
|
||||
var v = vm.Vm.init(alloc);
|
||||
v.table = &table;
|
||||
defer v.deinit();
|
||||
try std.testing.expectEqual(@as(i64, 43), toI64(try v.run(&fb.func, &.{})));
|
||||
}
|
||||
|
||||
test "comptime_vm exec: struct_gep with an explicit pointer base_type derefs to the field (no panic)" {
|
||||
const alloc = std.testing.allocator;
|
||||
var table = types.TypeTable.init(alloc);
|
||||
defer table.deinit();
|
||||
const s_ty = table.intern(.{ .@"struct" = .{ .name = dummy, .fields = &.{
|
||||
.{ .name = dummy, .ty = .i64 },
|
||||
.{ .name = dummy, .ty = .i64 },
|
||||
} } });
|
||||
const sptr = table.intern(.{ .pointer = .{ .pointee = s_ty } });
|
||||
const i64ptr = table.intern(.{ .pointer = .{ .pointee = .i64 } });
|
||||
|
||||
// p := alloca S (a *S); struct_gep(p, field 1) with base_type = *S → &p.y;
|
||||
// store 80; load → 80. Exercises aggType derefing a POINTER base_type (the
|
||||
// List write path sets base_type = *Struct; without the deref fieldOffset panics).
|
||||
var fb = Fb.init(alloc, &.{}, .i64);
|
||||
defer fb.deinit();
|
||||
const b0 = fb.block(&.{});
|
||||
const p = fb.add(b0, inst(.{ .alloca = s_ty }, sptr));
|
||||
const g = fb.add(b0, inst(.{ .struct_gep = .{ .base = ref(p), .field_index = 1, .base_type = sptr } }, i64ptr));
|
||||
const v80 = fb.add(b0, inst(.{ .const_int = 80 }, .i64));
|
||||
_ = fb.add(b0, inst(.{ .store = .{ .ptr = ref(g), .val = ref(v80), .val_ty = .i64 } }, .void));
|
||||
const got = fb.add(b0, inst(.{ .load = .{ .operand = ref(g) } }, .i64));
|
||||
_ = fb.add(b0, inst(.{ .ret = .{ .operand = ref(got) } }, .void));
|
||||
|
||||
var v = vm.Vm.init(alloc);
|
||||
v.table = &table;
|
||||
defer v.deinit();
|
||||
try std.testing.expectEqual(@as(i64, 80), toI64(try v.run(&fb.func, &.{})));
|
||||
}
|
||||
|
||||
test "comptime_vm exec: non-pointer optional wrap/unwrap/has_value/coalesce" {
|
||||
const alloc = std.testing.allocator;
|
||||
var table = types.TypeTable.init(alloc);
|
||||
|
||||
Reference in New Issue
Block a user