Skip to content

PostgreSQL Function Manager (fmgr) — The V1 Call Convention, Lookup Pipeline, and Extension ABI

Contents:

Every relational engine that supports user-defined functions (UDFs) must answer two questions at call time: where is the code and how are arguments and results exchanged? The answers form the engine’s function-call interface (FCI): the contract between the query executor and any callable unit — built-in, SQL-expression, extension C function, or PL/pgsql procedure.

Two design tensions drive every FCI:

  1. Lookup cost vs. call cost. Resolving a function OID to a callable address requires catalog access. In a SQL query that applies the same function to millions of rows, paying lookup cost per row is ruinous. The canonical resolution is to split the path: a one-time lookup phase produces a cached descriptor; the call phase uses only that descriptor, never touching the catalog again. This is the same separation that motivates prepared statements and plan caches in the query layer.

  2. Type safety vs. uniformity. A direct C call expresses argument types precisely but ties the caller to one callee signature — every new type would need a new entry point. Engines instead unify all arguments and return values into a single wide type (PostgreSQL uses Datum, a pointer-or-scalar union), letting every function have the same C signature. Type-specific conversion macros (PG_GETARG_INT32, PG_RETURN_FLOAT8) restore safety at the edges.

Three additional features emerge from the practical demands of a production SQL engine and are present in some form in every major RDBMS:

  • Strict-null short-circuit. A function marked strict should not be called when any argument is NULL — the result is implicitly NULL. Enforcing this in the manager rather than in every function body eliminates a pervasive class of defensive code.
  • Set-returning functions (SRFs). SQL allows a function to produce a set of rows (equivalent to a table-valued function in SQL Server, a pipelined function in Oracle). The call convention must support either value-per-call (iterator) or batch-materialize semantics.
  • Extension ABI stability. Dynamically loaded modules compiled against one minor version must refuse to load against an incompatible major version. A magic-struct pattern — a well-known symbol the loader checks before calling anything else — is the standard solution.

PostgreSQL’s answers to all three live in fmgr.h, fmgr.c, and the src/backend/utils/fmgr/README, which is the primary design document for this subsystem.

The patterns below recur across PostgreSQL, Oracle, DB2, SQL Server, and MySQL. PostgreSQL’s specific choices in the next section are one calibration of these shared dials.

Almost every engine separates function resolution (find the code address, read catalog metadata) from function invocation (pass arguments, collect result). The resolved metadata is stored in a per-function descriptor struct that the executor caches for the lifetime of a query (or plan). The descriptor holds: the code pointer, the number of expected arguments, the strictness flag, and a per-call scratch slot (fn_extra) that language handlers use to cache their own parsed-function state.

Rather than separate C entry points per type (add_int32, add_float8, …), engines use a single scalar wide enough to hold any value. Oracle uses a dvoid * with type codes; SQL Server uses its own variant-type struct; MySQL uses the Item hierarchy. PostgreSQL uses Datum — on 64-bit platforms a uintptr_t large enough to hold either a scalar value directly or a pointer to a heap-allocated value. All arguments arrive as a NullableDatum[] array; all results leave as a Datum plus an isnull flag.

A function’s call handler is itself a C function registered in pg_language. When the manager resolves a PL/pgSQL function, it does not call the PL/pgSQL bytecode directly; it calls the PL/pgSQL call handler, which then interprets the bytecode. The call handler receives the same FunctionCallInfo the manager would have passed to a C function, so from the manager’s perspective all languages look identical after lookup.

Loading a .so / .dll on every function call is prohibitively expensive. Engines keep a session-level cache of open library handles keyed by the file path. On the first call to any function in a library the file is dlopen’d and the address is resolved; subsequent calls reuse the cached handle. PostgreSQL adds a second layer: a hash table keyed by (fn_oid, xmin, ctid) that stores the resolved C function pointer so that even dlsym is skipped on repeat calls.

Any engine that supports dynamically loaded extensions needs a guard against loading a module compiled for a different ABI (different struct layouts, different FUNC_MAX_ARGS, etc.). The pattern: the loader looks for a well-known symbol (e.g., Pg_magic_func in PostgreSQL) in the loaded library before calling any user function. The symbol returns a struct of ABI-critical constants that the loader compares against its own compile-time values. A mismatch aborts the load with a useful error.

Concept / patternPostgreSQL name
Function lookup descriptorFmgrInfo (fmgr.h:56)
Per-call argument/result containerFunctionCallInfoBaseData / FunctionCallInfo (fmgr.h:85)
Uniform value typeDatum (postgres.h)
Code pointer in descriptorFmgrInfo.fn_addrPGFunction typedef
Language dispatcher (call handler)fn_addr = language’s lanplcallfoid for PL functions
Per-session library cacheCFuncHash hash table in fmgr.c
Built-in function tablefmgr_builtins[] + fmgr_builtin_oid_index[] (fmgrtab.h)
ABI guardPg_magic_struct / PG_MODULE_MAGIC macro (fmgr.h)
Strict-null short-circuitFmgrInfo.fn_strict; enforced by caller before FunctionCallInvoke
Set-returning function supportReturnSetInfo node in fcinfo->resultinfo
Per-call handler scratch slotFmgrInfo.fn_extra
Security-definer / proconfig wrapperfmgr_security_definer interposer function

