-
-
Notifications
You must be signed in to change notification settings - Fork 31.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
gh-105387: Limited C API implements Py_INCREF() as func #105388
Conversation
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
bdd5865
Compare
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). |
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. |
This change was written to be backported to Python 3.12: see the issue for the discussion on the version number. |
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 |
Limited C API: Python 3.11 (inline)You can look at xxlimited ( Preprocessor output ( static inline __attribute__((always_inline)) void Py_INCREF(PyObject *op)
{
uint32_t cur_refcnt = op->ob_refcnt_split[0];
uint32_t new_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 ( static inline __attribute__((always_inline)) void Py_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 ( static inline __attribute__((always_inline)) void Py_INCREF(PyObject *op)
{
uint32_t cur_refcnt = op->ob_refcnt_split[0];
uint32_t new_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 |
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 behavior as 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. |
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 <[email protected]>
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 <[email protected]>
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.