From 6c098830d59dc72e12b823b4d85c9580684848fd Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Thu, 8 Oct 2026 14:31:01 -0400 Subject: [PATCH 1/2] fix: match NumPy 3.15 proxy layout on 32-bit free-threaded builds NumPy 2.5.2+ aligns the first field after PyObject_HEAD to 8 bytes when built for Python 3.15+. On 32-bit free-threaded builds sizeof(PyObject) is 20, so the proxy structs read every field 4 bytes too early. Fixes #6196. Assisted-by: ClaudeCode:claude-opus-5-5 --- include/pybind11/numpy.h | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/include/pybind11/numpy.h b/include/pybind11/numpy.h index a26ac2b4cd..e1860c6e59 100644 --- a/include/pybind11/numpy.h +++ b/include/pybind11/numpy.h @@ -71,10 +71,18 @@ struct handle_type_name { template struct npy_format_descriptor; +// NumPy 2.5.2+ aligns the first field after PyObject_HEAD to 8 bytes on Python 3.15+. +// This changes the layout on 32-bit free-threaded builds, where sizeof(PyObject) == 20. +#if PY_VERSION_HEX >= 0x030F0000 +# define PYBIND11_NPY_FIRST_FIELD alignas(8) +#else +# define PYBIND11_NPY_FIRST_FIELD +#endif + /* NumPy 1 proxy (always includes legacy fields) */ struct PyArrayDescr1_Proxy { PyObject_HEAD - PyObject *typeobj; + PYBIND11_NPY_FIRST_FIELD PyObject *typeobj; char kind; char type; char byteorder; @@ -89,7 +97,7 @@ struct PyArrayDescr1_Proxy { struct PyArrayDescr_Proxy { PyObject_HEAD - PyObject *typeobj; + PYBIND11_NPY_FIRST_FIELD PyObject *typeobj; char kind; char type; char byteorder; @@ -101,7 +109,7 @@ struct PyArrayDescr_Proxy { /* NumPy 2 proxy, including legacy fields */ struct PyArrayDescr2_Proxy { PyObject_HEAD - PyObject *typeobj; + PYBIND11_NPY_FIRST_FIELD PyObject *typeobj; char kind; char type; char byteorder; @@ -121,7 +129,7 @@ struct PyArrayDescr2_Proxy { struct PyArray_Proxy { PyObject_HEAD - char *data; + PYBIND11_NPY_FIRST_FIELD char *data; int nd; ssize_t *dimensions; ssize_t *strides; @@ -130,6 +138,8 @@ struct PyArray_Proxy { int flags; }; +#undef PYBIND11_NPY_FIRST_FIELD + struct PyVoidScalarObject_Proxy { PyObject_VAR_HEAD char *obval; PyArrayDescr_Proxy *descr; From 066d88f1f247dc81c097165358696a343efc9ebf Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Tue, 20 Oct 2026 16:02:07 -0400 Subject: [PATCH 2/2] Update include/pybind11/numpy.h Co-authored-by: Ralf W. Grosse-Kunstleve --- include/pybind11/numpy.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/include/pybind11/numpy.h b/include/pybind11/numpy.h index e1860c6e59..0c99b0973d 100644 --- a/include/pybind11/numpy.h +++ b/include/pybind11/numpy.h @@ -72,7 +72,10 @@ template struct npy_format_descriptor; // NumPy 2.5.2+ aligns the first field after PyObject_HEAD to 8 bytes on Python 3.15+. -// This changes the layout on 32-bit free-threaded builds, where sizeof(PyObject) == 20. +// NumPy 2.5.2, the first release supporting Python 3.15, aligns the first +// field after PyObject_HEAD to 8 bytes on Python 3.15+. +// This assumes a supported NumPy/Python pairing: older NumPy builds on +// 32-bit free-threaded Python 3.15 have an incompatible layout. #if PY_VERSION_HEX >= 0x030F0000 # define PYBIND11_NPY_FIRST_FIELD alignas(8) #else