Skip to content

Commit

Permalink
gh-127295: ctypes: Switch field accessors to fixed-width integers (GH…
Browse files Browse the repository at this point in the history
…-127297)

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 <[email protected]>
  • Loading branch information
encukou and picnixz authored Dec 20, 2024
1 parent ba45e5c commit 78ffba4
Show file tree
Hide file tree
Showing 4 changed files with 501 additions and 652 deletions.
4 changes: 2 additions & 2 deletions Modules/_ctypes/_ctypes.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand Down
2 changes: 1 addition & 1 deletion Modules/_ctypes/callbacks.c
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand Down
Loading

0 comments on commit 78ffba4

Please sign in to comment.