Receive side of #4's cgreturn ABI (aee8149) for TY_STRUCT lvalues of
size <=24B. Producer materialises rhs into AX=bytes[0..7], DX=[8..15],
CX=[16..23], zero-padded to 24B; receive sites here read the regs and
write only `declared sz` bytes — MOVQ for full 8B chunks plus a sized
tail (MOVL/MOVW/MOVB) by the *declared* struct size. ASYMMETRY: do NOT
mirror the sender's three uniform MOVQs, else trailing 1..7B chunks
overrun the next local slot. Tail chunks in {3,5,6,7} are unreachable
under WW struct align rules (size%align==0) and fall through.
Five sites wired in each stage (cstage cgen.c, wwstage cgenexpr.ww +
cgenstmt.ww), call-result + structlit rhs at each:
- N_LET `let s: T = bar()` / `= T{...}` cgenstmt cglet
- N_ASSIGN N_IDENT-lhs `s = bar()` / `= T{...}` cgenexpr cgassign
- N_ASSIGN single-DOT local-base `o.f = ...`
- N_ASSIGN single-DOT ptr-base auto-deref `p.f = ...`
- N_ASSIGN single-DOT global-base `g.f = ...`
- N_ASSIGN chained-DOT depth>=2 `o.m.in = ...`
(The four dot-flavors share one shape pattern, hence "5 sites".) Where
the dst addr needs scratch (ptr-base/global-base/via_cx), it is loaded
into BX after the call so CX stays as the third value word; for
structlit field-walks BX is reloaded before each store since cgexpr
clobbers AX/BX between fields.
wwstage needed a new `structnaturalsize(si)` helper (cgenutil.ww):
si.totsize is mis-named — it's slot-padded to 8 by registerstruct for
stack-slot use, while the receive ABI wants the type's natural size
(max(foff+fsz)). Splitting si.totsize into naturalsize + slotsize is
tracked as the wwstage struct sizing follow-up (task #15); until that
lands, the helper recovers the natural size at receive sites.
Test 701_cgassign_struct.c (18 rows, 3 checks each — cstage value,
wwstage value, asm byte-identity), wired in Makefile after 698. The
headline ASYMMETRY case is the 20B `{i32×5}` row: sender pads to 24B
via three MOVQs, receiver writes MOVQ AX +0, MOVQ DX +8, MOVL CX +16.
A regression to a MOVQ tail there overruns 4B past the slot and
flips the exit-code check.
smoke.combined.ww is the auto-regen ride-along of strings.freeall
landing in 714d089 (worker-shlex).
Pre-existing gaps surfaced and tracked separately (not fixed here,
out of scope):
- task #16: silent drop of `(*p).f = ...` explicit-deref dot lhs.
- task #17: silent zero of nested STRUCTLIT field in N_LET / N_ASSIGN
initializer — the field_chain and field_global test rows use
explicit field writes (`o.m.t = 10i64;`) rather than nested
literals as a fixture-level workaround.
- task #9: module-name-mangle for fn labels avoided in the
field_global_call fixture by `let g: outer;` (no init).
make test: 59/59. 994_w6c_ww + 995_self_rebuild PASS — bootstrap
byte-identity is the load-bearing proof for this commit's scope.