diff --git a/cmd/w6c/cgen.c b/cmd/w6c/cgen.c index 2cc0bb4e..7c4a243d 100644 --- a/cmd/w6c/cgen.c +++ b/cmd/w6c/cgen.c @@ -11070,7 +11070,13 @@ cgexpr(Cg *c, Node *n, Local *locals) for (Tparam *p = u->params; p; p = p->next, i++) if (i == s_tag) { succ_t = p->type; break; } } - int success_is_str = type_isstr(succ_t); + /* #6 (Mech B): a slice success variant shares str's 24B + * {ptr,len,cap} header in the tagged ABI {DX,CX,R8}; the same + * shuffle serves both. A struct/aggregate success uses a + * different {AX,DX,CX} eightbyte ABI and stays on the bare + * MOVQ DX,AX fallthrough (separate latent, task #12) — keep + * this gate str||slice-specific. */ + int success_is_str = type_isstr(succ_t) || type_isslice(succ_t); char *cont = mklabel(c, "tryprop_ok"); ins2(c, A_CMPQ, aimm(s_tag), areg(D_AX)); ins1(c, A_JE, abranch(cont)); @@ -11206,7 +11212,13 @@ cgexpr(Cg *c, Node *n, Local *locals) for (Tparam *p = u->params; p; p = p->next, i++) if (i == s_tag) { succ_t = p->type; break; } } - int success_is_str = type_isstr(succ_t); + /* #6 (Mech B): a slice success variant shares str's 24B + * {ptr,len,cap} header in the tagged ABI {DX,CX,R8}; the same + * shuffle serves both. A struct/aggregate success uses a + * different {AX,DX,CX} eightbyte ABI and stays on the bare + * MOVQ DX,AX fallthrough (separate latent, task #12) — keep + * this gate str||slice-specific. */ + int success_is_str = type_isstr(succ_t) || type_isslice(succ_t); char *cont = mklabel(c, "tryunw_ok"); ins2(c, A_CMPQ, aimm(s_tag), areg(D_AX)); ins1(c, A_JE, abranch(cont)); diff --git a/selfhost/cmd/wcc/cgenexpr.ww b/selfhost/cmd/wcc/cgenexpr.ww index 6d0b48c2..2218e174 100644 --- a/selfhost/cmd/wcc/cgenexpr.ww +++ b/selfhost/cmd/wcc/cgenexpr.ww @@ -519,9 +519,14 @@ fn cgtryprop(c: *cgen, n: *syntax.node) void = { // shape, any variant order), not a name-keyed call-only lookup of // the first variant. Mirrors cstage cgen.c:10459-10466 // (success_is_str = type_isstr(succ_t at cg_tagged_success_tag)). + // #6 (Mech B): a slice success variant shares str's 24B {ptr,len,cap} + // header in the tagged ABI {DX,CX,R8}; widen the gate so slice rides + // the same shuffle. A struct/aggregate success uses a different + // {AX,DX,CX} ABI (separate latent, task #12) — keep str||slice-specific. let succisstr: bool = false; if (n.lhs != nil) { - succisstr = syntax.typeisstr(successvariant(n.lhs.type_: *syntax.tinfo)); + let sv: *syntax.tinfo = successvariant(n.lhs.type_: *syntax.tinfo); + succisstr = syntax.typeisstr(sv) || syntax.typeisslice(sv); }; if (succisstr) { // str IS []u8: success arrives DX=ptr, CX=len, R8=cap @@ -586,9 +591,13 @@ fn cgtryunw(c: *cgen, n: *syntax.node) void = { // Unwrap success value. (Same shuffle pattern as cgtryprop.) // #16: stamped success-variant type, any source/order (twin of // cgtryprop; cstage cgen.c:10595-10602). + // #6 (Mech B): slice success shares str's 24B header in the tagged + // ABI {DX,CX,R8} — widen the gate (twin of cgtryprop). Struct/aggregate + // success uses a different {AX,DX,CX} ABI (task #12) — str||slice only. let succisstr: bool = false; if (n.lhs != nil) { - succisstr = syntax.typeisstr(successvariant(n.lhs.type_: *syntax.tinfo)); + let sv: *syntax.tinfo = successvariant(n.lhs.type_: *syntax.tinfo); + succisstr = syntax.typeisstr(sv) || syntax.typeisslice(sv); }; if (succisstr) { // str IS []u8: success arrives DX=ptr, CX=len, R8=cap diff --git a/test/lang/unwrap_callarg_slice_test.ww b/test/lang/unwrap_callarg_slice_test.ww new file mode 100644 index 00000000..67416b55 --- /dev/null +++ b/test/lang/unwrap_callarg_slice_test.ww @@ -0,0 +1,61 @@ +// unwrap_callarg_slice_test — runtime contract for a whole 24B slice header +// produced by an UNWRAP source (`mk_sl()!` N_TRYUNW). #6 (Mechanism B, the +// deep root, BOTH stages, byte-id-BLIND): the unwrap success-shuffle +// materialised {ptr,len,cap}→{AX,BX,CX} ONLY when the success variant was +// str; a SLICE success got just `MOVQ DX,AX` (.ptr) — len stayed in CX, cap +// in R8, BX junk. So EVERY slice-unwrap consumer (let-receive store, +// call-arg push, ident-source) read the wrong .len/.cap. Fix-S widened the +// shuffle gate from is_str to type_isstr||type_isslice in both stages +// (cgen.c N_TRYPROP/N_TRYUNW + cgenexpr.ww cgtryprop/cgtryunw); str & slice +// share the IDENTICAL 24B {ptr,len,cap} ABI shuffle. +// +// WHY len!=cap and the poison decoys: the original #6 filing was MASKED by +// (1) len==cap and (2) a call-source where BX coincidentally held len. Here +// mk_sl returns g[0:2] over a backing of cap 5 → len 2, cap 5 (the cap is +// the universal tell — it reads CX=len pre-fix). The identsrc decoy seeds +// the consumer registers with a distinct sentinel so a dropped word reads +// the sentinel, not a coincidence. byte-id is BLIND to this bug (both +// stages were identically wrong) — the value asserts are the SOLE tooth. + +package unwrap_callarg_slice_test; + +type e = !i32; + +let g: [5]i32 = [10i32, 20i32, 30i32, 40i32, 50i32]; + +fn mk_sl() ([]i32 | e) = { return g[0:2]; }; + +fn slclen(s: []i32) i32 = { return s.len: i32; }; + +fn slccap(s: []i32) i32 = { return s.cap: i32; }; + +fn slcsum(s: []i32) i32 = { return s[0] + s[1]; }; + +@test fn slice_unwrap_letrecv() void = { + // let-receive store: MOVQ AX,slot+0 / BX,slot+8 / CX,slot+16. Pre-fix + // .cap reads CX (=len 2) — the load-bearing both-stage tell. + let s: []i32 = mk_sl()!; + assert(s.len: i32 == 2); + assert(s.cap: i32 == 5); + assert(s[0] + s[1] == 30); +}; + +@test fn slice_unwrap_callarg() void = { + // call-arg push of {CX,BX,AX}→{DI,SI,DX}. Decoy ([1i32], cap 1) + // poisons the callee's len/cap registers first. + let decoy: []i32 = [1i32]; + assert(slclen(decoy) == 1); + assert(slclen(mk_sl()!) == 2); + assert(slccap(mk_sl()!) == 5); + assert(slcsum(mk_sl()!) == 30); +}; + +@test fn slice_unwrap_identsrc() void = { + // ident source: cgtryunwcursor loads AX/DX/CX/R8 from the frame slot, + // never BX → BX = decoy junk, so a .len drop has no coincidence to + // hide behind. + let r: ([]i32 | e) = mk_sl(); + let decoy: []i32 = [99i32, 98i32, 97i32]; + assert(slclen(decoy) == 3); + assert(slclen(r!) == 2); +};