Uh oh!
There was an error while loading. Please reload this page.
gh-105387: Limited C API implements Py_INCREF() as func - #105388
Conversation
vstinner
commented
Jun 6, 2023
In the limited C API version 3.12, Py_INCREF() and Py_DECREF() functions are now implemented as opaque function calls to hide implementation details.
cbde052 to
bdd5865Comparevstinner
commented
Jun 9, 2023
The PR is now ready for a review. I rebased the PR on the main branch. I fixed the compatibility with Python 3.9 and older in main (commit 7ba0fd9). |
vstinner
commented
Jun 9, 2023
In Python 3.12, Py_INCREF() and Py_DECREF() implementation become more complicated than There are C compiler issues with the current implementation: issue #105059. |
vstinner
commented
Jun 9, 2023
This change was written to be backported to Python 3.12: see the issue for the discussion on the version number. |
zooba
commented
Jun 13, 2023
What happens if I build with 3.12 but set Py_LIMITED_API to 3.11? or to just This means that, in practice, it's only safe to set If you can't set |
vstinner
commented
Jun 13, 2023
Limited C API: Python 3.11 (inline)You can look at xxlimited ( Preprocessor output ( staticinline __attribute__((always_inline)) voidPy_INCREF(PyObject*op)
{
uint32_tcur_refcnt=op->ob_refcnt_split[0];
uint32_tnew_refcnt=cur_refcnt+1;
if (new_refcnt==0) {
return;
}
op->ob_refcnt_split[0] =new_refcnt;
((void)0);
}Extract of Xxo_getattro() x86-64 assembly code: => with the limited C API version 3.11, Py_INCREF() is inlined code (refcnt+1 and test for immutable refcnt) at the ABI level. Limited C API: Python 3.12 (function call)I modified xxlimited ( Preprocessor output ( staticinline __attribute__((always_inline)) voidPy_INCREF(PyObject*op)
{
_Py_IncRef(op);
}Extract of Xxo_getattro() x86-64 assembly code: => with the limited C API version 3.12, Py_INCREF() becomes a function call to _Py_IncRef() at the ABI level. Py_LIMITED_API=1 (inline)I modified Preprocessor output ( staticinline __attribute__((always_inline)) voidPy_INCREF(PyObject*op)
{
uint32_tcur_refcnt=op->ob_refcnt_split[0];
uint32_tnew_refcnt=cur_refcnt+1;
if (new_refcnt==0) {
return;
}
op->ob_refcnt_split[0] =new_refcnt;
((void)0);
}=> With
The trick is to convert It's a common pattern in the limited C API: look for |
vstinner
commented
Jun 13, 2023
If you build a C extension with Python 3.12 and target the limited C API 3.11, you get Python 3.12 Py_INCREF() inlined code (refcnt+1, test for immutable refcnt) which is backward compatible: the binary built with new Python 3.12 header files can be run on old Python 3.11 and older, see PEP 683: Stable ABI. If you consider that it's wrong, please revisit the whole PEP 683, but I don't see how this question is related to my change. Your question is about the code path unaffected by this PR.
You can set Py_LIMITED_API to an earlier version and the built binary works on old Python versions (it remains ABI compatible). Would you mind to elaborate why do you think that it does not work? |
If I understood correctly @zooba rationale, he wants to build a C extension with (the limited C API of) the new Python 3.(N+1), run it with an old Python 3.N and have the exact same behavioras if the C extension was built with an old Python 3.N. The current implementation of the limited C API DOES NOT provide such ABI warranty. There are many corner cases like the function being discussed here: Py_INCREF(). There are around 315 functions in Python 3.12 which are either implemented as a macro or a static inline function and so is likely to access structure members or relying on another implementation details: https://pythoncapi.readthedocs.io/stats.html#functions-defined-as-macros-and-static-inline-functions The intent of moving the limited C API towards "opaque function calls" is to build a C extension with Python 3.N and have exactly the Python 3.x behavior on Python 3.x since the executed code is no longer hardcoded in the extension binary, but instead we call call in the Python executable (libpython). For example, if this change lands in Python 3.12 beta3, a C extension is built with Python 3.12 beta3, but Python 3.12 beta4 changes again the Py_INCREF() implementation: the C extension will execute the exact new beta4 implementation, rather than using the copy of old beta3 implementation. Without opaque function calls, the promise of a "stable ABI" is weak. This change moves the limited C API / stable ABI one step towards a better (more stable) stable ABI. @zooba rationale is that the implementation of the limited C API and the stable ABI is broken is should be abandoned. I disagree here. IMO not only the current half-baken implementation is useful to many people, but also it is fixable in an increment way. Moreover, my long term goal is to bend the whole (regular) Python C API towards to limited C API / stable ABI. |
miss-islington
commented
Jun 14, 2023
Thanks @vstinner for the PR 🌮🎉.. I'm working now to backport this PR to: 3.12. |
…GH-105388) In the limited C API version 3.12, Py_INCREF() and Py_DECREF() functions are now implemented as opaque function calls to hide implementation details. (cherry picked from commit b542972) Co-authored-by: Victor Stinner <vstinner@python.org>
bedevere-bot
commented
Jun 14, 2023
GH-105763 is a backport of this pull request to the 3.12 branch. |
…5388) (#105763) gh-105387: Limited C API implements Py_INCREF() as func (GH-105388) In the limited C API version 3.12, Py_INCREF() and Py_DECREF() functions are now implemented as opaque function calls to hide implementation details. (cherry picked from commit b542972) Co-authored-by: Victor Stinner <vstinner@python.org>
In the limited C API version 3.12 and newer, Py_INCREF() and Py_DECREF() functions are now implemented as opaque function calls in the stable ABI to hide implementation details.