PostgreSQL’s function manager, universally called fmgr, is the single gateway through which the executor calls every SQL-callable unit. The in-tree src/backend/utils/fmgr/README is the design authority; this section distills it against the REL_18_STABLE source (commit 273fe94).

The architecture has three layers:

  1. The descriptor layerFmgrInfo and its population via fmgr_info.
  2. The call layerFunctionCallInfoBaseData, FunctionCallInvoke, and the DirectFunctionCall / FunctionCallNcoll / OidFunctionCallNcoll families.
  3. The extension ABI layerPG_FUNCTION_INFO_V1, PG_MODULE_MAGIC, and the dfmgr.c library loader.

FmgrInfo is the result of resolving a function OID once per query (or plan):

// FmgrInfo — src/include/fmgr.h
typedef struct FmgrInfo
{
PGFunction fn_addr; /* pointer to function or handler to be called */
Oid fn_oid; /* OID of function (NOT of handler, if any) */
short fn_nargs; /* number of input args (0..FUNC_MAX_ARGS) */
bool fn_strict; /* function is "strict" (NULL in => NULL out) */
bool fn_retset; /* function returns a set */
unsigned char fn_stats; /* collect stats if track_functions > this */
void *fn_extra; /* extra space for use by handler */
MemoryContext fn_mcxt; /* memory context to store fn_extra in */
fmNodePtr fn_expr; /* expression parse tree for call, or NULL */
} FmgrInfo;

fn_addr is the only field the call layer uses at invocation time. For a built-in, it is the C function’s address directly. For a C extension, it is the dlopen-resolved symbol. For any procedural language (PL/pgSQL, PL/Python, …), it is the language’s call handler address — the handler then uses fn_oid to locate the actual function body. fn_extra is the handler’s per-call cache slot: the PL/pgSQL handler, for example, stores its compiled function parse tree there after the first call so subsequent calls skip re-parsing.

fmgr_info (the public entry point) delegates to fmgr_info_cxt_security, which is where the dispatch logic lives:

// fmgr_info_cxt_security — src/backend/utils/fmgr/fmgr.c
static void
fmgr_info_cxt_security(Oid functionId, FmgrInfo *finfo, MemoryContext mcxt,
bool ignore_security)
{
const FmgrBuiltin *fbp;
/* ... */
if ((fbp = fmgr_isbuiltin(functionId)) != NULL)
{
/* Fast path: built-in, skip pg_proc lookup */
finfo->fn_nargs = fbp->nargs;
finfo->fn_strict = fbp->strict;
finfo->fn_addr = fbp->func;
finfo->fn_oid = functionId;
return;
}
/* Otherwise look up pg_proc via syscache */
procedureTuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(functionId));
/* ... */
if (!ignore_security &&
(procedureStruct->prosecdef || ... || FmgrHookIsNeeded(functionId)))
{
finfo->fn_addr = fmgr_security_definer; /* wrap in security layer */
return;
}
switch (procedureStruct->prolang)
{
case INTERNALlanguageId: /* alias for a built-in */
fbp = fmgr_lookupByName(prosrc);
finfo->fn_addr = fbp->func;
break;
case ClanguageId:
fmgr_info_C_lang(functionId, finfo, procedureTuple);
break;
case SQLlanguageId:
finfo->fn_addr = fmgr_sql;
break;
default:
fmgr_info_other_lang(functionId, finfo, procedureTuple);
break;
}
finfo->fn_oid = functionId;
ReleaseSysCache(procedureTuple);
}

The dispatch has four leaves, each matching a prolang value:

prolang valuefn_addr set toNotes
built-in (fast path)fbp->func (no syscache hit needed)fmgr_isbuiltin uses fmgr_builtin_oid_index[] — O(1) array lookup
INTERNALlanguageIdfbp->func via fmgr_lookupByNameUser-created alias for a built-in; slower path
ClanguageIddlopen-resolved symbol via CFuncHashfmgr_info_C_lang handles dlopen + CFuncHash
SQLlanguageIdfmgr_sql (the SQL-function evaluator)Interprets SQL function body inline
anything elselanguage’s lanplcallfoidfmgr_info_other_lang looks up pg_language

The built-in fast path. fmgr_isbuiltin avoids a syscache lookup entirely:

// fmgr_isbuiltin — src/backend/utils/fmgr/fmgr.c
static const FmgrBuiltin *
fmgr_isbuiltin(Oid id)
{
uint16 index;
if (id > fmgr_last_builtin_oid)
return NULL;
index = fmgr_builtin_oid_index[id];
if (index == InvalidOidBuiltinMapping)
return NULL;
return &fmgr_builtins[index];
}

fmgr_builtins[] and fmgr_builtin_oid_index[] are code-generated arrays (generated from pg_proc.dat by the build system into fmgrtab.c). The index array maps OID → slot in O(1) with a simple array dereference, making the built-in path a handful of instructions with no locking.

