From fb4cb6b7f0d7d5d551b668e7f7fb4cffc4eca6b7 Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Wed, 19 Mar 2025 01:23:54 +0500 Subject: [PATCH 1/3] Extract _copy_array_elements from PyCStructUnionType_update_stginfo --- Modules/_ctypes/stgdict.c | 464 ++++++++++++++++++++------------------ 1 file changed, 243 insertions(+), 221 deletions(-) diff --git a/Modules/_ctypes/stgdict.c b/Modules/_ctypes/stgdict.c index 107716d85fed2d..8f579507d0ef2b 100644 --- a/Modules/_ctypes/stgdict.c +++ b/Modules/_ctypes/stgdict.c @@ -210,6 +210,246 @@ MakeAnonFields(PyObject *type) return 0; } +/* + Copy array elements to stginfo->ffi_type_pointer.elements. Return -1 if error + occured. +*/ +int +_copy_array_elements(ctypes_state *st, int arrays_seen, Py_ssize_t total_size, + PyObject *layout_fields, Py_ssize_t ffi_ofs, + StgInfo *baseinfo, StgInfo *stginfo) +{ + void *type_block = NULL; /* to hold all the type information needed */ + +/* + * The value of MAX_STRUCT_SIZE depends on the platform Python is running on. + */ +#if defined(__aarch64__) || defined(__arm__) || defined(_M_ARM64) || defined(__sparc__) +# define MAX_STRUCT_SIZE 32 +#elif defined(__powerpc64__) +# define MAX_STRUCT_SIZE 64 +#else +# define MAX_STRUCT_SIZE 16 +#endif + + if (arrays_seen && (total_size <= MAX_STRUCT_SIZE)) { + /* + * See bpo-22273 and gh-110190. Arrays are normally treated as + * pointers, which is fine when an array name is being passed as + * parameter, but not when passing structures by value that contain + * arrays. + * Small structures passed by value are passed in registers, and in + * order to do this, libffi needs to know the true type of the array + * members of structs. Treating them as pointers breaks things. + * + * Small structures have different sizes depending on the platform + * where Python is running on: + * + * * x86-64: 16 bytes or less + * * Arm platforms (both 32 and 64 bit): 32 bytes or less + * * PowerPC 64 Little Endian: 64 bytes or less + * + * In that case, there can't be more than 16, 32 or 64 elements after + * unrolling arrays, as we (will) disallow bitfields. + * So we can collect the true ffi_type values in a fixed-size local + * array on the stack and, if any arrays were seen, replace the + * ffi_type_pointer.elements with a more accurate set, to allow + * libffi to marshal them into registers correctly. + * It means one more loop over the fields, but if we got here, + * the structure is small, so there aren't too many of those. + * + * Although the passing in registers is specific to the above + * platforms, the array-in-struct vs. pointer problem is general. + * But we restrict the type transformation to small structs + * nonetheless. + * + * Note that although a union may be small in terms of memory usage, it + * could contain many overlapping declarations of arrays, e.g. + * + * union { + * unsigned int_8 foo [16]; + * unsigned uint_8 bar [16]; + * unsigned int_16 baz[8]; + * unsigned uint_16 bozz[8]; + * unsigned int_32 fizz[4]; + * unsigned uint_32 buzz[4]; + * } + * + * which is still only 16 bytes in size. We need to convert this into + * the following equivalent for libffi: + * + * union { + * struct { int_8 e1; int_8 e2; ... int_8 e_16; } f1; + * struct { uint_8 e1; uint_8 e2; ... uint_8 e_16; } f2; + * struct { int_16 e1; int_16 e2; ... int_16 e_8; } f3; + * struct { uint_16 e1; uint_16 e2; ... uint_16 e_8; } f4; + * struct { int_32 e1; int_32 e2; ... int_32 e_4; } f5; + * struct { uint_32 e1; uint_32 e2; ... uint_32 e_4; } f6; + * } + * + * The same principle applies for a struct 32 or 64 bytes in size. + * + * So the struct/union needs setting up as follows: all non-array + * elements copied across as is, and all array elements replaced with + * an equivalent struct which has as many fields as the array has + * elements, plus one NULL pointer. + */ + + Py_ssize_t num_ffi_type_pointers = 0; /* for the dummy fields */ + Py_ssize_t num_ffi_types = 0; /* for the dummy structures */ + size_t alloc_size; /* total bytes to allocate */ + ffi_type **element_types; /* of this struct/union */ + ffi_type **dummy_types; /* of the dummy struct elements */ + ffi_type *structs; /* point to struct aliases of arrays */ + Py_ssize_t element_index; /* index into element_types for this */ + Py_ssize_t dummy_index = 0; /* index into dummy field pointers */ + Py_ssize_t struct_index = 0; /* index into dummy structs */ + + Py_ssize_t len = PyTuple_GET_SIZE(layout_fields); + + /* first pass to see how much memory to allocate */ + for (Py_ssize_t i = 0; i < len; ++i) { + PyObject *prop_obj = PyTuple_GET_ITEM(layout_fields, i); // borrowed + assert(prop_obj); + assert(PyType_IsSubtype(Py_TYPE(prop_obj), st->PyCField_Type)); + CFieldObject *prop = (CFieldObject *)prop_obj; // borrowed + + StgInfo *info; + if (PyStgInfo_FromType(st, prop->proto, &info) < 0) { + goto error; + } + assert(info); + + if (!PyCArrayTypeObject_Check(st, prop->proto)) { + /* Not an array. Just need an ffi_type pointer. */ + num_ffi_type_pointers++; + } + else { + /* It's an array. */ + Py_ssize_t length = info->length; + + StgInfo *einfo; + if (PyStgInfo_FromType(st, info->proto, &einfo) < 0) { + goto error; + } + if (einfo == NULL) { + PyErr_Format(PyExc_TypeError, + "second item in _fields_ tuple (index %zd) must be a C type", + i); + goto error; + } + /* + * We need one extra ffi_type to hold the struct, and one + * ffi_type pointer per array element + one for a NULL to + * mark the end. + */ + num_ffi_types++; + num_ffi_type_pointers += length + 1; + } + } + + /* + * At this point, we know we need storage for some ffi_types and some + * ffi_type pointers. We'll allocate these in one block. + * There are three sub-blocks of information: the ffi_type pointers to + * this structure/union's elements, the ffi_type_pointers to the + * dummy fields standing in for array elements, and the + * ffi_types representing the dummy structures. + */ + alloc_size = (ffi_ofs + 1 + len + num_ffi_type_pointers) * sizeof(ffi_type *) + + num_ffi_types * sizeof(ffi_type); + type_block = PyMem_Malloc(alloc_size); + + if (type_block == NULL) { + PyErr_NoMemory(); + goto error; + } + /* + * the first block takes up ffi_ofs + len + 1 which is the pointers * + * for this struct/union. The second block takes up + * num_ffi_type_pointers, so the sum of these is ffi_ofs + len + 1 + + * num_ffi_type_pointers as allocated above. The last bit is the + * num_ffi_types structs. + */ + element_types = (ffi_type **) type_block; + dummy_types = &element_types[ffi_ofs + len + 1]; + structs = (ffi_type *) &dummy_types[num_ffi_type_pointers]; + + if (num_ffi_types > 0) { + memset(structs, 0, num_ffi_types * sizeof(ffi_type)); + } + if (ffi_ofs && (baseinfo != NULL)) { + memcpy(element_types, + baseinfo->ffi_type_pointer.elements, + ffi_ofs * sizeof(ffi_type *)); + } + element_index = ffi_ofs; + + /* second pass to actually set the type pointers */ + for (Py_ssize_t i = 0; i < len; ++i) { + PyObject *prop_obj = PyTuple_GET_ITEM(layout_fields, i); // borrowed + assert(prop_obj); + assert(PyType_IsSubtype(Py_TYPE(prop_obj), st->PyCField_Type)); + CFieldObject *prop = (CFieldObject *)prop_obj; // borrowed + + StgInfo *info; + if (PyStgInfo_FromType(st, prop->proto, &info) < 0) { + goto error; + } + assert(info); + + assert(element_index < (ffi_ofs + len)); /* will be used below */ + if (!PyCArrayTypeObject_Check(st, prop->proto)) { + /* Not an array. Just copy over the element ffi_type. */ + element_types[element_index++] = &info->ffi_type_pointer; + } + else { + Py_ssize_t length = info->length; + StgInfo *einfo; + if (PyStgInfo_FromType(st, info->proto, &einfo) < 0) { + goto error; + } + if (einfo == NULL) { + PyErr_Format(PyExc_TypeError, + "second item in _fields_ tuple (index %zd) must be a C type", + i); + goto error; + } + element_types[element_index++] = &structs[struct_index]; + structs[struct_index].size = length * einfo->ffi_type_pointer.size; + structs[struct_index].alignment = einfo->ffi_type_pointer.alignment; + structs[struct_index].type = FFI_TYPE_STRUCT; + structs[struct_index].elements = &dummy_types[dummy_index]; + ++struct_index; + /* Copy over the element's type, length times. */ + while (length > 0) { + assert(dummy_index < (num_ffi_type_pointers)); + dummy_types[dummy_index++] = &einfo->ffi_type_pointer; + length--; + } + assert(dummy_index < (num_ffi_type_pointers)); + dummy_types[dummy_index++] = NULL; + } + } + + element_types[element_index] = NULL; + /* + * Replace the old elements with the new, taking into account + * base class elements where necessary. + */ + assert(stginfo->ffi_type_pointer.elements); + PyMem_Free(stginfo->ffi_type_pointer.elements); + stginfo->ffi_type_pointer.elements = element_types; + } + + return 0; + +error: + PyMem_Free(type_block); + return -1; +} + + /* Retrieve the (optional) _pack_ attribute from a type, the _fields_ attribute, and initialize StgInfo. Used for Structure and Union subclasses. @@ -433,227 +673,9 @@ PyCStructUnionType_update_stginfo(PyObject *type, PyObject *fields, int isStruct stginfo->align = total_align; stginfo->length = ffi_ofs + len; -/* - * The value of MAX_STRUCT_SIZE depends on the platform Python is running on. - */ -#if defined(__aarch64__) || defined(__arm__) || defined(_M_ARM64) || defined(__sparc__) -# define MAX_STRUCT_SIZE 32 -#elif defined(__powerpc64__) -# define MAX_STRUCT_SIZE 64 -#else -# define MAX_STRUCT_SIZE 16 -#endif - - if (arrays_seen && (total_size <= MAX_STRUCT_SIZE)) { - /* - * See bpo-22273 and gh-110190. Arrays are normally treated as - * pointers, which is fine when an array name is being passed as - * parameter, but not when passing structures by value that contain - * arrays. - * Small structures passed by value are passed in registers, and in - * order to do this, libffi needs to know the true type of the array - * members of structs. Treating them as pointers breaks things. - * - * Small structures have different sizes depending on the platform - * where Python is running on: - * - * * x86-64: 16 bytes or less - * * Arm platforms (both 32 and 64 bit): 32 bytes or less - * * PowerPC 64 Little Endian: 64 bytes or less - * - * In that case, there can't be more than 16, 32 or 64 elements after - * unrolling arrays, as we (will) disallow bitfields. - * So we can collect the true ffi_type values in a fixed-size local - * array on the stack and, if any arrays were seen, replace the - * ffi_type_pointer.elements with a more accurate set, to allow - * libffi to marshal them into registers correctly. - * It means one more loop over the fields, but if we got here, - * the structure is small, so there aren't too many of those. - * - * Although the passing in registers is specific to the above - * platforms, the array-in-struct vs. pointer problem is general. - * But we restrict the type transformation to small structs - * nonetheless. - * - * Note that although a union may be small in terms of memory usage, it - * could contain many overlapping declarations of arrays, e.g. - * - * union { - * unsigned int_8 foo [16]; - * unsigned uint_8 bar [16]; - * unsigned int_16 baz[8]; - * unsigned uint_16 bozz[8]; - * unsigned int_32 fizz[4]; - * unsigned uint_32 buzz[4]; - * } - * - * which is still only 16 bytes in size. We need to convert this into - * the following equivalent for libffi: - * - * union { - * struct { int_8 e1; int_8 e2; ... int_8 e_16; } f1; - * struct { uint_8 e1; uint_8 e2; ... uint_8 e_16; } f2; - * struct { int_16 e1; int_16 e2; ... int_16 e_8; } f3; - * struct { uint_16 e1; uint_16 e2; ... uint_16 e_8; } f4; - * struct { int_32 e1; int_32 e2; ... int_32 e_4; } f5; - * struct { uint_32 e1; uint_32 e2; ... uint_32 e_4; } f6; - * } - * - * The same principle applies for a struct 32 or 64 bytes in size. - * - * So the struct/union needs setting up as follows: all non-array - * elements copied across as is, and all array elements replaced with - * an equivalent struct which has as many fields as the array has - * elements, plus one NULL pointer. - */ - - Py_ssize_t num_ffi_type_pointers = 0; /* for the dummy fields */ - Py_ssize_t num_ffi_types = 0; /* for the dummy structures */ - size_t alloc_size; /* total bytes to allocate */ - void *type_block; /* to hold all the type information needed */ - ffi_type **element_types; /* of this struct/union */ - ffi_type **dummy_types; /* of the dummy struct elements */ - ffi_type *structs; /* point to struct aliases of arrays */ - Py_ssize_t element_index; /* index into element_types for this */ - Py_ssize_t dummy_index = 0; /* index into dummy field pointers */ - Py_ssize_t struct_index = 0; /* index into dummy structs */ - - /* first pass to see how much memory to allocate */ - for (Py_ssize_t i = 0; i < len; ++i) { - PyObject *prop_obj = PyTuple_GET_ITEM(layout_fields, i); // borrowed - assert(prop_obj); - assert(PyType_IsSubtype(Py_TYPE(prop_obj), st->PyCField_Type)); - CFieldObject *prop = (CFieldObject *)prop_obj; // borrowed - - StgInfo *info; - if (PyStgInfo_FromType(st, prop->proto, &info) < 0) { - goto error; - } - assert(info); - - if (!PyCArrayTypeObject_Check(st, prop->proto)) { - /* Not an array. Just need an ffi_type pointer. */ - num_ffi_type_pointers++; - } - else { - /* It's an array. */ - Py_ssize_t length = info->length; - - StgInfo *einfo; - if (PyStgInfo_FromType(st, info->proto, &einfo) < 0) { - goto error; - } - if (einfo == NULL) { - PyErr_Format(PyExc_TypeError, - "second item in _fields_ tuple (index %zd) must be a C type", - i); - goto error; - } - /* - * We need one extra ffi_type to hold the struct, and one - * ffi_type pointer per array element + one for a NULL to - * mark the end. - */ - num_ffi_types++; - num_ffi_type_pointers += length + 1; - } - } - - /* - * At this point, we know we need storage for some ffi_types and some - * ffi_type pointers. We'll allocate these in one block. - * There are three sub-blocks of information: the ffi_type pointers to - * this structure/union's elements, the ffi_type_pointers to the - * dummy fields standing in for array elements, and the - * ffi_types representing the dummy structures. - */ - alloc_size = (ffi_ofs + 1 + len + num_ffi_type_pointers) * sizeof(ffi_type *) + - num_ffi_types * sizeof(ffi_type); - type_block = PyMem_Malloc(alloc_size); - - if (type_block == NULL) { - PyErr_NoMemory(); - goto error; - } - /* - * the first block takes up ffi_ofs + len + 1 which is the pointers * - * for this struct/union. The second block takes up - * num_ffi_type_pointers, so the sum of these is ffi_ofs + len + 1 + - * num_ffi_type_pointers as allocated above. The last bit is the - * num_ffi_types structs. - */ - element_types = (ffi_type **) type_block; - dummy_types = &element_types[ffi_ofs + len + 1]; - structs = (ffi_type *) &dummy_types[num_ffi_type_pointers]; - - if (num_ffi_types > 0) { - memset(structs, 0, num_ffi_types * sizeof(ffi_type)); - } - if (ffi_ofs && (baseinfo != NULL)) { - memcpy(element_types, - baseinfo->ffi_type_pointer.elements, - ffi_ofs * sizeof(ffi_type *)); - } - element_index = ffi_ofs; - - /* second pass to actually set the type pointers */ - for (Py_ssize_t i = 0; i < len; ++i) { - PyObject *prop_obj = PyTuple_GET_ITEM(layout_fields, i); // borrowed - assert(prop_obj); - assert(PyType_IsSubtype(Py_TYPE(prop_obj), st->PyCField_Type)); - CFieldObject *prop = (CFieldObject *)prop_obj; // borrowed - - StgInfo *info; - if (PyStgInfo_FromType(st, prop->proto, &info) < 0) { - PyMem_Free(type_block); - goto error; - } - assert(info); - - assert(element_index < (ffi_ofs + len)); /* will be used below */ - if (!PyCArrayTypeObject_Check(st, prop->proto)) { - /* Not an array. Just copy over the element ffi_type. */ - element_types[element_index++] = &info->ffi_type_pointer; - } - else { - Py_ssize_t length = info->length; - StgInfo *einfo; - if (PyStgInfo_FromType(st, info->proto, &einfo) < 0) { - PyMem_Free(type_block); - goto error; - } - if (einfo == NULL) { - PyMem_Free(type_block); - PyErr_Format(PyExc_TypeError, - "second item in _fields_ tuple (index %zd) must be a C type", - i); - goto error; - } - element_types[element_index++] = &structs[struct_index]; - structs[struct_index].size = length * einfo->ffi_type_pointer.size; - structs[struct_index].alignment = einfo->ffi_type_pointer.alignment; - structs[struct_index].type = FFI_TYPE_STRUCT; - structs[struct_index].elements = &dummy_types[dummy_index]; - ++struct_index; - /* Copy over the element's type, length times. */ - while (length > 0) { - assert(dummy_index < (num_ffi_type_pointers)); - dummy_types[dummy_index++] = &einfo->ffi_type_pointer; - length--; - } - assert(dummy_index < (num_ffi_type_pointers)); - dummy_types[dummy_index++] = NULL; - } - } - - element_types[element_index] = NULL; - /* - * Replace the old elements with the new, taking into account - * base class elements where necessary. - */ - assert(stginfo->ffi_type_pointer.elements); - PyMem_Free(stginfo->ffi_type_pointer.elements); - stginfo->ffi_type_pointer.elements = element_types; + if (_copy_array_elements(st, arrays_seen, total_size, layout_fields, + ffi_ofs, baseinfo, stginfo) < 0) { + goto error; } /* We did check that this flag was NOT set above, it must not From df759d60345ecb54da2d76da335d84174d6d9cea Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Thu, 20 Mar 2025 00:48:51 +0500 Subject: [PATCH 2/3] Rearrange _replace_array_elements --- Modules/_ctypes/stgdict.c | 483 +++++++++++++++++++------------------- 1 file changed, 242 insertions(+), 241 deletions(-) diff --git a/Modules/_ctypes/stgdict.c b/Modules/_ctypes/stgdict.c index 8f579507d0ef2b..0ad0815f480f09 100644 --- a/Modules/_ctypes/stgdict.c +++ b/Modules/_ctypes/stgdict.c @@ -210,245 +210,10 @@ MakeAnonFields(PyObject *type) return 0; } -/* - Copy array elements to stginfo->ffi_type_pointer.elements. Return -1 if error - occured. -*/ -int -_copy_array_elements(ctypes_state *st, int arrays_seen, Py_ssize_t total_size, - PyObject *layout_fields, Py_ssize_t ffi_ofs, - StgInfo *baseinfo, StgInfo *stginfo) -{ - void *type_block = NULL; /* to hold all the type information needed */ - -/* - * The value of MAX_STRUCT_SIZE depends on the platform Python is running on. - */ -#if defined(__aarch64__) || defined(__arm__) || defined(_M_ARM64) || defined(__sparc__) -# define MAX_STRUCT_SIZE 32 -#elif defined(__powerpc64__) -# define MAX_STRUCT_SIZE 64 -#else -# define MAX_STRUCT_SIZE 16 -#endif - - if (arrays_seen && (total_size <= MAX_STRUCT_SIZE)) { - /* - * See bpo-22273 and gh-110190. Arrays are normally treated as - * pointers, which is fine when an array name is being passed as - * parameter, but not when passing structures by value that contain - * arrays. - * Small structures passed by value are passed in registers, and in - * order to do this, libffi needs to know the true type of the array - * members of structs. Treating them as pointers breaks things. - * - * Small structures have different sizes depending on the platform - * where Python is running on: - * - * * x86-64: 16 bytes or less - * * Arm platforms (both 32 and 64 bit): 32 bytes or less - * * PowerPC 64 Little Endian: 64 bytes or less - * - * In that case, there can't be more than 16, 32 or 64 elements after - * unrolling arrays, as we (will) disallow bitfields. - * So we can collect the true ffi_type values in a fixed-size local - * array on the stack and, if any arrays were seen, replace the - * ffi_type_pointer.elements with a more accurate set, to allow - * libffi to marshal them into registers correctly. - * It means one more loop over the fields, but if we got here, - * the structure is small, so there aren't too many of those. - * - * Although the passing in registers is specific to the above - * platforms, the array-in-struct vs. pointer problem is general. - * But we restrict the type transformation to small structs - * nonetheless. - * - * Note that although a union may be small in terms of memory usage, it - * could contain many overlapping declarations of arrays, e.g. - * - * union { - * unsigned int_8 foo [16]; - * unsigned uint_8 bar [16]; - * unsigned int_16 baz[8]; - * unsigned uint_16 bozz[8]; - * unsigned int_32 fizz[4]; - * unsigned uint_32 buzz[4]; - * } - * - * which is still only 16 bytes in size. We need to convert this into - * the following equivalent for libffi: - * - * union { - * struct { int_8 e1; int_8 e2; ... int_8 e_16; } f1; - * struct { uint_8 e1; uint_8 e2; ... uint_8 e_16; } f2; - * struct { int_16 e1; int_16 e2; ... int_16 e_8; } f3; - * struct { uint_16 e1; uint_16 e2; ... uint_16 e_8; } f4; - * struct { int_32 e1; int_32 e2; ... int_32 e_4; } f5; - * struct { uint_32 e1; uint_32 e2; ... uint_32 e_4; } f6; - * } - * - * The same principle applies for a struct 32 or 64 bytes in size. - * - * So the struct/union needs setting up as follows: all non-array - * elements copied across as is, and all array elements replaced with - * an equivalent struct which has as many fields as the array has - * elements, plus one NULL pointer. - */ - - Py_ssize_t num_ffi_type_pointers = 0; /* for the dummy fields */ - Py_ssize_t num_ffi_types = 0; /* for the dummy structures */ - size_t alloc_size; /* total bytes to allocate */ - ffi_type **element_types; /* of this struct/union */ - ffi_type **dummy_types; /* of the dummy struct elements */ - ffi_type *structs; /* point to struct aliases of arrays */ - Py_ssize_t element_index; /* index into element_types for this */ - Py_ssize_t dummy_index = 0; /* index into dummy field pointers */ - Py_ssize_t struct_index = 0; /* index into dummy structs */ - - Py_ssize_t len = PyTuple_GET_SIZE(layout_fields); - - /* first pass to see how much memory to allocate */ - for (Py_ssize_t i = 0; i < len; ++i) { - PyObject *prop_obj = PyTuple_GET_ITEM(layout_fields, i); // borrowed - assert(prop_obj); - assert(PyType_IsSubtype(Py_TYPE(prop_obj), st->PyCField_Type)); - CFieldObject *prop = (CFieldObject *)prop_obj; // borrowed - - StgInfo *info; - if (PyStgInfo_FromType(st, prop->proto, &info) < 0) { - goto error; - } - assert(info); - - if (!PyCArrayTypeObject_Check(st, prop->proto)) { - /* Not an array. Just need an ffi_type pointer. */ - num_ffi_type_pointers++; - } - else { - /* It's an array. */ - Py_ssize_t length = info->length; - - StgInfo *einfo; - if (PyStgInfo_FromType(st, info->proto, &einfo) < 0) { - goto error; - } - if (einfo == NULL) { - PyErr_Format(PyExc_TypeError, - "second item in _fields_ tuple (index %zd) must be a C type", - i); - goto error; - } - /* - * We need one extra ffi_type to hold the struct, and one - * ffi_type pointer per array element + one for a NULL to - * mark the end. - */ - num_ffi_types++; - num_ffi_type_pointers += length + 1; - } - } - - /* - * At this point, we know we need storage for some ffi_types and some - * ffi_type pointers. We'll allocate these in one block. - * There are three sub-blocks of information: the ffi_type pointers to - * this structure/union's elements, the ffi_type_pointers to the - * dummy fields standing in for array elements, and the - * ffi_types representing the dummy structures. - */ - alloc_size = (ffi_ofs + 1 + len + num_ffi_type_pointers) * sizeof(ffi_type *) + - num_ffi_types * sizeof(ffi_type); - type_block = PyMem_Malloc(alloc_size); - - if (type_block == NULL) { - PyErr_NoMemory(); - goto error; - } - /* - * the first block takes up ffi_ofs + len + 1 which is the pointers * - * for this struct/union. The second block takes up - * num_ffi_type_pointers, so the sum of these is ffi_ofs + len + 1 + - * num_ffi_type_pointers as allocated above. The last bit is the - * num_ffi_types structs. - */ - element_types = (ffi_type **) type_block; - dummy_types = &element_types[ffi_ofs + len + 1]; - structs = (ffi_type *) &dummy_types[num_ffi_type_pointers]; - - if (num_ffi_types > 0) { - memset(structs, 0, num_ffi_types * sizeof(ffi_type)); - } - if (ffi_ofs && (baseinfo != NULL)) { - memcpy(element_types, - baseinfo->ffi_type_pointer.elements, - ffi_ofs * sizeof(ffi_type *)); - } - element_index = ffi_ofs; - - /* second pass to actually set the type pointers */ - for (Py_ssize_t i = 0; i < len; ++i) { - PyObject *prop_obj = PyTuple_GET_ITEM(layout_fields, i); // borrowed - assert(prop_obj); - assert(PyType_IsSubtype(Py_TYPE(prop_obj), st->PyCField_Type)); - CFieldObject *prop = (CFieldObject *)prop_obj; // borrowed - - StgInfo *info; - if (PyStgInfo_FromType(st, prop->proto, &info) < 0) { - goto error; - } - assert(info); - - assert(element_index < (ffi_ofs + len)); /* will be used below */ - if (!PyCArrayTypeObject_Check(st, prop->proto)) { - /* Not an array. Just copy over the element ffi_type. */ - element_types[element_index++] = &info->ffi_type_pointer; - } - else { - Py_ssize_t length = info->length; - StgInfo *einfo; - if (PyStgInfo_FromType(st, info->proto, &einfo) < 0) { - goto error; - } - if (einfo == NULL) { - PyErr_Format(PyExc_TypeError, - "second item in _fields_ tuple (index %zd) must be a C type", - i); - goto error; - } - element_types[element_index++] = &structs[struct_index]; - structs[struct_index].size = length * einfo->ffi_type_pointer.size; - structs[struct_index].alignment = einfo->ffi_type_pointer.alignment; - structs[struct_index].type = FFI_TYPE_STRUCT; - structs[struct_index].elements = &dummy_types[dummy_index]; - ++struct_index; - /* Copy over the element's type, length times. */ - while (length > 0) { - assert(dummy_index < (num_ffi_type_pointers)); - dummy_types[dummy_index++] = &einfo->ffi_type_pointer; - length--; - } - assert(dummy_index < (num_ffi_type_pointers)); - dummy_types[dummy_index++] = NULL; - } - } - - element_types[element_index] = NULL; - /* - * Replace the old elements with the new, taking into account - * base class elements where necessary. - */ - assert(stginfo->ffi_type_pointer.elements); - PyMem_Free(stginfo->ffi_type_pointer.elements); - stginfo->ffi_type_pointer.elements = element_types; - } - - return 0; - -error: - PyMem_Free(type_block); - return -1; -} +int +_replace_array_elements(ctypes_state *st, PyObject *layout_fields, + Py_ssize_t ffi_ofs, StgInfo *baseinfo, StgInfo *stginfo); /* Retrieve the (optional) _pack_ attribute from a type, the _fields_ attribute, @@ -673,9 +438,21 @@ PyCStructUnionType_update_stginfo(PyObject *type, PyObject *fields, int isStruct stginfo->align = total_align; stginfo->length = ffi_ofs + len; - if (_copy_array_elements(st, arrays_seen, total_size, layout_fields, - ffi_ofs, baseinfo, stginfo) < 0) { - goto error; +/* + * The value of MAX_STRUCT_SIZE depends on the platform Python is running on. + */ +#if defined(__aarch64__) || defined(__arm__) || defined(_M_ARM64) || defined(__sparc__) +# define MAX_STRUCT_SIZE 32 +#elif defined(__powerpc64__) +# define MAX_STRUCT_SIZE 64 +#else +# define MAX_STRUCT_SIZE 16 +#endif + + if (arrays_seen && (total_size <= MAX_STRUCT_SIZE)) { + if (_replace_array_elements(st, layout_fields, ffi_ofs, baseinfo, stginfo) < 0) { + goto error; + } } /* We did check that this flag was NOT set above, it must not @@ -699,3 +476,227 @@ PyCStructUnionType_update_stginfo(PyObject *type, PyObject *fields, int isStruct Py_XDECREF(format_spec_obj); return retval; } + +/* + Copy array elements to stginfo->ffi_type_pointer.elements. Return -1 if error + occured. +*/ +int +_replace_array_elements(ctypes_state *st, PyObject *layout_fields, + Py_ssize_t ffi_ofs, StgInfo *baseinfo, StgInfo *stginfo) +{ + /* + * See bpo-22273 and gh-110190. Arrays are normally treated as + * pointers, which is fine when an array name is being passed as + * parameter, but not when passing structures by value that contain + * arrays. + * Small structures passed by value are passed in registers, and in + * order to do this, libffi needs to know the true type of the array + * members of structs. Treating them as pointers breaks things. + * + * Small structures have different sizes depending on the platform + * where Python is running on: + * + * * x86-64: 16 bytes or less + * * Arm platforms (both 32 and 64 bit): 32 bytes or less + * * PowerPC 64 Little Endian: 64 bytes or less + * + * In that case, there can't be more than 16, 32 or 64 elements after + * unrolling arrays, as we (will) disallow bitfields. + * So we can collect the true ffi_type values in a fixed-size local + * array on the stack and, if any arrays were seen, replace the + * ffi_type_pointer.elements with a more accurate set, to allow + * libffi to marshal them into registers correctly. + * It means one more loop over the fields, but if we got here, + * the structure is small, so there aren't too many of those. + * + * Although the passing in registers is specific to the above + * platforms, the array-in-struct vs. pointer problem is general. + * But we restrict the type transformation to small structs + * nonetheless. + * + * Note that although a union may be small in terms of memory usage, it + * could contain many overlapping declarations of arrays, e.g. + * + * union { + * unsigned int_8 foo [16]; + * unsigned uint_8 bar [16]; + * unsigned int_16 baz[8]; + * unsigned uint_16 bozz[8]; + * unsigned int_32 fizz[4]; + * unsigned uint_32 buzz[4]; + * } + * + * which is still only 16 bytes in size. We need to convert this into + * the following equivalent for libffi: + * + * union { + * struct { int_8 e1; int_8 e2; ... int_8 e_16; } f1; + * struct { uint_8 e1; uint_8 e2; ... uint_8 e_16; } f2; + * struct { int_16 e1; int_16 e2; ... int_16 e_8; } f3; + * struct { uint_16 e1; uint_16 e2; ... uint_16 e_8; } f4; + * struct { int_32 e1; int_32 e2; ... int_32 e_4; } f5; + * struct { uint_32 e1; uint_32 e2; ... uint_32 e_4; } f6; + * } + * + * The same principle applies for a struct 32 or 64 bytes in size. + * + * So the struct/union needs setting up as follows: all non-array + * elements copied across as is, and all array elements replaced with + * an equivalent struct which has as many fields as the array has + * elements, plus one NULL pointer. + */ + + Py_ssize_t num_ffi_type_pointers = 0; /* for the dummy fields */ + Py_ssize_t num_ffi_types = 0; /* for the dummy structures */ + size_t alloc_size; /* total bytes to allocate */ + void *type_block = NULL; /* to hold all the type information needed */ + ffi_type **element_types; /* of this struct/union */ + ffi_type **dummy_types; /* of the dummy struct elements */ + ffi_type *structs; /* point to struct aliases of arrays */ + Py_ssize_t element_index; /* index into element_types for this */ + Py_ssize_t dummy_index = 0; /* index into dummy field pointers */ + Py_ssize_t struct_index = 0; /* index into dummy structs */ + + Py_ssize_t len = PyTuple_GET_SIZE(layout_fields); + + /* first pass to see how much memory to allocate */ + for (Py_ssize_t i = 0; i < len; ++i) { + PyObject *prop_obj = PyTuple_GET_ITEM(layout_fields, i); // borrowed + assert(prop_obj); + assert(PyType_IsSubtype(Py_TYPE(prop_obj), st->PyCField_Type)); + CFieldObject *prop = (CFieldObject *)prop_obj; // borrowed + + StgInfo *info; + if (PyStgInfo_FromType(st, prop->proto, &info) < 0) { + goto error; + } + assert(info); + + if (!PyCArrayTypeObject_Check(st, prop->proto)) { + /* Not an array. Just need an ffi_type pointer. */ + num_ffi_type_pointers++; + } + else { + /* It's an array. */ + Py_ssize_t length = info->length; + + StgInfo *einfo; + if (PyStgInfo_FromType(st, info->proto, &einfo) < 0) { + goto error; + } + if (einfo == NULL) { + PyErr_Format(PyExc_TypeError, + "second item in _fields_ tuple (index %zd) must be a C type", + i); + goto error; + } + /* + * We need one extra ffi_type to hold the struct, and one + * ffi_type pointer per array element + one for a NULL to + * mark the end. + */ + num_ffi_types++; + num_ffi_type_pointers += length + 1; + } + } + + /* + * At this point, we know we need storage for some ffi_types and some + * ffi_type pointers. We'll allocate these in one block. + * There are three sub-blocks of information: the ffi_type pointers to + * this structure/union's elements, the ffi_type_pointers to the + * dummy fields standing in for array elements, and the + * ffi_types representing the dummy structures. + */ + alloc_size = (ffi_ofs + 1 + len + num_ffi_type_pointers) * sizeof(ffi_type *) + + num_ffi_types * sizeof(ffi_type); + type_block = PyMem_Malloc(alloc_size); + + if (type_block == NULL) { + PyErr_NoMemory(); + goto error; + } + /* + * the first block takes up ffi_ofs + len + 1 which is the pointers * + * for this struct/union. The second block takes up + * num_ffi_type_pointers, so the sum of these is ffi_ofs + len + 1 + + * num_ffi_type_pointers as allocated above. The last bit is the + * num_ffi_types structs. + */ + element_types = (ffi_type **) type_block; + dummy_types = &element_types[ffi_ofs + len + 1]; + structs = (ffi_type *) &dummy_types[num_ffi_type_pointers]; + + if (num_ffi_types > 0) { + memset(structs, 0, num_ffi_types * sizeof(ffi_type)); + } + if (ffi_ofs && (baseinfo != NULL)) { + memcpy(element_types, + baseinfo->ffi_type_pointer.elements, + ffi_ofs * sizeof(ffi_type *)); + } + element_index = ffi_ofs; + + /* second pass to actually set the type pointers */ + for (Py_ssize_t i = 0; i < len; ++i) { + PyObject *prop_obj = PyTuple_GET_ITEM(layout_fields, i); // borrowed + assert(prop_obj); + assert(PyType_IsSubtype(Py_TYPE(prop_obj), st->PyCField_Type)); + CFieldObject *prop = (CFieldObject *)prop_obj; // borrowed + + StgInfo *info; + if (PyStgInfo_FromType(st, prop->proto, &info) < 0) { + goto error; + } + assert(info); + + assert(element_index < (ffi_ofs + len)); /* will be used below */ + if (!PyCArrayTypeObject_Check(st, prop->proto)) { + /* Not an array. Just copy over the element ffi_type. */ + element_types[element_index++] = &info->ffi_type_pointer; + } + else { + Py_ssize_t length = info->length; + StgInfo *einfo; + if (PyStgInfo_FromType(st, info->proto, &einfo) < 0) { + goto error; + } + if (einfo == NULL) { + PyErr_Format(PyExc_TypeError, + "second item in _fields_ tuple (index %zd) must be a C type", + i); + goto error; + } + element_types[element_index++] = &structs[struct_index]; + structs[struct_index].size = length * einfo->ffi_type_pointer.size; + structs[struct_index].alignment = einfo->ffi_type_pointer.alignment; + structs[struct_index].type = FFI_TYPE_STRUCT; + structs[struct_index].elements = &dummy_types[dummy_index]; + ++struct_index; + /* Copy over the element's type, length times. */ + while (length > 0) { + assert(dummy_index < (num_ffi_type_pointers)); + dummy_types[dummy_index++] = &einfo->ffi_type_pointer; + length--; + } + assert(dummy_index < (num_ffi_type_pointers)); + dummy_types[dummy_index++] = NULL; + } + } + + element_types[element_index] = NULL; + /* + * Replace the old elements with the new, taking into account + * base class elements where necessary. + */ + assert(stginfo->ffi_type_pointer.elements); + PyMem_Free(stginfo->ffi_type_pointer.elements); + stginfo->ffi_type_pointer.elements = element_types; + + return 0; + +error: + PyMem_Free(type_block); + return -1; +} From fe887eb8454036b3086a4b9e3cce5d73051cfd5d Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Thu, 20 Mar 2025 00:50:09 +0500 Subject: [PATCH 3/3] Fix comment for _replace_array_elements --- Modules/_ctypes/stgdict.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/_ctypes/stgdict.c b/Modules/_ctypes/stgdict.c index 0ad0815f480f09..03a32a37350601 100644 --- a/Modules/_ctypes/stgdict.c +++ b/Modules/_ctypes/stgdict.c @@ -478,8 +478,8 @@ PyCStructUnionType_update_stginfo(PyObject *type, PyObject *fields, int isStruct } /* - Copy array elements to stginfo->ffi_type_pointer.elements. Return -1 if error - occured. + Replace array elements at stginfo->ffi_type_pointer.elements. + Return -1 if error occured. */ int _replace_array_elements(ctypes_state *st, PyObject *layout_fields,