cgen: str a,s=call() N_MASSIGN tuple-elem store -> 3-word -- Phase 2 G3 (both stages)

Reassign-destructuring a (scalar,str) tuple (a, s = call(), N_MASSIGN) stored only the str's ptr (DX->slot+0), dropping len/cap -- the last STORE-cluster gap. Reachable (valid ww; checker accepts str tuple elements) but unexercised in bootstrap (all N_MASSIGN sites returned <=8B tuples). Mirror the N_MLET destructure-store oracle (cgen.c:7475): on the one-str XOR, route the str's 3 words DX/CX/R8 -> slot+0/+8/+16; the slot pre-exists (localfind, not localadd). wwstage has no checker, so it derives str-ness from the callee return-type tuple via fnretlookupmod (structurally identical to cgmlet). Both XOR positions (str at l0 and l1). Kind-gated, never size==24. cstage==wwstage byte-identical.

Scope = one-str only, matching N_MLET exactly; str+str-both is unhandled by N_MLET too and is filed as a shared gap (task #22), with WHY-comments at both destructure sites. N_MLET emission unchanged (its edit is comment-only, verified byte-identical).

test/wcc/939: table-driven write-then-read-cap over both XOR positions (a,s=mk() and s,a=mk2()); cap!=len via mutation (not a sub-slice, #20); pre-poisoned via a non-G3 let-init; full triple+scalar asserted; fail-before/pass-after on both drivers. Completes the str-cap STORE cluster -- the read/write round-trip is now whole. main.combined.ww regenerated via the canonical make path.
This commit is contained in:
2026-05-24 16:54:14 +09:00
parent b51a7daa25
commit 80527f3868
6 changed files with 559 additions and 8 deletions

View File

@@ -21049,11 +21049,111 @@ fn cgfor(c: *cgen, n: *node) void = {
// cmd/w6c/cgen.c:2424-2440. Lvalues beyond two are dropped (same
// as C — no fixture uses >2 today).
fn cgmassign(c: *cgen, n: *node) void = {
if (n.rhs != nil) { cgexpr(c, n.rhs); };
emitline("\tPUSHQ\tDX\n");
let l0: *node = n.list;
let l1: *node = nil;
if (l0 != nil) { l1 = l0.next; };
// one-str only; two-str destructure is gap (task #22). The str
// tuple element's 24B slot already exists (reassignment), so
// localfind it and mirror cgmlet's (DX,CX,R8)->(.ptr,.len,.cap)
// routing; the bare scalar fallback below would store only DX and
// drop len/cap. wwstage has no checker, so str-ness comes from the
// called fn's return-type tuple element (as in cgmlet).
let p0t: *node = nil;
let p1t: *node = nil;
let rhs: *node = n.rhs;
if (rhs != nil) {
if (rhs.kind == nkind.N_CALL) {
let callee: *node = rhs.lhs;
if (callee != nil) {
let cnm: str;
cnm.ptr = nil; cnm.len = 0;
let cmod: str;
cmod.ptr = nil; cmod.len = 0;
if (callee.kind == nkind.N_IDENT) {
cnm = callee.str;
cmod = c.curmod;
};
if (callee.kind == nkind.N_DOT) {
cnm = callee.str;
if (callee.lhs != nil) {
if (callee.lhs.kind == nkind.N_IDENT) {
cmod = callee.lhs.str;
};
};
};
if (cnm.len > 0) {
let rtyp: *node = fnretlookupmod(c, cnm, cmod);
if (rtyp != nil) {
if (rtyp.kind == nkind.N_TTUPLE) {
let pp: *node = rtyp.list;
if (pp != nil) {
p0t = pp.lhs;
if (pp.next != nil) {
p1t = pp.next.lhs;
};
};
};
};
};
};
};
};
let s0_is_str: bool = isstrtype(c, p0t);
let s1_is_str: bool = isstrtype(c, p1t);
if (n.rhs != nil) { cgexpr(c, n.rhs); };
if (l0 != nil) {
if (l1 != nil) {
if (l0.kind == nkind.N_IDENT) {
if (l1.kind == nkind.N_IDENT) {
if (s0_is_str != s1_is_str) {
let off0: i32 = localfind(c, l0.str);
let off1: i32 = localfind(c, l1.str);
if (off0 != 0) {
if (off1 != 0) {
if (s0_is_str) {
// l0 str: ptr=DX, len=CX, cap=R8. l1 scalar = AX.
emitline("\tMOVQ\tDX, ");
emitoff(off0: i64);
emitline("(BP)\n");
emitline("\tMOVQ\tCX, ");
emitoff((off0 + 8): i64);
emitline("(BP)\n");
emitline("\tMOVQ\tR8, ");
emitoff((off0 + 16): i64);
emitline("(BP)\n");
emitline("\tMOVQ\tAX, ");
emitoff(off1: i64);
emitline("(BP)\n");
} else {
// l0 scalar; l1 str: ptr=DX, len=CX, cap=R8.
emitline("\tMOVQ\tAX, ");
emitoff(off0: i64);
emitline("(BP)\n");
emitline("\tMOVQ\tDX, ");
emitoff(off1: i64);
emitline("(BP)\n");
emitline("\tMOVQ\tCX, ");
emitoff((off1 + 8): i64);
emitline("(BP)\n");
emitline("\tMOVQ\tR8, ");
emitoff((off1 + 16): i64);
emitline("(BP)\n");
};
c.lastwasreturn = 0;
return;
};
};
};
};
};
};
};
emitline("\tPUSHQ\tDX\n");
if (l0 != nil) {
if (l0.kind == nkind.N_IDENT) {
let off: i32 = localfind(c, l0.str);

View File

@@ -1197,11 +1197,111 @@ fn cgfor(c: *cgen, n: *node) void = {
// cmd/w6c/cgen.c:2424-2440. Lvalues beyond two are dropped (same
// as C — no fixture uses >2 today).
fn cgmassign(c: *cgen, n: *node) void = {
if (n.rhs != nil) { cgexpr(c, n.rhs); };
emitline("\tPUSHQ\tDX\n");
let l0: *node = n.list;
let l1: *node = nil;
if (l0 != nil) { l1 = l0.next; };
// one-str only; two-str destructure is gap (task #22). The str
// tuple element's 24B slot already exists (reassignment), so
// localfind it and mirror cgmlet's (DX,CX,R8)->(.ptr,.len,.cap)
// routing; the bare scalar fallback below would store only DX and
// drop len/cap. wwstage has no checker, so str-ness comes from the
// called fn's return-type tuple element (as in cgmlet).
let p0t: *node = nil;
let p1t: *node = nil;
let rhs: *node = n.rhs;
if (rhs != nil) {
if (rhs.kind == nkind.N_CALL) {
let callee: *node = rhs.lhs;
if (callee != nil) {
let cnm: str;
cnm.ptr = nil; cnm.len = 0;
let cmod: str;
cmod.ptr = nil; cmod.len = 0;
if (callee.kind == nkind.N_IDENT) {
cnm = callee.str;
cmod = c.curmod;
};
if (callee.kind == nkind.N_DOT) {
cnm = callee.str;
if (callee.lhs != nil) {
if (callee.lhs.kind == nkind.N_IDENT) {
cmod = callee.lhs.str;
};
};
};
if (cnm.len > 0) {
let rtyp: *node = fnretlookupmod(c, cnm, cmod);
if (rtyp != nil) {
if (rtyp.kind == nkind.N_TTUPLE) {
let pp: *node = rtyp.list;
if (pp != nil) {
p0t = pp.lhs;
if (pp.next != nil) {
p1t = pp.next.lhs;
};
};
};
};
};
};
};
};
let s0_is_str: bool = isstrtype(c, p0t);
let s1_is_str: bool = isstrtype(c, p1t);
if (n.rhs != nil) { cgexpr(c, n.rhs); };
if (l0 != nil) {
if (l1 != nil) {
if (l0.kind == nkind.N_IDENT) {
if (l1.kind == nkind.N_IDENT) {
if (s0_is_str != s1_is_str) {
let off0: i32 = localfind(c, l0.str);
let off1: i32 = localfind(c, l1.str);
if (off0 != 0) {
if (off1 != 0) {
if (s0_is_str) {
// l0 str: ptr=DX, len=CX, cap=R8. l1 scalar = AX.
emitline("\tMOVQ\tDX, ");
emitoff(off0: i64);
emitline("(BP)\n");
emitline("\tMOVQ\tCX, ");
emitoff((off0 + 8): i64);
emitline("(BP)\n");
emitline("\tMOVQ\tR8, ");
emitoff((off0 + 16): i64);
emitline("(BP)\n");
emitline("\tMOVQ\tAX, ");
emitoff(off1: i64);
emitline("(BP)\n");
} else {
// l0 scalar; l1 str: ptr=DX, len=CX, cap=R8.
emitline("\tMOVQ\tAX, ");
emitoff(off0: i64);
emitline("(BP)\n");
emitline("\tMOVQ\tDX, ");
emitoff(off1: i64);
emitline("(BP)\n");
emitline("\tMOVQ\tCX, ");
emitoff((off1 + 8): i64);
emitline("(BP)\n");
emitline("\tMOVQ\tR8, ");
emitoff((off1 + 16): i64);
emitline("(BP)\n");
};
c.lastwasreturn = 0;
return;
};
};
};
};
};
};
};
emitline("\tPUSHQ\tDX\n");
if (l0 != nil) {
if (l0.kind == nkind.N_IDENT) {
let off: i32 = localfind(c, l0.str);

View File

@@ -21049,11 +21049,111 @@ fn cgfor(c: *cgen, n: *node) void = {
// cmd/w6c/cgen.c:2424-2440. Lvalues beyond two are dropped (same
// as C — no fixture uses >2 today).
fn cgmassign(c: *cgen, n: *node) void = {
if (n.rhs != nil) { cgexpr(c, n.rhs); };
emitline("\tPUSHQ\tDX\n");
let l0: *node = n.list;
let l1: *node = nil;
if (l0 != nil) { l1 = l0.next; };
// one-str only; two-str destructure is gap (task #22). The str
// tuple element's 24B slot already exists (reassignment), so
// localfind it and mirror cgmlet's (DX,CX,R8)->(.ptr,.len,.cap)
// routing; the bare scalar fallback below would store only DX and
// drop len/cap. wwstage has no checker, so str-ness comes from the
// called fn's return-type tuple element (as in cgmlet).
let p0t: *node = nil;
let p1t: *node = nil;
let rhs: *node = n.rhs;
if (rhs != nil) {
if (rhs.kind == nkind.N_CALL) {
let callee: *node = rhs.lhs;
if (callee != nil) {
let cnm: str;
cnm.ptr = nil; cnm.len = 0;
let cmod: str;
cmod.ptr = nil; cmod.len = 0;
if (callee.kind == nkind.N_IDENT) {
cnm = callee.str;
cmod = c.curmod;
};
if (callee.kind == nkind.N_DOT) {
cnm = callee.str;
if (callee.lhs != nil) {
if (callee.lhs.kind == nkind.N_IDENT) {
cmod = callee.lhs.str;
};
};
};
if (cnm.len > 0) {
let rtyp: *node = fnretlookupmod(c, cnm, cmod);
if (rtyp != nil) {
if (rtyp.kind == nkind.N_TTUPLE) {
let pp: *node = rtyp.list;
if (pp != nil) {
p0t = pp.lhs;
if (pp.next != nil) {
p1t = pp.next.lhs;
};
};
};
};
};
};
};
};
let s0_is_str: bool = isstrtype(c, p0t);
let s1_is_str: bool = isstrtype(c, p1t);
if (n.rhs != nil) { cgexpr(c, n.rhs); };
if (l0 != nil) {
if (l1 != nil) {
if (l0.kind == nkind.N_IDENT) {
if (l1.kind == nkind.N_IDENT) {
if (s0_is_str != s1_is_str) {
let off0: i32 = localfind(c, l0.str);
let off1: i32 = localfind(c, l1.str);
if (off0 != 0) {
if (off1 != 0) {
if (s0_is_str) {
// l0 str: ptr=DX, len=CX, cap=R8. l1 scalar = AX.
emitline("\tMOVQ\tDX, ");
emitoff(off0: i64);
emitline("(BP)\n");
emitline("\tMOVQ\tCX, ");
emitoff((off0 + 8): i64);
emitline("(BP)\n");
emitline("\tMOVQ\tR8, ");
emitoff((off0 + 16): i64);
emitline("(BP)\n");
emitline("\tMOVQ\tAX, ");
emitoff(off1: i64);
emitline("(BP)\n");
} else {
// l0 scalar; l1 str: ptr=DX, len=CX, cap=R8.
emitline("\tMOVQ\tAX, ");
emitoff(off0: i64);
emitline("(BP)\n");
emitline("\tMOVQ\tDX, ");
emitoff(off1: i64);
emitline("(BP)\n");
emitline("\tMOVQ\tCX, ");
emitoff((off1 + 8): i64);
emitline("(BP)\n");
emitline("\tMOVQ\tR8, ");
emitoff((off1 + 16): i64);
emitline("(BP)\n");
};
c.lastwasreturn = 0;
return;
};
};
};
};
};
};
};
emitline("\tPUSHQ\tDX\n");
if (l0 != nil) {
if (l0.kind == nkind.N_IDENT) {
let off: i32 = localfind(c, l0.str);