Skip to content

Commit cd9a56c

Browse files
encukouarhadthedeverlend-aasland
authored
gh-103509: PEP 697 -- Limited C API for Extending Opaque Types (GH-103511)
Co-authored-by: Oleg Iarygin <oleg@arhadthedev.net> Co-authored-by: Erlend E. Aasland <erlend.aasland@protonmail.com>
1 parent 35d2738 commit cd9a56c

30 files changed

+970
-19
lines changed

Doc/c-api/object.rst

+39
Original file line numberDiff line numberDiff line change
@@ -395,3 +395,42 @@ Object Protocol
395395
returns ``NULL`` if the object cannot be iterated.
396396
397397
.. versionadded:: 3.10
398+
399+
.. c:function:: void *PyObject_GetTypeData(PyObject *o, PyTypeObject *cls)
400+
401+
Get a pointer to subclass-specific data reserved for *cls*.
402+
403+
The object *o* must be an instance of *cls*, and *cls* must have been
404+
created using negative :c:member:`PyType_Spec.basicsize`.
405+
Python does not check this.
406+
407+
On error, set an exception and return ``NULL``.
408+
409+
.. versionadded:: 3.12
410+
411+
.. c:function:: Py_ssize_t PyType_GetTypeDataSize(PyTypeObject *cls)
412+
413+
Return the size of the instance memory space reserved for *cls*, i.e. the size of the
414+
memory :c:func:`PyObject_GetTypeData` returns.
415+
416+
This may be larger than requested using :c:member:`-PyType_Spec.basicsize <PyType_Spec.basicsize>`;
417+
it is safe to use this larger size (e.g. with :c:func:`!memset`).
418+
419+
The type *cls* **must** have been created using
420+
negative :c:member:`PyType_Spec.basicsize`.
421+
Python does not check this.
422+
423+
On error, set an exception and return a negative value.
424+
425+
.. versionadded:: 3.12
426+
427+
.. c:function:: void *PyObject_GetItemData(PyObject *o)
428+
429+
Get a pointer to per-item data for a class with
430+
:const:`Py_TPFLAGS_ITEMS_AT_END`.
431+
432+
On error, set an exception and return ``NULL``.
433+
:py:exc:`TypeError` is raised if *o* does not have
434+
:const:`Py_TPFLAGS_ITEMS_AT_END` set.
435+
436+
.. versionadded:: 3.12

Doc/c-api/structures.rst

+16
Original file line numberDiff line numberDiff line change
@@ -486,6 +486,22 @@ The following flags can be used with :c:member:`PyMemberDef.flags`:
486486
Emit an ``object.__getattr__`` :ref:`audit event <audit-events>`
487487
before reading.
488488
489+
.. c:macro:: Py_RELATIVE_OFFSET
490+
491+
Indicates that the :c:member:`~PyMemberDef.offset` of this ``PyMemberDef``
492+
entry indicates an offset from the subclass-specific data, rather than
493+
from ``PyObject``.
494+
495+
Can only be used as part of :c:member:`Py_tp_members <PyTypeObject.tp_members>`
496+
:c:type:`slot <PyTypeSlot>` when creating a class using negative
497+
:c:member:`~PyTypeDef.basicsize`.
498+
It is mandatory in that case.
499+
500+
This flag is only used in :c:type:`PyTypeSlot`.
501+
When setting :c:member:`~PyTypeObject.tp_members` during
502+
class creation, Python clears it and sets
503+
:c:member:`PyMemberDef.offset` to the offset from the ``PyObject`` struct.
504+
489505
.. index::
490506
single: READ_RESTRICTED
491507
single: WRITE_RESTRICTED

Doc/c-api/type.rst

