cgen: resolve local fn-ptr callees for variadic arg prep

The wwstage variadic call classification was name-keyed: a fn-ptr
FIELD call whose local base shadowed the current module name
(lib/log's log.println(log, args...)) picked the module fn's
signature — nfixed off by one, the fixed arg boxed into the gather,
the spread emitted as zeros — and a no-collision fn-ptr callee missed
the registry entirely, leaking the raw N_SPREAD as a single $0 word
(SIGSEGV / exit 255 in 8 of 11 logtest tests on the wwstage leg).
fnptrcalleetfn resolves a local fn-ptr callee (bare local or struct
field) to its N_TFN once, shared by the CALL-target choice,
callee_variadic_param (with the []T wrap registry params get from
installparams), calleecvariadic, and the widening param lookup, so
target and arg prep can never disagree. Graduates the #59.8 logtest
pin — DATABYTEID_DIVERGED-era M_DIVERGE count is now zero.
This commit is contained in:
2026-08-08 01:07:23 +09:00
parent c18005d833
commit 3f7452814b
5 changed files with 167 additions and 51 deletions

View File

@@ -7830,7 +7830,16 @@ fn cgcall(c: *cgen, n: *syntax.node) void = {
// the name-driven registry entirely (cmd/w6c/cgen.c:4161-4165).
let calleeparams: *syntax.node = nil;
if (callee != nil) {
if (callee.kind == syntax.nkind.N_IDENT) {
// #59.8: a LOCAL fn-ptr callee (bare local or struct-field)
// reads its own N_TFN params — the registry lookup below
// mis-hits when the local's name shadows the current module
// (lib/log's `log.println(log, args...)`), handing the
// module fn's signature to arg prep while the CALL target
// resolved the field. One resolver for both (fnptrcalleetfn).
let lft: *syntax.node = fnptrcalleetfn(c, callee);
if (lft != nil) {
calleeparams = lft.list;
} else { if (callee.kind == syntax.nkind.N_IDENT) {
calleeparams = fnparamslookup(c, callee.str);
} else { if (callee.kind == syntax.nkind.N_DOT) {
let cmod: str;
@@ -7841,7 +7850,7 @@ fn cgcall(c: *cgen, n: *syntax.node) void = {
};
};
calleeparams = fnparamslookupmod(c, callee.str, cmod);
}; };
}; }; };
};
// Hare-style variadic last param: gather N tail args into a
// frame-resident [N]T (vararg_d slot) plus a 24B slice
@@ -8419,48 +8428,11 @@ fn cgcall(c: *cgen, n: *syntax.node) void = {
isfnptrcall = true;
};
if (callee.kind == syntax.nkind.N_DOT) {
let base: *syntax.node = callee.lhs;
let fld: str = callee.str;
if (base != nil) {
if (base.kind == syntax.nkind.N_IDENT) {
let bn: str = base.str;
let lc: *local = localfindnode(c, bn);
if (lc != nil) {
let tn: *syntax.node = lc.tnode;
if (tn != nil) {
let lkind: syntax.nkind = tn.kind;
let sname: str;
sname.ptr = nil; sname.len = 0;
if (lkind == syntax.nkind.N_TNAME) { sname = tn.str; };
if (lkind == syntax.nkind.N_TPTR) {
let inner: *syntax.node = tn.lhs;
if (inner != nil) {
if (inner.kind == syntax.nkind.N_TNAME) { sname = inner.str; };
};
};
if (sname.len > 0) {
let si: *structinfo = structlookup(c, sname);
if (si != nil) {
let fi: *fieldinfo = si.fields;
for (fi != nil) {
let fn_: str = fi.fname;
if (syntax.streq(fn_, fld)) {
let ft: *syntax.node = fi.tnode;
if (ft != nil) {
if (ft.kind == syntax.nkind.N_TFN) {
isfnptrcall = true;
};
};
fi = nil;
} else {
fi = fi.finext;
};
};
};
};
};
};
};
// #59.8: same resolver as the arg-classification sites
// (fnptrcalleetfn) so CALL target and arg prep cannot
// disagree within one call.
if (fnptrcalleetfn(c, callee) != nil) {
isfnptrcall = true;
};
};
};

View File

