w6c+w6c_ww: global-base arm for indexed struct-element field read (fix #21)

The `arr[i].field` N_DOT read branch in both stages was gated on a LOCAL
base lookup (cstage `localfind != 0`, wwstage `localfindnode != nil`). A
module-GLOBAL base (`let g: [2]pt = [...]`) missed it:

  - cstage fell to a generic index-load that drops f->offset — it read
    element[i] at offset 0, so `g[i].b` returned a's value (g[0].b -> 1,
    g[1].b -> 3 instead of 2, 4).
  - wwstage fell to the module-qualified SB fallback — garbage, no main.g
    load at all.

Silent, byte-id-divergent. This is the READ twin of #11 (the global
`g[i] = v` write fix) and the #15 sibling. Local `[N]struct` bases read
correctly (tests 680/681 cover only those), which is why it was never
caught.

Fix (both stages, converged byte-identical): resolve the global the same
way the N_INDEX arm does — cstage `let_islet || def_isarraydef`, wwstage
`letvartnode || defvartnode` — and dispatch the base load by shape: array
-> LEAQ name(SB) (the symbol IS the storage), slice/ptr -> MOVQ name(SB)
(the symbol's first word IS the .ptr). The field then loads at f->offset
exactly as the local arm does. esz (element stride) and f->offset both
come from the type table (rule 13). Mirrors #11's write-side global-base
resolution. combined.ww embeds (w6c + wwdump) regenerate.

688_global_arr_elem_field: global `[2]pt` reads of .a/.b on both elements
(the .b reads are the bug), a non-8-aligned `[2]rec {tag:u8,x:i32,y:i64}`
to stress f->offset + a u8 sub-word leaf, and a slice-base read
(`let g: []rec = arr;`) that exercises the MOVQ-deref .ptr arm. Runtime
(cstage build+run) + cstage==wwstage byte-id per row. The slice row is
byte-id ONLY: its read asm is correct and identical on both stages, but a
slice-of-struct module global does not data-emit a symbol yet (a separate,
pre-existing data-emission gap, sibling of #10/#20), so it cannot link/run.
This commit is contained in:
2026-06-03 22:29:57 +09:00
parent d40224755a
commit 8dda8ea76c
6 changed files with 437 additions and 32 deletions

View File

@@ -8361,8 +8361,21 @@ cgexpr(Cg *c, Node *n, Local *locals)
int is_sl = bu && bu->kind == TY_SLICE;
int is_ptr = bu && bu->kind == TY_PTR;
int off = localfind(locals, idxbase->str);
/* #21 (READ twin of #11): a module-GLOBAL base
* makes localfind return 0, so the field-offset-
* aware branch was skipped and `g[i].field` fell to
* a generic index-load that drops f->offset (reads
* element[i] at offset 0). Resolve the global the
* same way the N_INDEX arm does (let_islet ||
* def_isarraydef) and dispatch the base load by
* shape: array -> LEAQ name(SB) (the symbol IS the
* storage), slice/ptr -> MOVQ name(SB) (the symbol's
* first word IS the .ptr). */
int isglobal = (off == 0)
&& (let_islet(idxbase->str)
|| def_isarraydef(idxbase->str));
if (f != NULL && (is_arr || is_sl || is_ptr)
&& off != 0) {
&& (off != 0 || isglobal)) {
int esz = (int)elemt->size;
cgexpr(c, n->lhs->rhs, locals);
if (esz > 1) {
@@ -8371,7 +8384,15 @@ cgexpr(Cg *c, Node *n, Local *locals)
ins2(c, A_IMULQ, areg(D_CX),
areg(D_AX));
}
if (is_arr)
if (isglobal && is_arr)
ins2(c, A_LEAQ,
masym(c, idxbase->str),
areg(D_BX));
else if (isglobal)
ins2(c, A_MOVQ,
masym(c, idxbase->str),
areg(D_BX));
else if (is_arr)
ins2(c, A_LEAQ,
amem(D_BP, off), areg(D_BX));
else