The C-extension cache. fmgr_info_C_lang reads prosrc (symbol name) and probin (library file path) from pg_proc, calls load_external_function (which opens the .so via dfmgr.c and caches the dlopen handle), then calls fetch_finfo_record to verify the PG_FUNCTION_INFO_V1 descriptor, and finally stores the resolved address in CFuncHash keyed by (fn_oid, xmin, ctid). Staleness is detected by comparing the cached xmin/ctid against the current pg_proc tuple — if they differ the cached entry is replaced.

The security-definer interposer. When prosecdef is true, proconfig is set, or a plugin hook (fmgr_hook) is active, fn_addr is set to fmgr_security_definer instead of the actual function. At call time, fmgr_security_definer switches user ID and/or applies GUC overrides, then invokes the real function through a private cached FmgrInfo stored in fn_extra. This makes security-definer transparent to callers.

Layer 2 — the FunctionCallInfo call layer

Section titled “Layer 2 — the FunctionCallInfo call layer”

FunctionCallInfoBaseData (typedef’d as FunctionCallInfo) is the per-call container passed to every function:

// FunctionCallInfoBaseData — src/include/fmgr.h
typedef struct FunctionCallInfoBaseData
{
FmgrInfo *flinfo; /* ptr to lookup info used for this call */
fmNodePtr context; /* pass info about context of call */
fmNodePtr resultinfo; /* pass or return extra info about result */
Oid fncollation; /* collation for function to use */
bool isnull; /* function must set true if result is NULL */
short nargs; /* # arguments actually passed */
NullableDatum args[]; /* flexible array of (Datum value, bool isnull) */
} FunctionCallInfoBaseData;

context is a Node * that carries call-context information: TriggerData for trigger functions, AggState / WindowAggState for aggregate/window functions, CallContext for stored procedures, and ErrorSaveContext for soft-error callers. The IsA(context, X) pattern lets a function detect its calling context. resultinfo carries ReturnSetInfo for set-returning functions.

Stack allocation with LOCAL_FCINFO. Since FunctionCallInfoBaseData has a flexible array member for arguments, callers use the LOCAL_FCINFO macro to allocate a correctly-sized struct on the stack:

// LOCAL_FCINFO — src/include/fmgr.h
#define LOCAL_FCINFO(name, nargs) \
union { \
FunctionCallInfoBaseData fcinfo; \
char fcinfo_data[SizeForFunctionCallInfo(nargs)]; \
} name##data; \
FunctionCallInfo name = &name##data.fcinfo

The union guarantees alignment. SizeForFunctionCallInfo(nargs) computes offsetof(args) + sizeof(NullableDatum) * nargs. Heap allocation uses palloc(SizeForFunctionCallInfo(nargs)).

Invocation macro. The actual call is a single indirect function call:

// FunctionCallInvoke — src/include/fmgr.h
#define FunctionCallInvoke(fcinfo) ((* (fcinfo)->flinfo->fn_addr) (fcinfo))

Three call-site patterns. Callers choose one of three families depending on how much context they have:

// DirectFunctionCall1Coll — src/backend/utils/fmgr/fmgr.c
// For calling a known PGFunction pointer directly; no FmgrInfo needed.
Datum
DirectFunctionCall1Coll(PGFunction func, Oid collation, Datum arg1)
{
LOCAL_FCINFO(fcinfo, 1);
InitFunctionCallInfoData(*fcinfo, NULL, 1, collation, NULL, NULL);
fcinfo->args[0].value = arg1;
fcinfo->args[0].isnull = false;
result = (*func) (fcinfo);
if (fcinfo->isnull)
elog(ERROR, "function %p returned NULL", (void *) func);
return result;
}

The three families and their use cases:

FamilyWhen to useNotes
DirectFunctionCallNcollCaller holds PGFunction*; no NULL args/resultNo FmgrInfo; asserts non-NULL result
FunctionCallNcollCaller has FmgrInfo * (from prior fmgr_info)Tracks pgstat_function_usage; allows NULL
OidFunctionCallNcollCaller has only an OIDCalls fmgr_info + FunctionCallNcoll inline

Coding conventions for C functions. Every fmgr-callable C function has the signature Datum func(PG_FUNCTION_ARGS) where PG_FUNCTION_ARGS expands to FunctionCallInfo fcinfo. Arguments are fetched with type-specific macros and results are returned the same way:

// Example fmgr-callable function using V1 convention
Datum
int4add(PG_FUNCTION_ARGS)
{
int32 arg1 = PG_GETARG_INT32(0);
int32 arg2 = PG_GETARG_INT32(1);
PG_RETURN_INT32(arg1 + arg2);
}

PG_GETARG_INT32(n) expands to DatumGetInt32(fcinfo->args[n].value). PG_RETURN_INT32(x) expands to return Int32GetDatum(x). For varlena types like text, PG_GETARG_TEXT_PP(n) de-TOASTs the argument (fetching the decompressed, detoasted value) and PG_RETURN_TEXT_P(x) returns a pointer as Datum. A non-strict function checks PG_ARGISNULL(n) before fetching; strict functions never see NULL arguments because the caller’s null-check fires first.

PG_FUNCTION_INFO_V1. Every C extension function must declare its calling convention with the PG_FUNCTION_INFO_V1 macro:

