diff --git a/selfhost/cmd/wcc/cgenexpr.ww b/selfhost/cmd/wcc/cgenexpr.ww index c8998889..1fd175e9 100644 --- a/selfhost/cmd/wcc/cgenexpr.ww +++ b/selfhost/cmd/wcc/cgenexpr.ww @@ -3293,6 +3293,29 @@ fn cgptrfieldload(c: *cgen, fi: *fieldinfo) void = { emitline(", AX\n"); }; +// emitchainbase — load the chained-DOT root BASE into CX for the viacx +// (global or `*T`-root) spine. Mirrors cstage cgen.c's uniform base +// resolution: a global is reached via LEAQ name(SB),CX; a `*struct` +// ROOT then derefs that address once (MOVQ (CX),CX) — covering the +// global `*struct` root, where BOTH flags hold (#16) — while a LOCAL +// `*T` root loads its frame slot (MOVQ off(BP),CX). The leaf load then +// indexes at totaloff off CX. +fn emitchainbase(c: *cgen, ptrroot: bool, isglobal: bool, + rootoff: i32, rootname: str) void = { + if (isglobal) { + emitline("\tLEAQ\t"); + emitsymname(c, rootname); + emitline("(SB), CX\n"); + if (ptrroot) { + emitline("\tMOVQ\t(CX), CX\n"); + }; + } else { + emitline("\tMOVQ\t"); + emitoff(rootoff: i64); + emitline("(BP), CX\n"); + }; +}; + fn cgdot(c: *cgen, n: *syntax.node) void = { let lhs: *syntax.node = n.lhs; let fld: str = n.str; @@ -4460,15 +4483,8 @@ fn cgdot(c: *cgen, n: *syntax.node) void = { let viacx: bool = isglobal || ptrroot; if (slicedelta >= 0) { if (viacx) { - if (ptrroot) { - emitline("\tMOVQ\t"); - emitoff(rootoff: i64); - emitline("(BP), CX\n"); - } else { - emitline("\tLEAQ\t"); - emitsymname(c, rootname); - emitline("(SB), CX\n"); - }; + emitchainbase(c, ptrroot, isglobal, + rootoff, rootname); emitline("\tMOVQ\t"); emitdispreg((totaloff + slicedelta): i64, "CX"); emitline(", AX\n"); @@ -4493,15 +4509,8 @@ fn cgdot(c: *cgen, n: *syntax.node) void = { tlu = tichase(tlu); let ttsz: i32 = tlu.size: i32; if (viacx) { - if (ptrroot) { - emitline("\tMOVQ\t"); - emitoff(rootoff: i64); - emitline("(BP), CX\n"); - } else { - emitline("\tLEAQ\t"); - emitsymname(c, rootname); - emitline("(SB), CX\n"); - }; + emitchainbase(c, ptrroot, isglobal, + rootoff, rootname); cgloadtaggedfield(c, "CX", totaloff, ttsz); } else { cgloadtaggedfield(c, "BP", @@ -4521,15 +4530,8 @@ fn cgdot(c: *cgen, n: *syntax.node) void = { // BP-rooted locals the registers do not alias so order is // free. if (viacx) { - if (ptrroot) { - emitline("\tMOVQ\t"); - emitoff(rootoff: i64); - emitline("(BP), CX\n"); - } else { - emitline("\tLEAQ\t"); - emitsymname(c, rootname); - emitline("(SB), CX\n"); - }; + emitchainbase(c, ptrroot, isglobal, + rootoff, rootname); emitline("\tMOVQ\t"); emitdispreg(totaloff: i64, "CX"); emitline(", AX\n"); @@ -4556,15 +4558,8 @@ fn cgdot(c: *cgen, n: *syntax.node) void = { let mov: str = "MOVSD"; if (syntax.typeisf32(leaftype)) { mov = "MOVSS"; }; if (viacx) { - if (ptrroot) { - emitline("\tMOVQ\t"); - emitoff(rootoff: i64); - emitline("(BP), CX\n"); - } else { - emitline("\tLEAQ\t"); - emitsymname(c, rootname); - emitline("(SB), CX\n"); - }; + emitchainbase(c, ptrroot, isglobal, + rootoff, rootname); emitline("\t"); emitline(mov); emitline("\t"); @@ -4582,15 +4577,8 @@ fn cgdot(c: *cgen, n: *syntax.node) void = { let lop: str = loadopsz(syntax.typeissigned(leaftype), leaftype.slotsize: i32); if (viacx) { - if (ptrroot) { - emitline("\tMOVQ\t"); - emitoff(rootoff: i64); - emitline("(BP), CX\n"); - } else { - emitline("\tLEAQ\t"); - emitsymname(c, rootname); - emitline("(SB), CX\n"); - }; + emitchainbase(c, ptrroot, isglobal, + rootoff, rootname); emitline("\t"); emitline(lop); emitline("\t"); @@ -11216,6 +11204,15 @@ fn cgassign(c: *cgen, n: *syntax.node) void = { let yok: bool = dotchainresolve(c, lhs, &rootname, &rootoff, &totaloff, &leaftype, &slicedelta, &isglobal, &ptrroot); + // A global `*struct` root chained STORE: cstage stores + // via the address-compute spine (LEAQ name(SB),BX; MOVQ + // (BX),BX; ADDQ; MOVQ AX,(BX)), NOT the READ path's + // offset-fold — so the fold spine below would diverge. + // Decline and fall through to the address-spine mirror, + // exactly as the addr-of path declines a ptr-rooted chain + // (cgenexpr.ww:5114). A LOCAL `*T` root (ptrroot && + // !isglobal) DOES fold in both stages and stays. (#16) + if (isglobal && ptrroot) { yok = false; }; if (yok) { // `*T` root and global share the CX-based emit: // loader runs AFTER cgexpr(rhs) so AX/BX/X0 stay @@ -11224,15 +11221,8 @@ fn cgassign(c: *cgen, n: *syntax.node) void = { if (slicedelta >= 0) { cgexpr(c, n.rhs); if (viacx) { - if (ptrroot) { - emitline("\tMOVQ\t"); - emitoff(rootoff: i64); - emitline("(BP), CX\n"); - } else { - emitline("\tLEAQ\t"); - emitsymname(c, rootname); - emitline("(SB), CX\n"); - }; + emitchainbase(c, ptrroot, isglobal, + rootoff, rootname); emitline("\tMOVQ\tAX, "); emitdispreg((totaloff + slicedelta): i64, "CX"); emitline("\n"); @@ -11539,15 +11529,8 @@ fn cgassign(c: *cgen, n: *syntax.node) void = { if (syntax.typeisf32(leaftype)) { mov = "MOVSS"; }; cgexpr(c, n.rhs); if (viacx) { - if (ptrroot) { - emitline("\tMOVQ\t"); - emitoff(rootoff: i64); - emitline("(BP), CX\n"); - } else { - emitline("\tLEAQ\t"); - emitsymname(c, rootname); - emitline("(SB), CX\n"); - }; + emitchainbase(c, ptrroot, isglobal, + rootoff, rootname); emitline("\t"); emitline(mov); emitline("\tX0, "); diff --git a/selfhost/cmd/wcc/cgenutil.ww b/selfhost/cmd/wcc/cgenutil.ww index d4f75137..c667aab0 100644 --- a/selfhost/cmd/wcc/cgenutil.ww +++ b/selfhost/cmd/wcc/cgenutil.ww @@ -4942,6 +4942,28 @@ export fn dotchainresolve(c: *cgen, n: *syntax.node, }; }; }; + // Global `*struct` root (`let gp: *S`): localfindnode is nil and + // letvarstructinfo below only matches a VALUE-struct global, so a + // `*struct` global root needs its own arm. Gate on N_TPTR→N_TNAME + // exactly as the local `*T` arm above; the post-resolution loop peels + // the TY_PTR off cur.type_ and validates the pointee is a struct. + // Mirrors cstage cgen.c's ptr_root + let_islet base resolution + // (LEAQ name(SB),CX; MOVQ (CX),CX). (#16, chained twin of #15.) + if (!resolved && lc == nil) { + let gtn: *syntax.node = letvartnode(c, cur.str); + if (gtn != nil) { + if (gtn.kind == syntax.nkind.N_TPTR) { + let pe: *syntax.node = gtn.lhs; + if (pe != nil) { + if (pe.kind == syntax.nkind.N_TNAME) { + *outisglobal = true; + *outptrroot = true; + resolved = true; + }; + }; + }; + }; + }; if (!resolved) { let gsi: *structinfo = letvarstructinfo(c, cur.str); if (gsi != nil) { diff --git a/test/wcc/689_globptr_field_read_run.c b/test/wcc/689_globptr_field_read_run.c index 682abb1c..eaac96be 100644 --- a/test/wcc/689_globptr_field_read_run.c +++ b/test/wcc/689_globptr_field_read_run.c @@ -29,14 +29,19 @@ * gp.len (*[]u8 pseudo-field) | 3 * lp.f (LOCAL ptr control) | 42 * bump param p.f (param control) | 10 + * gp.sf.len (CHAINED str leaf) | 3 (#16) + * gp.x.q (CHAINED struct leaf)| 7 (#16) * Each row also asserts w6c .s == w6c_ww .s byte-identical. * Pre-fix: cstage SEGV(139) on every global-ptr row; wwstage ran wrong; * cs vs ww .s differed. * - * SCOPE: single-dot field reads only. Chained-through-global-ptr-root - * (`gp.x.y`, `gp.sf.len` inline) is a sibling — both stages run correct - * after this fix, but the chained-N_DOT spines still diverge in idiom - * (cstage offset-folds; wwstage inner-loads + shuffles), filed separately. + * #16 (chained-through-global-ptr-root, the last two rows): after #15 + * both stages RAN correct but byte-DIVERGED on the chained-N_DOT spine — + * cstage offset-folds (LEAQ name(SB),CX; MOVQ (CX),CX; MOVQ (CX)) + * while wwstage's chained spine bailed (dotchainresolve missed a global + * `*struct` root) onto the naive inner-load-and-shuffle catch-all. Fix + * aligns wwstage UP: dotchainresolve resolves a global `*struct` root as + * isglobal && ptrroot and the spine emits the same offset-fold (rule 10). */ #include #include @@ -106,6 +111,24 @@ static const struct row ROWS[] = { "type S = struct { f: i64, g: i64 };\n" "fn rd(p: *S) i32 = { return (p.f + p.g): i32; };\n" "export fn main() i32 = { let s: S = S{f=4,g=6}; return rd(&s); };\n", 10 }, +/* #16: CHAINED read through a global-`*struct` root. Pre-fix BOTH stages + * ran correct after #15 but byte-DIVERGED — cstage offset-folds (LEAQ + * name(SB),CX; MOVQ (CX),CX; MOVQ (CX)) while wwstage's chained + * spine bailed (dotchainresolve missed a global `*struct` root) onto the + * naive inner-load-and-shuffle catch-all. */ +{ "chain_str_len", + "package main;\n" + "type S = struct { a: i64, sf: str };\n" + "let gp: *S = nil;\n" + "export fn main() i32 = { let s: S = S{a=0,sf=\"abc\"}; gp = &s;" + " return gp.sf.len: i32; };\n", 3 }, +{ "chain_struct_field", + "package main;\n" + "type Inner = struct { p: i64, q: i64 };\n" + "type S = struct { a: i64, x: Inner };\n" + "let gp: *S = nil;\n" + "export fn main() i32 = { let s: S = S{a=0,x=Inner{p=0,q=0}}; gp = &s;" + " s.x.q = 7; return gp.x.q: i32; };\n", 7 }, }; #define NROWS ((int)(sizeof ROWS / sizeof ROWS[0]))