https://github.com/python/cpython/commit/62fb15d8666f26883add93ae00cfff22ca95de2e
commit: 62fb15d8666f26883add93ae00cfff22ca95de2e
branch: main
author: Sergey Miryanov <sergey.mirya...@gmail.com>
committer: encukou <encu...@gmail.com>
date: 2025-03-24T13:55:09+01:00
summary:

gh-131311: Extract _replace_array_elements from 
PyCStructUnionType_update_stginfo (GH-131504)

files:
M Modules/_ctypes/stgdict.c

diff --git a/Modules/_ctypes/stgdict.c b/Modules/_ctypes/stgdict.c
index 4779d362d6a262..31b83374917152 100644
--- a/Modules/_ctypes/stgdict.c
+++ b/Modules/_ctypes/stgdict.c
@@ -210,6 +210,11 @@ MakeAnonFields(PyObject *type)
     return 0;
 }
 
+
+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,
   and initialize StgInfo.  Used for Structure and Union subclasses.
@@ -445,215 +450,9 @@ PyCStructUnionType_update_stginfo(PyObject *type, 
PyObject *fields, int isStruct
 #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();
+        if (_replace_array_elements(st, layout_fields, ffi_ofs, baseinfo, 
stginfo) < 0) {
             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;
     }
 
     /* We did check that this flag was NOT set above, it must not
@@ -677,3 +476,227 @@ PyCStructUnionType_update_stginfo(PyObject *type, 
PyObject *fields, int isStruct
     Py_XDECREF(format_spec_obj);
     return retval;
 }
+
+/*
+  Replace array elements at 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;
+}

_______________________________________________
Python-checkins mailing list -- python-checkins@python.org
To unsubscribe send an email to python-checkins-le...@python.org
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: arch...@mail-archive.com

Reply via email to