w6c: fix global-ptr field-store SEGV via decline-to-resolver (#6)

A module-global pointer's field store/compound (`let gp:*S=nil; gp.f += 5`,
`gp.in = Inner{...}`) SEGV'd in cstage: the enumerated N_DOT-lhs arms load
the base pointer with `MOVQ boff(BP),BX`, valid only for a LOCAL ptr slot,
but a module-global ptr has no local slot (localfind=0) so it dereferenced
the saved BP. wwstage was correct -- it routes these through its F6
cgplaceaddr resolver (its dedicated arm is scalar-`=`-only by design,
#60/#61). The byte-id gate was blind (no global-ptr compound in the
bootstrap corpus) and the deferral note was stale: this is a live cs!=ww
divergence with wwstage as the oracle.

cstage already has an equivalent assign-resolver (cgen.c ~7488) that emits
byte-identically to wwstage's F6 route, but the enumerated arms intercepted
the global case first. Fix (align cstage UP, cstage-only): two precondition
entry-guards decline a module-global `*struct` base for the compound +
non-scalar-field cases so they fall through to the resolver. Plain-scalar
`=` stays in the enumerated arm (its #47 fix already matches wwstage). The
decline and resolver accept-sets exactly partition the global-base
N_DOT-lhs space (no gap, no overlap); tagged/float field stores now both
loud-stop symmetrically (were SEGV'ing). The discriminant keys on
localfind-presence + let_islet, so a param at offset 0 stays local.

New both-stage + byte-id test 689_globptr_field_store_run covers offset-0/8,
compound, struct/str field, chained gp.x.y, indexed gp.a[i].f, with local +
offset-0-param controls. The field-READ path is independently broken in
both stages (filed #15). make clean && make test: all 403 passed, byte-id
990-996 green.
This commit is contained in:
2026-06-23 01:12:27 +09:00
parent 30a4920ccf
commit 02967e04ce
3 changed files with 309 additions and 2 deletions

View File

@@ -2406,6 +2406,74 @@ cgplaceaddr(Cg *c, Node *n, int dst_reg, Local *locals)
return 0;
}
/* #6 — a module-GLOBAL `*struct` pointer base (`gp.f = v`, `gp.f += v`)
* carries via_ptr=1 but has NO local slot: localfind returns 0, so the
* enumerated via_ptr field-store arms emit `MOVQ (BP),BX` (deref the
* saved BP = garbage → SEGV). wwstage routes every such case EXCEPT
* plain-scalar-`=` through its F6 cgplaceaddr resolver (the scalar-only
* dedicated arm, selfhost cgenexpr.ww; compound + non-scalar = the
* deferred #60/#61). cstage's own F6 assign-resolver (cgen.c N_ASSIGN,
* via cgplaceaddr) emits byte-identically. So decline those cases at the
* enumerated arm's dispatch — they fall to the resolver, aligning cstage
* UP to wwstage and restoring byte-id (handle where the resolver handles;
* loud-stop, same message, where it loud-stops on tagged/float). The
* plain-scalar-`=` struct-field case STAYS in the enumerated arm — its
* #47 fix already matches wwstage's dedicated arm. Discriminant is
* localfind-ABSENCE + let_islet (storage class), NEVER a boff==0
* sentinel: a param at offset 0 has localfind != 0 and stays local. */
static int
global_ptr_field_decline(Node *lhs, int op, Local *locals)
{
if (lhs == NULL || lhs->kind != N_DOT || lhs->lhs == NULL)
return 0;
Node *base = lhs->lhs;
if (base->kind == N_UN && base->op == TK_STAR) base = base->lhs;
if (base == NULL || base->kind != N_IDENT) return 0;
if (localfind(locals, base->str) != 0 || !let_islet(base->str))
return 0;
Type *bu = type_chase_named(base->type);
if (bu == NULL || bu->kind != TY_PTR) return 0;
Type *su = type_chase_named(bu->sub);
if (su == NULL) return 0;
/* `(*gp).ptr/.len/.cap` pseudo-field: no struct field, always
* resolver (wwstage loud-stops "unsupported assign target shape"). */
if (su->kind == TY_SLICE || su->kind == TY_STR) return 1;
if (su->kind != TY_STRUCT) return 0;
if (op != TK_ASSIGN) return 1;
for (Tfield *fl = su->fields; fl; fl = fl->next)
if (strcmp(fl->name, lhs->str) == 0) {
Type *fu = type_chase_named(fl->type);
int isf32 = 0;
if (fld_isfloat(fl->type, &isf32)) return 1;
if (fu && (fu->kind == TY_STRUCT
|| fu->kind == TY_ARRAY || fu->kind == TY_TUPLE
|| fu->kind == TY_STR || fu->kind == TY_SLICE
|| fu->kind == TY_TAGGED))
return 1;
return 0;
}
return 0;
}
/* #6 (chained twin) — a multi-dot LHS (`gp.x.y = v`) rooted at a module-
* GLOBAL pointer the walk dereferences: the chained value-struct walker
* (cgen.c N_ASSIGN) conflates is_global with ptr_root and emits the same
* `MOVQ (BP),CX` SEGV for plain `=`. Decline at entry → the F6 resolver
* (byte-id with wwstage). A global VALUE-struct root (`gv.x.y`, root type
* TY_STRUCT) and a LOCAL pointer root (localfind != 0) both stay in the
* walker — they are already correct. */
static int
global_ptr_chain_root_decline(Node *lhs, Local *locals)
{
Node *cur = lhs;
while (cur && cur->kind == N_DOT) cur = cur->lhs;
if (cur == NULL || cur->kind != N_IDENT) return 0;
if (localfind(locals, cur->str) != 0 || !let_islet(cur->str))
return 0;
Type *u = type_chase_named(cur->type);
return (u != NULL && u->kind == TY_PTR);
}
/* FA1 (#15): append() header-place access, cgplaceaddr's append
* consumer. direct = ident-local header in the frame (BP-disp — the
* legacy emission, kept byte-identical); indirect = header address
@@ -5078,7 +5146,8 @@ cgexpr(Cg *c, Node *n, Local *locals)
* `p.f = v`. v1 scope: bare-IDENT inner only; (*expr).f
* (non-IDENT inner) falls through to the existing drop
* behaviour pending follow-up task. */
if (n->lhs && n->lhs->kind == N_DOT && n->lhs->lhs &&
if (!global_ptr_field_decline(n->lhs, n->op, locals)
&& n->lhs && n->lhs->kind == N_DOT && n->lhs->lhs &&
(n->lhs->lhs->kind == N_IDENT ||
(n->lhs->lhs->kind == N_UN && n->lhs->lhs->op == TK_STAR
&& n->lhs->lhs->lhs
@@ -6080,7 +6149,8 @@ cgexpr(Cg *c, Node *n, Local *locals)
* (the existing 1-deep branch only fires for `ident.field = …`).
* Only plain `=` is wired — compound on a chained value-struct
* field is rare and stays unhandled. */
if (n->lhs && n->lhs->kind == N_DOT && n->lhs->lhs
if (!global_ptr_chain_root_decline(n->lhs, locals)
&& n->lhs && n->lhs->kind == N_DOT && n->lhs->lhs
&& n->lhs->lhs->kind == N_DOT && n->op == TK_ASSIGN) {
struct { Type *pu; const char *name; } steps[16];
int nsteps = 0;