selfhost+lib+test: route cgcall + nodeis{slice,str} N_DOT through fnretlookupmod (#34)
Class A silent miscompile, surfaced by landing strings.slice in Hare's natural delegation form `fromutf8_unsafe(utf8.slice(begin, end))` (ref/hare/strings/iter.ha:75). strings.slice itself returns str, so the inner utf8.slice (cross-module N_DOT) call's cgcall return-ABI fixup hit post-#4e fnretlookup's same-module-first walk and grabbed strings.slice's own str return — emitted a spurious `MOVQ DX, BX` after the cross-module CALL even though utf8.slice returns []u8 (selfhost/cmd/wcc/cgenexpr.ww cgcall return-ABI fixup, line 3249-3261 pre-fix). Every other consumer of cgcall:3249's str-shuffle decision sat on the same bare-leaf table and was silently miscompiling on the same collision shape pre-#34. Sibling: nodeisslice + nodeisstr N_CALL arms in selfhost/cmd/wcc/cgenutil.ww were N_IDENT-only — for a cross- module N_DOT call returning a slice or str, pushargsrev fell through to the natural 1-word PUSHQ AX, dropping the `.len` (and `.cap` for slices) of the return value when consumed as a call arg. strings.slice's body passes utf8.slice's []u8 result to fromutf8_unsafe; pre-fix wwstage pushed 1 word vs cstage's 3, breaking the receiver's slice-3-pop drain. Cstage carries no sister bug: cmd/w6c/cgen.c reads return shape from the typed `n->lhs->type` (TY_FN sig) for both str-shuffle and slice-/str-arg push counts — module-aware via the typed AST, sidestepping any bare-leaf table. Mirror of #4e's cstage-no- sister-bug note. Fix: route cgcall return-ABI fixup + nodeisslice/nodeisstr N_CALL arms through fnretlookupmod with `callee.lhs.str` (N_DOT qualifier) or `c.curmod` (N_IDENT). Mirror of #28 fnparamslookupmod / #31 fnretlookupmod N_DOT re-routing. Remaining bare-leaf fnretlookup consumer sites (~8 sites across cgenexpr/cgenutil/cgenstmt/cgendecl listed in task #34a) stay on the graduated bare-leaf path — none of the present-corpus N_DOT leaf collisions have return-shape divergence at those sites. A future stdlib port introducing a return-shape-divergent same-leaf N_DOT collision will need the *mod re-routing — filed as #34a sibling-latents. Bundled three concerns per rule 11: cgcall fix, nodeisslice/ nodeisstr fix, and strings.slice retire + sentinel. (a) alone leaves strings.slice byte-id breaking on slice-arg push count. (b) alone leaves a phantom MOVQ DX, BX on the inner cross- module CALL. (c) alone fails 995_self_rebuild without (a)+(b). The three cannot land separately bisect-cleanly; the 745 sentinel pins the primary repro (cgcall str-shuffle) which sentinel-flips on a cgcall:3257 revert. 745_fnret34_modshadow pins the fix with 1 row: caller.slice returns str (same leaf as the cross-module callee, divergent return shape); caller.run calls myutf8.slice returning []u8. Asserts CALL myutf8.slice present inside caller.run TEXT + `MOVQ DX, BX` anti-check on each stage plus cs-vs-ws byte-id. strings.slice retired in lib/strings/strings.ww: the deferral block becomes the natural Hare delegation form with two local utf8.decoder reconstructions for the iterator endpoints — ww has no anonymous-embed (parallel to the existing `move` helper). iter_slice_cases mirrors ref/hare/strings/iter.ha:110-127; sidesteps the Hare `let t = s;` iterator-copy via fresh strings.iter() to stay clear of #35's sibling latents. 119/119 ok. ww2 == ww3 == ww4 byte-id holds.
This commit is contained in:
@@ -489,11 +489,27 @@ fn nodeisslice(c: *cgen, n: *node) bool = {
|
||||
// fallthrough emits one PUSHQ AX (loses .len/.cap) and the pop
|
||||
// side under-drains by 2 words, leaving R8/R9 unset for the
|
||||
// receiver. Mirrors nodeisstr's N_CALL arm just below.
|
||||
// N_DOT (cross-module callee, #34): route through fnretlookupmod
|
||||
// so a same-leaf caller-module fn with diverging return shape
|
||||
// doesn't shadow the explicit `mod.f()` qualifier — surfaced by
|
||||
// strings.slice returning `fromutf8_unsafe(utf8.slice(...))`
|
||||
// where strings.slice itself returns str.
|
||||
if (k == nkind.N_CALL) {
|
||||
let callee: *node = n.lhs;
|
||||
if (callee != nil) {
|
||||
if (callee.kind == nkind.N_IDENT) {
|
||||
let rt: *node = fnretlookup(c, callee.str);
|
||||
let rt: *node = fnretlookupmod(c, callee.str, c.curmod);
|
||||
return isslicetype(c, rt);
|
||||
};
|
||||
if (callee.kind == nkind.N_DOT) {
|
||||
let cmod: str;
|
||||
cmod.ptr = nil; cmod.len = 0;
|
||||
if (callee.lhs != nil) {
|
||||
if (callee.lhs.kind == nkind.N_IDENT) {
|
||||
cmod = callee.lhs.str;
|
||||
};
|
||||
};
|
||||
let rt: *node = fnretlookupmod(c, callee.str, cmod);
|
||||
return isslicetype(c, rt);
|
||||
};
|
||||
};
|
||||
@@ -609,8 +625,21 @@ fn nodeisstr(c: *cgen, n: *node) bool = {
|
||||
let callee: *node = n.lhs;
|
||||
if (callee != nil) {
|
||||
if (callee.kind == nkind.N_IDENT) {
|
||||
let cnm: str = callee.str;
|
||||
let rt: *node = fnretlookup(c, cnm);
|
||||
let rt: *node = fnretlookupmod(c, callee.str, c.curmod);
|
||||
return isstrtype(c, rt);
|
||||
};
|
||||
// #34: cross-module N_DOT — route through fnretlookupmod
|
||||
// so a same-leaf caller-module fn (different return shape)
|
||||
// doesn't shadow the explicit qualifier.
|
||||
if (callee.kind == nkind.N_DOT) {
|
||||
let cmod: str;
|
||||
cmod.ptr = nil; cmod.len = 0;
|
||||
if (callee.lhs != nil) {
|
||||
if (callee.lhs.kind == nkind.N_IDENT) {
|
||||
cmod = callee.lhs.str;
|
||||
};
|
||||
};
|
||||
let rt: *node = fnretlookupmod(c, callee.str, cmod);
|
||||
return isstrtype(c, rt);
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user