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:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user