wwstage: fold chained global-ptr field READ to cstage offset-fold (#16)
wwstage's chained-N_DOT resolver (dotchainresolve) didn't resolve a global
*struct root (only local *T and global value-struct), so gp.sf.len / gp.x.y
bailed to an inner-dot load + shuffle, byte-diverging from cstage's offset-fold.
Both stages already ran correct after #15 (475c003) -- a pure rule-10 asm
divergence. cstage is untouched (the oracle); wwstage aligns up.
Resolve a global N_TPTR root, and extract emitchainbase for the viacx base-load
(byte-identical across the 5 read + 2 store sites it replaces). The chained
STORE caller declines the global-ptr root (yok=false) so it falls to cstage's
address-spine mirror -- matching the #6/#15 decline-to-resolver discipline;
local *T chained stores still fold.
Test: +2 chained rows (gp.sf.len, gp.x.q), runtime + byte-id; proven to fail
byte-id with only the compiler files reverted, pass with the fix.
Sibling follow-ups filed: #17 (>32B tagged word-order), #18 (chained read into
an i64 sink MOVSXD check).
This commit is contained in:
@@ -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, ");
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 <folded>(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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@@ -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 <folded>(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]))
|
||||
|
||||
Reference in New Issue
Block a user