From 6553d60e915c2ee89ad1b9a410432e516ba6f0a7 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Sat, 8 Aug 2026 02:28:33 +0900 Subject: [PATCH] cgen: peel *fn field types for local struct-field callees MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fnptrcalleetfn's N_DOT arm accepted only a bare N_TFN field tnode, so a call through a field declared `cb: *fn(...)` missed, fell through to the name registry, and emitted CALL cb(SB) — an undefined symbol (loud at link; cstage calls the stamped ptr-to-fn indirectly). Same TPTR peel the N_IDENT arm already had. Fixture fnptrfield_call covers the by-value and via-pointer shapes; corpus pin 1478/2956. --- docs/test-system-v2.md | 4 ++-- internal/wwfixture/types.ww | 8 ++++---- selfhost/cmd/wcc/cgenutil.ww | 12 ++++++++++++ test/wcc/data/fnptrfield_call/case.ww | 22 ++++++++++++++++++++++ 4 files changed, 40 insertions(+), 6 deletions(-) create mode 100644 test/wcc/data/fnptrfield_call/case.ww diff --git a/docs/test-system-v2.md b/docs/test-system-v2.md index b49b27f0..e1f9d96a 100644 --- a/docs/test-system-v2.md +++ b/docs/test-system-v2.md @@ -32,9 +32,9 @@ categories out of the ordinary developer target. | Fixed point and self-host | `test-bootstrap` | | Host linker/platform behavior | `test-platform` | -The live declarative compiler corpus has 1,477 fixtures and 2,954 C/WW cells: +The live declarative compiler corpus has 1,478 fixtures and 2,956 C/WW cells: 335 expected rejections (311 shared and 24 stage-specific), 17 compile-only -successes, 191 exit-zero programs, and 934 explicit-exit programs. +successes, 191 exit-zero programs, and 935 explicit-exit programs. 147 native C carriers remain. They are partitioned exactly once as five in-process units, 24 byte/artifact gates, six bootstrap gates, one platform diff --git a/internal/wwfixture/types.ww b/internal/wwfixture/types.ww index 7906f5c6..5fa66bd7 100644 --- a/internal/wwfixture/types.ww +++ b/internal/wwfixture/types.ww @@ -1,13 +1,13 @@ package wwfixture; def protocolversion: i32 = 1; -def corpuscount: i32 = 1477; +def corpuscount: i32 = 1478; def errorcount: i32 = 335; def compilecount: i32 = 17; def runcount: i32 = 191; -def runexitcount: i32 = 934; -def nativecount: i32 = 2954; -def corpushash: str = "64258f4de9ac7d71c4ad6221dd2ae7338a38cd5c620fc4ce22c1105021559e83"; +def runexitcount: i32 = 935; +def nativecount: i32 = 2956; +def corpushash: str = "2ac2342a80c4148968250bf4ce93852b78cdefb6af96622fa533eea4a68837c6"; type directive = enum i32 { ERROR = 0, diff --git a/selfhost/cmd/wcc/cgenutil.ww b/selfhost/cmd/wcc/cgenutil.ww index 18f0e35a..456e2030 100644 --- a/selfhost/cmd/wcc/cgenutil.ww +++ b/selfhost/cmd/wcc/cgenutil.ww @@ -108,6 +108,18 @@ fn fnptrcalleetfn(c: *cgen, callee: *syntax.node) *syntax.node = { let ft: *syntax.node = fi.tnode; if (ft != nil) { if (ft.kind == syntax.nkind.N_TFN) { return ft; }; + // `cb: *fn(...)` — same TPTR peel as the N_IDENT + // arm above; without it the field-call fell through + // to the name registry and emitted CALL (SB) + // (undefined symbol — loud at link, but cstage + // resolves the stamped ptr-to-fn and calls indirect). + if (ft.kind == syntax.nkind.N_TPTR) { + if (ft.lhs != nil) { + if (ft.lhs.kind == syntax.nkind.N_TFN) { + return ft.lhs; + }; + }; + }; }; return nil; }; diff --git a/test/wcc/data/fnptrfield_call/case.ww b/test/wcc/data/fnptrfield_call/case.ww new file mode 100644 index 00000000..4eeff68a --- /dev/null +++ b/test/wcc/data/fnptrfield_call/case.ww @@ -0,0 +1,22 @@ +//ww:run-exit 36 +package main; + +// A call through a struct field declared `*fn(...)`, by value and via +// pointer. The wwstage fnptrcalleetfn N_DOT arm lacked the TPTR peel +// its N_IDENT arm had, so the callee fell through to the name registry +// and emitted CALL cb(SB) — an undefined symbol. + +type cbs = struct { + cb: *fn(x: i32) i32, + k: i32, +}; + +fn twice(x: i32) i32 = { return x * 2; }; + +fn main() i32 = { + let o: cbs; + o.cb = &twice; + o.k = 4; + let p: *cbs = &o; + return o.cb(10) + p.cb(6) + o.k; +};