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

Mark UnmanagedCallersOnlyTest as NativeAOT incompatible #74546

Merged
merged 4 commits into from
Aug 29, 2022

Conversation

MichalStrehovsky
Copy link
Member

@MichalStrehovsky MichalStrehovsky commented Aug 25, 2022

No description provided.

We try to prevent compiler crashes and aborts as much as reasonable. The justification is that if the problematic codepath exists, but it's unreachable with a JIT based runtime, the app would work despite having the problem. We take this stance for missing references and other invalidness. In .NET Native times we saw a lot of invalid code being generated by non-C# compilers.

When building the interop tests, I saw a compiler crash when compiling UnmanagedCallersOnly tests.

What happens is that we try to compile the problematic method and hit the throw. We then go down to the fallback logic that tries to recompile the method as a throwing stub, but hit the throw again. This is unrecoverable and crashes the compiler.

It's hard to come up with a JIT compatible behavior for this, but I think this is as close as possible. It would have been easier to emulate if the check happened when the method address is taken, but we only do this check once we try to JIT the method. In either case this likely produces a pretty unrecoverable runtime exception.
@jkotas
Copy link
Member

jkotas commented Aug 27, 2022

I have tried a simple repro:

using System.Runtime.InteropServices;

unsafe class Program
{
    static void Main()
    {
        new Program().ToString();
        delegate* unmanaged<void> p = &foo;
        p();
    }

    [UnmanagedCallersOnly]
    static void foo() // ildasm / change to instance method in IL / ilasm
    {
        Console.WriteLine("Here!");
    }
}

It still fails compilation with this change with:

ILC: Method '[repro]Program.foo()' will always throw because: UnmanagedCallersOnly attribute specified on non-static method 'Void Program.foo()'
Error: VTable of type 'System.InvalidProgramException' not computed by the IL scanner. You can work around by running the compilation with scanner disabled.
ILCompiler.ScannerFailedException: VTable of type 'System.InvalidProgramException' not computed by the IL scanner. You can work around by running the compilation with scanner disabled.

@jkotas
Copy link
Member

jkotas commented Aug 27, 2022

We try to prevent compiler crashes and aborts as much as reasonable. The justification is that if the problematic codepath exists, but it's unreachable with a JIT based runtime, the app would work despite having the problem.

Yes, to the limit. I agree with this stance for stuff you can write in C#. If you can only generate the bad IL using ilasm or some other non-C# compiler, I think it is fine for the compiler crash on it. We have not been trying to 100% match behavior for bad IL between runtime and AOT compilers.

We take this stance for missing references and other invalidness. In .NET Native times we saw a lot of invalid code being generated by non-C# compilers.

The goal for .NET Native for UWP was to get all apps, no matter how broken, AOT compiled and working. We do not have to the same bar for the current native AOT project.

The runtime failure mode if somebody tries to call this method is AV in stackwalk:

repro!StackFrameIterator::InternalInit+0x68:
00007ff7`7a4499e8 488b07          mov     rax,qword ptr [rdi] ds:0000b87e`fafb6000=????????????????
0:000> g
(3740.6bbc): Access violation - code c0000005 (!!! second chance !!!)
repro!StackFrameIterator::InternalInit+0x68:
00007ff7`7a4499e8 488b07          mov     rax,qword ptr [rdi] ds:000000b8`7efae800=00000254d70c0000

I think it is better to crash the compiler instead of being exposed to this failure mode.

@MichalStrehovsky
Copy link
Member Author

MichalStrehovsky commented Aug 27, 2022

This will be reachable by user written code once we fix the TODO to make this also throw when marshalling is needed (I'm touching that code here). Just make an unmanagedcallersonly with a bool or something...

@jkotas
Copy link
Member

jkotas commented Aug 28, 2022

We can make the UnmanagedCallersOnly with bool fail gracefully by skipping the IsMarshallingRequired check for _isFallbackBodyCompilation. C# enforces that arguments and return value of UnmanagedCallersOnly are unmanaged types, so the JIT should not have a problem with compiling the fallback method as proper reverse P/Invoke as long as you wrote it in C#.

@MichalStrehovsky MichalStrehovsky changed the title Don't crash compilation on invalid UnmanagedCallersOnly Mark UnmanagedCallersOnlyTest as NativeAOT incompatible Aug 29, 2022
Copy link
Member

@jkotas jkotas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks

@jkotas jkotas merged commit f7870ad into dotnet:main Aug 29, 2022
@ghost ghost locked as resolved and limited conversation to collaborators Sep 28, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants