w6c+wwstage: agree on mixed-scalar tuple sret layout (#240)

An over-cap tuple mixing a scalar with slices/str (e.g. (int,[]u8,str),
56B) laid out differently in the two stages — gate-blind, since no
bootstrap path returns such a tuple. Two silent cs!=ww bugs, one per
ABI side:

  - callee SEND (cstage cgen.c N_RETURN over-cap-tuple arm): foff
    advanced by the LITERAL expression's type size. A bare int literal
    element is stamped TY_UNTYPED_INT (size 0), so `e->type->size`
    added 0 for a leading scalar — the next element clobbered it at
    offset 0 and every trailing element packed 8 bytes low. wwstage
    already sized from the return-type tuple (c.fnret.list), so the
    callee frames diverged. Fix: size foff from cg_ret_type's tuple
    params (rule-13 type table), aligning cstage to wwstage and to the
    t.N reader's f->offset.

  - caller RECEIVE (wwstage cgenstmt.ww cglet N_TTUPLE arm): the
    in-cap register tuple-receive branch had no capacity gate, so a
    56B over-cap tuple was received via AX/DX/CX/R8 (+ R8 fill)
    instead of from the sret dest the callee wrote. cstage gates the
    twin branch on `sz == 16 || sz == 32` and falls over-cap tuples
    through to the sret receive. Fix: add the same size gate to
    wwstage, aligning it to cstage.

Both stages now emit byte-identical asm and the value round-trips.
Regen w6c + wwdump combined.ww (cgenstmt embeds in both).

New 940_mixed_scalar_tuple_sret_run: leading/trailing/middle scalar
shapes, annotated + inferred let, each self-asserting every element
(scalar direct, slice/str via len) — both drivers exit 0 + cs==ww
byte-id (12/12).
This commit is contained in:
2026-06-01 19:01:58 +09:00
parent 6acddc3a82
commit b79f005489
6 changed files with 317 additions and 4 deletions

View File

@@ -27296,6 +27296,14 @@ fn cglet(c: *cgen, n: *node) void = {
// positional slot (eoff steps by the element's slot size: a
// slice/str takes its 24B header). str IS []u8 (24B) → 32B tuple
// (#1/Phase 3, task #5). Mirror of the cstage unified branch.
//
// #240: cap-gate on sz (16/32 = in-cap, AX/DX/CX/R8). Without it
// an OVER-cap mixed-scalar tuple (e.g. (int,[]u8,str), 56B) was
// received here via the register cursor (4 GP regs + R8 fill)
// instead of from the sret dest the callee actually wrote — a
// silent cs!=ww divergence (cstage gates the twin branch on
// `sz == 16 || sz == 32`, cgen.c N_LET, and falls an over-cap
// tuple through to the sret receive below).
if (n.lhs != nil) {
if (n.lhs.kind == nkind.N_TTUPLE) {
let p0: *node = n.lhs.list;
@@ -27311,7 +27319,7 @@ fn cglet(c: *cgen, n: *node) void = {
|| isslicetype(c, p1t);
if (p0 != nil) {
if (p1 != nil) {
if (s0_is_str != s1_is_str) {
if ((s0_is_str != s1_is_str) && (sz == 16 || sz == 32)) {
cgexpr(c, rhs);
let gpcur: i32 = 0;
let ssecur: i32 = 0;

View File

@@ -1084,6 +1084,14 @@ fn cglet(c: *cgen, n: *node) void = {
// positional slot (eoff steps by the element's slot size: a
// slice/str takes its 24B header). str IS []u8 (24B) → 32B tuple
// (#1/Phase 3, task #5). Mirror of the cstage unified branch.
//
// #240: cap-gate on sz (16/32 = in-cap, AX/DX/CX/R8). Without it
// an OVER-cap mixed-scalar tuple (e.g. (int,[]u8,str), 56B) was
// received here via the register cursor (4 GP regs + R8 fill)
// instead of from the sret dest the callee actually wrote — a
// silent cs!=ww divergence (cstage gates the twin branch on
// `sz == 16 || sz == 32`, cgen.c N_LET, and falls an over-cap
// tuple through to the sret receive below).
if (n.lhs != nil) {
if (n.lhs.kind == nkind.N_TTUPLE) {
let p0: *node = n.lhs.list;
@@ -1099,7 +1107,7 @@ fn cglet(c: *cgen, n: *node) void = {
|| isslicetype(c, p1t);
if (p0 != nil) {
if (p1 != nil) {
if (s0_is_str != s1_is_str) {
if ((s0_is_str != s1_is_str) && (sz == 16 || sz == 32)) {
cgexpr(c, rhs);
let gpcur: i32 = 0;
let ssecur: i32 = 0;

View File

@@ -27296,6 +27296,14 @@ fn cglet(c: *cgen, n: *node) void = {
// positional slot (eoff steps by the element's slot size: a
// slice/str takes its 24B header). str IS []u8 (24B) → 32B tuple
// (#1/Phase 3, task #5). Mirror of the cstage unified branch.
//
// #240: cap-gate on sz (16/32 = in-cap, AX/DX/CX/R8). Without it
// an OVER-cap mixed-scalar tuple (e.g. (int,[]u8,str), 56B) was
// received here via the register cursor (4 GP regs + R8 fill)
// instead of from the sret dest the callee actually wrote — a
// silent cs!=ww divergence (cstage gates the twin branch on
// `sz == 16 || sz == 32`, cgen.c N_LET, and falls an over-cap
// tuple through to the sret receive below).
if (n.lhs != nil) {
if (n.lhs.kind == nkind.N_TTUPLE) {
let p0: *node = n.lhs.list;
@@ -27311,7 +27319,7 @@ fn cglet(c: *cgen, n: *node) void = {
|| isslicetype(c, p1t);
if (p0 != nil) {
if (p1 != nil) {
if (s0_is_str != s1_is_str) {
if ((s0_is_str != s1_is_str) && (sz == 16 || sz == 32)) {
cgexpr(c, rhs);
let gpcur: i32 = 0;
let ssecur: i32 = 0;