@@ -50,6 +50,72 @@ fn findvariadicparam(ps: *syntax.node, nfixed_out: *i32) *syntax.node = {
return nil;
};
// fnptrcalleetfn — resolve a callee that names a LOCAL fn pointer
// (bare local `fp(...)`, or field call `w.emit(...)` where w is a
// local struct value / *struct and emit is an N_TFN field) to its
// N_TFN type node. Returns nil for every other callee shape — the
// name registry stays primary for module fns. Extracted from the
// cgcall isfnptrcall detection so the CALL-target choice and the
// arg classification key off ONE resolution and can never disagree
// within a call (#59.8: a local whose name shadows the current
// module made fnparamslookupmod match the module's own same-leaf
// variadic fn — nfixed off by one, the fixed arg boxed into the
// gather and the spread emitted as zeros; with no collision the
// lookup missed entirely and the raw N_SPREAD leaked to pushargsrev
// as a single $0 word). Cstage mirror: cgcall's callee_params from
// the checker-stamped n->lhs->type (cmd/w6c/cgen.c TY_FN cu->params).
fn fnptrcalleetfn(c: *cgen, callee: *syntax.node) *syntax.node = {
if (callee == nil) { return nil; };
if (callee.kind == syntax.nkind.N_IDENT) {
let lc0: *local = localfindnode(c, callee.str);
if (lc0 == nil) { return nil; };
let tn0: *syntax.node = lc0.tnode;
if (tn0 == nil) { return nil; };
if (tn0.kind == syntax.nkind.N_TFN) { return tn0; };
if (tn0.kind == syntax.nkind.N_TPTR) {
if (tn0.lhs != nil) {
if (tn0.lhs.kind == syntax.nkind.N_TFN) {
return tn0.lhs;
};
};
};
return nil;
};
if (callee.kind != syntax.nkind.N_DOT) { return nil; };
let base: *syntax.node = callee.lhs;
if (base == nil) { return nil; };
if (base.kind != syntax.nkind.N_IDENT) { return nil; };
let lc: *local = localfindnode(c, base.str);
if (lc == nil) { return nil; };
let tn: *syntax.node = lc.tnode;
if (tn == nil) { return nil; };
let sname: str;
sname.ptr = nil; sname.len = 0;
if (tn.kind == syntax.nkind.N_TNAME) { sname = tn.str; };
if (tn.kind == syntax.nkind.N_TPTR) {
if (tn.lhs != nil) {
if (tn.lhs.kind == syntax.nkind.N_TNAME) {
sname = tn.lhs.str;
};
};
};
if (sname.len == 0) { return nil; };
let si: *structinfo = structlookup(c, sname);
if (si == nil) { return nil; };
let fi: *fieldinfo = si.fields;
for (fi != nil) {
if (syntax.streq(fi.fname, callee.str)) {
let ft: *syntax.node = fi.tnode;
if (ft != nil) {
if (ft.kind == syntax.nkind.N_TFN) { return ft; };
};
return nil;
};
fi = fi.finext;
};
return nil;
};
// callee_variadic_param — convenience wrapper: looks up the callee
// by name and finds its variadic param + nfixed. Returns nil if the
// callee isn't registered or has no variadic param.
@@ -65,6 +131,34 @@ fn findvariadicparam(ps: *syntax.node, nfixed_out: *i32) *syntax.node = {
fn callee_variadic_param(c: *cgen, callee: *syntax.node, nfixed_out: *i32) *syntax.node = {
*nfixed_out = 0;
if (callee == nil) { return nil; };
// #59.8: a LOCAL fn-ptr callee resolves through its own N_TFN,
// never the name registry (which the collision shape mis-hits).
// A raw field-N_TFN variadic param carries the bare element T
// (registry params are installparams-promoted to []T); wrap it
// so the vararg_sl descriptor slot's tnode classifies as a
// slice downstream (mirror check.ww installparams / cstage
// check.c:455).
let ft: *syntax.node = fnptrcalleetfn(c, callee);
if (ft != nil) {
let vp: *syntax.node = findvariadicparam(ft.list, nfixed_out);
if (vp == nil) { return nil; };
if (vp.lhs != nil) {
if (vp.lhs.kind != syntax.nkind.N_TSLICE) {
let w: *syntax.node = syntax.newnode(
syntax.nkind.N_PARAM, "", 0, 0);
w.str = vp.str;
w.op = vp.op;
let sl: *syntax.node = syntax.newnode(
syntax.nkind.N_TSLICE, "", 0, 0);
sl.lhs = vp.lhs;
sl.type_ = syntax.typeslice(
vp.lhs.type_: *syntax.tinfo): *void;
w.lhs = sl;
return w;
};
};
return vp;
};
let ps: *syntax.node = nil;
if (callee.kind == syntax.nkind.N_IDENT) {
if (callee.str.len == 0) { return nil; };
@@ -96,7 +190,12 @@ fn calleecvariadic(c: *cgen, callee: *syntax.node, nfixed_out: *i32) bool = {
*nfixed_out = 0;
if (callee == nil) { return false; };
let ps: *syntax.node = nil;
if (callee.kind == syntax.nkind.N_IDENT) {
// #59.8: a LOCAL fn-ptr callee reads its own N_TFN params — the
// name registry mis-hits when the local shadows a module name.
let lft: *syntax.node = fnptrcalleetfn(c, callee);
if (lft != nil) {
ps = lft.list;
} else { if (callee.kind == syntax.nkind.N_IDENT) {
if (callee.str.len == 0) { return false; };
ps = fnparamslookup(c, callee.str);
} else { if (callee.kind == syntax.nkind.N_DOT) {
@@ -109,7 +208,7 @@ fn calleecvariadic(c: *cgen, callee: *syntax.node, nfixed_out: *i32) bool = {
};
};
ps = fnparamslookupmod(c, callee.str, cmod);
}; };
}; }; };
let p: *syntax.node = ps;
for (p != nil) {
if (p.kind == syntax.nkind.N_PARAM) {