diff --git a/selfhost/cmd/wcc/cgenexpr.ww b/selfhost/cmd/wcc/cgenexpr.ww index 774cbd6b..bb4137d7 100644 --- a/selfhost/cmd/wcc/cgenexpr.ww +++ b/selfhost/cmd/wcc/cgenexpr.ww @@ -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 ` 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 `.field = v` where `` itself is a chain // of dots resolving to a *struct. Mirrors the C cgen branch // added to close trap 1 (cmd/w6c/cgen.c). Without this, only diff --git a/test/lang/glob_ptr_field_test.ww b/test/lang/glob_ptr_field_test.ww index c5a3736a..3743b9df 100644 --- a/test/lang/glob_ptr_field_test.ww +++ b/test/lang/glob_ptr_field_test.ww @@ -24,3 +24,17 @@ let gp: *S = nil; gp.f = 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); +};