wcc: module-imported array indexed-load via cg_dotbase_addr (#128b)

Fix segfault-class memory corruption on `module.array[i]` indexed-read
where both stages emitted MOVQ-not-LEAQ on the module-qualified base
plus wrong stride. Extends the #135 cg_dotbase_addr/dotbaseaddr helper
to handle the SK_USE module-ident-base case: when bt is NULL/ty_err
and let_islet(base.str) resolves to TY_ARRAY, emit LEAQ base(SB),dst
instead of MOVQ. Wwstage parallel via letvartnode/N_TARRAY check.
Stride fix via let_var_type fallback in cgindex when n.lhs.kind==N_DOT.

Use-site fix per #135 precedent (Option B); preserves cgdot's MOVQ
semantics for the whole-array-assign defensive case (zero current
consumers). Test 915 carries 3 module-u16 indexed-read rows
(strconv.left_shift_table[0/2/4]) + 2 local-array controls; the
strconv.left_shift_table[2]:u32 probe segfaulted (exit 139) pre-fix
and exits cleanly post-fix. Broader width-variation rows (u8/u32/i32
module-imported) deferred as informational enhancement. Test 915
skips its inline cs==ww .s cmp on needs_import rows (line 217-222)
since `ww build` only drives cstage; reviewer externally verified
byte-id on /tmp/k128probe.combined.ww (driver-expanded form, no
imports). Future enhancement: 915 could read the driver-emitted
combined.ww and add a cmp leg there.

Bootstrap NEUTRAL (zero current module.array[i] consumers; strconv
decimal.ww uses IDENT-base from within package). 178/178 incl.
990-997 + combined_ww_fresh green. Sibling bugs #137 (chained N_DOT)
/ #141 (variadic-gather esz==2) / #142 (wwstage primsize-on-alias)
properly deferred to backlog.
This commit is contained in:
2026-05-27 03:07:49 +09:00
parent 7230f3aa61
commit 1de1b9a8a9
6 changed files with 372 additions and 3 deletions

View File

@@ -692,6 +692,8 @@ static Mod *mod_map;
typedef struct LetVar LetVar;
struct LetVar {
const char *name;
Type *type; /* #128b: imported-let type lookup for module-
* qualified N_INDEX base esz dispatch. */
LetVar *next;
};
static LetVar *letvars;
@@ -905,6 +907,7 @@ let_collect(Cg *c, Node *file)
if (let_emit_size(d->type) == 0) continue;
LetVar *lv = amalloc(c->a, sizeof *lv);
lv->name = d->str;
lv->type = d->type; /* #128b */
lv->next = letvars;
letvars = lv;
}
@@ -919,6 +922,22 @@ let_islet(const char *name)
return 0;
}
/* #128b: look up a top-level let's type by leaf name. Sister of
* wwstage's letvartnode (selfhost/cmd/wcc/cgen.ww:999). Used at the
* cgindex / cg_dotbase_addr sites where a module-qualified base
* (`mod.arr`) leaves n->lhs->type NULL (SK_USE-bound module ident),
* so the imported array's element type / size must come through
* this let-map lookup instead. Returns NULL if name isn't a tracked
* top-level let. */
static Type *
let_var_type(const char *name)
{
if (name == NULL) return NULL;
for (LetVar *lv = letvars; lv; lv = lv->next)
if (strcmp(lv->name, name) == 0) return lv->type;
return NULL;
}
/* Glue `<module>.<ident>` into a fresh arena buffer. */
static const char *
mod_join(Cg *c, const char *mod, const char *ident)
@@ -1240,6 +1259,26 @@ cg_dotbase_addr(Cg *c, Node *base, int dst_reg, Local *locals)
Node *inner = base->lhs;
if (inner == NULL || inner->kind != N_IDENT) return 0;
Type *bt = inner->type;
/* #128b: module-qualified `mod.arr` where arr is an imported
* top-level `let X: [N]T`. The checker leaves SK_USE module-idents
* with NULL/ty_err type; detect via let_islet + let_var_type-of-
* TY_ARRAY and emit LEAQ X(SB) for the array's base address.
* Without this, the N_INDEX fallback at cgen.c:~6760 falls to
* cgexpr(base) which auto-MOVQs the symbol contents as if it
* were a pointer-var (= load 8 bytes of the array's first
* elements + treat as junk address) — segfault-class miscompile. */
if (bt == NULL || bt == ty_err) {
if (let_islet(base->str)) {
Type *lt = let_var_type(base->str);
Type *lu = type_chase_named(lt);
if (lu && lu->kind == TY_ARRAY) {
ins2(c, A_LEAQ, masym(c, base->str),
areg(dst_reg));
return 1;
}
}
return 0;
}
Type *bu = type_chase_named(bt);
if (bu == NULL) return 0;
int viaptr = 0;
@@ -6659,6 +6698,19 @@ cgexpr(Cg *c, Node *n, Local *locals)
* For `*[N]T` drill through to the array so esz/esub reflect
* T, not sizeof(array). */
Type *bt = n->lhs ? n->lhs->type : NULL;
/* #128b: module-qualified `mod.arr[i]` — n->lhs is N_DOT and
* its type is NULL (SK_USE-bound module ident). Look up the
* imported let's type via let_var_type so esz/esub reflect
* the imported array's element width instead of falling to
* the esz=1 default (→ MOVZBQ wrong-width load). Sister of
* the dst-side cg_dotbase_addr branch that emits LEAQ for
* the base address. */
if ((bt == NULL || bt == ty_err)
&& n->lhs && n->lhs->kind == N_DOT
&& n->lhs->str
&& let_islet(n->lhs->str)) {
bt = let_var_type(n->lhs->str);
}
Type *u = (bt && bt->kind == TY_NAMED) ? bt->under : bt;
Type *eff = idx_eff(bt);
int esz = 1;