]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-127295: ctypes: Switch field accessors to fixed-width integers (GH-127297)
authorPetr Viktorin <encukou@gmail.com>
Fri, 20 Dec 2024 13:28:18 +0000 (14:28 +0100)
committerGitHub <noreply@github.com>
Fri, 20 Dec 2024 13:28:18 +0000 (14:28 +0100)
This should be a pure refactoring, without user-visible behaviour changes.

Before this change, ctypes uses traditional native C types, usually identified
by [`struct` format characters][struct-chars] when a short (and
identifier-friendly) name is needed:
- `signed char` (`b`) / `unsigned char` (`B`)
- `short` (`h`) / `unsigned short` (`h`)
- `int` (`i`) / `unsigned int` (`i`)
- `long` (`l`) / `unsigned long` (`l`)
- `long long` (`q`) / `unsigned long long` (`q`)

These map to C99 fixed-width types, which this PR switches to: -
- `int8_t`/`uint8_t`
- `int16_t`/`uint16_t`
- `int32_t`/`uint32_t`
- `int64_t`/`uint64_t`

The C standard doesn't guarantee that the “traditional” types must map to the
fixints. But, [`ctypes` currently requires it][swapdefs], so the assumption won't
break anything.

By “map” I mean that the *size* of the types matches. The *alignment*
requirements might not. This needs to be kept in mind but is not an issue in
`ctypes` accessors, which [explicitly handle unaligned memory][memcpy] for the
integer types.

Note that there are 5 “traditional” C type sizes, but 4 fixed-width ones. Two of
the former are functionally identical to one another; which ones they are is
platform-specific (e.g. `int`==`long`==`int32_t`.) This means that one of the
[current][current-impls-1] [implementations][current-impls-2] is redundant on
any given platform.

The fixint types are parametrized by the number of bytes/bits, and one bit for
signedness. This makes it easier to autogenerate code for them or to write
generic macros (though generic API like
[`PyLong_AsNativeBytes`][PyLong_AsNativeBytes] is problematic for performance
reasons -- especially compared to a `memcpy` with compile-time-constant size).

When one has a *different* integer type, determining the corresponding fixint
means a `sizeof` and signedness check. This is easier and more robust than the
current implementations (see [`wchar_t`][sizeof-wchar_t] or
[`_Bool`][sizeof-bool]).

[swapdefs]: https://github.com/python/cpython/blob/v3.13.0/Modules/_ctypes/cfield.c#L420-L444
[struct-chars]: https://docs.python.org/3/library/struct.html#format-characters
[current-impls-1]: https://github.com/python/cpython/blob/v3.13.0/Modules/_ctypes/cfield.c#L470-L653
[current-impls-2]: https://github.com/python/cpython/blob/v3.13.0/Modules/_ctypes/cfield.c#L703-L944
[memcpy]: https://github.com/python/cpython/blob/v3.13.0/Modules/_ctypes/cfield.c#L613
[PyLong_AsNativeBytes]: https://docs.python.org/3/c-api/long.html#c.PyLong_AsNativeBytes
[sizeof-wchar_t]: https://github.com/python/cpython/blob/v3.13.0/Modules/_ctypes/cfield.c#L1547-L1555
[sizeof-bool]: https://github.com/python/cpython/blob/v3.13.0/Modules/_ctypes/cfield.c#L1562-L1572

Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com>
Modules/_ctypes/_ctypes.c
Modules/_ctypes/callbacks.c
Modules/_ctypes/cfield.c
Modules/_ctypes/ctypes.h

index dcdb7b2052a11e6caba553e09f3be344c62f147f..ac520ffaad6c907b90887c428f9062ec420a7d29 100644 (file)
@@ -1979,7 +1979,7 @@ c_void_p_from_param_impl(PyObject *type, PyTypeObject *cls, PyObject *value)
             return NULL;
         parg->pffi_type = &ffi_type_pointer;
         parg->tag = 'P';