// PG_FUNCTION_INFO_V1 — src/include/fmgr.h
#define PG_FUNCTION_INFO_V1(funcname) \
extern PGDLLEXPORT Datum funcname(PG_FUNCTION_ARGS); \
extern PGDLLEXPORT const Pg_finfo_record * CppConcat(pg_finfo_,funcname)(void); \
const Pg_finfo_record * \
CppConcat(pg_finfo_,funcname) (void) \
{ \
static const Pg_finfo_record my_finfo = { 1 }; \
return &my_finfo; \
} \
extern int no_such_variable

This generates a companion pg_finfo_<funcname> function that returns a Pg_finfo_record with api_version = 1. fetch_finfo_record looks up this companion symbol by name (psprintf("pg_finfo_%s", funcname)) and calls it to verify the version. If no such symbol is found, fmgr raises an error with the hint message “SQL-callable functions need an accompanying PG_FUNCTION_INFO_V1(funcname).” Version-0 (the “old style”) has been removed; V1 is the only supported convention.

PG_MODULE_MAGIC. Beyond per-function version checks, a shared library must pass a whole-module ABI check. The PG_MODULE_MAGIC macro (or the newer PG_MODULE_MAGIC_EXT(...)) emits a Pg_magic_struct under the well-known symbol Pg_magic_func:

// Pg_magic_struct and Pg_abi_values — src/include/fmgr.h
typedef struct
{
int version; /* PostgreSQL major version */
int funcmaxargs; /* FUNC_MAX_ARGS */
int indexmaxkeys; /* INDEX_MAX_KEYS */
int namedatalen; /* NAMEDATALEN */
int float8byval; /* FLOAT8PASSBYVAL */
char abi_extra[32]; /* see pg_config_manual.h */
} Pg_abi_values;

dfmgr.c’s load_external_function calls Pg_magic_func() on the freshly loaded library and compares the returned struct against the server’s own values field-by-field. Any mismatch triggers incompatible_module_error() with a message naming the mismatched field. This prevents obscure crashes from struct layout mismatches when, for example, a library was compiled with a different NAMEDATALEN.

PostgreSQL’s normal error path (ereport(ERROR, ...)) unwinds via longjmp and requires a full subtransaction cleanup — expensive for cases like datatype input functions that simply want to reject malformed input. As of PG14, a function can report a soft error instead:

// errsave / ereturn pattern — src/backend/utils/fmgr/README
// Instead of: ereport(ERROR, (errcode(...), errmsg(...)));
// Write: errsave(fcinfo->context, (errcode(...), errmsg(...)));
// Or combine: ereturn(fcinfo->context, (Datum) 0, (errcode(...), errmsg(...)));

If fcinfo->context is an ErrorSaveContext node, errsave stores the error information there and returns normally (the function then returns a dummy Datum). If context is NULL or any other node type, errsave behaves identically to ereport(ERROR). Callers that want to trap soft errors allocate an ErrorSaveContext, pass it as fcinfo->context, and inspect escontext.error_occurred after the call. The README explicitly restricts soft errors to recoverable conditions (invalid input syntax, out-of-range values); internal errors and OOM must still use the hard path.

A function marked proretset = true in pg_proc receives a ReturnSetInfo node in fcinfo->resultinfo. Two return modes are supported:

Value-per-call mode (iterator): the function is called repeatedly; it sets ReturnSetInfo.isDone to ExprMultipleResult for each row and ExprEndResult when done. The executor calls it in a loop. Value-per-call functions must not clean up resources in the final call because the executor may stop calling them early (e.g., due to LIMIT).

The funcapi.h macros encapsulate the protocol. The first-call detection is simply “is fn_extra still NULL?”, and the cross-call state lives in a FuncCallContext hung off fn_extra:

// SRF value-per-call macros — src/include/funcapi.h
#define SRF_IS_FIRSTCALL() (fcinfo->flinfo->fn_extra == NULL)
#define SRF_FIRSTCALL_INIT() init_MultiFuncCall(fcinfo)
#define SRF_PERCALL_SETUP() per_MultiFuncCall(fcinfo)
#define SRF_RETURN_NEXT(_funcctx, _result) \
do { \
ReturnSetInfo *rsi; \
(_funcctx)->call_cntr++; \
rsi = (ReturnSetInfo *) fcinfo->resultinfo; \
rsi->isDone = ExprMultipleResult; \
PG_RETURN_DATUM(_result); \
} while (0)
#define SRF_RETURN_DONE(_funcctx) \
do { \
ReturnSetInfo *rsi; \
end_MultiFuncCall(fcinfo, _funcctx); \
rsi = (ReturnSetInfo *) fcinfo->resultinfo; \
rsi->isDone = ExprEndResult; \
PG_RETURN_NULL(); \
} while (0)

init_MultiFuncCall runs exactly once: it asserts the function was called in a set-accepting context (IsA(fcinfo->resultinfo, ReturnSetInfo)), creates a dedicated AllocSetContextCreate child of fn_mcxt named “SRF multi-call context”, allocates a zeroed FuncCallContext there, stashes the pointer into fn_extra, and — crucially — registers a shutdown_MultiFuncCall callback via RegisterExprContextCallback so the cross-call context is freed even if the executor abandons the scan early (the LIMIT case):

