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

NativeAOT incorrect native code when assigning unmanaged function pointer to struct member #107396

Closed
rolfbjarne opened this issue Sep 5, 2024 · 14 comments · Fixed by #107491
Closed
Assignees
Labels
area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI in-pr There is an active PR which will close this issue when it is merged regression-from-last-release
Milestone

Comments

@rolfbjarne
Copy link
Member

rolfbjarne commented Sep 5, 2024

Description

NativeAOT seems to generate incorrect native code when assigning unmanaged function pointer to struct member

Reproduction Steps

Compile & run:

using System.Runtime.InteropServices;

unsafe class Program {
	CMBlockBufferCustomBlockSource Cblock;
	public Program ()
	{
		Cblock = default (CMBlockBufferCustomBlockSource);
		delegate* unmanaged<void> allocate = &AllocateCallback;
		delegate* unmanaged<void> free = &FreeCallback;

		Cblock.Allocate = &AllocateCallback;
		Cblock.Free = &FreeCallback;

		delegate* unmanaged<void> allocateInStruct = Cblock.Allocate;
		delegate* unmanaged<void> freeInStruct = Cblock.Free;

		var eq = ((IntPtr) (void *) freeInStruct).ToString ("x") == ((IntPtr) (void *) free).ToString ("x");
		Compare ("Cblock.Allocate", allocateInStruct, allocate);
		Compare ("Cblock.Free", freeInStruct, free);

		Cblock.Allocate = allocate;
		Cblock.Free = free;

		allocateInStruct = Cblock.Allocate;
		freeInStruct = Cblock.Free;

		Compare ("Cblock.Allocate", allocateInStruct, allocate);
		Compare ("Cblock.Free", freeInStruct, free);
	}

	static void Compare (string msg, void* a, void* b)
	{
			var eq = a == b;
			Console.WriteLine ($"{msg}: 0x{((IntPtr) a).ToString ("x")} vs 0x{((IntPtr) b).ToString ("x")} {(eq ? "Equal ✅" : "Not equal ❌")}");
	}

	static void Main ()
	{
		new Program ();
	}

	[UnmanagedCallersOnly]
	static void AllocateCallback ()
	{
	}

	[UnmanagedCallersOnly]
	static void FreeCallback ()
	{
	}
}

[StructLayout (LayoutKind.Sequential, Pack = 4)]
internal struct CMBlockBufferCustomBlockSource {
	public unsafe delegate* unmanaged<void> Allocate;
	public unsafe delegate* unmanaged<void> Free;
}

csproj

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net9.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <PublishAot>true</PublishAot>
    <_IsPublishing>true</_IsPublishing>
    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
  </PropertyGroup>
</Project>

Expected behavior

$ dotnet run
Cblock.Allocate: 0x3011029d0 vs 0x3011029d0 Equal ✅
Cblock.Free: 0x3011029e8 vs 0x3011029e8 Equal ✅
Cblock.Allocate: 0x3011029d0 vs 0x3011029d0 Equal ✅
Cblock.Free: 0x3011029e8 vs 0x3011029e8 Equal ✅

Actual behavior

$ dotnet publish /p:PublishAot=true && ./bin/Release/net9.0/osx-arm64/publish/repro
Cblock.Allocate: 0x421280 vs 0x104e7c2c0 Not equal ❌
Cblock.Free: 0x421298 vs 0x104e7c2e0 Not equal ❌
Cblock.Allocate: 0x104e7c2c0 vs 0x104e7c2c0 Equal ✅
Cblock.Free: 0x104e7c2e0 vs 0x104e7c2e0 Equal ✅

Regression?

I'm not sure; this showed up in a new test failure, but unrelated things might have hidden it.

While creating a smaller test case, there were numerous times I'd change something seemingly unrelevant (such as remove a Console.WriteLine), and the problem would go away.

Known Workarounds

Assigning the function pointer to an intermediate variable seems to work.

Configuration

$ donut --info
.NET SDK:
 Version:           9.0.100-rc.2.24420.1
 Commit:            e2b7b9d2b4
 Workload version:  9.0.100-manifests.3edc8368
 MSBuild version:   17.12.0-preview-24415-04+f422d8d7d

Runtime Environment:
 OS Name:     Mac OS X
 OS Version:  14.6
 OS Platform: Darwin
 RID:         osx-arm64
 Base Path:   /Users/rolf/work/maccore/net9.0/xamarin-macios/builds/downloads/dotnet-sdk-9.0.100-rc.2.24420.1/sdk/9.0.100-rc.2.24420.1/

