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

@@ -15624,15 +15624,30 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = {
};
};
};
// #257: an N_DOT `[N]T`-field base (`x.o[lo:hi]` as a call
// arg) carries no tnode — resolve esz / base-address from the
// checker-stamped element tinfo on base.type_ instead. Cstage
// twin reads base->type (cgen.c pushargs N_SLICE esz). Mirror
// of the cgslice #252 site.
let dotbu: *tinfo = nil;
if (base != nil) { if (base.kind == nkind.N_DOT) {
dotbu = base.type_: *tinfo;
for (dotbu != nil && dotbu.kind == tykind.TY_NAMED) {
dotbu = dotbu.under;
};
};};
// esz from the type table for an N_IDENT base (#76; mirrors
// the cgindex idiom). Non-ident base stays esz=1 -> ptr
// unscaled, matching cstage's base->kind==N_IDENT gate.
// the cgindex idiom) or an N_DOT array/slice-field base (#257:
// scale by the field's element width, not esz=1 -> silently
// wrong for non-u8). Other non-ident bases stay esz=1.
let esz: i32 = 1;
if (baselocal != nil) {
esz = elemsizeofc(c, baselocal.tnode);
} else { if (globaltn != nil) {
esz = elemsizeofc(c, globaltn);
};};
} else { if (dotbu != nil && dotbu.sub != nil) {
esz = dotbu.sub.size: i32;
};};};
// base address → push
if (baselocal != nil) {
let tn: *node = baselocal.tnode;
@@ -15661,9 +15676,14 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = {
emitsymname(c, globalname);
emitline("(SB), AX\n");
};
} else { if (dotbaseaddr(c, base, "AX")) {
// #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.
} else {
cgexpr(c, base);
};};
};};};
emitline("\tPUSHQ\tAX\n");
// hi (default base length) → push
if (hi != nil) {

View File

@@ -274,15 +274,30 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = {
};
};
};
// #257: an N_DOT `[N]T`-field base (`x.o[lo:hi]` as a call
// arg) carries no tnode — resolve esz / base-address from the
// checker-stamped element tinfo on base.type_ instead. Cstage
// twin reads base->type (cgen.c pushargs N_SLICE esz). Mirror
// of the cgslice #252 site.
let dotbu: *tinfo = nil;
if (base != nil) { if (base.kind == nkind.N_DOT) {
dotbu = base.type_: *tinfo;
for (dotbu != nil && dotbu.kind == tykind.TY_NAMED) {
dotbu = dotbu.under;
};
};};
// esz from the type table for an N_IDENT base (#76; mirrors
// the cgindex idiom). Non-ident base stays esz=1 -> ptr
// unscaled, matching cstage's base->kind==N_IDENT gate.
// the cgindex idiom) or an N_DOT array/slice-field base (#257:
// scale by the field's element width, not esz=1 -> silently
// wrong for non-u8). Other non-ident bases stay esz=1.
let esz: i32 = 1;
if (baselocal != nil) {
esz = elemsizeofc(c, baselocal.tnode);
} else { if (globaltn != nil) {
esz = elemsizeofc(c, globaltn);
};};
} else { if (dotbu != nil && dotbu.sub != nil) {
esz = dotbu.sub.size: i32;
};};};
// base address → push
if (baselocal != nil) {
let tn: *node = baselocal.tnode;
@@ -311,9 +326,14 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = {
emitsymname(c, globalname);
emitline("(SB), AX\n");
};
} else { if (dotbaseaddr(c, base, "AX")) {
// #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.
} else {
cgexpr(c, base);
};};
};};};
emitline("\tPUSHQ\tAX\n");
// hi (default base length) → push
if (hi != nil) {

View File

@@ -15624,15 +15624,30 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = {
};
};
};
// #257: an N_DOT `[N]T`-field base (`x.o[lo:hi]` as a call
// arg) carries no tnode — resolve esz / base-address from the
// checker-stamped element tinfo on base.type_ instead. Cstage
// twin reads base->type (cgen.c pushargs N_SLICE esz). Mirror
// of the cgslice #252 site.
let dotbu: *tinfo = nil;
if (base != nil) { if (base.kind == nkind.N_DOT) {
dotbu = base.type_: *tinfo;
for (dotbu != nil && dotbu.kind == tykind.TY_NAMED) {
dotbu = dotbu.under;
};
};};
// esz from the type table for an N_IDENT base (#76; mirrors
// the cgindex idiom). Non-ident base stays esz=1 -> ptr
// unscaled, matching cstage's base->kind==N_IDENT gate.
// the cgindex idiom) or an N_DOT array/slice-field base (#257:
// scale by the field's element width, not esz=1 -> silently
// wrong for non-u8). Other non-ident bases stay esz=1.
let esz: i32 = 1;
if (baselocal != nil) {
esz = elemsizeofc(c, baselocal.tnode);
} else { if (globaltn != nil) {
esz = elemsizeofc(c, globaltn);
};};
} else { if (dotbu != nil && dotbu.sub != nil) {
esz = dotbu.sub.size: i32;
};};};
// base address → push
if (baselocal != nil) {
let tn: *node = baselocal.tnode;
@@ -15661,9 +15676,14 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = {
emitsymname(c, globalname);
emitline("(SB), AX\n");
};
} else { if (dotbaseaddr(c, base, "AX")) {
// #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.
} else {
cgexpr(c, base);
};};
};};};
emitline("\tPUSHQ\tAX\n");
// hi (default base length) → push
if (hi != nil) {