@@ -611,9 +611,9 @@ PyCStructUnionType_update_stgdict(PyObject *type, PyObject *fields, int isStruct
611
611
stgdict -> align = total_align ;
612
612
stgdict -> length = len ; /* ADD ffi_ofs? */
613
613
614
- #define MAX_ELEMENTS 16
614
+ #define MAX_STRUCT_SIZE 16
615
615
616
- if (arrays_seen && (size <= 16 )) {
616
+ if (arrays_seen && (size <= MAX_STRUCT_SIZE )) {
617
617
/*
618
618
* See bpo-22273. Arrays are normally treated as pointers, which is
619
619
* fine when an array name is being passed as parameter, but not when
@@ -634,11 +634,49 @@ PyCStructUnionType_update_stgdict(PyObject *type, PyObject *fields, int isStruct
634
634
* Although the passing in registers is specific to 64-bit Linux, the
635
635
* array-in-struct vs. pointer problem is general. But we restrict the
636
636
* type transformation to small structs nonetheless.
637
+ *
638
+ * Note that although a union may be small in terms of memory usage, it
639
+ * could contain many overlapping declarations of arrays, e.g.
640
+ *
641
+ * union {
642
+ * unsigned int_8 foo [16];
643
+ * unsigned uint_8 bar [16];
644
+ * unsigned int_16 baz[8];
645
+ * unsigned uint_16 bozz[8];
646
+ * unsigned int_32 fizz[4];
647
+ * unsigned uint_32 buzz[4];
648
+ * }
649
+ *
650
+ * which is still only 16 bytes in size. We need to convert this into
651
+ * the following equivalent for libffi:
652
+ *
653
+ * union {
654
+ * struct { int_8 e1; int_8 e2; ... int_8 e_16; } f1;
655
+ * struct { uint_8 e1; uint_8 e2; ... uint_8 e_16; } f2;
656
+ * struct { int_16 e1; int_16 e2; ... int_16 e_8; } f3;
657
+ * struct { uint_16 e1; uint_16 e2; ... uint_16 e_8; } f4;
658
+ * struct { int_32 e1; int_32 e2; ... int_32 e_4; } f5;
659
+ * struct { uint_32 e1; uint_32 e2; ... uint_32 e_4; } f6;
660
+ * }
661
+ *
662
+ * So the struct/union needs setting up as follows: all non-array
663
+ * elements copied across as is, and all array elements replaced with
664
+ * an equivalent struct which has as many fields as the array has
665
+ * elements, plus one NULL pointer.
637
666
*/
638
- ffi_type * actual_types [MAX_ELEMENTS + 1 ];
639
- int actual_type_index = 0 ;
640
667
641
- memset (actual_types , 0 , sizeof (actual_types ));
668
+ Py_ssize_t num_ffi_type_pointers = 0 ; /* for the dummy fields */
669
+ Py_ssize_t num_ffi_types = 0 ; /* for the dummy structures */
670
+ size_t alloc_size ; /* total bytes to allocate */
671
+ void * type_block ; /* to hold all the type information needed */
672
+ ffi_type * * element_types ; /* of this struct/union */
673
+ ffi_type * * dummy_types ; /* of the dummy struct elements */
674
+ ffi_type * structs ; /* point to struct aliases of arrays */
675
+ Py_ssize_t element_index ; /* index into element_types for this */
676
+ Py_ssize_t dummy_index = 0 ; /* index into dummy field pointers */
677
+ Py_ssize_t struct_index = 0 ; /* index into dummy structs */
678
+
679
+ /* first pass to see how much memory to allocate */
642
680
for (i = 0 ; i < len ; ++ i ) {
643
681
PyObject * name , * desc ;
644
682
PyObject * pair = PySequence_GetItem (fields , i );
@@ -648,24 +686,123 @@ PyCStructUnionType_update_stgdict(PyObject *type, PyObject *fields, int isStruct
648
686
if (pair == NULL ) {
649
687
return -1 ;
650
688
}
689
+ if (!PyArg_ParseTuple (pair , "UO|i" , & name , & desc , & bitsize )) {
690
+ PyErr_SetString (PyExc_TypeError ,
691
+ "'_fields_' must be a sequence of (name, C type) pairs" );
692
+ Py_DECREF (pair );
693
+ return -1 ;
694
+ }
695
+ dict = PyType_stgdict (desc );
696
+ if (dict == NULL ) {
697
+ Py_DECREF (pair );
698
+ PyErr_Format (PyExc_TypeError ,
699
+ "second item in _fields_ tuple (index %zd) must be a C type" ,
700
+ i );
701
+ return -1 ;
702
+ }
703
+ if (!PyCArrayTypeObject_Check (desc )) {
704
+ /* Not an array. Just need an ffi_type pointer. */
705
+ num_ffi_type_pointers ++ ;
706
+ }
707
+ else {
708
+ /* It's an array. */
709
+ Py_ssize_t length = dict -> length ;
710
+ StgDictObject * edict ;
711
+
712
+ edict = PyType_stgdict (dict -> proto );
713
+ if (edict == NULL ) {
714
+ Py_DECREF (pair );
715
+ PyErr_Format (PyExc_TypeError ,
716
+ "second item in _fields_ tuple (index %zd) must be a C type" ,
717
+ i );
718
+ return -1 ;
719
+ }
720
+ /*
721
+ * We need one extra ffi_type to hold the struct, and one
722
+ * ffi_type pointer per array element + one for a NULL to
723
+ * mark the end.
724
+ */
725
+ num_ffi_types ++ ;
726
+ num_ffi_type_pointers += length + 1 ;
727
+ }
728
+ Py_DECREF (pair );
729
+ }
730
+
731
+ /*
732
+ * At this point, we know we need storage for some ffi_types and some
733
+ * ffi_type pointers. We'll allocate these in one block.
734
+ * There are three sub-blocks of information: the ffi_type pointers to
735
+ * this structure/union's elements, the ffi_type_pointers to the
736
+ * dummy fields standing in for array elements, and the
737
+ * ffi_types representing the dummy structures.
738
+ */
739
+ alloc_size = (ffi_ofs + 1 + len + num_ffi_type_pointers ) * sizeof (ffi_type * ) +
740
+ num_ffi_types * sizeof (ffi_type );
741
+ type_block = PyMem_Malloc (alloc_size );
742
+
743
+ if (type_block == NULL ) {
744
+ PyErr_NoMemory ();
745
+ return -1 ;
746
+ }
747
+ /*
748
+ * the first block takes up ffi_ofs + len + 1 which is the pointers *
749
+ * for this struct/union. The second block takes up
750
+ * num_ffi_type_pointers, so the sum of these is ffi_ofs + len + 1 +
751
+ * num_ffi_type_pointers as allocated above. The last bit is the
752
+ * num_ffi_types structs.
753
+ */
754
+ element_types = (ffi_type * * ) type_block ;
755
+ dummy_types = & element_types [ffi_ofs + len + 1 ];
756
+ structs = (ffi_type * ) & dummy_types [num_ffi_type_pointers ];
757
+
758
+ if (num_ffi_types > 0 ) {
759
+ memset (structs , 0 , num_ffi_types * sizeof (ffi_type ));
760
+ }
761
+ if (ffi_ofs && (basedict != NULL )) {
762
+ memcpy (element_types ,
763
+ basedict -> ffi_type_pointer .elements ,
764
+ ffi_ofs * sizeof (ffi_type * ));
765
+ }
766
+ element_index = ffi_ofs ;
767
+
768
+ /* second pass to actually set the type pointers */
769
+ for (i = 0 ; i < len ; ++ i ) {
770
+ PyObject * name , * desc ;
771
+ PyObject * pair = PySequence_GetItem (fields , i );
772
+ StgDictObject * dict ;
773
+ int bitsize = 0 ;
774
+
775
+ if (pair == NULL ) {
776
+ PyMem_Free (type_block );
777
+ return -1 ;
778
+ }
779
+ /* In theory, we made this call in the first pass, so it *shouldn't*
780
+ * fail. However, you never know, and the code above might change
781
+ * later - keeping the check in here is a tad defensive but it
782
+ * will affect program size only slightly and performance hardly at
783
+ * all.
784
+ */
651
785
if (!PyArg_ParseTuple (pair , "UO|i" , & name , & desc , & bitsize )) {
652
786
PyErr_SetString (PyExc_TypeError ,
653
787
"'_fields_' must be a sequence of (name, C type) pairs" );
654
- Py_XDECREF (pair );
788
+ Py_DECREF (pair );
789
+ PyMem_Free (type_block );
655
790
return -1 ;
656
791
}
657
792
dict = PyType_stgdict (desc );
793
+ /* Possibly this check could be avoided, but see above comment. */
658
794
if (dict == NULL ) {
659
795
Py_DECREF (pair );
796
+ PyMem_Free (type_block );
660
797
PyErr_Format (PyExc_TypeError ,
661
798
"second item in _fields_ tuple (index %zd) must be a C type" ,
662
799
i );
663
800
return -1 ;
664
801
}
802
+ assert (element_index < (ffi_ofs + len )); /* will be used below */
665
803
if (!PyCArrayTypeObject_Check (desc )) {
666
804
/* Not an array. Just copy over the element ffi_type. */
667
- actual_types [actual_type_index ++ ] = & dict -> ffi_type_pointer ;
668
- assert (actual_type_index <= MAX_ELEMENTS );
805
+ element_types [element_index ++ ] = & dict -> ffi_type_pointer ;
669
806
}
670
807
else {
671
808
int length = dict -> length ;
@@ -674,42 +811,38 @@ PyCStructUnionType_update_stgdict(PyObject *type, PyObject *fields, int isStruct
674
811
edict = PyType_stgdict (dict -> proto );
675
812
if (edict == NULL ) {
676
813
Py_DECREF (pair );
814
+ PyMem_Free (type_block );
677
815
PyErr_Format (PyExc_TypeError ,
678
816
"second item in _fields_ tuple (index %zd) must be a C type" ,
679
817
i );
680
818
return -1 ;
681
819
}
820
+ element_types [element_index ++ ] = & structs [struct_index ];
821
+ structs [struct_index ].size = length * edict -> ffi_type_pointer .size ;
822
+ structs [struct_index ].alignment = edict -> ffi_type_pointer .alignment ;
823
+ structs [struct_index ].type = FFI_TYPE_STRUCT ;
824
+ structs [struct_index ].elements = & dummy_types [dummy_index ];
825
+ ++ struct_index ;
682
826
/* Copy over the element's type, length times. */
683
827
while (length > 0 ) {
684
- actual_types [ actual_type_index ++ ] = & edict -> ffi_type_pointer ;
685
- assert ( actual_type_index <= MAX_ELEMENTS ) ;
828
+ assert ( dummy_index < ( num_ffi_type_pointers )) ;
829
+ dummy_types [ dummy_index ++ ] = & edict -> ffi_type_pointer ;
686
830
length -- ;
687
831
}
832
+ assert (dummy_index < (num_ffi_type_pointers ));
833
+ dummy_types [dummy_index ++ ] = NULL ;
688
834
}
689
835
Py_DECREF (pair );
690
836
}
691
837
692
- actual_types [ actual_type_index ++ ] = NULL ;
838
+ element_types [ element_index ] = NULL ;
693
839
/*
694
840
* Replace the old elements with the new, taking into account
695
841
* base class elements where necessary.
696
842
*/
697
843
assert (stgdict -> ffi_type_pointer .elements );
698
844
PyMem_Free (stgdict -> ffi_type_pointer .elements );
699
- stgdict -> ffi_type_pointer .elements = PyMem_New (ffi_type * ,
700
- ffi_ofs + actual_type_index );
701
- if (stgdict -> ffi_type_pointer .elements == NULL ) {
702
- PyErr_NoMemory ();
703
- return -1 ;
704
- }
705
- if (ffi_ofs ) {
706
- memcpy (stgdict -> ffi_type_pointer .elements ,
707
- basedict -> ffi_type_pointer .elements ,
708
- ffi_ofs * sizeof (ffi_type * ));
709
-
710
- }
711
- memcpy (& stgdict -> ffi_type_pointer .elements [ffi_ofs ], actual_types ,
712
- actual_type_index * sizeof (ffi_type * ));
845
+ stgdict -> ffi_type_pointer .elements = element_types ;
713
846
}
714
847
715
848
/* We did check that this flag was NOT set above, it must not
0 commit comments