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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user