// init_MultiFuncCall — src/backend/utils/fmgr/funcapi.c
FuncCallContext *
init_MultiFuncCall(PG_FUNCTION_ARGS)
{
FuncCallContext *retval;
if (fcinfo->resultinfo == NULL || !IsA(fcinfo->resultinfo, ReturnSetInfo))
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("set-valued function called in context that cannot accept a set")));
if (fcinfo->flinfo->fn_extra == NULL)
{
ReturnSetInfo *rsi = (ReturnSetInfo *) fcinfo->resultinfo;
MemoryContext multi_call_ctx;
multi_call_ctx = AllocSetContextCreate(fcinfo->flinfo->fn_mcxt,
"SRF multi-call context",
ALLOCSET_SMALL_SIZES);
retval = (FuncCallContext *)
MemoryContextAllocZero(multi_call_ctx, sizeof(FuncCallContext));
retval->multi_call_memory_ctx = multi_call_ctx;
fcinfo->flinfo->fn_extra = retval;
RegisterExprContextCallback(rsi->econtext, shutdown_MultiFuncCall,
PointerGetDatum(fcinfo->flinfo));
}
else
elog(ERROR, "init_MultiFuncCall cannot be called more than once");
return retval;
}

per_MultiFuncCall is a one-liner that just casts fn_extra back to FuncCallContext *. call_cntr (incremented by SRF_RETURN_NEXT) and the optional max_calls give the function its loop counter; user_fctx is the per-SRF scratch pointer for whatever cursor state the function needs. Because the shutdown callback owns teardown, the README’s warning holds: a value-per-call SRF must not assume SRF_RETURN_DONE will ever run, so any non-memory resource (file descriptor, open cursor) is unsafe in this mode — use materialize mode instead.

Materialize mode: the function creates a Tuplestore in econtext->ecxt_per_query_memory, fills it in one call, stores the pointer and a TupleDesc into ReturnSetInfo, and sets returnMode = SFRM_Materialize. The InitMaterializedSRF helper function (in funcapi.c) encapsulates the boilerplate:

// InitMaterializedSRF — src/backend/utils/fmgr/funcapi.c
void
InitMaterializedSRF(FunctionCallInfo fcinfo, bits32 flags)
{
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
/* sanity checks on rsinfo ... */
per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
old_context = MemoryContextSwitchTo(per_query_ctx);
tupstore = tuplestore_begin_heap(random_access, false, work_mem);
MemoryContextSwitchTo(old_context);
rsinfo->returnMode = SFRM_Materialize;
rsinfo->setResult = tupstore;
rsinfo->setDesc = stored_tupdesc;
}

funcapi.c also provides init_MultiFuncCall / per_MultiFuncCall / end_MultiFuncCall for value-per-call SRFs that need per-call state (stored in FuncCallContext, allocated in fn_mcxt).

The diagram below traces a single value end-to-end — from the pg_proc OID through the two-phase lookup/call split to a returned Datum — and then shows how a set-returning function reuses that same FunctionCallInvoke edge once per row via the fn_extra first-call latch.

flowchart TD
  OID["pg_proc OID<br/>(functionId)"]
  FI["fmgr_info / fmgr_info_cxt_security<br/>(lookup phase, once per query)"]
  FBP["fmgr_isbuiltin<br/>builtin? fn_addr = fbp->func"]
  CL["fmgr_info_C_lang<br/>CFuncHash / fetch_finfo_record"]
  OL["fmgr_info_other_lang / fmgr_sql<br/>fn_addr = call handler"]
  FINFO["FmgrInfo populated<br/>fn_addr, fn_nargs, fn_strict, fn_extra"]
  LF["LOCAL_FCINFO(fcinfo, nargs)<br/>+ InitFunctionCallInfoData<br/>+ fill args[].value / .isnull"]
  STRICT{"fn_strict &&<br/>any arg isnull?"}
  NULLOUT["result = NULL<br/>(skip the call)"]
  INV["FunctionCallInvoke(fcinfo)<br/>(* fn_addr)(fcinfo)"]
  BODY["function body<br/>Datum f(PG_FUNCTION_ARGS)<br/>PG_GETARG_* / PG_RETURN_*"]
  RET["result Datum + fcinfo->isnull"]

  OID --> FI
  FI -->|"builtin fast path"| FBP
  FI -->|"C language"| CL
  FI -->|"SQL / PL"| OL
  FBP --> FINFO
  CL --> FINFO
  OL --> FINFO
  FINFO --> LF
  LF --> STRICT
  STRICT -->|"yes"| NULLOUT
  STRICT -->|"no"| INV
  INV --> BODY
  BODY --> RET

  RET -->|"fn_retset SRF only"| FIRST{"SRF_IS_FIRSTCALL?<br/>fn_extra == NULL"}
  FIRST -->|"yes"| INIT["SRF_FIRSTCALL_INIT<br/>init_MultiFuncCall<br/>alloc FuncCallContext in fn_extra"]
  FIRST -->|"no"| PER["SRF_PERCALL_SETUP<br/>per_MultiFuncCall"]
  INIT --> PER
  PER --> MORE{"call_cntr <<br/>max_calls?"}
  MORE -->|"yes"| NEXT["SRF_RETURN_NEXT<br/>isDone = ExprMultipleResult<br/>executor re-invokes via fn_addr"]
  MORE -->|"no"| DONE["SRF_RETURN_DONE<br/>end_MultiFuncCall<br/>isDone = ExprEndResult"]
  NEXT -->|"loop"| INV

