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:
@@ -348,19 +348,35 @@ pub const Vm = struct {
|
||||
if (g.init_val) |iv| try self.layoutConst(table, iv, g.ty, addr);
|
||||
return addr;
|
||||
}
|
||||
// No `__sx_default_context` global yet — this is the LOWERING-time path
|
||||
// (the global is emitted later, at codegen). Materialize a ZEROED `Context`
|
||||
// of the right size instead: a type-fn that never touches the allocator
|
||||
// ignores it; one that DOES allocate reads a null `alloc_fn` (zeroed) and
|
||||
// `call_indirect` on the null func-ref bails → legacy fallback (which has a
|
||||
// real default context). A real lowering-time context (with the CAllocator
|
||||
// thunk func-refs, so allocating type-fns also run on the VM) is a follow-up.
|
||||
// `internString` of an existing name is idempotent (pool-only, no layout
|
||||
// change) — the same `@constCast` the reader handlers use on the table.
|
||||
// No `__sx_default_context` global yet — the LOWERING-time path (a type-fn
|
||||
// const runs at scanDecls, before Pass 1c emits that global). Build the
|
||||
// REAL default context directly from the CAllocator→Allocator thunks
|
||||
// (forced to exist by `runComptimeTypeFunc` before this runs), mirroring
|
||||
// the legacy `defaultContextValue` / `emitDefaultContextGlobal`: the inline
|
||||
// `Allocator` value is `{ ctx: *void = null, alloc_fn, dealloc_fn }` (three
|
||||
// pointer-sized words) at the head of `Context`, so `context.allocator`
|
||||
// dispatches `alloc_bytes` → `call_indirect` → the thunk → native `malloc`,
|
||||
// all on the VM. If a thunk is absent (std not imported), the field stays
|
||||
// null (zeroed) and an allocating body bails — same as a non-std program.
|
||||
const ctx_name = @constCast(table).internString("Context");
|
||||
const ctx_ty = table.findByName(ctx_name) orelse
|
||||
return self.failMsg("comptime VM: no Context type to materialize the implicit context");
|
||||
return self.machine.allocBytes(table.typeSizeBytes(ctx_ty), table.typeAlignBytes(ctx_ty)); // zeroed
|
||||
const addr = self.machine.allocBytes(table.typeSizeBytes(ctx_ty), table.typeAlignBytes(ctx_ty)); // zeroed
|
||||
const ps: Addr = table.pointer_size;
|
||||
if (self.findFuncByName(module, "__thunk_CAllocator_Allocator_alloc_bytes")) |fid|
|
||||
try self.machine.writeWord(addr + ps, ps, funcRefWord(fid)); // allocator.alloc_fn @ +ptr_size
|
||||
if (self.findFuncByName(module, "__thunk_CAllocator_Allocator_dealloc_bytes")) |fid|
|
||||
try self.machine.writeWord(addr + 2 * ps, ps, funcRefWord(fid)); // allocator.dealloc_fn @ +2*ptr_size
|
||||
return addr;
|
||||
}
|
||||
|
||||
/// Find a module function by its exact name → its `FuncId`, or null. Used to
|
||||
/// resolve the CAllocator thunk func-refs for the lowering-time default context.
|
||||
fn findFuncByName(_: *Vm, module: *const Module, name: []const u8) ?inst_mod.FuncId {
|
||||
for (module.functions.items, 0..) |*f, i| {
|
||||
if (std.mem.eql(u8, module.types.getString(f.name), name)) return inst_mod.FuncId.fromIndex(@intCast(i));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Lay a static `ConstantValue` of type `ty` into flat memory at `addr` (the
|
||||
@@ -689,6 +705,10 @@ pub const Vm = struct {
|
||||
} else if (!bty.isBuiltin()) {
|
||||
switch (table.get(bty)) {
|
||||
.array => |a| elem = a.element,
|
||||
// `[*]T` (a List's `items` field) / `*T`: the base IS the
|
||||
// data pointer; subslicing yields `{ base + lo, hi - lo }`.
|
||||
.many_pointer => |mp| elem = mp.element,
|
||||
.pointer => |p| elem = p.pointee,
|
||||
.slice => |sl| {
|
||||
elem = sl.element;
|
||||
data = try self.sliceData(table, base);
|
||||
@@ -1496,13 +1516,15 @@ pub const Vm = struct {
|
||||
/// lowering set it, else the base operand's Ref type — dereferenced when the
|
||||
/// base is a POINTER (`struct_gep` on an `alloca` result is `*S` → `S`).
|
||||
fn aggType(self: *Vm, table: *const types.TypeTable, fa: inst_mod.FieldAccess, ref_types: []const TypeId) Error!TypeId {
|
||||
if (fa.base_type) |bt| return bt;
|
||||
const rt = try self.refTy(ref_types, fa.base);
|
||||
if (!rt.isBuiltin()) {
|
||||
const info = table.get(rt);
|
||||
if (info == .pointer) return info.pointer.pointee;
|
||||
}
|
||||
return rt;
|
||||
// The explicit `base_type` when lowering set it, else the base operand's
|
||||
// Ref type. Either way, deref ONE pointer level when the result is a
|
||||
// pointer-to-struct: a `struct_gep`/`struct_get` on a `*Struct` receiver
|
||||
// (e.g. `list.field` where `list: *List`) computes the field offset on the
|
||||
// POINTEE struct, with the base register already holding the pointer
|
||||
// address. Lowering sets `base_type = *Struct` on the write/lvalue path.
|
||||
const raw = fa.base_type orelse (try self.refTy(ref_types, fa.base));
|
||||
if (!raw.isBuiltin() and table.get(raw) == .pointer) return table.get(raw).pointer.pointee;
|
||||
return raw;
|
||||
}
|
||||
|
||||
/// The byte offset of tuple element `idx` — the positional analogue of
|
||||
|
||||
Reference in New Issue
Block a user