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

@@ -10243,7 +10243,11 @@ cgexpr(Cg *c, Node *n, Local *locals)
/* `match (p.field)` — point sl_off at the field's slot
* inside the parent struct. The slot layout (tag at +0,
* value words at +8/+16) is contiguous within the struct,
* so no spill is needed. */
* so no spill is needed. #29: gated below on a
* CONFIRMED-LOCAL base — a global base (`match (g.field)`)
* has localfind==0, so 0+field.offset would land in the
* saved-BP/return-addr region; it falls through to the
* spill `else`, which resolves g(SB). */
Type *bt = s->lhs->type;
Type *bu = type_chase_named(bt);
Tfield *f = NULL;
@@ -10254,8 +10258,8 @@ cgexpr(Cg *c, Node *n, Local *locals)
}
}
}
if (f) {
int boff = localfind(locals, s->lhs->str);
int boff = f ? localfind(locals, s->lhs->str) : 0;
if (f && !(boff == 0 && let_islet(s->lhs->str))) {
sl_off = boff + (int)f->offset;
} else {
/* fall back to spill — `match (h.e)` where