issue 0141: re-refine root cause — IR is correct; it's a legacy slot_ptr chain + null comptime allocator (VM has neither failure mode)

This commit is contained in:
agra
2026-06-18 14:41:33 +03:00
parent c085840964
commit 3c0e0852a8

View File

@@ -123,6 +123,47 @@ i.e. what about the generic `List(T)` instantiation is incomplete then that flip
the field-access decision (start at `lower/expr.zig:lowerFieldAccess` /
`lowerFieldAccessOnType` and the `*T`-receiver path).
## Root cause — RE-REFINED (2026-06-18): the IR is CORRECT; it's a legacy slot_ptr chain + null comptime allocator
Direction 1's premise is **wrong** — the IR is NOT mis-lowered. Instrumented
`lowerFieldAccess` (the `*T`-receiver auto-deref) on the repro: `list.len` lowers
with `obj_ty = *List__EnumVariant` (kind=**pointer**), so the auto-deref fires
(`obj = load(list, List); struct_get(obj, len_idx)`) — **byte-identical** to the
runtime/`#run` shape. There is no `struct_get`-vs-`struct_gep` divergence in the
READ path (field reads ALWAYS lower to load+`struct_get` via `expr.zig:~915`;
`struct_gep` is only the WRITE/lvalue path — the "38 hits" above were writes).
The bail is purely in the **legacy interp's slot_ptr semantics**. Instrumented the
`.struct_get` arm: for `list.len` the base is a `slot_ptr`, and the arm's own
`slot_ptr` auto-deref (`loadSlot`) yields **another `slot_ptr`** (a `*List` param
→ slot_ptr → slot_ptr chain), which no `switch(base)` case resolves → "base has no
fields". So `load(*List)` produced a slot_ptr the interp can't flatten to the
aggregate at this call shape.
There is a SECOND, independent blocker that survives even if the slot_ptr chain is
fixed: `List.append` grows on the first call (cap 0→4) → `context.allocator.alloc_bytes`
`call_indirect` on a **null `alloc_fn`** at lowering time (the comptime allocator
is null at type-construction time — confirmed in BOTH evaluators; see the
CHECKPOINT-COMPILER-API 2026-06-18 entry). So the repro needs BOTH the slot_ptr
chain AND the comptime allocator fixed.
**Strategic implication (2026-06-18).** Both blockers are LEGACY-interp issues
(slot_ptr chains; null comptime allocator dispatch — "raw fn-pointers from extern
calls aren't dispatchable in interp"). The flat-memory comptime VM (`comptime_vm.zig`)
has neither failure mode by construction: it uses flat byte memory (no slot_ptr
chains) and models `malloc`/`call_indirect` natively. So the principled fix is NOT
to patch the legacy interp (code slated for deletion), but to let the VM evaluate
these type-fns: (a) re-express the metatype `define`/`make_enum` over the
compiler-API so the type-fn body hits NO `call_builtin(define)` (which forces a
legacy fallback today), and (b) make `materializeDefaultContext` lay the REAL
allocator func-refs at lowering time (the global exists by Pass 1c, so the
`layoutConst` path should populate them — verify it handles the inline-protocol
`Allocator` field, which may need a `.protocol`/inline-struct arm). Then a
List-building type-fn runs entirely on the VM. The remaining tension is dual-path
validation: while the legacy still fails, a corpus example can't pass gate-OFF — so
this lands cleanly only alongside the VM-default flip + legacy deletion (the
end-state). Until then it stays a deferred enhancement, not a blocker.
**Plumbing (only after STEP 0 is green):**
1. `scanDecls` (`decl.zig:777` site): instead of `evalComptimeType` now,
(a) pre-register a forward nominal slot named `cd.name` + bind the alias