Uh oh!
There was an error while loading. Please reload this page.
Convert exception-related MethodDescCallSite calls to UnmanagedCallersOnly - #124834
Conversation
…sOnly Replace MethodDescCallSite/CallDescrWorker calls with more efficient UnmanagedCallersOnly reverse P/Invoke calls in exception handling code paths (priority 2 from dotnet#123864): - GetExceptionMessage: Object.ToString via UCO - ExceptionPreserveStackTrace: InternalPreserveStackTrace via UCO - WrapNonCompliantException: RuntimeWrappedException ctor via UCO - CreateTypeInitializationExceptionObject: TypeInitializationException via UCO - GetResourceStringFromManaged: Environment.GetResourceStringLocal via UCO - GetEventArgsForNotification: FirstChanceExceptionEventArgs ctor via UCO Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This pull request converts six exception-related MethodDescCallSite call sites in excep.cpp to use the more efficient UnmanagedCallersOnly reverse P/Invoke pattern, following the established approach from priority 1 and 2 tasks in issue #123864.
Changes:
- Replaces
MethodDescCallSite/CallDescrWorkerwithUnmanagedCallersOnlyCaller::InvokeThrowingfor six exception-handling methods - Adds six new UnmanagedCallersOnly wrapper methods in managed code (Object.CoreCLR.cs, Exception.CoreCLR.cs, Environment.CoreCLR.cs)
- Updates metasig.h with six new method signatures and corelib.h with updated DEFINE_METHOD entries, removing obsolete constructor definitions
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/metasig.h | Adds 6 new metasig definitions for UnmanagedCallersOnly wrappers (PtrObj_PtrStr, PtrObj_PtrObj, PtrStr_PtrStr, PtrException_PtrException, PtrChar_PtrException_PtrObj, PtrException_PtrObj, all with PtrException_RetVoid suffix) |
| src/coreclr/vm/corelib.h | Updates DEFINE_METHOD entries for the 6 converted methods and removes 3 obsolete constructor entries (TYPE_INIT_EXCEPTION.STR_EX_CTOR, RUNTIME_WRAPPED_EXCEPTION.OBJ_CTOR, FIRSTCHANCE_EVENTARGS.CTOR) |
| src/coreclr/vm/excep.cpp | Converts 6 call sites to use UnmanagedCallersOnlyCaller: GetExceptionMessage, ExceptionPreserveStackTrace, WrapNonCompliantException, CreateTypeInitializationExceptionObject, GetResourceStringFromManaged, GetEventArgsForNotification |
| src/coreclr/System.Private.CoreLib/src/System/Object.CoreCLR.cs | Adds GetToString UnmanagedCallersOnly wrapper with Exception* out-parameter |
| src/coreclr/System.Private.CoreLib/src/System/Exception.CoreCLR.cs | Adds 4 UnmanagedCallersOnly wrappers: InternalPreserveStackTrace, CreateRuntimeWrappedException, CreateTypeInitializationException, CreateFirstChanceExceptionEventArgs |
| src/coreclr/System.Private.CoreLib/src/System/Environment.CoreCLR.cs | Converts GetResourceStringLocal to UnmanagedCallersOnly pattern with string* in/out parameters |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
- Move GetToString from System.Object to System.Exception to avoid breaking reflection in user code - Clean up stale comment about SystemException fallback in CreateTypeInitializationExceptionObject - Rename GetResourceStringLocal to GetResourceString, drop Local suffix - Delete unused DEFINE_CLASS(FIRSTCHANCE_EVENTARGS) from corelib.h - Fold GetResourceStringFromManaged into its single caller ResMgrGetString, pass key as char* and move string creation to managed side - Add GCPROTECT around ResMgrGetString caller in clrex.cpp Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…o single UCO call Combine event args construction and delegate invocation into a single managed DeliverFirstChanceNotification UCO method. Multicast delegate iteration is now handled by managed Delegate.Invoke. Remove the native DeliverExceptionNotification and GetEventArgsForNotification methods. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
AaronRobinsonMSFT
commented
Feb 25, 2026
@EgorBot -linux_amd -osx_arm64 usingBenchmarkDotNet.Attributes;usingBenchmarkDotNet.Running;usingSystem;BenchmarkSwitcher.FromAssembly(typeof(Bench).Assembly).Run(args);publicclassBench{privateException_cachedException=newInvalidOperationException("test");[Benchmark]publicvoidThrowCatch_NoHandler(){try{throw_cachedException;}catch{}}[Benchmark]publicvoidThrowCatch_WithHandler(){AppDomain.CurrentDomain.FirstChanceException+=Handler;try{throw_cachedException;}catch{}finally{AppDomain.CurrentDomain.FirstChanceException-=Handler;}}privatestaticvoidHandler(object?sender,System.Runtime.ExceptionServices.FirstChanceExceptionEventArgse){}} |
…dd OnFirstChanceException method
Replace the CanDeliverNotificationToCurrentAppDomain check, which read the managed FirstChanceException delegate field via CoreLibBinder in cooperative GC mode, with a native Volatile<BOOL> flag set via a SuppressGCTransition QCall when a handler is first registered. This avoids the GCX_COOP transition in DeliverFirstChanceNotification on every exception throw when no handler is registered. The flag is sticky: once set to TRUE it is never cleared, avoiding race conditions between concurrent add/remove operations. - Remove CanDeliverNotificationToCurrentAppDomain and FIELD__APPCONTEXT__FIRST_CHANCE_EXCEPTION - Add AppDomain_SetFirstChanceExceptionHandler QCall (SuppressGCTransition) - Move GCX_COOP inside the handler check in DeliverFirstChanceNotification Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
…124854) First set of priority 3 conversions from #123864 — replace `MethodDescCallSite`/`CallDescrWorker` calls with `UnmanagedCallersOnly` reverse P/Invoke calls. ## Converted call sites | File | Call site | Method | |------|-----------|--------| | `appdomain.cpp` | `AppDomain::RaiseExitProcessEvent` | `ON_PROCESS_EXIT` | | `appdomain.cpp` | `AppDomain::OnUnhandledException` | `ON_UNHANDLED_EXCEPTION` | | `assembly.cpp` | `RunManagedStartup` | `MANAGED_STARTUP` | | `assembly.cpp` | `RunMainInternal` (TARGET_BROWSER) | `HANDLE_ASYNC_ENTRYPOINT` | | `invokeutil.cpp` | `InvokeUtil::CreateClassLoadExcept` | `ReflectionTypeLoadException` ctor | | `invokeutil.cpp` | `InvokeUtil::CreateTargetExcept` | `TargetInvocationException` ctor | ## Remaining priority 3 items (follow ups) - Main entry point in `RunMainInternal` (dynamic MethodDesc) - `CustomAttribute_CreateCustomAttributeInstance` (dynamic ctor, already a QCall) - `CorHost2::ExecuteAssembly` (dynamic method lookup) - `FuncEvalWrapper`/`DoNormalFuncEval` (debugger func-eval) ## Pattern Follows the same conversion pattern as #124834 (priority 2). Each conversion adds an `[UnmanagedCallersOnly]` managed wrapper with an `Exception*` out-parameter and replaces the native `MethodDescCallSite` + `ARG_SLOT` with `UnmanagedCallersOnlyCaller::InvokeThrowing`. --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Jan Kotas <jkotas@microsoft.com>
Summary
Replace
MethodDescCallSite/CallDescrWorkercalls with more efficientUnmanagedCallersOnlyreverse P/Invoke calls in exception handling code paths (priority 2 from #123864).Changes
Convert the following call sites in
excep.cppto useUnmanagedCallersOnlyCaller:GetExceptionMessage:Object.ToStringvia UCO (METHOD__RUNTIMEHELPERS__CALL_TO_STRING)ExceptionPreserveStackTrace:InternalPreserveStackTracevia UCOWrapNonCompliantException:RuntimeWrappedExceptionconstruction via UCOCreateTypeInitializationExceptionObject:TypeInitializationExceptionconstruction via UCOGetResourceStringFromManaged:Environment.GetResourceStringLocalvia UCOGetEventArgsForNotification:FirstChanceExceptionEventArgsconstruction via UCOPattern
Each conversion follows the established pattern:
[UnmanagedCallersOnly]static wrapper in managed code withException*out-parametermetasig.handDEFINE_METHODincorelib.hMethodDescCallSite+ARG_SLOTwithUnmanagedCallersOnlyCaller::InvokeThrowingDEFINE_METHODentries for old constructorsAll GC references passed to
InvokeThrowingare in GCPROTECT'd locations.