cgen: build the full slice header in the unwrap success shuffle (#6 Mech B)
The N_TRYUNW/N_TRYPROP success shuffle materialized {ptr,len,cap} into {AX,BX,CX} only when the success variant was a str; a slice success got only MOVQ DX,AX (ptr), leaving every slice-unwrap consumer (call-arg push, let-receive store, ident-source) reading junk .len/.cap — silent on BOTH stages (byte-id blind, cstage not the oracle). Widen the success gate to type_isstr||type_isslice (cstage) / typeisstr||typeisslice (wwstage) at all four shuffle sites; str and slice share the identical 24B header shuffle. Stays str||slice-specific — a struct success variant uses a different {AX,DX,CX} ABI (task #12). Value-asserting pin (len!=cap, poison-decoy) reddens under each stage's independent revert.
Follows #6 Mech A (Fix-R); order forced (C1 first or the slice call-arg push reddens byte-id).
This commit is contained in:
@@ -11070,7 +11070,13 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
for (Tparam *p = u->params; p; p = p->next, i++)
|
for (Tparam *p = u->params; p; p = p->next, i++)
|
||||||
if (i == s_tag) { succ_t = p->type; break; }
|
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");
|
char *cont = mklabel(c, "tryprop_ok");
|
||||||
ins2(c, A_CMPQ, aimm(s_tag), areg(D_AX));
|
ins2(c, A_CMPQ, aimm(s_tag), areg(D_AX));
|
||||||
ins1(c, A_JE, abranch(cont));
|
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++)
|
for (Tparam *p = u->params; p; p = p->next, i++)
|
||||||
if (i == s_tag) { succ_t = p->type; break; }
|
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");
|
char *cont = mklabel(c, "tryunw_ok");
|
||||||
ins2(c, A_CMPQ, aimm(s_tag), areg(D_AX));
|
ins2(c, A_CMPQ, aimm(s_tag), areg(D_AX));
|
||||||
ins1(c, A_JE, abranch(cont));
|
ins1(c, A_JE, abranch(cont));
|
||||||
|
|||||||
@@ -519,9 +519,14 @@ fn cgtryprop(c: *cgen, n: *syntax.node) void = {
|
|||||||
// shape, any variant order), not a name-keyed call-only lookup of
|
// shape, any variant order), not a name-keyed call-only lookup of
|
||||||
// the first variant. Mirrors cstage cgen.c:10459-10466
|
// the first variant. Mirrors cstage cgen.c:10459-10466
|
||||||
// (success_is_str = type_isstr(succ_t at cg_tagged_success_tag)).
|
// (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;
|
let succisstr: bool = false;
|
||||||
if (n.lhs != nil) {
|
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) {
|
if (succisstr) {
|
||||||
// str IS []u8: success arrives DX=ptr, CX=len, R8=cap
|
// 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.)
|
// Unwrap success value. (Same shuffle pattern as cgtryprop.)
|
||||||
// #16: stamped success-variant type, any source/order (twin of
|
// #16: stamped success-variant type, any source/order (twin of
|
||||||
// cgtryprop; cstage cgen.c:10595-10602).
|
// 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;
|
let succisstr: bool = false;
|
||||||
if (n.lhs != nil) {
|
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) {
|
if (succisstr) {
|
||||||
// str IS []u8: success arrives DX=ptr, CX=len, R8=cap
|
// str IS []u8: success arrives DX=ptr, CX=len, R8=cap
|
||||||
|
|||||||
61
test/lang/unwrap_callarg_slice_test.ww
Normal file
61
test/lang/unwrap_callarg_slice_test.ww
Normal file
@@ -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);
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user