New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ctypes array cannot be returned by value on ARM64 #110190
Comments
I looked into it. Long words in short: class Example1(ctypes.Structure):
_fields_ = [("coords", ctypes.c_double * 3)]
class Example2(ctypes.Structure):
_fields_ = [("coords", ctypes.c_void_p * 3)]
# Three ways to wrap the struct:
class Example3(ctypes.Structure):
_fields_ = [("x", ctypes.c_void_p), ("y", ctypes.c_void_p), ("z", ctypes.c_void_p)]
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: ({float.hex(o.coords[0])}, {float.hex(o.coords[1])}, {float.hex(o.coords[2])})"
)
_lib.set_defaults.restype = Example2
o = _lib.set_defaults()
print(f"Example2: ({o.coords[0]:x}, {o.coords[1]:x}, {o.coords[2]})")
_lib.set_defaults.restype = Example3
o = _lib.set_defaults()
print(f"Example3: ({o.x:x}, {o.y:x}, {o.z})")
Notice the binary representation of Example1 has |
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.
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:
|
Now the cause is clear, |
Hello, I'm looking at this issue and working on a fix. |
Set MAX_STRUCT_SIZE to 32 in stgdict.c when on Arm platforms. This because on Arm platforms structs with at most 4 elements of any floating point type values can be passed through registers. If the type is double the maximum size of the struct is 32 bytes. On x86-64 Linux, it's maximum 16 bytes hence we need to differentiate.
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. |
Set MAX_STRUCT_SIZE to 32 in stgdict.c when on Arm platforms. This because on Arm platforms structs with at most 4 elements of any floating point type values can be passed through registers. If the type is double the maximum size of the struct is 32 bytes. On x86-64 Linux, it's maximum 16 bytes hence we need to differentiate.
…112604) Set MAX_STRUCT_SIZE to 32 in stgdict.c when on Arm platforms. This because on Arm platforms structs with at most 4 elements of any floating point type values can be passed through registers. If the type is double the maximum size of the struct is 32 bytes. On x86-64 Linux, it's maximum 16 bytes hence we need to differentiate. (cherry picked from commit bc68f4a)
…112604) Set MAX_STRUCT_SIZE to 32 in stgdict.c when on Arm platforms. This because on Arm platforms structs with at most 4 elements of any floating point type values can be passed through registers. If the type is double the maximum size of the struct is 32 bytes. On x86-64 Linux, it's maximum 16 bytes hence we need to differentiate. (cherry picked from commit bc68f4a)
…2767) Set MAX_STRUCT_SIZE to 32 in stgdict.c when on Arm platforms. This because on Arm platforms structs with at most 4 elements of any floating point type values can be passed through registers. If the type is double the maximum size of the struct is 32 bytes. On x86-64 Linux, it's maximum 16 bytes hence we need to differentiate. (cherry picked from commit bc68f4a)
…2766) Set MAX_STRUCT_SIZE to 32 in stgdict.c when on Arm platforms. This because on Arm platforms structs with at most 4 elements of any floating point type values can be passed through registers. If the type is double the maximum size of the struct is 32 bytes. On x86-64 Linux, it's maximum 16 bytes hence we need to differentiate. (cherry picked from commit bc68f4a)
This is now fixed in (to be released) 3.11.8, 3.12.1, and 3.13.0 alpha 3. |
While this is fixed, it uncovered that a similar failure is happening on PPC64LE. The new tests introduced by Diego here fail on our PPC64LE buildbots as well. See for instance: |
on PPC64LE (python#112818) (cherry picked from commit 9f67042)
…ngh-112604 on PPC64LE (pythonGH-112818) (cherry picked from commit 9f67042) Co-authored-by: Łukasz Langa <lukasz@langa.pl>
…ngh-112604 on PPC64LE (pythonGH-112818) (cherry picked from commit 9f67042) Co-authored-by: Łukasz Langa <lukasz@langa.pl>
I'm seeing the same test fail on Windows ARM64, though not quite in the same place:
(I added My build was using MSVC 19.37, which is known to have at least one ARM64 regression, so this may be another one. I haven't yet figured out how to confirm, so leaving some details here in case someone else knows before I get back to it. |
@zooba thanks for reporting it. I'll try to have a look at this next week. |
@zooba I have the fix, PR is coming. |
Fix the same issue of PR python#112959 on Windows ARM64 platform when using MSVC compiler. MSVC predifined platform preprocessor macro is _M_ARM64.
Fix the same issue of PR python#112959 on Windows ARM64 platform when using MSVC compiler. MSVC predefined platform preprocessor macro is _M_ARM64.
…onGH-114753) (cherry picked from commit a06b606) Co-authored-by: Diego Russo <diego.russo@arm.com>
…onGH-114753) (cherry picked from commit a06b606) Co-authored-by: Diego Russo <diego.russo@arm.com>
Thanks for the quick fix for Windows! Sorry for hijacking the issue - I'll leave it open for the macOS issue. |
The macOS issue has been fixed since the first PR but then the tests refactoring exposed the bug on different platforms. I mean we can keep this issue open until we cover all different platforms but I guess at some point we need to draw a line? |
Let's just label it "ARM64" instead of Darwin, and if the only remaining issue is SPARC then we can probably close until someone has a machine handy to repro and test. I'll leave open for now in case someone already does, but if there's nothing heard, it can be closed. |
Set MAX_STRUCT_SIZE to 32 in stgdict.c when on Arm platforms. This because on Arm platforms structs with at most 4 elements of any floating point type values can be passed through registers. If the type is double the maximum size of the struct is 32 bytes. On x86-64 Linux, it's maximum 16 bytes hence we need to differentiate.
…12959) Fix the same issue of PR python#112604 on PPC64LE platform Refactor tests to make easier to add more platfroms if needed.
Hi. I found some time to dig into this more and currently I suspect gcc to be the culprit - both Python and libffi seem to do what is expected. But it's definitely too soon to say for sure - I have asked those more knowledgeable internally to confirm that, so we'll see. (It seems that gcc doesn't conform to ABI when a struct with small float/double arrays is returned from a function - when I change the test structs from one double array to several doubles, it works as expected). I hope to find a fix soon, but I understand if you want to close this (although this seems to be a general SPARC issue, so Linux SPARC is likely affected as well). If so, can we perhaps skip those tests on SPARC? |
@kulikjak thanks for the update! |
Is there a link to the GCC bug? Thanks. |
Sure, here: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=114416 |
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
I saved this file as
reproduce.py
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:
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:
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
The text was updated successfully, but these errors were encountered: