Skip to content

Commit b49c963

Browse files
authored
[3.11] gh-110190: Fix ctypes structs with array on Arm (#112604) (#112766)
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. (cherry picked from commit bc68f4a)
1 parent 010819a commit b49c963

File tree

4 files changed

+197
-20
lines changed

4 files changed

+197
-20
lines changed

Lib/ctypes/test/test_structures.py

Lines changed: 125 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
import platform
22
import sys
33
import unittest
4-
from ctypes import *
54
from ctypes.test import need_symbol
5+
from ctypes import (CDLL, Array, Structure, Union, POINTER, sizeof, byref, alignment,
6+
c_void_p, c_char, c_wchar, c_byte, c_ubyte,
7+
c_uint8, c_uint16, c_uint32,
8+
c_short, c_ushort, c_int, c_uint,
9+
c_long, c_ulong, c_longlong, c_ulonglong, c_float, c_double)
610
from struct import calcsize
711
import _ctypes_test
812
from test import support
@@ -499,12 +503,59 @@ class Test3B(Test3A):
499503
('more_data', c_float * 2),
500504
]
501505

506+
class Test3C1(Structure):
507+
_fields_ = [
508+
("data", c_double * 4)
509+
]
510+
511+
class DataType4(Array):
512+
_type_ = c_double
513+
_length_ = 4
514+
515+
class Test3C2(Structure):
516+
_fields_ = [
517+
("data", DataType4)
518+
]
519+
520+
class Test3C3(Structure):
521+
_fields_ = [
522+
("x", c_double),
523+
("y", c_double),
524+
("z", c_double),
525+
("t", c_double)
526+
]
527+
528+
class Test3D1(Structure):
529+
_fields_ = [
530+
("data", c_double * 5)
531+
]
532+
533+
class DataType5(Array):
534+
_type_ = c_double
535+
_length_ = 5
536+
537+
class Test3D2(Structure):
538+
_fields_ = [
539+
("data", DataType5)
540+
]
541+
542+
class Test3D3(Structure):
543+
_fields_ = [
544+
("x", c_double),
545+
("y", c_double),
546+
("z", c_double),
547+
("t", c_double),
548+
("u", c_double)
549+
]
550+
551+
# Load the shared library
552+
dll = CDLL(_ctypes_test.__file__)
553+
502554
s = Test2()
503555
expected = 0
504556
for i in range(16):
505557
s.data[i] = i
506558
expected += i
507-
dll = CDLL(_ctypes_test.__file__)
508559
func = dll._testfunc_array_in_struct1
509560
func.restype = c_int
510561
func.argtypes = (Test2,)
@@ -545,6 +596,78 @@ class Test3B(Test3A):
545596
self.assertAlmostEqual(s.more_data[0], -3.0, places=6)
546597
self.assertAlmostEqual(s.more_data[1], -2.0, places=6)
547598

599+
# Tests for struct Test3C
600+
expected = (1.0, 2.0, 3.0, 4.0)
601+
func = dll._testfunc_array_in_struct_set_defaults_3C
602+
func.restype = Test3C1
603+
result = func()
604+
# check the default values have been set properly
605+
self.assertEqual(
606+
(result.data[0],
607+
result.data[1],
608+
result.data[2],
609+
result.data[3]),
610+
expected
611+
)
612+
613+
func = dll._testfunc_array_in_struct_set_defaults_3C
614+
func.restype = Test3C2
615+
result = func()
616+
# check the default values have been set properly
617+
self.assertEqual(
618+
(result.data[0],
619+
result.data[1],
620+
result.data[2],
621+
result.data[3]),
622+
expected
623+
)
624+
625+
func = dll._testfunc_array_in_struct_set_defaults_3C
626+
func.restype = Test3C3
627+
result = func()
628+
# check the default values have been set properly
629+
self.assertEqual((result.x, result.y, result.z, result.t), expected)
630+
631+
# Tests for struct Test3D
632+
expected = (1.0, 2.0, 3.0, 4.0, 5.0)
633+
func = dll._testfunc_array_in_struct_set_defaults_3D
634+
func.restype = Test3D1
635+
result = func()
636+
# check the default values have been set properly
637+
self.assertEqual(
638+
(result.data[0],
639+
result.data[1],
640+
result.data[2],
641+
result.data[3],
642+
result.data[4]),
643+
expected
644+
)
645+
646+
func = dll._testfunc_array_in_struct_set_defaults_3D
647+
func.restype = Test3D2
648+
result = func()
649+
# check the default values have been set properly
650+
self.assertEqual(
651+
(result.data[0],
652+
result.data[1],
653+
result.data[2],
654+
result.data[3],
655+
result.data[4]),
656+
expected
657+
)
658+
659+
func = dll._testfunc_array_in_struct_set_defaults_3D
660+
func.restype = Test3D3
661+
result = func()
662+
# check the default values have been set properly
663+
self.assertEqual(
664+
(result.x,
665+
result.y,
666+
result.z,
667+
result.t,
668+
result.u),
669+
expected)
670+
548671
def test_38368(self):
549672
class U(Union):
550673
_fields_ = [
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix ctypes structs with array on Arm platform by setting ``MAX_STRUCT_SIZE`` to 32 in stgdict. Patch by Diego Russo.

Modules/_ctypes/_ctypes_test.c

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,42 @@ _testfunc_array_in_struct2a(Test3B in)
133133
return result;
134134
}
135135

