← board


prio: 55

C: address of an EXTERNAL function called through a pointer does nothing

Symptom

Calling an EXTERNAL (crtl) function through a function pointer produces no output / wrong behaviour — the indirect call goes to a wrong address. Own (internal) functions via a pointer work.

#include <stdio.h>
int (*p)(const char*) = puts;      /* or &puts, local or global */
int main(){ p("hi"); return 0; }   /* prints nothing */

Both local and global pointers, bare or address-of, are affected — so it is the proc-ADDRESS of an external symbol that is wrong for an indirect call, not the pointer-init path (that was the separate &func fix, commit caab6bde). Direct calls to the same externals work.

Likely site

Wherever a bare/address-of external function name is lowered to a code-address value (IR_PROCADDR / the fn-pointer decay). An external proc's address is probably emitted as 0 / an unrelinked slot rather than its resolved code address.

Gate

puts/fprintf via a pointer prints; 00189 matches (dropped from pxx.skip).

Analysis 2026-07-07 (isolated, not yet fixed)

Located to the proc-address path, NOT the pointer-init:

Likely fix direction: when an "external" proc actually has an internal definition/resolution in this link (crtl), IR_PROCADDR should use the internal code-address fixup (the non-external branch at ir_codegen.inc:1655) rather than the GOT slot — or PatchDynCallSites must fill the address-of slot with the resolved internal address. Track A (codegen/linking) — focused session; verify across targets + that sqlite aSyscall + direct calls still work.

Refined 2026-07-07 — still broken after crtl-bind; external-vs-internal is the crux

Re-tested after feature-c-crtl-bind: puts/fprintf via a pointer STILL produce no output (00189 still fails). Key facts:

Precise pin 2026-07-07 (hypothesis tested + reverted)

Baseline confirmed: DIRECT puts("x")/printf(...) WORK in the libc-free static binary (EmitCallProc's DynCall slot is patched to the crtl impl address). Only taking the ADDRESS fails. puts is ProcExternal AND ProcBodyCompiled = FALSE (it's DynCall-resolved, not compiled as a normal internal proc), so the tried fix "route IR_PROCADDR through the internal fixup when ProcBodyCompiled" did NOT apply and was reverted. Real root cause: a global init int (*p)(const char*) = puts; records puts as a proc-address PendingInit, but an EXTERNAL proc has no compile-time constant address — its address is a DynCall slot resolved by PatchDynCallSites. The proc-address PendingInit materializer (and IR_PROCADDR/EmitExternalProcAddr for the value form) needs to emit a DynCall-slot LOAD / fixup for an external proc, the same mechanism the direct CALL uses — currently the address paths leave it 0. Fix: make the external-proc address paths (global-init proc-address PendingInit + IR_PROCADDR) go through RegisterExternal + a patched DynCall slot like the call path. Deep linking/codegen, focused session.

RESOLVED 2026-07-07 (Track A+C, sole-A)

Root cause was NOT the proc address. Verified int(*p)(const char*)=puts stores the CORRECT address (p==puts==0x41d388, printf %p) — direct call and address both fine. The blank output came from the string-literal ARGUMENT: AN_CALL_IND lowering (ir.inc) never applied the char* (+8) length-prefix skip that the direct AN_CALL path applies to a frozen string literal handed to a char* param. So p("hello") passed the Pascal length word, and the callee printed nothing. Int args worked (isolated with putchar fnptr → "AB", puts fnptr → blank).

Fix (ir.inc, AN_CALL_IND arg loop): mirror the direct-call marshalling — string literal → tyPointer param (or variadic slot) gets IR_BINOP(+8); managed string → pointer param passes as-is. C-mode only; Pascal self-build byte-identical. Gate: make test (self-host byte-identical) + c-conformance 194/0 + lua + sqlite threads all green.