From 6c098830d59dc72e12b823b4d85c9580684848fd Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Thu, 8 Oct 2026 14:31:01 -0400 Subject: [PATCH] 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;