wwstage: displacement store for global-ptr scalar field, align to cstage
cgassign had dedicated N_DOT-store arms for a local-ptr base, a global value-struct, and chained bases, but none for a global-pointer scalar field. That case fell through to the generic cgplaceaddr/dotchainaddr route, which folds the field offset (ADDQ $foff,BX) then stores to (BX). cstage emits a single displacement store (MOVQ AX,foff(BX)) via its via_ptr global scalar arm, so the two stages diverged on asm shape (rule 10). Both forms are runtime-correct here -- BX is a fresh throwaway in the generic route -- so this was a byte-id divergence, not a miscompile. Add the missing displacement-store arm, predicate-mirroring cstage's via_ptr global scalar arm exactly: plain assignment only, scalar field only; non-scalar field types stay on the generic path (their global-ptr deref is a separate deferred item). glob_ptr_field_test.ww gains an off-8 row as the regression pin -- offset-0 cannot catch it because ADDQ $0 is suppressed. Surfaced by the fold-2 Fam-5 migration.
This commit is contained in:
@@ -10801,6 +10801,91 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
// #8 (rule-10 byte-id): scalar field STORE through a module-GLOBAL
|
||||||
|
// `*struct` pointer base (`gp.f = v`; gp is isletvar — not a local,
|
||||||
|
// not a value-struct). The local-ptr arm (lc!=nil) and the value-
|
||||||
|
// struct global arm (letvarstructinfo, N_TNAME-only) both miss it, so
|
||||||
|
// it fell to the F6 cgplaceaddr route which folds the field offset via
|
||||||
|
// `ADDQ $foff, BX` + a plain `MOVQ AX,(BX)`; cstage's via_ptr global
|
||||||
|
// scalar arm (cgen.c #47, boff==0 && let_islet) emits the DISPLACEMENT
|
||||||
|
// store `<sop> AX, foff(BX)`. Align ww UP. Plain `=` scalar only —
|
||||||
|
// str/slice/tagged/float/struct fields keep their F6 route; compound
|
||||||
|
// (`OP=`) stays on F6 (the latent via_ptr global LOAD is #60/#61,
|
||||||
|
// deferred). The PUSHQ/POPQ AX bracketing the base load mirrors cstage
|
||||||
|
// byte-for-byte (rhs in AX survives the LEAQ/MOVQ, which touch only BX).
|
||||||
|
if (lhs != nil) {
|
||||||
|
if (lhs.kind == syntax.nkind.N_DOT && n.op == syntax.tkind.TK_ASSIGN) {
|
||||||
|
let base: *syntax.node = lhs.lhs;
|
||||||
|
let fld: str = lhs.str;
|
||||||
|
// `(*gp).f` parses as N_DOT over N_UN(STAR, IDENT); retarget
|
||||||
|
// to the inner IDENT so it fires identically to `gp.f`
|
||||||
|
// (mirror of the local-ptr arm + cstage retarget).
|
||||||
|
if (base != nil) {
|
||||||
|
if (base.kind == syntax.nkind.N_UN) {
|
||||||
|
if (base.op == syntax.tkind.TK_STAR) {
|
||||||
|
if (base.lhs != nil) {
|
||||||
|
if (base.lhs.kind == syntax.nkind.N_IDENT) {
|
||||||
|
base = base.lhs;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
if (base != nil) {
|
||||||
|
if (base.kind == syntax.nkind.N_IDENT) {
|
||||||
|
let bn: str = base.str;
|
||||||
|
if (localfindnode(c, bn) == nil) {
|
||||||
|
let tn: *syntax.node = letvartnode(c, bn);
|
||||||
|
if (tn != nil) {
|
||||||
|
if (tn.kind == syntax.nkind.N_TPTR) {
|
||||||
|
let inner: *syntax.node = tn.lhs;
|
||||||
|
let si: *structinfo = structlookupchain(c, inner);
|
||||||
|
if (si != nil) {
|
||||||
|
let fi: *fieldinfo = si.fields;
|
||||||
|
for (fi != nil) {
|
||||||
|
if (syntax.streq(fi.fname, fld)) {
|
||||||
|
// scalar only: str/slice/tagged/float/
|
||||||
|
// struct fields keep their F6 route.
|
||||||
|
let scalar: bool = true;
|
||||||
|
if (istaggedtype(c, fi.tnode)) { scalar = false; };
|
||||||
|
if (isstrtype(c, fi.tnode) || isslicetype(c, fi.tnode)) { scalar = false; };
|
||||||
|
if (isfloattype(c, fi.tnode)) { scalar = false; };
|
||||||
|
if (fi.tnode != nil) {
|
||||||
|
if (fi.tnode.kind == syntax.nkind.N_TNAME) {
|
||||||
|
if (aliasprimsize(c, fi.tnode.str) == 0) {
|
||||||
|
if (structlookup(c, fi.tnode.str) != nil) { scalar = false; };
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
if (scalar) {
|
||||||
|
cgexpr(c, n.rhs);
|
||||||
|
emitline("\tPUSHQ\tAX\n");
|
||||||
|
emitline("\tLEAQ\t");
|
||||||
|
emitsymname(c, bn);
|
||||||
|
emitline("(SB), BX\n");
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitdispreg(0i64, "BX");
|
||||||
|
emitline(", BX\n");
|
||||||
|
emitline("\tPOPQ\tAX\n");
|
||||||
|
let sop: str = fieldstoreop(c, fi);
|
||||||
|
emitline("\t");
|
||||||
|
emitline(sop);
|
||||||
|
emitline("\tAX, ");
|
||||||
|
emitdispreg(fi.foff: i64, "BX");
|
||||||
|
emitline("\n");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
fi = fi.finext;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
// Chained `<expr>.field = v` where `<expr>` itself is a chain
|
// Chained `<expr>.field = v` where `<expr>` itself is a chain
|
||||||
// of dots resolving to a *struct. Mirrors the C cgen branch
|
// of dots resolving to a *struct. Mirrors the C cgen branch
|
||||||
// added to close trap 1 (cmd/w6c/cgen.c). Without this, only
|
// added to close trap 1 (cmd/w6c/cgen.c). Without this, only
|
||||||
|
|||||||
@@ -24,3 +24,17 @@ let gp: *S = nil;
|
|||||||
gp.f = 7;
|
gp.f = 7;
|
||||||
assert(backing.f: i32 == 7);
|
assert(backing.f: i32 == 7);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@test fn store_off8() void = {
|
||||||
|
// #8 (rule-10): a NON-zero-offset field (g at +8) through the global
|
||||||
|
// pointer. wwstage folded the offset into BX (`ADDQ $8,BX` + plain
|
||||||
|
// `MOVQ AX,(BX)`) while cstage emitted the displacement store
|
||||||
|
// `MOVQ AX,8(BX)` — a cs!=ww .s divergence T2 catches. Offset-0 rows
|
||||||
|
// CANNOT pin this (`ADDQ $0` is suppressed). backing.g is poisoned
|
||||||
|
// non-zero first, then overwritten via gp.g, so a store that lands at
|
||||||
|
// the wrong displacement is caught on read-back.
|
||||||
|
gp = &backing;
|
||||||
|
backing.g = 11;
|
||||||
|
gp.g = 7;
|
||||||
|
assert(backing.g: i32 == 7);
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user