@@ -350,6 +350,9 @@ PyCStructUnionType_update_stgdict(PyObject *type, PyObject *fields, int isStruct
350
350
int pack ;
351
351
Py_ssize_t ffi_ofs ;
352
352
int big_endian ;
353
+ #if defined(X86_64 )
354
+ int arrays_seen = 0 ;
355
+ #endif
353
356
354
357
/* HACK Alert: I cannot be bothered to fix ctypes.com, so there has to
355
358
be a way to use the old, broken semantics: _fields_ are not extended
@@ -501,6 +504,10 @@ PyCStructUnionType_update_stgdict(PyObject *type, PyObject *fields, int isStruct
501
504
Py_XDECREF (pair );
502
505
return -1 ;
503
506
}
507
+ #if defined(X86_64 )
508
+ if (PyCArrayTypeObject_Check (desc ))
509
+ arrays_seen = 1 ;
510
+ #endif
504
511
dict = PyType_stgdict (desc );
505
512
if (dict == NULL ) {
506
513
Py_DECREF (pair );
@@ -641,6 +648,106 @@ PyCStructUnionType_update_stgdict(PyObject *type, PyObject *fields, int isStruct
641
648
stgdict -> align = total_align ;
642
649
stgdict -> length = len ; /* ADD ffi_ofs? */
643
650
651
+ #if defined(X86_64 )
652
+
653
+ #define MAX_ELEMENTS 16
654
+
655
+ if (arrays_seen && (size <= 16 )) {
656
+ /*
657
+ * See bpo-22273. Arrays are normally treated as pointers, which is
658
+ * fine when an array name is being passed as parameter, but not when
659
+ * passing structures by value that contain arrays. On 64-bit Linux,
660
+ * small structures passed by value are passed in registers, and in
661
+ * order to do this, libffi needs to know the true type of the array
662
+ * members of structs. Treating them as pointers breaks things.
663
+ *
664
+ * By small structures, we mean ones that are 16 bytes or less. In that
665
+ * case, there can't be more than 16 elements after unrolling arrays,
666
+ * as we (will) disallow bitfields. So we can collect the true ffi_type
667
+ * values in a fixed-size local array on the stack and, if any arrays
668
+ * were seen, replace the ffi_type_pointer.elements with a more
669
+ * accurate set, to allow libffi to marshal them into registers
670
+ * correctly. It means one more loop over the fields, but if we got
671
+ * here, the structure is small, so there aren't too many of those.
672
+ */
673
+ ffi_type * actual_types [MAX_ELEMENTS + 1 ];
674
+ int actual_type_index = 0 ;
675
+
676
+ memset (actual_types , 0 , sizeof (actual_types ));
677
+ for (i = 0 ; i < len ; ++ i ) {
678
+ PyObject * name , * desc ;
679
+ PyObject * pair = PySequence_GetItem (fields , i );
680
+ StgDictObject * dict ;
681
+ int bitsize = 0 ;
682
+
683
+ if (pair == NULL ) {
684
+ return -1 ;
685
+ }
686
+ if (!PyArg_ParseTuple (pair , "UO|i" , & name , & desc , & bitsize )) {
687
+ PyErr_SetString (PyExc_TypeError ,
688
+ "'_fields_' must be a sequence of (name, C type) pairs" );
689
+ Py_XDECREF (pair );
690
+ return -1 ;
691
+ }
692
+ dict = PyType_stgdict (desc );
693
+ if (dict == NULL ) {
694
+ Py_DECREF (pair );
695
+ PyErr_Format (PyExc_TypeError ,
696
+ "second item in _fields_ tuple (index %zd) must be a C type" ,
697
+ i );
698
+ return -1 ;
699
+ }
700
+ if (!PyCArrayTypeObject_Check (desc )) {
701
+ /* Not an array. Just copy over the element ffi_type. */
702
+ actual_types [actual_type_index ++ ] = & dict -> ffi_type_pointer ;
703
+ assert (actual_type_index <= MAX_ELEMENTS );
704
+ }
705
+ else {
706
+ int length = dict -> length ;
707
+ StgDictObject * edict ;
708
+
709
+ edict = PyType_stgdict (dict -> proto );
710
+ if (edict == NULL ) {
711
+ Py_DECREF (pair );
712
+ PyErr_Format (PyExc_TypeError ,
713
+ "second item in _fields_ tuple (index %zd) must be a C type" ,
714
+ i );
715
+ return -1 ;
716
+ }
717
+ /* Copy over the element's type, length times. */
718
+ while (length > 0 ) {
719
+ actual_types [actual_type_index ++ ] = & edict -> ffi_type_pointer ;
720
+ assert (actual_type_index <= MAX_ELEMENTS );
721
+ length -- ;
722
+ }
723
+ }
724
+ Py_DECREF (pair );
725
+ }
726
+
727
+ actual_types [actual_type_index ++ ] = NULL ;
728
+ /*
729
+ * Replace the old elements with the new, taking into account
730
+ * base class elements where necessary.
731
+ */
732
+ assert (stgdict -> ffi_type_pointer .elements );
733
+ PyMem_Free (stgdict -> ffi_type_pointer .elements );
734
+ stgdict -> ffi_type_pointer .elements = PyMem_New (ffi_type * ,
735
+ ffi_ofs + actual_type_index );
736
+ if (stgdict -> ffi_type_pointer .elements == NULL ) {
737
+ PyErr_NoMemory ();
738
+ return -1 ;
739
+ }
740
+ if (ffi_ofs ) {
741
+ memcpy (stgdict -> ffi_type_pointer .elements ,
742
+ basedict -> ffi_type_pointer .elements ,
743
+ ffi_ofs * sizeof (ffi_type * ));
744
+
745
+ }
746
+ memcpy (& stgdict -> ffi_type_pointer .elements [ffi_ofs ], actual_types ,
747
+ actual_type_index * sizeof (ffi_type * ));
748
+ }
749
+ #endif
750
+
644
751
/* We did check that this flag was NOT set above, it must not
645
752
have been set until now. */
646
753
if (stgdict -> flags & DICTFLAG_FINAL ) {
0 commit comments