wcc/ww: match on a global value-struct tagged field reads g(SB) (#29)

A match whose scrutinee is a tagged field of a GLOBAL value-struct read
the tag/payload from the BP region (saved-BP + return-addr) instead of
g(SB) and returned garbage. Both stages were identical-wrong, so the
byte-id gate could not see it -- a gate-blind regression introduced by
M1 (#25): M1's in-place N_DOT match arm uses localfind(base), which
returns the 0 not-found sentinel for a global base, so 0+field.offset
landed in the frame.

Gate the in-place arm on a confirmed-local base -- `localfind(base)==0
&& let_islet/isletvar(base)`, verbatim from cstage's own global test at
cgen.c:2000 (both stages, same spelling). A global base now falls
through to the existing spill path, which cgexprs the scrutinee and
resolves g(SB). M1's local-field in-place ($32) path is untouched.

Regenerates the w6c and wwdump combined.ww. Table-driven 841 test
(global int/reassign/str-payload + a local-field M1 regression row),
runtime-discriminating: pre-fix returns garbage, post-fix 42 on both
stages; rob's direct-global-field spill caveat confirmed at runtime.
This commit is contained in:
2026-06-14 16:52:00 +09:00
parent 7a6b67fecc
commit c86c6a3bbf
6 changed files with 328 additions and 18 deletions

View File

@@ -2961,11 +2961,17 @@ fn cgmatch(c: *cgen, n: *node) void = {
// type chases to TY_STRUCT and whose field resolves by name.
// A *ptr-field base (`match (h.e)`, h:*struct) chases to
// TY_PTR, the by-name scan misses, and it falls to the spill
// arm below — exactly as cstage, no separate deref guard. A
// global VALUE-struct base mis-resolves here in BOTH stages
// (shared latent #29), matched identically — no ww-only
// global guard. align-DOWN to cstage (rule 10): both stages
// emit TEXT $32 on the local-field case.
// arm below — exactly as cstage, no separate deref guard.
// #29: the in-place arm is also gated on a CONFIRMED-LOCAL
// base (bglobal below). A global VALUE-struct base
// (`match (g.field)`) has localfind==0, so the M1 base.off
// would land in the saved-BP/return-addr region — both
// stages emitted `MOVQ (BP),AX` (gate-blind both-wrong). The
// global-base predicate `localfind==0 && isletvar` (cstage
// twin: let_islet, cgen.c:2000) routes it through to the
// spill `else`, which resolves g(SB). align-DOWN to cstage
// (rule 10): both stages emit TEXT $32 on the local-field
// case and the same g(SB) spill on the global-field case.
let mfld: *tfield = nil;
if (scrut.kind == nkind.N_DOT && scrut.lhs != nil
&& scrut.lhs.kind == nkind.N_IDENT
@@ -2982,7 +2988,12 @@ fn cgmatch(c: *cgen, n: *node) void = {
};
};
};
let bglobal: bool = false;
if (mfld != nil) {
bglobal = (localfind(c, scrut.lhs.str) == 0)
&& isletvar(c, scrut.lhs.str);
};
if (mfld != nil && !bglobal) {
let foff: i32 = mfld.offset: i32;
scrutoff = localfind(c, scrut.lhs.str) + foff;
scrutt = matchscrutt(c, scrut);