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:
@@ -1,13 +1,13 @@
|
|||||||
package wwfixture;
|
package wwfixture;
|
||||||
|
|
||||||
def protocolversion: i32 = 1;
|
def protocolversion: i32 = 1;
|
||||||
def corpuscount: i32 = 1238;
|
def corpuscount: i32 = 1239;
|
||||||
def errorcount: i32 = 314;
|
def errorcount: i32 = 314;
|
||||||
def compilecount: i32 = 12;
|
def compilecount: i32 = 12;
|
||||||
def runcount: i32 = 145;
|
def runcount: i32 = 146;
|
||||||
def runexitcount: i32 = 767;
|
def runexitcount: i32 = 767;
|
||||||
def nativecount: i32 = 2476;
|
def nativecount: i32 = 2478;
|
||||||
def corpushash: str = "a78db801f50ad02de11fed981bd6d9c2d67509e69a69615744f1b2ad3ef4814d";
|
def corpushash: str = "369aca2f1c0081faf7297d8fa976a7374d97a07b76d2df079f7eea60660bda2a";
|
||||||
|
|
||||||
type directive = enum i32 {
|
type directive = enum i32 {
|
||||||
ERROR = 0,
|
ERROR = 0,
|
||||||
|
|||||||
@@ -7830,7 +7830,16 @@ fn cgcall(c: *cgen, n: *syntax.node) void = {
|
|||||||
// the name-driven registry entirely (cmd/w6c/cgen.c:4161-4165).
|
// the name-driven registry entirely (cmd/w6c/cgen.c:4161-4165).
|
||||||
let calleeparams: *syntax.node = nil;
|
let calleeparams: *syntax.node = nil;
|
||||||
if (callee != 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);
|
calleeparams = fnparamslookup(c, callee.str);
|
||||||
} else { if (callee.kind == syntax.nkind.N_DOT) {
|
} else { if (callee.kind == syntax.nkind.N_DOT) {
|
||||||
let cmod: str;
|
let cmod: str;
|
||||||
@@ -7841,7 +7850,7 @@ fn cgcall(c: *cgen, n: *syntax.node) void = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
calleeparams = fnparamslookupmod(c, callee.str, cmod);
|
calleeparams = fnparamslookupmod(c, callee.str, cmod);
|
||||||
}; };
|
}; }; };
|
||||||
};
|
};
|
||||||
// Hare-style variadic last param: gather N tail args into a
|
// Hare-style variadic last param: gather N tail args into a
|
||||||
// frame-resident [N]T (vararg_d slot) plus a 24B slice
|
// frame-resident [N]T (vararg_d slot) plus a 24B slice
|
||||||
@@ -8419,50 +8428,13 @@ fn cgcall(c: *cgen, n: *syntax.node) void = {
|
|||||||
isfnptrcall = true;
|
isfnptrcall = true;
|
||||||
};
|
};
|
||||||
if (callee.kind == syntax.nkind.N_DOT) {
|
if (callee.kind == syntax.nkind.N_DOT) {
|
||||||
let base: *syntax.node = callee.lhs;
|
// #59.8: same resolver as the arg-classification sites
|
||||||
let fld: str = callee.str;
|
// (fnptrcalleetfn) so CALL target and arg prep cannot
|
||||||
if (base != nil) {
|
// disagree within one call.
|
||||||
if (base.kind == syntax.nkind.N_IDENT) {
|
if (fnptrcalleetfn(c, callee) != nil) {
|
||||||
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;
|
isfnptrcall = true;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
fi = nil;
|
|
||||||
} else {
|
|
||||||
fi = fi.finext;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
// sret hidden first-arg (#23): load &dest into RDI AFTER all
|
// sret hidden first-arg (#23): load &dest into RDI AFTER all
|
||||||
// user-arg pops have finished — intidx started at 1 so RDI was
|
// user-arg pops have finished — intidx started at 1 so RDI was
|
||||||
|
|||||||
@@ -50,6 +50,72 @@ fn findvariadicparam(ps: *syntax.node, nfixed_out: *i32) *syntax.node = {
|
|||||||
return nil;
|
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
|
// callee_variadic_param — convenience wrapper: looks up the callee
|
||||||
// by name and finds its variadic param + nfixed. Returns nil if the
|
// by name and finds its variadic param + nfixed. Returns nil if the
|
||||||
// callee isn't registered or has no variadic param.
|
// 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 = {
|
fn callee_variadic_param(c: *cgen, callee: *syntax.node, nfixed_out: *i32) *syntax.node = {
|
||||||
*nfixed_out = 0;
|
*nfixed_out = 0;
|
||||||
if (callee == nil) { return nil; };
|
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;
|
let ps: *syntax.node = nil;
|
||||||
if (callee.kind == syntax.nkind.N_IDENT) {
|
if (callee.kind == syntax.nkind.N_IDENT) {
|
||||||
if (callee.str.len == 0) { return nil; };
|
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;
|
*nfixed_out = 0;
|
||||||
if (callee == nil) { return false; };
|
if (callee == nil) { return false; };
|
||||||
let ps: *syntax.node = nil;
|
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; };
|
if (callee.str.len == 0) { return false; };
|
||||||
ps = fnparamslookup(c, callee.str);
|
ps = fnparamslookup(c, callee.str);
|
||||||
} else { if (callee.kind == syntax.nkind.N_DOT) {
|
} 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);
|
ps = fnparamslookupmod(c, callee.str, cmod);
|
||||||
}; };
|
}; }; };
|
||||||
let p: *syntax.node = ps;
|
let p: *syntax.node = ps;
|
||||||
for (p != nil) {
|
for (p != nil) {
|
||||||
if (p.kind == syntax.nkind.N_PARAM) {
|
if (p.kind == syntax.nkind.N_PARAM) {
|
||||||
|
|||||||
@@ -169,8 +169,17 @@ static const struct ent ents[] = {
|
|||||||
.mode = M_ID, .cite = "#59.5 graduated by the stamp-keyed recognizers" },
|
.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) */
|
||||||
|
/* #59.8 graduated: the wwstage variadic call classification was
|
||||||
|
* name-keyed — a local fn-ptr field callee whose 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 entirely (raw N_SPREAD leaked
|
||||||
|
* as one $0 word). fnptrcalleetfn now resolves local fn-ptr
|
||||||
|
* callees for CALL target and arg prep alike. Runtime pin:
|
||||||
|
* r598_fnptr_field_variadic. */
|
||||||
{ .fixture = "lib/log/logtest.ww",
|
{ .fixture = "lib/log/logtest.ww",
|
||||||
.mode = M_DIVERGE, .cite = "#59.8" },
|
.mode = M_ID, .cite = "#59.8 graduated by the local-first callee resolver" },
|
||||||
/* #59.9 graduated: the wwstage checker never typed an N_BIN operand
|
/* #59.9 graduated: the wwstage checker never typed an N_BIN operand
|
||||||
* of `as`, so an enum OR-fold reached cgen unstamped and lowered as
|
* of `as`, so an enum OR-fold reached cgen unstamped and lowered as
|
||||||
* a phantom tagged assert (unconditional exit 1). checkisas now
|
* a phantom tagged assert (unconditional exit 1). checkisas now
|
||||||
|
|||||||
36
test/wcc/data/r598_fnptr_field_variadic/case.ww
Normal file
36
test/wcc/data/r598_fnptr_field_variadic/case.ww
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
//ww:run
|
||||||
|
// #59.8: a variadic call through a struct fn-pointer FIELD with a
|
||||||
|
// spread-forwarded tail (`v.cb(v, args...)`). The pre-fix wwstage
|
||||||
|
// classified the callee by NAME: with no registry hit the raw
|
||||||
|
// N_SPREAD leaked to pushargsrev as a single $0 word (callee saw
|
||||||
|
// args.ptr=0), and when the base local shadowed the module name it
|
||||||
|
// picked the module fn's signature and mis-counted nfixed.
|
||||||
|
package main;
|
||||||
|
|
||||||
|
type vt = struct {
|
||||||
|
cb: fn(v: *vt, args: i64...) void,
|
||||||
|
acc: i64,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn sum(v: *vt, args: i64...) void = {
|
||||||
|
let i: i32 = 0;
|
||||||
|
for (i < args.len) {
|
||||||
|
v.acc += args[i];
|
||||||
|
i += 1;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
fn dispatch(v: *vt, args: i64...) void = {
|
||||||
|
v.cb(v, args...);
|
||||||
|
};
|
||||||
|
|
||||||
|
fn main() i32 = {
|
||||||
|
let v: vt;
|
||||||
|
v.cb = sum;
|
||||||
|
v.acc = 0i64;
|
||||||
|
dispatch(&v, 1i64, 2i64, 3i64);
|
||||||
|
if (v.acc != 6i64) { return 1; };
|
||||||
|
v.cb(&v, 10i64);
|
||||||
|
if (v.acc != 16i64) { return 2; };
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user