selfhost+test: route chained N_INDEX outer element size through indexvaluetnode (#24)
Wwstage cgindex's base-inspection (cgenexpr.ww) only computed esz/
signed_elem when base.kind == N_IDENT or N_DOT. For a chained
`names[i][k]` (names: **u8) the outer N_INDEX has base.kind ==
N_INDEX; esz fell through to the default 8 so the outer load
emitted `MOVQ (AX), AX` over a 1-byte u8 plus a stray
`MOVQ $8, CX; IMULQ CX, AX` scaling on the outer index that cstage
doesn't emit. Wrong-width-narrow-load: the byte was read as 8 bytes
(reaching into adjacent memory) and the outer offset multiplied by
sizeof *u8 instead of sizeof u8.
Cstage walks n->lhs->type directly via the typed AST
(cmd/w6c/cgen.c idx_eff → eff->sub->size at N_INDEX). Wwstage
needed the parallel via indexvaluetnode — return the value-type
of an N_INDEX expression by stripping one element layer off base's
type, recursing for chained inner. cgindex's else-if chain now
adds the N_INDEX arm: call indexvaluetnode + elemsizeofc/
elemissignedc.
Class A wwstage cgen UNDER. Surfaced first time the codebase
exercised the **T[i][k] shape — through expanddir in
selfhost/cmd/ww/main.ww (post-#22 dir-enum, commit 9e0816e). The
workaround there split names[i][k] into `let nm: *u8 = names[i];
nm[k]` to route through the bare-pointer index path. Retired in
this commit: expanddir uses the natural chained form since the
read path is now byte-identical across stages.
Bundling justification (rule 11): the workaround retirement is
the in-tree verification this fix works — without retiring,
neither bootstrap byte-id nor 995_self_rebuild exercises the
chained read shape. Test 739_chained_index pins cstage-byte-
identical asm for **u8 (MOVZBQ load, 1 inner-stride-8 IMULQ pair,
no outer scale) + **i32 (MOVSXD load, inner $8 + outer $4 IMULQ
pairs).
Sister latents filed (no in-tree consumer, no probe):
Task #27 — cgassign chained-write N_INDEX: write path
`names[i][k] = v` for **u8 has the same dispatch gap. Selfhost +
lib grep is empty.
New latent (filed during review) — cgindex N_DOT base on chained
index: `obj.mat[i][k]` over a struct-field base falls back to
esz=8. indexvaluetnode currently handles N_IDENT + N_INDEX bases
only.
113/113 ok. ww2 == ww3 == ww4 byte-id.
This commit is contained in:
@@ -1241,6 +1241,32 @@ fn elemsizeofc(c: *cgen, t: *node) i32 = {
|
||||
return slotsize(c, elem);
|
||||
};
|
||||
|
||||
// indexvaluetnode — type node of the value produced by an N_INDEX
|
||||
// expression. Walks base's type and returns its element. Recurses
|
||||
// through chained N_INDEX so `names[i][k]` (names: **u8) resolves
|
||||
// the outer base type to *u8 (the post-inner-index value type), so
|
||||
// cgindex can compute the outer element size honestly. Mirrors
|
||||
// cstage's `n->lhs->type` via typed-AST (cmd/w6c/cgen.c idx_eff).
|
||||
fn indexvaluetnode(c: *cgen, n: *node) *node = {
|
||||
if (n == nil) { return nil; };
|
||||
if (n.kind != nkind.N_INDEX) { return nil; };
|
||||
let base: *node = n.lhs;
|
||||
if (base == nil) { return nil; };
|
||||
let bt: *node = nil;
|
||||
if (base.kind == nkind.N_IDENT) {
|
||||
let lc: *local = localfindnode(c, base.str);
|
||||
if (lc != nil) { bt = lc.tnode; }
|
||||
else { bt = letvartnode(c, base.str); };
|
||||
};
|
||||
if (base.kind == nkind.N_INDEX) { bt = indexvaluetnode(c, base); };
|
||||
if (bt == nil) { return nil; };
|
||||
let k: nkind = bt.kind;
|
||||
if (k == nkind.N_TPTR) { return bt.lhs; };
|
||||
if (k == nkind.N_TSLICE) { return bt.lhs; };
|
||||
if (k == nkind.N_TARRAY) { return bt.lhs; };
|
||||
return nil;
|
||||
};
|
||||
|
||||
// nodeisunsigned — best-effort cgen-time inference from the AST. We
|
||||
// don't have a typed AST yet, so we walk surface nodes:
|
||||
// nkind.N_INTLIT — never marked unsigned (no tsuffix plumbing yet)
|
||||
|
||||
Reference in New Issue
Block a user