.NET workloads installed:
Configured to use loose manifests when installing new manifests.
 [macos]
   Installation Source: SDK 9.0.100-rc.2
   Manifest Version:    14.5.9308-ci.dev-rolf-bump-main-in-net9-0-2024-09-04/9.0.100-rc.2
   Manifest Path:       /Users/rolf/work/maccore/net9.0/xamarin-macios/builds/downloads/dotnet-sdk-9.0.100-rc.2.24420.1/sdk-manifests/9.0.100-rc.2/microsoft.net.sdk.macos/WorkloadManifest.json
   Install Type:        FileBased

 [ios]
   Installation Source: SDK 9.0.100-rc.2
   Manifest Version:    17.5.9308-ci.dev-rolf-bump-main-in-net9-0-2024-09-04/9.0.100-rc.2
   Manifest Path:       /Users/rolf/work/maccore/net9.0/xamarin-macios/builds/downloads/dotnet-sdk-9.0.100-rc.2.24420.1/sdk-manifests/9.0.100-rc.2/microsoft.net.sdk.ios/WorkloadManifest.json
   Install Type:        FileBased

 [maccatalyst]
   Installation Source: SDK 9.0.100-rc.2
   Manifest Version:    17.5.9308-ci.dev-rolf-bump-main-in-net9-0-2024-09-04/9.0.100-rc.2
   Manifest Path:       /Users/rolf/work/maccore/net9.0/xamarin-macios/builds/downloads/dotnet-sdk-9.0.100-rc.2.24420.1/sdk-manifests/9.0.100-rc.2/microsoft.net.sdk.maccatalyst/WorkloadManifest.json
   Install Type:        FileBased

 [tvos]
   Installation Source: SDK 9.0.100-rc.2
   Manifest Version:    17.5.9308-ci.dev-rolf-bump-main-in-net9-0-2024-09-04/9.0.100-rc.2
   Manifest Path:       /Users/rolf/work/maccore/net9.0/xamarin-macios/builds/downloads/dotnet-sdk-9.0.100-rc.2.24420.1/sdk-manifests/9.0.100-rc.2/microsoft.net.sdk.tvos/WorkloadManifest.json
   Install Type:        FileBased


Host:
  Version:      9.0.0-rc.1.24414.5
  Architecture: arm64
  Commit:       static

.NET SDKs installed:
  9.0.100-rc.2.24420.1 [/Users/rolf/work/maccore/net9.0/xamarin-macios/builds/downloads/dotnet-sdk-9.0.100-rc.2.24420.1/sdk]

.NET runtimes installed:
  Microsoft.AspNetCore.App 9.0.0-rc.1.24412.15 [/Users/rolf/work/maccore/net9.0/xamarin-macios/builds/downloads/dotnet-sdk-9.0.100-rc.2.24420.1/shared/Microsoft.AspNetCore.App]
  Microsoft.NETCore.App 9.0.0-rc.1.24414.5 [/Users/rolf/work/maccore/net9.0/xamarin-macios/builds/downloads/dotnet-sdk-9.0.100-rc.2.24420.1/shared/Microsoft.NETCore.App]

Other architectures found:
  None

Environment variables:
  Not set

global.json file:
  /Users/rolf/work/maccore/net9.0/xamarin-macios/global.json

Learn more:
  https://aka.ms/dotnet/info

Download .NET:
  https://aka.ms/dotnet/download

Other information

No response

@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 Sep 5, 2024
@dotnet-policy-service dotnet-policy-service bot added the untriaged New issue has not been triaged by the area owner label Sep 5, 2024
@vcsjones vcsjones added area-NativeAOT-coreclr and removed needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners labels Sep 5, 2024
Copy link
Contributor

Tagging subscribers to this area: @agocke, @MichalStrehovsky, @jkotas
See info in area-owners.md if you want to be subscribed.

@vitek-karas
Copy link
Member

Doesn't repro on win-x64 with Preview 6. So it's either recent regression or arm64 specific.

@vitek-karas
Copy link
Member

Also doesn't repro on win-x64 RC2, so it's very likely arm64 specific.

@rolfbjarne
Copy link
Member Author

I can't repro on x64/macOS either.

@MichalStrehovsky
Copy link
Member

