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

@@ -1,8 +1,10 @@
/*
* 949_dotbase_addr_slice_run — runtime + byte-id net for the array-
* field-base-address family: #252 (the addr-of + slice SIBLING of #135)
* and #253 (the CHAINED-base close-out). 949_dotbase_arr_run covers the
* single-level read/write index path.
* field-base-address family: #252 (the addr-of + slice SIBLING of #135),
* #253 (the CHAINED-base close-out), and #257 (the CALL-ARG consumption
* axis — an inline struct-array-field slice passed straight as a call
* argument). 949_dotbase_arr_run covers the single-level read/write
* index path.
*
* #252: taking `&x.o[i]` (address-of an element) or slicing
* `x.o[lo:hi]` / `x.o[lo:]` of a struct's `[N]T`-typed FIELD computed
@@ -377,6 +379,78 @@ static const struct row rows[] = {
" let o: top; o.a.b.m[1] = 66u8;\n"
" return o.a.b.m[1]: i32;\n"
"};\n", 66, 0 },
/* #257 call-arg consumption axis. An INLINE slice of a struct
* `[N]T`-field passed DIRECTLY as a call argument materialized the
* slice .ptr from the field VALUE, not its ADDRESS: the pushargs
* N_SLICE inline builder's non-ident else-arm did plain cgexpr(base)
* -> 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. Fix routes the else-arm through cg_dotbase_addr /
* dotbaseaddr (array-field-gated; chained inner via #253) + extends
* the N_IDENT-only esz gate to N_DOT bases (element width from the
* checker-stamped base->type). cs==ww both segfaulted identically
* pre-fix (gate-blind). */
{ "callarg_u8",
"package main;\n"
"type e = struct { o: [4]u8 };\n"
"fn rd(b: []u8) i32 = { return b[0]: i32; };\n"
"export fn main() i32 = {\n"
" let x: e;\n"
" x.o[1] = 66u8;\n"
" return rd(x.o[1:4]);\n"
"};\n", 66, 1 },
{ "callarg_i32",
"package main;\n"
"type e = struct { o: [4]i32 };\n"
"fn rd(b: []i32) i32 = { return b[0]; };\n"
"export fn main() i32 = {\n"
" let x: e;\n"
" x.o[1] = 88;\n"
" return rd(x.o[1:3]);\n"
"};\n", 88, 1 },
{ "callarg_ptr_u8",
"package main;\n"
"type e = struct { o: [4]u8 };\n"
"fn rd(b: []u8) i32 = { return b[0]: i32; };\n"
"fn f(p: *e) i32 = { return rd(p.o[1:4]); };\n"
"export fn main() i32 = {\n"
" let x: e;\n"
" x.o[1] = 66u8;\n"
" return f(&x);\n"
"};\n", 66, 1 },
{ "callarg_chain",
"package main;\n"
"type inner = struct { m: [4]u8 };\n"
"type outer = struct { p: *inner };\n"
"fn rd(b: []u8) i32 = { return b[0]: i32; };\n"
"export fn main() i32 = {\n"
" let a: inner; a.m[1] = 66u8;\n"
" let o: outer; o.p = &a;\n"
" return rd(o.p.m[1:4]);\n"
"};\n", 66, 1 },
/* Controls: an N_IDENT slice arg (hoist-to-local) and a bare-local-
* array slice arg take the N_IDENT fast-paths, NOT the N_DOT else-arm
* — assert they still emit correct code. */
{ "callarg_ctrl_local",
"package main;\n"
"type e = struct { o: [4]u8 };\n"
"fn rd(b: []u8) i32 = { return b[0]: i32; };\n"
"export fn main() i32 = {\n"
" let x: e;\n"
" x.o[1] = 66u8;\n"
" let sl: []u8 = x.o[1:4];\n"
" return rd(sl);\n"
"};\n", 66, 1 },
{ "callarg_ctrl_arr",
"package main;\n"
"fn rd(b: []u8) i32 = { return b[0]: i32; };\n"
"export fn main() i32 = {\n"
" let a: [4]u8;\n"
" a[1] = 66u8;\n"
" return rd(a[1:4]);\n"
"};\n", 66, 1 },
{ NULL, NULL, 0, 0 }
};