cgen: key the str/slice arg recognizers off the checker stamp

The wwstage nodeisstr/nodeisslice recognizers were name-keyed for
every non-local shape: an indirect fn-pointer callee ((*f)() — the
errnotest #59.5 divergence, streq receiving a shifted register file)
and module-global let/const idents (path.sepstr — union-widen pushes
zero-filled len/cap) both fell to false while cstage keys off the
checker stamp unconditionally. Both recognizers now fall back to the
stamped n.type_; push and pop sites share them, so the drain stays
balanced by construction. Graduates the #59.5 errnotest pin.
This commit is contained in:
2026-08-08 00:58:04 +09:00
parent 90af21e132
commit c421c2b20a
6 changed files with 103 additions and 7 deletions

View File

@@ -1,13 +1,13 @@
package wwfixture; package wwfixture;
def protocolversion: i32 = 1; def protocolversion: i32 = 1;
def corpuscount: i32 = 1233; def corpuscount: i32 = 1236;
def errorcount: i32 = 314; def errorcount: i32 = 314;
def compilecount: i32 = 12; def compilecount: i32 = 12;
def runcount: i32 = 141; def runcount: i32 = 144;
def runexitcount: i32 = 766; def runexitcount: i32 = 766;
def nativecount: i32 = 2466; def nativecount: i32 = 2472;
def corpushash: str = "65d3642dfb77b486f5aed23044a979bbce37c36fbbcb7a7aedae20d87c7b0e4f"; def corpushash: str = "4c8f4a693d2d8193b15f4215610c1d070dc83493b1189af772ca09cad4a69db7";
type directive = enum i32 { type directive = enum i32 {
ERROR = 0, ERROR = 0,

View File

@@ -1342,6 +1342,11 @@ fn nodeisslice(c: *cgen, n: *syntax.node) bool = {
if (deflookup(c, nm)) { if (deflookup(c, nm)) {
return syntax.typeisslice(n.type_: *syntax.tinfo); return syntax.typeisslice(n.type_: *syntax.tinfo);
}; };
// #142-adjacent: module-global let/const slice ident — the
// str twin's rationale verbatim (see nodeisstr N_IDENT).
if (isletvar(c, nm)) {
return syntax.typeisslice(n.type_: *syntax.tinfo);
};
return false; return false;
}; };
if (k == syntax.nkind.N_SLICE) { return true; }; if (k == syntax.nkind.N_SLICE) { return true; };
@@ -1376,7 +1381,13 @@ fn nodeisslice(c: *cgen, n: *syntax.node) bool = {
return isslicetype(c, rtyp); return isslicetype(c, rtyp);
}; };
}; };
return false; // #59.5: a callee that is neither N_IDENT nor N_DOT — an
// indirect call through a fn-pointer VALUE (`(*f)()`,
// N_UN TK_STAR) — used to fall to false, so a slice return
// pushed one word and cgcall's pop sizer under-drained.
// Read the checker stamp (check.ww fn-VALUE callee arm),
// the callee-shape-blind key cstage node_isslice uses.
return syntax.typeisslice(n.type_: *syntax.tinfo);
}; };
// N_DOT: read the checker-stamped n.type_. Struct field, nested // N_DOT: read the checker-stamped n.type_. Struct field, nested
// dot, value-struct hops, and pseudo-fields (.ptr/.len/.cap) all // dot, value-struct hops, and pseudo-fields (.ptr/.len/.cap) all
@@ -1465,6 +1476,14 @@ fn nodeisstr(c: *cgen, n: *syntax.node) bool = {
if (deflookup(c, nm)) { if (deflookup(c, nm)) {
return syntax.typeisstr(n.type_: *syntax.tinfo); return syntax.typeisstr(n.type_: *syntax.tinfo);
}; };
// #142-adjacent: a module-global let/const str ident (path's
// `const sepstr: str`) is neither a frame slot nor a def; the
// false fall-through sent its union-widen push to the scalar
// arm (len/cap zero-filled). Read the checker stamp, the key
// cstage node_isstr uses unconditionally.
if (isletvar(c, nm)) {
return syntax.typeisstr(n.type_: *syntax.tinfo);
};
return false; return false;
}; };
if (k == syntax.nkind.N_CALL) { if (k == syntax.nkind.N_CALL) {
@@ -1489,7 +1508,9 @@ fn nodeisstr(c: *cgen, n: *syntax.node) bool = {
return isstrtype(c, rtyp); return isstrtype(c, rtyp);
}; };
}; };
return false; // #59.5: indirect fn-pointer callee (`(*f)()`) — read the
// checker stamp; see the nodeisslice N_CALL twin above.
return syntax.typeisstr(n.type_: *syntax.tinfo);
}; };
// #46 (F7-c2): `arr[i]` whose element is a str. The prior structural // #46 (F7-c2): `arr[i]` whose element is a str. The prior structural
// walk only recognised N_IDENT and N_DOT bases (idxelemtn off the // walk only recognised N_IDENT and N_DOT bases (idxelemtn off the

