Repository navigation
ctypes array cannot be returned by value on ARM64 #110190
Description
Bug report
Bug description:
Dear,
I'm wrapping a C-library with the ctypes module on Darwin (ARM64). I have a function that simply initializes a C-struct and returns the struct by value. The C-struct contains one simple C-array type. When using ArrayTypes in Python, it seems the struct is not returned properly.
On Windows and Linux platforms the same code does work as expected.
The strange thing is that if I lay out the struct explicitly with singular types in Python (as I expect the layout of the struct is the same), it does work as expected on all tried platforms.
To reproduce, I first create a simple .c file with the struct and the initializing function. In Python, I give code that I use to compile (with gcc), and then three different ways to wrap the struct and function in Python using ctypes. I believe the three ways should be equivalent (and they are on Windows and Linux), but not on Darwin (ARM64).
I saved this file as ctypesexample.c
struct example{double coords[3];};
struct example set_defaults(){
struct example ex;
ex.coords[0] = 1.0;
ex.coords[1] = 2.0;
ex.coords[2] = 3.0;
return ex;}I saved this file as reproduce.py
import os
import subprocess
import ctypes
def compile():
"""Compile the c file using gcc."""
subprocess.call(["gcc","-c","-Wall","-Werror","-fpic","ctypesexample.c"])
subprocess.call(["gcc","-shared","-o","libctypesexample","ctypesexample.o"])
assert os.path.isfile('libctypesexample')
# Three ways to wrap the struct:
class Example1(ctypes.Structure):
_fields_ = [
('coords', ctypes.c_double * 3)
]
class CoordType(ctypes.Array):
_type_ = ctypes.c_double
_length_ = 3
class Example2(ctypes.Structure):
_fields_ = [('coords', CoordType)]
class Example3(ctypes.Structure):
_fields_ = [
('x', ctypes.c_double),
('y', ctypes.c_double),
('z', ctypes.c_double)
]
def run():
# Load the shared library
so_location = os.path.abspath("libctypesexample")
_lib = ctypes.cdll.LoadLibrary(so_location)
_lib.set_defaults.restype = Example1
o = _lib.set_defaults()
print(f"Example1: ({o.coords[0]}, {o.coords[1]}, {o.coords[2]})")
_lib.set_defaults.restype = Example2
o = _lib.set_defaults()
print(f"Example2: ({o.coords[0]}, {o.coords[1]}, {o.coords[2]})")
_lib.set_defaults.restype = Example3
o = _lib.set_defaults()
print(f"Example3: ({o.x}, {o.y}, {o.z})")
if __name__ == "__main__":
if not os.path.isfile('libctypesexample'):
compile()
run()I tried this code on Linux (debian) -- both in Docker and WSL. And this works perfectly. Equivalent code in Windows is also working fine. In the past, I have run similar code as above on many different versions of Python (3.5-3.11) on both platforms for years (and still) without problems. For example on WSL with the following platform info
uname_result(system='Linux', release='5.15.90.1-microsoft-standard-WSL2', version='#1 SMP Fri Jan 27 02:56:13 UTC 2023', machine='x86_64')
and Python:
Python 3.10.6 (main, Mar 10 2023, 10:55:28) [GCC 11.3.0] on linux
I get this correct output:
Example1: (1.0, 2.0, 3.0)
Example2: (1.0, 2.0, 3.0)
Example3: (1.0, 2.0, 3.0)
While on Darwin (ARM) with the following platform info:
system='Darwin', release='22.1.0', version='Darwin Kernel Version 22.1.0: Sun Oct 9 20:14:30 PDT 2022; root:xnu-8792.41.9~2/RELEASE_ARM64_T8103', machine='arm64')
and Python
Python 3.11.4 (main, Jun 12 2023, 11:39:45) [Clang 14.0.3 (clang-1403.0.22.14.1)] on darwin
I get the following output:
Example1: (3.025693809e-314, 3.0256938013e-314, 2.1481083817e-314)
Example2: (3.025693809e-314, 3.0256938013e-314, 2.1481083817e-314)
Example3: (1.0, 2.0, 3.0)
In summary, the struct doesn't seem to be properly returned as value using array types, though with a literal listing of the types, it seems there is no problem. So there is a workaround, but as we're using ctypes for automatic wrapping through reflection and the code works properly on Windows and Linux, it would be quite some work to implement the workaround.
I hope my bug report finds you without any errors, and many thanks for giving the opportunity to report.
Best regards
CPython versions tested on:
3.10, 3.11
Operating systems tested on:
Linux, macOS, Windows
Linked PRs
- gh-110190: Fix ctypes structs with array on Arm #112604
- [3.11] gh-110190: Fix ctypes structs with array on Arm (#112604) #112766
- [3.12] gh-110190: Fix ctypes structs with array on Arm (#112604) #112767
- gh-110190: Temporarily skip new test introduced in gh-112604 on PPC64LE #112818
- [3.12] gh-110190: Temporarily skip new test introduced in gh-112604 on PPC64LE (GH-112818) #112829
- [3.11] gh-110190: Temporarily skip new test introduced in gh-112604 on PPC64LE (GH-112818) #112830
- gh-110190: Fix ctypes structs with array on PPCLE64 #112959
- [3.11] gh-110190: Fix ctypes structs with array on PPCLE64 (GH-112959) #113167
- [3.12] gh-110190: Fix ctypes structs with array on PPCLE64 (GH-112959) #113170
- gh-110190: Fix ctypes structs with array on Windows ARM64 #114753
- [3.11] gh-110190: Fix ctypes structs with array on Windows ARM64 (GH-114753) #114774
- [3.12] gh-110190: Fix ctypes structs with array on Windows ARM64 (GH-114753) #114775
- gh-110190: Fix ctypes structs with array on SPARC #118233
Metadata
Metadata
Assignees
Labels
Projects
- StatusShow more project fieldsDone
Activity
sunmy2019 commentedon Oct 2, 2023
I looked into it.
ctypesutilizeslibffito make foreign function calls, andctypesgenerates wrong signatures.Long words in short:
stgdict->ffi_type_pointeris wrong. Current behavior would cause return types interpreted asctypes.c_void_p * 3.Notice the binary representation of Example1 has
ffffcc6b11d0which is same as Example2 and Example3.sunmy2019 commentedon Oct 2, 2023
The special handling is here:
cpython/Modules/_ctypes/stgdict.c
Lines 700 to 710 in 21a6263
You seem to hit one special HVA calling convention of aarch64 which is currently not handled.
https://learn.microsoft.com/en-us/cpp/build/arm64-windows-abi-conventions?view=msvc-170#return-values
Details
Integral values are returned in x0.
Floating-point values are returned in s0, d0, or v0, as appropriate.
A type is considered to be an HFA or HVA if all of the following hold:
HVA values with four or fewer elements are returned in s0-s3, d0-d3, or v0-v3, as appropriate.
Types returned by value are handled differently depending on whether they have certain properties, and whether the function is a non-static member function. Types which have all of these properties,
and are returned by non-member functions or static member functions, use the following return style:
All other types use this convention:
sunmy2019 commentedon Oct 2, 2023
Now the cause is clear,
ctypesdid not handle one special rule of aarch64, but fixing it is beyond my ability.diegorusso commentedon Nov 29, 2023
Hello, I'm looking at this issue and working on a fix.
pythongh-110190: Fix ctypes structs with array on Arm
diegorusso commentedon Dec 1, 2023
Hello, I've investigated this further and actually this was happening across Arm platforms including Linux aarch64 and aarch32 and not just on Darwin. I've raised the PR that fixes the issue providing information and context. Feel free to review it and any feedback is welcome.
gh-110190: Fix ctypes structs with array on Arm (#112604)
[3.11] pythongh-110190: Fix ctypes structs with array on Arm (python#…
[3.12] pythongh-110190: Fix ctypes structs with array on Arm (python#…
[3.12] gh-110190: Fix ctypes structs with array on Arm (#112604) (#11…
[3.11] gh-110190: Fix ctypes structs with array on Arm (#112604) (#11…
ambv commentedon Dec 6, 2023
This is now fixed in (to be released) 3.11.8, 3.12.1, and 3.13.0 alpha 3.
Thanks, Diego! ✨ 🍰 ✨
60 remaining items
[3.11] pythongh-110190: Temporarily skip new test introduced in pytho…
[3.11] pythongh-110190: Fix ctypes structs with array on PPCLE64 (pyt…
pythongh-110190: Fix ctypes structs with array on Windows ARM64 (pyth…
pythongh-110190: Fix ctypes structs with array on SPARC (pythonGH-118233
[3.11] pythongh-110190: Fix ctypes structs with array on Arm (python#…
[3.11] pythongh-110190: Temporarily skip new test introduced in pytho…
[3.11] pythongh-110190: Fix ctypes structs with array on PPCLE64 (pyt…
pythongh-110190: Fix ctypes structs with array on Windows ARM64 (pyth…
pythongh-110190: Fix ctypes structs with array on SPARC (pythonGH-118233
pythongh-110190: Fix ctypes structs with array on SPARC (pythonGH-118233
[3.11] pythongh-110190: Fix ctypes structs with array on Arm (python#…
[3.11] pythongh-110190: Temporarily skip new test introduced in pytho…
[3.11] pythongh-110190: Fix ctypes structs with array on PPCLE64 (pyt…
pythongh-110190: Fix ctypes structs with array on Windows ARM64 (pyth…
pythongh-110190: Fix ctypes structs with array on SPARC (pythonGH-118233