Figure 2 — fmgr call path and the SRF value-per-call protocol. The top half is the per-value path: fmgr_info populates an FmgrInfo once, then each call stack-allocates an fcinfo with LOCAL_FCINFO, applies the strict-null short-circuit, and dispatches through FunctionCallInvoke. The bottom half (the fn_retset branch) shows the value-per-call loop: the fn_extra == NULL latch distinguishes the first call (which allocates a FuncCallContext) from every subsequent call, and SRF_RETURN_NEXT re-enters the same FunctionCallInvoke edge until SRF_RETURN_DONE sets ExprEndResult.

flowchart TD
  EX["Executor<br/>(ExecMakeTableFunctionResult,<br/>ExprEvalStep, etc.)"]
  FI["fmgr_info<br/>(lookup phase)"]
  FCI["FunctionCallInvoke<br/>(call phase)"]
  BI["fmgr_isbuiltin<br/>O(1) OID index"]
  CH["CFuncHash<br/>(C extension cache)"]
  DL["dfmgr.c<br/>load_external_function<br/>+ fetch_finfo_record"]
  SD["fmgr_security_definer<br/>(interposer)"]
  PL["fmgr_info_other_lang<br/>→ lanplcallfoid"]
  SQL["fmgr_sql<br/>(SQL function evaluator)"]
  FN["fn_addr<br/>(PGFunction*)"]

  EX -->|"fmgr_info(oid, &flinfo)"| FI
  FI -->|"builtin OID"| BI
  FI -->|"C language"| CH
  CH -->|"cache miss"| DL
  FI -->|"security / hook"| SD
  FI -->|"SQL language"| SQL
  FI -->|"PL language"| PL
  BI --> FN
  CH --> FN
  SD --> FN
  SQL --> FN
  PL --> FN
  FN -->|"FunctionCallInvoke(fcinfo)"| FCI
  EX -->|"LOCAL_FCINFO + args"| FCI

Figure 1 — fmgr component flow. The executor drives two separate phases: the lookup phase (left) produces an FmgrInfo with fn_addr set; the call phase (right) passes FunctionCallInfo through fn_addr. CFuncHash short-circuits the dfmgr.c library load on repeat calls. fmgr_security_definer interposes as fn_addr when prosecdef, proconfig, or a plugin hook is active.

  • fmgr_info — public entry point; delegates to fmgr_info_cxt_security with CurrentMemoryContext and ignore_security = false.
  • fmgr_info_cxt — same but takes an explicit MemoryContext for subsidiary data.
  • fmgr_info_cxt_security — dispatch core: fast-path built-in check, syscache lookup, prolang switch.
  • fmgr_isbuiltin — O(1) array lookup in fmgr_builtin_oid_index; returns FmgrBuiltin * or NULL.
  • fmgr_lookupByName — linear scan of fmgr_builtins[] by name; used only for INTERNALlanguageId aliases.
  • fmgr_info_C_lang — C-extension path: lookup_C_func in CFuncHash, then load_external_function + fetch_finfo_record, then record_C_func.
  • fetch_finfo_record — locates pg_finfo_<name> symbol, calls it, validates api_version == 1.
  • lookup_C_func / record_C_funcCFuncHash get/set; staleness check via xmin + ctid.
  • fmgr_info_other_lang — PL handler path: resolves lanplcallfoid via fmgr_info_cxt_security with ignore_security = true.
  • fmgr_security_definer — interposer; caches real FmgrInfo + userid + GUC lists in fn_extra; restores after call.
  • fmgr_info_copy — shallow copy of FmgrInfo; zeroes fn_extra so handler state is not aliased.
  • fmgr_symbol — utility: given OID, returns (mod, fn) strings identifying the C symbol (used by pg_get_function_sqlbody and JIT).
  • fmgr_internal_function — reverse lookup: name → OID in fmgr_builtins[].
  • FunctionCallInvoke — macro; one indirect call through fn_addr.
  • InitFunctionCallInfoData — macro; fills all scalar fields of FunctionCallInfoBaseData except args[].
  • LOCAL_FCINFO — macro; stack-allocates a correctly-sized FunctionCallInfoBaseData.
  • DirectFunctionCallNColl (1–9) — call a known PGFunction pointer; no FmgrInfo; no NULL args/result allowed.
  • CallerFInfoFunctionCall1/2 — like DirectFunctionCall but takes an FmgrInfo * (useful when the caller has one and wants fn_extra to persist).
  • FunctionCallNColl (0–9) — call via FmgrInfo *; tracks pgstat_init_function_usage.
  • OidFunctionCallNColl (0–9) — call by OID; calls fmgr_info inline.
  • PG_FUNCTION_INFO_V1 — macro; generates pg_finfo_<name> symbol.
  • PG_MODULE_MAGIC / PG_MODULE_MAGIC_EXT — macro; generates Pg_magic_func symbol with Pg_magic_struct.
  • load_external_function (in dfmgr.c) — dlopen + dlsym with library cache; calls Pg_magic_func for ABI check.
  • fetch_finfo_record — locates + validates pg_finfo_<name>.
  • InitMaterializedSRF — sets up Tuplestore + TupleDesc in ReturnSetInfo for materialize-mode SRFs.
  • init_MultiFuncCall / per_MultiFuncCall / end_MultiFuncCall — value-per-call SRF state management via FuncCallContext.
  • get_call_result_type — resolves the actual return type of a polymorphic function from the call expression; used by SRFs to build their TupleDesc.