Yeah. there seems to be some confusion about which register holds the value. Here's the full disassembly of the constructor:

   0x00000055555fed70 <+0>:     stp     x29, x30, [sp, #-64]!
   0x00000055555fed74 <+4>:     stp     x19, x20, [sp, #16]
   0x00000055555fed78 <+8>:     stp     x21, x22, [sp, #32]
   0x00000055555fed7c <+12>:    stp     x23, x24, [sp, #48]
   0x00000055555fed80 <+16>:    mov     x29, sp
   0x00000055555fed84 <+20>:    mov     x19, x0
=> 0x00000055555fed88 <+24>:    stp     xzr, xzr, [x19, #8]
   0x00000055555fed8c <+28>:    adrp    x20, 0x55555ff000 <codegenbug_Program__Compare+416>
   0x00000055555fed90 <+32>:    add     x20, x20, #0x190
   0x00000055555fed94 <+36>:    adrp    x21, 0x55555ff000 <codegenbug_Program__Compare+416>
   0x00000055555fed98 <+40>:    add     x21, x21, #0x1b0
   0x00000055555fed9c <+44>:    ldr     q16, 0x55555fee50 <codegenbug_Program___ctor+224>
   0x00000055555feda0 <+48>:    stur    q16, [x19, #8]
   0x00000055555feda4 <+52>:    ldp     x22, x23, [x19, #8]
   0x00000055555feda8 <+56>:    mov     x0, x23
   0x00000055555fedac <+60>:    adrp    x1, 0x55556aa000
   0x00000055555fedb0 <+64>:    add     x1, x1, #0x350
   0x00000055555fedb4 <+68>:    mov     x2, xzr
   0x00000055555fedb8 <+72>:    bl      0x55555c93a0 <S_P_CoreLib_System_Number___FormatInt64_g__FormatInt64Slow_22_0>
   0x00000055555fedbc <+76>:    mov     x24, x0
   0x00000055555fedc0 <+80>:    mov     x0, x21
   0x00000055555fedc4 <+84>:    adrp    x1, 0x55556aa000
   0x00000055555fedc8 <+88>:    add     x1, x1, #0x350
   0x00000055555fedcc <+92>:    mov     x2, xzr
   0x00000055555fedd0 <+96>:    bl      0x55555c93a0 <S_P_CoreLib_System_Number___FormatInt64_g__FormatInt64Slow_22_0>
   0x00000055555fedd4 <+100>:   mov     x1, x0
   0x00000055555fedd8 <+104>:   mov     x0, x24
   0x00000055555feddc <+108>:   bl      0x55555bfd30 <String__Equals_2>
   0x00000055555fede0 <+112>:   mov     x2, x20
   0x00000055555fede4 <+116>:   mov     x1, x22
   0x00000055555fede8 <+120>:   adrp    x0, 0x555569f000
   0x00000055555fedec <+124>:   add     x0, x0, #0x498
   0x00000055555fedf0 <+128>:   bl      0x55555fee60 <codegenbug_Program__Compare>
   0x00000055555fedf4 <+132>:   mov     x2, x21
   0x00000055555fedf8 <+136>:   mov     x1, x23
   0x00000055555fedfc <+140>:   adrp    x0, 0x555569f000
   0x00000055555fee00 <+144>:   add     x0, x0, #0x4d0
   0x00000055555fee04 <+148>:   bl      0x55555fee60 <codegenbug_Program__Compare>
   0x00000055555fee08 <+152>:   stp     x20, x21, [x19, #8]
   0x00000055555fee0c <+156>:   ldp     x22, x23, [x19, #8]
   0x00000055555fee10 <+160>:   mov     x2, x20
   0x00000055555fee14 <+164>:   mov     x1, x22
   0x00000055555fee18 <+168>:   adrp    x0, 0x555569f000
   0x00000055555fee1c <+172>:   add     x0, x0, #0x498
   0x00000055555fee20 <+176>:   bl      0x55555fee60 <codegenbug_Program__Compare>
   0x00000055555fee24 <+180>:   mov     x2, x21
   0x00000055555fee28 <+184>:   mov     x1, x23
   0x00000055555fee2c <+188>:   adrp    x0, 0x555569f000
   0x00000055555fee30 <+192>:   add     x0, x0, #0x4d0
   0x00000055555fee34 <+196>:   bl      0x55555fee60 <codegenbug_Program__Compare>
   0x00000055555fee38 <+200>:   ldp     x23, x24, [sp, #48]
   0x00000055555fee3c <+204>:   ldp     x21, x22, [sp, #32]
   0x00000055555fee40 <+208>:   ldp     x19, x20, [sp, #16]
   0x00000055555fee44 <+212>:   ldp     x29, x30, [sp], #64
   0x00000055555fee48 <+216>:   ret
   0x00000055555fee4c <+220>:   udf     #0
   0x00000055555fee50 <+224>:   .inst   0x00421378 ; undefined
   0x00000055555fee54 <+228>:   udf     #0
   0x00000055555fee58 <+232>:   .inst   0x00421390 ; undefined
   0x00000055555fee5c <+236>:   udf     #0

The interesting part is:

   0x00000055555fed8c <+28>:    adrp    x20, 0x55555ff000 <codegenbug_Program__Compare+416>
   0x00000055555fed90 <+32>:    add     x20, x20, #0x190
   0x00000055555fed94 <+36>:    adrp    x21, 0x55555ff000 <codegenbug_Program__Compare+416>
   0x00000055555fed98 <+40>:    add     x21, x21, #0x1b0

This is where we take the address of AllocateCallback and FreeCallback (there is only one place where we do this). We place AllocateCallback in x20, FreeCallback in x21.

Nothing seems to touch x20 until we get to the compare call:

   0x00000055555fede0 <+112>:   mov     x2, x20
   0x00000055555fede4 <+116>:   mov     x1, x22
   0x00000055555fede8 <+120>:   adrp    x0, 0x555569f000
   0x00000055555fedec <+124>:   add     x0, x0, #0x498
   0x00000055555fedf0 <+128>:   bl      0x55555fee60 <codegenbug_Program__Compare>

And at this point x22 should have the same value as x20 but it doesn't, Compare fails.

@MichalStrehovsky MichalStrehovsky added area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI and removed area-NativeAOT-coreclr labels Sep 6, 2024
Copy link
Contributor

Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch
See info in area-owners.md if you want to be subscribed.

@vitek-karas
Copy link
Member

This works on .NET 8 (osx arm64), but breaks on .NET 9 (I tried Preview6 and later, and it all fails).

@JulieLeeMSFT
Copy link
Member

@AndyAyersMS, PTAL if we need to fix this in .NET 9.

@JulieLeeMSFT JulieLeeMSFT added this to the 9.0.0 milestone Sep 6, 2024
@JulieLeeMSFT JulieLeeMSFT removed the untriaged New issue has not been triaged by the area owner label Sep 6, 2024
@AndyAyersMS
Copy link
Member

I can repro on my volterra. Digging in.

@AndyAyersMS
Copy link
Member

There is one other place the addresses get taken, but it's hidden.

The JIT tries to set up x22 and x23 via this stretch of code up at the start of the method:

            ldr     q16, [@RWD00]
            str     q16, [x19, #0x08]
            ldp     x22, x23, [x19, #0x08]

However the function addresses are not properly captured in the vector constant @RWD00:

RWD00  	dq	0000000000422020h, 0000000000422038h

I am not sure if we support relocs in jit emitted data. If not, we'll need to block transforming these two separate stores into a single vector constant store.

@EgorBo do you know?

@AndyAyersMS
Copy link
Member

@rolfbjarne I have a fix.

Would it be ok to add your example as a new test case?

AndyAyersMS added a commit to AndyAyersMS/runtime that referenced this issue Sep 7, 2024
We can't represent relocations in data currently.

Fixes dotnet#107396.
@dotnet-policy-service dotnet-policy-service bot added the in-pr There is an active PR which will close this issue when it is merged label Sep 7, 2024
@rolfbjarne
Copy link
Member Author

@rolfbjarne I have a fix.

Would it be ok to add your example as a new test case?

Yes, of course.

github-actions bot pushed a commit that referenced this issue Sep 7, 2024
We can't represent relocations in data currently.

Fixes #107396.
@MichalStrehovsky
Copy link
Member

I am not sure if we support relocs in jit emitted data. If not, we'll need to block transforming these two separate stores into a single vector constant store.

@EgorBo do you know?

FWIW, the JitInterface has provisions for relocs in JIT emitted data. For this method on arm64 I don't see RyuJIT calling recordRelocation for the data. Would it be worth filing another bug to investigate why?

@EgorBo
Copy link
Member

EgorBo commented Sep 9, 2024

I am not sure if we support relocs in jit emitted data. If not, we'll need to block transforming these two separate stores into a single vector constant store.
@EgorBo do you know?

FWIW, the JitInterface has provisions for relocs in JIT emitted data. For this method on arm64 I don't see RyuJIT calling recordRelocation for the data. Would it be worth filing another bug to investigate why?

I guess it should call it now with #107491

jeffschwMSFT added a commit that referenced this issue Sep 11, 2024
We can't represent relocations in data currently.

Fixes #107396.

Co-authored-by: Andy Ayers <[email protected]>
Co-authored-by: Jeff Schwartz <[email protected]>
jtschuster pushed a commit to jtschuster/runtime that referenced this issue Sep 17, 2024
sirntar pushed a commit to sirntar/runtime that referenced this issue Sep 30, 2024
@github-actions github-actions bot locked and limited conversation to collaborators Oct 10, 2024
mikelle-rogers pushed a commit to mikelle-rogers/runtime that referenced this issue Dec 10, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI in-pr There is an active PR which will close this issue when it is merged regression-from-last-release
Projects
Archived in project
Development

Successfully merging a pull request may close this issue.

7 participants