Macros in the C API will be converted to static inline functions orregular functions. This will help avoid macro pitfalls in C/C++, andmake the functions usable from other programming languages.
To avoid compiler warnings, function arguments of pointer typeswill be cast to appropriate types using additional macros.The cast will not be done in the limited C API version 3.11:users who opt in to the new limited API may need to add casts tothe exact expected type.
To avoid introducing incompatible changes, macros which can be used asl-value in an assignment will not be converted.
The use of macros may have unintended adverse effects that are hard toavoid, even for experienced C developers. Some issues have been knownfor years, while others have been discovered recently in Python.Working around macro pitfalls makes the macro code harder to read andto maintain.
Converting macros to functions has multiple advantages:
Functions don’t need the following workarounds for macropitfalls, making them usually easier to read and to maintain thansimilar macro code:
do{...}while(0) to write multiple statements.Converting macros and static inline functions to regular functions makesthese regular functions accessible to projects which use Python butcannot use macros and static inline functions.
Most macros will be converted to static inline functions.
The following macros will not be converted:
#definePy_HAVE_CONDVAR.#defineMETH_VARARGS0x0001.Py_GCC_ATTRIBUTE(),Py_ALWAYS_INLINE,Py_MEMCPY().PyAPI_FUNC,Py_DEPRECATED,Py_PYTHON_H.Py_STRINGIFY().Py_BEGIN_ALLOW_THREADS (contains an unpaired}),Py_VISIT(relies on specific variable names), Py_RETURN_RICHCOMPARE (returnsfrom the calling function).PyBytes_AS_STRING().Static inline functions in the public C API may be converted to regularfunctions, but only if there is no measurable performance impact ofchanging the function.The performance impact should be measured with benchmarks.
Currently, most macros accepting pointers cast pointer arguments totheir expected types. For example, in Python 3.6, thePy_TYPE()macro casts its argument toPyObject*:
#define Py_TYPE(ob) (((PyObject*)(ob))->ob_type)ThePy_TYPE() macro accepts thePyObject* type, but also anypointer types, such asPyLongObject* andPyDictObject*.
Functions are strongly typed, and can only accept one type of argument.
To avoid compiler errors and warnings in existing code, when a macro isconverted to a function and the macro casts at least one of its argumentsa new macro will be added to keep the cast. The new macroand the function will have the same name.
Example with thePy_TYPE()macro converted to a static inline function:
staticinlinePyTypeObject*Py_TYPE(PyObject*ob){returnob->ob_type;}#define Py_TYPE(ob) Py_TYPE((PyObject*)(ob))
The cast is kept for all pointer types, not onlyPyObject*.This includes casts tovoid*: removing a cast tovoid* would emita new warning if the function is called with aconstvoid* variable.For example, thePyUnicode_WRITE() macro casts itsdata argument tovoid*, and so it currently acceptsconstvoid* type, even thoughit writes intodata. This PEP will not change this.
The casts will be excluded from the limited C API version 3.11 and newer.When an API user opts into the new limited API, they must pass the expectedtype or perform the cast.
As an example,Py_TYPE() will be defined like this:
staticinlinePyTypeObject*Py_TYPE(PyObject*ob){returnob->ob_type;}#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 < 0x030b0000# define Py_TYPE(ob) Py_TYPE((PyObject*)(ob))#endif
When a macro is converted to a function, its return type must not changeto prevent emitting new compiler warnings.
For example, Python 3.7 changed the return type ofPyUnicode_AsUTF8()fromchar* toconstchar* (commit).The change emitted new compiler warnings when building C extensionsexpectingchar*. This PEP doesn’t change the return type to preventthis issue.
The PEP is designed to avoid C API incompatible changes.
Only C extensions explicitly targeting the limited C API version 3.11must now pass the expected types to functions: pointer arguments are nolonger cast to the expected types.
Function arguments of pointer types are still cast and return types arenot changed to prevent emitting new compiler warnings.
Macros which can be used as l-value in an assignment are not modified bythis PEP to avoid incompatible changes.
Macros:
#define PySet_Check(ob) \ (Py_IS_TYPE(ob, &PySet_Type) \ || PyType_IsSubtype(Py_TYPE(ob), &PySet_Type))#define Py_IS_NAN(X) ((X) != (X))
If theop or theX argument has a side effect, the side effect isduplicated: it executed twice byPySet_Check() andPy_IS_NAN().
For example, thepos++ argument in thePyUnicode_WRITE(kind,data,pos++,ch) code has a side effect.This code is safe because thePyUnicode_WRITE() macro only uses its3rd argument once and so does not duplicatepos++ side effect.
Example of thebpo-43181: Python macros don’t shield arguments. ThePyObject_TypeCheck()macro before it has been fixed:
#define PyObject_TypeCheck(ob, tp) \ (Py_IS_TYPE(ob, tp) || PyType_IsSubtype(Py_TYPE(ob), (tp)))
C++ usage example:
PyObject_TypeCheck(ob,U(f<a,b>(c)))
The preprocessor first expands it:
(Py_IS_TYPE(ob,f<a,b>(c))||...)
C++"<" and">" characters are not treated as brackets by thepreprocessor, so thePy_IS_TYPE() macro is invoked with 3 arguments:
obf<ab>(c)The compilation fails with an error onPy_IS_TYPE() which only takes2 arguments.
The bug is that theop andtp arguments ofPyObject_TypeCheck()must be put between parentheses: replacePy_IS_TYPE(ob,tp) withPy_IS_TYPE((ob),(tp)). In regular C code, these parentheses areredundant, can be seen as a bug, and so are often forgotten when writingmacros.
To avoid Macro Pitfalls, thePyObject_TypeCheck() macro has beenconverted to a static inline function:commit.
Example showing the usage of commas in a macro which has a return value.
Python 3.7 macro:
#define PyObject_INIT(op, typeobj) \ ( Py_TYPE(op) = (typeobj), _Py_NewReference((PyObject *)(op)), (op) )
Python 3.8 function (simplified code):
staticinlinePyObject*_PyObject_INIT(PyObject*op,PyTypeObject*typeobj){Py_TYPE(op)=typeobj;_Py_NewReference(op);returnop;}#define PyObject_INIT(op, typeobj) \ _PyObject_INIT(_PyObject_CAST(op), (typeobj))
"\"."returnop;" rather than the surprising",(op)" syntax at the end of the macro.PyObject* and so doesn’t need casts like(PyObject*)(op).typeobj,rather than(typeobj).Example showing the usage of an#ifdef inside a macro.
Python 3.7 macro (simplified code):
#ifdef COUNT_ALLOCS# define _Py_INC_TPALLOCS(OP) inc_count(Py_TYPE(OP))# define _Py_COUNT_ALLOCS_COMMA ,#else# define _Py_INC_TPALLOCS(OP)# define _Py_COUNT_ALLOCS_COMMA#endif/* COUNT_ALLOCS */#define _Py_NewReference(op) ( \ _Py_INC_TPALLOCS(op) _Py_COUNT_ALLOCS_COMMA \ Py_REFCNT(op) = 1)
Python 3.8 function (simplified code):
staticinlinevoid_Py_NewReference(PyObject*op){_Py_INC_TPALLOCS(op);Py_REFCNT(op)=1;}
This macro reuses arguments, and possibly callsPyUnicode_KIND multipletimes:
#define PyUnicode_READ_CHAR(unicode, index) \(assert(PyUnicode_Check(unicode)), \ assert(PyUnicode_IS_READY(unicode)), \ (Py_UCS4) \ (PyUnicode_KIND((unicode)) == PyUnicode_1BYTE_KIND ? \ ((const Py_UCS1 *)(PyUnicode_DATA((unicode))))[(index)] : \ (PyUnicode_KIND((unicode)) == PyUnicode_2BYTE_KIND ? \ ((const Py_UCS2 *)(PyUnicode_DATA((unicode))))[(index)] : \ ((const Py_UCS4 *)(PyUnicode_DATA((unicode))))[(index)] \ ) \ ))
Possible implementation as a static inlined function:
staticinlinePy_UCS4PyUnicode_READ_CHAR(PyObject*unicode,Py_ssize_tindex){assert(PyUnicode_Check(unicode));assert(PyUnicode_IS_READY(unicode));switch(PyUnicode_KIND(unicode)){casePyUnicode_1BYTE_KIND:return(Py_UCS4)((constPy_UCS1*)(PyUnicode_DATA(unicode)))[index];casePyUnicode_2BYTE_KIND:return(Py_UCS4)((constPy_UCS2*)(PyUnicode_DATA(unicode)))[index];casePyUnicode_4BYTE_KIND:default:return(Py_UCS4)((constPy_UCS4*)(PyUnicode_DATA(unicode)))[index];}}
This is a list of macros already converted to functions betweenPython 3.8 and Python 3.11.Even though some converted macros (likePy_INCREF()) are verycommonly used by C extensions, these conversions did not significantlyimpact Python performance and most of them didn’t break backwardcompatibility.
Python 3.8:
Py_DECREF()Py_INCREF()Py_XDECREF()Py_XINCREF()PyObject_INIT()PyObject_INIT_VAR()_PyObject_GC_UNTRACK()_Py_Dealloc()Python 3.9:
PyIndex_Check()PyObject_CheckBuffer()PyObject_GET_WEAKREFS_LISTPTR()PyObject_IS_GC()PyObject_NEW(): alias toPyObject_New()PyObject_NEW_VAR(): alias toPyObjectVar_New()To avoid performance slowdown on Python built without LTO,private static inline functions have been added to the internal C API:
_PyIndex_Check()_PyObject_IS_GC()_PyType_HasFeature()_PyType_IS_GC()Python 3.11:
PyObject_CallOneArg()PyObject_Vectorcall()PyVectorcall_Function()_PyObject_FastCall()To avoid performance slowdown on Python built without LTO, aprivate static inline function has been added to the internal C API:
_PyVectorcall_FunctionInline()While other converted macros didn’t break the backward compatibility,there is an exception.
The 3 macrosPy_REFCNT(),Py_TYPE() andPy_SIZE() have beenconverted to static inline functions in Python 3.10 and 3.11 to disallowusing them as l-value in assignment. It is an incompatible change madeon purpose: seebpo-39573 forthe rationale.
This PEP does not propose converting macros which can be used as l-valueto avoid introducing new incompatible changes.
There have been concerns that converting macros to functions can degradeperformance.
This section explains performance concerns and shows benchmark resultsusingPR 29728, whichreplaces the following static inline functions with macros:
PyObject_TypeCheck()PyType_Check(),PyType_CheckExact()PyType_HasFeature()PyVectorcall_NARGS()Py_DECREF(),Py_XDECREF()Py_INCREF(),Py_XINCREF()Py_IS_TYPE()Py_NewRef()Py_REFCNT(),Py_TYPE(),Py_SIZE()The benchmarks were run on Fedora 35 (Linux) with GCC 11 on a laptop with 8logical CPUs (4 physical CPU cores).
First of all, converting macros tostatic inline functions hasnegligible impact on performance: the measured differences are consistentwith noise due to unrelated factors.
Static inline functions are a new feature in the C99 standard. Modern Ccompilers have efficient heuristics to decide if a function should beinlined or not.
When a C compiler decides to not inline, there is likely a good reason.For example, inlining would reuse a register which requires tosave/restore the register value on the stack and so increases the stackmemory usage, or be less efficient.
Benchmark of the./python-mtest-j5 command on Python built inrelease mode withgcc-O3, LTO and PGO:
There isno significant performance difference between macros andstatic inline functions when static inline functionsare inlined.
Performance in debug buildscan suffer when macros are converted tofunctions. This is compensated by better debuggability: debuggers canretrieve function names, set breakpoints inside functions, etc.
On Windows, when Python is built in debug mode by Visual Studio, staticinline functions are not inlined.
On other platforms,./configure--with-pydebug uses the-Og compileroption on compilers that support it (including GCC and LLVM Clang).-Og means “optimize debugging experience”.Otherwise, the-O0 compiler option is used.-O0 means “disable most optimizations”.
With GCC 11,gcc-Og can inline static inline functions, whereasgcc-O0 does not inline static inline functions.
Benchmark of the./python-mtest-j10 command on Python built indebug mode withgcc-O0 (that is, compiler optimizations,including inlining, are explicitly disabled):
Replacing macros with static inline functions makes Python1.04x slower when the compilerdoes not inline static inlinefunctions.
Note that benchmarks should not be run on a Python debug build.Moreover, using link-time optimization (LTO) and profile-guided optimization(PGO) is recommended for best performance and reliable benchmarks.PGO helps the compiler to decide if functions should be inlined or not.
ThePy_ALWAYS_INLINE macro can be used to force inlining. This macrouses__attribute__((always_inline)) with GCC and Clang, and__forceinline with MSC.
Previous attempts to usePy_ALWAYS_INLINE didn’t show any benefit, and wereabandoned. See for examplebpo-45094“Consider using__forceinline and__attribute__((always_inline)) onstatic inline functions (Py_INCREF,Py_TYPE) for debug build”.
When thePy_INCREF() macro was converted to a static inlinefunction in 2018 (commit),it was decided not to force inlining. The machine code was analyzed withmultiple C compilers and compiler options, andPy_INCREF() was alwaysinlined without having to force inlining. The only case where it was notinlined was the debug build. See discussion inbpo-35059 “ConvertPy_INCREF() andPyObject_INIT() to inlined functions”.
On the other side, thePy_NO_INLINE macro can be used to disableinlining. It can be used to reduce the stack memory usage, or to preventinlining on LTO+PGO builds, which generally inline code more aggressively:seebpo-33720. ThePy_NO_INLINE macro uses__attribute__((noinline)) with GCC andClang, and__declspec(noinline) with MSC.
This technique is available, though we currently don’t know a concretefunction for which it would be useful.Note that with macros, it is not possible to disable inlining at all.
Macros are always “inlined” with any C compiler.
The duplication of side effects can be worked around in the caller ofthe macro.
People using macros should be considered “consenting adults”. People whofeel unsafe with macros should simply not use them.
These ideas are rejected because macrosare error prone, and it is too easyto miss a macro pitfall when writing and reviewing macro code. Moreover, macrosare harder to read and maintain than functions.
python-dev mailing list threads:
This document is placed in the public domain or under theCC0-1.0-Universal license, whichever is more permissive.
Source:https://github.com/python/peps/blob/main/peps/pep-0670.rst
Last modified:2025-02-01 08:55:40 GMT