Position hints (as of 2026-06-05, commit 273fe94)

Section titled “Position hints (as of 2026-06-05, commit 273fe94)”
SymbolFileLine
FmgrInfosrc/include/fmgr.h56
FunctionCallInfoBaseDatasrc/include/fmgr.h85
PGFunction typedefsrc/include/fmgr.h40
LOCAL_FCINFOsrc/include/fmgr.h110
InitFunctionCallInfoDatasrc/include/fmgr.h150
FunctionCallInvokesrc/include/fmgr.h172
PG_FUNCTION_ARGSsrc/include/fmgr.h193
PG_ARGISNULLsrc/include/fmgr.h209
PG_RETURN_NULLsrc/include/fmgr.h345
PG_FUNCTION_INFO_V1src/include/fmgr.h415
Pg_finfo_recordsrc/include/fmgr.h394
Pg_abi_valuessrc/include/fmgr.h466
Pg_magic_structsrc/include/fmgr.h478
FmgrBuiltinsrc/include/utils/fmgrtab.h25
fmgr_isbuiltinsrc/backend/utils/fmgr/fmgr.c76
fmgr_lookupByNamesrc/backend/utils/fmgr/fmgr.c101
fmgr_infosrc/backend/utils/fmgr/fmgr.c127
fmgr_info_cxtsrc/backend/utils/fmgr/fmgr.c137
fmgr_info_cxt_securitysrc/backend/utils/fmgr/fmgr.c147
fmgr_symbolsrc/backend/utils/fmgr/fmgr.c281
fmgr_info_C_langsrc/backend/utils/fmgr/fmgr.c349
fmgr_info_other_langsrc/backend/utils/fmgr/fmgr.c418
fetch_finfo_recordsrc/backend/utils/fmgr/fmgr.c455
lookup_C_funcsrc/backend/utils/fmgr/fmgr.c515
record_C_funcsrc/backend/utils/fmgr/fmgr.c539
fmgr_info_copysrc/backend/utils/fmgr/fmgr.c580
fmgr_internal_functionsrc/backend/utils/fmgr/fmgr.c595
fmgr_security_definersrc/backend/utils/fmgr/fmgr.c632
DirectFunctionCall1Collsrc/backend/utils/fmgr/fmgr.c792
FunctionCall1Collsrc/backend/utils/fmgr/fmgr.c1129
OidFunctionCall1Collsrc/backend/utils/fmgr/fmgr.c1411
InitMaterializedSRFsrc/backend/utils/fmgr/funcapi.c76
init_MultiFuncCallsrc/backend/utils/fmgr/funcapi.c133
per_MultiFuncCallsrc/backend/utils/fmgr/funcapi.c208
end_MultiFuncCallsrc/backend/utils/fmgr/funcapi.c220
get_call_result_typesrc/backend/utils/fmgr/funcapi.c276
FuncCallContextsrc/include/funcapi.h57
SRF_IS_FIRSTCALLsrc/include/funcapi.h305
SRF_FIRSTCALL_INITsrc/include/funcapi.h307
SRF_PERCALL_SETUPsrc/include/funcapi.h309
SRF_RETURN_NEXTsrc/include/funcapi.h311
SRF_RETURN_DONEsrc/include/funcapi.h329
  • fmgr_isbuiltin is an O(1) array lookup, not a binary search. Verified in fmgr.c:76–93. The function reads fmgr_builtin_oid_index[id] (a code-generated uint16 array) and returns &fmgr_builtins[index]. No iteration; the comment “fast lookup only possible if original oid still assigned” notes that reassigned OIDs (from CREATE FUNCTION ... INTERNAL) fall through to the fmgr_lookupByName linear scan.

  • CFuncHash checks staleness by xmin + ctid, not just OID. Verified in lookup_C_func (fmgr.c:515–533). The entry is accepted only if both fn_xmin == HeapTupleHeaderGetRawXmin(t_data) and ItemPointerEquals(&fn_tid, &t_self). This catches pg_proc tuple updates (e.g., ALTER FUNCTION) without a session restart.

  • V0 calling convention has been fully removed. The README states “the V0 interface has been removed”. Verified: the api_version switch in fmgr_info_C_lang (fmgr.c:399–410) has only case 1 — any other value falls to elog(ERROR, "unrecognized function API version: %d"). There is no case 0 branch.

  • fmgr_security_definer is transparent to the outer fcinfo. Verified in fmgr.c:738–777. The function saves fcinfo->flinfo, swaps in the cached inner FmgrInfo, calls via FunctionCallInvoke, then restores fcinfo->flinfo in both the success and PG_CATCH paths. The outer fcinfo (with all the caller’s arguments) is passed unchanged; only flinfo is swapped for the duration.

  • PG_MODULE_MAGIC_EXT is the PG18 addition; plain PG_MODULE_MAGIC is equivalent to PG_MODULE_MAGIC_EXT() with no named arguments. Verified in fmgr.h:478–495. Pg_magic_struct has name and version fields that remain zero (NULL) when the plain macro is used. These fields are informational only and are not compared during the ABI check.

  • Soft errors via errsave/ereturn require the caller to pass an ErrorSaveContext * as fcinfo->context. Verified in README §“Handling Soft Errors”. If context is NULL or not an ErrorSaveContext, errsave calls ereport(ERROR) — no behavioral change for callers that do not opt in.

  1. fmgr_hook / needs_fmgr_hook plugin API. Two global function pointers (fmgr_hook, needs_fmgr_hook) are declared in fmgr.c:39–40 but no in-tree caller sets them (they are extension points). The documentation for what a plugin must do when it sets these globals lives only in the header comment in fmgr.c. Investigation path: search src/include/fmgr.h for fmgr_hook_type and trace call sites; look for third-party extensions (e.g., pg_hint_plan) that use these hooks for examples.

  2. CallerFInfoFunctionCall1/2 use cases. These functions pass a caller- supplied FmgrInfo * to a known PGFunction; the comment says “these functions work like the DirectFunctionCall functions except that” the FmgrInfo is available. The only in-tree users are in array_map and a few other places in utils/adt/. Whether these have performance advantages over DirectFunctionCall in typical call sites is not documented; the distinction is subtle and the README does not discuss them.

  3. fn_expr thread. FmgrInfo.fn_expr is described as “expression parse tree for call, or NULL” and is “information about the arguments rather than the function.” How consistently this is set by the executor across different plan node types (scan nodes vs. projection vs. aggregate transition) is not verified here. Investigation path: grep for fmgr_info_set_expr in executor/.

