summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Doc/c-api/object.rst39
-rw-r--r--Doc/c-api/structures.rst16
-rw-r--r--Doc/c-api/type.rst48
-rw-r--r--Doc/c-api/typeobj.rst20
-rw-r--r--Doc/data/stable_abi.dat2
-rw-r--r--Doc/whatsnew/3.12.rst15
-rw-r--r--Include/cpython/object.h1
-rw-r--r--Include/descrobject.h1
-rw-r--r--Include/internal/pycore_object.h5
-rw-r--r--Include/object.h5
-rw-r--r--Include/pyport.h11
-rw-r--r--Lib/test/test_capi/test_misc.py158
-rw-r--r--Lib/test/test_stable_abi_ctypes.py2
-rw-r--r--Misc/NEWS.d/next/C API/2023-04-13-16-54-00.gh-issue-103509.A26Qu8.rst5
-rw-r--r--Misc/stable_abi.toml9
-rw-r--r--Modules/Setup.stdlib.in2
-rw-r--r--Modules/_testcapi/heaptype.c131
-rw-r--r--Modules/_testcapi/heaptype_relative.c343
-rw-r--r--Modules/_testcapi/parts.h1
-rw-r--r--Modules/_testcapimodule.c3
-rw-r--r--Objects/descrobject.c6
-rw-r--r--Objects/typeobject.c107
-rw-r--r--PC/pyconfig.h2
-rwxr-xr-xPC/python3dll.c2
-rw-r--r--PCbuild/_testcapi.vcxproj1
-rw-r--r--PCbuild/_testcapi.vcxproj.filters3
-rw-r--r--Python/structmember.c12
-rwxr-xr-xconfigure35
-rw-r--r--configure.ac1
-rw-r--r--pyconfig.h.in3
30 files changed, 970 insertions, 19 deletions
diff --git a/Doc/c-api/object.rst b/Doc/c-api/object.rst
index 0a12bb9..a0c3194 100644
--- a/Doc/c-api/object.rst
+++ b/Doc/c-api/object.rst
@@ -395,3 +395,42 @@ Object Protocol
returns ``NULL`` if the object cannot be iterated.
.. versionadded:: 3.10
+
+.. c:function:: void *PyObject_GetTypeData(PyObject *o, PyTypeObject *cls)
+
+ Get a pointer to subclass-specific data reserved for *cls*.
+
+ The object *o* must be an instance of *cls*, and *cls* must have been
+ created using negative :c:member:`PyType_Spec.basicsize`.
+ Python does not check this.
+
+ On error, set an exception and return ``NULL``.
+
+ .. versionadded:: 3.12
+
+.. c:function:: Py_ssize_t PyType_GetTypeDataSize(PyTypeObject *cls)
+
+ Return the size of the instance memory space reserved for *cls*, i.e. the size of the
+ memory :c:func:`PyObject_GetTypeData` returns.
+
+ This may be larger than requested using :c:member:`-PyType_Spec.basicsize <PyType_Spec.basicsize>`;
+ it is safe to use this larger size (e.g. with :c:func:`!memset`).
+
+ The type *cls* **must** have been created using
+ negative :c:member:`PyType_Spec.basicsize`.
+ Python does not check this.
+
+ On error, set an exception and return a negative value.
+
+ .. versionadded:: 3.12
+
+.. c:function:: void *PyObject_GetItemData(PyObject *o)
+
+ Get a pointer to per-item data for a class with
+ :const:`Py_TPFLAGS_ITEMS_AT_END`.
+
+ On error, set an exception and return ``NULL``.
+ :py:exc:`TypeError` is raised if *o* does not have
+ :const:`Py_TPFLAGS_ITEMS_AT_END` set.
+
+ .. versionadded:: 3.12
diff --git a/Doc/c-api/structures.rst b/Doc/c-api/structures.rst
index 9618a0c..338db63 100644
--- a/Doc/c-api/structures.rst
+++ b/Doc/c-api/structures.rst
@@ -486,6 +486,22 @@ The following flags can be used with :c:member:`PyMemberDef.flags`:
Emit an ``object.__getattr__`` :ref:`audit event <audit-events>`
before reading.
+.. c:macro:: Py_RELATIVE_OFFSET
+
+ Indicates that the :c:member:`~PyMemberDef.offset` of this ``PyMemberDef``
+ entry indicates an offset from the subclass-specific data, rather than
+ from ``PyObject``.
+
+ Can only be used as part of :c:member:`Py_tp_members <PyTypeObject.tp_members>`
+ :c:type:`slot <PyTypeSlot>` when creating a class using negative
+ :c:member:`~PyTypeDef.basicsize`.
+ It is mandatory in that case.
+
+ This flag is only used in :c:type:`PyTypeSlot`.
+ When setting :c:member:`~PyTypeObject.tp_members` during
+ class creation, Python clears it and sets
+ :c:member:`PyMemberDef.offset` to the offset from the ``PyObject`` struct.
+
.. index::
single: READ_RESTRICTED
single: WRITE_RESTRICTED
diff --git a/Doc/c-api/type.rst b/Doc/c-api/type.rst
index 9fd40e1..c21fd92 100644
--- a/Doc/c-api/type.rst
+++ b/Doc/c-api/type.rst
@@ -353,25 +353,57 @@ The following functions and structs are used to create
Structure defining a type's behavior.
- .. c:member:: const char* PyType_Spec.name
+ .. c:member:: const char* name
Name of the type, used to set :c:member:`PyTypeObject.tp_name`.
- .. c:member:: int PyType_Spec.basicsize
- .. c:member:: int PyType_Spec.itemsize
+ .. c:member:: int basicsize
- Size of the instance in bytes, used to set
- :c:member:`PyTypeObject.tp_basicsize` and
- :c:member:`PyTypeObject.tp_itemsize`.
+ If positive, specifies the size of the instance in bytes.
+ It is used to set :c:member:`PyTypeObject.tp_basicsize`.
- .. c:member:: int PyType_Spec.flags
+ If zero, specifies that :c:member:`~PyTypeObject.tp_basicsize`
+ should be inherited.
+
+ If negative, the absolute value specifies how much space instances of the
+ class need *in addition* to the superclass.
+ Use :c:func:`PyObject_GetTypeData` to get a pointer to subclass-specific
+ memory reserved this way.
+
+ .. versionchanged:: 3.12
+
+ Previously, this field could not be negative.
+
+ .. c:member:: int itemsize
+
+ Size of one element of a variable-size type, in bytes.
+ Used to set :c:member:`PyTypeObject.tp_itemsize`.
+ See ``tp_itemsize`` documentation for caveats.
+
+ If zero, :c:member:`~PyTypeObject.tp_itemsize` is inherited.
+ Extending arbitrary variable-sized classes is dangerous,
+ since some types use a fixed offset for variable-sized memory,
+ which can then overlap fixed-sized memory used by a subclass.
+ To help prevent mistakes, inheriting ``itemsize`` is only possible
+ in the following situations:
+
+ - The base is not variable-sized (its
+ :c:member:`~PyTypeObject.tp_itemsize`).
+ - The requested :c:member:`PyType_Spec.basicsize` is positive,
+ suggesting that the memory layout of the base class is known.
+ - The requested :c:member:`PyType_Spec.basicsize` is zero,
+ suggesting that the subclass does not access the instance's memory
+ directly.
+ - With the :const:`Py_TPFLAGS_ITEMS_AT_END` flag.
+
+ .. c:member:: unsigned int flags
Type flags, used to set :c:member:`PyTypeObject.tp_flags`.
If the ``Py_TPFLAGS_HEAPTYPE`` flag is not set,
:c:func:`PyType_FromSpecWithBases` sets it automatically.
- .. c:member:: PyType_Slot *PyType_Spec.slots
+ .. c:member:: PyType_Slot *slots
Array of :c:type:`PyType_Slot` structures.
Terminated by the special slot value ``{0, NULL}``.
diff --git a/Doc/c-api/typeobj.rst b/Doc/c-api/typeobj.rst
index e963b90..e13db3f 100644
--- a/Doc/c-api/typeobj.rst
+++ b/Doc/c-api/typeobj.rst
@@ -1171,6 +1171,26 @@ and :c:type:`PyType_Type` effectively act as defaults.)
:c:member:`~PyTypeObject.tp_weaklistoffset` field is set in a superclass.
+ .. data:: Py_TPFLAGS_ITEMS_AT_END
+
+ Only usable with variable-size types, i.e. ones with non-zero
+ :c:member:`~PyObject.tp_itemsize`.
+
+ Indicates that the variable-sized portion of an instance of this type is
+ at the end of the instance's memory area, at an offset of
+ :c:expr:`Py_TYPE(obj)->tp_basicsize` (which may be different in each
+ subclass).
+
+ When setting this flag, be sure that all superclasses either
+ use this memory layout, or are not variable-sized.
+ Python does not check this.
+
+ .. versionadded:: 3.12
+
+ **Inheritance:**
+
+ This flag is inherited.
+
.. XXX Document more flags here?
diff --git a/Doc/data/stable_abi.dat b/Doc/data/stable_abi.dat
index 4cc06d2..f112d26 100644
--- a/Doc/data/stable_abi.dat
+++ b/Doc/data/stable_abi.dat
@@ -521,6 +521,7 @@ function,PyObject_GetAttrString,3.2,,
function,PyObject_GetBuffer,3.11,,
function,PyObject_GetItem,3.2,,
function,PyObject_GetIter,3.2,,
+function,PyObject_GetTypeData,3.12,,
function,PyObject_HasAttr,3.2,,
function,PyObject_HasAttrString,3.2,,
function,PyObject_Hash,3.2,,
@@ -675,6 +676,7 @@ function,PyType_GetModuleState,3.10,,
function,PyType_GetName,3.11,,
function,PyType_GetQualName,3.11,,
function,PyType_GetSlot,3.4,,
+function,PyType_GetTypeDataSize,3.12,,
function,PyType_IsSubtype,3.2,,
function,PyType_Modified,3.2,,
function,PyType_Ready,3.2,,
diff --git a/Doc/whatsnew/3.12.rst b/Doc/whatsnew/3.12.rst
index 3dfd787..edbf921 100644
--- a/Doc/whatsnew/3.12.rst
+++ b/Doc/whatsnew/3.12.rst
@@ -1159,6 +1159,21 @@ New Features
(Contributed by Petr Viktorin in :gh:`101101`.)
+* :pep:`697`: Added API for extending types whose instance memory layout is
+ opaque:
+
+ - :c:member:`PyType_Spec.basicsize` can be zero or negative to specify
+ inheriting or extending the base class size.
+ - :c:func:`PyObject_GetTypeData` and :c:func:`PyType_GetTypeDataSize`
+ added to allow access to subclass-specific instance data.
+ - :const:`Py_TPFLAGS_ITEMS_AT_END` and :c:func:`PyObject_GetItemData`
+ added to allow safely extending certain variable-sized types, including
+ :c:var:`PyType_Type`.
+ - :c:macro:`Py_RELATIVE_OFFSET` added to allow defining
+ :c:type:`members <PyMemberDef>` in terms of a subclass-specific struct.
+
+ (Contributed by Petr Viktorin in :gh:`103509`.)
+
* Added the new limited C API function :c:func:`PyType_FromMetaclass`,
which generalizes the existing :c:func:`PyType_FromModuleAndSpec` using
an additional metaclass argument.
diff --git a/Include/cpython/object.h b/Include/cpython/object.h
index ce4d13c..d8eff69 100644
--- a/Include/cpython/object.h
+++ b/Include/cpython/object.h
@@ -553,6 +553,7 @@ Py_DEPRECATED(3.11) typedef int UsingDeprecatedTrashcanMacro;
Py_TRASHCAN_END; \
} while(0);
+PyAPI_FUNC(void *) PyObject_GetItemData(PyObject *obj);
PyAPI_FUNC(int) _PyObject_VisitManagedDict(PyObject *obj, visitproc visit, void *arg);
PyAPI_FUNC(void) _PyObject_ClearManagedDict(PyObject *obj);
diff --git a/Include/descrobject.h b/Include/descrobject.h
index 0a420b8..fd66d17 100644
--- a/Include/descrobject.h
+++ b/Include/descrobject.h
@@ -83,6 +83,7 @@ struct PyMemberDef {
#define Py_READONLY 1
#define Py_AUDIT_READ 2 // Added in 3.10, harmless no-op before that
#define _Py_WRITE_RESTRICTED 4 // Deprecated, no-op. Do not reuse the value.
+#define Py_RELATIVE_OFFSET 8
PyAPI_FUNC(PyObject *) PyMember_GetOne(const char *, PyMemberDef *);
PyAPI_FUNC(int) PyMember_SetOne(char *, PyMemberDef *, PyObject *);
diff --git a/Include/internal/pycore_object.h b/Include/internal/pycore_object.h
index 91853ad..b9e700e 100644
--- a/Include/internal/pycore_object.h
+++ b/Include/internal/pycore_object.h
@@ -389,11 +389,6 @@ extern PyObject ** _PyObject_ComputedDictPointer(PyObject *);
extern void _PyObject_FreeInstanceAttributes(PyObject *obj);
extern int _PyObject_IsInstanceDictEmpty(PyObject *);
-// Access macro to the members which are floating "behind" the object
-static inline PyMemberDef* _PyHeapType_GET_MEMBERS(PyHeapTypeObject *etype) {
- return (PyMemberDef*)((char*)etype + Py_TYPE(etype)->tp_basicsize);
-}
-
PyAPI_FUNC(PyObject *) _PyObject_LookupSpecial(PyObject *, PyObject *);
/* C function call trampolines to mitigate bad function pointer casts.
diff --git a/Include/object.h b/Include/object.h
index 66c3df0..81aeb2d 100644
--- a/Include/object.h
+++ b/Include/object.h
@@ -355,6 +355,8 @@ PyAPI_FUNC(PyObject *) PyType_GetQualName(PyTypeObject *);
#endif
#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030C0000
PyAPI_FUNC(PyObject *) PyType_FromMetaclass(PyTypeObject*, PyObject*, PyType_Spec*, PyObject*);
+PyAPI_FUNC(void *) PyObject_GetTypeData(PyObject *obj, PyTypeObject *cls);
+PyAPI_FUNC(Py_ssize_t) PyType_GetTypeDataSize(PyTypeObject *cls);
#endif
/* Generic type check */
@@ -521,6 +523,9 @@ given type object has a specified feature.
// subject itself (rather than a mapped attribute on it):
#define _Py_TPFLAGS_MATCH_SELF (1UL << 22)
+/* Items (ob_size*tp_itemsize) are found at the end of an instance's memory */
+#define Py_TPFLAGS_ITEMS_AT_END (1UL << 23)
+
/* These flags are used to determine if a type is a subclass. */
#define Py_TPFLAGS_LONG_SUBCLASS (1UL << 24)
#define Py_TPFLAGS_LIST_SUBCLASS (1UL << 25)
diff --git a/Include/pyport.h b/Include/pyport.h
index bd0ba6d..d7c6ae6 100644
--- a/Include/pyport.h
+++ b/Include/pyport.h
@@ -765,4 +765,15 @@ extern char * _getpty(int *, int, mode_t, int);
#undef __bool__
#endif
+// Make sure we have maximum alignment, even if the current compiler
+// does not support max_align_t. Note that:
+// - Autoconf reports alignment of unknown types to 0.
+// - 'long double' has maximum alignment on *most* platforms,
+// looks like the best we can do for pre-C11 compilers.
+// - The value is tested, see test_alignof_max_align_t
+#if !defined(ALIGNOF_MAX_ALIGN_T) || ALIGNOF_MAX_ALIGN_T == 0
+# undef ALIGNOF_MAX_ALIGN_T
+# define ALIGNOF_MAX_ALIGN_T _Alignof(long double)
+#endif
+
#endif /* Py_PYPORT_H */
diff --git a/Lib/test/test_capi/test_misc.py b/Lib/test/test_capi/test_misc.py
index 1d426d0..22be3c0 100644
--- a/Lib/test/test_capi/test_misc.py
+++ b/Lib/test/test_capi/test_misc.py
@@ -16,11 +16,13 @@ import time
import unittest
import warnings
import weakref
+import operator
from test import support
from test.support import MISSING_C_DOCSTRINGS
from test.support import import_helper
from test.support import threading_helper
from test.support import warnings_helper
+from test.support import requires_limited_api
from test.support.script_helper import assert_python_failure, assert_python_ok, run_python_until_end
try:
import _posixsubprocess
@@ -770,7 +772,6 @@ class CAPITest(unittest.TestCase):
MutableBase.meth = lambda self: 'changed'
self.assertEqual(instance.meth(), 'changed')
-
def test_pynumber_tobase(self):
from _testcapi import pynumber_tobase
small_number = 123
@@ -1072,6 +1073,161 @@ class CAPITest(unittest.TestCase):
self.assertIsNone(d.extra)
+@requires_limited_api
+class TestHeapTypeRelative(unittest.TestCase):
+ """Test API for extending opaque types (PEP 697)"""
+
+ @requires_limited_api
+ def test_heaptype_relative_sizes(self):
+ # Test subclassing using "relative" basicsize, see PEP 697
+ def check(extra_base_size, extra_size):
+ Base, Sub, instance, data_ptr, data_offset, data_size = (
+ _testcapi.make_sized_heaptypes(
+ extra_base_size, -extra_size))
+
+ # no alignment shenanigans when inheriting directly
+ if extra_size == 0:
+ self.assertEqual(Base.__basicsize__, Sub.__basicsize__)
+ self.assertEqual(data_size, 0)
+
+ else:
+ # The following offsets should be in increasing order:
+ offsets = [
+ (0, 'start of object'),
+ (Base.__basicsize__, 'end of base data'),
+ (data_offset, 'subclass data'),
+ (data_offset + extra_size, 'end of requested subcls data'),
+ (data_offset + data_size, 'end of reserved subcls data'),
+ (Sub.__basicsize__, 'end of object'),
+ ]
+ ordered_offsets = sorted(offsets, key=operator.itemgetter(0))
+ self.assertEqual(
+ offsets, ordered_offsets,
+ msg=f'Offsets not in expected order, got: {ordered_offsets}')
+
+ # end of reserved subcls data == end of object
+ self.assertEqual(Sub.__basicsize__, data_offset + data_size)
+
+ # we don't reserve (requested + alignment) or more data
+ self.assertLess(data_size - extra_size,
+ _testcapi.ALIGNOF_MAX_ALIGN_T)
+
+ # The offsets/sizes we calculated should be aligned.
+ self.assertEqual(data_offset % _testcapi.ALIGNOF_MAX_ALIGN_T, 0)
+ self.assertEqual(data_size % _testcapi.ALIGNOF_MAX_ALIGN_T, 0)
+
+ sizes = sorted({0, 1, 2, 3, 4, 7, 8, 123,
+ object.__basicsize__,
+ object.__basicsize__-1,
+ object.__basicsize__+1})
+ for extra_base_size in sizes:
+ for extra_size in sizes:
+ args = dict(extra_base_size=extra_base_size,
+ extra_size=extra_size)
+ with self.subTest(**args):
+ check(**args)
+
+ def test_HeapCCollection(self):
+ """Make sure HeapCCollection works properly by itself"""
+ collection = _testcapi.HeapCCollection(1, 2, 3)
+ self.assertEqual(list(collection), [1, 2, 3])
+
+ def test_heaptype_inherit_itemsize(self):
+ """Test HeapCCollection subclasses work properly"""
+ sizes = sorted({0, 1, 2, 3, 4, 7, 8, 123,
+ object.__basicsize__,
+ object.__basicsize__-1,
+ object.__basicsize__+1})
+ for extra_size in sizes:
+ with self.subTest(extra_size=extra_size):
+ Sub = _testcapi.subclass_var_heaptype(
+ _testcapi.HeapCCollection, -extra_size, 0, 0)
+ collection = Sub(1, 2, 3)
+ collection.set_data_to_3s()
+
+ self.assertEqual(list(collection), [1, 2, 3])
+ mem = collection.get_data()
+ self.assertGreaterEqual(len(mem), extra_size)
+ self.assertTrue(set(mem) <= {3}, f'got {mem!r}')
+
+ def test_heaptype_invalid_inheritance(self):
+ with self.assertRaises(SystemError,
+ msg="Cannot extend variable-size class without "
+ + "Py_TPFLAGS_ITEMS_AT_END"):
+ _testcapi.subclass_heaptype(int, -8, 0)
+
+ def test_heaptype_relative_members(self):
+ """Test HeapCCollection subclasses work properly"""
+ sizes = sorted({0, 1, 2, 3, 4, 7, 8, 123,
+ object.__basicsize__,
+ object.__basicsize__-1,
+ object.__basicsize__+1})
+ for extra_base_size in sizes:
+ for extra_size in sizes:
+ for offset in sizes:
+ with self.subTest(extra_base_size=extra_base_size, extra_size=extra_size, offset=offset):
+ if offset < extra_size:
+ Sub = _testcapi.make_heaptype_with_member(
+ extra_base_size, -extra_size, offset, True)
+ Base = Sub.mro()[1]
+ instance = Sub()
+ self.assertEqual(instance.memb, instance.get_memb())
+ instance.set_memb(13)
+ self.assertEqual(instance.memb, instance.get_memb())
+ self.assertEqual(instance.get_memb(), 13)
+ instance.memb = 14
+ self.assertEqual(instance.memb, instance.get_memb())
+ self.assertEqual(instance.get_memb(), 14)
+ self.assertGreaterEqual(instance.get_memb_offset(), Base.__basicsize__)
+ self.assertLess(instance.get_memb_offset(), Sub.__basicsize__)
+ with self.assertRaises(SystemError):
+ instance.get_memb_relative()
+ with self.assertRaises(SystemError):
+ instance.set_memb_relative(0)
+ else:
+ with self.assertRaises(SystemError):
+ Sub = _testcapi.make_heaptype_with_member(
+ extra_base_size, -extra_size, offset, True)
+ with self.assertRaises(SystemError):
+ Sub = _testcapi.make_heaptype_with_member(
+ extra_base_size, extra_size, offset, True)
+ with self.subTest(extra_base_size=extra_base_size, extra_size=extra_size):
+ with self.assertRaises(SystemError):
+ Sub = _testcapi.make_heaptype_with_member(
+ extra_base_size, -extra_size, -1, True)
+
+ def test_heaptype_relative_members_errors(self):
+ with self.assertRaisesRegex(
+ SystemError,
+ r"With Py_RELATIVE_OFFSET, basicsize must be negative"):
+ _testcapi.make_heaptype_with_member(0, 1234, 0, True)
+ with self.assertRaisesRegex(
+ SystemError, r"Member offset out of range \(0\.\.-basicsize\)"):
+ _testcapi.make_heaptype_with_member(0, -8, 1234, True)
+ with self.assertRaisesRegex(
+ SystemError, r"Member offset out of range \(0\.\.-basicsize\)"):
+ _testcapi.make_heaptype_with_member(0, -8, -1, True)
+
+ Sub = _testcapi.make_heaptype_with_member(0, -8, 0, True)
+ instance = Sub()
+ with self.assertRaisesRegex(
+ SystemError, r"PyMember_GetOne used with Py_RELATIVE_OFFSET"):
+ instance.get_memb_relative()
+ with self.assertRaisesRegex(
+ SystemError, r"PyMember_SetOne used with Py_RELATIVE_OFFSET"):
+ instance.set_memb_relative(0)
+
+ def test_pyobject_getitemdata_error(self):
+ """Test PyObject_GetItemData fails on unsupported types"""
+ with self.assertRaises(TypeError):
+ # None is not variable-length
+ _testcapi.pyobject_getitemdata(None)
+ with self.assertRaises(TypeError):
+ # int is variable-length, but doesn't have the
+ # Py_TPFLAGS_ITEMS_AT_END layout (and flag)
+ _testcapi.pyobject_getitemdata(0)
+
+
class TestPendingCalls(unittest.TestCase):
def pendingcalls_submit(self, l, n):
diff --git a/Lib/test/test_stable_abi_ctypes.py b/Lib/test/test_stable_abi_ctypes.py
index 2feaaf8..4ca39d8 100644
--- a/Lib/test/test_stable_abi_ctypes.py
+++ b/Lib/test/test_stable_abi_ctypes.py
@@ -529,6 +529,7 @@ SYMBOL_NAMES = (
"PyObject_GetBuffer",
"PyObject_GetItem",
"PyObject_GetIter",
+ "PyObject_GetTypeData",
"PyObject_HasAttr",
"PyObject_HasAttrString",
"PyObject_Hash",
@@ -679,6 +680,7 @@ SYMBOL_NAMES = (
"PyType_GetName",
"PyType_GetQualName",
"PyType_GetSlot",
+ "PyType_GetTypeDataSize",
"PyType_IsSubtype",
"PyType_Modified",
"PyType_Ready",
diff --git a/Misc/NEWS.d/next/C API/2023-04-13-16-54-00.gh-issue-103509.A26Qu8.rst b/Misc/NEWS.d/next/C API/2023-04-13-16-54-00.gh-issue-103509.A26Qu8.rst
new file mode 100644
index 0000000..af630c3
--- /dev/null
+++ b/Misc/NEWS.d/next/C API/2023-04-13-16-54-00.gh-issue-103509.A26Qu8.rst
@@ -0,0 +1,5 @@
+Added C API for extending types whose instance memory layout is opaque:
+:c:member:`PyType_Spec.basicsize` can now be zero or negative,
+:c:func:`PyObject_GetTypeData` can be used to get subclass-specific data,
+and :c:macro:`Py_TPFLAGS_ITEMS_AT_END` can be used to safely extend
+variable-size objects. See :pep:`697` for details.
diff --git a/Misc/stable_abi.toml b/Misc/stable_abi.toml
index 23baeee..48299e9 100644
--- a/Misc/stable_abi.toml
+++ b/Misc/stable_abi.toml
@@ -2397,3 +2397,12 @@
added = '3.12' # Before 3.12, available in "structmember.h" w/o Py_ prefix
[const.Py_AUDIT_READ]
added = '3.12' # Before 3.12, available in "structmember.h"
+
+[function.PyObject_GetTypeData]
+ added = '3.12'
+[function.PyType_GetTypeDataSize]
+ added = '3.12'
+[const.Py_RELATIVE_OFFSET]
+ added = '3.12'
+[const.Py_TPFLAGS_ITEMS_AT_END]
+ added = '3.12'
diff --git a/Modules/Setup.stdlib.in b/Modules/Setup.stdlib.in
index a90c1e9..6b48334 100644
--- a/Modules/Setup.stdlib.in
+++ b/Modules/Setup.stdlib.in
@@ -169,7 +169,7 @@
@MODULE__XXTESTFUZZ_TRUE@_xxtestfuzz _xxtestfuzz/_xxtestfuzz.c _xxtestfuzz/fuzzer.c
@MODULE__TESTBUFFER_TRUE@_testbuffer _testbuffer.c
@MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c
-@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/vectorcall_limited.c _testcapi/heaptype.c _testcapi/unicode.c _testcapi/getargs.c _testcapi/pytime.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c _testcapi/pyos.c _testcapi/immortal.c
+@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/vectorcall_limited.c _testcapi/heaptype.c _testcapi/unicode.c _testcapi/getargs.c _testcapi/pytime.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c _testcapi/pyos.c _testcapi/immortal.c _testcapi/heaptype_relative.c
@MODULE__TESTCLINIC_TRUE@_testclinic _testclinic.c
# Some testing modules MUST be built as shared libraries.
diff --git a/Modules/_testcapi/heaptype.c b/Modules/_testcapi/heaptype.c
index 6384fbc..3488e35 100644
--- a/Modules/_testcapi/heaptype.c
+++ b/Modules/_testcapi/heaptype.c
@@ -371,7 +371,6 @@ create_type_from_repeated_slots(PyObject *self, PyObject *variant_obj)
}
-
static PyObject *
make_immutable_type_with_base(PyObject *self, PyObject *base)
{
@@ -399,6 +398,17 @@ make_type_with_base(PyObject *self, PyObject *base)
}
+static PyObject *
+pyobject_getitemdata(PyObject *self, PyObject *o)
+{
+ void *pointer = PyObject_GetItemData(o);
+ if (pointer == NULL) {
+ return NULL;
+ }
+ return PyLong_FromVoidPtr(pointer);
+}
+
+
static PyMethodDef TestMethods[] = {
{"pytype_fromspec_meta", pytype_fromspec_meta, METH_O},
{"test_type_from_ephemeral_spec", test_type_from_ephemeral_spec, METH_NOARGS},
@@ -411,6 +421,7 @@ static PyMethodDef TestMethods[] = {
METH_NOARGS},
{"make_immutable_type_with_base", make_immutable_type_with_base, METH_O},
{"make_type_with_base", make_type_with_base, METH_O},
+ {"pyobject_getitemdata", pyobject_getitemdata, METH_O},
{NULL},
};
@@ -987,6 +998,113 @@ static PyType_Spec HeapCTypeSetattr_spec = {
HeapCTypeSetattr_slots
};
+PyDoc_STRVAR(HeapCCollection_doc,
+"Tuple-like heap type that uses PyObject_GetItemData for items.");
+
+static PyObject*
+HeapCCollection_new(PyTypeObject *subtype, PyObject *args, PyObject *kwds)
+{
+ PyObject *self = NULL;
+ PyObject *result = NULL;
+
+ Py_ssize_t size = PyTuple_GET_SIZE(args);
+ self = subtype->tp_alloc(subtype, size);
+ if (!self) {
+ goto finally;
+ }
+ PyObject **data = PyObject_GetItemData(self);
+ if (!data) {
+ goto finally;
+ }
+
+ for (Py_ssize_t i = 0; i < size; i++) {
+ data[i] = Py_NewRef(PyTuple_GET_ITEM(args, i));
+ }
+
+ result = self;
+ self = NULL;
+ finally:
+ Py_XDECREF(self);
+ return result;
+}
+
+static Py_ssize_t
+HeapCCollection_length(PyVarObject *self)
+{
+ return Py_SIZE(self);
+}
+
+static PyObject*
+HeapCCollection_item(PyObject *self, Py_ssize_t i)
+{
+ if (i < 0 || i >= Py_SIZE(self)) {
+ return PyErr_Format(PyExc_IndexError, "index %zd out of range", i);
+ }
+ PyObject **data = PyObject_GetItemData(self);
+ if (!data) {
+ return NULL;
+ }
+ return Py_NewRef(data[i]);
+}
+
+static int
+HeapCCollection_traverse(PyObject *self, visitproc visit, void *arg)
+{
+ PyObject **data = PyObject_GetItemData(self);
+ if (!data) {
+ return -1;
+ }
+ for (Py_ssize_t i = 0; i < Py_SIZE(self); i++) {
+ Py_VISIT(data[i]);
+ }
+ return 0;
+}
+
+static int
+HeapCCollection_clear(PyObject *self)
+{
+ PyObject **data = PyObject_GetItemData(self);
+ if (!data) {
+ return -1;
+ }
+ Py_ssize_t size = Py_SIZE(self);
+ Py_SET_SIZE(self, 0);
+ for (Py_ssize_t i = 0; i < size; i++) {
+ Py_CLEAR(data[i]);
+ }
+ return 0;
+}
+
+static void
+HeapCCollection_dealloc(PyObject *self)
+{
+ PyTypeObject *tp = Py_TYPE(self);
+ HeapCCollection_clear(self);
+ PyObject_GC_UnTrack(self);
+ tp->tp_free(self);
+ Py_DECREF(tp);
+}
+
+static PyType_Slot HeapCCollection_slots[] = {
+ {Py_tp_new, HeapCCollection_new},
+ {Py_sq_length, HeapCCollection_length},
+ {Py_sq_item, HeapCCollection_item},
+ {Py_tp_traverse, HeapCCollection_traverse},
+ {Py_tp_clear, HeapCCollection_clear},
+ {Py_tp_dealloc, HeapCCollection_dealloc},
+ {Py_tp_doc, (void *)HeapCCollection_doc},
+ {0, 0},
+};
+
+static PyType_Spec HeapCCollection_spec = {
+ .name = "_testcapi.HeapCCollection",
+ .basicsize = sizeof(PyVarObject),
+ .itemsize = sizeof(PyObject*),
+ .flags = (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE |
+ Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_ITEMS_AT_END),
+ .slots = HeapCCollection_slots,
+};
+
int
_PyTestCapi_Init_Heaptype(PyObject *m) {
_testcapimodule = PyModule_GetDef(m);
@@ -1110,5 +1228,16 @@ _PyTestCapi_Init_Heaptype(PyObject *m) {
}
PyModule_AddObject(m, "HeapCTypeMetaclassCustomNew", HeapCTypeMetaclassCustomNew);
+ PyObject *HeapCCollection = PyType_FromMetaclass(
+ NULL, m, &HeapCCollection_spec, NULL);
+ if (HeapCCollection == NULL) {
+ return -1;
+ }
+ int rc = PyModule_AddType(m, (PyTypeObject *)HeapCCollection);
+ Py_DECREF(HeapCCollection);
+ if (rc < 0) {
+ return -1;
+ }
+
return 0;
}
diff --git a/Modules/_testcapi/heaptype_relative.c b/Modules/_testcapi/heaptype_relative.c
new file mode 100644
index 0000000..c247ca3
--- /dev/null
+++ b/Modules/_testcapi/heaptype_relative.c
@@ -0,0 +1,343 @@
+#define Py_LIMITED_API 0x030c0000 // 3.12
+#include "parts.h"
+#include <stddef.h> // max_align_t
+#include <string.h> // memset
+
+#ifdef LIMITED_API_AVAILABLE
+
+static PyType_Slot empty_slots[] = {
+ {0, NULL},
+};
+
+static PyObject *
+make_sized_heaptypes(PyObject *module, PyObject *args)
+{
+ PyObject *base = NULL;
+ PyObject *sub = NULL;
+ PyObject *instance = NULL;
+ PyObject *result = NULL;
+
+ int extra_base_size, basicsize;
+
+ int r = PyArg_ParseTuple(args, "ii", &extra_base_size, &basicsize);
+ if (!r) {
+ goto finally;
+ }
+
+ PyType_Spec base_spec = {
+ .name = "_testcapi.Base",
+ .basicsize = sizeof(PyObject) + extra_base_size,
+ .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
+ .slots = empty_slots,
+ };
+ PyType_Spec sub_spec = {
+ .name = "_testcapi.Sub",
+ .basicsize = basicsize,
+ .flags = Py_TPFLAGS_DEFAULT,
+ .slots = empty_slots,
+ };
+
+ base = PyType_FromMetaclass(NULL, module, &base_spec, NULL);
+ if (!base) {
+ goto finally;
+ }
+ sub = PyType_FromMetaclass(NULL, module, &sub_spec, base);
+ if (!sub) {
+ goto finally;
+ }
+ instance = PyObject_CallNoArgs(sub);
+ if (!instance) {
+ goto finally;
+ }
+ char *data_ptr = PyObject_GetTypeData(instance, (PyTypeObject *)sub);
+ if (!data_ptr) {
+ goto finally;
+ }
+ Py_ssize_t data_size = PyType_GetTypeDataSize((PyTypeObject *)sub);
+ if (data_size < 0) {
+ goto finally;
+ }
+
+ result = Py_BuildValue("OOOKnn", base, sub, instance,
+ (unsigned long long)data_ptr,
+ (Py_ssize_t)(data_ptr - (char*)instance),
+ data_size);
+ finally:
+ Py_XDECREF(base);
+ Py_XDECREF(sub);
+ Py_XDECREF(instance);
+ return result;
+}
+
+static PyObject *
+var_heaptype_set_data_to_3s(
+ PyObject *self, PyTypeObject *defining_class,
+ PyObject **args, Py_ssize_t nargs, PyObject *kwnames)
+{
+ void *data_ptr = PyObject_GetTypeData(self, defining_class);
+ if (!data_ptr) {
+ return NULL;
+ }
+ Py_ssize_t data_size = PyType_GetTypeDataSize(defining_class);
+ if (data_size < 0) {
+ return NULL;
+ }
+ memset(data_ptr, 3, data_size);
+ Py_RETURN_NONE;
+}
+
+static PyObject *
+var_heaptype_get_data(PyObject *self, PyTypeObject *defining_class,
+ PyObject **args, Py_ssize_t nargs, PyObject *kwnames)
+{
+ void *data_ptr = PyObject_GetTypeData(self, defining_class);
+ if (!data_ptr) {
+ return NULL;
+ }
+ Py_ssize_t data_size = PyType_GetTypeDataSize(defining_class);
+ if (data_size < 0) {
+ return NULL;
+ }
+ return PyBytes_FromStringAndSize(data_ptr, data_size);
+}
+
+static PyMethodDef var_heaptype_methods[] = {
+ {"set_data_to_3s", _PyCFunction_CAST(var_heaptype_set_data_to_3s),
+ METH_METHOD | METH_FASTCALL | METH_KEYWORDS},
+ {"get_data", _PyCFunction_CAST(var_heaptype_get_data),
+ METH_METHOD | METH_FASTCALL | METH_KEYWORDS},
+ {NULL},
+};
+
+static PyObject *
+subclass_var_heaptype(PyObject *module, PyObject *args)
+{
+ PyObject *result = NULL;
+
+ PyObject *base; // borrowed from args
+ int basicsize, itemsize;
+ long pfunc;
+
+ int r = PyArg_ParseTuple(args, "Oiil", &base, &basicsize, &itemsize, &pfunc);
+ if (!r) {
+ goto finally;
+ }
+
+ PyType_Slot slots[] = {
+ {Py_tp_methods, var_heaptype_methods},
+ {0, NULL},
+ };
+
+ PyType_Spec sub_spec = {
+ .name = "_testcapi.Sub",
+ .basicsize = basicsize,
+ .itemsize = itemsize,
+ .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_ITEMS_AT_END,
+ .slots = slots,
+ };
+
+ result = PyType_FromMetaclass(NULL, module, &sub_spec, base);
+ finally:
+ return result;
+}
+
+static PyObject *
+subclass_heaptype(PyObject *module, PyObject *args)
+{
+ PyObject *result = NULL;
+
+ PyObject *base; // borrowed from args
+ int basicsize, itemsize;
+
+ int r = PyArg_ParseTuple(args, "Oii", &base, &basicsize, &itemsize);
+ if (!r) {
+ goto finally;
+ }
+
+ PyType_Slot slots[] = {
+ {Py_tp_methods, var_heaptype_methods},
+ {0, NULL},
+ };
+
+ PyType_Spec sub_spec = {
+ .name = "_testcapi.Sub",
+ .basicsize = basicsize,
+ .itemsize = itemsize,
+ .flags = Py_TPFLAGS_DEFAULT,
+ .slots = slots,
+ };
+
+ result = PyType_FromMetaclass(NULL, module, &sub_spec, base);
+ finally:
+ return result;
+}
+
+static PyMemberDef *
+heaptype_with_member_extract_and_check_memb(PyObject *self)
+{
+ PyMemberDef *def = PyType_GetSlot(Py_TYPE(self), Py_tp_members);
+ if (!def) {
+ if (!PyErr_Occurred()) {
+ PyErr_SetString(PyExc_ValueError, "tp_members is NULL");
+ }
+ return NULL;
+ }
+ if (!def[0].name) {
+ PyErr_SetString(PyExc_ValueError, "tp_members[0] is NULL");
+ return NULL;
+ }
+ if (def[1].name) {
+ PyErr_SetString(PyExc_ValueError, "tp_members[1] is not NULL");
+ return NULL;
+ }
+ if (strcmp(def[0].name, "memb")) {
+ PyErr_SetString(PyExc_ValueError, "tp_members[0] is not for `memb`");
+ return NULL;
+ }
+ if (def[0].flags) {
+ PyErr_SetString(PyExc_ValueError, "tp_members[0] has flags set");
+ return NULL;
+ }
+ return def;
+}
+
+static PyObject *
+heaptype_with_member_get_memb(PyObject *self, PyObject *Py_UNUSED(ignored))
+{
+ PyMemberDef *def = heaptype_with_member_extract_and_check_memb(self);
+ return PyMember_GetOne((const char *)self, def);
+}
+
+static PyObject *
+heaptype_with_member_set_memb(PyObject *self, PyObject *value)
+{
+ PyMemberDef *def = heaptype_with_member_extract_and_check_memb(self);
+ int r = PyMember_SetOne((char *)self, def, value);
+ if (r < 0) {
+ return NULL;
+ }
+ Py_RETURN_NONE;
+}
+
+static PyObject *
+get_memb_offset(PyObject *self, PyObject *Py_UNUSED(ignored))
+{
+ PyMemberDef *def = heaptype_with_member_extract_and_check_memb(self);
+ return PyLong_FromSsize_t(def->offset);
+}
+
+static PyObject *
+heaptype_with_member_get_memb_relative(PyObject *self, PyObject *Py_UNUSED(ignored))
+{
+ PyMemberDef def = {"memb", Py_T_BYTE, sizeof(PyObject), Py_RELATIVE_OFFSET};
+ return PyMember_GetOne((const char *)self, &def);
+}
+
+static PyObject *
+heaptype_with_member_set_memb_relative(PyObject *self, PyObject *value)
+{
+ PyMemberDef def = {"memb", Py_T_BYTE, sizeof(PyObject), Py_RELATIVE_OFFSET};
+ int r = PyMember_SetOne((char *)self, &def, value);
+ if (r < 0) {
+ return NULL;
+ }
+ Py_RETURN_NONE;
+}
+
+static PyMethodDef heaptype_with_member_methods[] = {
+ {"get_memb", heaptype_with_member_get_memb, METH_NOARGS},
+ {"set_memb", heaptype_with_member_set_memb, METH_O},
+ {"get_memb_offset", get_memb_offset, METH_NOARGS},
+ {"get_memb_relative", heaptype_with_member_get_memb_relative, METH_NOARGS},
+ {"set_memb_relative", heaptype_with_member_set_memb_relative, METH_O},
+ {NULL},
+};
+
+static PyObject *
+make_heaptype_with_member(PyObject *module, PyObject *args)
+{
+ PyObject *base = NULL;
+ PyObject *result = NULL;
+
+ int extra_base_size, basicsize, offset, add_flag;
+
+ int r = PyArg_ParseTuple(args, "iiip", &extra_base_size, &basicsize, &offset, &add_flag);
+ if (!r) {
+ goto finally;
+ }
+
+ PyType_Spec base_spec = {
+ .name = "_testcapi.Base",
+ .basicsize = sizeof(PyObject) + extra_base_size,
+ .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
+ .slots = empty_slots,
+ };
+ base = PyType_FromMetaclass(NULL, module, &base_spec, NULL);
+ if (!base) {
+ goto finally;
+ }
+
+ PyMemberDef members[] = {
+ {"memb", Py_T_BYTE, offset, add_flag ? Py_RELATIVE_OFFSET : 0},
+ {0},
+ };
+ PyType_Slot slots[] = {
+ {Py_tp_members, members},
+ {Py_tp_methods, heaptype_with_member_methods},
+ {0, NULL},
+ };
+
+ PyType_Spec sub_spec = {
+ .name = "_testcapi.Sub",
+ .basicsize = basicsize,
+ .flags = Py_TPFLAGS_DEFAULT,
+ .slots = slots,
+ };
+
+ result = PyType_FromMetaclass(NULL, module, &sub_spec, base);
+ finally:
+ Py_XDECREF(base);
+ return result;
+}
+
+
+static PyObject *
+test_alignof_max_align_t(PyObject *module, PyObject *Py_UNUSED(ignored))
+{
+ // We define ALIGNOF_MAX_ALIGN_T even if the compiler doesn't support
+ // max_align_t. Double-check that it's correct.
+ assert(ALIGNOF_MAX_ALIGN_T > 0);
+ assert(ALIGNOF_MAX_ALIGN_T >= _Alignof(long long));
+ assert(ALIGNOF_MAX_ALIGN_T >= _Alignof(long double));
+ assert(ALIGNOF_MAX_ALIGN_T >= _Alignof(void*));
+ assert(ALIGNOF_MAX_ALIGN_T >= _Alignof(void (*)(void)));
+
+ // Ensure it's a power of two
+ assert((ALIGNOF_MAX_ALIGN_T & (ALIGNOF_MAX_ALIGN_T - 1)) == 0);
+
+ Py_RETURN_NONE;
+}
+
+static PyMethodDef TestMethods[] = {
+ {"make_sized_heaptypes", make_sized_heaptypes, METH_VARARGS},
+ {"subclass_var_heaptype", subclass_var_heaptype, METH_VARARGS},
+ {"subclass_heaptype", subclass_heaptype, METH_VARARGS},
+ {"make_heaptype_with_member", make_heaptype_with_member, METH_VARARGS},
+ {"test_alignof_max_align_t", test_alignof_max_align_t, METH_NOARGS},
+ {NULL},
+};
+
+int
+_PyTestCapi_Init_HeaptypeRelative(PyObject *m) {
+ if (PyModule_AddFunctions(m, TestMethods) < 0) {
+ return -1;
+ }
+
+ if (PyModule_AddIntMacro(m, ALIGNOF_MAX_ALIGN_T) < 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
+#endif // LIMITED_API_AVAILABLE
diff --git a/Modules/_testcapi/parts.h b/Modules/_testcapi/parts.h
index 4d2d683..d75412d 100644
--- a/Modules/_testcapi/parts.h
+++ b/Modules/_testcapi/parts.h
@@ -43,6 +43,7 @@ int _PyTestCapi_Init_Immortal(PyObject *module);
#ifdef LIMITED_API_AVAILABLE
int _PyTestCapi_Init_VectorcallLimited(PyObject *module);
+int _PyTestCapi_Init_HeaptypeRelative(PyObject *module);
#endif // LIMITED_API_AVAILABLE
#endif
diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c
index 47e0ed9..1ecc442 100644
--- a/Modules/_testcapimodule.c
+++ b/Modules/_testcapimodule.c
@@ -4324,6 +4324,9 @@ PyInit__testcapi(void)
if (_PyTestCapi_Init_VectorcallLimited(m) < 0) {
return NULL;
}
+ if (_PyTestCapi_Init_HeaptypeRelative(m) < 0) {
+ return NULL;
+ }
#endif
PyState_AddModule(m, &_testcapimodule);
diff --git a/Objects/descrobject.c b/Objects/descrobject.c
index 334be75..17c0c85 100644
--- a/Objects/descrobject.c
+++ b/Objects/descrobject.c
@@ -978,6 +978,12 @@ PyDescr_NewMember(PyTypeObject *type, PyMemberDef *member)
{
PyMemberDescrObject *descr;
+ if (member->flags & Py_RELATIVE_OFFSET) {
+ PyErr_SetString(
+ PyExc_SystemError,
+ "PyDescr_NewMember used with Py_RELATIVE_OFFSET");
+ return NULL;
+ }
descr = (PyMemberDescrObject *)descr_new(&PyMemberDescr_Type,
type, member->name);
if (descr != NULL)
diff --git a/Objects/typeobject.c b/Objects/typeobject.c
index 4ced04b..171c76a 100644
--- a/Objects/typeobject.c
+++ b/Objects/typeobject.c
@@ -18,6 +18,7 @@
#include "structmember.h" // PyMemberDef
#include <ctype.h>
+#include <stddef.h> // ptrdiff_t
/*[clinic input]
class type "PyTypeObject *" "&PyType_Type"
@@ -1686,6 +1687,12 @@ PyType_GenericNew(PyTypeObject *type, PyObject *args, PyObject *kwds)
/* Helpers for subtyping */
+static inline PyMemberDef *
+_PyHeapType_GET_MEMBERS(PyHeapTypeObject* type)
+{
+ return PyObject_GetItemData((PyObject *)type);
+}
+
static int
traverse_slots(PyTypeObject *type, PyObject *self, visitproc visit, void *arg)
{
@@ -3873,6 +3880,15 @@ static const PySlot_Offset pyslot_offsets[] = {
#include "typeslots.inc"
};
+/* Align up to the nearest multiple of alignof(max_align_t)
+ * (like _Py_ALIGN_UP, but for a size rather than pointer)
+ */
+static Py_ssize_t
+_align_up(Py_ssize_t size)
+{
+ return (size + ALIGNOF_MAX_ALIGN_T - 1) & ~(ALIGNOF_MAX_ALIGN_T - 1);
+}
+
/* Given a PyType_FromMetaclass `bases` argument (NULL, type, or tuple of
* types), return a tuple of types.
*/
@@ -4013,6 +4029,20 @@ _PyType_FromMetaclass_impl(
assert(memb->flags == READONLY);
vectorcalloffset = memb->offset;
}
+ if (memb->flags & Py_RELATIVE_OFFSET) {
+ if (spec->basicsize > 0) {
+ PyErr_SetString(
+ PyExc_SystemError,
+ "With Py_RELATIVE_OFFSET, basicsize must be negative.");
+ goto finally;
+ }
+ if (memb->offset < 0 || memb->offset >= -spec->basicsize) {
+ PyErr_SetString(
+ PyExc_SystemError,
+ "Member offset out of range (0..-basicsize)");
+ goto finally;
+ }
+ }
}
break;
case Py_tp_doc:
@@ -4154,6 +4184,32 @@ _PyType_FromMetaclass_impl(
// here we just check its work
assert(_PyType_HasFeature(base, Py_TPFLAGS_BASETYPE));
+ /* Calculate sizes */
+
+ Py_ssize_t basicsize = spec->basicsize;
+ Py_ssize_t type_data_offset = spec->basicsize;
+ if (basicsize == 0) {
+ /* Inherit */
+ basicsize = base->tp_basicsize;
+ }
+ else if (basicsize < 0) {
+ /* Extend */
+ type_data_offset = _align_up(base->tp_basicsize);
+ basicsize = type_data_offset + _align_up(-spec->basicsize);
+
+ /* Inheriting variable-sized types is limited */
+ if (base->tp_itemsize
+ && !((base->tp_flags | spec->flags) & Py_TPFLAGS_ITEMS_AT_END))
+ {
+ PyErr_SetString(
+ PyExc_SystemError,
+ "Cannot extend variable-size class without Py_TPFLAGS_ITEMS_AT_END.");
+ goto finally;
+ }
+ }
+
+ Py_ssize_t itemsize = spec->itemsize;
+
/* Allocate the new type
*
* Between here and PyType_Ready, we should limit:
@@ -4201,8 +4257,8 @@ _PyType_FromMetaclass_impl(
/* Copy the sizes */
- type->tp_basicsize = spec->basicsize;
- type->tp_itemsize = spec->itemsize;
+ type->tp_basicsize = basicsize;
+ type->tp_itemsize = itemsize;
/* Copy all the ordinary slots */
@@ -4219,6 +4275,16 @@ _PyType_FromMetaclass_impl(
size_t len = Py_TYPE(type)->tp_itemsize * nmembers;
memcpy(_PyHeapType_GET_MEMBERS(res), slot->pfunc, len);
type->tp_members = _PyHeapType_GET_MEMBERS(res);
+ PyMemberDef *memb;
+ Py_ssize_t i;
+ for (memb = _PyHeapType_GET_MEMBERS(res), i = nmembers;
+ i > 0; ++memb, --i)
+ {
+ if (memb->flags & Py_RELATIVE_OFFSET) {
+ memb->flags &= ~Py_RELATIVE_OFFSET;
+ memb->offset += type_data_offset;
+ }
+ }
}
break;
default:
@@ -4227,6 +4293,7 @@ _PyType_FromMetaclass_impl(
PySlot_Offset slotoffsets = pyslot_offsets[slot->slot];
short slot_offset = slotoffsets.slot_offset;
if (slotoffsets.subslot_offset == -1) {
+ /* Set a slot in the main PyTypeObject */
*(void**)((char*)res_start + slot_offset) = slot->pfunc;
}
else {
@@ -4461,6 +4528,34 @@ PyType_GetModuleByDef(PyTypeObject *type, PyModuleDef *def)
return NULL;
}
+void *
+PyObject_GetTypeData(PyObject *obj, PyTypeObject *cls)
+{
+ assert(PyObject_TypeCheck(obj, cls));
+ return (char *)obj + _align_up(cls->tp_base->tp_basicsize);
+}
+
+Py_ssize_t
+PyType_GetTypeDataSize(PyTypeObject *cls)
+{
+ ptrdiff_t result = cls->tp_basicsize - _align_up(cls->tp_base->tp_basicsize);
+ if (result < 0) {
+ return 0;
+ }
+ return result;
+}
+
+void *
+PyObject_GetItemData(PyObject *obj)
+{
+ if (!PyType_HasFeature(Py_TYPE(obj), Py_TPFLAGS_ITEMS_AT_END)) {
+ PyErr_Format(PyExc_TypeError,
+ "type '%s' does not have Py_TPFLAGS_ITEMS_AT_END",
+ Py_TYPE(obj)->tp_name);
+ return NULL;
+ }
+ return (char *)obj + Py_TYPE(obj)->tp_basicsize;
+}
/* Internal API to look for a name through the MRO, bypassing the method cache.
This returns a borrowed reference, and might set an exception.
@@ -5158,7 +5253,8 @@ PyTypeObject PyType_Type = {
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC |
Py_TPFLAGS_BASETYPE | Py_TPFLAGS_TYPE_SUBCLASS |
- Py_TPFLAGS_HAVE_VECTORCALL, /* tp_flags */
+ Py_TPFLAGS_HAVE_VECTORCALL |
+ Py_TPFLAGS_ITEMS_AT_END, /* tp_flags */
type_doc, /* tp_doc */
(traverseproc)type_traverse, /* tp_traverse */
(inquiry)type_clear, /* tp_clear */
@@ -6572,9 +6668,14 @@ inherit_special(PyTypeObject *type, PyTypeObject *base)
else if (PyType_IsSubtype(base, &PyDict_Type)) {
type->tp_flags |= Py_TPFLAGS_DICT_SUBCLASS;
}
+
+ /* Setup some inheritable flags */
if (PyType_HasFeature(base, _Py_TPFLAGS_MATCH_SELF)) {
type->tp_flags |= _Py_TPFLAGS_MATCH_SELF;
}
+ if (PyType_HasFeature(base, Py_TPFLAGS_ITEMS_AT_END)) {
+ type->tp_flags |= Py_TPFLAGS_ITEMS_AT_END;
+ }
}
static int
diff --git a/PC/pyconfig.h b/PC/pyconfig.h
index 8a3bf89..3415efe 100644
--- a/PC/pyconfig.h
+++ b/PC/pyconfig.h
@@ -330,6 +330,7 @@ Py_NO_ENABLE_SHARED to find out. Also support MS_NO_COREDLL for b/w compat */
# define SIZEOF_HKEY 8
# define SIZEOF_SIZE_T 8
# define ALIGNOF_SIZE_T 8
+# define ALIGNOF_MAX_ALIGN_T 8
/* configure.ac defines HAVE_LARGEFILE_SUPPORT iff
sizeof(off_t) > sizeof(long), and sizeof(long long) >= sizeof(off_t).
On Win64 the second condition is not true, but if fpos_t replaces off_t
@@ -351,6 +352,7 @@ Py_NO_ENABLE_SHARED to find out. Also support MS_NO_COREDLL for b/w compat */
# else
# define SIZEOF_TIME_T 4
# endif
+# define ALIGNOF_MAX_ALIGN_T 8
#endif
#ifdef _DEBUG
diff --git a/PC/python3dll.c b/PC/python3dll.c
index 706affa..7e848ab 100755
--- a/PC/python3dll.c
+++ b/PC/python3dll.c
@@ -467,6 +467,7 @@ EXPORT_FUNC(PyObject_GetAttrString)
EXPORT_FUNC(PyObject_GetBuffer)
EXPORT_FUNC(PyObject_GetItem)
EXPORT_FUNC(PyObject_GetIter)
+EXPORT_FUNC(PyObject_GetTypeData)
EXPORT_FUNC(PyObject_HasAttr)
EXPORT_FUNC(PyObject_HasAttrString)
EXPORT_FUNC(PyObject_Hash)
@@ -618,6 +619,7 @@ EXPORT_FUNC(PyType_GetModuleState)
EXPORT_FUNC(PyType_GetName)
EXPORT_FUNC(PyType_GetQualName)
EXPORT_FUNC(PyType_GetSlot)
+EXPORT_FUNC(PyType_GetTypeDataSize)
EXPORT_FUNC(PyType_IsSubtype)
EXPORT_FUNC(PyType_Modified)
EXPORT_FUNC(PyType_Ready)
diff --git a/PCbuild/_testcapi.vcxproj b/PCbuild/_testcapi.vcxproj
index 2194124..56448b6 100644
--- a/PCbuild/_testcapi.vcxproj
+++ b/PCbuild/_testcapi.vcxproj
@@ -98,6 +98,7 @@
<ClCompile Include="..\Modules\_testcapi\vectorcall.c" />
<ClCompile Include="..\Modules\_testcapi\vectorcall_limited.c" />
<ClCompile Include="..\Modules\_testcapi\heaptype.c" />
+ <ClCompile Include="..\Modules\_testcapi\heaptype_relative.c" />
<ClCompile Include="..\Modules\_testcapi\unicode.c" />
<ClCompile Include="..\Modules\_testcapi\pytime.c" />
<ClCompile Include="..\Modules\_testcapi\datetime.c" />
diff --git a/PCbuild/_testcapi.vcxproj.filters b/PCbuild/_testcapi.vcxproj.filters
index 0e42e49..297c9ce 100644
--- a/PCbuild/_testcapi.vcxproj.filters
+++ b/PCbuild/_testcapi.vcxproj.filters
@@ -24,6 +24,9 @@
<ClCompile Include="..\Modules\_testcapi\heaptype.c">
<Filter>Source Files</Filter>
</ClCompile>
+ <ClCompile Include="..\Modules\_testcapi\heaptype_relative.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
<ClCompile Include="..\Modules\_testcapi\unicode.c">
<Filter>Source Files</Filter>
</ClCompile>
diff --git a/Python/structmember.c b/Python/structmember.c
index 1b8be28..19a7522 100644
--- a/Python/structmember.c
+++ b/Python/structmember.c
@@ -8,6 +8,12 @@ PyObject *
PyMember_GetOne(const char *obj_addr, PyMemberDef *l)
{
PyObject *v;
+ if (l->flags & Py_RELATIVE_OFFSET) {
+ PyErr_SetString(
+ PyExc_SystemError,
+ "PyMember_GetOne used with Py_RELATIVE_OFFSET");
+ return NULL;
+ }
const char* addr = obj_addr + l->offset;
switch (l->type) {
@@ -103,6 +109,12 @@ int
PyMember_SetOne(char *addr, PyMemberDef *l, PyObject *v)
{
PyObject *oldv;
+ if (l->flags & Py_RELATIVE_OFFSET) {
+ PyErr_SetString(
+ PyExc_SystemError,
+ "PyMember_SetOne used with Py_RELATIVE_OFFSET");
+ return -1;
+ }
addr += l->offset;
diff --git a/configure b/configure
index 8133d47..b8fa9d6 100755
--- a/configure
+++ b/configure
@@ -10729,6 +10729,41 @@ cat >>confdefs.h <<_ACEOF
_ACEOF
+# The cast to long int works around a bug in the HP C Compiler,
+# see AC_CHECK_SIZEOF for more information.
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking alignment of max_align_t" >&5
+$as_echo_n "checking alignment of max_align_t... " >&6; }
+if ${ac_cv_alignof_max_align_t+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if ac_fn_c_compute_int "$LINENO" "(long int) offsetof (ac__type_alignof_, y)" "ac_cv_alignof_max_align_t" "$ac_includes_default
+#ifndef offsetof
+# define offsetof(type, member) ((char *) &((type *) 0)->member - (char *) 0)
+#endif
+typedef struct { char x; max_align_t y; } ac__type_alignof_;"; then :
+
+else
+ if test "$ac_cv_type_max_align_t" = yes; then
+ { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error 77 "cannot compute alignment of max_align_t
+See \`config.log' for more details" "$LINENO" 5; }
+ else
+ ac_cv_alignof_max_align_t=0
+ fi
+fi
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_alignof_max_align_t" >&5
+$as_echo "$ac_cv_alignof_max_align_t" >&6; }
+
+
+
+cat >>confdefs.h <<_ACEOF
+#define ALIGNOF_MAX_ALIGN_T $ac_cv_alignof_max_align_t
+_ACEOF
+
+
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for long double" >&5
diff --git a/configure.ac b/configure.ac
index 3f20d89..0940b93 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2921,6 +2921,7 @@ AC_CHECK_SIZEOF(size_t, 4)
AC_CHECK_ALIGNOF(size_t)
AC_CHECK_SIZEOF(pid_t, 4)
AC_CHECK_SIZEOF(uintptr_t)
+AC_CHECK_ALIGNOF(max_align_t)
AC_TYPE_LONG_DOUBLE
AC_CHECK_SIZEOF(long double, 16)
diff --git a/pyconfig.h.in b/pyconfig.h.in
index 236cee6..2c22b27 100644
--- a/pyconfig.h.in
+++ b/pyconfig.h.in
@@ -19,6 +19,9 @@
/* The normal alignment of `long', in bytes. */
#undef ALIGNOF_LONG
+/* The normal alignment of `max_align_t', in bytes. */
+#undef ALIGNOF_MAX_ALIGN_T
+
/* The normal alignment of `size_t', in bytes. */
#undef ALIGNOF_SIZE_T