wwstage: recognize an unwrap success in the slice/str call-arg recognizers (#6 Mech A)
nodeisslice/nodeisstr lacked an N_TRYUNW/N_TRYPROP arm, so a str/slice produced by an unwrap (f()!, r!, r?) and passed as a call arg fell to the 1-word scalar push, dropping .len/.cap; cstage's type-keyed node_isslice/node_isstr already pushed 3 words. Add the type-keyed arm reading the checker-stamped success-variant n.type_, mirroring #9's N_UN/TK_STAR arm. Fixes the str case (wwstage align-up to cstage); the slice success shuffle that both stages still get wrong is fixed in the Mech B follow-up.
This commit is contained in:
@@ -1383,6 +1383,16 @@ fn nodeisslice(c: *cgen, n: *syntax.node) bool = {
|
||||
if (k == syntax.nkind.N_UN && n.op == syntax.tkind.TK_STAR) {
|
||||
return syntax.typeisslice(n.type_: *syntax.tinfo);
|
||||
};
|
||||
// #6 (Mech A): an unwrap source (`f()!` N_TRYUNW, `r!`/`r?` N_TRYPROP)
|
||||
// whose success variant is a slice. Without this arm the unwrap fell
|
||||
// through to the scalar single-PUSHQ default, marshalling .ptr and
|
||||
// dropping .len/.cap (cgcall's pop sizer under-drains). n.type_ is
|
||||
// checker-stamped to the success variant (check.ww N_TRYPROP/N_TRYUNW),
|
||||
// mirroring cstage node_isslice = type_isslice(n->type). Twin of the
|
||||
// N_UN(TK_STAR) #9 arm above.
|
||||
if (k == syntax.nkind.N_TRYUNW || k == syntax.nkind.N_TRYPROP) {
|
||||
return syntax.typeisslice(n.type_: *syntax.tinfo);
|
||||
};
|
||||
return false;
|
||||
};
|
||||
|
||||
@@ -1486,6 +1496,13 @@ fn nodeisstr(c: *cgen, n: *syntax.node) bool = {
|
||||
if (k == syntax.nkind.N_UN && n.op == syntax.tkind.TK_STAR) {
|
||||
return syntax.typeisstr(n.type_: *syntax.tinfo);
|
||||
};
|
||||
// #6 (Mech A): str twin of the N_TRYUNW/N_TRYPROP slice arm in
|
||||
// nodeisslice — an unwrap source whose success variant is a str.
|
||||
// n.type_ is checker-stamped to the success variant; mirrors cstage
|
||||
// node_isstr = type_isstr(n->type).
|
||||
if (k == syntax.nkind.N_TRYUNW || k == syntax.nkind.N_TRYPROP) {
|
||||
return syntax.typeisstr(n.type_: *syntax.tinfo);
|
||||
};
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
43
test/lang/unwrap_callarg_str_test.ww
Normal file
43
test/lang/unwrap_callarg_str_test.ww
Normal file
@@ -0,0 +1,43 @@
|
||||
// unwrap_callarg_str_test — runtime contract for a whole 24B str header
|
||||
// produced by an UNWRAP source (`mk_se()!` N_TRYUNW) passed BY VALUE as a
|
||||
// call argument. #6 (Mechanism A): cgenutil.ww's nodeisstr had no
|
||||
// N_TRYUNW/N_TRYPROP arm, so the unwrap fell through to the scalar
|
||||
// single-PUSHQ default — pushargsrev marshalled only .ptr (1 word) and
|
||||
// cgcall's pop sizer drained one word, so the callee read .len/.cap from
|
||||
// stale arg registers. cstage (cmd/w6c/cgen.c node_isstr) was always
|
||||
// type-keyed and correct; this aligns wwstage UP. Twin of #9's
|
||||
// deref_callarg str pin — same masking mechanism, different node kind.
|
||||
//
|
||||
// WHY the poison call: a pre-fix `strlen(mk_se()!)` pushes only .ptr, so
|
||||
// the callee reads .len from whatever arg register the caller left. The
|
||||
// preceding NORMAL-arg call strlen(decoy) deterministically leaves the
|
||||
// callee's .len register holding decoy's len 2 (≠ backing's 5), so a
|
||||
// pre-fix 1-word push reads 2 and the assert REDDENS. This is a wwstage-
|
||||
// only gap (cs≠ww), so the tooth is byte-id (cstage already pushed 3) AND
|
||||
// value-under-the-wwstage-frontend; byte-id (990-996) proves the stages
|
||||
// AGREE, not that the code is correct.
|
||||
|
||||
package unwrap_callarg_str_test;
|
||||
|
||||
type e = !i32;
|
||||
|
||||
fn mk_se() (str | e) = { return "abcde"; };
|
||||
|
||||
fn strlen(s: str) i32 = { return s.len: i32; };
|
||||
|
||||
fn strcap(s: str) i32 = { return s.cap: i32; };
|
||||
|
||||
fn strfirst(s: str) i32 = { return s[0]: i32; };
|
||||
|
||||
@test fn str_unwrap_callarg() void = {
|
||||
// Decoy ("xy", len 2) poisons the callee's .len register via a
|
||||
// normal-arg call; the unwrap-source call must overwrite it with 5.
|
||||
let decoy: str = "xy";
|
||||
assert(strlen(decoy) == 2);
|
||||
assert(strlen(mk_se()!) == 5);
|
||||
// .cap is the 3rd dropped word; decoy ("xy") seeds cap 2, so a
|
||||
// pre-fix 1-word push leaves the callee reading 2 here, not 5.
|
||||
assert(strcap(mk_se()!) == 5);
|
||||
// strfirst pins .ptr survived the push ('a' == 97).
|
||||
assert(strfirst(mk_se()!) == 97);
|
||||
};
|
||||
Reference in New Issue
Block a user