-        parg->obj = fd->setfunc(&parg->value, value, 0);
+        parg->obj = fd->setfunc(&parg->value, value, sizeof(void*));
         if (parg->obj == NULL) {
             Py_DECREF(parg);
             return NULL;
@@ -2444,7 +2444,7 @@ PyCSimpleType_from_param_impl(PyObject *type, PyTypeObject *cls,
 
     parg->tag = fmt[0];
     parg->pffi_type = fd->pffi_type;
-    parg->obj = fd->setfunc(&parg->value, value, 0);
+    parg->obj = fd->setfunc(&parg->value, value, info->size);
     if (parg->obj)
         return (PyObject *)parg;
     PyObject *exc = PyErr_GetRaisedException();
index 7b9f6437c7d55fc64ec0ba54e10833a332a2bc7f..89c0749a093765028bdec92fa9b9eca01555e285 100644 (file)
@@ -264,7 +264,7 @@ static void _CallPythonObject(ctypes_state *st,
            be the result.  EXCEPT when restype is py_object - Python
            itself knows how to manage the refcount of these objects.
         */
-        PyObject *keep = setfunc(mem, result, 0);
+        PyObject *keep = setfunc(mem, result, restype->size);
 
         if (keep == NULL) {
             /* Could not convert callback result. */
index 2b9e8a1a10d6f5666ed962c95bc235caeb4c66dd..dcac9da75360a4ee4885ce53c4336cd7e570abb3 100644 (file)
@@ -10,6 +10,7 @@
 
 #include "pycore_bitutils.h"      // _Py_bswap32()
 #include "pycore_call.h"          // _PyObject_CallNoArgs()
+#include <stdbool.h>              // bool
 
 #include <ffi.h>
 #include "ctypes.h"
@@ -320,61 +321,6 @@ PyType_Spec cfield_spec = {
 };
 
 
-/******************************************************************/
-/*
-  Accessor functions
-*/
-
-/* Derived from Modules/structmodule.c:
-   Helper routine to get a Python integer and raise the appropriate error
-   if it isn't one */
-
-static int
-get_long(PyObject *v, long *p)
-{
-    long x = PyLong_AsUnsignedLongMask(v);
-    if (x == -1 && PyErr_Occurred())
-        return -1;
-    *p = x;
-    return 0;
-}
-
-/* Same, but handling unsigned long */
-
-static int
-get_ulong(PyObject *v, unsigned long *p)
-{
-    unsigned long x = PyLong_AsUnsignedLongMask(v);
-    if (x == (unsigned long)-1 && PyErr_Occurred())
-        return -1;
-    *p = x;
-    return 0;
-}
-
-/* Same, but handling native long long. */
-
-static int
-get_longlong(PyObject *v, long long *p)
-{
-    long long x = PyLong_AsUnsignedLongLongMask(v);
-    if (x == -1 && PyErr_Occurred())
-        return -1;
-    *p = x;
-    return 0;
-}
-
-/* Same, but handling native unsigned long long. */
-
-static int
-get_ulonglong(PyObject *v, unsigned long long *p)
-{
-    unsigned long long x = PyLong_AsUnsignedLongLongMask(v);
-    if (x == (unsigned long long)-1 && PyErr_Occurred())
-        return -1;
-    *p = x;
-    return 0;
-}
-
 /*****************************************************************
  * Integer fields, with bitfield support
  */
@@ -404,34 +350,8 @@ Py_ssize_t NUM_BITS(Py_ssize_t bitsize) {
 /* This macro RETURNS the first parameter with the bit field CHANGED. */
 #define SET(type, x, v, size)                                                 \
     (NUM_BITS(size) ?                                                   \
-     ( ( (type)x & ~(BIT_MASK(type, size) << LOW_BIT(size)) ) | ( ((type)v & BIT_MASK(type, size)) << LOW_BIT(size) ) ) \
-     : (type)v)
-
-#if SIZEOF_SHORT == 2
-#  define SWAP_SHORT _Py_bswap16
-#else
-#  error "unsupported short size"
-#endif
-
-#if SIZEOF_INT == 4
-#  define SWAP_INT _Py_bswap32
-#else
-#  error "unsupported int size"
-#endif
-
-#if SIZEOF_LONG == 4
-#  define SWAP_LONG _Py_bswap32
-#elif SIZEOF_LONG == 8
-#  define SWAP_LONG _Py_bswap64
-#else
-#  error "unsupported long size"
-#endif
-
-#if SIZEOF_LONG_LONG == 8
-#  define SWAP_LONG_LONG _Py_bswap64
-#else
-#  error "unsupported long long size"
-#endif
+     ( ( (type)(x) & ~(BIT_MASK(type, size) << LOW_BIT(size)) ) | ( ((type)(v) & BIT_MASK(type, size)) << LOW_BIT(size) ) ) \
+     : (type)(v))
 
 /*****************************************************************
  * The setter methods return an object which must be kept alive, to keep the
@@ -454,203 +374,145 @@ Py_ssize_t NUM_BITS(Py_ssize_t bitsize) {
 #endif
 
 /*****************************************************************
- * integer accessor methods, supporting bit fields
+ * accessor methods for fixed-width integers (e.g. int8_t, uint64_t),
+ * supporting bit fields.
+ * These are named e.g. `i8_set`/`i8_get` or `u64_set`/`u64_get`,
+ * and are all alike, so they're defined using a macro.
  */
 
-static PyObject *
-b_set(void *ptr, PyObject *value, Py_ssize_t size)
-{
-    long val;
-    if (get_long(value, &val) < 0)
-        return NULL;
-    *(signed char *)ptr = SET(signed char, *(signed char *)ptr, val, size);
-    _RET(value);
-}
-
-
-static PyObject *
-b_get(void *ptr, Py_ssize_t size)
-{
-    signed char val = *(signed char *)ptr;
-    GET_BITFIELD(val, size);
-    return PyLong_FromLong(val);
-}
-
-static PyObject *
-B_set(void *ptr, PyObject *value, Py_ssize_t size)
-{
-    unsigned long val;
-    if (get_ulong(value, &val) < 0)
-        return NULL;
-    *(unsigned char *)ptr = SET(unsigned char, *(unsigned char*)ptr, val, size);
-    _RET(value);
-}
-
-
-static PyObject *
-B_get(void *ptr, Py_ssize_t size)
-{
-    unsigned char val = *(unsigned char *)ptr;
-    GET_BITFIELD(val, size);
-    return PyLong_FromLong(val);
-}
-
-static PyObject *
-h_set(void *ptr, PyObject *value, Py_ssize_t size)
-{
-    long val;
-    short x;
-    if (get_long(value, &val) < 0)
-        return NULL;
-    memcpy(&x, ptr, sizeof(x));
-    x = SET(short, x, val, size);
-    memcpy(ptr, &x, sizeof(x));
-    _RET(value);
-}
-
-
-static PyObject *
-h_set_sw(void *ptr, PyObject *value, Py_ssize_t size)
-{
-    long val;
-    short field;
-    if (get_long(value, &val) < 0) {
-        return NULL;
-    }
-    memcpy(&field, ptr, sizeof(field));
-    field = SWAP_SHORT(field);
-    field = SET(short, field, val, size);
-    field = SWAP_SHORT(field);
-    memcpy(ptr, &field, sizeof(field));
-    _RET(value);
-}
-
-static PyObject *
-h_get(void *ptr, Py_ssize_t size)
-{
-    short val;
-    memcpy(&val, ptr, sizeof(val));
-    GET_BITFIELD(val, size);
-    return PyLong_FromLong((long)val);
-}
-
-static PyObject *
-h_get_sw(void *ptr, Py_ssize_t size)
-{
-    short val;
-    memcpy(&val, ptr, sizeof(val));
-    val = SWAP_SHORT(val);
-    GET_BITFIELD(val, size);
-    return PyLong_FromLong(val);
-}
-
-static PyObject *
-H_set(void *ptr, PyObject *value, Py_ssize_t size)
-{
-    unsigned long val;
-    unsigned short x;
-    if (get_ulong(value, &val) < 0)
-        return NULL;
-    memcpy(&x, ptr, sizeof(x));
-    x = SET(unsigned short, x, val, size);
-    memcpy(ptr, &x, sizeof(x));
-    _RET(value);
-}
-
-static PyObject *
-H_set_sw(void *ptr, PyObject *value, Py_ssize_t size)
-{
-    unsigned long val;
-    unsigned short field;
-    if (get_ulong(value, &val) < 0) {
-        return NULL;
-    }
-    memcpy(&field, ptr, sizeof(field));
-    field = SWAP_SHORT(field);
-    field = SET(unsigned short, field, val, size);
-    field = SWAP_SHORT(field);
-    memcpy(ptr, &field, sizeof(field));
-    _RET(value);
-}
-
-
-static PyObject *
-H_get(void *ptr, Py_ssize_t size)
-{
-    unsigned short val;
-    memcpy(&val, ptr, sizeof(val));
-    GET_BITFIELD(val, size);
-    return PyLong_FromLong(val);
-}
-
-static PyObject *
-H_get_sw(void *ptr, Py_ssize_t size)
-{
-    unsigned short val;
-    memcpy(&val, ptr, sizeof(val));
-    val = SWAP_SHORT(val);
-    GET_BITFIELD(val, size);
-    return PyLong_FromLong(val);
-}
-
-static PyObject *
-i_set(void *ptr, PyObject *value, Py_ssize_t size)
-{
-    long val;
-    int x;
-    if (get_long(value, &val) < 0)
-        return NULL;
-    memcpy(&x, ptr, sizeof(x));
-    x = SET(int, x, val, size);
-    memcpy(ptr, &x, sizeof(x));
-    _RET(value);
-}
-
-static PyObject *
-i_set_sw(void *ptr, PyObject *value, Py_ssize_t size)
-{
-    long val;
-    int field;
-    if (get_long(value, &val) < 0) {
-        return NULL;
-    }
-    memcpy(&field, ptr, sizeof(field));
-    field = SWAP_INT(field);
-    field = SET(int, field, val, size);
-    field = SWAP_INT(field);
-    memcpy(ptr, &field, sizeof(field));
-    _RET(value);
-}
+#define FIXINT_GETSET(TAG, CTYPE, NBITS, PYAPI_FROMFUNC)                      \
+    static PyObject *                                                         \
+    TAG ## _set(void *ptr, PyObject *value, Py_ssize_t size_arg)              \
+    {                                                                         \
+        assert(NUM_BITS(size_arg) || (size_arg == (NBITS) / 8));              \
+        CTYPE val;                                                            \
+        if (PyLong_Check(value)                                               \
+            && PyUnstable_Long_IsCompact((PyLongObject *)value))              \
+        {                                                                     \
+            val = (CTYPE)PyUnstable_Long_CompactValue(                        \
+                      (PyLongObject *)value);                                 \
+        }                                                                     \
+        else {                                                                \
+            Py_ssize_t res = PyLong_AsNativeBytes(                            \
+                value, &val, (NBITS) / 8,                                     \
+                Py_ASNATIVEBYTES_NATIVE_ENDIAN                                \
+                | Py_ASNATIVEBYTES_ALLOW_INDEX);                              \
+            if (res < 0) {                                                    \
+                return NULL;                                                  \
+            }                                                                 \
+        }                                                                     \
+        CTYPE prev;                                                           \
+        memcpy(&prev, ptr, (NBITS) / 8);                                      \
+        val = SET(CTYPE, prev, val, size_arg);                                \
+        memcpy(ptr, &val, (NBITS) / 8);                                       \
+        _RET(value);                                                          \
+    }                                                                         \
+                                                                              \
+    static PyObject *                                                         \
+    TAG ## _get(void *ptr, Py_ssize_t size_arg)                               \
+    {                                                                         \
+        assert(NUM_BITS(size_arg) || (size_arg == (NBITS) / 8));              \
+        CTYPE val;                                                            \
+        memcpy(&val, ptr, sizeof(val));                                       \
+        GET_BITFIELD(val, size_arg);                                          \
+        return PYAPI_FROMFUNC(val);                                           \
+    }                                                                         \
+    ///////////////////////////////////////////////////////////////////////////
+
+/* Another macro for byte-swapped variants (e.g. `i8_set_sw`/`i8_get_sw`) */
+
+#define FIXINT_GETSET_SW(TAG, CTYPE, NBITS, PYAPI_FROMFUNC, PY_SWAPFUNC)      \
+    static PyObject *                                                         \
+    TAG ## _set_sw(void *ptr, PyObject *value, Py_ssize_t size_arg)           \
+    {                                                                         \
+        CTYPE val;                                                            \
+        PyObject *res = TAG ## _set(&val, value, (NBITS) / 8);                \
+        if (res == NULL) {                                                    \
+            return NULL;                                                      \
+        }                                                                     \
+        Py_DECREF(res);                                                       \
+        CTYPE field;                                                          \
+        memcpy(&field, ptr, sizeof(field));                                   \
+        field = PY_SWAPFUNC(field);                                           \
+        field = SET(CTYPE, field, val, size_arg);                             \
+        field = PY_SWAPFUNC(field);                                           \
+        memcpy(ptr, &field, sizeof(field));                                   \
+        _RET(value);                                                          \
+    }                                                                         \
+                                                                              \
+    static PyObject *                                                         \
+    TAG ## _get_sw(void *ptr, Py_ssize_t size_arg)                            \
+    {                                                                         \
+        assert(NUM_BITS(size_arg) || (size_arg == (NBITS) / 8));              \
+        CTYPE val;                                                            \
+        memcpy(&val, ptr, sizeof(val));                                       \
+        val = PY_SWAPFUNC(val);                                               \
+        GET_BITFIELD(val, size_arg);                                          \
+        return PYAPI_FROMFUNC(val);                                           \
+    }                                                                         \
+    ///////////////////////////////////////////////////////////////////////////
+
+/* These macros are expanded for all supported combinations of byte sizes
+ * (1, 2, 4, 8), signed and unsigned, native and swapped byteorder.
+ * That's a lot, so generate the list with Argument Clinic (`make clinic`).
+ */
 
+/*[python input]
+for nbits in 8, 16, 32, 64:
+    for sgn in 'i', 'u':
+        u = 'u' if sgn == 'u' else ''
+        U = u.upper()
+        apibits = max(nbits, 32)
+        parts = [
+            f'{sgn}{nbits}',
+            f'{u}int{nbits}_t',
+            f'{nbits}',
+            f'PyLong_From{U}Int{apibits}',
+        ]
+        print(f'FIXINT_GETSET({", ".join(parts)})')
+        if nbits > 8:
+            parts.append(f'_Py_bswap{nbits}')
+            print(f'FIXINT_GETSET_SW({", ".join(parts)})')
+[python start generated code]*/
+FIXINT_GETSET(i8, int8_t, 8, PyLong_FromInt32)
+FIXINT_GETSET(u8, uint8_t, 8, PyLong_FromUInt32)
+FIXINT_GETSET(i16, int16_t, 16, PyLong_FromInt32)
+FIXINT_GETSET_SW(i16, int16_t, 16, PyLong_FromInt32, _Py_bswap16)
+FIXINT_GETSET(u16, uint16_t, 16, PyLong_FromUInt32)
+FIXINT_GETSET_SW(u16, uint16_t, 16, PyLong_FromUInt32, _Py_bswap16)
+FIXINT_GETSET(i32, int32_t, 32, PyLong_FromInt32)
+FIXINT_GETSET_SW(i32, int32_t, 32, PyLong_FromInt32, _Py_bswap32)
+FIXINT_GETSET(u32, uint32_t, 32, PyLong_FromUInt32)
+FIXINT_GETSET_SW(u32, uint32_t, 32, PyLong_FromUInt32, _Py_bswap32)
+FIXINT_GETSET(i64, int64_t, 64, PyLong_FromInt64)
+FIXINT_GETSET_SW(i64, int64_t, 64, PyLong_FromInt64, _Py_bswap64)
+FIXINT_GETSET(u64, uint64_t, 64, PyLong_FromUInt64)
+FIXINT_GETSET_SW(u64, uint64_t, 64, PyLong_FromUInt64, _Py_bswap64)
+/*[python end generated code: output=3d60c96fa58e07d5 input=0b7e166f2ea18e70]*/
+
+// For one-byte types, swapped variants are the same as native
+#define i8_set_sw i8_set
+#define i8_get_sw i8_get
+#define u8_set_sw u8_set
+#define u8_get_sw u8_get
+
+#undef FIXINT_GETSET
+#undef FIXINT_GETSET_SW
 
-static PyObject *
-i_get(void *ptr, Py_ssize_t size)
-{
-    int val;
-    memcpy(&val, ptr, sizeof(val));
-    GET_BITFIELD(val, size);
-    return PyLong_FromLong(val);
-}
-
-static PyObject *
-i_get_sw(void *ptr, Py_ssize_t size)
-{
-    int val;
-    memcpy(&val, ptr, sizeof(val));
-    val = SWAP_INT(val);
-    GET_BITFIELD(val, size);
-    return PyLong_FromLong(val);
-}
+/*****************************************************************
+ * non-integer accessor methods, not supporting bit fields
+ */
 
 #ifndef MS_WIN32
 /* http://msdn.microsoft.com/en-us/library/cc237864.aspx */
 #define VARIANT_FALSE 0x0000
 #define VARIANT_TRUE 0xFFFF
 #endif
-/* short BOOL - VARIANT_BOOL */
+/* v: short BOOL - VARIANT_BOOL */
 static PyObject *
-vBOOL_set(void *ptr, PyObject *value, Py_ssize_t size)
+v_set(void *ptr, PyObject *value, Py_ssize_t size)
 {
+    assert(NUM_BITS(size) || (size == sizeof(short int)));
     switch (PyObject_IsTrue(value)) {
     case -1:
         return NULL;
@@ -664,22 +526,25 @@ vBOOL_set(void *ptr, PyObject *value, Py_ssize_t size)
 }
 
 static PyObject *
-vBOOL_get(void *ptr, Py_ssize_t size)
+v_get(void *ptr, Py_ssize_t size)
 {
+    assert(NUM_BITS(size) || (size == sizeof(short int)));
     return PyBool_FromLong((long)*(short int *)ptr);
 }
 
+/* bool ('?'): bool (i.e. _Bool) */
 static PyObject *
 bool_set(void *ptr, PyObject *value, Py_ssize_t size)
 {
+    assert(NUM_BITS(size) || (size == sizeof(bool)));
     switch (PyObject_IsTrue(value)) {
     case -1:
         return NULL;
     case 0:
-        *(_Bool *)ptr = 0;
+        *(bool *)ptr = 0;
         _RET(value);
     default:
-        *(_Bool *)ptr = 1;
+        *(bool *)ptr = 1;
         _RET(value);
     }
 }
@@ -687,260 +552,15 @@ bool_set(void *ptr, PyObject *value, Py_ssize_t size)
 static PyObject *
 bool_get(void *ptr, Py_ssize_t size)
 {
-    return PyBool_FromLong((long)*(_Bool *)ptr);
-}
-
-static PyObject *
-I_set(void *ptr, PyObject *value, Py_ssize_t size)
-{
-    unsigned long val;
-    unsigned int x;
-    if (get_ulong(value, &val) < 0)
-        return  NULL;
-    memcpy(&x, ptr, sizeof(x));
-    x = SET(unsigned int, x, val, size);
-    memcpy(ptr, &x, sizeof(x));
-    _RET(value);
-}
-
-static PyObject *
-I_set_sw(void *ptr, PyObject *value, Py_ssize_t size)
-{
-    unsigned long val;
-    unsigned int field;
-    if (get_ulong(value, &val) < 0) {
-        return  NULL;
-    }
-    memcpy(&field, ptr, sizeof(field));
-    field = SWAP_INT(field);
-    field = SET(unsigned int, field, (unsigned int)val, size);
-    field = SWAP_INT(field);
-    memcpy(ptr, &field, sizeof(field));
-    _RET(value);
-}
-
-
-static PyObject *
-I_get(void *ptr, Py_ssize_t size)
-{
-    unsigned int val;
-    memcpy(&val, ptr, sizeof(val));
-    GET_BITFIELD(val, size);
-    return PyLong_FromUnsignedLong(val);
-}
-
-static PyObject *
-I_get_sw(void *ptr, Py_ssize_t size)
-{
-    unsigned int val;
-    memcpy(&val, ptr, sizeof(val));
-    val = SWAP_INT(val);
-    GET_BITFIELD(val, size);
-    return PyLong_FromUnsignedLong(val);
-}
-
-static PyObject *
-l_set(void *ptr, PyObject *value, Py_ssize_t size)
-{
-    long val;
-    long x;
-    if (get_long(value, &val) < 0)
-        return NULL;
-    memcpy(&x, ptr, sizeof(x));
-    x = SET(long, x, val, size);
-    memcpy(ptr, &x, sizeof(x));
-    _RET(value);
-}
-
-static PyObject *
-l_set_sw(void *ptr, PyObject *value, Py_ssize_t size)
-{
-    long val;
-    long field;
-    if (get_long(value, &val) < 0) {
-        return NULL;
-    }
-    memcpy(&field, ptr, sizeof(field));
-    field = SWAP_LONG(field);
-    field = SET(long, field, val, size);
-    field = SWAP_LONG(field);
-    memcpy(ptr, &field, sizeof(field));
-    _RET(value);
-}
-
-
-static PyObject *
-l_get(void *ptr, Py_ssize_t size)
-{
-    long val;
-    memcpy(&val, ptr, sizeof(val));
-    GET_BITFIELD(val, size);
-    return PyLong_FromLong(val);
-}
-
-static PyObject *
-l_get_sw(void *ptr, Py_ssize_t size)
-{
-    long val;
-    memcpy(&val, ptr, sizeof(val));
-    val = SWAP_LONG(val);
-    GET_BITFIELD(val, size);
-    return PyLong_FromLong(val);
-}
-
-static PyObject *
-L_set(void *ptr, PyObject *value, Py_ssize_t size)
-{
-    unsigned long val;
-    unsigned long x;
-    if (get_ulong(value, &val) < 0)
-        return  NULL;
-    memcpy(&x, ptr, sizeof(x));
-    x = SET(unsigned long, x, val, size);
-    memcpy(ptr, &x, sizeof(x));
-    _RET(value);
-}
-
-static PyObject *
-L_set_sw(void *ptr, PyObject *value, Py_ssize_t size)
-{
-    unsigned long val;
-    unsigned long field;
-    if (get_ulong(value, &val) < 0) {
-        return  NULL;
-    }
-    memcpy(&field, ptr, sizeof(field));
-    field = SWAP_LONG(field);
-    field = SET(unsigned long, field, val, size);
-    field = SWAP_LONG(field);
-    memcpy(ptr, &field, sizeof(field));
-    _RET(value);
-}
-
-
-static PyObject *
-L_get(void *ptr, Py_ssize_t size)
-{
-    unsigned long val;
-    memcpy(&val, ptr, sizeof(val));
-    GET_BITFIELD(val, size);
-    return PyLong_FromUnsignedLong(val);
-}
-
-static PyObject *
-L_get_sw(void *ptr, Py_ssize_t size)
-{
-    unsigned long val;
-    memcpy(&val, ptr, sizeof(val));
-    val = SWAP_LONG(val);
-    GET_BITFIELD(val, size);
-    return PyLong_FromUnsignedLong(val);
-}
-
-static PyObject *
-q_set(void *ptr, PyObject *value, Py_ssize_t size)
-{
-    long long val;
-    long long x;
-    if (get_longlong(value, &val) < 0)
-        return NULL;
-    memcpy(&x, ptr, sizeof(x));
-    x = SET(long long, x, val, size);
-    memcpy(ptr, &x, sizeof(x));
-    _RET(value);
-}
-
-static PyObject *
-q_set_sw(void *ptr, PyObject *value, Py_ssize_t size)
-{
-    long long val;
-    long long field;
-    if (get_longlong(value, &val) < 0) {
-        return NULL;
-    }
-    memcpy(&field, ptr, sizeof(field));
-    field = SWAP_LONG_LONG(field);
-    field = SET(long long, field, val, size);
-    field = SWAP_LONG_LONG(field);
-    memcpy(ptr, &field, sizeof(field));
-    _RET(value);
-}
-
-static PyObject *
-q_get(void *ptr, Py_ssize_t size)
-{
-    long long val;
-    memcpy(&val, ptr, sizeof(val));
-    GET_BITFIELD(val, size);
-    return PyLong_FromLongLong(val);
-}
-
-static PyObject *
-q_get_sw(void *ptr, Py_ssize_t size)
-{
-    long long val;
-    memcpy(&val, ptr, sizeof(val));
-    val = SWAP_LONG_LONG(val);
-    GET_BITFIELD(val, size);
-    return PyLong_FromLongLong(val);
-}
-
-static PyObject *
-Q_set(void *ptr, PyObject *value, Py_ssize_t size)
-{
-    unsigned long long val;
-    unsigned long long x;
-    if (get_ulonglong(value, &val) < 0)
-        return NULL;
-    memcpy(&x, ptr, sizeof(x));
-    x = SET(long long, x, val, size);
-    memcpy(ptr, &x, sizeof(x));
-    _RET(value);
-}
-
-static PyObject *
-Q_set_sw(void *ptr, PyObject *value, Py_ssize_t size)
-{
-    unsigned long long val;
-    unsigned long long field;
-    if (get_ulonglong(value, &val) < 0) {
-        return NULL;
-    }
-    memcpy(&field, ptr, sizeof(field));
-    field = SWAP_LONG_LONG(field);
-    field = SET(unsigned long long, field, val, size);
-    field = SWAP_LONG_LONG(field);
-    memcpy(ptr, &field, sizeof(field));
-    _RET(value);
-}
-
-static PyObject *
-Q_get(void *ptr, Py_ssize_t size)
-{
-    unsigned long long val;
-    memcpy(&val, ptr, sizeof(val));
-    GET_BITFIELD(val, size);
-    return PyLong_FromUnsignedLongLong(val);
-}
-
-static PyObject *
-Q_get_sw(void *ptr, Py_ssize_t size)
-{
-    unsigned long long val;
-    memcpy(&val, ptr, sizeof(val));
-    val = SWAP_LONG_LONG(val);
-    GET_BITFIELD(val, size);
-    return PyLong_FromUnsignedLongLong(val);
+    assert(NUM_BITS(size) || (size == sizeof(bool)));
+    return PyBool_FromLong((long)*(bool *)ptr);
 }
 
-/*****************************************************************
- * non-integer accessor methods, not supporting bit fields
- */
-
-
+/* g: long double */
 static PyObject *
 g_set(void *ptr, PyObject *value, Py_ssize_t size)
 {
+    assert(NUM_BITS(size) || (size == sizeof(long double)));
     long double x;
 
     x = PyFloat_AsDouble(value);
@@ -953,14 +573,17 @@ g_set(void *ptr, PyObject *value, Py_ssize_t size)
 static PyObject *
 g_get(void *ptr, Py_ssize_t size)
 {
+    assert(NUM_BITS(size) || (size == sizeof(long double)));
     long double val;
     memcpy(&val, ptr, sizeof(long double));
     return PyFloat_FromDouble(val);
 }
 
+/* d: double */
 static PyObject *
 d_set(void *ptr, PyObject *value, Py_ssize_t size)
 {
+    assert(NUM_BITS(size) || (size == sizeof(double)));
     double x;
 
     x = PyFloat_AsDouble(value);
@@ -973,15 +596,18 @@ d_set(void *ptr, PyObject *value, Py_ssize_t size)
 static PyObject *
 d_get(void *ptr, Py_ssize_t size)
 {
+    assert(NUM_BITS(size) || (size == sizeof(double)));
     double val;
     memcpy(&val, ptr, sizeof(val));
     return PyFloat_FromDouble(val);
 }
 
 #if defined(Py_HAVE_C_COMPLEX) && defined(Py_FFI_SUPPORT_C_COMPLEX)
+/* C: double complex */
 static PyObject *
 C_set(void *ptr, PyObject *value, Py_ssize_t size)
 {
+    assert(NUM_BITS(size) || (size == sizeof(double complex)));
     Py_complex c = PyComplex_AsCComplex(value);
 
     if (c.real == -1 && PyErr_Occurred()) {
@@ -995,15 +621,18 @@ C_set(void *ptr, PyObject *value, Py_ssize_t size)
 static PyObject *
 C_get(void *ptr, Py_ssize_t size)
 {
+    assert(NUM_BITS(size) || (size == sizeof(double complex)));
     double complex x;
 
     memcpy(&x, ptr, sizeof(x));
     return PyComplex_FromDoubles(creal(x), cimag(x));
 }
 
+/* E: float complex */
 static PyObject *
 E_set(void *ptr, PyObject *value, Py_ssize_t size)
 {
+    assert(NUM_BITS(size) || (size == sizeof(float complex)));
     Py_complex c = PyComplex_AsCComplex(value);
 
     if (c.real == -1 && PyErr_Occurred()) {
@@ -1017,15 +646,18 @@ E_set(void *ptr, PyObject *value, Py_ssize_t size)
 static PyObject *
 E_get(void *ptr, Py_ssize_t size)
 {
+    assert(NUM_BITS(size) || (size == sizeof(float complex)));
     float complex x;
 
     memcpy(&x, ptr, sizeof(x));
     return PyComplex_FromDoubles(crealf(x), cimagf(x));
 }
 
+/* F: long double complex */
 static PyObject *
 F_set(void *ptr, PyObject *value, Py_ssize_t size)
 {
+    assert(NUM_BITS(size) || (size == sizeof(long double complex)));
     Py_complex c = PyComplex_AsCComplex(value);
 
     if (c.real == -1 && PyErr_Occurred()) {
@@ -1039,6 +671,7 @@ F_set(void *ptr, PyObject *value, Py_ssize_t size)
 static PyObject *
 F_get(void *ptr, Py_ssize_t size)
 {
+    assert(NUM_BITS(size) || (size == sizeof(long double complex)));
     long double complex x;
 
     memcpy(&x, ptr, sizeof(x));
@@ -1046,9 +679,11 @@ F_get(void *ptr, Py_ssize_t size)
 }
 #endif
 
+/* d: double */
 static PyObject *
 d_set_sw(void *ptr, PyObject *value, Py_ssize_t size)
 {
+    assert(NUM_BITS(size) || (size == sizeof(double)));
     double x;
 
     x = PyFloat_AsDouble(value);
@@ -1067,6 +702,7 @@ d_set_sw(void *ptr, PyObject *value, Py_ssize_t size)
 static PyObject *
 d_get_sw(void *ptr, Py_ssize_t size)
 {
+    assert(NUM_BITS(size) || (size == sizeof(double)));
 #ifdef WORDS_BIGENDIAN
     return PyFloat_FromDouble(PyFloat_Unpack8(ptr, 1));
 #else
@@ -1074,9 +710,11 @@ d_get_sw(void *ptr, Py_ssize_t size)
 #endif
 }
 
+/* f: float */
 static PyObject *
 f_set(void *ptr, PyObject *value, Py_ssize_t size)
 {
+    assert(NUM_BITS(size) || (size == sizeof(float)));
     float x;
 
     x = (float)PyFloat_AsDouble(value);
@@ -1089,6 +727,7 @@ f_set(void *ptr, PyObject *value, Py_ssize_t size)
 static PyObject *
 f_get(void *ptr, Py_ssize_t size)
 {
+    assert(NUM_BITS(size) || (size == sizeof(float)));
     float val;
     memcpy(&val, ptr, sizeof(val));
     return PyFloat_FromDouble(val);
@@ -1097,6 +736,7 @@ f_get(void *ptr, Py_ssize_t size)
 static PyObject *
 f_set_sw(void *ptr, PyObject *value, Py_ssize_t size)
 {
+    assert(NUM_BITS(size) || (size == sizeof(float)));
     float x;
 
     x = (float)PyFloat_AsDouble(value);
@@ -1115,6 +755,7 @@ f_set_sw(void *ptr, PyObject *value, Py_ssize_t size)
 static PyObject *
 f_get_sw(void *ptr, Py_ssize_t size)
 {
+    assert(NUM_BITS(size) || (size == sizeof(float)));
 #ifdef WORDS_BIGENDIAN
     return PyFloat_FromDouble(PyFloat_Unpack4(ptr, 1));
 #else
@@ -1122,6 +763,7 @@ f_get_sw(void *ptr, Py_ssize_t size)
 #endif
 }
 
+/* O: Python object */
 /*
   py_object refcounts:
 
@@ -1135,6 +777,7 @@ f_get_sw(void *ptr, Py_ssize_t size)
 static PyObject *
 O_get(void *ptr, Py_ssize_t size)
 {
+    assert(NUM_BITS(size) || (size == sizeof(PyObject *)));
     PyObject *ob = *(PyObject **)ptr;
     if (ob == NULL) {
         if (!PyErr_Occurred())
@@ -1149,15 +792,18 @@ O_get(void *ptr, Py_ssize_t size)
 static PyObject *
 O_set(void *ptr, PyObject *value, Py_ssize_t size)
 {
+    assert(NUM_BITS(size) || (size == sizeof(PyObject *)));
     /* Hm, does the memory block need it's own refcount or not? */
     *(PyObject **)ptr = value;
     return Py_NewRef(value);
 }
 
 
+/* c: a single byte-character */
 static PyObject *
 c_set(void *ptr, PyObject *value, Py_ssize_t size)
 {
+    assert(NUM_BITS(size) || (size == sizeof(char)));
     if (PyBytes_Check(value)) {
         if (PyBytes_GET_SIZE(value) != 1) {
             PyErr_Format(PyExc_TypeError,
@@ -1204,13 +850,15 @@ c_set(void *ptr, PyObject *value, Py_ssize_t size)
 static PyObject *
 c_get(void *ptr, Py_ssize_t size)
 {
+    assert(NUM_BITS(size) || (size == sizeof(char)));
     return PyBytes_FromStringAndSize((char *)ptr, 1);
 }
 
-/* u - a single wchar_t character */
+/* u: a single wchar_t character */
 static PyObject *
 u_set(void *ptr, PyObject *value, Py_ssize_t size)
 {
+    assert(NUM_BITS(size) || (size == sizeof(wchar_t)));
     Py_ssize_t len;
     wchar_t chars[2];
     if (!PyUnicode_Check(value)) {
@@ -1244,10 +892,11 @@ u_set(void *ptr, PyObject *value, Py_ssize_t size)
 static PyObject *
 u_get(void *ptr, Py_ssize_t size)
 {
+    assert(NUM_BITS(size) || (size == sizeof(wchar_t)));
     return PyUnicode_FromWideChar((wchar_t *)ptr, 1);
 }
 
-/* U - a unicode string */
+/* U: a wchar_t* unicode string */
 static PyObject *
 U_get(void *ptr, Py_ssize_t size)
 {
@@ -1306,6 +955,7 @@ U_set(void *ptr, PyObject *value, Py_ssize_t length)
 }
 
 
+/* s: a byte string */
 static PyObject *
 s_get(void *ptr, Py_ssize_t size)
 {
@@ -1355,6 +1005,7 @@ s_set(void *ptr, PyObject *value, Py_ssize_t length)
     _RET(value);
 }
 
+/* z: a byte string, can be set from integer pointer */
 static PyObject *
 z_set(void *ptr, PyObject *value, Py_ssize_t size)
 {
@@ -1391,6 +1042,7 @@ z_get(void *ptr, Py_ssize_t size)
     }
 }
 
+/* Z: a wchar* string, can be set from integer pointer */
 static PyObject *
 Z_set(void *ptr, PyObject *value, Py_ssize_t size)
 {
@@ -1445,8 +1097,9 @@ Z_get(void *ptr, Py_ssize_t size)
 
 
 #ifdef MS_WIN32
+/* X: COM BSTR (wide-char string to be handled handled using Windows API) */
 static PyObject *
-BSTR_set(void *ptr, PyObject *value, Py_ssize_t size)
+X_set(void *ptr, PyObject *value, Py_ssize_t size)
 {
     BSTR bstr;
 
@@ -1490,7 +1143,7 @@ BSTR_set(void *ptr, PyObject *value, Py_ssize_t size)
 
 
 static PyObject *
-BSTR_get(void *ptr, Py_ssize_t size)
+X_get(void *ptr, Py_ssize_t size)
 {
     BSTR p;
     p = *(BSTR *)ptr;
@@ -1505,9 +1158,11 @@ BSTR_get(void *ptr, Py_ssize_t size)
 }
 #endif
 
+/* P: generic pointer */
 static PyObject *
 P_set(void *ptr, PyObject *value, Py_ssize_t size)
 {
+    assert(NUM_BITS(size) || (size == sizeof(void *)));
     void *v;
     if (value == Py_None) {
         *(void **)ptr = NULL;
@@ -1539,154 +1194,339 @@ P_set(void *ptr, PyObject *value, Py_ssize_t size)
 static PyObject *
 P_get(void *ptr, Py_ssize_t size)
 {
+    assert(NUM_BITS(size) || (size == sizeof(void *)));
     if (*(void **)ptr == NULL) {
         Py_RETURN_NONE;
     }
     return PyLong_FromVoidPtr(*(void **)ptr);
 }
 
-static struct fielddesc formattable[] = {
-    { 's', s_set, s_get, NULL},
-    { 'b', b_set, b_get, NULL},
-    { 'B', B_set, B_get, NULL},
-    { 'c', c_set, c_get, NULL},
-    { 'd', d_set, d_get, NULL, d_set_sw, d_get_sw},
-#if defined(Py_HAVE_C_COMPLEX) && defined(Py_FFI_SUPPORT_C_COMPLEX)
-    { 'C', C_set, C_get, NULL},
-    { 'E', E_set, E_get, NULL},
-    { 'F', F_set, F_get, NULL},
-#endif
-    { 'g', g_set, g_get, NULL},
-    { 'f', f_set, f_get, NULL, f_set_sw, f_get_sw},
-    { 'h', h_set, h_get, NULL, h_set_sw, h_get_sw},
-    { 'H', H_set, H_get, NULL, H_set_sw, H_get_sw},
-    { 'i', i_set, i_get, NULL, i_set_sw, i_get_sw},
-    { 'I', I_set, I_get, NULL, I_set_sw, I_get_sw},
-    { 'l', l_set, l_get, NULL, l_set_sw, l_get_sw},
-    { 'L', L_set, L_get, NULL, L_set_sw, L_get_sw},
-    { 'q', q_set, q_get, NULL, q_set_sw, q_get_sw},
-    { 'Q', Q_set, Q_get, NULL, Q_set_sw, Q_get_sw},
-    { 'P', P_set, P_get, NULL},
-    { 'z', z_set, z_get, NULL},
-    { 'u', u_set, u_get, NULL},
-    { 'U', U_set, U_get, NULL},
-    { 'Z', Z_set, Z_get, NULL},
-#ifdef MS_WIN32
-    { 'X', BSTR_set, BSTR_get, NULL},
-#endif
-    { 'v', vBOOL_set, vBOOL_get, NULL},
-#if SIZEOF__BOOL == SIZEOF_INT
-    { '?', bool_set, bool_get, NULL, I_set_sw, I_get_sw},
-#elif SIZEOF__BOOL == SIZEOF_LONG
-    { '?', bool_set, bool_get, NULL, L_set_sw, L_get_sw},
-#elif SIZEOF__BOOL == SIZEOF_LONG_LONG
-    { '?', bool_set, bool_get, NULL, Q_set_sw, Q_get_sw},
-#else
-    { '?', bool_set, bool_get, NULL},
-#endif /* SIZEOF__BOOL */
-    { 'O', O_set, O_get, NULL},
-    { 0, NULL, NULL, NULL},
+/* Table with info about all formats.
+ * Must be accessed via _ctypes_get_fielddesc, which initializes it on
+ * first use. After initialization it's treated as constant & read-only.
+ */
+
+struct formattable {
+/*[python input]
+for nbytes in 8, 16, 32, 64:
+    for sgn in 'i', 'u':
+        print(f'    struct fielddesc fmt_{sgn}{nbytes};')
+for code in 'sbBcdCEFgfhHiIlLqQPzuUZXvO':
+    print(f'    struct fielddesc fmt_{code};')
+[python start generated code]*/
+    struct fielddesc fmt_i8;
+    struct fielddesc fmt_u8;
+    struct fielddesc fmt_i16;
+    struct fielddesc fmt_u16;
+    struct fielddesc fmt_i32;
+    struct fielddesc fmt_u32;
+    struct fielddesc fmt_i64;
+    struct fielddesc fmt_u64;
+    struct fielddesc fmt_s;
+    struct fielddesc fmt_b;
+    struct fielddesc fmt_B;
+    struct fielddesc fmt_c;
+    struct fielddesc fmt_d;
+    struct fielddesc fmt_C;
+    struct fielddesc fmt_E;
+    struct fielddesc fmt_F;
+    struct fielddesc fmt_g;
+    struct fielddesc fmt_f;
+    struct fielddesc fmt_h;
+    struct fielddesc fmt_H;
+    struct fielddesc fmt_i;
+    struct fielddesc fmt_I;
+    struct fielddesc fmt_l;
+    struct fielddesc fmt_L;
+    struct fielddesc fmt_q;
+    struct fielddesc fmt_Q;
+    struct fielddesc fmt_P;
+    struct fielddesc fmt_z;
+    struct fielddesc fmt_u;
+    struct fielddesc fmt_U;
+    struct fielddesc fmt_Z;
+    struct fielddesc fmt_X;
+    struct fielddesc fmt_v;
+    struct fielddesc fmt_O;
+/*[python end generated code: output=fa648744ec7f919d input=087d58357d4bf2c5]*/
+
+    // bool has code '?':
+    struct fielddesc fmt_bool;
+
+    // always contains NULLs:
+    struct fielddesc fmt_nil;
 };
 
-/*
-  Ideas: Implement VARIANT in this table, using 'V' code.
-  Use '?' as code for BOOL.
-*/
+static struct formattable formattable;
+
+
+/* Get fielddesc info for a fixed-width integer.
+ * N.B: - must be called after (or from) _ctypes_init_fielddesc!
+ *      - nbytes must be one of the supported values
+ */
+
+static inline struct fielddesc *
+_ctypes_fixint_fielddesc(Py_ssize_t nbytes, bool is_signed)
+{
+#define _PACK(NBYTES, SGN) ((NBYTES<<2) + (SGN ? 1 : 0))
+    switch (_PACK(nbytes, is_signed)) {
+/*[python input]
+for nbytes in 8, 16, 32, 64:
+    for sgn in 'i', 'u':
+        is_signed = sgn == 'i'
+        print(f'    case (_PACK({nbytes // 8}, {int(is_signed)})): '
+              + f'return &formattable.fmt_{sgn}{nbytes};')
+[python start generated code]*/
+    case (_PACK(1, 1)): return &formattable.fmt_i8;
+    case (_PACK(1, 0)): return &formattable.fmt_u8;
+    case (_PACK(2, 1)): return &formattable.fmt_i16;
+    case (_PACK(2, 0)): return &formattable.fmt_u16;
+    case (_PACK(4, 1)): return &formattable.fmt_i32;
+    case (_PACK(4, 0)): return &formattable.fmt_u32;
+    case (_PACK(8, 1)): return &formattable.fmt_i64;
+    case (_PACK(8, 0)): return &formattable.fmt_u64;
+/*[python end generated code: output=0194ba35c4d64ff3 input=ee9f6f5bb872d645]*/
+#undef _PACK
+    }
+    /* ctypes currently only supports platforms where the basic integer types
+     * (`char`, `short`, `int`, `long`, `long long`) have 1, 2, 4, or 8 bytes
+     * (i.e. 8 to 64 bits).
+     */
+    Py_UNREACHABLE();
+}
+
+
+/* Macro to call _ctypes_fixint_fielddesc for a given C type. */
+
+_Py_COMP_DIAG_PUSH
+#if defined(__GNUC__) && (__GNUC__ < 14)
+/* The signedness check expands to an expression that's always true or false.
+ * Older GCC gives a '-Wtype-limits' warning for this, which is a GCC bug
+ * (docs say it should "not warn for constant expressions"):
+ *      https://gcc.gnu.org/bugzilla/show_bug.cgi?id=86647
+ * Silence that warning.
+ */
+#pragma GCC diagnostic ignored "-Wtype-limits"
+#endif
+
+#define FIXINT_FIELDDESC_FOR(C_TYPE) \
+    _ctypes_fixint_fielddesc(sizeof(C_TYPE), (C_TYPE)-1 < 0)
+
 
 /* Delayed initialization. Windows cannot statically reference dynamically
    loaded addresses from DLLs. */
 void
 _ctypes_init_fielddesc(void)
 {
-    struct fielddesc *fd = formattable;
-    for (; fd->code; ++fd) {
-        switch (fd->code) {
-        case 's': fd->pffi_type = &ffi_type_pointer; break;
-        case 'b': fd->pffi_type = &ffi_type_schar; break;
-        case 'B': fd->pffi_type = &ffi_type_uchar; break;
-        case 'c': fd->pffi_type = &ffi_type_schar; break;
-        case 'd': fd->pffi_type = &ffi_type_double; break;
+    /* Fixed-width integers */
+
+/*[python input]
+for nbytes in 8, 16, 32, 64:
+    for sgn in 'i', 'u':
+        is_signed = sgn == 'i'
+        u = 'u' if sgn == 'u' else 's'
+        parts = [
+            f"0",
+            f'&ffi_type_{u}int{nbytes}',
+            f'{sgn}{nbytes}_set',
+            f'{sgn}{nbytes}_get',
+            f'{sgn}{nbytes}_set_sw',
+            f'{sgn}{nbytes}_get_sw',
+        ]
+        print(f'    formattable.fmt_{sgn}{nbytes} = (struct fielddesc){{')
+        print(f'            {', '.join(parts)} }};')
+[python start generated code]*/
+    formattable.fmt_i8 = (struct fielddesc){
+            0, &ffi_type_sint8, i8_set, i8_get, i8_set_sw, i8_get_sw };
+    formattable.fmt_u8 = (struct fielddesc){
+            0, &ffi_type_uint8, u8_set, u8_get, u8_set_sw, u8_get_sw };
+    formattable.fmt_i16 = (struct fielddesc){
+            0, &ffi_type_sint16, i16_set, i16_get, i16_set_sw, i16_get_sw };
+    formattable.fmt_u16 = (struct fielddesc){
+            0, &ffi_type_uint16, u16_set, u16_get, u16_set_sw, u16_get_sw };
+    formattable.fmt_i32 = (struct fielddesc){
+            0, &ffi_type_sint32, i32_set, i32_get, i32_set_sw, i32_get_sw };
+    formattable.fmt_u32 = (struct fielddesc){
+            0, &ffi_type_uint32, u32_set, u32_get, u32_set_sw, u32_get_sw };
+    formattable.fmt_i64 = (struct fielddesc){
+            0, &ffi_type_sint64, i64_set, i64_get, i64_set_sw, i64_get_sw };
+    formattable.fmt_u64 = (struct fielddesc){
+            0, &ffi_type_uint64, u64_set, u64_get, u64_set_sw, u64_get_sw };
+/*[python end generated code: output=16806fe0ca3a9c4c input=850b8dd6388b1b10]*/
+
+
+    /* Native C integers.
+     * These use getters/setters for fixed-width ints but have their own
+     * `code` and `pffi_type`.
+     */
+
+/*[python input]
+for base_code, base_c_type in [
+    ('b', 'char'),
+    ('h', 'short'),
+    ('i', 'int'),
+    ('l', 'long'),
+    ('q', 'long long'),
+]:
+    for code, c_type, ffi_type in [
+        (base_code, 'signed ' + base_c_type, 's' + base_c_type),
+        (base_code.upper(), 'unsigned ' + base_c_type, 'u' + base_c_type),
+    ]:
+        print(f'    formattable.fmt_{code} = *FIXINT_FIELDDESC_FOR({c_type});')
+        print(f"    formattable.fmt_{code}.code = '{code}';")
+        if base_code == 'q':
+            # ffi doesn't have `long long`; keep use the fixint type
+            pass
+        else:
+            print(f'    formattable.fmt_{code}.pffi_type = &ffi_type_{ffi_type};')
+[python start generated code]*/
+    formattable.fmt_b = *FIXINT_FIELDDESC_FOR(signed char);
+    formattable.fmt_b.code = 'b';
+    formattable.fmt_b.pffi_type = &ffi_type_schar;
+    formattable.fmt_B = *FIXINT_FIELDDESC_FOR(unsigned char);
+    formattable.fmt_B.code = 'B';
+    formattable.fmt_B.pffi_type = &ffi_type_uchar;
+    formattable.fmt_h = *FIXINT_FIELDDESC_FOR(signed short);
+    formattable.fmt_h.code = 'h';
+    formattable.fmt_h.pffi_type = &ffi_type_sshort;
+    formattable.fmt_H = *FIXINT_FIELDDESC_FOR(unsigned short);
+    formattable.fmt_H.code = 'H';
+    formattable.fmt_H.pffi_type = &ffi_type_ushort;
+    formattable.fmt_i = *FIXINT_FIELDDESC_FOR(signed int);
+    formattable.fmt_i.code = 'i';
+    formattable.fmt_i.pffi_type = &ffi_type_sint;
+    formattable.fmt_I = *FIXINT_FIELDDESC_FOR(unsigned int);
+    formattable.fmt_I.code = 'I';
+    formattable.fmt_I.pffi_type = &ffi_type_uint;
+    formattable.fmt_l = *FIXINT_FIELDDESC_FOR(signed long);
+    formattable.fmt_l.code = 'l';
+    formattable.fmt_l.pffi_type = &ffi_type_slong;
+    formattable.fmt_L = *FIXINT_FIELDDESC_FOR(unsigned long);
+    formattable.fmt_L.code = 'L';
+    formattable.fmt_L.pffi_type = &ffi_type_ulong;
+    formattable.fmt_q = *FIXINT_FIELDDESC_FOR(signed long long);
+    formattable.fmt_q.code = 'q';
+    formattable.fmt_Q = *FIXINT_FIELDDESC_FOR(unsigned long long);
+    formattable.fmt_Q.code = 'Q';
+/*[python end generated code: output=873c87a2e6b5075a input=ee814ca263aac18e]*/
+
+
+    /* Other types have bespoke setters and getters named `@_set` and `@_get`,
+     * where `@` is the type code.
+     * Some have swapped variants, `@_set_sw` and `@_get_sw`
+     */
+
+#define _TABLE_ENTRY(SYMBOL, FFI_TYPE, ...)                                   \
+    formattable.fmt_ ## SYMBOL =                                              \
+        (struct fielddesc){(#SYMBOL)[0], (FFI_TYPE), __VA_ARGS__};            \
+    ///////////////////////////////////////////////////////////////////////////
+
+#define TABLE_ENTRY(SYMBOL, FFI_TYPE)                                         \
+    _TABLE_ENTRY(SYMBOL, FFI_TYPE, SYMBOL ## _set, SYMBOL ## _get)            \
+    ///////////////////////////////////////////////////////////////////////////
+
+#define TABLE_ENTRY_SW(SYMBOL, FFI_TYPE)                                      \
+    _TABLE_ENTRY(SYMBOL, FFI_TYPE, SYMBOL ## _set,                            \
+        SYMBOL ## _get, SYMBOL ## _set_sw, SYMBOL ## _get_sw)                 \
+    ///////////////////////////////////////////////////////////////////////////
+
+    TABLE_ENTRY_SW(d, &ffi_type_double);
 #if defined(Py_HAVE_C_COMPLEX) && defined(Py_FFI_SUPPORT_C_COMPLEX)
-        case 'C': fd->pffi_type = &ffi_type_complex_double; break;
-        case 'E': fd->pffi_type = &ffi_type_complex_float; break;
-        case 'F': fd->pffi_type = &ffi_type_complex_longdouble; break;
+    TABLE_ENTRY(C, &ffi_type_complex_double);
+    TABLE_ENTRY(E, &ffi_type_complex_float);
+    TABLE_ENTRY(F, &ffi_type_complex_longdouble);
 #endif
-        case 'g': fd->pffi_type = &ffi_type_longdouble; break;
-        case 'f': fd->pffi_type = &ffi_type_float; break;
-        case 'h': fd->pffi_type = &ffi_type_sshort; break;
-        case 'H': fd->pffi_type = &ffi_type_ushort; break;
-        case 'i': fd->pffi_type = &ffi_type_sint; break;
-        case 'I': fd->pffi_type = &ffi_type_uint; break;
-        /* XXX Hm, sizeof(int) == sizeof(long) doesn't hold on every platform */
-        /* As soon as we can get rid of the type codes, this is no longer a problem */
-    #if SIZEOF_LONG == 4
-        case 'l': fd->pffi_type = &ffi_type_sint32; break;
-        case 'L': fd->pffi_type = &ffi_type_uint32; break;
-    #elif SIZEOF_LONG == 8
-        case 'l': fd->pffi_type = &ffi_type_sint64; break;
-        case 'L': fd->pffi_type = &ffi_type_uint64; break;
-    #else
-        #error
-    #endif
-    #if SIZEOF_LONG_LONG == 8
-        case 'q': fd->pffi_type = &ffi_type_sint64; break;
-        case 'Q': fd->pffi_type = &ffi_type_uint64; break;
-    #else
-        #error
-    #endif
-        case 'P': fd->pffi_type = &ffi_type_pointer; break;
-        case 'z': fd->pffi_type = &ffi_type_pointer; break;
-        case 'u':
-            if (sizeof(wchar_t) == sizeof(short))
-                fd->pffi_type = &ffi_type_sshort;
-            else if (sizeof(wchar_t) == sizeof(int))
-                fd->pffi_type = &ffi_type_sint;
-            else if (sizeof(wchar_t) == sizeof(long))
-                fd->pffi_type = &ffi_type_slong;
-            else
-                Py_UNREACHABLE();
-            break;
-        case 'U': fd->pffi_type = &ffi_type_pointer; break;
-        case 'Z': fd->pffi_type = &ffi_type_pointer; break;
-    #ifdef MS_WIN32
-        case 'X': fd->pffi_type = &ffi_type_pointer; break;
-    #endif
-        case 'v': fd->pffi_type = &ffi_type_sshort; break;
-    #if SIZEOF__BOOL == 1
-        case '?': fd->pffi_type = &ffi_type_uchar; break; /* Also fallback for no native _Bool support */
-    #elif SIZEOF__BOOL == SIZEOF_SHORT
-        case '?': fd->pffi_type = &ffi_type_ushort; break;
-    #elif SIZEOF__BOOL == SIZEOF_INT
-        case '?': fd->pffi_type = &ffi_type_uint; break;
-    #elif SIZEOF__BOOL == SIZEOF_LONG
-        case '?': fd->pffi_type = &ffi_type_ulong; break;
-    #elif SIZEOF__BOOL == SIZEOF_LONG_LONG
-        case '?': fd->pffi_type = &ffi_type_ulong; break;
-    #endif /* SIZEOF__BOOL */
-        case 'O': fd->pffi_type = &ffi_type_pointer; break;
-        default:
-            Py_UNREACHABLE();
-        }
-    }
+    TABLE_ENTRY(g, &ffi_type_longdouble);
+    TABLE_ENTRY_SW(f, &ffi_type_float);
+    TABLE_ENTRY(v, &ffi_type_sshort);    /* vBOOL */
+
+    // ctypes.c_char is signed for FFI, even where C wchar_t is unsigned.
+    TABLE_ENTRY(c, _ctypes_fixint_fielddesc(sizeof(char), true)->pffi_type);
+    // ctypes.c_wchar is signed for FFI, even where C wchar_t is unsigned.
+    TABLE_ENTRY(u, _ctypes_fixint_fielddesc(sizeof(wchar_t), true)->pffi_type);
+
+    TABLE_ENTRY(s, &ffi_type_pointer);
+    TABLE_ENTRY(P, &ffi_type_pointer);
+    TABLE_ENTRY(z, &ffi_type_pointer);
+    TABLE_ENTRY(U, &ffi_type_pointer);
+    TABLE_ENTRY(Z, &ffi_type_pointer);
+#ifdef MS_WIN32
+    TABLE_ENTRY(X, &ffi_type_pointer);
+#endif
+    TABLE_ENTRY(O, &ffi_type_pointer);
+
+#undef TABLE_ENTRY_SW
+#undef TABLE_ENTRY
+#undef _TABLE_ENTRY
 
+    /* bool has code '?', fill it in manually */
+
+    // ctypes.c_bool is unsigned for FFI, even where C bool is signed.
+    formattable.fmt_bool = *_ctypes_fixint_fielddesc(sizeof(bool), false);
+    formattable.fmt_bool.code = '?';
+    formattable.fmt_bool.setfunc = bool_set;
+    formattable.fmt_bool.getfunc = bool_get;
 }
+#undef FIXINT_FIELDDESC_FOR
+_Py_COMP_DIAG_POP
 
 struct fielddesc *
 _ctypes_get_fielddesc(const char *fmt)
 {
-    static int initialized = 0;
-    struct fielddesc *table = formattable;
-
+    static bool initialized = false;
+    static PyMutex mutex = {0};
+    PyMutex_Lock(&mutex);
     if (!initialized) {
-        initialized = 1;
         _ctypes_init_fielddesc();
+        initialized = true;
     }
-
-    for (; table->code; ++table) {
-        if (table->code == fmt[0])
-            return table;
+    PyMutex_Unlock(&mutex);
+    struct fielddesc *result = NULL;
+    switch(fmt[0]) {
+/*[python input]
+for code in 'sbBcdCEFgfhHiIlLqQPzuUZXvO':
+    print(f"        case '{code}': result = &formattable.fmt_{code}; break;")
+[python start generated code]*/
+        case 's': result = &formattable.fmt_s; break;
+        case 'b': result = &formattable.fmt_b; break;
+        case 'B': result = &formattable.fmt_B; break;
+        case 'c': result = &formattable.fmt_c; break;
+        case 'd': result = &formattable.fmt_d; break;
+        case 'C': result = &formattable.fmt_C; break;
+        case 'E': result = &formattable.fmt_E; break;
+        case 'F': result = &formattable.fmt_F; break;
+        case 'g': result = &formattable.fmt_g; break;
+        case 'f': result = &formattable.fmt_f; break;
+        case 'h': result = &formattable.fmt_h; break;
+        case 'H': result = &formattable.fmt_H; break;
+        case 'i': result = &formattable.fmt_i; break;
+        case 'I': result = &formattable.fmt_I; break;
+        case 'l': result = &formattable.fmt_l; break;
+        case 'L': result = &formattable.fmt_L; break;
+        case 'q': result = &formattable.fmt_q; break;
+        case 'Q': result = &formattable.fmt_Q; break;
+        case 'P': result = &formattable.fmt_P; break;
+        case 'z': result = &formattable.fmt_z; break;
+        case 'u': result = &formattable.fmt_u; break;
+        case 'U': result = &formattable.fmt_U; break;
+        case 'Z': result = &formattable.fmt_Z; break;
+        case 'X': result = &formattable.fmt_X; break;
+        case 'v': result = &formattable.fmt_v; break;
+        case 'O': result = &formattable.fmt_O; break;
+/*[python end generated code: output=81a8223dda9f81f7 input=2f59666d3c024edf]*/
+        case '?': result = &formattable.fmt_bool; break;
     }
-    return NULL;
+    if (!result || !result->code) {
+        return NULL;
+    }
+    assert(result->pffi_type);
+    assert(result->setfunc);
+    assert(result->getfunc);
+    return result;
 }
 
+/*
+  Ideas: Implement VARIANT in this table, using 'V' code.
+*/
+
 /*---------------- EOF ----------------*/
index 7e0804054cded40a03349d623ead118e856f41a5..45e00a538fb5a53a6619c14d6b544c2e37a0baa3 100644 (file)
@@ -113,8 +113,17 @@ extern PyType_Spec cthunk_spec;
 
 typedef struct tagPyCArgObject PyCArgObject;
 typedef struct tagCDataObject CDataObject;
-typedef PyObject *(* GETFUNC)(void *, Py_ssize_t size);
-typedef PyObject *(* SETFUNC)(void *, PyObject *value, Py_ssize_t size);
+
+// GETFUNC: convert the C value at *ptr* to Python object, return the object
+// SETFUNC: write content of the PyObject *value* to the location at *ptr*;
+//   return a new reference to either *value*, or None for simple types
+//   (see _CTYPES_DEBUG_KEEP).
+// Note that the *size* arg can have different meanings depending on context:
+//     for string-like arrays it's the size in bytes
+//     for int-style fields it's either the type size, or bitfiled info
+//         that can be unpacked using the LOW_BIT & NUM_BITS macros.
+typedef PyObject *(* GETFUNC)(void *ptr, Py_ssize_t size);
+typedef PyObject *(* SETFUNC)(void *ptr, PyObject *value, Py_ssize_t size);
 typedef PyCArgObject *(* PARAMFUNC)(ctypes_state *st, CDataObject *obj);
 
 /* A default buffer in CDataObject, which can be used for small C types.  If
@@ -239,9 +248,9 @@ extern CThunkObject *_ctypes_alloc_callback(ctypes_state *st,
 /* a table entry describing a predefined ctypes type */
 struct fielddesc {
     char code;
+    ffi_type *pffi_type; /* always statically allocated */
     SETFUNC setfunc;
     GETFUNC getfunc;
-    ffi_type *pffi_type; /* always statically allocated */
     SETFUNC setfunc_swapped;
     GETFUNC getfunc_swapped;
 };