From a448f50f7f516838e821a97a693b374091e34683 Mon Sep 17 00:00:00 2001 From: agra Date: Wed, 17 Jun 2026 08:21:55 +0300 Subject: [PATCH] =?UTF-8?q?issue(0141):=20refine=20root=20cause=20?= =?UTF-8?q?=E2=80=94=20wrong=20IR=20(struct=5Fget=20vs=20struct=5Fgep)=20a?= =?UTF-8?q?t=20scanDecls?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instrumentation shows List.append lowers list.len/list.cap to struct_gep (correct) at #run/emit time but struct_get (wrong, value access on a *T receiver) at scanDecls/metatype time — same source, different IR. The function IS lowered both ways, just to wrong IR at scanDecls due to incomplete generic-instantiation context. So an interp-side lazy-lower hook can't fix it (IR is wrong before the interp runs); the fix is either robust field-access lowering or deferring the comptime type-construction eval to a complete-world pass (like #run). Supersedes the two-layer framing. --- ...mptime-list-growth-in-type-construction.md | 44 ++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/issues/0141-comptime-list-growth-in-type-construction.md b/issues/0141-comptime-list-growth-in-type-construction.md index 36e7da4a..779007f2 100644 --- a/issues/0141-comptime-list-growth-in-type-construction.md +++ b/issues/0141-comptime-list-growth-in-type-construction.md @@ -43,7 +43,49 @@ program is lowered), whereas a metatype `::` const evaluates during `scanDecls` It is NOT metatype/EnumVariant-specific — a plain `List(i64)` grown in a `-> Type` body bails identically (`.sx-tmp/probe_li64.sx`). -## Root cause — TWO independent layers +## Root cause — REFINED (2026-06-17): wrong IR at scanDecls, not just "not ready" + +Deeper investigation overturns the "two independent layers" framing below. The +real root cause is a single one: **a generic stdlib method's body is lowered to +WRONG IR when that lowering is triggered at `scanDecls` time** (during the +metatype `::` eval), because the generic struct instantiation context is +incomplete then. + +Proof (instrumented `interp.zig`'s `.struct_get` / `.struct_gep` arms, ran the +same `List(i64)` append both ways): + +| Eval time | `list.len` / `list.cap` (where `list: *List(T)`) lowers to | Result | +|---|---|---| +| `#run` (EMIT time, world complete) | `struct_gep` (pointer field access) — 38 hits | works | +| metatype `::` (scanDecls time) | `struct_get` (VALUE field access) — fails on the 1st | bails | + +So `List.append` is fully lowered in BOTH cases (no unlowered/extern call fires), +but at `scanDecls` time `list.len` lowers as `struct_get` on the pointer VALUE +instead of `struct_gep` THROUGH the pointer — `struct_get` on a `*List` receiver +sees a `slot_ptr` whose load is another `slot_ptr` and bails. The two "layers" +below are both symptoms of this same incomplete-context lowering (the null +allocator is the same story for the CAllocator thunks). + +**Consequence for the fix:** an interp-side "lazy-lower the missing function" +hook does NOT help — the function is already lowered, just to wrong IR before the +interp ever runs. The fix must ensure the bodies the metatype eval needs are +lowered with a COMPLETE type context. Two viable directions: + 1. **Make field-access lowering robust** — `list.len` on a `*List(T)` receiver + must emit `struct_gep` whenever the receiver is a pointer-to-struct, even if + the pointee's generic instantiation isn't finalized yet (resolve the field + index against the in-progress instantiation). Localized to the + field-access / generic-struct-instantiation path; risk is mis-lowering other + in-progress generics. + 2. **Defer the comptime type-construction eval** to a dedicated pass AFTER the + stdlib/generic machinery the constructors call is lowered, but before general + body lowering of code that USES the constructed types (their forward slots + are already pre-registered, so `*Name` / annotations resolve in the interim). + This is the true "lazy/deferred" shape — the eval runs in a complete world, + exactly like `#run`. Bigger (pipeline ordering) but matches why `#run` works. + Decision pending (see the conversation) — direction 2 is the principled match to + the `#run`-works/metatype-fails asymmetry. + +## Root cause — TWO independent layers (SUPERSEDED by the refinement above) ### Layer 1 — null comptime allocator (has a known fix)