-
-
Notifications
You must be signed in to change notification settings - Fork 30.3k
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
Add a new Include/cpython/ subdirectory for the "CPython API" with implementation details #79315
Comments
The PEP-384 "Defining a Stable ABI" introduced Py_LIMITED_API define to exclude functions from the Python C API. The problem is when a new API is introduced, it has to explicitly be excluded using "#ifndef Py_LIMITED_API". If the author forgets it, the function is added to be stable API by mistake. I propose to move the API which should be excluded from the stable ABI to a new subdirectory: Include/pycapi/. To not break the backward compatibility, I propose to automatically include new header files from existing header files. For example, Include/pycapi/pyapi_objimpl.h would be automatically included by Include/pycapi/pycapi_objimpl.h. New header files would have a "pycapi_" prefix to avoid conflict Include/ header files, if Include/pycapi/ directory is in the header search paths. This change is a follow-up of bpo-35081 which moved Py_BUILD_CORE code to Include/internal/. It is also part of a larger project to cleanup the C API, see: The change is backward compatible: #include <Python.h> will still provide exactly the same API. |
There are not just two sides. It is common to wrap new stable C API with something like: #if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x03050000
What will you do with this? |
objimpl.h always includes pycapi/pycapi_objimpl.h, so I don't think that we need a strong rules. I propose to always add move code using "#if ... Py_LIMITED_API" to the pycapi/ subdirectory, even if it uses "#if !defined(Py_LIMITED_API)". |
Do you want to keep only stable ABI v.3.2 and move both newer stable API and non-stable API to the pycapi/ subdirectory? Sorry, I don't found a sense in this. |
The raw definition could be that Include/.h is part of the stable ABI, and Include/pycapi/.h are the definitions using Py_LIMITED_API and so can be stable or not stable depending on Py_LIMITED_API value :-) To be honest, I'm not sure that I understand how "Py_LIMITED_API+0 >= 0x03050000" works and should be used. I understand that you would prefer to leave PyObject_Calloc() in Include/objimpl.h. Honestly, I have no strong opinion on that. We can leave it there if you prefer. -- Maybe the rule "move everything using Py_LIMITED_API to pycapi" is misleading. My intent is that API in Include/*.h should not leak implementation details. It should be the starting point to design a new C API which does not leak any implementation detail: It's easier with an example: #define _PyObject_GC_TRACK(o) do { \
PyGC_Head *g = _Py_AS_GC(o); \
if (g->_gc_next != 0) { \
Py_FatalError("GC object already tracked"); \
} \
assert((g->_gc_prev & _PyGC_PREV_MASK_COLLECTING) == 0); \
... This macro is private: it starts with "_Py", so it doesn't belong to Include/*.h. Moreover, it access private fields like PyGC_Head._gc_prev. From my point of view, the ideal API would not access *any* structure field and PyGC_Header structure must not be used nor part of the C API. -- After saying that, I looked again at my PR, and I still see private functions in objimpl.h. Example: PyAPI_FUNC(PyObject *) _PyObject_New(PyTypeObject *);
PyAPI_FUNC(PyVarObject *) _PyObject_NewVar(PyTypeObject *, Py_ssize_t);
#define PyObject_New(type, typeobj) \
( (type *) _PyObject_New(typeobj) )
#define PyObject_NewVar(type, typeobj, n) \
( (type *) _PyObject_NewVar((typeobj), (n)) ) These functions are not excluded from Py_LIMITED_API. Since they are private, we are free to remove them whenever we want, so IMHO it's fine to exclude from Py_LIMITED_API right now if we want. Another example: static inline PyObject*
PyObject_INIT(PyObject *op, PyTypeObject *typeobj)
{
assert(op != NULL);
Py_TYPE(op) = typeobj;
_Py_NewReference(op);
return op;
} It's a public function but it calls the private function _Py_NewReference(). So _Py_NewReference() must be part of Py_LIMITED_API somehow... In release mode (if Py_TRACE_REFS is not defined), _Py_NewReference() is defined like that: /* Without Py_TRACE_REFS, there's little enough to do that we expand code
inline. */
static inline void _Py_NewReference(PyObject *op)
{
if (_Py_tracemalloc_config.tracing) {
_PyTraceMalloc_NewReference(op);
}
_Py_INC_TPALLOCS(op);
_Py_INC_REFTOTAL;
Py_REFCNT(op) = 1;
} It does access to the private _Py_tracemalloc_config variable and private macros/functions _Py_INC_TPALLOCS(op) and _Py_INC_REFTOTAL. We *can* always define _Py_NewReference() as a function call if Py_LIMITED_API is defined, but it would have an impact on performance. Right now, I don't want to risk to introduce a performance slowdown. I have a "Proof-of-concept" implementation of my proposed "new C API": My implementation currently uses 3 defines:
But this project is highly experimental and I don't want to make it upstream before we measured properly the impact on the performance, the API has been properly reviewed and discussed, and the overall project has been approved by core developers. For example, by writing a PEP :-) -- In short, I'm not sure of what can or should be done right now for Include/pycapi/ :-) I wrote the PR to open the discussion :-) |
It's described here: https://docs.python.org/3/c-api/stable.html If a stable ABI consumer just declares "#define PY_LIMITED_API 1", then they'll get the original stable ABI as defined in Python 3.2. If they don't care about versions prior to 3.6, they can instead declare "#define PY_LIMITED_API 0x03060000", and get access to the functions added to the stable ABI in 3.3, 3.4, 3.5, and 3.6. For this PR though, I think it's OK to ignore that detail, as once all the internal APIs are in "Include/internal", and all the APIs that don't offer ABI stability guarantees are in "Include/TBD" (see note below), then the general rule to follow is that everything added to the headers directly in "Include/" needs a Py_LIMITED_API guard that matches the upcoming release. Note: I wrote "TBD" rather than "pycapi" above, as "pycapi" sounds like the name of a preferred public API to me, rather than "code compiled against this API is not portable to later versions, and may not be portable to other implementations". Given the name of the macro, "Include/unlimited/.h" may make sense, especially if those header files are all written to error out at compile time if PY_LIMITED_API is defined. "Include/unstable_abi/.h" would be another self-describing name. |
On actually looking at the initial changes in the PR:
In your initial PR, the only API that subtle distinction affects is PyObject_Calloc (since that's a new addition to the stable ABI in 3.5+), and moving that back to the public header means you can add the desired "Py_LIMITED_API is not defined" check to the header in the new directory. |
I created a new PR 10624:
Include/unstable/ directory must not be added to the search paths for headers (gcc -I Include/unstable/). unstable/objimpl.h must not be included directly: it fails with a compiler error. |
I propose the following organization:
It should become easier to see what is exposed or not to the stable ABI just by looking at Include.*.h. It should also become easier to spot in a review when a pull request something to the stable ABI, whereas it should be added to the unstable or internal API. |
Just to avoid the risk of name conflict, would it make sense to rename "unstable" to "pyunstable" or something else with "py" inside? I'm not sure if #include "unstable/objimpl.h" first looks the same directory than the header file that does the include? Note: I tested "make install" and I get a /opt/py38/include/python3.8dm/unstable/ directory which contains a single file (yet): objimpl.h. |
I think the rules for C includes are that However, given your technique of mostly hiding the new directory name from API consumers, what do you think of calling the new directory "cpython" rather than "unstable"? The idea there would be that the "unstable ABI" eventually become known as "the CPython C API" (since it exposes a lot of CPython implementation details", while the limited API could become known as "the portable cross-implementation Python C API". (I know, I know, you were aiming to avoid further bikeshedding on the name, but "cpython" would namespace things nicely even if a compiler does something weird with header file lookups, and helps make it clearer to CPython contributors that we still need to care about public API stability in that directory, we just don't need to care about cross-implementation portability) |
Oh ok, thanks.
I'm not comfortable with "CPython" name. For me, everything the "CPython C API" is the concatenation of all files in Include/ but also in subdirectories. Right now, it's unclear what is the "Python" API ("portable" API, without implemenetation details) vs the "CPython API" (implementation details). "unstable" comes from the PEP-384: "Defining a Stable ABI". IMHO what is not in the "Stable ABI" is the "Unstable ABI". By extension, APIs excluded by Py_LIMITED_API make the "unstable API". From my point of view, "CPython API" would be more internal/ + unstable/ APIs.
Everybody seems to be confused by what is the "Python C API"... I see even more confusion if we have a "CPython C API". Do you see? "CPython" vs "Python", "Python C" vs "CPython"... IMHO "unstable" is more explicit :-) It means: "don't touch this" :-D |
The "unstable" name bugs me as it suggests we might change it without notice which isn't true at all. It's more a limited versus broad API. So maybe rename the directory "broad"? |
Brett:
Brett: Nick proposed "Include\cpython", do you prefer this name? |
Another proposal: Include\impl\ as in "implementation details". |
I created a poll on discuss.python.org for the name of the new subdirectory :-) |
As a heavy user of the non-limited Python C API, I would like to offer my suggestions for consideration. (I'm not allowed to post in discourse) First off, to me, 'unstable' comes off quite negative, i.e. risky or erratic. Brett's suggestion of 'broad' is, well, seemingly too "broad" :) In no particular order, some ideas:
|
Jeremy Kloth:
Ok, the 3rd people who dislike my "unstable" name, so it sounds really bad :-) Jeremy Kloth:
I don't think that it's true that the "#ifndef Py_LIMITED_API" is unstable or volatile. Most of this API didn't change much in the last 10 years. So sorry, "unstable" was really a bad name. Brett Cannon:
Jeremy Kloth:
The name by itself doesn't explain why an API should be in Include/ or Include/<name>/. What is extra or not? Jeremy Kloth:
Sorry, I dislike humor in an API. An API has to make sense :-( -- Ok, after I read all proposition, I now prefer "cpython". Extract of my updated PR which gives the rationale: Include/.h should be the "portable Python API", whereas It now makes sense to me what should go to Include/ and what should go to Include/cpython/. Obviously, Include/cpython/ is incomplete. It's only the public flavor of the "CPython API". There is also the private CPython internal API in Include/internal/. |
Nick Coghlan, Steve Dower and Paul Moore and me prefer "cpython" name, so let's go with that one! |
New changeset e421106 by Victor Stinner in branch 'master': |
Number lines containing Py_LIMITED_API in Include/ dir: 13:pyerrors.h |
New changeset 6eb9966 by Victor Stinner in branch 'master': |
New changeset 75e4699 by Victor Stinner in branch 'master': |
New changeset 5a8c240 by Victor Stinner in branch 'master': |
New changeset 4060283 by Victor Stinner in branch 'master': |
New changeset 366dc3a by Nicholas Sim in branch 'master': |
New changeset 4a6bf27 by Nicholas Sim in branch 'master': |
Include/README.rst and https://devguide.python.org/c-api/ now define guideliens for header files and the 3 APIs. I consider that this issue is now fixed. Even if there are still non-limited API declared in Include/*.h, changing that can be done in follow-up issues. |
Thanks, Victor! |
New changeset 0a883a7 by Victor Stinner in branch 'main': |
I reopen the issue since there is new activity on it :-) |
commit 37b1d60 (upstream/main, main)
|
New changeset 77b24ba by Victor Stinner in branch 'main': |
New changeset 8e5de40 by Victor Stinner in branch 'main': |
New changeset 5f09bb0 by Victor Stinner in branch 'main': |
New changeset d4a85f1 by Victor Stinner in branch 'main': |
New changeset ca219f6 by Victor Stinner in branch 'main': |
New changeset 5c4d1f6 by Victor Stinner in branch 'main': |
New changeset 85addfb by Victor Stinner in branch 'main': |
Move non-limited C API from Include/memoryobject.h to a new Include/cpython/memoryobject.h header file.
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
bugs.python.org fields:
Linked PRs
The text was updated successfully, but these errors were encountered: