Include/internal/pycore_function.h
cpython 3.14 @ ab2d84fe1023/Include/internal/pycore_function.h
The private extension of PyFunctionObject. The public
Include/cpython/funcobject.h exposes the struct layout for
Py_LIMITED_API users; this header adds the interpreter-private
details: the func_version monotone counter that the specializing
interpreter uses to guard call-site caches, the PyFunction_Event
enum and callback mechanism for sys.monitoring, the
_PyFunction_FromConstructor constructor shortcut, and the
_Py_FUNC_DISABLE_INLINING hint that prevents the tier-2 optimizer
from inlining a specific function's bytecode.
Map
| Lines | Symbol | Role | gopy |
|---|---|---|---|
| 1-60 | FUNC_VERSION_* constants, _PyFunction_SetVersion, _PyFunction_GetVersionForCurrentState | A 32-bit monotone counter per function; stamped into call-site inline caches so the specialized CALL_PY_EXACT_ARGS instruction can validate without inspecting the full function. | objects/function.go |
| 60-120 | PyFunction_Event, PyFunction_WatchCallback, PyFunction_AddWatcher, PyFunction_ClearWatcher | Event dispatch layer: watchers registered via sys.monitoring or C extension are called when a function is created, modified, or destroyed. | objects/function.go |
| 120-160 | _PyFunction_FromConstructor, _Py_FUNC_DISABLE_INLINING | Constructor shortcut for type.__new__ that avoids the slower PyFunction_NewWithQualName path; and a compiler hint that suppresses tier-2 inlining for specific C-level helpers. | objects/function.go |
Reading
func_version counter (lines 1 to 60)
cpython 3.14 @ ab2d84fe1023/Include/internal/pycore_function.h#L1-60
/* Upper limit: when the counter wraps here, all cached versions are
invalidated by setting every function's func_version to 0. */
#define FUNC_VERSION_MAX UINT32_MAX
#define FUNC_VERSION_UNSET 0
/* Atomically increment the interpreter-global function version
counter and assign the result to func->func_version. */
extern void _PyFunction_SetVersion(PyFunctionObject *func, uint32_t version);
/* Return the current func_version. The caller compares this against
a value stored in an inline cache; mismatch triggers despecialization. */
static inline uint32_t
_PyFunction_GetVersionForCurrentState(PyFunctionObject *func) {
return func->func_version;
}
func_version is a 32-bit integer that serves as a guard tag for
call-site inline caches. When the specializing interpreter rewrites a
CALL instruction to CALL_PY_EXACT_ARGS, it records the current
func_version of the callee in the instruction's inline cache words.
On each subsequent call the specialized instruction reads the cache and
compares it against the live func->func_version. If they match, the
call proceeds with no further checks. If they differ, the instruction
despecializes back to the generic CALL and the slow path runs.
_PyFunction_SetVersion is called only from Objects/funcobject.c
whenever a mutable attribute changes: __code__, __defaults__,
__kwdefaults__, __closure__, or __annotations__. The new version
value comes from an interpreter-global monotone counter that starts at
1 and never resets (wrapping to 0 effectively invalidates every cache).
Setting func_version = 0 is the explicit invalidation signal: since
FUNC_VERSION_UNSET = 0, any cache that was stamped with a non-zero
version will fail the comparison.
The version counter is per-interpreter, not per-thread, so all threads sharing an interpreter state see a consistent versioning of all functions. In the free-threaded build the global counter is incremented with an atomic fetch-and-add.
In gopy, objects/function.go holds Version uint32 on the Function
struct. Every setter (SetCode, SetDefaults, SetKwDefaults,
SetClosure) zeros it, matching the CPython invalidation contract.
GetVersionForCurrentState is ported as a direct one-line method.
gopy does not yet run the tier-1 specializer, so no inline cache
actually reads the version; the field tracks the same invariants so
the specializer can be wired in later.
Specialization invalidation pattern
A changed __defaults__ invalidates every call site that was
specialized against that function. The sequence in CPython is:
func_set_defaults(inObjects/funcobject.c) stores the new tuple, then calls_PyFunction_SetVersion(func, 0)._PyFunction_SetVersionwritesfunc->func_version = version.- On the next call, the specialized instruction reads the cached
version from its inline cache words, finds
0 != cached_version, and falls back to the generic handler, which re-specializes (or leaves it generic if the shape keeps changing).
The pattern ensures that no lock is needed: the version write is
visible to any thread that subsequently reads the cache, because the
specialized instruction reads func_version after the function pointer
comparison, and the function pointer cannot change without going
through func_set_code (which also zeroes the version).
PyFunction_Event and watchers (lines 60 to 120)
cpython 3.14 @ ab2d84fe1023/Include/internal/pycore_function.h#L60-120
typedef enum {
PyFunction_EVENT_CREATE,
PyFunction_EVENT_DESTROY,
PyFunction_EVENT_MODIFY_CODE,
PyFunction_EVENT_MODIFY_DEFAULTS,
PyFunction_EVENT_MODIFY_KWDEFAULTS,
} PyFunction_Event;
typedef int (*PyFunction_WatchCallback)(
PyFunction_Event event,
PyFunctionObject *func,
PyObject *new_value);
PyAPI_FUNC(int) PyFunction_AddWatcher(PyFunction_WatchCallback callback);
PyAPI_FUNC(int) PyFunction_ClearWatcher(int watcher_id);
The watcher mechanism lets C extensions and sys.monitoring
observe function lifecycle events without patching CPython's internal
call paths. Up to 8 watchers can be registered simultaneously. When
any watched event fires, CPython iterates the registered callbacks and
calls each with the event type, the function object, and (for
MODIFY_* events) the incoming new value before it is installed.
PyFunction_EVENT_CREATE fires at the end of
PyFunction_NewWithQualName, after all fields are initialized but
before the function is returned to the caller. A watcher that stamps
the version for the first time does so here.
PyFunction_EVENT_DESTROY fires at the start of func_dealloc, while
the function object is still fully valid. After the callbacks return,
func_version is zeroed to prevent stale cache hits if the memory is
reused for another function that happens to land at the same address.
MODIFY_CODE, MODIFY_DEFAULTS, and MODIFY_KWDEFAULTS fire after
the validation checks in the setter but before the new value is
stored. new_value is the incoming object; if the callback returns
nonzero, the setter aborts and raises the exception the callback set.
In gopy, objects/function.go does not implement the watcher registry.
The mechanism is needed only when sys.monitoring is active or when
a C extension explicitly calls PyFunction_AddWatcher; gopy defers
both until the monitoring subsystem is ported.
_PyFunction_FromConstructor and _Py_FUNC_DISABLE_INLINING (lines 120 to 160)
cpython 3.14 @ ab2d84fe1023/Include/internal/pycore_function.h#L120-160
/* Faster constructor used by type_new_impl to avoid keyword-argument
parsing overhead when building a function for __init__ or __new__. */
extern PyObject *_PyFunction_FromConstructor(PyFrameConstructor *constr);
/* Suppress tier-2 inlining for a specific function. Used on C-level
helpers that are too large or too polymorphic to inline profitably. */
#define _Py_FUNC_DISABLE_INLINING(func) \
do { (func)->func_version = FUNC_VERSION_MAX; } while (0)
_PyFunction_FromConstructor is the fast path that type.__new__
takes when building the implicit __init__ and __new__ wrappers for
a new class. It accepts a PyFrameConstructor (a pre-filled struct of
the code object, globals, and builtins pointers) and avoids the full
keyword-argument dispatch that PyFunction_NewWithQualName handles.
_Py_FUNC_DISABLE_INLINING sets func_version = FUNC_VERSION_MAX
(i.e. UINT32_MAX). The tier-2 optimizer reserves version UINT32_MAX
as a sentinel meaning "never inline this function". By convention, any
function whose version reaches UINT32_MAX is skipped by the inliner
regardless of its call frequency. The macro is applied to a small set
of C-level built-ins (like functools.reduce and itertools.chain)
that the optimizer is not equipped to inline.
In gopy, _PyFunction_FromConstructor is not yet ported; class
construction goes through the general NewFunctionWithQualName path.
_Py_FUNC_DISABLE_INLINING has no counterpart because gopy does not
yet run the tier-2 optimizer.
gopy mirror
The central field is Version uint32 on objects.Function. All
setters that CPython invalidates by zeroing func_version do the same
in gopy: SetCode, SetDefaults, SetKwDefaults, and SetClosure
each assign f.Version = 0 before returning. The
GetVersionForCurrentState method is a direct port.
The event watcher subsystem (PyFunction_Event, PyFunction_AddWatcher,
PyFunction_ClearWatcher) is not yet implemented. When sys.monitoring
is ported, the watcher registry will live alongside the function type
initialization in objects/function.go, mirroring the C layout where
the callback table is stored on the interpreter state struct.