From 392f6c5bc8e9548e83b00d1ca6b2223345d8c2ec Mon Sep 17 00:00:00 2001 From: Finn Thain Date: Tue, 3 Dec 2024 11:32:55 +1100 Subject: [PATCH] Specify minimum PyGC_Head and PyObject alignment to fix build failure As documented in InternalDocs/garbage_collector.md, the garbage collector stores flags in the least significant two bits of the _gc_prev pointer in struct PyGC_Head. Consequently, this pointer is only capable of storing a location that's aligned to a 4-byte boundary. This alignment requirement is documented but it's not actually encoded. The code only works when python happens to run on a platform that has a sufficiently large minimum alignment for the structs in question. The same problem arises with PyObject pointers because the least significant bits get used for PyStackRef tags. Since we know that 2 bits are needed, we also know the minimum alignment that's needed. Let's make that explicit, so the compiler can then make those bits available. This patch fixes a segfault in _bootstrap_python. In 3.14.0 beta 2 this fixes the "Assertion `!PyStackRef_IsTaggedInt(ref)' failed" when built with --config-pydebug. Also, making the requirements explicit improves clarity. This bug was previously investigated by Adrian Glaubitz here: https://lists.debian.org/debian-68k/2024/11/msg00020.html https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1087600 Although Adrian's patch isn't really correct (because natural alignment is not needed), he deserves full credit for finding the root cause. --- Include/internal/pycore_gc.h | 2 +- Include/internal/pycore_interp_structs.h | 3 ++- Include/object.h | 9 +++++++-- .../Build/2024-12-04-10-00-35.gh-issue-127545.t0THjE.rst | 1 + 4 files changed, 11 insertions(+), 4 deletions(-) create mode 100644 Misc/NEWS.d/next/Build/2024-12-04-10-00-35.gh-issue-127545.t0THjE.rst diff --git a/Include/internal/pycore_gc.h b/Include/internal/pycore_gc.h index a6519aa086309d..2f65ddd47076f1 100644 --- a/Include/internal/pycore_gc.h +++ b/Include/internal/pycore_gc.h @@ -133,7 +133,7 @@ static inline void _PyObject_GC_SET_SHARED(PyObject *op) { */ #define _PyGC_NEXT_MASK_OLD_SPACE_1 1 -#define _PyGC_PREV_SHIFT 2 +#define _PyGC_PREV_SHIFT _PyObject_ALIGNMENT_SHIFT #define _PyGC_PREV_MASK (((uintptr_t) -1) << _PyGC_PREV_SHIFT) /* set for debugging information */ diff --git a/Include/internal/pycore_interp_structs.h b/Include/internal/pycore_interp_structs.h index f25f5847b3b307..88df75c8774382 100644 --- a/Include/internal/pycore_interp_structs.h +++ b/Include/internal/pycore_interp_structs.h @@ -163,8 +163,9 @@ typedef struct { // Tagged pointer to previous object in the list. // Lowest two bits are used for flags documented later. + // Those bits are made available by the struct's minimum alignment. uintptr_t _gc_prev; -} PyGC_Head; +} PyGC_Head Py_ALIGNED(1 << _PyObject_ALIGNMENT_SHIFT); #define _PyGC_Head_UNUSED PyGC_Head diff --git a/Include/object.h b/Include/object.h index 994cac1ad17501..37b93679d34477 100644 --- a/Include/object.h +++ b/Include/object.h @@ -101,6 +101,11 @@ whose size is determined when the object is allocated. #define PyObject_VAR_HEAD PyVarObject ob_base; #define Py_INVALID_SIZE (Py_ssize_t)-1 +/* PyObjects are given a minimum alignment so that the least significant bits + * of an object pointer become available for other purposes. + */ +#define _PyObject_ALIGNMENT_SHIFT 2 + /* Nothing is actually declared to be a PyObject, but every pointer to * a Python object can be cast to a PyObject*. This is inheritance built * by hand. Similarly every pointer to a variable-size Python object can, @@ -142,7 +147,7 @@ struct _object { #endif PyTypeObject *ob_type; -}; +} Py_ALIGNED(1 << _PyObject_ALIGNMENT_SHIFT); #else // Objects that are not owned by any thread use a thread id (tid) of zero. // This includes both immortal objects and objects whose reference count @@ -160,7 +165,7 @@ struct _object { uint32_t ob_ref_local; // local reference count Py_ssize_t ob_ref_shared; // shared (atomic) reference count PyTypeObject *ob_type; -}; +} Py_ALIGNED(1 << _PyObject_ALIGNMENT_SHIFT); #endif /* Cast argument to PyObject* type. */ diff --git a/Misc/NEWS.d/next/Build/2024-12-04-10-00-35.gh-issue-127545.t0THjE.rst b/Misc/NEWS.d/next/Build/2024-12-04-10-00-35.gh-issue-127545.t0THjE.rst new file mode 100644 index 00000000000000..3667e2778b7b93 --- /dev/null +++ b/Misc/NEWS.d/next/Build/2024-12-04-10-00-35.gh-issue-127545.t0THjE.rst @@ -0,0 +1 @@ +Fix crash when building on Linux/m68k.