+40-8
Original file line numberDiff line numberDiff line change
@@ -353,25 +353,57 @@ The following functions and structs are used to create
353353
354354
Structure defining a type's behavior.
355355
356-
.. c:member:: const char* PyType_Spec.name
356+
.. c:member:: const char* name
357357
358358
Name of the type, used to set :c:member:`PyTypeObject.tp_name`.
359359
360-
.. c:member:: int PyType_Spec.basicsize
361-
.. c:member:: int PyType_Spec.itemsize
360+
.. c:member:: int basicsize
362361
363-
Size of the instance in bytes, used to set
364-
:c:member:`PyTypeObject.tp_basicsize` and
365-
:c:member:`PyTypeObject.tp_itemsize`.
362+
If positive, specifies the size of the instance in bytes.
363+
It is used to set :c:member:`PyTypeObject.tp_basicsize`.
366364
367-
.. c:member:: int PyType_Spec.flags
365+
If zero, specifies that :c:member:`~PyTypeObject.tp_basicsize`
366+
should be inherited.
367+
368+
If negative, the absolute value specifies how much space instances of the
369+
class need *in addition* to the superclass.
370+
Use :c:func:`PyObject_GetTypeData` to get a pointer to subclass-specific
371+
memory reserved this way.
372+
373+
.. versionchanged:: 3.12
374+
375+
Previously, this field could not be negative.
376+
377+
.. c:member:: int itemsize
378+
379+
Size of one element of a variable-size type, in bytes.
380+
Used to set :c:member:`PyTypeObject.tp_itemsize`.
381+
See ``tp_itemsize`` documentation for caveats.
382+
383+
If zero, :c:member:`~PyTypeObject.tp_itemsize` is inherited.
384+
Extending arbitrary variable-sized classes is dangerous,
385+
since some types use a fixed offset for variable-sized memory,
386+
which can then overlap fixed-sized memory used by a subclass.
387+
To help prevent mistakes, inheriting ``itemsize`` is only possible
388+
in the following situations:
389+
390+
- The base is not variable-sized (its
391+
:c:member:`~PyTypeObject.tp_itemsize`).
392+
- The requested :c:member:`PyType_Spec.basicsize` is positive,
393+
suggesting that the memory layout of the base class is known.
394+
- The requested :c:member:`PyType_Spec.basicsize` is zero,
395+
suggesting that the subclass does not access the instance's memory
396+
directly.
397+
- With the :const:`Py_TPFLAGS_ITEMS_AT_END` flag.
398+
399+
.. c:member:: unsigned int flags
368400
369401
Type flags, used to set :c:member:`PyTypeObject.tp_flags`.
370402
371403
If the ``Py_TPFLAGS_HEAPTYPE`` flag is not set,
372404
:c:func:`PyType_FromSpecWithBases` sets it automatically.
373405
374-
.. c:member:: PyType_Slot *PyType_Spec.slots
406+
.. c:member:: PyType_Slot *slots
375407
376408
Array of :c:type:`PyType_Slot` structures.
377409
Terminated by the special slot value ``{0, NULL}``.

Doc/c-api/typeobj.rst

+20
Original file line numberDiff line numberDiff line change
@@ -1171,6 +1171,26 @@ and :c:type:`PyType_Type` effectively act as defaults.)
11711171
:c:member:`~PyTypeObject.tp_weaklistoffset` field is set in a superclass.
11721172

11731173

1174+
.. data:: Py_TPFLAGS_ITEMS_AT_END
1175+
1176+
Only usable with variable-size types, i.e. ones with non-zero
1177+
:c:member:`~PyObject.tp_itemsize`.
1178+
1179+
Indicates that the variable-sized portion of an instance of this type is
1180+
at the end of the instance's memory area, at an offset of
1181+
:c:expr:`Py_TYPE(obj)->tp_basicsize` (which may be different in each
1182+
subclass).
1183+
1184+
When setting this flag, be sure that all superclasses either
1185+
use this memory layout, or are not variable-sized.
1186+
Python does not check this.
1187+
1188+
.. versionadded:: 3.12
1189+
1190+
**Inheritance:**
1191+
1192+
This flag is inherited.
1193+
11741194
.. XXX Document more flags here?
11751195
11761196

Doc/data/stable_abi.dat

+2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Doc/whatsnew/3.12.rst

+15
Original file line numberDiff line numberDiff line change
@@ -1159,6 +1159,21 @@ New Features
11591159

11601160
(Contributed by Petr Viktorin in :gh:`101101`.)
11611161

1162+
* :pep:`697`: Added API for extending types whose instance memory layout is
1163+
opaque:
1164+
1165+
- :c:member:`PyType_Spec.basicsize` can be zero or negative to specify
1166+
inheriting or extending the base class size.
1167+
- :c:func:`PyObject_GetTypeData` and :c:func:`PyType_GetTypeDataSize`
1168+
added to allow access to subclass-specific instance data.
1169+
- :const:`Py_TPFLAGS_ITEMS_AT_END` and :c:func:`PyObject_GetItemData`
1170+
added to allow safely extending certain variable-sized types, including
1171+
:c:var:`PyType_Type`.
1172+
- :c:macro:`Py_RELATIVE_OFFSET` added to allow defining
1173+
:c:type:`members <PyMemberDef>` in terms of a subclass-specific struct.
1174+
1175+
(Contributed by Petr Viktorin in :gh:`103509`.)
1176+
11621177
* Added the new limited C API function :c:func:`PyType_FromMetaclass`,
11631178
which generalizes the existing :c:func:`PyType_FromModuleAndSpec` using
11641179
an additional metaclass argument.

