Enable R2R precompilation of objc_msgSend P/Invoke stubs#124770
Enable R2R precompilation of objc_msgSend P/Invoke stubs#124770davidnguyen-tech wants to merge 6 commits intodotnet:mainfrom
Conversation
The R2R PInvokeILEmitter previously threw NotSupportedException for any P/Invoke requiring an ObjC pending exception check (objc_msgSend family), forcing runtime IL stub generation. On JIT-less platforms like iOS with CoreCLR, these stubs fall back to the interpreter. This change teaches the R2R emitter to emit a call to ObjectiveCMarshal.ThrowPendingExceptionObject() after the native call, mirroring the common/NativeAOT PInvokeILEmitter. For blittable ObjC P/Invokes, the entire stub is now precompiled into the R2R image. The ThrowPendingExceptionObject method was previously only available in NativeAOT's System.Private.CoreLib. This change adds it to CoreCLR's ObjectiveCMarshal.CoreCLR.cs as well, delegating to the existing StubHelpers.GetPendingExceptionObject() infrastructure. Changes: - ObjectiveCMarshal.CoreCLR.cs: Add ThrowPendingExceptionObject() - PInvokeILEmitter.cs: Remove NotSupportedException for ObjC; emit ThrowPendingExceptionObject call after the native call - Marshaller.ReadyToRun.cs: Add IsObjCMessageSendPInvoke helper - GeneratesPInvoke: Return true for ObjC P/Invokes - CreateValueFromKey: Catch RequiresRuntimeJitException for ObjC P/Invokes with non-blittable params - CorInfoImpl: Remove assert that assumed GeneratesPInvoke is false when stubIL is null Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Remove Marshaller.IsObjCMessageSendPInvoke wrapper, call MarshalHelpers.ShouldCheckForPendingException directly in GeneratesPInvoke - Clarify GeneratesPInvoke comment: IsMarshallingRequired returns true for ObjC P/Invokes because they need an IL stub, not actual type marshalling - Add comment to empty catch block explaining JIT fallback behavior Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… ILCompiler directly now.
There was a problem hiding this comment.
Pull request overview
Enables ReadyToRun (R2R) precompilation of Objective-C objc_msgSend-family P/Invoke stubs by emitting a managed pending-exception check after the native call, avoiding a runtime-stub/interpreter fallback on JIT-less Apple platforms.
Changes:
- Add
ObjectiveCMarshal.ThrowPendingExceptionObject()in CoreCLR and expose it to the runtime binder. - Update the R2R
PInvokeILEmitterto emit a post-call pending-exception check instead of rejecting ObjC P/Invokes. - Adjust R2R compilation policy and IL caching behavior to allow ObjC P/Invokes to be attempted and to gracefully fall back when unsupported.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| src/coreclr/vm/corelib.h | Adds CoreLib binder entry for ObjectiveCMarshal.ThrowPendingExceptionObject. |
| src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs | Simplifies P/Invoke stub IL retrieval logic now that unsupported cases are handled/cached in ILCache. |
| src/coreclr/tools/aot/ILCompiler.ReadyToRun/IL/Stubs/PInvokeILEmitter.cs | Emits ThrowPendingExceptionObject after ObjC native calls; removes ObjC-specific NotSupportedException. |
| src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCompilationModuleGroupBase.cs | Allows GeneratesPInvoke for ObjC msgSend P/Invokes even when marshalling is otherwise considered required. |
| src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilation.cs | Caches “requires runtime JIT” decisions for unsupported P/Invoke stub emission via RequiresRuntimeJitException catch. |
| src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/ObjectiveCMarshal.CoreCLR.cs | Implements ThrowPendingExceptionObject() using pending-exception storage and ExceptionDispatchInfo.Throw. |
|
Tagging subscribers to 'os-ios': @vitek-karas, @kotlarmilos, @steveisok, @akoeplinger |
| if (!Marshaller.IsMarshallingRequired(method)) | ||
| return true; | ||
|
|
||
| if (MarshalHelpers.ShouldCheckForPendingException(method.Context.Target, method.GetPInvokeMethodMetadata())) |
There was a problem hiding this comment.
As we discussed yesterday, this check here looks wrong. This check is already done as part of Marshaller.IsMarshallingRequired and I believe it should be removed from there. In your case, this method is returning true for pinvokes with non-blittable types that require check for pending exception. It should be returning false instead and I believe this is the reason you are forcefully catching the requires jit exception above. If the checks here would be correct, you shouldn't need to do that.
|
Were there also any interpreted pinvokes with non-blittable types ? In case we might need additional fixes, related to the original plan of using |
|
Do we need to re-enable or create a new sets of tests for this? We have tests for these APIs, /cc @jkoritzinsky |
The tests there are falling back to JIT/Interpreter for these stubs (which is correct behavior). We could add testing to validate that the methods were actually generated ( |
Problem
TODO: Add measurements on a sample app
The R2R PInvokeILEmitter previously threw
NotSupportedExceptionfor any P/Invoke requiring an ObjC pending exception check (objc_msgSendcalls), forcing runtime IL stub generation every time.On JIT-less platforms like iOS with CoreCLR, these stubs fall back to the interpreter, with unnecessary performance costs.
Solution
Change R2R P/Invoke IL Emitter to emit a call to
ObjectiveCMarshal.ThrowPendingExceptionObject()after the native call, mirroring the NativeAOT PInvokeILEmitter:runtime/src/coreclr/tools/Common/TypeSystem/IL/Stubs/PInvokeILEmitter.cs
Lines 349 to 353 in 66f3596
For blittable ObjC P/Invokes, the entire stub is now precompiled into the R2R image.