Beyond PostgreSQL — Comparative Designs & Research Frontiers

Section titled “Beyond PostgreSQL — Comparative Designs & Research Frontiers”
  • Oracle’s call-spec and PL/SQL function manager. Oracle’s CALL_SPEC mechanism and the PL/SQL engine → native compilation pipeline address the same lookup-cost/type-safety tensions. Oracle’s DETERMINISTIC and RESULT_CACHE function attributes are analogs to PostgreSQL’s strict + provolatile flags; a side-by-side would clarify what PostgreSQL’s planner can and cannot cache.

  • SQL Server’s CLR integration and function ABI. SQL Server’s CLR integration uses .NET metadata for ABI verification (a richer version of PG_MODULE_MAGIC). The managed/unmanaged boundary is an interesting comparison point for PostgreSQL’s fmgr_security_definer pattern.

  • MySQL’s UDF ABI (xxx_init / xxx / xxx_deinit). MySQL’s three-function UDF API (init, main, deinit) maps directly to PostgreSQL’s fn_extra per-call cache slot — both solve the “parse once, execute many” problem. MySQL’s ABI has no module-magic analog; version mismatch causes crashes rather than load-time errors.

  • DuckDB’s function registration API. DuckDB’s ScalarFunction / TableFunction C++ API exposes the same two-phase pattern (register with metadata → invoke with DataChunk) but at a higher abstraction level with vectorized chunks rather than scalar Datum. A comparison with PostgreSQL’s SRF materialize mode would illuminate the per-tuple vs. batch-vectorized trade-off. Relevant to PostgreSQL’s own vectorization research tracks.

  • The “INIT function” approach in extension ABI design. PostgreSQL’s _PG_init hook (declared PGDLLEXPORT in fmgr.h:434) is called once per library load and is the canonical place to register background workers, hooks, and custom lock managers. The pattern is similar to Linux’s module_init / module_exit. A comparison with alternative designs (e.g., DuckDB’s LoadInternal or SQLite’s sqlite3_auto_extension) would be useful for the postgres-extensions.md doc.

  • src/backend/utils/fmgr/README — primary design document for the V1 FCI; the authoritative description of FmgrInfo, FunctionCallInfo, call contexts, TOAST handling, SRF modes, soft errors, and function handler notes.
  • Database System Concepts (Silberschatz, Korth & Sudarshan, 7e) — §9.5 “Accessing SQL from a Programming Language” and §27 “PostgreSQL” for UDF integration context.
  • src/backend/utils/fmgr/fmgr.c — lookup pipeline, call helpers, CFuncHash, fmgr_security_definer.
  • src/backend/utils/fmgr/funcapi.c — SRF support (InitMaterializedSRF, MultiFuncCall, get_call_result_type).
  • src/backend/utils/fmgr/dfmgr.c — dynamic library loader (dlopen cache, load_external_function, Pg_magic_func ABI check).
  • src/include/fmgr.h — all public types, macros, and function declarations for fmgr users.
  • src/include/utils/fmgrtab.hFmgrBuiltin, fmgr_builtins[], fmgr_builtin_oid_index[].
  • src/include/funcapi.hReturnSetInfo, FuncCallContext, TypeFuncClass, SRF helper declarations.