-
Notifications
You must be signed in to change notification settings - Fork 5k
Interpreter P/Invoke support #115393
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
base: main
Are you sure you want to change the base?
Interpreter P/Invoke support #115393
Conversation
kg
commented
May 8, 2025
•
edited
Loading
edited
- Implements the static field intrinsic for IntPtr.Zero
- Adds a CALL_PINVOKE opcode that is similar to CALL but has a second data item containing the address-of-address or address the target native function to call
- Modified InvokeCompiledMethod to accept the target address separately instead of fetching it from the MethodDesc unconditionally, so we can reuse it for p/invoke
- Adds a custom C++ library with some functions to call via pinvoke for testing
- Fixes CEE_LDC_R8 (it was loading nans sometimes for reasons I can't identify)
- Adds a couple basic tests for p/invoke, with more commented out that don't work yet
- Fixes some whitespace errors left over from my boxing PR
Tagging subscribers to this area: @mangod9 |
@AaronRobinsonMSFT all the things you've commented on are from my WIP PR that I am still cleaning up, Katelyn has built her stuff on top of that. |
src/coreclr/interpreter/compiler.cpp
Outdated
{ | ||
void *addressOfAddress; | ||
CORINFO_CONST_LOOKUP lookup; | ||
m_compHnd->getAddressOfPInvokeTarget(targetMethod, &lookup); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is an optimization (JIT calls it PInvoke inlining) that is allowed only when pInvokeMarshalingRequired
returns false.
When pInvokeMarshalingRequired
returns true, the PInvoke method should be called like a regular method. It is fine to call the PInvoke method like a regular method even when pInvokeMarshalingRequired
returns false. The regular JIT does that when optimizations are off, in cold code, or to resolve problematic interactions with exception handling.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The regular JIT does that
impCanPInvokeInline
and impCanPInvokeInlineCallSite
are the methods that decide whether to do PInvoke inlining in regular JIT
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense. So if marshaling is required I should update this to go through the normal call path, and the target method will have IL? I will try to cook up a test for that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right.
Or do not worry about PInvoke inlining at all in the interpreter and call all PInvokes as regular methods. PInvoke inlining is an optimization. Interpreter JIT does not do any optimizations today, and building an optimizing interpreter JIT should be a separate project.
the target method will have IL?
Yes, the target method will have IL.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried calling the pinvoke target as a regular method and it asserted, but maybe it will work now that the native call PR is in. I'll test it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I expect that we will need to do something about the hidden IL stub arguments and PInvoke IL stub sharing to make the marshalling stubs work. The easiest path may be to disable PInvoke IL stub sharing in interpreter mode (or get rid of it completely - it may not be as important as it used to be).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Assert failure(PID 56980 [0x0000de94], Thread: 57112 [0xdf18]): IsIL() || IsNoMetadata()
CORECLR! MethodDesc::PrepareCode + 0x1F8 (0x00007ffa`16b59ee8)
CORECLR! MethodDesc::PrepareInitialCode + 0x231 (0x00007ffa`16b5a991)
CORECLR! InterpExecMethod + 0x67C3 (0x00007ffa`16dbb313)
CORECLR! ExecuteInterpretedMethod + 0xE1 (0x00007ffa`16b5dbc1)
This is what happens if I do a normal call to invoke a non-marshaled PInvoke method. Am I doing something wrong? When I first encountered it I went "That makes sense, it's not an IL method".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This assert also happens if I try to invoke a marshaled pinvoke method as if it were a regular IL method.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The interpreter related code in prestub.cpp (and other places) is not prepared to handle PInvokes.
We should agree on how we are going to deal with sharing of marshalling stubs first; and then do the required changes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The working directory change is controversial, so I'm open to changing it around and making InterpreterTester be the one that builds the dll instead. IMO Interpreter.dll should be built alongside pinvoke.dll since that's the actual dependency, and we should be running it where-it-was-built instead of in InterpreterTester's artifacts dir. IIRC msbuild has never been able to express 'when foo.csproj is referenced by bar.csproj, copy these satellite files along with foo.dll'. I like being able to directly run Interpreter.dll with corerun in the debugger and have it work correctly without having to know to set a special cwd. But I can see the argument for doing it the other way. This will fail in a less confusing way once we have EH, at least - at that point you would see the dll not found error and be able to figure out what's wrong pretty quickly. |
PInvoke test passes Update dllimport assertion to accept interpreter frames Remove outdated comment Fix CI Fix merge damage Fix merge damage Clean up CI warning in dllimport.cpp for debug assertion Add broken test for pinvoke marshaling Detect marshaling and assert Disable puts call in test Fix release build Maybe fix CI by being less fancy with ifdefs
Don't rely on fp rounding for the doubles part of the pinvoke test Run Interpreter tests with their original built location as the working directory so that pinvoke.dll can be located Whitespace cleanups Better solution to missing dll Add assertion Simplify InterpreterTester.cs