Skip to content
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

Non-POD arguments passed incorrectly on ARM64 #106471

Open
azeno opened this issue Aug 15, 2024 · 3 comments
Open

Non-POD arguments passed incorrectly on ARM64 #106471

azeno opened this issue Aug 15, 2024 · 3 comments
Labels
area-Interop-coreclr documentation Documentation bug or enhancement, does not impact product or test code untriaged New issue has not been triaged by the area owner

Comments

@azeno
Copy link

azeno commented Aug 15, 2024

Description

While trying to run ImGui.NET on win-arm64 I ran into some sort of argument passing issue from managed to native code when calling https://github.com/cimgui/cimgui/blob/35a4e8f8932c6395156ffacee288b9c30e50cb63/cimgui.cpp#L207 via the managed wrapper https://github.com/ImGuiNET/ImGui.NET/blob/70a87022f775025b90dbe2194e44983c79de0911/src/ImGui.NET/Generated/ImGui.gen.cs#L21374

I managed to reproduce it with following stripped down version. Note that this code runs fine in win-x64. It also runs fine in win-arm64 when removing the default constructor from the Vector2 struct. This makes me wonder whether it's even a dotnet issue, therefor feel free to point me to the appropriate channels if it isn't.

Reproduction Steps

C#

using System.Runtime.InteropServices;

var result = NativeMethods.MyFunction(default, default);
Console.WriteLine($"{nameof(NativeMethods.MyFunction)} returned {result}");

struct Vector2 { float x, y; }

static class NativeMethods
{
    [DllImport("PInvoke.dll", CallingConvention = CallingConvention.Cdecl)]
    public static extern int MyFunction(Vector2 vector, int value);
}

C++

struct Vector2
{
    float x, y;
    // Uncomment the following line and MyFunction returns the correct result
    Vector2() : x(0.0f), y(0.0f) { }
};

extern "C" __declspec(dllexport) int MyFunction(Vector2 vector, int value)
{
    return value;
}

Attached is a little VS solution containing the above code
PInvoke.zip

Expected behavior

MyFunction should receive 0 as value.

Actual behavior

MyFunction receives some random number as value.

Regression?

No response

Known Workarounds

No response

Configuration

.net8, Windows 11, ARM64

Other information

I've also tried building the project with clang but got the same result.

@dotnet-issue-labeler dotnet-issue-labeler bot added the needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners label Aug 15, 2024
@dotnet-policy-service dotnet-policy-service bot added the untriaged New issue has not been triaged by the area owner label Aug 15, 2024
@huoyaoyuan huoyaoyuan added area-Interop-coreclr and removed needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners labels Aug 15, 2024
@huoyaoyuan
Copy link
Member

It's caused by ABI difference on ARM64. You can see more details at https://learn.microsoft.com/cpp/build/arm64-windows-abi-conventions

In short, the default constructor in C++ code make it no longer considered HFA on ARM64:

A type is considered to be an HFA or HVA if all of the following hold:

  • It's non-empty,
  • It doesn't have any non-trivial default or copy constructors, destructors, or assignment operators,
  • All of its members have the same HFA or HVA type, or are float, double, or neon types that match the other members' HFA or HVA types.

The argument passing schema for HFA and regular structs are different on ARM64. On x64 there's no such concept and all structs are passed in the same way.

I'm not sure whether .NET supports passing HFAs as a single argument at all.

@azeno
Copy link
Author

azeno commented Aug 16, 2024

Thank you for the explanation. As a workaround it was possible in my case to change the default constructor to Vector2() = default;.

azeno added a commit to vvvv/imgui that referenced this issue Aug 16, 2024
azeno added a commit to vvvv/cimgui that referenced this issue Aug 16, 2024
azeno added a commit to vvvv/ImGui.NET-nativebuild that referenced this issue Aug 16, 2024
azeno added a commit to vvvv/VL.StandardLibs that referenced this issue Aug 16, 2024
This commit also rebuilds the x64 binaries to ensure both use the same source code after applying workaorund from dotnet/runtime#106471
azeno added a commit to vvvv/VL.StandardLibs that referenced this issue Aug 16, 2024
This commit also rebuilds the x64 binaries to ensure both use the same source code after applying workaorund from dotnet/runtime#106471
@jkotas
Copy link
Member

jkotas commented Aug 17, 2024

.NET interop targets C ABI. The structs used for interop must be C structs on the unmanaged side in a portable code. They cannot have C++ constructors, destructors or virtual methods.

The calling convention details for non-POD (Plain Old Data) C++ types are often different from the calling convention details of plain C structs as you have discovered.

This came up a few times before (e.g. in #12312). We should mention this in the docs.

@jkotas jkotas added the documentation Documentation bug or enhancement, does not impact product or test code label Aug 17, 2024
@jkotas jkotas changed the title Argument not passed correctly on ARM64 Non-POD arguments passed incorrectly on ARM64 Aug 17, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-Interop-coreclr documentation Documentation bug or enhancement, does not impact product or test code untriaged New issue has not been triaged by the area owner
Projects
Status: No status
Development

No branches or pull requests

3 participants