wcc/ww: match on a tagged struct-field scrutinee reads it in place (#25)

wwstage cgmatch unconditionally spilled any non-ident match scrutinee
-- including an addressable BP-relative N_DOT struct field -- into
@match_spill and dispatched off the copy (frame $48); cstage reads
such a field in place at its BP offset ($32). Both stages were already
runtime-correct (latent rule-10 leanness, not a miscompile); this
aligns wwstage down to cstage so the asm is byte-identical.

The new in-place arm mirrors cstage cgen.c:10241-10296 verbatim: an
N_DOT scrutinee with a bare N_IDENT base whose type chases to a value
TY_STRUCT and whose field is found by name reads tag/payload at
localfind(base)+field.offset. The *ptr-field and call-result cases
stay on the spill path by construction (their base does not chase to
TY_STRUCT) -- no extra guard. A global value-struct base mis-resolves
identically in both stages (localfind returns 0); left untouched as a
shared latent (#29), since a ww-only guard would break byte-id.

Regenerates the w6c and wwdump combined.ww. Table-driven 831 test:
6 rows (local-field, *ptr-field, plain-ident, call-result, payload
remap, str payload) x runtime-both-stages + cs-vs-ww byte-id.
This commit is contained in:
2026-06-14 11:59:11 +09:00
parent 851915e6fe
commit 533333bd1a
5 changed files with 415 additions and 0 deletions

View File

@@ -26298,6 +26298,41 @@ fn cgmatch(c: *cgen, n: *node) void = {
};
}; };
} else {
// M1 (#25): match on a tagged field of a BARE-ident VALUE
// struct reads the box IN PLACE at base.off + field.offset
// — the box (tag@+0, word0@+8, word1@+16) is contiguous in
// the parent frame, so no @match_spill copy. Verbatim mirror
// of cstage N_MATCH's in-place arm (cmd/w6c/cgen.c:10241-
// 10296): gate on N_DOT with a bare-IDENT base whose stamped
// 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.
let mfld: *tfield = nil;
if (scrut.kind == nkind.N_DOT && scrut.lhs != nil
&& scrut.lhs.kind == nkind.N_IDENT
&& scrut.lhs.type_ != nil) {
let bu: *tinfo = tichase(scrut.lhs.type_: *tinfo);
if (bu != nil && bu.kind == tykind.TY_STRUCT) {
let fl: *tfield = bu.fields;
for (fl != nil) {
if (streq(fl.name, scrut.str)) {
mfld = fl;
break;
};
fl = fl.tnext;
};
};
};
if (mfld != nil) {
let foff: i32 = mfld.offset: i32;
scrutoff = localfind(c, scrut.lhs.str) + foff;
scrutt = matchscrutt(c, scrut);
} else {
// Non-ident scrutinee (call result, arr[i], p.field,
// ?, etc.). Spill into an `@match_spill` scratch slot
// and dispatch off it. Tagged returns (N_CALL) follow
@@ -26398,6 +26433,7 @@ fn cgmatch(c: *cgen, n: *node) void = {
};
};
}; };
};
};
};
let endl: str = mklabel(c, "match_end");