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

@@ -22306,8 +22306,28 @@ fn cgdot(c: *cgen, n: *node) void = {
let idxbase: *node = lhs.lhs;
if (idxbase != nil) { if (idxbase.kind == nkind.N_IDENT) {
let lc: *local = localfindnode(c, idxbase.str);
if (lc != nil) { if (lc.tnode != nil) {
let tn: *node = lc.tnode;
// #21 (READ twin of #11): a module-GLOBAL base makes
// localfindnode return nil, so this field-offset-aware
// branch was skipped and `g[i].field` fell through to
// the module-qualified fallback below (garbage — no
// main.g load at all). Resolve the global's tnode via
// letvartnode/defvartnode (the same source cgindex's
// global arm uses) 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). Mirrors cstage cgen.c's #21 arm.
let tn: *node = nil;
let isglobal: bool = false;
let gname: str;
gname.ptr = nil; gname.len = 0;
if (lc != nil) {
tn = lc.tnode;
} else {
tn = letvartnode(c, idxbase.str);
if (tn == nil) { tn = defvartnode(c, idxbase.str); };
if (tn != nil) { isglobal = true; gname = idxbase.str; };
};
if (tn != nil) {
let elemt: *node = nil;
let baseisarray: bool = false;
let tk: nkind = tn.kind;
@@ -22342,14 +22362,26 @@ fn cgdot(c: *cgen, n: *node) void = {
emitline(", CX\n");
emitline("\tIMULQ\tCX, AX\n");
};
if (baseisarray) {
emitline("\tLEAQ\t");
emitoff(lc.off: i64);
emitline("(BP), BX\n");
if (isglobal) {
if (baseisarray) {
emitline("\tLEAQ\t");
emitsymname(c, gname);
emitline("(SB), BX\n");
} else {
emitline("\tMOVQ\t");
emitsymname(c, gname);
emitline("(SB), BX\n");
};
} else {
emitline("\tMOVQ\t");
emitoff(lc.off: i64);
emitline("(BP), BX\n");
if (baseisarray) {
emitline("\tLEAQ\t");
emitoff(lc.off: i64);
emitline("(BP), BX\n");
} else {
emitline("\tMOVQ\t");
emitoff(lc.off: i64);
emitline("(BP), BX\n");
};
};
emitline("\tADDQ\tAX, BX\n");
if (viaptr) {
@@ -22415,7 +22447,7 @@ fn cgdot(c: *cgen, n: *node) void = {
};
};
};
};};
};
};};
};
};

View File

@@ -2562,8 +2562,28 @@ fn cgdot(c: *cgen, n: *node) void = {
let idxbase: *node = lhs.lhs;
if (idxbase != nil) { if (idxbase.kind == nkind.N_IDENT) {
let lc: *local = localfindnode(c, idxbase.str);
if (lc != nil) { if (lc.tnode != nil) {
let tn: *node = lc.tnode;
// #21 (READ twin of #11): a module-GLOBAL base makes
// localfindnode return nil, so this field-offset-aware
// branch was skipped and `g[i].field` fell through to
// the module-qualified fallback below (garbage — no
// main.g load at all). Resolve the global's tnode via
// letvartnode/defvartnode (the same source cgindex's
// global arm uses) 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). Mirrors cstage cgen.c's #21 arm.
let tn: *node = nil;
let isglobal: bool = false;
let gname: str;
gname.ptr = nil; gname.len = 0;
if (lc != nil) {
tn = lc.tnode;
} else {
tn = letvartnode(c, idxbase.str);
if (tn == nil) { tn = defvartnode(c, idxbase.str); };
if (tn != nil) { isglobal = true; gname = idxbase.str; };
};
if (tn != nil) {
let elemt: *node = nil;
let baseisarray: bool = false;
let tk: nkind = tn.kind;
@@ -2598,14 +2618,26 @@ fn cgdot(c: *cgen, n: *node) void = {
emitline(", CX\n");
emitline("\tIMULQ\tCX, AX\n");
};
if (baseisarray) {
emitline("\tLEAQ\t");
emitoff(lc.off: i64);
emitline("(BP), BX\n");
if (isglobal) {
if (baseisarray) {
emitline("\tLEAQ\t");
emitsymname(c, gname);
emitline("(SB), BX\n");
} else {
emitline("\tMOVQ\t");
emitsymname(c, gname);
emitline("(SB), BX\n");
};
} else {
emitline("\tMOVQ\t");
emitoff(lc.off: i64);
emitline("(BP), BX\n");
if (baseisarray) {
emitline("\tLEAQ\t");
emitoff(lc.off: i64);
emitline("(BP), BX\n");
} else {
emitline("\tMOVQ\t");
emitoff(lc.off: i64);
emitline("(BP), BX\n");
};
};
emitline("\tADDQ\tAX, BX\n");
if (viaptr) {
@@ -2671,7 +2703,7 @@ fn cgdot(c: *cgen, n: *node) void = {
};
};
};
};};
};
};};
};
};

View File

@@ -22306,8 +22306,28 @@ fn cgdot(c: *cgen, n: *node) void = {
let idxbase: *node = lhs.lhs;
if (idxbase != nil) { if (idxbase.kind == nkind.N_IDENT) {
let lc: *local = localfindnode(c, idxbase.str);
if (lc != nil) { if (lc.tnode != nil) {
let tn: *node = lc.tnode;
// #21 (READ twin of #11): a module-GLOBAL base makes
// localfindnode return nil, so this field-offset-aware
// branch was skipped and `g[i].field` fell through to
// the module-qualified fallback below (garbage — no
// main.g load at all). Resolve the global's tnode via
// letvartnode/defvartnode (the same source cgindex's
// global arm uses) 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). Mirrors cstage cgen.c's #21 arm.
let tn: *node = nil;
let isglobal: bool = false;
let gname: str;
gname.ptr = nil; gname.len = 0;
if (lc != nil) {
tn = lc.tnode;
} else {
tn = letvartnode(c, idxbase.str);
if (tn == nil) { tn = defvartnode(c, idxbase.str); };
if (tn != nil) { isglobal = true; gname = idxbase.str; };
};
if (tn != nil) {
let elemt: *node = nil;
let baseisarray: bool = false;
let tk: nkind = tn.kind;
@@ -22342,14 +22362,26 @@ fn cgdot(c: *cgen, n: *node) void = {
emitline(", CX\n");
emitline("\tIMULQ\tCX, AX\n");
};
if (baseisarray) {
emitline("\tLEAQ\t");
emitoff(lc.off: i64);
emitline("(BP), BX\n");
if (isglobal) {
if (baseisarray) {
emitline("\tLEAQ\t");
emitsymname(c, gname);
emitline("(SB), BX\n");
} else {
emitline("\tMOVQ\t");
emitsymname(c, gname);
emitline("(SB), BX\n");
};
} else {
emitline("\tMOVQ\t");
emitoff(lc.off: i64);
emitline("(BP), BX\n");
if (baseisarray) {
emitline("\tLEAQ\t");
emitoff(lc.off: i64);
emitline("(BP), BX\n");
} else {
emitline("\tMOVQ\t");
emitoff(lc.off: i64);
emitline("(BP), BX\n");
};
};
emitline("\tADDQ\tAX, BX\n");
if (viaptr) {
@@ -22415,7 +22447,7 @@ fn cgdot(c: *cgen, n: *node) void = {
};
};
};
};};
};
};};
};
};