Repository navigation
[C API] Prepare PyTypeObject structure for a stable ABI: avoid accessing members in the public API #84351
Copy link
Copy link
Closed
Labels
Description
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:
assignee = None
closed_at = <Date 2022-01-28.14:25:55.330>
created_at = <Date 2020-04-03.11:57:03.054>
labels = ['expert-C-API', '3.10']
title = '[C API] Prepare PyTypeObject structure for a stable ABI: avoid accessing members in the public API'
updated_at = <Date 2022-01-28.14:25:55.330>
user = 'https://github.com/vstinner'bugs.python.org fields:
activity = <Date 2022-01-28.14:25:55.330>
actor = 'vstinner'
assignee = 'none'
closed = True
closed_date = <Date 2022-01-28.14:25:55.330>
closer = 'vstinner'
components = ['C API']
creation = <Date 2020-04-03.11:57:03.054>
creator = 'vstinner'
dependencies = []
files = []
hgrepos = []
issue_num = 40170
keywords = ['patch']
message_count = 59.0
messages = ['365689', '365795', '365848', '365850', '365852', '365862', '365863', '365873', '365956', '365960', '365961', '365962', '365964', '365966', '366152', '366155', '366417', '366474', '366494', '366521', '366522', '366529', '372050', '372054', '372056', '373293', '373295', '373298', '373338', '373346', '377372', '380757', '381775', '385176', '387057', '387059', '387092', '387113', '387115', '387129', '387131', '387133', '387140', '387142', '387146', '387177', '405956', '405964', '405966', '411816', '411817', '411818', '411819', '411822', '411824', '411919', '411939', '411998', '411999']
nosy_count = 7.0
nosy_names = ['rhettinger', 'ronaldoussoren', 'vstinner', 'corona10', 'miss-islington', 'shihai1991', 'erlendaasland']
pr_nums = ['19375', '19376', '19377', '19378', '19379', '19426', '19428', '19464', '19541', '21390', '21391', '22375', '23153', '23235', '23236', '24535', '24548', '24553', '24555', '29470', '29471', '29472', '30938', '30940', '30942', '30943']
priority = 'normal'
resolution = 'fixed'
stage = 'resolved'
status = 'closed'
superseder = None
type = None
url = 'https://bugs.python.org/issue40170'
versions = ['Python 3.10']Reactions are currently unavailable
Activity
vstinner commentedon Apr 3, 2020
Leaking the PyTypeObject structure in the C API indirectly causes ABI issue (especially for statically allocated types), cause practical issues when old fields are removed and new fields are added (ex: tp_vectorcall addition and tp_print removal caused a lot of troubles with C code generated by Cython: see bpo-37250), prevents us to add feature and experiment optimization.
I don't expect that we will be able to make PyTypeObject opaque soon. The purpose of this issue is to track the work done towards this goal.
I propose to slowly prepare the Python code base, the C API and third party code (especially Cython) to make PyTypeObject structure opaque.
We have to identify most common code patterns which access directly PyTypeObject fields, provide helper functions, and ease the migration to solutions which don't access directly PyTypeObject.
See also bpo-39573: "Make PyObject an opaque structure in the limited C API" and bpo-39947 "Make the PyThreadState structure opaque (move it to the internal C API)".
Longer rationale about making structures of the C API opaque:
--
Multiple practical issues are preventing us to make PyTypeObject opaque right now.
(*) Many C extension modules are still using statically allocated types: there is an on-going effort in bpo-40077 to convert C extension modules one by one to PyType_FromSpec().
(*) Py_TYPE(obj)->tp_name is commonly accessed to format an error message. Example:
I worked on bpo-34595 and started a discussion on python-dev to propose to add a new %T formatter to PyUnicode_FromFormatV() and so indirectly to PyUnicode_FromFormat() and PyErr_Format():
https://mail.python.org/archives/list/python-dev@python.org/thread/HKYUMTVHNBVB5LJNRMZ7TPUQKGKAERCJ/#3UAMHYG6UF4MPLXBZORHO4JVKUBRUZ53
Sadly, we failed to reach a consensus and I gave up on this idea. We should reconsider this idea.
We need to agree on how types should be formatted:
There is also the question of breaking applications which rely on the current exact error message. And the question of removing legacy "%.100s" which was used before Python was able to allocate a buffer large enough to arbitrary string length. When an error is formatted in pure Python, names are never truncated.
(*) Call the function of the parent type when a method is overriden in a subclass. Example with PyTypeObject.tp_free called in a deallocator:
() The PEP-384 provides the most generic PyType_GetSlot() but it's not convenient to use: need to handle error (NULL), need to cast the void into the expected type (error prone cast), etc.
We should slowly add more and more helper functions for most common use cases. We can try to convert a few C extension modules of the Python stdlib to see which use cases are the most common.
Hopefully, many use cases are already abstracted by widely used functions like PyNumber_Add(), PySequence_Size(), etc.
(*) Likely other issues that I forgot.
vstinner commentedon Apr 4, 2020
Macros and static inline functions of the public C API which access directly PyTypeObject fields. There may be more.
static inline vectorcallfunc
PyVectorcall_Function(PyObject *callable)
{
...
tp = Py_TYPE(callable);
offset = tp->tp_vectorcall_offset;
...
}
vstinner commentedon Apr 6, 2020
New changeset 38aefc5 by Victor Stinner in branch 'master':
bpo-40170: PyObject_GET_WEAKREFS_LISTPTR() becomes a function (GH-19377)
38aefc5
aixtools commentedon Apr 6, 2020
Just manually verified that PR19377, when compiled against xlc - crashes during make:
Objects/genobject.c:127: _PyObject_GC_TRACK: Assertion "!(((PyGC_Head *)(op)-1)->_gc_next != 0)" failed: object already tracked by the garbage collector
Enable tracemalloc to get the memory block allocation traceback
object address : 30084150
object refcount : 0
object type : 20013aa8
object type name: generator
object repr : <refcnt 0 at 30084150>
Fatal Python error: _PyObject_AssertFailed: _PyObject_AssertFailed
Python runtime state: core initialized
Current thread 0x00000001 (most recent call first):
File "<frozen importlib._bootstrap_external>", line 1593 in _setup
File "<frozen importlib._bootstrap_external>", line 1634 in _install
File "<frozen importlib._bootstrap>", line 1189 in _install_external_importers
/bin/sh: 24117648 IOT/Abort trap(coredump)
make: 1254-004 The error code from the last command is 134.
Stop.
FYI: about two hours ago I verified that xlc and 08050e9 : bpo-40147: Fix a compiler warning on Windows in Python/compile.c (GH-19389)
all was green.
aixtools commentedon Apr 6, 2020
Just checked - seems to be SPECIFIC to xlc-v16 as neither xlv-v11 nor xlc-v13 have any issues building.
vstinner commentedon Apr 6, 2020
Py_TRASHCAN_BEGIN() access directly PyTypeObject.tp_dealloc:
It should use PyType_GetSlot() or a new getter function (to read PyTypeObject.tp_dealloc) should be added.
vstinner commentedon Apr 6, 2020
Oh. It seems like currently, PyType_GetSlot() can only be used on a heap allocated types :-( The function starts with:
vstinner commentedon Apr 6, 2020
That sounds like an AIX specific issue. Please open a separated issue.
vstinner commentedon Apr 7, 2020
New changeset 9205520 by Victor Stinner in branch 'master':
bpo-40170: PyObject_NEW() becomes an alias to PyObject_New() (GH-19379)
9205520
vstinner commentedon Apr 7, 2020
New changeset ef5c615 by Victor Stinner in branch 'master':
bpo-40170: Convert PyObject_CheckBuffer() macro to a function (GH-19376)
ef5c615
vstinner commentedon Apr 7, 2020
New changeset 45ec5b9 by Victor Stinner in branch 'master':
bpo-40170: PyType_HasFeature() now always calls PyType_GetFlags() (GH-19378)
45ec5b9
42 remaining items
vstinner commentedon Jan 27, 2022
TODO:
Macros still accessing directly PyTypeObject members:
Try again to apply "bpo-40170: PyType_HasFeature() now always calls PyType_GetFlags() (bpo-40170: PyType_HasFeature() no longer acccess directly tp_flags #19378)" which has been reverted?
Py_TYPE(obj)->tp_name is still commonly used to format error messages: see rejected bpo-34595
Other TODO tasks which can be addressed in follow-up issues:
vstinner commentedon Jan 27, 2022
I searched for "_PyGC_FINALIZED" in top 5000 PyPI projects. It seems like only Cython is impacted.
ddtrace and guppy3 use directly the internal C API.
== Cython 0.29.26 ==
== ddtrace 0.57.3 ==
In ddtrace/profiling/collector/stack.pyx:
== guppy3-3.1.2 ==
In src/heapy/hv.c:
vstinner commentedon Jan 27, 2022
I searched for "_PyObject_DebugMallocStats" in top 5000 PyPI projects. There is a single project using it: guppy3.
Extract of guppy3 src/heapy/xmemstats.c:
... dlptr__PyObject_DebugMallocStats = addr_of_symbol("_PyObject_DebugMallocStats"); ... static PyObject * hp_xmemstats(PyObject *self, PyObject *args) { if (dlptr__PyObject_DebugMallocStats) { fprintf(stderr, "======================================================================\n"); fprintf(stderr, "Output from _PyObject_DebugMallocStats()\n\n"); dlptr__PyObject_DebugMallocStats(stderr); } ... }addr_of_symbol() is implemented with dlsym() or GetModuleHandle(NULL)+GetProcAddress(): it searchs for the symbol in the current process.
vstinner commentedon Jan 27, 2022
In the top 5000 PyPI projects, the _PyObject_SIZE() and _PyObject_VAR_SIZE() functions are used by 7 projects:
vstinner commentedon Jan 27, 2022
New changeset af32b3e by Victor Stinner in branch 'main':
bpo-40170: PyType_SUPPORTS_WEAKREFS() becomes a regular function (GH-30938)
af32b3e
vstinner commentedon Jan 27, 2022
New changeset 6b491b9 by Victor Stinner in branch 'main':
bpo-40170: Remove _Py_GetAllocatedBlocks() function (GH-30940)
6b491b9
vstinner commentedon Jan 27, 2022
New changeset 0575551 by Victor Stinner in branch 'main':
bpo-40170: Move _Py_GetAllocatedBlocks() to pycore_pymem.h (GH-30943)
0575551
vstinner commentedon Jan 27, 2022
New changeset 18ea973 by Victor Stinner in branch 'main':
bpo-40170: Remove PyHeapType_GET_MEMBERS() macro (GH-30942)
18ea973
vstinner commentedon Jan 28, 2022
Changes already done:
Macros converted to regular functions:
Always implemented as a function, remove macro optimization in the non-limited API:
Py_TRASHCAN_BEGIN() macro now calls _PyTrash_cond() function: no longer read directly tp_dealloc member
PyObject_NEW() macro becomes an alias to PyObject_New()
Remove PyHeapType_GET_MEMBERS() (move it to the internal C API)
PyType_HasFeature() is left unchanged since the change caused performance issue on macOS, whereas Python is not built with LTO.
[-][C API] Make PyTypeObject structure an opaque structure in the public C API[/-][+][C API] Prepare PyTypeObject structure for a stable ABI: avoid accessing members in the public API[/+][-][C API] Make PyTypeObject structure an opaque structure in the public C API[/-][+][C API] Prepare PyTypeObject structure for a stable ABI: avoid accessing members in the public API[/+]vstinner commentedon Jan 28, 2022
I close the issue. While this issue is not fully fixed, it's a milestone of my "stable ABI" goal. I prefer to address remaining issues in new separted issues.
This issues is not fully fixed, there are are still a 4 macros which access directly PyTypeObject members:
PySequence_ITEM() and PyType_HasFeature() are important for performance, I prefer to have a PEP before changing these two functions.
_PyObject_SIZE() and _PyObject_VAR_SIZE() should be made public with a better name like PyObject_SizeOf() and PyVarObject_SizeOf(). But I prefer to do that in a separated issue.
IMO it would be interesting to merge the PyHeapTypeObject structure into the PyTypeObject structure:
https://bugs.python.org/issue46433#msg411167
And make the PyTypeObject opaque: deprecate static types and promote the usage of heap types in C extensions. That's a big project which may be splitted into multiple issues and the final change may need its own PEP.