diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index 482057a8..5ec14204 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -17580,6 +17580,19 @@ fn nodeisslice(c: *cgen, n: *node) bool = { let nm: str = n.str; let lc: *local = localfindnode(c, nm); if (lc != nil) { return isslicetype(c, lc.tnode); }; + // #21: a module-level `def g: []T` ident is not a frame + // slot (localfindnode→nil), so the local arm above misses + // it and it would fall to the scalar single-PUSHQ default, + // dropping len/cap. cstage's node_isslice is type-keyed + // (cmd/w6c/cgen.c:226-229 type_isslice(n->type)); align + // wwstage UP by reading the checker-stamped .type_ (the def + // decl's str/slice type node, check.ww exprtype N_IDENT + // arm). A def has no DATA symbol — it rides cgident's + // const-fold, so the push site and cgcall's pop sizer flip + // together on this shared recognizer (load-bearing). + if (deflookup(c, nm)) { + return typeisslice(n.type_: *tinfo); + }; return false; }; if (k == nkind.N_SLICE) { return true; }; @@ -17657,6 +17670,19 @@ fn nodeisstr(c: *cgen, n: *node) bool = { // MOVQ BX,CX shuffle on returns of str-aliased locals. if (isstrtype(c, lc.tnode)) { return true; }; }; + // #21: a module-level `def s: str` ident is not a frame slot + // (localfindnode→nil), so the local arm above misses it and + // it would fall to the scalar single-PUSHQ default, dropping + // len/cap. cstage's node_isstr is type-keyed (cmd/w6c/ + // cgen.c:213-216 type_isstr(n->type)); align wwstage UP by + // reading the checker-stamped .type_ (the def decl's str type + // node, check.ww exprtype N_IDENT arm). A def has no DATA + // symbol — it rides cgident's const-fold (LEAQ _S_n, MOVQ + // $len), so the push site and cgcall's pop sizer flip together + // on this shared recognizer (load-bearing). + if (deflookup(c, nm)) { + return typeisstr(n.type_: *tinfo); + }; return false; }; if (k == nkind.N_CALL) { diff --git a/selfhost/cmd/wcc/cgenutil.ww b/selfhost/cmd/wcc/cgenutil.ww index c9ce4d34..ba68eea0 100644 --- a/selfhost/cmd/wcc/cgenutil.ww +++ b/selfhost/cmd/wcc/cgenutil.ww @@ -1149,6 +1149,19 @@ fn nodeisslice(c: *cgen, n: *node) bool = { let nm: str = n.str; let lc: *local = localfindnode(c, nm); if (lc != nil) { return isslicetype(c, lc.tnode); }; + // #21: a module-level `def g: []T` ident is not a frame + // slot (localfindnode→nil), so the local arm above misses + // it and it would fall to the scalar single-PUSHQ default, + // dropping len/cap. cstage's node_isslice is type-keyed + // (cmd/w6c/cgen.c:226-229 type_isslice(n->type)); align + // wwstage UP by reading the checker-stamped .type_ (the def + // decl's str/slice type node, check.ww exprtype N_IDENT + // arm). A def has no DATA symbol — it rides cgident's + // const-fold, so the push site and cgcall's pop sizer flip + // together on this shared recognizer (load-bearing). + if (deflookup(c, nm)) { + return typeisslice(n.type_: *tinfo); + }; return false; }; if (k == nkind.N_SLICE) { return true; }; @@ -1226,6 +1239,19 @@ fn nodeisstr(c: *cgen, n: *node) bool = { // MOVQ BX,CX shuffle on returns of str-aliased locals. if (isstrtype(c, lc.tnode)) { return true; }; }; + // #21: a module-level `def s: str` ident is not a frame slot + // (localfindnode→nil), so the local arm above misses it and + // it would fall to the scalar single-PUSHQ default, dropping + // len/cap. cstage's node_isstr is type-keyed (cmd/w6c/ + // cgen.c:213-216 type_isstr(n->type)); align wwstage UP by + // reading the checker-stamped .type_ (the def decl's str type + // node, check.ww exprtype N_IDENT arm). A def has no DATA + // symbol — it rides cgident's const-fold (LEAQ _S_n, MOVQ + // $len), so the push site and cgcall's pop sizer flip together + // on this shared recognizer (load-bearing). + if (deflookup(c, nm)) { + return typeisstr(n.type_: *tinfo); + }; return false; }; if (k == nkind.N_CALL) { diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 0cad6de7..e103128a 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -17580,6 +17580,19 @@ fn nodeisslice(c: *cgen, n: *node) bool = { let nm: str = n.str; let lc: *local = localfindnode(c, nm); if (lc != nil) { return isslicetype(c, lc.tnode); }; + // #21: a module-level `def g: []T` ident is not a frame + // slot (localfindnode→nil), so the local arm above misses + // it and it would fall to the scalar single-PUSHQ default, + // dropping len/cap. cstage's node_isslice is type-keyed + // (cmd/w6c/cgen.c:226-229 type_isslice(n->type)); align + // wwstage UP by reading the checker-stamped .type_ (the def + // decl's str/slice type node, check.ww exprtype N_IDENT + // arm). A def has no DATA symbol — it rides cgident's + // const-fold, so the push site and cgcall's pop sizer flip + // together on this shared recognizer (load-bearing). + if (deflookup(c, nm)) { + return typeisslice(n.type_: *tinfo); + }; return false; }; if (k == nkind.N_SLICE) { return true; }; @@ -17657,6 +17670,19 @@ fn nodeisstr(c: *cgen, n: *node) bool = { // MOVQ BX,CX shuffle on returns of str-aliased locals. if (isstrtype(c, lc.tnode)) { return true; }; }; + // #21: a module-level `def s: str` ident is not a frame slot + // (localfindnode→nil), so the local arm above misses it and + // it would fall to the scalar single-PUSHQ default, dropping + // len/cap. cstage's node_isstr is type-keyed (cmd/w6c/ + // cgen.c:213-216 type_isstr(n->type)); align wwstage UP by + // reading the checker-stamped .type_ (the def decl's str type + // node, check.ww exprtype N_IDENT arm). A def has no DATA + // symbol — it rides cgident's const-fold (LEAQ _S_n, MOVQ + // $len), so the push site and cgcall's pop sizer flip together + // on this shared recognizer (load-bearing). + if (deflookup(c, nm)) { + return typeisstr(n.type_: *tinfo); + }; return false; }; if (k == nkind.N_CALL) { diff --git a/test/wcc/829_slice_str_global_arg.c b/test/wcc/829_slice_str_global_arg.c index 371930e2..c21b7762 100644 --- a/test/wcc/829_slice_str_global_arg.c +++ b/test/wcc/829_slice_str_global_arg.c @@ -31,9 +31,22 @@ * str_glob_ptr | let s:str="abcd"; take reads x[0] (uses ptr) | 97 * slice_glob_len | let g:[]int=[10..50]; take reads x.len | 5 * slice_glob_idx | let g:[]int=[10..50]; take reads x[2] | 30 + * def_str_len | def s:str="abcd"; take reads x.len (#21) | 4 + * def_str_ptr | def s:str="abcd"; take reads x[0] (#21) | 97 + * def_str_return | return ; take reads x.len (#21) | 5 * ctrl_local_str | LOCAL str arg (off!=0, unchanged, byte-id) | 2 * ctrl_local_sl | LOCAL []int arg (off!=0, unchanged, byte-id) | 8 * + * The def_* rows are the #21 residual of this by-value-global family: a + * module-level `def s: str` (NO DATA symbol) passed BY VALUE. Pre-fix wwstage's + * nodeisstr was LOCAL-keyed for an N_IDENT (def→false), so the def-str fell to + * the scalar single-PUSHQ default and dropped len/cap; cstage's node_isstr is + * TYPE-keyed (n->type) and pushes all 3 words. A def rides cgident's const-fold + * (LEAQ _S_n, MOVQ $len) — NOT the let LEAQ name(SB) path — then the now + * type-keyed nodeisstr triple push. def_str_return is a SIBLING guard (a + * def-str flowed through a fn return, then pushed via nodeisstr's pre-existing + * N_CALL arm) — byte-id-identical pre/post-fix, no #21 teeth of its own. + * * The *_glob_* rows are mutation-sane: pre-fix wwstage drops len/cap and the * callee reads garbage; cstage is correct. ctrl_* guard the unchanged local * path. A byte-id row pins cstage==wwstage `.s` for each source. @@ -101,6 +114,42 @@ static const struct row rows[] = { "export fn main() i32 = { return take(g): i32; };\n", 30 }, + /* def_str_len — THE #21 regression pin: a module-level `def s: str` + * passed BY VALUE, callee reads x.len. Pre-fix wwstage drops len/cap + * (scalar single-PUSHQ default); cstage type-keyed pushes all 3 words. + * A def has NO DATA symbol — it rides cgident's const-fold (LEAQ _S_x, + * MOVQ $len BX, $len CX), then the type-keyed str triple push. */ + { "def_str_len", + "package main;\n" + "def s: str = \"abcd\";\n" + "fn take(x: str) int = { return x.len: int; };\n" + "export fn main() i32 = { return take(s): i32; };\n", + 4 }, + + /* def_str_ptr — def-str by value, callee reads x[0] (proves the ptr + * word survived the push). 'a' == 97. */ + { "def_str_ptr", + "package main;\n" + "def s: str = \"abcd\";\n" + "fn take(x: str) int = { return x[0]: int; };\n" + "export fn main() i32 = { return take(s): i32; };\n", + 97 }, + + /* def_str_return — a def-str flowed through a fn return: give()'s + * `return s` leaves the const-folded str header (AX=ptr/BX=len/CX=cap), + * and the outer `take(give())` arg rides nodeisstr's pre-existing N_CALL + * arm (give returns str) for the 3-word push. A #21 SIBLING, not the + * N_IDENT def arm — it is byte-id-IDENTICAL pre- and post-fix (no teeth + * of its own), kept as a convergence guard that a def-str surviving a fn + * return stays a full str header on both stages. */ + { "def_str_return", + "package main;\n" + "def s: str = \"hello\";\n" + "fn give() str = { return s; };\n" + "fn take(x: str) int = { return x.len: int; };\n" + "export fn main() i32 = { return take(give()): i32; };\n", + 5 }, + /* ctrl_local_str — a LOCAL str arg (off!=0). cgen unchanged; pins the * local-str push path stays byte-id (no regress). */ { "ctrl_local_str",