Include/cpython/object.h

+1
Original file line numberDiff line numberDiff line change
@@ -553,6 +553,7 @@ Py_DEPRECATED(3.11) typedef int UsingDeprecatedTrashcanMacro;
553553
Py_TRASHCAN_END; \
554554
} while(0);
555555

556+
PyAPI_FUNC(void *) PyObject_GetItemData(PyObject *obj);
556557

557558
PyAPI_FUNC(int) _PyObject_VisitManagedDict(PyObject *obj, visitproc visit, void *arg);
558559
PyAPI_FUNC(void) _PyObject_ClearManagedDict(PyObject *obj);

Include/descrobject.h

+1
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ struct PyMemberDef {
8383
#define Py_READONLY 1
8484
#define Py_AUDIT_READ 2 // Added in 3.10, harmless no-op before that
8585
#define _Py_WRITE_RESTRICTED 4 // Deprecated, no-op. Do not reuse the value.
86+
#define Py_RELATIVE_OFFSET 8
8687

8788
PyAPI_FUNC(PyObject *) PyMember_GetOne(const char *, PyMemberDef *);
8889
PyAPI_FUNC(int) PyMember_SetOne(char *, PyMemberDef *, PyObject *);

Include/internal/pycore_object.h

-5
Original file line numberDiff line numberDiff line change
@@ -389,11 +389,6 @@ extern PyObject ** _PyObject_ComputedDictPointer(PyObject *);
389389
extern void _PyObject_FreeInstanceAttributes(PyObject *obj);
390390
extern int _PyObject_IsInstanceDictEmpty(PyObject *);
391391

392-
// Access macro to the members which are floating "behind" the object
393-
static inline PyMemberDef* _PyHeapType_GET_MEMBERS(PyHeapTypeObject *etype) {
394-
return (PyMemberDef*)((char*)etype + Py_TYPE(etype)->tp_basicsize);
395-
}
396-
397392
PyAPI_FUNC(PyObject *) _PyObject_LookupSpecial(PyObject *, PyObject *);
398393

399394
/* C function call trampolines to mitigate bad function pointer casts.

Include/object.h

+5
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,8 @@ PyAPI_FUNC(PyObject *) PyType_GetQualName(PyTypeObject *);
355355
#endif
356356
#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030C0000
357357
PyAPI_FUNC(PyObject *) PyType_FromMetaclass(PyTypeObject*, PyObject*, PyType_Spec*, PyObject*);
358+
PyAPI_FUNC(void *) PyObject_GetTypeData(PyObject *obj, PyTypeObject *cls);
359+
PyAPI_FUNC(Py_ssize_t) PyType_GetTypeDataSize(PyTypeObject *cls);
358360
#endif
359361

360362
/* Generic type check */
@@ -521,6 +523,9 @@ given type object has a specified feature.
521523
// subject itself (rather than a mapped attribute on it):
522524
#define _Py_TPFLAGS_MATCH_SELF (1UL << 22)
523525

526+
/* Items (ob_size*tp_itemsize) are found at the end of an instance's memory */
527+
#define Py_TPFLAGS_ITEMS_AT_END (1UL << 23)
528+
524529
/* These flags are used to determine if a type is a subclass. */
525530
#define Py_TPFLAGS_LONG_SUBCLASS (1UL << 24)
526531
#define Py_TPFLAGS_LIST_SUBCLASS (1UL << 25)

Include/pyport.h

+11
Original file line numberDiff line numberDiff line change
@@ -765,4 +765,15 @@ extern char * _getpty(int *, int, mode_t, int);
765765
#undef __bool__
766766
#endif
767767

768+
// Make sure we have maximum alignment, even if the current compiler
769+
// does not support max_align_t. Note that:
770+
// - Autoconf reports alignment of unknown types to 0.
771+
// - 'long double' has maximum alignment on *most* platforms,
772+
// looks like the best we can do for pre-C11 compilers.
773+
// - The value is tested, see test_alignof_max_align_t
774+
#if !defined(ALIGNOF_MAX_ALIGN_T) || ALIGNOF_MAX_ALIGN_T == 0
775+
# undef ALIGNOF_MAX_ALIGN_T
776+
# define ALIGNOF_MAX_ALIGN_T _Alignof(long double)
777+
#endif
778+
768779
#endif /* Py_PYPORT_H */

0 commit comments

Comments
 (0)