selfhost+test: route N_DOT callee through fnparamslookupmod (#28)
Wwstage cgcall now mirrors cstage's typed-AST callee-params lookup for module-qualified mod.fn(...) calls, restoring tagged- union widening on cross-module slice args. Class A runtime miscompile — masked from 995_self_rebuild because wwstage tools don't call bytes.index directly; surfaced by lib/strings landing dragging utf8 + bytes into the wwstage-tool dep chain via strings.byteindex's `bytes.X(toutf8(...), n)` call sites. Pre-fix: wwstage's cgcall (cgenexpr.ww) looked up calleeparams only when callee.kind == N_IDENT. For N_DOT callees (the module-qualified mod.fn() form), calleeparams stayed nil → pushargsrev's widening detection gated on param != nil never fired → wwstage fell through to the N_IDENT-slice fast path pushing only 3 slot words (cap, len, ptr) WITHOUT the variant tag. Receiving fn's `match (needle)` then dispatched on (needle.ptr in CX) instead of needle.tag, with R8/R9 carrying .len/.cap instead of .ptr/.len. Wrong arm + wrong payload. Cstage handles N_DOT natively via the checker-set type on n->lhs->type (cmd/w6c/cgen.c:4156-4165), so cg_widen_tagged_push slice path pushes 4 words including tag. Polarity catalog: wwstage UNDER — calleeparams lookup missing N_DOT dispatch arm. Sister to #19 (N_TSLICE variantindex arm), #21 (N_CALL pushargsrev arm), #24 (N_CALL nodeisslice arm), #27 (aliaslookup same-mod-first). The pattern: wwstage dispatchers keep missing arms cstage has natively via typed-AST resolution. Convergence wwstage → cstage (rule 10's spirit overrides letter when correctness is at stake — Path 2 of aligning cstage DOWN would create a runtime miscompile in both stages). Fix: cgcall N_DOT branch pulls module from callee.lhs.str and function name from callee.str, calls new fnparamslookupmod helper. Helper does same-module-first walk then existing first-match fallback (mirrors #27's aliaslookup fix shape). New fnret.fmod field carries module identity; collectfnrets sets f.fmod = d.module at registration. Module-qualified pkg.fn path unchanged. Tests: - 727_modcall_widen_slice pins MOVQ $1 + PUSHQ AX (tag-synth) before the receiving fn's CALL on canonical mod.fn(slice, ...) shape with the callee param widened to a tagged union. Three assertions per row: cstage tag-synth presence, wwstage tag-synth presence, cstage↔wwstage cmp -s byte-id. Sentinel- flip-verified: comment out fnparamslookupmod call → wwstage tag-synth absent + cmp diverges. 99/99 ok. 995_self_rebuild stays green (ww2==ww3==ww4 byte-id).
This commit is contained in:
@@ -12954,13 +12954,28 @@ fn cgcall(c: *cgen, n: *node) void = {
|
||||
|
||||
// Look up the callee's declared params for tagged-union widening.
|
||||
// fn-pointer calls (callee is a local) don't get widening — the
|
||||
// user must build the tagged value explicitly. Matches the most
|
||||
// common case (direct named calls).
|
||||
// user must build the tagged value explicitly.
|
||||
//
|
||||
// N_DOT (`mod.fn(...)`) covers cross-module calls; pre-#28 wwstage
|
||||
// only handled N_IDENT, leaving N_DOT calls without widening
|
||||
// detection — pushargsrev then fell through to the N_IDENT-slice
|
||||
// fast path and dropped the variant tag word on widened slice args.
|
||||
// Cstage finds params via the checker-set `n->lhs->type`, sidestepping
|
||||
// the name-driven registry entirely (cmd/w6c/cgen.c:4161-4165).
|
||||
let calleeparams: *node = nil;
|
||||
if (callee != nil) {
|
||||
if (callee.kind == nkind.N_IDENT) {
|
||||
calleeparams = fnparamslookup(c, callee.str);
|
||||
};
|
||||
} else { 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;
|
||||
};
|
||||
};
|
||||
calleeparams = fnparamslookupmod(c, callee.str, cmod);
|
||||
}; };
|
||||
};
|
||||
// Hare-style variadic last param: gather N tail args into a
|
||||
// frame-resident [N]T (`@vararg_d_<seq>`) plus a 24B slice
|
||||
@@ -19648,6 +19663,7 @@ fn emitdatasection(c: *cgen) void = {
|
||||
|
||||
type fnret = struct {
|
||||
fname: str,
|
||||
fmod: str,
|
||||
rtype: *node,
|
||||
params: *node,
|
||||
frnext: *fnret,
|
||||
@@ -19658,8 +19674,9 @@ fn collectfnrets(c: *cgen, file: *node) void = {
|
||||
let d: *node = file.list;
|
||||
for (d != nil) {
|
||||
if (d.kind == nkind.N_FNDECL) {
|
||||
let f: *fnret = amalloc(c.a, 48u64): *fnret;
|
||||
let f: *fnret = amalloc(c.a, 64u64): *fnret;
|
||||
f.fname = d.str;
|
||||
f.fmod = d.module;
|
||||
f.rtype = d.lhs;
|
||||
f.params = d.list;
|
||||
f.frnext = c.fnrets;
|
||||
@@ -19692,6 +19709,25 @@ fn fnparamslookup(c: *cgen, name: str) *node = {
|
||||
return nil;
|
||||
};
|
||||
|
||||
// fnparamslookupmod — same-module-first leaf walk. Module-qualified
|
||||
// `mod.fn(...)` calls go through this so a leaf collision (multiple
|
||||
// modules export the same name, e.g. `os.read` and `io.read`) resolves
|
||||
// to the explicit module. Falls back to the first leaf match if no
|
||||
// matching module is registered — mirrors aliaslookup's two-pass shape
|
||||
// (cgen.ww:75, fixed in #27).
|
||||
fn fnparamslookupmod(c: *cgen, name: str, mod: str) *node = {
|
||||
if (mod.len > 0) {
|
||||
let f: *fnret = c.fnrets;
|
||||
for (f != nil) {
|
||||
if (streq(f.fname, name)) {
|
||||
if (streq(f.fmod, mod)) { return f.params; };
|
||||
};
|
||||
f = f.frnext;
|
||||
};
|
||||
};
|
||||
return fnparamslookup(c, name);
|
||||
};
|
||||
|
||||
// ---- def-constant registry ------------------------------------------
|
||||
//
|
||||
// `def NAME: T = LIT;` becomes a DATA symbol the C-side w6c emits; an
|
||||
|
||||
@@ -1515,6 +1515,7 @@ fn emitdatasection(c: *cgen) void = {
|
||||
|
||||
type fnret = struct {
|
||||
fname: str,
|
||||
fmod: str,
|
||||
rtype: *node,
|
||||
params: *node,
|
||||
frnext: *fnret,
|
||||
@@ -1525,8 +1526,9 @@ fn collectfnrets(c: *cgen, file: *node) void = {
|
||||
let d: *node = file.list;
|
||||
for (d != nil) {
|
||||
if (d.kind == nkind.N_FNDECL) {
|
||||
let f: *fnret = amalloc(c.a, 48u64): *fnret;
|
||||
let f: *fnret = amalloc(c.a, 64u64): *fnret;
|
||||
f.fname = d.str;
|
||||
f.fmod = d.module;
|
||||
f.rtype = d.lhs;
|
||||
f.params = d.list;
|
||||
f.frnext = c.fnrets;
|
||||
@@ -1559,6 +1561,25 @@ fn fnparamslookup(c: *cgen, name: str) *node = {
|
||||
return nil;
|
||||
};
|
||||
|
||||
// fnparamslookupmod — same-module-first leaf walk. Module-qualified
|
||||
// `mod.fn(...)` calls go through this so a leaf collision (multiple
|
||||
// modules export the same name, e.g. `os.read` and `io.read`) resolves
|
||||
// to the explicit module. Falls back to the first leaf match if no
|
||||
// matching module is registered — mirrors aliaslookup's two-pass shape
|
||||
// (cgen.ww:75, fixed in #27).
|
||||
fn fnparamslookupmod(c: *cgen, name: str, mod: str) *node = {
|
||||
if (mod.len > 0) {
|
||||
let f: *fnret = c.fnrets;
|
||||
for (f != nil) {
|
||||
if (streq(f.fname, name)) {
|
||||
if (streq(f.fmod, mod)) { return f.params; };
|
||||
};
|
||||
f = f.frnext;
|
||||
};
|
||||
};
|
||||
return fnparamslookup(c, name);
|
||||
};
|
||||
|
||||
// ---- def-constant registry ------------------------------------------
|
||||
//
|
||||
// `def NAME: T = LIT;` becomes a DATA symbol the C-side w6c emits; an
|
||||
|
||||
@@ -2856,13 +2856,28 @@ fn cgcall(c: *cgen, n: *node) void = {
|
||||
|
||||
// Look up the callee's declared params for tagged-union widening.
|
||||
// fn-pointer calls (callee is a local) don't get widening — the
|
||||
// user must build the tagged value explicitly. Matches the most
|
||||
// common case (direct named calls).
|
||||
// user must build the tagged value explicitly.
|
||||
//
|
||||
// N_DOT (`mod.fn(...)`) covers cross-module calls; pre-#28 wwstage
|
||||
// only handled N_IDENT, leaving N_DOT calls without widening
|
||||
// detection — pushargsrev then fell through to the N_IDENT-slice
|
||||
// fast path and dropped the variant tag word on widened slice args.
|
||||
// Cstage finds params via the checker-set `n->lhs->type`, sidestepping
|
||||
// the name-driven registry entirely (cmd/w6c/cgen.c:4161-4165).
|
||||
let calleeparams: *node = nil;
|
||||
if (callee != nil) {
|
||||
if (callee.kind == nkind.N_IDENT) {
|
||||
calleeparams = fnparamslookup(c, callee.str);
|
||||
};
|
||||
} else { 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;
|
||||
};
|
||||
};
|
||||
calleeparams = fnparamslookupmod(c, callee.str, cmod);
|
||||
}; };
|
||||
};
|
||||
// Hare-style variadic last param: gather N tail args into a
|
||||
// frame-resident [N]T (`@vararg_d_<seq>`) plus a 24B slice
|
||||
|
||||
@@ -12954,13 +12954,28 @@ fn cgcall(c: *cgen, n: *node) void = {
|
||||
|
||||
// Look up the callee's declared params for tagged-union widening.
|
||||
// fn-pointer calls (callee is a local) don't get widening — the
|
||||
// user must build the tagged value explicitly. Matches the most
|
||||
// common case (direct named calls).
|
||||
// user must build the tagged value explicitly.
|
||||
//
|
||||
// N_DOT (`mod.fn(...)`) covers cross-module calls; pre-#28 wwstage
|
||||
// only handled N_IDENT, leaving N_DOT calls without widening
|
||||
// detection — pushargsrev then fell through to the N_IDENT-slice
|
||||
// fast path and dropped the variant tag word on widened slice args.
|
||||
// Cstage finds params via the checker-set `n->lhs->type`, sidestepping
|
||||
// the name-driven registry entirely (cmd/w6c/cgen.c:4161-4165).
|
||||
let calleeparams: *node = nil;
|
||||
if (callee != nil) {
|
||||
if (callee.kind == nkind.N_IDENT) {
|
||||
calleeparams = fnparamslookup(c, callee.str);
|
||||
};
|
||||
} else { 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;
|
||||
};
|
||||
};
|
||||
calleeparams = fnparamslookupmod(c, callee.str, cmod);
|
||||
}; };
|
||||
};
|
||||
// Hare-style variadic last param: gather N tail args into a
|
||||
// frame-resident [N]T (`@vararg_d_<seq>`) plus a 24B slice
|
||||
@@ -19648,6 +19663,7 @@ fn emitdatasection(c: *cgen) void = {
|
||||
|
||||
type fnret = struct {
|
||||
fname: str,
|
||||
fmod: str,
|
||||
rtype: *node,
|
||||
params: *node,
|
||||
frnext: *fnret,
|
||||
@@ -19658,8 +19674,9 @@ fn collectfnrets(c: *cgen, file: *node) void = {
|
||||
let d: *node = file.list;
|
||||
for (d != nil) {
|
||||
if (d.kind == nkind.N_FNDECL) {
|
||||
let f: *fnret = amalloc(c.a, 48u64): *fnret;
|
||||
let f: *fnret = amalloc(c.a, 64u64): *fnret;
|
||||
f.fname = d.str;
|
||||
f.fmod = d.module;
|
||||
f.rtype = d.lhs;
|
||||
f.params = d.list;
|
||||
f.frnext = c.fnrets;
|
||||
@@ -19692,6 +19709,25 @@ fn fnparamslookup(c: *cgen, name: str) *node = {
|
||||
return nil;
|
||||
};
|
||||
|
||||
// fnparamslookupmod — same-module-first leaf walk. Module-qualified
|
||||
// `mod.fn(...)` calls go through this so a leaf collision (multiple
|
||||
// modules export the same name, e.g. `os.read` and `io.read`) resolves
|
||||
// to the explicit module. Falls back to the first leaf match if no
|
||||
// matching module is registered — mirrors aliaslookup's two-pass shape
|
||||
// (cgen.ww:75, fixed in #27).
|
||||
fn fnparamslookupmod(c: *cgen, name: str, mod: str) *node = {
|
||||
if (mod.len > 0) {
|
||||
let f: *fnret = c.fnrets;
|
||||
for (f != nil) {
|
||||
if (streq(f.fname, name)) {
|
||||
if (streq(f.fmod, mod)) { return f.params; };
|
||||
};
|
||||
f = f.frnext;
|
||||
};
|
||||
};
|
||||
return fnparamslookup(c, name);
|
||||
};
|
||||
|
||||
// ---- def-constant registry ------------------------------------------
|
||||
//
|
||||
// `def NAME: T = LIT;` becomes a DATA symbol the C-side w6c emits; an
|
||||
|
||||
Reference in New Issue
Block a user