View File

@@ -157,8 +157,14 @@ static const struct ent ents[] = {
{ .fixture = "lib/encoding/base64/base64_test.ww", { .fixture = "lib/encoding/base64/base64_test.ww",
.mode = M_ID }, .mode = M_ID },
/* #59.4 hextest graduated to M_ID above (#22a reviewer fixes) */ /* #59.4 hextest graduated to M_ID above (#22a reviewer fixes) */
/* #59.5 graduated: the wwstage nodeisstr/nodeisslice recognizers
* fell to false for an indirect fn-pointer callee (`(*f)()`) and
* for module-global let/const idents, so a 3-word str/slice value
* pushed one word (or zero-filled its widen box) and cgcall
* under-drained; both now fall back to the checker stamp. Runtime
* pins: r595_indirect_call_str_arg, globalconst_{str,slice}_widen. */
{ .fixture = "lib/errors/errnotest.ww", { .fixture = "lib/errors/errnotest.ww",
.mode = M_DIVERGE, .cite = "#59.5" }, .mode = M_ID, .cite = "#59.5 graduated by the stamp-keyed recognizers" },
/* #59.6 fmt graduated to M_ID above (#129 fix) */ /* #59.6 fmt graduated to M_ID above (#129 fix) */
/* #59.7 siphash graduated to M_ID above (#61 fix) */ /* #59.7 siphash graduated to M_ID above (#61 fix) */
{ .fixture = "lib/log/logtest.ww", { .fixture = "lib/log/logtest.ww",

View File

@@ -0,0 +1,23 @@
//ww:run
// A module-global const []u8 widened into a tagged-union call arg —
// the slice twin of globalconst_str_widen (path's const dot/dotdot
// shape).
package main;
const gb: []u8 = ['x', 'y', 'z'];
fn f(x: ([]u8 | i32)) i32 = {
match (x) {
case let s: []u8 => {
if (len(s) != 3) { return 1; };
if (s[2] != 122u8) { return 2; };
return 0;
};
case let v: i32 => { return 3; };
};
return 4;
};
fn main() i32 = {
return f(gb);
};

View File

@@ -0,0 +1,24 @@
//ww:run
// A module-global const str widened into a tagged-union call arg.
// The pre-fix wwstage nodeisstr N_IDENT arm knew only frame slots and
// defs, so the widen push took the scalar arm and zero-filled len/cap
// (path.sepstr's strings.hasprefix shape).
package main;
const gs: str = "hello";
fn f(x: (str | i32)) i32 = {
match (x) {
case let s: str => {
if (s.len != 5) { return 1; };
if (s[4] != 111u8) { return 2; };
return 0;
};
case let v: i32 => { return 3; };
};
return 4;
};
fn main() i32 = {
return f(gs);
};

View File

@@ -0,0 +1,22 @@
//ww:run
// #59.5: a str returned by an INDIRECT fn-pointer call, passed
// directly as a call arg. The pre-fix wwstage nodeisstr/nodeisslice
// N_CALL arms were callee-name-keyed and fell to false for the
// N_UN(STAR) callee, so only the ptr word was pushed and the callee
// read a shifted register file (len=b.ptr).
package main;
fn hello() str = {
return "hello";
};
fn taker(s: str) i32 = {
if (s.len != 5) { return 1; };
if (s[0] != 104u8) { return 2; };
return 0;
};
fn main() i32 = {
let f = (&hello): *fn() str;
return taker((*f)());
};