136+
/*
137+
* See gh-110190. structs containing arrays of up to four floating point types
138+
* (max 32 bytes) are passed in registers on Arm.
139+
*/
140+
141+
typedef struct {
142+
double data[4];
143+
} Test3C;
144+
145+
EXPORT(Test3C)
146+
_testfunc_array_in_struct_set_defaults_3C(void)
147+
{
148+
Test3C s;
149+
s.data[0] = 1.0;
150+
s.data[1] = 2.0;
151+
s.data[2] = 3.0;
152+
s.data[3] = 4.0;
153+
return s;
154+
}
155+
156+
typedef struct {
157+
double data[5];
158+
} Test3D;
159+
160+
EXPORT(Test3D)
161+
_testfunc_array_in_struct_set_defaults_3D(void)
162+
{
163+
Test3D s;
164+
s.data[0] = 1.0;
165+
s.data[1] = 2.0;
166+
s.data[2] = 3.0;
167+
s.data[3] = 4.0;
168+
s.data[4] = 5.0;
169+
return s;
170+
}
171+
136172
typedef union {
137173
long a_long;
138174
struct {

Modules/_ctypes/stgdict.c

Lines changed: 35 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -662,29 +662,43 @@ PyCStructUnionType_update_stgdict(PyObject *type, PyObject *fields, int isStruct
662662
stgdict->align = total_align;
663663
stgdict->length = len; /* ADD ffi_ofs? */
664664

665-
#define MAX_STRUCT_SIZE 16
665+
/*
666+
* On Arm platforms, structs with at most 4 elements of any floating point
667+
* type values can be passed through registers. If the type is double the
668+
* maximum size of the struct is 32 bytes.
669+
* By Arm platforms it is meant both 32 and 64-bit.
670+
*/
671+
#if defined(__aarch64__) || defined(__arm__)
672+
# define MAX_STRUCT_SIZE 32
673+
#else
674+
# define MAX_STRUCT_SIZE 16
675+
#endif
666676

667677
if (arrays_seen && (size <= MAX_STRUCT_SIZE)) {
668678
/*
669-
* See bpo-22273. Arrays are normally treated as pointers, which is
670-
* fine when an array name is being passed as parameter, but not when
671-
* passing structures by value that contain arrays. On 64-bit Linux,
672-
* small structures passed by value are passed in registers, and in
673-
* order to do this, libffi needs to know the true type of the array
674-
* members of structs. Treating them as pointers breaks things.
679+
* See bpo-22273 and gh-110190. Arrays are normally treated as
680+
* pointers, which is fine when an array name is being passed as
681+
* parameter, but not when passing structures by value that contain
682+
* arrays.
683+
* On x86_64 Linux and Arm platforms, small structures passed by
684+
* value are passed in registers, and in order to do this, libffi needs
685+
* to know the true type of the array members of structs. Treating them
686+
* as pointers breaks things.
675687
*
676-
* By small structures, we mean ones that are 16 bytes or less. In that
677-
* case, there can't be more than 16 elements after unrolling arrays,
678-
* as we (will) disallow bitfields. So we can collect the true ffi_type
679-
* values in a fixed-size local array on the stack and, if any arrays
680-
* were seen, replace the ffi_type_pointer.elements with a more
681-
* accurate set, to allow libffi to marshal them into registers
682-
* correctly. It means one more loop over the fields, but if we got
683-
* here, the structure is small, so there aren't too many of those.
688+
* By small structures, we mean ones that are 16 bytes or less on
689+
* x86-64 and 32 bytes or less on Arm. In that case, there can't be
690+
* more than 16 or 32 elements after unrolling arrays, as we (will)
691+
* disallow bitfields. So we can collect the true ffi_type values in
692+
* a fixed-size local array on the stack and, if any arrays were seen,
693+
* replace the ffi_type_pointer.elements with a more accurate set,
694+
* to allow libffi to marshal them into registers correctly.
695+
* It means one more loop over the fields, but if we got here,
696+
* the structure is small, so there aren't too many of those.
684697
*
685-
* Although the passing in registers is specific to 64-bit Linux, the
686-
* array-in-struct vs. pointer problem is general. But we restrict the
687-
* type transformation to small structs nonetheless.
698+
* Although the passing in registers is specific to x86_64 Linux
699+
* and Arm platforms, the array-in-struct vs. pointer problem is
700+
* general. But we restrict the type transformation to small structs
701+
* nonetheless.
688702
*
689703
* Note that although a union may be small in terms of memory usage, it
690704
* could contain many overlapping declarations of arrays, e.g.
@@ -710,6 +724,9 @@ PyCStructUnionType_update_stgdict(PyObject *type, PyObject *fields, int isStruct
710724
* struct { uint_32 e1; uint_32 e2; ... uint_32 e_4; } f6;
711725
* }
712726
*
727+
* The same principle applies for a struct 32 bytes in size like in
728+
* the case of Arm platforms.
729+
*
713730
* So the struct/union needs setting up as follows: all non-array
714731
* elements copied across as is, and all array elements replaced with
715732
* an equivalent struct which has as many fields as the array has

0 commit comments

Comments
 (0)