gh-110190: Fix ctypes structs with array on Arm (#112604)

Set MAX_STRUCT_SIZE to 32 in stgdict.c when on Arm platforms.
This because on Arm platforms structs with at most 4 elements of any
floating point type values can be passed through registers. If the type
is double the maximum size of the struct is 32 bytes.
On x86-64 Linux, it's maximum 16 bytes hence we need to differentiate.
This commit is contained in:
Diego Russo 2023-12-05 15:07:50 +00:00 • committed by GitHub
parent e7e1116a78
commit bc68f4a4ab
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 193 additions and 20 deletions

View file

@ -150,6 +150,42 @@ _testfunc_array_in_struct2a(Test3B in)
return result;
}
/*
* See gh-110190. structs containing arrays of up to four floating point types
* (max 32 bytes) are passed in registers on Arm.
*/
typedef struct {
double data[4];
} Test3C;
EXPORT(Test3C)
_testfunc_array_in_struct_set_defaults_3C(void)
{
Test3C s;
s.data[0] = 1.0;
s.data[1] = 2.0;
s.data[2] = 3.0;
s.data[3] = 4.0;
return s;
}
typedef struct {
double data[5];
} Test3D;
EXPORT(Test3D)
_testfunc_array_in_struct_set_defaults_3D(void)
{
Test3D s;
s.data[0] = 1.0;
s.data[1] = 2.0;
s.data[2] = 3.0;
s.data[3] = 4.0;
s.data[4] = 5.0;
return s;
}
typedef union {
long a_long;
struct {

View file

@ -697,29 +697,43 @@ PyCStructUnionType_update_stgdict(PyObject *type, PyObject *fields, int isStruct
stgdict->align = total_align;
stgdict->length = len; /* ADD ffi_ofs? */
#define MAX_STRUCT_SIZE 16
/*
* On Arm platforms, structs with at most 4 elements of any floating point
* type values can be passed through registers. If the type is double the
* maximum size of the struct is 32 bytes.
* By Arm platforms it is meant both 32 and 64-bit.
*/
#if defined(__aarch64__) || defined(__arm__)
# define MAX_STRUCT_SIZE 32
#else
# define MAX_STRUCT_SIZE 16
#endif
if (arrays_seen && (size <= MAX_STRUCT_SIZE)) {
/*
* See bpo-22273. 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. On 64-bit Linux,
* 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.
* 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.
* On x86_64 Linux and Arm platforms, 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.
*
* By small structures, we mean ones that are 16 bytes or less. In that
* case, there can't be more than 16 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.
* By small structures, we mean ones that are 16 bytes or less on
* x86-64 and 32 bytes or less on Arm. In that case, there can't be
* more than 16 or 32 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 64-bit Linux, the
* array-in-struct vs. pointer problem is general. But we restrict the
* type transformation to small structs nonetheless.
* Although the passing in registers is specific to x86_64 Linux
* and Arm 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.
@ -745,6 +759,9 @@ PyCStructUnionType_update_stgdict(PyObject *type, PyObject *fields, int isStruct
* struct { uint_32 e1; uint_32 e2; ... uint_32 e_4; } f6;
* }
*
* The same principle applies for a struct 32 bytes in size like in
* the case of Arm platforms.
*
* 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