From 1fa1745581e26069093a679626e346b675aa534f Mon Sep 17 00:00:00 2001 From: Jan Vorlicek Date: Fri, 4 Oct 2024 09:56:48 +0200 Subject: [PATCH] Improve software exception handling performance (#108480) * Improve software exception handling performance I have recently discovered that large part of the time spent in the EH while handling software exception is in the RtlLookupFunctionEntry Windows API. That API is called when unwinding native (non-managed) frames on stack. The current implementation of the IL_Throw JIT helper that is used to throw managed exceptions needs three unwinds to get to the managed caller. Due to the two passes of EH, it means there are six calls made to that API per throw. This change reduces that to just a single unwind, which leads to about 12% improvement in single threaded scenarios and about 30% improvement in multi threaded one for a case when an exception is thrown over 10 managed frames and then caught. It also leads to similar improvements in async exception handling performance. On Linux, the multi-threaded gain is much higher, I've measured 3 fold improvement. An additional benefit of this change is removal of the helper method frame from IL_Throw and IL_Rethrow, which contributes to the current effort of getting rid of the helper method frames. I have made this change for the new EH only. --- src/coreclr/inc/vptr_list.h | 3 + src/coreclr/vm/excep.cpp | 76 +++++++++++++++++++ src/coreclr/vm/frames.h | 67 +++++++++++++++++ src/coreclr/vm/jithelpers.cpp | 136 +++++++++++++++++----------------- 4 files changed, 215 insertions(+), 67 deletions(-) diff --git a/src/coreclr/inc/vptr_list.h b/src/coreclr/inc/vptr_list.h index 8150147bfa2cb..f0d686f139cea 100644 --- a/src/coreclr/inc/vptr_list.h +++ b/src/coreclr/inc/vptr_list.h @@ -55,6 +55,9 @@ VPTR_CLASS(DebuggerSecurityCodeMarkFrame) VPTR_CLASS(DebuggerExitFrame) VPTR_CLASS(DebuggerU2MCatchHandlerFrame) VPTR_CLASS(FaultingExceptionFrame) +#ifdef FEATURE_EH_FUNCLETS +VPTR_CLASS(SoftwareExceptionFrame) +#endif // FEATURE_EH_FUNCLETS VPTR_CLASS(FuncEvalFrame) VPTR_CLASS(HelperMethodFrame) VPTR_CLASS(HelperMethodFrame_1OBJ) diff --git a/src/coreclr/vm/excep.cpp b/src/coreclr/vm/excep.cpp index 8418202b93389..ece90cbda507b 100644 --- a/src/coreclr/vm/excep.cpp +++ b/src/coreclr/vm/excep.cpp @@ -11553,3 +11553,79 @@ MethodDesc * GetUserMethodForILStub(Thread * pThread, UINT_PTR uStubSP, MethodDe #endif // FEATURE_COMINTEROP return pUserMD; } + + +#ifdef FEATURE_EH_FUNCLETS + +void SoftwareExceptionFrame::UpdateRegDisplay(const PREGDISPLAY pRD, bool updateFloats) +{ + LIMITED_METHOD_DAC_CONTRACT; + +#define CALLEE_SAVED_REGISTER(regname) pRD->pCurrentContext->regname = *dac_cast((TADDR)m_ContextPointers.regname); + ENUM_CALLEE_SAVED_REGISTERS(); +#undef CALLEE_SAVED_REGISTER + +#define CALLEE_SAVED_REGISTER(regname) pRD->pCurrentContextPointers->regname = m_ContextPointers.regname; + ENUM_CALLEE_SAVED_REGISTERS(); +#undef CALLEE_SAVED_REGISTER + +#define CALLEE_SAVED_REGISTER(regname) pRD->pCurrentContext->regname = m_Context.regname; + ENUM_FP_CALLEE_SAVED_REGISTERS(); +#undef CALLEE_SAVED_REGISTER + + SetIP(pRD->pCurrentContext, ::GetIP(&m_Context)); + SetSP(pRD->pCurrentContext, ::GetSP(&m_Context)); + + pRD->ControlPC = ::GetIP(&m_Context); + pRD->SP = ::GetSP(&m_Context); + + pRD->IsCallerContextValid = FALSE; + pRD->IsCallerSPValid = FALSE; // Don't add usage of this field. This is only temporary. +} + +#ifndef DACCESS_COMPILE +// +// Init a new frame +// +void SoftwareExceptionFrame::Init() +{ + WRAPPER_NO_CONTRACT; + +#define CALLEE_SAVED_REGISTER(regname) m_ContextPointers.regname = NULL; + ENUM_CALLEE_SAVED_REGISTERS(); +#undef CALLEE_SAVED_REGISTER + +#ifndef TARGET_UNIX + Thread::VirtualUnwindCallFrame(&m_Context, &m_ContextPointers); +#else // !TARGET_UNIX + BOOL success = PAL_VirtualUnwind(&m_Context, &m_ContextPointers); + if (!success) + { + _ASSERTE(!"SoftwareExceptionFrame::Init failed"); + EEPOLICY_HANDLE_FATAL_ERROR(COR_E_EXECUTIONENGINE); + } +#endif // !TARGET_UNIX + +#define CALLEE_SAVED_REGISTER(regname) if (m_ContextPointers.regname == NULL) m_ContextPointers.regname = &m_Context.regname; + ENUM_CALLEE_SAVED_REGISTERS(); +#undef CALLEE_SAVED_REGISTER + + _ASSERTE(ExecutionManager::IsManagedCode(::GetIP(&m_Context))); + + m_ReturnAddress = ::GetIP(&m_Context); +} + +// +// Init and Link in a new frame +// +void SoftwareExceptionFrame::InitAndLink(Thread *pThread) +{ + WRAPPER_NO_CONTRACT; + + Init(); + + Push(pThread); +} + +#endif // DACCESS_COMPILE +#endif // FEATURE_EH_FUNCLETS diff --git a/src/coreclr/vm/frames.h b/src/coreclr/vm/frames.h index 8031a94dce557..071a96c8301b6 100644 --- a/src/coreclr/vm/frames.h +++ b/src/coreclr/vm/frames.h @@ -190,6 +190,9 @@ FRAME_TYPE_NAME(ResumableFrame) FRAME_TYPE_NAME(RedirectedThreadFrame) #endif // FEATURE_HIJACK FRAME_TYPE_NAME(FaultingExceptionFrame) +#ifdef FEATURE_EH_FUNCLETS +FRAME_TYPE_NAME(SoftwareExceptionFrame) +#endif // FEATURE_EH_FUNCLETS #ifdef DEBUGGING_SUPPORTED FRAME_TYPE_NAME(FuncEvalFrame) #endif // DEBUGGING_SUPPORTED @@ -1146,6 +1149,69 @@ class FaultingExceptionFrame : public Frame DEFINE_VTABLE_GETTER_AND_DTOR(FaultingExceptionFrame) }; +#ifdef FEATURE_EH_FUNCLETS + +class SoftwareExceptionFrame : public Frame +{ + TADDR m_ReturnAddress; + T_CONTEXT m_Context; + T_KNONVOLATILE_CONTEXT_POINTERS m_ContextPointers; + + VPTR_VTABLE_CLASS(SoftwareExceptionFrame, Frame) + +public: +#ifndef DACCESS_COMPILE + SoftwareExceptionFrame() { + LIMITED_METHOD_CONTRACT; + } +#endif + + virtual TADDR GetReturnAddressPtr() + { + LIMITED_METHOD_DAC_CONTRACT; + return PTR_HOST_MEMBER_TADDR(SoftwareExceptionFrame, this, m_ReturnAddress); + } + + void Init(); + void InitAndLink(Thread *pThread); + + Interception GetInterception() + { + LIMITED_METHOD_DAC_CONTRACT; + return INTERCEPTION_EXCEPTION; + } + + virtual ETransitionType GetTransitionType() + { + LIMITED_METHOD_DAC_CONTRACT; + return TT_InternalCall; + } + + unsigned GetFrameAttribs() + { + LIMITED_METHOD_DAC_CONTRACT; + return FRAME_ATTR_EXCEPTION; + } + + T_CONTEXT* GetContext() + { + LIMITED_METHOD_DAC_CONTRACT; + return &m_Context; + } + + virtual BOOL NeedsUpdateRegDisplay() + { + return TRUE; + } + + virtual void UpdateRegDisplay(const PREGDISPLAY, bool updateFloats = false); + + // Keep as last entry in class + DEFINE_VTABLE_GETTER_AND_DTOR(SoftwareExceptionFrame) +}; + +#endif // FEATURE_EH_FUNCLETS + //----------------------------------------------------------------------- // Frame for debugger function evaluation // @@ -3190,6 +3256,7 @@ class FrameWithCookie void Poll() { WRAPPER_NO_CONTRACT; m_frame.Poll(); } void SetStackPointerPtr(TADDR sp) { WRAPPER_NO_CONTRACT; m_frame.SetStackPointerPtr(sp); } void InitAndLink(T_CONTEXT *pContext) { WRAPPER_NO_CONTRACT; m_frame.InitAndLink(pContext); } + void InitAndLink(Thread *pThread) { WRAPPER_NO_CONTRACT; m_frame.InitAndLink(pThread); } void Init(Thread *pThread, OBJECTREF *pObjRefs, UINT numObjRefs, BOOL maybeInterior) { WRAPPER_NO_CONTRACT; m_frame.Init(pThread, pObjRefs, numObjRefs, maybeInterior); } ValueClassInfo ** GetValueClassInfoList() { WRAPPER_NO_CONTRACT; return m_frame.GetValueClassInfoList(); } diff --git a/src/coreclr/vm/jithelpers.cpp b/src/coreclr/vm/jithelpers.cpp index 5438a2a92e959..5b617b8e1d013 100644 --- a/src/coreclr/vm/jithelpers.cpp +++ b/src/coreclr/vm/jithelpers.cpp @@ -2675,65 +2675,68 @@ HCIMPLEND /*************************************************************/ -#ifdef FEATURE_EH_FUNCLETS -void ThrowNew(OBJECTREF oref) +HCIMPL1(void, IL_Throw, Object* obj) { - if (oref == 0) - DispatchManagedException(kNullReferenceException); - else - if (!IsException(oref->GetMethodTable())) + FCALL_CONTRACT; + + /* Make no assumptions about the current machine state */ + ResetCurrentContext(); + + FC_GC_POLL_NOT_NEEDED(); // throws always open up for GC + + OBJECTREF oref = ObjectToOBJECTREF(obj); + +#ifdef FEATURE_EH_FUNCLETS + if (g_isNewExceptionHandlingEnabled) { - GCPROTECT_BEGIN(oref); + Thread *pThread = GetThread(); - WrapNonCompliantException(&oref); + FrameWithCookie exceptionFrame; + *(&exceptionFrame)->GetGSCookiePtr() = GetProcessGSCookie(); + RtlCaptureContext(exceptionFrame.GetContext()); + exceptionFrame.InitAndLink(pThread); - GCPROTECT_END(); - } - else - { // We know that the object derives from System.Exception + FC_CAN_TRIGGER_GC(); - // If the flag indicating ForeignExceptionRaise has been set, - // then do not clear the "_stackTrace" field of the exception object. - if (GetThread()->GetExceptionState()->IsRaisingForeignException()) - { - ((EXCEPTIONREF)oref)->SetStackTraceString(NULL); - } + if (oref == 0) + DispatchManagedException(kNullReferenceException); else + if (!IsException(oref->GetMethodTable())) { - ((EXCEPTIONREF)oref)->ClearStackTracePreservingRemoteStackTrace(); - } - } + GCPROTECT_BEGIN(oref); - DispatchManagedException(oref); -} -#endif // FEATURE_EH_FUNCLETS + WrapNonCompliantException(&oref); -HCIMPL1(void, IL_Throw, Object* obj) -{ - FCALL_CONTRACT; + GCPROTECT_END(); + } + else + { // We know that the object derives from System.Exception - /* Make no assumptions about the current machine state */ - ResetCurrentContext(); + // If the flag indicating ForeignExceptionRaise has been set, + // then do not clear the "_stackTrace" field of the exception object. + if (pThread->GetExceptionState()->IsRaisingForeignException()) + { + ((EXCEPTIONREF)oref)->SetStackTraceString(NULL); + } + else + { + ((EXCEPTIONREF)oref)->ClearStackTracePreservingRemoteStackTrace(); + } + } - FC_GC_POLL_NOT_NEEDED(); // throws always open up for GC + DispatchManagedException(oref, exceptionFrame.GetContext()); + FC_CAN_TRIGGER_GC_END(); + UNREACHABLE(); + } +#endif // FEATURE_EH_FUNCLETS HELPER_METHOD_FRAME_BEGIN_ATTRIB_NOPOLL(Frame::FRAME_ATTR_EXCEPTION); // Set up a frame - OBJECTREF oref = ObjectToOBJECTREF(obj); - #if defined(_DEBUG) && defined(TARGET_X86) __helperframe.InsureInit(NULL); g_ExceptionEIP = (LPVOID)__helperframe.GetReturnAddress(); #endif // defined(_DEBUG) && defined(TARGET_X86) -#ifdef FEATURE_EH_FUNCLETS - if (g_isNewExceptionHandlingEnabled) - { - ThrowNew(oref); - UNREACHABLE(); - } -#endif - if (oref == 0) COMPlusThrow(kNullReferenceException); else @@ -2768,49 +2771,48 @@ HCIMPLEND /*************************************************************/ -#ifdef FEATURE_EH_FUNCLETS -void RethrowNew() +HCIMPL0(void, IL_Rethrow) { - Thread *pThread = GetThread(); + FCALL_CONTRACT; - ExInfo *pActiveExInfo = (ExInfo*)pThread->GetExceptionState()->GetCurrentExceptionTracker(); + FC_GC_POLL_NOT_NEEDED(); // throws always open up for GC - CONTEXT exceptionContext; - RtlCaptureContext(&exceptionContext); +#ifdef FEATURE_EH_FUNCLETS + if (g_isNewExceptionHandlingEnabled) + { + Thread *pThread = GetThread(); - ExInfo exInfo(pThread, pActiveExInfo->m_ptrs.ExceptionRecord, &exceptionContext, ExKind::None); + FrameWithCookie exceptionFrame; + *(&exceptionFrame)->GetGSCookiePtr() = GetProcessGSCookie(); + RtlCaptureContext(exceptionFrame.GetContext()); + exceptionFrame.InitAndLink(pThread); - GCPROTECT_BEGIN(exInfo.m_exception); - PREPARE_NONVIRTUAL_CALLSITE(METHOD__EH__RH_RETHROW); - DECLARE_ARGHOLDER_ARRAY(args, 2); + ExInfo *pActiveExInfo = (ExInfo*)pThread->GetExceptionState()->GetCurrentExceptionTracker(); - args[ARGNUM_0] = PTR_TO_ARGHOLDER(pActiveExInfo); - args[ARGNUM_1] = PTR_TO_ARGHOLDER(&exInfo); + ExInfo exInfo(pThread, pActiveExInfo->m_ptrs.ExceptionRecord, exceptionFrame.GetContext(), ExKind::None); - pThread->IncPreventAbort(); + FC_CAN_TRIGGER_GC(); - //Ex.RhRethrow(ref ExInfo activeExInfo, ref ExInfo exInfo) - CALL_MANAGED_METHOD_NORET(args) - GCPROTECT_END(); -} -#endif // FEATURE_EH_FUNCLETS + GCPROTECT_BEGIN(exInfo.m_exception); + PREPARE_NONVIRTUAL_CALLSITE(METHOD__EH__RH_RETHROW); + DECLARE_ARGHOLDER_ARRAY(args, 2); -HCIMPL0(void, IL_Rethrow) -{ - FCALL_CONTRACT; + args[ARGNUM_0] = PTR_TO_ARGHOLDER(pActiveExInfo); + args[ARGNUM_1] = PTR_TO_ARGHOLDER(&exInfo); - FC_GC_POLL_NOT_NEEDED(); // throws always open up for GC + pThread->IncPreventAbort(); - HELPER_METHOD_FRAME_BEGIN_ATTRIB_NOPOLL(Frame::FRAME_ATTR_EXCEPTION); // Set up a frame + //Ex.RhRethrow(ref ExInfo activeExInfo, ref ExInfo exInfo) + CALL_MANAGED_METHOD_NORET(args) + GCPROTECT_END(); -#ifdef FEATURE_EH_FUNCLETS - if (g_isNewExceptionHandlingEnabled) - { - RethrowNew(); + FC_CAN_TRIGGER_GC_END(); UNREACHABLE(); } #endif + HELPER_METHOD_FRAME_BEGIN_ATTRIB_NOPOLL(Frame::FRAME_ATTR_EXCEPTION); // Set up a frame + OBJECTREF throwable = GetThread()->GetThrowable(); if (throwable != NULL) {