w6c+wwstage: struct-array-field slice as call-arg via dotbaseaddr (#257)

An inline slice of a struct `[N]T`-field passed DIRECTLY as a call
argument (`rd(x.o[lo:hi])`) materialized the slice .ptr from the field
VALUE, not its ADDRESS: the pushargs/pushargsrev N_SLICE inline builder's
non-ident else-arm did plain cgexpr(base), so the N_DOT field auto-derefs
(MOVL field,AX used as .ptr) -> callee derefs garbage -> SEGFAULT. The
let-init / assign-rhs / return / hoist-to-local contexts already routed
through the cgslice #252 choke-point; only this call-arg builder kept a
private duplicate. cs==ww both segfaulted identically pre-fix (gate-blind).

Fix (symmetric both stages):
  - route the else-arm through cg_dotbase_addr / dotbaseaddr (the cgslice
    #252 choke-point: array-field-gated, so `[]T`/str/`*T` fields fall
    through to cgexpr; chained inner `o.p.m` handled via its #253 arm);
  - extend the N_IDENT-only esz gate to N_DOT bases, taking the element
    width from the checker-stamped base->type (rule-13 type table), so
    non-u8 call-arg slices scale stride.

Before: `MOVL -8(BP),AX; PUSHQ AX` (field value as .ptr). After:
`LEAQ -8(BP),AX; PUSHQ AX` (field address). cs==ww byte-identical.

Helper note: used dotbaseaddr (not dotchainaddr as first scoped) — it is
the established cgslice choke-point and is array-field-gated, so a slice/
str-typed field base keeps the correct cgexpr header-ptr load; bare
dotchainaddr lacks that gate and would mis-emit the field address for
those. dotbaseaddr already handles the chained `o.p.m` inner via #253.

Tests: test/wcc/949 gains 6 call-arg rows (u8, i32-esz-stride, via-*struct,
chained, + hoist-to-local and bare-local-array controls), each run-
correctness AND cs==ww byte-id.

PROOF-GREP residual: the tagged-union-element indexed-STORE arm
(cgen.c:~4972 / cgenexpr.ww:~5024) is the same N_DOT-base auto-deref shape,
still unrouted in BOTH stages (symmetric, segfaults) — a distinct
consumption axis filed separately; NOT fixed here.
This commit is contained in:
2026-06-02 04:38:41 +09:00
parent d8aaa54b41
commit 0f2587d294
5 changed files with 164 additions and 20 deletions

View File

@@ -6167,11 +6167,14 @@ cgexpr(Cg *c, Node *n, Local *locals)
Type *bt = base ? base->type : NULL;
Type *bu = (bt && bt->kind == TY_NAMED) ?
bt->under : bt;
/* N_IDENT-gated: non-ident bases stay esz=1
* (unscaled), byte-id with wwstage which has no
* tnode there (rule 10) -- #76 residual, non-
* ident cluster #74. */
int esz = (base && base->kind == N_IDENT
/* esz from the type table for an N_IDENT base
* (#76) or an N_DOT array/slice-field base
* (#257: scale by the field's element width via
* the checker-stamped base->type, not esz=1 --
* silently wrong for non-u8). Other non-ident
* bases stay esz=1 (unscaled). */
int esz = (base && (base->kind == N_IDENT
|| base->kind == N_DOT)
&& bu && bu->sub)
? (int)bu->sub->size : 1;
/* base addr → push */
@@ -6192,6 +6195,13 @@ cgexpr(Cg *c, Node *n, Local *locals)
} else {
ins2(c, A_MOVQ, amem(D_BP, boff), areg(D_AX));
}
} else if (cg_dotbase_addr(c, base, D_AX, locals)) {
/* #257: N_DOT `[N]T`-field base as a call
* arg → field ADDRESS (LEAQ), not the
* auto-deref VALUE load cgexpr emits. Same
* choke-point as the cgslice #252 site;
* `[]T`/str/`*T` fields fall through to
* cgexpr (correct header/ptr load). */
} else {
cgexpr(c, base, locals);
}