From b7bb968e7c63d87c23fae0b00376dcfc316d7867 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Wed, 1 Oct 2025 14:54:31 +0200 Subject: [PATCH 01/51] Set target method desc for resumption stubs --- src/coreclr/vm/jitinterface.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index aea6055a76dbff..f31b650dc9f349 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -14810,6 +14810,9 @@ CORINFO_METHOD_HANDLE CEEJitInfo::getAsyncResumptionStub() amTracker.SuppressRelease(); + ILStubResolver *pResolver = result->AsDynamicMethodDesc()->GetILStubResolver(); + pResolver->SetStubTargetMethodDesc(m_pMethodBeingCompiled); + const char* optimizationTierName = "UnknownTier"; #ifdef FEATURE_TIERED_COMPILATION switch (ncv.GetOptimizationTier()) From d5d08640c9463d573971cd1f3037df2824d011ec Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Wed, 1 Oct 2025 16:21:57 +0200 Subject: [PATCH 02/51] Add JIT-EE boilerplate --- src/coreclr/inc/cordebuginfo.h | 35 ++++ src/coreclr/inc/corinfo.h | 10 ++ src/coreclr/inc/icorjitinfoimpl_generated.h | 6 + src/coreclr/inc/jiteeversionguid.h | 10 +- src/coreclr/jit/ICorJitInfo_names_generated.h | 1 + .../jit/ICorJitInfo_wrapper_generated.hpp | 11 ++ .../tools/Common/JitInterface/CorInfoImpl.cs | 9 ++ .../JitInterface/CorInfoImpl_generated.cs | 153 ++++++++++-------- .../tools/Common/JitInterface/CorInfoTypes.cs | 35 ++++ .../ThunkGenerator/ThunkInput.txt | 4 + .../aot/jitinterface/jitinterface_generated.h | 12 ++ .../superpmi-shim-collector/icorjitinfo.cpp | 10 ++ .../icorjitinfo_generated.cpp | 10 ++ .../icorjitinfo_generated.cpp | 9 ++ .../tools/superpmi/superpmi/icorjitinfo.cpp | 13 ++ src/coreclr/vm/jitinterface.cpp | 45 ++++++ src/coreclr/vm/jitinterface.h | 21 +++ 17 files changed, 320 insertions(+), 74 deletions(-) diff --git a/src/coreclr/inc/cordebuginfo.h b/src/coreclr/inc/cordebuginfo.h index b0e813dffd3ab9..03fe0d9d463cd6 100644 --- a/src/coreclr/inc/cordebuginfo.h +++ b/src/coreclr/inc/cordebuginfo.h @@ -431,4 +431,39 @@ class ICorDebugInfo // Source information about the IL instruction in the inlinee SourceTypes Source; }; + + struct AsyncContinuationVarInfo + { + // IL number of variable (or one of the special IL numbers, like UNKNOWN_ILNUM) + uint32_t VarNumber; + // Offset in continuation's data where this variable is stored + uint32_t Offset; + }; + + struct AsyncSuspensionPoint + { + // State number assigned to this suspension point. UINT_MAX if no state + // number is assigned (when AsyncInfo::NumSuspensionPoints == 1). + uint32_t StateNumber; + // IL offset in the root method that resulted in the creation of this suspension point. + uint32_t RootILOffset; + // Index of inline tree node containing the IL offset (0 for root) + uint32_t Inlinee; + // IL offset that resulted in the creation of the suspension point. + uint32_t ILOffset; + uint32_t NumVars; + // Count of AsyncContinuationVarInfo + uint32_t NumContinuationVars; + // Index into array of AsyncContinuationVarInfo of first variable + uint32_t ContinuationVarsIndex; + }; + + struct AsyncInfo + { + // Offset in contiuation's data where state number is stored. UINT_MAX + // if no state number is stored (when NumSuspensionPoints == 1). + uint32_t StateOffset; + // Number of suspension points in the method. + uint32_t NumSuspensionPoints; + }; }; diff --git a/src/coreclr/inc/corinfo.h b/src/coreclr/inc/corinfo.h index b0d0108a31dffe..528b12f801fdb8 100644 --- a/src/coreclr/inc/corinfo.h +++ b/src/coreclr/inc/corinfo.h @@ -2912,6 +2912,16 @@ class ICorStaticInfo uint32_t numMappings // [IN] Number of rich mappings ) = 0; + // Report async debug information to EE. + // The arrays are expected to be allocated with allocateArray + // and ownership is transferred to the EE with this call. + virtual void reportAsyncDebugInfo( + ICorDebugInfo::AsyncInfo* asyncInfo, // [IN] Async method information + ICorDebugInfo::AsyncSuspensionPoint* suspensionPoints, // [IN] Async suspension points + ICorDebugInfo::AsyncContinuationVarInfo* vars, // [IN] Async continuation variable info + uint32_t numVars // [IN] Number of continuation variables + ) = 0; + // Report back some metadata about the compilation to the EE -- for // example, metrics about the compilation. virtual void reportMetadata( diff --git a/src/coreclr/inc/icorjitinfoimpl_generated.h b/src/coreclr/inc/icorjitinfoimpl_generated.h index 6f4908d9c05235..d20a1a39698a65 100644 --- a/src/coreclr/inc/icorjitinfoimpl_generated.h +++ b/src/coreclr/inc/icorjitinfoimpl_generated.h @@ -453,6 +453,12 @@ void reportRichMappings( ICorDebugInfo::RichOffsetMapping* mappings, uint32_t numMappings) override; +void reportAsyncDebugInfo( + ICorDebugInfo::AsyncInfo* asyncInfo, + ICorDebugInfo::AsyncSuspensionPoint* suspensionPoints, + ICorDebugInfo::AsyncContinuationVarInfo* vars, + uint32_t numVars) override; + void reportMetadata( const char* key, const void* value, diff --git a/src/coreclr/inc/jiteeversionguid.h b/src/coreclr/inc/jiteeversionguid.h index e1fda2eeb3cbb0..64cb2e4f1d8c61 100644 --- a/src/coreclr/inc/jiteeversionguid.h +++ b/src/coreclr/inc/jiteeversionguid.h @@ -37,11 +37,11 @@ #include -constexpr GUID JITEEVersionIdentifier = { /* 3d2bdd20-eced-4a07-b9fb-227ce7f55fcd */ - 0x3d2bdd20, - 0xeced, - 0x4a07, - {0xb9, 0xfb, 0x22, 0x7c, 0xe7, 0xf5, 0x5f, 0xcd} +constexpr GUID JITEEVersionIdentifier = { /* e1c4ef4f-62a1-4055-813e-23837fdfdffa */ + 0xe1c4ef4f, + 0x62a1, + 0x4055, + {0x81, 0x3e, 0x23, 0x83, 0x7f, 0xdf, 0xdf, 0xfa} }; #endif // JIT_EE_VERSIONING_GUID_H diff --git a/src/coreclr/jit/ICorJitInfo_names_generated.h b/src/coreclr/jit/ICorJitInfo_names_generated.h index 1e455eb98bc74e..7a0e5b39c718cb 100644 --- a/src/coreclr/jit/ICorJitInfo_names_generated.h +++ b/src/coreclr/jit/ICorJitInfo_names_generated.h @@ -112,6 +112,7 @@ DEF_CLR_API(setBoundaries) DEF_CLR_API(getVars) DEF_CLR_API(setVars) DEF_CLR_API(reportRichMappings) +DEF_CLR_API(reportAsyncDebugInfo) DEF_CLR_API(reportMetadata) DEF_CLR_API(allocateArray) DEF_CLR_API(freeArray) diff --git a/src/coreclr/jit/ICorJitInfo_wrapper_generated.hpp b/src/coreclr/jit/ICorJitInfo_wrapper_generated.hpp index 71dc8eaf7119af..e600aa12f90ebe 100644 --- a/src/coreclr/jit/ICorJitInfo_wrapper_generated.hpp +++ b/src/coreclr/jit/ICorJitInfo_wrapper_generated.hpp @@ -1067,6 +1067,17 @@ void WrapICorJitInfo::reportRichMappings( API_LEAVE(reportRichMappings); } +void WrapICorJitInfo::reportAsyncDebugInfo( + ICorDebugInfo::AsyncInfo* asyncInfo, + ICorDebugInfo::AsyncSuspensionPoint* suspensionPoints, + ICorDebugInfo::AsyncContinuationVarInfo* vars, + uint32_t numVars) +{ + API_ENTER(reportAsyncDebugInfo); + wrapHnd->reportAsyncDebugInfo(asyncInfo, suspensionPoints, vars, numVars); + API_LEAVE(reportAsyncDebugInfo); +} + void WrapICorJitInfo::reportMetadata( const char* key, const void* value, diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs index c0bd2516d730bf..2a8ce050d7329d 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs @@ -3215,6 +3215,15 @@ private void reportRichMappings(InlineTreeNode* inlineTree, uint numInlineTree, Marshal.FreeHGlobal((IntPtr)mappings); } +#pragma warning disable CA1822 // Mark members as static + private void reportAsyncDebugInfo(AsyncInfo* asyncInfo, AsyncSuspensionPoint* suspensionPoints, AsyncContinuationVarInfo* vars, uint numVars) +#pragma warning restore CA1822 // Mark members as static + { + Marshal.FreeHGlobal((IntPtr)asyncInfo); + Marshal.FreeHGlobal((IntPtr)suspensionPoints); + Marshal.FreeHGlobal((IntPtr)vars); + } + #pragma warning disable CA1822 // Mark members as static private void reportMetadata(byte* key, void* value, nuint length) #pragma warning restore CA1822 // Mark members as static diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoImpl_generated.cs b/src/coreclr/tools/Common/JitInterface/CorInfoImpl_generated.cs index 8d0c83046277dd..9ff76dddc4462b 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoImpl_generated.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoImpl_generated.cs @@ -1607,6 +1607,20 @@ private static void _reportRichMappings(IntPtr thisHandle, IntPtr* ppException, } } + [UnmanagedCallersOnly] + private static void _reportAsyncDebugInfo(IntPtr thisHandle, IntPtr* ppException, AsyncInfo* asyncInfo, AsyncSuspensionPoint* suspensionPoints, AsyncContinuationVarInfo* vars, uint numVars) + { + var _this = GetThis(thisHandle); + try + { + _this.reportAsyncDebugInfo(asyncInfo, suspensionPoints, vars, numVars); + } + catch (Exception ex) + { + *ppException = _this.AllocException(ex); + } + } + [UnmanagedCallersOnly] private static void _reportMetadata(IntPtr thisHandle, IntPtr* ppException, byte* key, void* value, UIntPtr length) { @@ -2606,7 +2620,7 @@ private static uint _getJitFlags(IntPtr thisHandle, IntPtr* ppException, CORJIT_ private static IntPtr GetUnmanagedCallbacks() { - void** callbacks = (void**)Marshal.AllocCoTaskMem(sizeof(IntPtr) * 176); + void** callbacks = (void**)Marshal.AllocCoTaskMem(sizeof(IntPtr) * 177); callbacks[0] = (delegate* unmanaged)&_isIntrinsic; callbacks[1] = (delegate* unmanaged)&_notifyMethodInfoUsage; @@ -2716,74 +2730,75 @@ private static IntPtr GetUnmanagedCallbacks() callbacks[105] = (delegate* unmanaged)&_getVars; callbacks[106] = (delegate* unmanaged)&_setVars; callbacks[107] = (delegate* unmanaged)&_reportRichMappings; - callbacks[108] = (delegate* unmanaged)&_reportMetadata; - callbacks[109] = (delegate* unmanaged)&_allocateArray; - callbacks[110] = (delegate* unmanaged)&_freeArray; - callbacks[111] = (delegate* unmanaged)&_getArgNext; - callbacks[112] = (delegate* unmanaged)&_getArgType; - callbacks[113] = (delegate* unmanaged)&_getExactClasses; - callbacks[114] = (delegate* unmanaged)&_getArgClass; - callbacks[115] = (delegate* unmanaged)&_getHFAType; - callbacks[116] = (delegate* unmanaged)&_runWithErrorTrap; - callbacks[117] = (delegate* unmanaged)&_runWithSPMIErrorTrap; - callbacks[118] = (delegate* unmanaged)&_getEEInfo; - callbacks[119] = (delegate* unmanaged)&_getAsyncInfo; - callbacks[120] = (delegate* unmanaged)&_getMethodDefFromMethod; - callbacks[121] = (delegate* unmanaged)&_printMethodName; - callbacks[122] = (delegate* unmanaged)&_getMethodNameFromMetadata; - callbacks[123] = (delegate* unmanaged)&_getMethodHash; - callbacks[124] = (delegate* unmanaged)&_getSystemVAmd64PassStructInRegisterDescriptor; - callbacks[125] = (delegate* unmanaged)&_getSwiftLowering; - callbacks[126] = (delegate* unmanaged)&_getFpStructLowering; - callbacks[127] = (delegate* unmanaged)&_getThreadTLSIndex; - callbacks[128] = (delegate* unmanaged)&_getAddrOfCaptureThreadGlobal; - callbacks[129] = (delegate* unmanaged)&_getHelperFtn; - callbacks[130] = (delegate* unmanaged)&_getFunctionEntryPoint; - callbacks[131] = (delegate* unmanaged)&_getFunctionFixedEntryPoint; - callbacks[132] = (delegate* unmanaged)&_getLazyStringLiteralHelper; - callbacks[133] = (delegate* unmanaged)&_embedModuleHandle; - callbacks[134] = (delegate* unmanaged)&_embedClassHandle; - callbacks[135] = (delegate* unmanaged)&_embedMethodHandle; - callbacks[136] = (delegate* unmanaged)&_embedFieldHandle; - callbacks[137] = (delegate* unmanaged)&_embedGenericHandle; - callbacks[138] = (delegate* unmanaged)&_getLocationOfThisType; - callbacks[139] = (delegate* unmanaged)&_getAddressOfPInvokeTarget; - callbacks[140] = (delegate* unmanaged)&_GetCookieForPInvokeCalliSig; - callbacks[141] = (delegate* unmanaged)&_GetCookieForInterpreterCalliSig; - callbacks[142] = (delegate* unmanaged)&_getJustMyCodeHandle; - callbacks[143] = (delegate* unmanaged)&_GetProfilingHandle; - callbacks[144] = (delegate* unmanaged)&_getCallInfo; - callbacks[145] = (delegate* unmanaged)&_getStaticFieldContent; - callbacks[146] = (delegate* unmanaged)&_getObjectContent; - callbacks[147] = (delegate* unmanaged)&_getStaticFieldCurrentClass; - callbacks[148] = (delegate* unmanaged)&_getVarArgsHandle; - callbacks[149] = (delegate* unmanaged)&_constructStringLiteral; - callbacks[150] = (delegate* unmanaged)&_emptyStringLiteral; - callbacks[151] = (delegate* unmanaged)&_getFieldThreadLocalStoreID; - callbacks[152] = (delegate* unmanaged)&_GetDelegateCtor; - callbacks[153] = (delegate* unmanaged)&_MethodCompileComplete; - callbacks[154] = (delegate* unmanaged)&_getTailCallHelpers; - callbacks[155] = (delegate* unmanaged)&_getAsyncResumptionStub; - callbacks[156] = (delegate* unmanaged)&_convertPInvokeCalliToCall; - callbacks[157] = (delegate* unmanaged)&_notifyInstructionSetUsage; - callbacks[158] = (delegate* unmanaged)&_updateEntryPointForTailCall; - callbacks[159] = (delegate* unmanaged)&_allocMem; - callbacks[160] = (delegate* unmanaged)&_reserveUnwindInfo; - callbacks[161] = (delegate* unmanaged)&_allocUnwindInfo; - callbacks[162] = (delegate* unmanaged)&_allocGCInfo; - callbacks[163] = (delegate* unmanaged)&_setEHcount; - callbacks[164] = (delegate* unmanaged)&_setEHinfo; - callbacks[165] = (delegate* unmanaged)&_logMsg; - callbacks[166] = (delegate* unmanaged)&_doAssert; - callbacks[167] = (delegate* unmanaged)&_reportFatalError; - callbacks[168] = (delegate* unmanaged)&_getPgoInstrumentationResults; - callbacks[169] = (delegate* unmanaged)&_allocPgoInstrumentationBySchema; - callbacks[170] = (delegate* unmanaged)&_recordCallSite; - callbacks[171] = (delegate* unmanaged)&_recordRelocation; - callbacks[172] = (delegate* unmanaged)&_getRelocTypeHint; - callbacks[173] = (delegate* unmanaged)&_getExpectedTargetArchitecture; - callbacks[174] = (delegate* unmanaged)&_getJitFlags; - callbacks[175] = (delegate* unmanaged)&_getSpecialCopyHelper; + callbacks[108] = (delegate* unmanaged)&_reportAsyncDebugInfo; + callbacks[109] = (delegate* unmanaged)&_reportMetadata; + callbacks[110] = (delegate* unmanaged)&_allocateArray; + callbacks[111] = (delegate* unmanaged)&_freeArray; + callbacks[112] = (delegate* unmanaged)&_getArgNext; + callbacks[113] = (delegate* unmanaged)&_getArgType; + callbacks[114] = (delegate* unmanaged)&_getExactClasses; + callbacks[115] = (delegate* unmanaged)&_getArgClass; + callbacks[116] = (delegate* unmanaged)&_getHFAType; + callbacks[117] = (delegate* unmanaged)&_runWithErrorTrap; + callbacks[118] = (delegate* unmanaged)&_runWithSPMIErrorTrap; + callbacks[119] = (delegate* unmanaged)&_getEEInfo; + callbacks[120] = (delegate* unmanaged)&_getAsyncInfo; + callbacks[121] = (delegate* unmanaged)&_getMethodDefFromMethod; + callbacks[122] = (delegate* unmanaged)&_printMethodName; + callbacks[123] = (delegate* unmanaged)&_getMethodNameFromMetadata; + callbacks[124] = (delegate* unmanaged)&_getMethodHash; + callbacks[125] = (delegate* unmanaged)&_getSystemVAmd64PassStructInRegisterDescriptor; + callbacks[126] = (delegate* unmanaged)&_getSwiftLowering; + callbacks[127] = (delegate* unmanaged)&_getFpStructLowering; + callbacks[128] = (delegate* unmanaged)&_getThreadTLSIndex; + callbacks[129] = (delegate* unmanaged)&_getAddrOfCaptureThreadGlobal; + callbacks[130] = (delegate* unmanaged)&_getHelperFtn; + callbacks[131] = (delegate* unmanaged)&_getFunctionEntryPoint; + callbacks[132] = (delegate* unmanaged)&_getFunctionFixedEntryPoint; + callbacks[133] = (delegate* unmanaged)&_getLazyStringLiteralHelper; + callbacks[134] = (delegate* unmanaged)&_embedModuleHandle; + callbacks[135] = (delegate* unmanaged)&_embedClassHandle; + callbacks[136] = (delegate* unmanaged)&_embedMethodHandle; + callbacks[137] = (delegate* unmanaged)&_embedFieldHandle; + callbacks[138] = (delegate* unmanaged)&_embedGenericHandle; + callbacks[139] = (delegate* unmanaged)&_getLocationOfThisType; + callbacks[140] = (delegate* unmanaged)&_getAddressOfPInvokeTarget; + callbacks[141] = (delegate* unmanaged)&_GetCookieForPInvokeCalliSig; + callbacks[142] = (delegate* unmanaged)&_GetCookieForInterpreterCalliSig; + callbacks[143] = (delegate* unmanaged)&_getJustMyCodeHandle; + callbacks[144] = (delegate* unmanaged)&_GetProfilingHandle; + callbacks[145] = (delegate* unmanaged)&_getCallInfo; + callbacks[146] = (delegate* unmanaged)&_getStaticFieldContent; + callbacks[147] = (delegate* unmanaged)&_getObjectContent; + callbacks[148] = (delegate* unmanaged)&_getStaticFieldCurrentClass; + callbacks[149] = (delegate* unmanaged)&_getVarArgsHandle; + callbacks[150] = (delegate* unmanaged)&_constructStringLiteral; + callbacks[151] = (delegate* unmanaged)&_emptyStringLiteral; + callbacks[152] = (delegate* unmanaged)&_getFieldThreadLocalStoreID; + callbacks[153] = (delegate* unmanaged)&_GetDelegateCtor; + callbacks[154] = (delegate* unmanaged)&_MethodCompileComplete; + callbacks[155] = (delegate* unmanaged)&_getTailCallHelpers; + callbacks[156] = (delegate* unmanaged)&_getAsyncResumptionStub; + callbacks[157] = (delegate* unmanaged)&_convertPInvokeCalliToCall; + callbacks[158] = (delegate* unmanaged)&_notifyInstructionSetUsage; + callbacks[159] = (delegate* unmanaged)&_updateEntryPointForTailCall; + callbacks[160] = (delegate* unmanaged)&_allocMem; + callbacks[161] = (delegate* unmanaged)&_reserveUnwindInfo; + callbacks[162] = (delegate* unmanaged)&_allocUnwindInfo; + callbacks[163] = (delegate* unmanaged)&_allocGCInfo; + callbacks[164] = (delegate* unmanaged)&_setEHcount; + callbacks[165] = (delegate* unmanaged)&_setEHinfo; + callbacks[166] = (delegate* unmanaged)&_logMsg; + callbacks[167] = (delegate* unmanaged)&_doAssert; + callbacks[168] = (delegate* unmanaged)&_reportFatalError; + callbacks[169] = (delegate* unmanaged)&_getPgoInstrumentationResults; + callbacks[170] = (delegate* unmanaged)&_allocPgoInstrumentationBySchema; + callbacks[171] = (delegate* unmanaged)&_recordCallSite; + callbacks[172] = (delegate* unmanaged)&_recordRelocation; + callbacks[173] = (delegate* unmanaged)&_getRelocTypeHint; + callbacks[174] = (delegate* unmanaged)&_getExpectedTargetArchitecture; + callbacks[175] = (delegate* unmanaged)&_getJitFlags; + callbacks[176] = (delegate* unmanaged)&_getSpecialCopyHelper; return (IntPtr)callbacks; } diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs b/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs index 2ae78da1fde8f0..e7e11c79095109 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs @@ -1319,6 +1319,41 @@ public struct RichOffsetMapping public SourceTypes Source; } + public struct AsyncContinuationVarInfo + { + // IL number of variable (or one of the special IL numbers, like UNKNOWN_ILNUM) + public uint VarNumber; + // Offset in continuation's data where this variable is stored + public uint Offset; + } + + public struct AsyncSuspensionPoint + { + // State number assigned to this suspension point. UINT_MAX if no state + // number is assigned (when AsyncInfo::NumSuspensionPoints == 1). + public uint StateNumber; + // IL offset in the root method that resulted in the creation of this suspension point. + public uint RootILOffset; + // Index of inline tree node containing the IL offset (0 for root) + public uint Inlinee; + // IL offset that resulted in the creation of the suspension point. + public uint ILOffset; + public uint NumVars; + // Count of AsyncContinuationVarInfo + public uint NumContinuationVars; + // Index into array of AsyncContinuationVarInfo of first variable + public uint ContinuationVarsIndex; + } + + public struct AsyncInfo + { + // Offset in contiuation's data where state number is stored. UINT_MAX + // if no state number is stored (when NumSuspensionPoints == 1). + public uint StateOffset; + // Number of suspension points in the method. + public uint NumSuspensionPoints; + } + // This enum is used for JIT to tell EE where this token comes from. // E.g. Depending on different opcodes, we might allow/disallow certain types of tokens or // return different types of handles (e.g. boxed vs. regular entrypoints) diff --git a/src/coreclr/tools/Common/JitInterface/ThunkGenerator/ThunkInput.txt b/src/coreclr/tools/Common/JitInterface/ThunkGenerator/ThunkInput.txt index 4c4945475399c1..7bb7bce49ffc04 100644 --- a/src/coreclr/tools/Common/JitInterface/ThunkGenerator/ThunkInput.txt +++ b/src/coreclr/tools/Common/JitInterface/ThunkGenerator/ThunkInput.txt @@ -161,6 +161,9 @@ ICorDebugInfo::NativeVarInfo*,NativeVarInfo* ICorDebugInfo::BoundaryTypes*,BoundaryTypes* ICorDebugInfo::InlineTreeNode*,InlineTreeNode* ICorDebugInfo::RichOffsetMapping*,RichOffsetMapping* +ICorDebugInfo::AsyncInfo*,AsyncInfo* +ICorDebugInfo::AsyncSuspensionPoint*,AsyncSuspensionPoint* +ICorDebugInfo::AsyncContinuationVarInfo*,AsyncContinuationVarInfo* struct _EXCEPTION_POINTERS*,_EXCEPTION_POINTERS* ICorJitInfo::errorTrapFunction,void* @@ -274,6 +277,7 @@ FUNCTIONS void getVars(CORINFO_METHOD_HANDLE ftn, uint32_t* cVars, ICorDebugInfo::ILVarInfo** vars, bool* extendOthers) void setVars(CORINFO_METHOD_HANDLE ftn, uint32_t cVars, ICorDebugInfo::NativeVarInfo* vars) void reportRichMappings(ICorDebugInfo::InlineTreeNode* inlineTreeNodes, uint32_t numInlineTreeNodes, ICorDebugInfo::RichOffsetMapping* mappings, uint32_t numMappings) + void reportAsyncDebugInfo(ICorDebugInfo::AsyncInfo* asyncInfo, ICorDebugInfo::AsyncSuspensionPoint* suspensionPoints, ICorDebugInfo::AsyncContinuationVarInfo* vars, uint32_t numVars) void reportMetadata(const char* key, const void* value, size_t length) void*allocateArray(size_t cBytes); void freeArray(void*array); diff --git a/src/coreclr/tools/aot/jitinterface/jitinterface_generated.h b/src/coreclr/tools/aot/jitinterface/jitinterface_generated.h index 4069f29308dadd..57ffa7277e9ebf 100644 --- a/src/coreclr/tools/aot/jitinterface/jitinterface_generated.h +++ b/src/coreclr/tools/aot/jitinterface/jitinterface_generated.h @@ -119,6 +119,7 @@ struct JitInterfaceCallbacks void (* getVars)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_METHOD_HANDLE ftn, uint32_t* cVars, ICorDebugInfo::ILVarInfo** vars, bool* extendOthers); void (* setVars)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_METHOD_HANDLE ftn, uint32_t cVars, ICorDebugInfo::NativeVarInfo* vars); void (* reportRichMappings)(void * thisHandle, CorInfoExceptionClass** ppException, ICorDebugInfo::InlineTreeNode* inlineTreeNodes, uint32_t numInlineTreeNodes, ICorDebugInfo::RichOffsetMapping* mappings, uint32_t numMappings); + void (* reportAsyncDebugInfo)(void * thisHandle, CorInfoExceptionClass** ppException, ICorDebugInfo::AsyncInfo* asyncInfo, ICorDebugInfo::AsyncSuspensionPoint* suspensionPoints, ICorDebugInfo::AsyncContinuationVarInfo* vars, uint32_t numVars); void (* reportMetadata)(void * thisHandle, CorInfoExceptionClass** ppException, const char* key, const void* value, size_t length); void* (* allocateArray)(void * thisHandle, CorInfoExceptionClass** ppException, size_t cBytes); void (* freeArray)(void * thisHandle, CorInfoExceptionClass** ppException, void* array); @@ -1257,6 +1258,17 @@ class JitInterfaceWrapper : public ICorJitInfo if (pException != nullptr) throw pException; } + virtual void reportAsyncDebugInfo( + ICorDebugInfo::AsyncInfo* asyncInfo, + ICorDebugInfo::AsyncSuspensionPoint* suspensionPoints, + ICorDebugInfo::AsyncContinuationVarInfo* vars, + uint32_t numVars) +{ + CorInfoExceptionClass* pException = nullptr; + _callbacks->reportAsyncDebugInfo(_thisHandle, &pException, asyncInfo, suspensionPoints, vars, numVars); + if (pException != nullptr) throw pException; +} + virtual void reportMetadata( const char* key, const void* value, diff --git a/src/coreclr/tools/superpmi/superpmi-shim-collector/icorjitinfo.cpp b/src/coreclr/tools/superpmi/superpmi-shim-collector/icorjitinfo.cpp index 0a6118d73ab862..7fc8560a236310 100644 --- a/src/coreclr/tools/superpmi/superpmi-shim-collector/icorjitinfo.cpp +++ b/src/coreclr/tools/superpmi/superpmi-shim-collector/icorjitinfo.cpp @@ -1233,6 +1233,16 @@ void interceptor_ICJI::reportRichMappings(ICorDebugInfo::InlineTreeNode* inli original_ICorJitInfo->reportRichMappings(inlineTreeNodes, numInlineTreeNodes, mappings, numMappings); } +void interceptor_ICJI::reportAsyncDebugInfo(ICorDebugInfo::AsyncInfo* asyncInfo, + ICorDebugInfo::AsyncSuspensionPoint* suspensionPoints, + ICorDebugInfo::AsyncContinuationVarInfo* vars, + uint32_t numVars) +{ + mc->cr->AddCall("reportAsyncDebugInfo"); + // TODO: record async debug info + original_ICorJitInfo->reportAsyncDebugInfo(asyncInfo, suspensionPoints, vars, numVars); +} + void interceptor_ICJI::reportMetadata(const char* key, const void* value, size_t length) { mc->cr->AddCall("reportMetadata"); diff --git a/src/coreclr/tools/superpmi/superpmi-shim-counter/icorjitinfo_generated.cpp b/src/coreclr/tools/superpmi/superpmi-shim-counter/icorjitinfo_generated.cpp index ac148df7c4e295..c06ed0b9e45e9f 100644 --- a/src/coreclr/tools/superpmi/superpmi-shim-counter/icorjitinfo_generated.cpp +++ b/src/coreclr/tools/superpmi/superpmi-shim-counter/icorjitinfo_generated.cpp @@ -876,6 +876,16 @@ void interceptor_ICJI::reportRichMappings( original_ICorJitInfo->reportRichMappings(inlineTreeNodes, numInlineTreeNodes, mappings, numMappings); } +void interceptor_ICJI::reportAsyncDebugInfo( + ICorDebugInfo::AsyncInfo* asyncInfo, + ICorDebugInfo::AsyncSuspensionPoint* suspensionPoints, + ICorDebugInfo::AsyncContinuationVarInfo* vars, + uint32_t numVars) +{ + mcs->AddCall("reportAsyncDebugInfo"); + original_ICorJitInfo->reportAsyncDebugInfo(asyncInfo, suspensionPoints, vars, numVars); +} + void interceptor_ICJI::reportMetadata( const char* key, const void* value, diff --git a/src/coreclr/tools/superpmi/superpmi-shim-simple/icorjitinfo_generated.cpp b/src/coreclr/tools/superpmi/superpmi-shim-simple/icorjitinfo_generated.cpp index 1f47ea740c0c48..18516e58feef20 100644 --- a/src/coreclr/tools/superpmi/superpmi-shim-simple/icorjitinfo_generated.cpp +++ b/src/coreclr/tools/superpmi/superpmi-shim-simple/icorjitinfo_generated.cpp @@ -768,6 +768,15 @@ void interceptor_ICJI::reportRichMappings( original_ICorJitInfo->reportRichMappings(inlineTreeNodes, numInlineTreeNodes, mappings, numMappings); } +void interceptor_ICJI::reportAsyncDebugInfo( + ICorDebugInfo::AsyncInfo* asyncInfo, + ICorDebugInfo::AsyncSuspensionPoint* suspensionPoints, + ICorDebugInfo::AsyncContinuationVarInfo* vars, + uint32_t numVars) +{ + original_ICorJitInfo->reportAsyncDebugInfo(asyncInfo, suspensionPoints, vars, numVars); +} + void interceptor_ICJI::reportMetadata( const char* key, const void* value, diff --git a/src/coreclr/tools/superpmi/superpmi/icorjitinfo.cpp b/src/coreclr/tools/superpmi/superpmi/icorjitinfo.cpp index a1b207c3de797e..af4107d01b1079 100644 --- a/src/coreclr/tools/superpmi/superpmi/icorjitinfo.cpp +++ b/src/coreclr/tools/superpmi/superpmi/icorjitinfo.cpp @@ -1057,6 +1057,19 @@ void MyICJI::reportRichMappings( freeArray(mappings); } +void MyICJI::reportAsyncDebugInfo( + ICorDebugInfo::AsyncInfo* asyncInfo, + ICorDebugInfo::AsyncSuspensionPoint* suspensionPoints, + ICorDebugInfo::AsyncContinuationVarInfo* vars, + uint32_t numVars) +{ + jitInstance->mc->cr->AddCall("reportAsyncDebugInfo"); + // TODO: record async debug info + freeArray(asyncInfo); + freeArray(suspensionPoints); + freeArray(vars); +} + void MyICJI::reportMetadata(const char* key, const void* value, size_t length) { jitInstance->mc->cr->AddCall("reportMetadata"); diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index f31b650dc9f349..a28bb895fd481e 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -10726,6 +10726,10 @@ CEECodeGenInfo::CEECodeGenInfo(PrepareCodeConfig* config, MethodDesc* fd, COR_IL , m_numInlineTreeNodes(0) , m_richOffsetMappings(NULL) , m_numRichOffsetMappings(0) + , m_asyncInfo(NULL) + , m_asyncSuspensionPoints(NULL) + , m_asyncContinuationVars(NULL) + , m_numAsyncContinuationVars(0) , m_gphCache() { STANDARD_VM_CONTRACT; @@ -11169,6 +11173,37 @@ void CEECodeGenInfo::reportRichMappings( EE_TO_JIT_TRANSITION(); } +void CEECodeGenInfo::reportAsyncDebugInfo( + ICorDebugInfo::AsyncInfo* asyncInfo, + ICorDebugInfo::AsyncSuspensionPoint* suspensionPoints, + ICorDebugInfo::AsyncContinuationVarInfo* vars, + uint32_t numVars) +{ + CONTRACTL { + NOTHROW; + GC_NOTRIGGER; + MODE_PREEMPTIVE; + } CONTRACTL_END; + + JIT_TO_EE_TRANSITION(); + + if (((EECodeGenManager*)m_jitManager)->IsStoringRichDebugInfo()) + { + m_asyncInfo = asyncInfo; + m_asyncSuspensionPoints = suspensionPoints; + m_asyncContinuationVars = vars; + m_numAsyncContinuationVars = numVars; + } + else + { + freeArrayInternal(asyncInfo); + freeArrayInternal(suspensionPoints); + freeArrayInternal(vars); + } + + EE_TO_JIT_TRANSITION(); +} + void CEECodeGenInfo::reportMetadata( const char* key, const void* value, @@ -15014,6 +15049,16 @@ void CEEInfo::reportRichMappings( UNREACHABLE(); // only called on derived class. } +void CEEInfo::reportAsyncDebugInfo( + ICorDebugInfo::AsyncInfo* asyncInfo, + ICorDebugInfo::AsyncSuspensionPoint* suspensionPoints, + ICorDebugInfo::AsyncContinuationVarInfo* vars, + uint32_t numVars) +{ + LIMITED_METHOD_CONTRACT; + UNREACHABLE(); // only called on derived class. +} + void CEEInfo::reportMetadata(const char* key, const void* value, size_t length) { LIMITED_METHOD_CONTRACT; diff --git a/src/coreclr/vm/jitinterface.h b/src/coreclr/vm/jitinterface.h index 460c7e7f3fdd5a..11551c0bd20f71 100644 --- a/src/coreclr/vm/jitinterface.h +++ b/src/coreclr/vm/jitinterface.h @@ -531,11 +531,21 @@ class CEECodeGenInfo : public CEEInfo freeArrayInternal(m_inlineTreeNodes); if (m_richOffsetMappings != NULL) freeArrayInternal(m_richOffsetMappings); + if (m_asyncInfo != NULL) + freeArrayInternal(m_asyncInfo); + if (m_asyncSuspensionPoints != NULL) + freeArrayInternal(m_asyncSuspensionPoints); + if (m_asyncContinuationVars != NULL) + freeArrayInternal(m_asyncContinuationVars); m_inlineTreeNodes = NULL; m_numInlineTreeNodes = 0; m_richOffsetMappings = NULL; m_numRichOffsetMappings = 0; + m_asyncInfo = NULL; + m_asyncSuspensionPoints = NULL; + m_asyncContinuationVars = NULL; + m_numAsyncContinuationVars = 0; } // ICorDebugInfo stuff. @@ -562,6 +572,12 @@ class CEECodeGenInfo : public CEEInfo ICorDebugInfo::RichOffsetMapping* mappings, uint32_t numMappings) override final; + void reportAsyncDebugInfo( + ICorDebugInfo::AsyncInfo* asyncInfo, + ICorDebugInfo::AsyncSuspensionPoint* suspensionPoints, + ICorDebugInfo::AsyncContinuationVarInfo* vars, + uint32_t numVars) override final; + void reportMetadata(const char* key, const void* value, size_t length) override final; virtual void WriteCode(EECodeGenManager * jitMgr) = 0; @@ -625,6 +641,11 @@ class CEECodeGenInfo : public CEEInfo ICorDebugInfo::RichOffsetMapping *m_richOffsetMappings; ULONG32 m_numRichOffsetMappings; + ICorDebugInfo::AsyncInfo *m_asyncInfo; + ICorDebugInfo::AsyncSuspensionPoint *m_asyncSuspensionPoints; + ICorDebugInfo::AsyncContinuationVarInfo *m_asyncContinuationVars; + ULONG32 m_numAsyncContinuationVars; + // The first time a call is made to CEEJitInfo::GetProfilingHandle() from this thread // for this method, these values are filled in. Thereafter, these values are used // in lieu of calling into the base CEEInfo::GetProfilingHandle() again. This protects the From d7277fb8f5304f2fecc696b56b9e59436b454845 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Wed, 1 Oct 2025 21:31:00 +0200 Subject: [PATCH 03/51] Add debug information for runtime async information - Add new JIT-EE API to report back debug information about the generated state machine and continuations - Refactor debug info storage on VM side to be more easily extensible. The new format has either a thin or fat header. The fat header is used when we have either uninstrumented bounds, patchpoint info, rich debug info or async debug info, and stores the blob sizes of all of those components in addition to the bounds and vars. - Add new async debug information to the storage on the VM side - Set get target method desc for async resumption stubs, to be used for mapping from continuations back to the async IL function that it will resume. --- src/coreclr/inc/cordebuginfo.h | 11 +- src/coreclr/inc/corinfo.h | 4 +- src/coreclr/jit/async.cpp | 74 +++ src/coreclr/jit/async.h | 9 + src/coreclr/jit/compiler.h | 2 +- src/coreclr/jit/gentree.h | 3 + src/coreclr/jit/importercalls.cpp | 11 +- .../tools/Common/JitInterface/CorInfoTypes.cs | 3 - .../tools/superpmi/superpmi/icorjitinfo.cpp | 4 +- src/coreclr/vm/codeman.cpp | 30 +- src/coreclr/vm/debuginfostore.cpp | 422 +++++++++--------- src/coreclr/vm/debuginfostore.h | 59 ++- src/coreclr/vm/jitinterface.cpp | 37 +- src/coreclr/vm/jitinterface.h | 21 +- 14 files changed, 385 insertions(+), 305 deletions(-) diff --git a/src/coreclr/inc/cordebuginfo.h b/src/coreclr/inc/cordebuginfo.h index 03fe0d9d463cd6..a90a0289df50f1 100644 --- a/src/coreclr/inc/cordebuginfo.h +++ b/src/coreclr/inc/cordebuginfo.h @@ -442,27 +442,18 @@ class ICorDebugInfo struct AsyncSuspensionPoint { - // State number assigned to this suspension point. UINT_MAX if no state - // number is assigned (when AsyncInfo::NumSuspensionPoints == 1). - uint32_t StateNumber; // IL offset in the root method that resulted in the creation of this suspension point. uint32_t RootILOffset; // Index of inline tree node containing the IL offset (0 for root) uint32_t Inlinee; // IL offset that resulted in the creation of the suspension point. uint32_t ILOffset; - uint32_t NumVars; - // Count of AsyncContinuationVarInfo + // Count of AsyncContinuationVarInfo in array of locals uint32_t NumContinuationVars; - // Index into array of AsyncContinuationVarInfo of first variable - uint32_t ContinuationVarsIndex; }; struct AsyncInfo { - // Offset in contiuation's data where state number is stored. UINT_MAX - // if no state number is stored (when NumSuspensionPoints == 1). - uint32_t StateOffset; // Number of suspension points in the method. uint32_t NumSuspensionPoints; }; diff --git a/src/coreclr/inc/corinfo.h b/src/coreclr/inc/corinfo.h index 528b12f801fdb8..36b4ab4925f0f9 100644 --- a/src/coreclr/inc/corinfo.h +++ b/src/coreclr/inc/corinfo.h @@ -2917,8 +2917,8 @@ class ICorStaticInfo // and ownership is transferred to the EE with this call. virtual void reportAsyncDebugInfo( ICorDebugInfo::AsyncInfo* asyncInfo, // [IN] Async method information - ICorDebugInfo::AsyncSuspensionPoint* suspensionPoints, // [IN] Async suspension points - ICorDebugInfo::AsyncContinuationVarInfo* vars, // [IN] Async continuation variable info + ICorDebugInfo::AsyncSuspensionPoint* suspensionPoints, // [IN] Array of async suspension points, indexed by state number + ICorDebugInfo::AsyncContinuationVarInfo* vars, // [IN] Array of async continuation variable info uint32_t numVars // [IN] Number of continuation variables ) = 0; diff --git a/src/coreclr/jit/async.cpp b/src/coreclr/jit/async.cpp index ec92f9b23ca6a7..76288114c9866b 100644 --- a/src/coreclr/jit/async.cpp +++ b/src/coreclr/jit/async.cpp @@ -760,6 +760,8 @@ PhaseStatus AsyncTransformation::Run() m_comp->fgInvalidateDfsTree(); + ReportDebugInfo(); + return PhaseStatus::MODIFIED_EVERYTHING; } @@ -818,6 +820,8 @@ void AsyncTransformation::Transform( BasicBlock* resumeBB = CreateResumption(block, *remainder, call, callDefInfo, stateNum, layout); m_resumptionBBs.push_back(resumeBB); + + CreateDebugInfoForSuspensionPoint(call, layout); } //------------------------------------------------------------------------ @@ -2202,6 +2206,76 @@ GenTreeStoreInd* AsyncTransformation::StoreAtOffset(GenTree* base, unsigned offs return store; } +//------------------------------------------------------------------------ +// AsyncTransformation::CreateDebugInfoForSuspensionPoint: +// Create debug info for the specific suspension point we just created. +// +// Parameters: +// asyncCall - Call node resulting in the suspension point +// stateNum - State number that was assigned to the suspension point +// layout - Layout of continuation +// +void AsyncTransformation::CreateDebugInfoForSuspensionPoint(GenTreeCall* asyncCall, const ContinuationLayout& layout) +{ + if (!m_comp->opts.compDbgInfo) + { + return; + } + + uint32_t numLocals = 0; + for (const LiveLocalInfo& local : layout.Locals) + { + unsigned ilVarNum = m_comp->compMap2ILvarNum(local.LclNum); + if (ilVarNum == ICorDebugInfo::UNKNOWN_ILNUM) + { + continue; + } + + ICorDebugInfo::AsyncContinuationVarInfo varInf; + varInf.VarNumber = ilVarNum; + varInf.Offset = local.DataOffset; + m_dbgContinuationVars.push_back(varInf); + numLocals++; + } + + ICorDebugInfo::AsyncSuspensionPoint suspensionPoint; + const DebugInfo& di = asyncCall->GetAsyncInfo().DebugInfo; + suspensionPoint.RootILOffset = di.GetRoot().GetLocation().GetOffset(); + suspensionPoint.Inlinee = di.GetInlineContext()->GetOrdinal(); + suspensionPoint.ILOffset = di.GetLocation().GetOffset(); + suspensionPoint.NumContinuationVars = numLocals; + + m_dbgSuspensionPoints.push_back(suspensionPoint); +} + +//------------------------------------------------------------------------ +// AsyncTransformation::ReportDebugInfo: +// Report debug info back to EE. +// +void AsyncTransformation::ReportDebugInfo() +{ + if (!m_comp->opts.compDbgInfo) + { + return; + } + + ICorDebugInfo::AsyncInfo asyncInfo; + asyncInfo.NumSuspensionPoints = static_cast(m_dbgSuspensionPoints.size()); + + ICorDebugInfo::AsyncSuspensionPoint* hostSuspensionPoints = + static_cast(m_comp->info.compCompHnd->allocateArray( + m_dbgSuspensionPoints.size() * sizeof(ICorDebugInfo::AsyncSuspensionPoint))); + std::copy(m_dbgSuspensionPoints.begin(), m_dbgSuspensionPoints.end(), hostSuspensionPoints); + + ICorDebugInfo::AsyncContinuationVarInfo* hostVars = + static_cast(m_comp->info.compCompHnd->allocateArray( + m_dbgContinuationVars.size() * sizeof(ICorDebugInfo::AsyncContinuationVarInfo))); + std::copy(m_dbgContinuationVars.begin(), m_dbgContinuationVars.end(), hostVars); + + m_comp->info.compCompHnd->reportAsyncDebugInfo(&asyncInfo, hostSuspensionPoints, hostVars, + static_cast(m_dbgContinuationVars.size())); +} + //------------------------------------------------------------------------ // AsyncTransformation::GetDataArrayVar: // Create a new local to hold the data array of the continuation object. This diff --git a/src/coreclr/jit/async.h b/src/coreclr/jit/async.h index e30aaf760e6395..b8c0459ef9055e 100644 --- a/src/coreclr/jit/async.h +++ b/src/coreclr/jit/async.h @@ -63,6 +63,10 @@ class AsyncTransformation BasicBlock* m_lastResumptionBB = nullptr; BasicBlock* m_sharedReturnBB = nullptr; + // Debug info + jitstd::vector m_dbgContinuationVars; + jitstd::vector m_dbgSuspensionPoints; + bool IsLive(unsigned lclNum); void Transform(BasicBlock* block, GenTreeCall* call, @@ -132,6 +136,9 @@ class AsyncTransformation GenTreeFlags indirFlags = GTF_IND_NONFAULTING); GenTreeStoreInd* StoreAtOffset(GenTree* base, unsigned offset, GenTree* value, var_types storeType); + void CreateDebugInfoForSuspensionPoint(GenTreeCall* asyncCall, const ContinuationLayout& layout); + void ReportDebugInfo(); + unsigned GetDataArrayVar(); unsigned GetGCDataArrayVar(); unsigned GetResultBaseVar(); @@ -147,6 +154,8 @@ class AsyncTransformation : m_comp(comp) , m_liveLocalsScratch(comp->getAllocator(CMK_Async)) , m_resumptionBBs(comp->getAllocator(CMK_Async)) + , m_dbgContinuationVars(comp->getAllocator(CMK_Async)) + , m_dbgSuspensionPoints(comp->getAllocator(CMK_Async)) { } diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 134d236f78a108..263111e04ca82f 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -4601,7 +4601,7 @@ class Compiler CORINFO_CALL_INFO* callInfo, IL_OFFSET rawILOffset); - void impSetupAndSpillForAsyncCall(GenTreeCall* call, OPCODE opcode, unsigned prefixFlags); + void impSetupAndSpillForAsyncCall(GenTreeCall* call, OPCODE opcode, unsigned prefixFlags, const DebugInfo& callInstDI); void impInsertAsyncContinuationForLdvirtftnCall(GenTreeCall* call); diff --git a/src/coreclr/jit/gentree.h b/src/coreclr/jit/gentree.h index b76a27879a4a7e..d348b04f95f099 100644 --- a/src/coreclr/jit/gentree.h +++ b/src/coreclr/jit/gentree.h @@ -4400,6 +4400,9 @@ struct AsyncCallInfo bool SaveAndRestoreSynchronizationContextField = false; bool HasSuspensionIndicatorDef = false; unsigned SynchronizationContextLclNum = BAD_VAR_NUM; + + // Exact debug info of call IL instruction + DebugInfo DebugInfo; }; // Return type descriptor of a GT_CALL node. diff --git a/src/coreclr/jit/importercalls.cpp b/src/coreclr/jit/importercalls.cpp index b5f22d85d10dd4..1ad1bad152a0d7 100644 --- a/src/coreclr/jit/importercalls.cpp +++ b/src/coreclr/jit/importercalls.cpp @@ -386,7 +386,7 @@ var_types Compiler::impImportCall(OPCODE opcode, if (sig->isAsyncCall()) { - impSetupAndSpillForAsyncCall(call->AsCall(), opcode, prefixFlags); + impSetupAndSpillForAsyncCall(call->AsCall(), opcode, prefixFlags, di); } impPopCallArgs(sig, call->AsCall()); @@ -691,7 +691,7 @@ var_types Compiler::impImportCall(OPCODE opcode, if (sig->isAsyncCall()) { - impSetupAndSpillForAsyncCall(call->AsCall(), opcode, prefixFlags); + impSetupAndSpillForAsyncCall(call->AsCall(), opcode, prefixFlags, di); } // Now create the argument list. @@ -6800,10 +6800,15 @@ void Compiler::impCheckForPInvokeCall( // call - The call // opcode - The IL opcode for the call // prefixFlags - Flags containing context handling information from IL +// callInstDI - Debug info for the exact call instruction // -void Compiler::impSetupAndSpillForAsyncCall(GenTreeCall* call, OPCODE opcode, unsigned prefixFlags) +void Compiler::impSetupAndSpillForAsyncCall(GenTreeCall* call, + OPCODE opcode, + unsigned prefixFlags, + const DebugInfo& callInstDI) { AsyncCallInfo asyncInfo; + asyncInfo.DebugInfo = callInstDI; if ((prefixFlags & PREFIX_IS_TASK_AWAIT) != 0) { diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs b/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs index e7e11c79095109..1b9ed8c69cf8de 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs @@ -1347,9 +1347,6 @@ public struct AsyncSuspensionPoint public struct AsyncInfo { - // Offset in contiuation's data where state number is stored. UINT_MAX - // if no state number is stored (when NumSuspensionPoints == 1). - public uint StateOffset; // Number of suspension points in the method. public uint NumSuspensionPoints; } diff --git a/src/coreclr/tools/superpmi/superpmi/icorjitinfo.cpp b/src/coreclr/tools/superpmi/superpmi/icorjitinfo.cpp index af4107d01b1079..996e417a50e43d 100644 --- a/src/coreclr/tools/superpmi/superpmi/icorjitinfo.cpp +++ b/src/coreclr/tools/superpmi/superpmi/icorjitinfo.cpp @@ -1052,7 +1052,7 @@ void MyICJI::reportRichMappings( uint32_t numMappings) { jitInstance->mc->cr->AddCall("reportRichMappings"); - // TODO: record these mappings + // Compile output that we do not currently save freeArray(inlineTreeNodes); freeArray(mappings); } @@ -1064,7 +1064,7 @@ void MyICJI::reportAsyncDebugInfo( uint32_t numVars) { jitInstance->mc->cr->AddCall("reportAsyncDebugInfo"); - // TODO: record async debug info + // Compile output that we do not currently save freeArray(asyncInfo); freeArray(suspensionPoints); freeArray(vars); diff --git a/src/coreclr/vm/codeman.cpp b/src/coreclr/vm/codeman.cpp index fae188a904282d..18a2559db515f9 100644 --- a/src/coreclr/vm/codeman.cpp +++ b/src/coreclr/vm/codeman.cpp @@ -3963,17 +3963,6 @@ BOOL EECodeGenManager::GetBoundariesAndVarsWorker( if (pDebugInfo == NULL) return FALSE; -#ifdef FEATURE_ON_STACK_REPLACEMENT - BOOL hasFlagByte = TRUE; -#else - BOOL hasFlagByte = FALSE; -#endif - - if (m_storeRichDebugInfo) - { - hasFlagByte = TRUE; - } - // Uncompress. This allocates memory and may throw. CompressDebugInfo::RestoreBoundariesAndVars( fpNew, @@ -3981,8 +3970,7 @@ BOOL EECodeGenManager::GetBoundariesAndVarsWorker( boundsType, pDebugInfo, // input pcMap, ppMap, // output - pcVars, ppVars, // output - hasFlagByte + pcVars, ppVars // output ); return TRUE; @@ -4047,22 +4035,10 @@ size_t EECodeGenManager::WalkILOffsetsWorker(PTR_BYTE pDebugInfo, if (pDebugInfo == NULL) return 0; -#ifdef FEATURE_ON_STACK_REPLACEMENT - BOOL hasFlagByte = TRUE; -#else - BOOL hasFlagByte = FALSE; -#endif - - if (m_storeRichDebugInfo) - { - hasFlagByte = TRUE; - } - // Uncompress. This allocates memory and may throw. return CompressDebugInfo::WalkILOffsets( pDebugInfo, // input boundsType, - hasFlagByte, pContext, pfnWalkILOffsets ); @@ -6442,8 +6418,7 @@ BOOL ReadyToRunJitManager::GetBoundariesAndVars( boundsType, pDebugInfo, // input pcMap, ppMap, // output - pcVars, ppVars, // output - FALSE); // no patchpoint info + pcVars, ppVars); // output return TRUE; } @@ -6475,7 +6450,6 @@ size_t ReadyToRunJitManager::WalkILOffsets( return CompressDebugInfo::WalkILOffsets( pDebugInfo, // input boundsType, - FALSE, // no patchpoint info pContext, pfnWalkILOffsets); } diff --git a/src/coreclr/vm/debuginfostore.cpp b/src/coreclr/vm/debuginfostore.cpp index 2687329aea9862..f97483662f9cd8 100644 --- a/src/coreclr/vm/debuginfostore.cpp +++ b/src/coreclr/vm/debuginfostore.cpp @@ -312,6 +312,9 @@ static int g_CDI_bVarsTotalCompress = 0; static int g_CDI_bRichDebugInfoTotalUncompress = 0; static int g_CDI_bRichDebugInfoTotalCompress = 0; + +static int g_CDI_bAsyncDebugInfoTotalUncompress = 0; +static int g_CDI_bAsyncDebugInfoTotalCompress = 0; #endif // Helper to write a compressed Native Var Info @@ -457,12 +460,60 @@ static void DoRichOffsetMappings( } } +template +static void DoAsyncSuspensionPoints( + T trans, + ULONG32 cSuspensionPoints, + ICorDebugInfo::AsyncSuspensionPoint* suspensionPoints) +{ + // Loop through and transfer each Entry in the Mapping. + uint32_t lastRootILOffset = 0; + uint32_t lastInlinee = 0; + uint32_t lastILOffset = 0; + for (uint32_t i = 0; i < cSuspensionPoints; i++) + { + ICorDebugInfo::AsyncSuspensionPoint* sp = &suspensionPoints[i]; + + trans.DoEncodedDeltaU32NonMonotonic(sp->RootILOffset, lastRootILOffset); + lastRootILOffset = sp->RootILOffset; + + trans.DoEncodedDeltaU32NonMonotonic(sp->Inlinee, lastInlinee); + lastInlinee = sp->Inlinee; + + trans.DoEncodedDeltaU32NonMonotonic(sp->ILOffset, lastILOffset); + lastILOffset = sp->ILOffset; + + trans.DoEncodedU32(sp->NumContinuationVars); + } +} + +template +static void DoAsyncVars( + T trans, + ULONG32 cVars, + ICorDebugInfo::AsyncContinuationVarInfo* vars) +{ + uint32_t lastOffset = 0; + for (uint32_t i = 0; i < cVars; i++) + { + ICorDebugInfo::AsyncContinuationVarInfo* var = &vars[i]; + + trans.DoEncodedAdjustedU32(var->VarNumber, (DWORD) ICorDebugInfo::MAX_ILNUM); + + trans.DoEncodedDeltaU32(var->Offset, lastOffset); + lastOffset = var->Offset; + } +} + enum EXTRA_DEBUG_INFO_FLAGS { // Debug info contains patchpoint information EXTRA_DEBUG_INFO_PATCHPOINT = 1, // Debug info contains rich information EXTRA_DEBUG_INFO_RICH = 2, + // Debug info contains async information + EXTRA_DEBUG_INFO_ASYNC = 4, + EXTRA_DEBUG_INFO_UNINSTRUMENTED_BOUNDS = 8, }; #ifndef DACCESS_COMPILE @@ -694,7 +745,42 @@ void CompressDebugInfo::CompressRichDebugInfo( PVOID pBlob = pWriter->GetBlob(&cbBlob); g_CDI_bRichDebugInfoTotalUncompress += 8 + cInlineTree * sizeof(ICorDebugInfo::InlineTreeNode) + cRichOffsetMappings * sizeof(ICorDebugInfo::RichOffsetMapping); - g_CDI_bRichDebugInfoTotalCompress += 4 + cbBlob; + g_CDI_bRichDebugInfoTotalCompress += cbBlob; +#endif +} + +void CompressDebugInfo::CompressAsyncDebugInfo( + IN ICorDebugInfo::AsyncInfo* asyncInfo, + IN ICorDebugInfo::AsyncSuspensionPoint* pSuspensionPoints, + IN ICorDebugInfo::AsyncContinuationVarInfo* pAsyncVars, + IN ULONG iAsyncVars, + IN OUT NibbleWriter* pWriter) +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + + _ASSERTE(pWriter != NULL); + _ASSERTE((asyncInfo->NumSuspensionPoints > 0) && (pSuspensionPoints != NULL)); + pWriter->WriteEncodedU32(asyncInfo->NumSuspensionPoints); + pWriter->WriteEncodedU32(iAsyncVars); + + TransferWriter t(*pWriter); + DoAsyncSuspensionPoints(t, asyncInfo->NumSuspensionPoints, pSuspensionPoints); + DoAsyncVars(t, iAsyncVars, pAsyncVars); + + pWriter->Flush(); + +#ifdef _DEBUG + DWORD cbBlob; + PVOID pBlob = pWriter->GetBlob(&cbBlob); + + g_CDI_bAsyncDebugInfoTotalUncompress += 8 + asyncInfo->NumSuspensionPoints * sizeof(ICorDebugInfo::AsyncSuspensionPoint) + iAsyncVars * sizeof(ICorDebugInfo::AsyncContinuationVarInfo); + g_CDI_bAsyncDebugInfoTotalCompress += cbBlob; #endif } @@ -810,19 +896,22 @@ static void ComposeMapping(const InstrumentedILOffsetMapping * pProfilerILMap, I } } -PTR_BYTE CompressDebugInfo::CompressBoundariesAndVars( - IN ICorDebugInfo::OffsetMapping* pOffsetMapping, - IN ULONG iOffsetMapping, - const InstrumentedILOffsetMapping * pInstrumentedILBounds, - IN ICorDebugInfo::NativeVarInfo* pNativeVarInfo, - IN ULONG iNativeVarInfo, - IN PatchpointInfo* patchpointInfo, - IN ICorDebugInfo::InlineTreeNode* pInlineTree, - IN ULONG iInlineTree, - IN ICorDebugInfo::RichOffsetMapping* pRichOffsetMappings, - IN ULONG iRichOffsetMappings, - IN BOOL writeFlagByte, - IN LoaderHeap* pLoaderHeap +PTR_BYTE CompressDebugInfo::Compress( + IN ICorDebugInfo::OffsetMapping* pOffsetMapping, + IN ULONG iOffsetMapping, + const InstrumentedILOffsetMapping * pInstrumentedILBounds, + IN ICorDebugInfo::NativeVarInfo* pNativeVarInfo, + IN ULONG iNativeVarInfo, + IN PatchpointInfo* patchpointInfo, + IN ICorDebugInfo::InlineTreeNode* pInlineTree, + IN ULONG iInlineTree, + IN ICorDebugInfo::RichOffsetMapping* pRichOffsetMappings, + IN ULONG iRichOffsetMappings, + IN ICorDebugInfo::AsyncInfo* asyncInfo, + IN ICorDebugInfo::AsyncSuspensionPoint* pSuspensionPoints, + IN ICorDebugInfo::AsyncContinuationVarInfo* pAsyncVars, + IN ULONG iAsyncVars, + IN LoaderHeap* pLoaderHeap ) { CONTRACTL { @@ -831,7 +920,8 @@ PTR_BYTE CompressDebugInfo::CompressBoundariesAndVars( PRECONDITION((iNativeVarInfo == 0) == (pNativeVarInfo == NULL)); PRECONDITION((iInlineTree == 0) || (pInlineTree != NULL)); PRECONDITION((iRichOffsetMappings == 0) || (pRichOffsetMappings != NULL)); - PRECONDITION(writeFlagByte || ((patchpointInfo == NULL) && (iInlineTree == 0) && (iRichOffsetMappings == 0))); + PRECONDITION((asyncInfo->NumSuspensionPoints == 0) || (pSuspensionPoints != NULL)); + PRECONDITION((iAsyncVars == 0) || (pAsyncVars != NULL)); PRECONDITION(pLoaderHeap != NULL); } CONTRACTL_END; @@ -892,31 +982,53 @@ PTR_BYTE CompressDebugInfo::CompressBoundariesAndVars( pRichDebugInfo = richDebugInfoBuffer.GetBlob(&cbRichDebugInfo); } + NibbleWriter asyncInfoBuffer; + DWORD cbAsyncInfo = 0; + PVOID pAsyncInfoBlob = NULL; + if (asyncInfo->NumSuspensionPoints > 0) + { + CompressDebugInfo::CompressAsyncDebugInfo(asyncInfo, pSuspensionPoints, pAsyncVars, iAsyncVars, &asyncInfoBuffer); + pAsyncInfoBlob = asyncInfoBuffer.GetBlob(&cbAsyncInfo); + } + // Now write it all out to the buffer in a compact fashion. NibbleWriter w; - if (cbUninstrumentedBounds != 0) + + bool isFat = + (cbPatchpointInfo > 0) || + (cbRichDebugInfo > 0) || + (cbAsyncInfo > 0) || + (cbUninstrumentedBounds > 0); + + if (isFat) { - w.WriteEncodedU32(DebugInfoBoundsHasInstrumentedBounds); // 0xFFFFFFFF is used to indicate that the instrumented bounds are present. + w.WriteEncodedU32(DebugInfoFat); w.WriteEncodedU32(cbBounds); + w.WriteEncodedU32(cbVars); w.WriteEncodedU32(cbUninstrumentedBounds); + w.WriteEncodedU32(cbPatchpointInfo); + w.WriteEncodedU32(cbRichDebugInfo); + w.WriteEncodedU32(cbAsyncInfo); } else { w.WriteEncodedU32(cbBounds); + w.WriteEncodedU32(cbVars); } - w.WriteEncodedU32(cbVars); + w.Flush(); DWORD cbHeader; PVOID pHeader = w.GetBlob(&cbHeader); S_UINT32 cbFinalSize(0); - if (writeFlagByte) - cbFinalSize += 1; - + cbFinalSize += cbHeader; + cbFinalSize += cbBounds; + cbFinalSize += cbVars; + cbFinalSize += cbUninstrumentedBounds; cbFinalSize += cbPatchpointInfo; - cbFinalSize += S_UINT32(4) + S_UINT32(cbRichDebugInfo); - cbFinalSize += S_UINT32(cbHeader) + S_UINT32(cbBounds) + S_UINT32(cbUninstrumentedBounds) + S_UINT32(cbVars); + cbFinalSize += cbRichDebugInfo; + cbFinalSize += cbAsyncInfo; if (cbFinalSize.IsOverflow()) ThrowHR(COR_E_OVERFLOW); @@ -924,29 +1036,6 @@ PTR_BYTE CompressDebugInfo::CompressBoundariesAndVars( BYTE *ptrStart = (BYTE *)(void *)pLoaderHeap->AllocMem(S_SIZE_T(cbFinalSize.Value())); BYTE *ptr = ptrStart; - if (writeFlagByte) - { - BYTE flagByte = 0; - if (cbPatchpointInfo > 0) - flagByte |= EXTRA_DEBUG_INFO_PATCHPOINT; - if (cbRichDebugInfo > 0) - flagByte |= EXTRA_DEBUG_INFO_RICH; - - *ptr++ = flagByte; - } - - if (cbPatchpointInfo > 0) - memcpy(ptr, (BYTE*) patchpointInfo, cbPatchpointInfo); - ptr += cbPatchpointInfo; - - if (cbRichDebugInfo > 0) - { - memcpy(ptr, &cbRichDebugInfo, 4); - ptr += 4; - memcpy(ptr, pRichDebugInfo, cbRichDebugInfo); - ptr += cbRichDebugInfo; - } - memcpy(ptr, pHeader, cbHeader); ptr += cbHeader; @@ -954,13 +1043,25 @@ PTR_BYTE CompressDebugInfo::CompressBoundariesAndVars( memcpy(ptr, pBounds, cbBounds); ptr += cbBounds; + if (cbVars > 0) + memcpy(ptr, pVars, cbVars); + ptr += cbVars; + if (cbUninstrumentedBounds > 0) memcpy(ptr, pUninstrumentedBounds, cbUninstrumentedBounds); ptr += cbUninstrumentedBounds; - if (cbVars > 0) - memcpy(ptr, pVars, cbVars); - ptr += cbVars; + if (cbPatchpointInfo > 0) + memcpy(ptr, (BYTE*) patchpointInfo, cbPatchpointInfo); + ptr += cbPatchpointInfo; + + if (cbRichDebugInfo > 0) + memcpy(ptr, pRichDebugInfo, cbRichDebugInfo); + ptr += cbRichDebugInfo; + + if (cbAsyncInfo > 0) + memcpy(ptr, pAsyncInfoBlob, cbAsyncInfo); + ptr += cbAsyncInfo; #ifdef _DEBUG ULONG32 cNewBounds = 0; @@ -968,7 +1069,7 @@ PTR_BYTE CompressDebugInfo::CompressBoundariesAndVars( ICorDebugInfo::OffsetMapping *pNewMap = NULL; ICorDebugInfo::NativeVarInfo *pNewVars = NULL; RestoreBoundariesAndVars( - DecompressNew, NULL, BoundsType::Instrumented, ptrStart, &cNewBounds, &pNewMap, &cNewVars, &pNewVars, writeFlagByte); + DecompressNew, NULL, BoundsType::Instrumented, ptrStart, &cNewBounds, &pNewMap, &cNewVars, &pNewVars); _ASSERTE(cNewBounds == iOffsetMapping); _ASSERTE(cNewBounds == 0 || pNewMap != NULL); @@ -1060,6 +1161,53 @@ static void DoBounds(PTR_BYTE addrBounds, uint32_t cbBounds, TNumBounds countHan // Uncompression (restore) routines //----------------------------------------------------------------------------- +DebugInfoChunks CompressDebugInfo::Restore(IN PTR_BYTE pDebugInfo) +{ + CONTRACTL + { + THROWS; // reading from nibble stream may throw on invalid data. + GC_NOTRIGGER; + MODE_ANY; + SUPPORTS_DAC; + } + CONTRACTL_END; + + NibbleReader r(pDebugInfo, 42 /* maximum size of compressed 7 UINT32s */); + + ULONG cbBoundsOrFatMarker = r.ReadEncodedU32(); + + DebugInfoChunks chunks; + + if (cbBoundsOrFatMarker == DebugInfoFat) + { + // Fat header + chunks.cbBounds = r.ReadEncodedU32(); + chunks.cbVars = r.ReadEncodedU32(); + chunks.cbUninstrumentedBounds = r.ReadEncodedU32(); + chunks.cbPatchpointInfo = r.ReadEncodedU32(); + chunks.cbRichDebugInfo = r.ReadEncodedU32(); + chunks.cbAsyncInfo = r.ReadEncodedU32(); + } + else + { + chunks.cbBounds = cbBoundsOrFatMarker; + chunks.cbVars = r.ReadEncodedU32(); + chunks.cbUninstrumentedBounds = 0; + chunks.cbPatchpointInfo = 0; + chunks.cbRichDebugInfo = 0; + chunks.cbAsyncInfo = 0; + } + + chunks.pBounds = pDebugInfo + r.GetNextByteIndex(); + chunks.pVars = chunks.pBounds + chunks.cbBounds; + chunks.pUninstrumentedBounds = chunks.pVars + chunks.cbVars; + chunks.pPatchpointInfo = chunks.pUninstrumentedBounds + chunks.cbUninstrumentedBounds; + chunks.pRichDebugInfo = chunks.pPatchpointInfo + chunks.cbPatchpointInfo; + chunks.pAsyncInfo = chunks.pRichDebugInfo + chunks.cbRichDebugInfo; + chunks.pEnd = chunks.pAsyncInfo + chunks.cbAsyncInfo; + return chunks; +} + // Uncompress data supplied by Compress functions. void CompressDebugInfo::RestoreBoundariesAndVars( IN FP_IDS_NEW fpNew, @@ -1069,8 +1217,7 @@ void CompressDebugInfo::RestoreBoundariesAndVars( OUT ULONG32 * pcMap, // number of entries in ppMap OUT ICorDebugInfo::OffsetMapping **ppMap, // pointer to newly allocated array OUT ULONG32 *pcVars, - OUT ICorDebugInfo::NativeVarInfo **ppVars, - BOOL hasFlagByte + OUT ICorDebugInfo::NativeVarInfo **ppVars ) { CONTRACTL @@ -1087,50 +1234,16 @@ void CompressDebugInfo::RestoreBoundariesAndVars( if (pcVars != NULL) *pcVars = 0; if (ppVars != NULL) *ppVars = NULL; - if (hasFlagByte) - { - // Check flag byte and skip over any patchpoint info - BYTE flagByte = *pDebugInfo; - pDebugInfo++; - - if ((flagByte & EXTRA_DEBUG_INFO_PATCHPOINT) != 0) - { - PTR_PatchpointInfo patchpointInfo = dac_cast(pDebugInfo); - pDebugInfo += patchpointInfo->PatchpointInfoSize(); - flagByte &= ~EXTRA_DEBUG_INFO_PATCHPOINT; - } - - if ((flagByte & EXTRA_DEBUG_INFO_RICH) != 0) - { - UINT32 cbRichDebugInfo = *PTR_UINT32(pDebugInfo); - pDebugInfo += 4; - pDebugInfo += cbRichDebugInfo; - flagByte &= ~EXTRA_DEBUG_INFO_RICH; - } - - _ASSERTE(flagByte == 0); - } - - NibbleReader r(pDebugInfo, 24 /* maximum size of compressed 4 UINT32s */); - - ULONG cbBounds = r.ReadEncodedU32(); - ULONG cbUninstrumentedBounds = 0; - if (cbBounds == DebugInfoBoundsHasInstrumentedBounds) - { - // This means we have instrumented bounds. - cbBounds = r.ReadEncodedU32(); - cbUninstrumentedBounds = r.ReadEncodedU32(); - } - ULONG cbVars = r.ReadEncodedU32(); + DebugInfoChunks chunks = Restore(pDebugInfo); - PTR_BYTE addrBounds = pDebugInfo + r.GetNextByteIndex(); - PTR_BYTE addrVars = addrBounds + cbBounds + cbUninstrumentedBounds; + PTR_BYTE addrBounds = chunks.pBounds; + unsigned cbBounds = chunks.cbBounds; - if ((boundsType == BoundsType::Uninstrumented) && cbUninstrumentedBounds != 0) + if ((boundsType == BoundsType::Uninstrumented) && chunks.cbUninstrumentedBounds != 0) { // If we have uninstrumented bounds, we will use them instead of the regular bounds. - addrBounds = addrBounds + cbBounds; - cbBounds = cbUninstrumentedBounds; + addrBounds = chunks.pUninstrumentedBounds; + cbBounds = chunks.cbUninstrumentedBounds; } if ((pcMap != NULL || ppMap != NULL) && (cbBounds != 0)) @@ -1162,9 +1275,9 @@ void CompressDebugInfo::RestoreBoundariesAndVars( }); } - if ((pcVars != NULL || ppVars != NULL) && (cbVars != 0)) + if ((pcVars != NULL || ppVars != NULL) && (chunks.cbVars != 0)) { - NibbleReader r(addrVars, cbVars); + NibbleReader r(chunks.pVars, chunks.cbVars); TransferReader t(r); UINT32 cNumEntries = r.ReadEncodedU32(); @@ -1194,7 +1307,6 @@ void CompressDebugInfo::RestoreBoundariesAndVars( size_t CompressDebugInfo::WalkILOffsets( IN PTR_BYTE pDebugInfo, BoundsType boundsType, - BOOL hasFlagByte, void* pContext, size_t (* pfnWalkILOffsets)(ICorDebugInfo::OffsetMapping *pOffsetMapping, void *pContext) ) @@ -1208,50 +1320,16 @@ size_t CompressDebugInfo::WalkILOffsets( } CONTRACTL_END; - if (hasFlagByte) - { - // Check flag byte and skip over any patchpoint info - BYTE flagByte = *pDebugInfo; - pDebugInfo++; - - if ((flagByte & EXTRA_DEBUG_INFO_PATCHPOINT) != 0) - { - PTR_PatchpointInfo patchpointInfo = dac_cast(pDebugInfo); - pDebugInfo += patchpointInfo->PatchpointInfoSize(); - flagByte &= ~EXTRA_DEBUG_INFO_PATCHPOINT; - } - - if ((flagByte & EXTRA_DEBUG_INFO_RICH) != 0) - { - UINT32 cbRichDebugInfo = *PTR_UINT32(pDebugInfo); - pDebugInfo += 4; - pDebugInfo += cbRichDebugInfo; - flagByte &= ~EXTRA_DEBUG_INFO_RICH; - } - - _ASSERTE(flagByte == 0); - } + DebugInfoChunks chunks = Restore(pDebugInfo); - NibbleReader r(pDebugInfo, 24 /* maximum size of compressed 4 UINT32s */); + PTR_BYTE addrBounds = chunks.pBounds; + unsigned cbBounds = chunks.cbBounds; - ULONG cbBounds = r.ReadEncodedU32_NoThrow(); - ULONG cbUninstrumentedBounds = 0; - if (cbBounds == DebugInfoBoundsHasInstrumentedBounds) - { - // This means we have instrumented bounds. - cbBounds = r.ReadEncodedU32(); - cbUninstrumentedBounds = r.ReadEncodedU32(); - } - ULONG cbVars = r.ReadEncodedU32_NoThrow(); - - PTR_BYTE addrBounds = pDebugInfo + r.GetNextByteIndex(); - PTR_BYTE addrVars = addrBounds + cbBounds + cbUninstrumentedBounds; - - if ((boundsType == BoundsType::Uninstrumented) && cbUninstrumentedBounds != 0) + if ((boundsType == BoundsType::Uninstrumented) && chunks.cbUninstrumentedBounds != 0) { // If we have uninstrumented bounds, we will use them instead of the regular bounds. - addrBounds = addrBounds + cbBounds; - cbBounds = cbUninstrumentedBounds; + addrBounds = chunks.pUninstrumentedBounds; + cbBounds = chunks.cbUninstrumentedBounds; } if (cbBounds != 0) @@ -1298,14 +1376,12 @@ PatchpointInfo * CompressDebugInfo::RestorePatchpointInfo(IN PTR_BYTE pDebugInfo } CONTRACTL_END; - // Check flag byte. - BYTE flagByte = *pDebugInfo; - pDebugInfo++; + DebugInfoChunks chunks = Restore(pDebugInfo); - if ((flagByte & EXTRA_DEBUG_INFO_PATCHPOINT) == 0) + if (chunks.cbPatchpointInfo == 0) return NULL; - return static_cast(PTR_READ(dac_cast(pDebugInfo), dac_cast(pDebugInfo)->PatchpointInfoSize())); + return static_cast(PTR_READ(dac_cast(chunks.pPatchpointInfo), chunks.cbPatchpointInfo)); } #endif @@ -1327,29 +1403,9 @@ void CompressDebugInfo::RestoreRichDebugInfo( } CONTRACTL_END; - BYTE flagByte = *pDebugInfo; - if ((flagByte & EXTRA_DEBUG_INFO_RICH) == 0) - { - *ppInlineTree = NULL; - *pNumInlineTree = 0; - *ppRichMappings = NULL; - *pNumRichMappings = 0; - return; - } + DebugInfoChunks chunks = Restore(pDebugInfo); - pDebugInfo++; - -#ifdef FEATURE_ON_STACK_REPLACEMENT - if ((flagByte & EXTRA_DEBUG_INFO_PATCHPOINT) != 0) - { - PTR_PatchpointInfo patchpointInfo = dac_cast(pDebugInfo); - pDebugInfo += patchpointInfo->PatchpointInfoSize(); - } -#endif - - UINT32 cbRichDebugInfo = *PTR_UINT32(pDebugInfo); - pDebugInfo += 4; - NibbleReader r(pDebugInfo, cbRichDebugInfo); + NibbleReader r(chunks.pRichDebugInfo, chunks.cbRichDebugInfo); *pNumInlineTree = r.ReadEncodedU32(); *pNumRichMappings = r.ReadEncodedU32(); @@ -1382,49 +1438,13 @@ void CompressDebugInfo::EnumMemoryRegions(CLRDataEnumMemoryFlags flags, PTR_BYTE PTR_BYTE pStart = pDebugInfo; - if (hasFlagByte) - { - // Check flag byte and skip over any patchpoint info - BYTE flagByte = *pDebugInfo; - pDebugInfo++; - - if ((flagByte & EXTRA_DEBUG_INFO_PATCHPOINT) != 0) - { - PTR_PatchpointInfo patchpointInfo = dac_cast(pDebugInfo); - pDebugInfo += patchpointInfo->PatchpointInfoSize(); - flagByte &= ~EXTRA_DEBUG_INFO_PATCHPOINT; - } - - if ((flagByte & EXTRA_DEBUG_INFO_RICH) != 0) - { - UINT32 cbRichDebugInfo = *PTR_UINT32(pDebugInfo); - pDebugInfo += 4; - pDebugInfo += cbRichDebugInfo; - flagByte &= ~EXTRA_DEBUG_INFO_RICH; - } - - _ASSERTE(flagByte == 0); - } - - NibbleReader r(pDebugInfo, 24 /* maximum size of compressed 4 UINT32s */); - - ULONG cbBounds = r.ReadEncodedU32(); - ULONG cbUninstrumentedBounds = 0; - if (cbBounds == DebugInfoBoundsHasInstrumentedBounds) - { - // This means we have instrumented bounds. - cbBounds = r.ReadEncodedU32(); - cbUninstrumentedBounds = r.ReadEncodedU32(); - } - ULONG cbVars = r.ReadEncodedU32(); - - pDebugInfo += r.GetNextByteIndex() + cbBounds + cbUninstrumentedBounds + cbVars; + DebugInfoChunks chunks = Restore(pDebugInfo); // NibbleReader reads in units of sizeof(NibbleChunkType) // So we need to account for any partial chunk at the end. - pDebugInfo = AlignUp(dac_cast(pDebugInfo), sizeof(NibbleReader::NibbleChunkType)); + PTR_BYTE pEnd = AlignUp(dac_cast(chunks.pEnd), sizeof(NibbleReader::NibbleChunkType)); - DacEnumMemoryRegion(dac_cast(pStart), pDebugInfo - pStart); + DacEnumMemoryRegion(dac_cast(pStart), pEnd - pStart); } #endif // DACCESS_COMPILE diff --git a/src/coreclr/vm/debuginfostore.h b/src/coreclr/vm/debuginfostore.h index b8ca0b27ad2a9c..916203d8dd46d1 100644 --- a/src/coreclr/vm/debuginfostore.h +++ b/src/coreclr/vm/debuginfostore.h @@ -60,6 +60,23 @@ enum class BoundsType Uninstrumented, // Get the uninstrumented bounds }; +struct DebugInfoChunks +{ + PTR_BYTE pBounds; + ULONG32 cbBounds; + PTR_BYTE pVars; + ULONG32 cbVars; + PTR_BYTE pUninstrumentedBounds; + ULONG32 cbUninstrumentedBounds; + PTR_BYTE pPatchpointInfo; + ULONG32 cbPatchpointInfo; + PTR_BYTE pRichDebugInfo; + ULONG32 cbRichDebugInfo; + PTR_BYTE pAsyncInfo; + ULONG32 cbAsyncInfo; + PTR_BYTE pEnd; +}; + //----------------------------------------------------------------------------- // Utility routines used for compression // Note that the compression is just an implementation detail of the stores, @@ -87,20 +104,32 @@ class CompressDebugInfo IN ICorDebugInfo::RichOffsetMapping* pRichOffsetMappings, IN OUT NibbleWriter* pWriter); + static void CompressAsyncDebugInfo( + IN ICorDebugInfo::AsyncInfo* asyncInfo, + IN ICorDebugInfo::AsyncSuspensionPoint* pSuspensionPoints, + IN ICorDebugInfo::AsyncContinuationVarInfo* pAsyncVars, + IN ULONG iAsyncVars, + IN OUT NibbleWriter* pWriter); + + static DebugInfoChunks Restore(IN PTR_BYTE pDebugInfo); + public: // Stores the result in LoaderHeap - static PTR_BYTE CompressBoundariesAndVars( - IN ICorDebugInfo::OffsetMapping * pOffsetMapping, - IN ULONG iOffsetMapping, - const InstrumentedILOffsetMapping * pInstrumentedILBounds, - IN ICorDebugInfo::NativeVarInfo * pNativeVarInfo, - IN ULONG iNativeVarInfo, - IN PatchpointInfo * patchpointInfo, - IN ICorDebugInfo::InlineTreeNode * pInlineTree, - IN ULONG iInlineTree, - IN ICorDebugInfo::RichOffsetMapping * pRichOffsetMappings, - IN ULONG iRichOffsetMappings, - IN BOOL writeFlagByte, + static PTR_BYTE Compress( + IN ICorDebugInfo::OffsetMapping* pOffsetMapping, + IN ULONG iOffsetMapping, + const InstrumentedILOffsetMapping* pInstrumentedILBounds, + IN ICorDebugInfo::NativeVarInfo* pNativeVarInfo, + IN ULONG iNativeVarInfo, + IN PatchpointInfo* patchpointInfo, + IN ICorDebugInfo::InlineTreeNode* pInlineTree, + IN ULONG iInlineTree, + IN ICorDebugInfo::RichOffsetMapping* pRichOffsetMappings, + IN ULONG iRichOffsetMappings, + IN ICorDebugInfo::AsyncInfo* asyncInfo, + IN ICorDebugInfo::AsyncSuspensionPoint* pSuspensionPoints, + IN ICorDebugInfo::AsyncContinuationVarInfo* pAsyncVars, + IN ULONG iAsyncVars, IN LoaderHeap * pLoaderHeap ); @@ -113,15 +142,13 @@ class CompressDebugInfo OUT ULONG32 * pcMap, // number of entries in ppMap OUT ICorDebugInfo::OffsetMapping **ppMap, // pointer to newly allocated array OUT ULONG32 *pcVars, - OUT ICorDebugInfo::NativeVarInfo **ppVars, - BOOL hasFlagByte + OUT ICorDebugInfo::NativeVarInfo **ppVars ); // Walk the ILOffsets without needing to allocate a buffer static size_t WalkILOffsets( IN PTR_BYTE pDebugInfo, BoundsType boundsType, - BOOL hasFlagByte, void* pContext, size_t (* pfnWalkILOffsets)(ICorDebugInfo::OffsetMapping *pOffsetMapping, void *pContext) ); @@ -177,6 +204,6 @@ class DebugInfoManager #endif }; -#define DebugInfoBoundsHasInstrumentedBounds 0xFFFFFFFF +#define DebugInfoFat 0xFFFFFFFF #endif // __DebugInfoStore_H_ diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index a28bb895fd481e..28ce25c3a2dbaf 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -10726,9 +10726,8 @@ CEECodeGenInfo::CEECodeGenInfo(PrepareCodeConfig* config, MethodDesc* fd, COR_IL , m_numInlineTreeNodes(0) , m_richOffsetMappings(NULL) , m_numRichOffsetMappings(0) - , m_asyncInfo(NULL) - , m_asyncSuspensionPoints(NULL) - , m_asyncContinuationVars(NULL) + , m_dbgAsyncSuspensionPoints(NULL) + , m_dbgAsyncContinuationVars(NULL) , m_numAsyncContinuationVars(0) , m_gphCache() { @@ -10742,6 +10741,7 @@ CEECodeGenInfo::CEECodeGenInfo(PrepareCodeConfig* config, MethodDesc* fd, COR_IL m_ILHeader = ilHeader; m_jitFlags = GetCompileFlags(config, m_pMethodBeingCompiled, &m_MethodInfo); + m_dbgAsyncInfo.NumSuspensionPoints = 0; } void CEECodeGenInfo::getHelperFtn(CorInfoHelpFunc ftnNum, /* IN */ @@ -11187,19 +11187,10 @@ void CEECodeGenInfo::reportAsyncDebugInfo( JIT_TO_EE_TRANSITION(); - if (((EECodeGenManager*)m_jitManager)->IsStoringRichDebugInfo()) - { - m_asyncInfo = asyncInfo; - m_asyncSuspensionPoints = suspensionPoints; - m_asyncContinuationVars = vars; - m_numAsyncContinuationVars = numVars; - } - else - { - freeArrayInternal(asyncInfo); - freeArrayInternal(suspensionPoints); - freeArrayInternal(vars); - } + m_dbgAsyncInfo = *asyncInfo; + m_dbgAsyncSuspensionPoints = suspensionPoints; + m_dbgAsyncContinuationVars = vars; + m_numAsyncContinuationVars = numVars; EE_TO_JIT_TRANSITION(); } @@ -11466,7 +11457,7 @@ void CEECodeGenInfo::CompressDebugInfo(PCODE nativeEntry, NativeCodeVersion nati return; } - if ((m_iOffsetMapping == 0) && (m_iNativeVarInfo == 0) && (patchpointInfo == NULL) && (m_numInlineTreeNodes == 0) && (m_numRichOffsetMappings == 0)) + if ((m_iOffsetMapping == 0) && (m_iNativeVarInfo == 0) && (patchpointInfo == NULL) && (m_numInlineTreeNodes == 0) && (m_numRichOffsetMappings == 0) && (m_dbgAsyncInfo.NumSuspensionPoints == 0)) return; if (patchpointInfo != NULL) @@ -11476,14 +11467,6 @@ void CEECodeGenInfo::CompressDebugInfo(PCODE nativeEntry, NativeCodeVersion nati EX_TRY { - BOOL writeFlagByte = FALSE; -#ifdef FEATURE_ON_STACK_REPLACEMENT - writeFlagByte = TRUE; -#endif - if (m_jitManager->IsStoringRichDebugInfo()) - writeFlagByte = TRUE; - - const InstrumentedILOffsetMapping *pILOffsetMapping = NULL; InstrumentedILOffsetMapping loadTimeMapping; #ifdef FEATURE_REJIT @@ -11511,13 +11494,13 @@ void CEECodeGenInfo::CompressDebugInfo(PCODE nativeEntry, NativeCodeVersion nati } #endif - PTR_BYTE pDebugInfo = CompressDebugInfo::CompressBoundariesAndVars( + PTR_BYTE pDebugInfo = CompressDebugInfo::Compress( m_pOffsetMapping, m_iOffsetMapping, pILOffsetMapping, m_pNativeVarInfo, m_iNativeVarInfo, patchpointInfo, m_inlineTreeNodes, m_numInlineTreeNodes, m_richOffsetMappings, m_numRichOffsetMappings, - writeFlagByte, + &m_dbgAsyncInfo, m_dbgAsyncSuspensionPoints, m_dbgAsyncContinuationVars, m_numAsyncContinuationVars, m_pMethodBeingCompiled->GetLoaderAllocator()->GetLowFrequencyHeap()); SetDebugInfo(pDebugInfo); diff --git a/src/coreclr/vm/jitinterface.h b/src/coreclr/vm/jitinterface.h index 11551c0bd20f71..18ade9201b1600 100644 --- a/src/coreclr/vm/jitinterface.h +++ b/src/coreclr/vm/jitinterface.h @@ -531,20 +531,17 @@ class CEECodeGenInfo : public CEEInfo freeArrayInternal(m_inlineTreeNodes); if (m_richOffsetMappings != NULL) freeArrayInternal(m_richOffsetMappings); - if (m_asyncInfo != NULL) - freeArrayInternal(m_asyncInfo); - if (m_asyncSuspensionPoints != NULL) - freeArrayInternal(m_asyncSuspensionPoints); - if (m_asyncContinuationVars != NULL) - freeArrayInternal(m_asyncContinuationVars); + if (m_dbgAsyncSuspensionPoints != NULL) + freeArrayInternal(m_dbgAsyncSuspensionPoints); + if (m_dbgAsyncContinuationVars != NULL) + freeArrayInternal(m_dbgAsyncContinuationVars); m_inlineTreeNodes = NULL; m_numInlineTreeNodes = 0; m_richOffsetMappings = NULL; m_numRichOffsetMappings = 0; - m_asyncInfo = NULL; - m_asyncSuspensionPoints = NULL; - m_asyncContinuationVars = NULL; + m_dbgAsyncSuspensionPoints = NULL; + m_dbgAsyncContinuationVars = NULL; m_numAsyncContinuationVars = 0; } @@ -641,9 +638,9 @@ class CEECodeGenInfo : public CEEInfo ICorDebugInfo::RichOffsetMapping *m_richOffsetMappings; ULONG32 m_numRichOffsetMappings; - ICorDebugInfo::AsyncInfo *m_asyncInfo; - ICorDebugInfo::AsyncSuspensionPoint *m_asyncSuspensionPoints; - ICorDebugInfo::AsyncContinuationVarInfo *m_asyncContinuationVars; + ICorDebugInfo::AsyncInfo m_dbgAsyncInfo; + ICorDebugInfo::AsyncSuspensionPoint *m_dbgAsyncSuspensionPoints; + ICorDebugInfo::AsyncContinuationVarInfo *m_dbgAsyncContinuationVars; ULONG32 m_numAsyncContinuationVars; // The first time a call is made to CEEJitInfo::GetProfilingHandle() from this thread From 6addd0e1da4c22d70af66e392ed6e10881da0c23 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Wed, 1 Oct 2025 21:41:57 +0200 Subject: [PATCH 04/51] Update managed view --- src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs b/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs index 1b9ed8c69cf8de..ed2a0b0b2d662f 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs @@ -1329,9 +1329,6 @@ public struct AsyncContinuationVarInfo public struct AsyncSuspensionPoint { - // State number assigned to this suspension point. UINT_MAX if no state - // number is assigned (when AsyncInfo::NumSuspensionPoints == 1). - public uint StateNumber; // IL offset in the root method that resulted in the creation of this suspension point. public uint RootILOffset; // Index of inline tree node containing the IL offset (0 for root) @@ -1341,8 +1338,6 @@ public struct AsyncSuspensionPoint public uint NumVars; // Count of AsyncContinuationVarInfo public uint NumContinuationVars; - // Index into array of AsyncContinuationVarInfo of first variable - public uint ContinuationVarsIndex; } public struct AsyncInfo From 6a5bf19d720b6e3b6404b2f1c9e4e3d66ff3b9ed Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Wed, 1 Oct 2025 21:44:02 +0200 Subject: [PATCH 05/51] Remove TODOs --- .../tools/superpmi/superpmi-shim-collector/icorjitinfo.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/coreclr/tools/superpmi/superpmi-shim-collector/icorjitinfo.cpp b/src/coreclr/tools/superpmi/superpmi-shim-collector/icorjitinfo.cpp index 7fc8560a236310..ba04575f0117c9 100644 --- a/src/coreclr/tools/superpmi/superpmi-shim-collector/icorjitinfo.cpp +++ b/src/coreclr/tools/superpmi/superpmi-shim-collector/icorjitinfo.cpp @@ -1229,7 +1229,7 @@ void interceptor_ICJI::reportRichMappings(ICorDebugInfo::InlineTreeNode* inli uint32_t numMappings) { mc->cr->AddCall("reportRichMappings"); - // TODO: record these mappings + // Compile output that we do not currently save original_ICorJitInfo->reportRichMappings(inlineTreeNodes, numInlineTreeNodes, mappings, numMappings); } @@ -1239,7 +1239,7 @@ void interceptor_ICJI::reportAsyncDebugInfo(ICorDebugInfo::AsyncInfo* uint32_t numVars) { mc->cr->AddCall("reportAsyncDebugInfo"); - // TODO: record async debug info + // Compile output that we do not currently save original_ICorJitInfo->reportAsyncDebugInfo(asyncInfo, suspensionPoints, vars, numVars); } From 262260a9f30e76e4dfc404bbcce4aefdf3b346f1 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Wed, 1 Oct 2025 21:46:58 +0200 Subject: [PATCH 06/51] Leaf transition --- src/coreclr/vm/jitinterface.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index 28ce25c3a2dbaf..f86721844a9823 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -11185,14 +11185,14 @@ void CEECodeGenInfo::reportAsyncDebugInfo( MODE_PREEMPTIVE; } CONTRACTL_END; - JIT_TO_EE_TRANSITION(); + JIT_TO_EE_TRANSITION_LEAF(); m_dbgAsyncInfo = *asyncInfo; m_dbgAsyncSuspensionPoints = suspensionPoints; m_dbgAsyncContinuationVars = vars; m_numAsyncContinuationVars = numVars; - EE_TO_JIT_TRANSITION(); + EE_TO_JIT_TRANSITION_LEAF(); } void CEECodeGenInfo::reportMetadata( From 4e2a829bca809a723002507e7a893429c2850cfb Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Wed, 1 Oct 2025 21:50:42 +0200 Subject: [PATCH 07/51] Comment --- src/coreclr/inc/cordebuginfo.h | 3 ++- src/coreclr/inc/corinfo.h | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/coreclr/inc/cordebuginfo.h b/src/coreclr/inc/cordebuginfo.h index a90a0289df50f1..1611dde47878f1 100644 --- a/src/coreclr/inc/cordebuginfo.h +++ b/src/coreclr/inc/cordebuginfo.h @@ -448,7 +448,8 @@ class ICorDebugInfo uint32_t Inlinee; // IL offset that resulted in the creation of the suspension point. uint32_t ILOffset; - // Count of AsyncContinuationVarInfo in array of locals + // Count of AsyncContinuationVarInfo in array of locals starting where + // the previous suspension point's locals end. uint32_t NumContinuationVars; }; diff --git a/src/coreclr/inc/corinfo.h b/src/coreclr/inc/corinfo.h index 36b4ab4925f0f9..5722f5a616f025 100644 --- a/src/coreclr/inc/corinfo.h +++ b/src/coreclr/inc/corinfo.h @@ -2918,8 +2918,8 @@ class ICorStaticInfo virtual void reportAsyncDebugInfo( ICorDebugInfo::AsyncInfo* asyncInfo, // [IN] Async method information ICorDebugInfo::AsyncSuspensionPoint* suspensionPoints, // [IN] Array of async suspension points, indexed by state number - ICorDebugInfo::AsyncContinuationVarInfo* vars, // [IN] Array of async continuation variable info - uint32_t numVars // [IN] Number of continuation variables + ICorDebugInfo::AsyncContinuationVarInfo* vars, // [IN] Array of async continuation variable info + uint32_t numVars // [IN] Number of entries in the async vars array ) = 0; // Report back some metadata about the compilation to the EE -- for From 820afa975b6d7ad58cc68376239c5f35dfd75abb Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Wed, 1 Oct 2025 21:52:32 +0200 Subject: [PATCH 08/51] Delete unused enum --- src/coreclr/vm/debuginfostore.cpp | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/coreclr/vm/debuginfostore.cpp b/src/coreclr/vm/debuginfostore.cpp index f97483662f9cd8..2cdfba389bc784 100644 --- a/src/coreclr/vm/debuginfostore.cpp +++ b/src/coreclr/vm/debuginfostore.cpp @@ -505,17 +505,6 @@ static void DoAsyncVars( } } -enum EXTRA_DEBUG_INFO_FLAGS -{ - // Debug info contains patchpoint information - EXTRA_DEBUG_INFO_PATCHPOINT = 1, - // Debug info contains rich information - EXTRA_DEBUG_INFO_RICH = 2, - // Debug info contains async information - EXTRA_DEBUG_INFO_ASYNC = 4, - EXTRA_DEBUG_INFO_UNINSTRUMENTED_BOUNDS = 8, -}; - #ifndef DACCESS_COMPILE BYTE* DecompressNew(void*, size_t cBytes) From 3621eae193ab6ee7773746375a94c57e84a590c7 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Wed, 1 Oct 2025 21:57:03 +0200 Subject: [PATCH 09/51] Restore comment --- src/coreclr/vm/debuginfostore.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/vm/debuginfostore.cpp b/src/coreclr/vm/debuginfostore.cpp index 2cdfba389bc784..ce5f482dc6b75f 100644 --- a/src/coreclr/vm/debuginfostore.cpp +++ b/src/coreclr/vm/debuginfostore.cpp @@ -991,7 +991,7 @@ PTR_BYTE CompressDebugInfo::Compress( if (isFat) { - w.WriteEncodedU32(DebugInfoFat); + w.WriteEncodedU32(DebugInfoFat);// 0xFFFFFFFF is used to indicate that this is a fat header w.WriteEncodedU32(cbBounds); w.WriteEncodedU32(cbVars); w.WriteEncodedU32(cbUninstrumentedBounds); From f597424a23a6edafc2d67212a0ae68964021b8b4 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Wed, 1 Oct 2025 22:10:42 +0200 Subject: [PATCH 10/51] Fix osx build --- src/coreclr/jit/async.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/coreclr/jit/async.cpp b/src/coreclr/jit/async.cpp index 76288114c9866b..7956f6f60fa275 100644 --- a/src/coreclr/jit/async.cpp +++ b/src/coreclr/jit/async.cpp @@ -2265,12 +2265,14 @@ void AsyncTransformation::ReportDebugInfo() ICorDebugInfo::AsyncSuspensionPoint* hostSuspensionPoints = static_cast(m_comp->info.compCompHnd->allocateArray( m_dbgSuspensionPoints.size() * sizeof(ICorDebugInfo::AsyncSuspensionPoint))); - std::copy(m_dbgSuspensionPoints.begin(), m_dbgSuspensionPoints.end(), hostSuspensionPoints); + for (size_t i = 0; i < m_dbgSuspensionPoints.size(); i++) + hostSuspensionPoints[i] = m_dbgSuspensionPoints[i]; ICorDebugInfo::AsyncContinuationVarInfo* hostVars = static_cast(m_comp->info.compCompHnd->allocateArray( m_dbgContinuationVars.size() * sizeof(ICorDebugInfo::AsyncContinuationVarInfo))); - std::copy(m_dbgContinuationVars.begin(), m_dbgContinuationVars.end(), hostVars); + for (size_t i = 0; i < m_dbgContinuationVars.size(); i++) + hostVars[i] = m_dbgContinuationVars[i]; m_comp->info.compCompHnd->reportAsyncDebugInfo(&asyncInfo, hostSuspensionPoints, hostVars, static_cast(m_dbgContinuationVars.size())); From 6bb6cee13e4d7e6c281268445a9e207ec828e458 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Wed, 1 Oct 2025 22:45:30 +0200 Subject: [PATCH 11/51] Fix GCC build --- src/coreclr/jit/async.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/jit/async.cpp b/src/coreclr/jit/async.cpp index 7956f6f60fa275..1e560842352827 100644 --- a/src/coreclr/jit/async.cpp +++ b/src/coreclr/jit/async.cpp @@ -2226,7 +2226,7 @@ void AsyncTransformation::CreateDebugInfoForSuspensionPoint(GenTreeCall* asyncCa for (const LiveLocalInfo& local : layout.Locals) { unsigned ilVarNum = m_comp->compMap2ILvarNum(local.LclNum); - if (ilVarNum == ICorDebugInfo::UNKNOWN_ILNUM) + if (ilVarNum == (unsigned)ICorDebugInfo::UNKNOWN_ILNUM) { continue; } From f580e3f7175c933128c8ca950d72b6539e376826 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Thu, 2 Oct 2025 12:17:56 +0200 Subject: [PATCH 12/51] Change sentinel value, fix contract --- src/coreclr/vm/debuginfostore.cpp | 17 +++++++++-------- src/coreclr/vm/debuginfostore.h | 4 ++-- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/coreclr/vm/debuginfostore.cpp b/src/coreclr/vm/debuginfostore.cpp index ce5f482dc6b75f..d0479081d72f4e 100644 --- a/src/coreclr/vm/debuginfostore.cpp +++ b/src/coreclr/vm/debuginfostore.cpp @@ -984,6 +984,7 @@ PTR_BYTE CompressDebugInfo::Compress( NibbleWriter w; bool isFat = + (cbBounds == DebugInfoFat) || (cbPatchpointInfo > 0) || (cbRichDebugInfo > 0) || (cbAsyncInfo > 0) || @@ -991,7 +992,7 @@ PTR_BYTE CompressDebugInfo::Compress( if (isFat) { - w.WriteEncodedU32(DebugInfoFat);// 0xFFFFFFFF is used to indicate that this is a fat header + w.WriteEncodedU32(DebugInfoFat); // Indicator that this is a fat header w.WriteEncodedU32(cbBounds); w.WriteEncodedU32(cbVars); w.WriteEncodedU32(cbUninstrumentedBounds); @@ -1150,7 +1151,7 @@ static void DoBounds(PTR_BYTE addrBounds, uint32_t cbBounds, TNumBounds countHan // Uncompression (restore) routines //----------------------------------------------------------------------------- -DebugInfoChunks CompressDebugInfo::Restore(IN PTR_BYTE pDebugInfo) +DebugInfoChunks CompressDebugInfo::DecodeChunks(IN PTR_BYTE pDebugInfo) { CONTRACTL { @@ -1223,7 +1224,7 @@ void CompressDebugInfo::RestoreBoundariesAndVars( if (pcVars != NULL) *pcVars = 0; if (ppVars != NULL) *ppVars = NULL; - DebugInfoChunks chunks = Restore(pDebugInfo); + DebugInfoChunks chunks = DecodeChunks(pDebugInfo); PTR_BYTE addrBounds = chunks.pBounds; unsigned cbBounds = chunks.cbBounds; @@ -1309,7 +1310,7 @@ size_t CompressDebugInfo::WalkILOffsets( } CONTRACTL_END; - DebugInfoChunks chunks = Restore(pDebugInfo); + DebugInfoChunks chunks = DecodeChunks(pDebugInfo); PTR_BYTE addrBounds = chunks.pBounds; unsigned cbBounds = chunks.cbBounds; @@ -1358,14 +1359,14 @@ PatchpointInfo * CompressDebugInfo::RestorePatchpointInfo(IN PTR_BYTE pDebugInfo { CONTRACTL { - NOTHROW; + THROWS; GC_NOTRIGGER; MODE_ANY; SUPPORTS_DAC; } CONTRACTL_END; - DebugInfoChunks chunks = Restore(pDebugInfo); + DebugInfoChunks chunks = DecodeChunks(pDebugInfo); if (chunks.cbPatchpointInfo == 0) return NULL; @@ -1392,7 +1393,7 @@ void CompressDebugInfo::RestoreRichDebugInfo( } CONTRACTL_END; - DebugInfoChunks chunks = Restore(pDebugInfo); + DebugInfoChunks chunks = DecodeChunks(pDebugInfo); NibbleReader r(chunks.pRichDebugInfo, chunks.cbRichDebugInfo); @@ -1427,7 +1428,7 @@ void CompressDebugInfo::EnumMemoryRegions(CLRDataEnumMemoryFlags flags, PTR_BYTE PTR_BYTE pStart = pDebugInfo; - DebugInfoChunks chunks = Restore(pDebugInfo); + DebugInfoChunks chunks = DecodeChunks(pDebugInfo); // NibbleReader reads in units of sizeof(NibbleChunkType) // So we need to account for any partial chunk at the end. diff --git a/src/coreclr/vm/debuginfostore.h b/src/coreclr/vm/debuginfostore.h index 916203d8dd46d1..8a56b5b0a3300b 100644 --- a/src/coreclr/vm/debuginfostore.h +++ b/src/coreclr/vm/debuginfostore.h @@ -111,7 +111,7 @@ class CompressDebugInfo IN ULONG iAsyncVars, IN OUT NibbleWriter* pWriter); - static DebugInfoChunks Restore(IN PTR_BYTE pDebugInfo); + static DebugInfoChunks DecodeChunks(IN PTR_BYTE pDebugInfo); public: // Stores the result in LoaderHeap @@ -204,6 +204,6 @@ class DebugInfoManager #endif }; -#define DebugInfoFat 0xFFFFFFFF +#define DebugInfoFat 0 #endif // __DebugInfoStore_H_ From 55b952239fecae6f36812711f5627901dac8aa8f Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Thu, 2 Oct 2025 13:41:48 +0200 Subject: [PATCH 13/51] Bump R2R --- src/coreclr/inc/readytorun.h | 5 ++-- .../Common/Internal/Runtime/ModuleHeaders.cs | 2 +- .../ReadyToRun/DebugInfoTableNode.cs | 22 +++++++++++++++-- .../JitInterface/CorInfoImpl.ReadyToRun.cs | 12 ++-------- .../DebugInfo.cs | 24 +++++++++++++++++-- 5 files changed, 48 insertions(+), 17 deletions(-) diff --git a/src/coreclr/inc/readytorun.h b/src/coreclr/inc/readytorun.h index 134b685ff9fbe6..93826f5b8ca771 100644 --- a/src/coreclr/inc/readytorun.h +++ b/src/coreclr/inc/readytorun.h @@ -19,10 +19,10 @@ // src/coreclr/nativeaot/Runtime/inc/ModuleHeaders.h // If you update this, ensure you run `git grep MINIMUM_READYTORUN_MAJOR_VERSION` // and handle pending work. -#define READYTORUN_MAJOR_VERSION 16 +#define READYTORUN_MAJOR_VERSION 17 #define READYTORUN_MINOR_VERSION 0x0000 -#define MINIMUM_READYTORUN_MAJOR_VERSION 16 +#define MINIMUM_READYTORUN_MAJOR_VERSION 17 // R2R Version 2.1 adds the InliningInfo section // R2R Version 2.2 adds the ProfileDataInfo section @@ -47,6 +47,7 @@ // R2R Version 14 changed x86 code generation to use funclets // R2R Version 15 removes double to int/uint helper calls // R2R Version 16 replaces the compression format for debug boundaries with a new format that is smaller and more efficient to parse +// R2R Version 17 adds support for producing "fat" debug information (that e.g. can include async debug info) struct READYTORUN_CORE_HEADER { diff --git a/src/coreclr/tools/Common/Internal/Runtime/ModuleHeaders.cs b/src/coreclr/tools/Common/Internal/Runtime/ModuleHeaders.cs index 1ea99252052d90..090d62bad8cc17 100644 --- a/src/coreclr/tools/Common/Internal/Runtime/ModuleHeaders.cs +++ b/src/coreclr/tools/Common/Internal/Runtime/ModuleHeaders.cs @@ -15,7 +15,7 @@ internal struct ReadyToRunHeaderConstants { public const uint Signature = 0x00525452; // 'RTR' - public const ushort CurrentMajorVersion = 16; + public const ushort CurrentMajorVersion = 17; public const ushort CurrentMinorVersion = 0; } #if READYTORUN diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/DebugInfoTableNode.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/DebugInfoTableNode.cs index b809ef074b9af0..d1318d881bd74e 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/DebugInfoTableNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/DebugInfoTableNode.cs @@ -90,8 +90,24 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) byte[] vars = method.DebugVarInfos; NibbleWriter nibbleWriter = new NibbleWriter(); - nibbleWriter.WriteUInt((uint)(bounds?.Length ?? 0)); - nibbleWriter.WriteUInt((uint)(vars?.Length ?? 0)); + uint boundsLength = (uint)(bounds?.Length ?? 0); + bool isFatHeader = boundsLength == DebugInfoFat; + + if (isFatHeader) + { + nibbleWriter.WriteUInt(DebugInfoFat); + nibbleWriter.WriteUInt(boundsLength); + nibbleWriter.WriteUInt((uint)(vars?.Length ?? 0)); + nibbleWriter.WriteUInt(0); // cbUninstrumentedBounds + nibbleWriter.WriteUInt(0); // cbPatchpointInfo + nibbleWriter.WriteUInt(0); // cbRichDebugInfo + nibbleWriter.WriteUInt(0); // cbAsyncInfo + } + else + { + nibbleWriter.WriteUInt(boundsLength); + nibbleWriter.WriteUInt((uint)(vars?.Length ?? 0)); + } byte[] header = nibbleWriter.ToArray(); methodDebugBlob.Write(header, 0, header.Length); @@ -307,5 +323,7 @@ static void WriteEncodedStackOffset(NibbleWriter _writer, int offset, bool assum return writer.ToArray(); } + + private const int DebugInfoFat = 0; } } diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs index 92302c2181d398..461fbb8fb68a6b 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs @@ -1506,11 +1506,7 @@ private void setVars(CORINFO_METHOD_STRUCT_* ftn, uint cVars, NativeVarInfo* var if (cVars == 0) return; - _debugVarInfos = new NativeVarInfo[cVars]; - for (int i = 0; i < cVars; i++) - { - _debugVarInfos[i] = vars[i]; - } + _debugVarInfos = new Span(vars, (int)cVars).ToArray(); // JIT gave the ownership of this to us, so need to free this. freeArray(vars); @@ -1523,11 +1519,7 @@ private void setBoundaries(CORINFO_METHOD_STRUCT_* ftn, uint cMap, OffsetMapping { Debug.Assert(_debugLocInfos == null); - _debugLocInfos = new OffsetMapping[cMap]; - for (int i = 0; i < cMap; i++) - { - _debugLocInfos[i] = pMap[i]; - } + _debugLocInfos = new Span(pMap, (int)cMap).ToArray(); // JIT gave the ownership of this to us, so need to free this. freeArray(pMap); diff --git a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/DebugInfo.cs b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/DebugInfo.cs index 019b74c714b3a9..e17638bcb36b23 100644 --- a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/DebugInfo.cs +++ b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/DebugInfo.cs @@ -116,8 +116,28 @@ private void EnsureInitialized() } NibbleReader reader = new NibbleReader(imageReader, (int)debugInfoOffset); - uint boundsByteCount = reader.ReadUInt(); - uint variablesByteCount = reader.ReadUInt(); + + uint boundsByteCountOrIndicator = reader.ReadUInt(); + + uint boundsByteCount = 0; + uint variablesByteCount = 0; + + const int DebugInfoFat = 0; + if (_runtimeFunction.ReadyToRunReader.ReadyToRunHeader.MajorVersion >= 17 && boundsByteCountOrIndicator == DebugInfoFat) + { + boundsByteCount = reader.ReadUInt(); + variablesByteCount = reader.ReadUInt(); + reader.ReadUInt(); // uninstrumented bounds + reader.ReadUInt(); // patchpoint info + reader.ReadUInt(); // rich debug info + reader.ReadUInt(); // async info + } + else + { + boundsByteCount = boundsByteCountOrIndicator; + variablesByteCount = reader.ReadUInt(); + } + int boundsOffset = reader.GetNextByteOffset(); int variablesOffset = (int)(boundsOffset + boundsByteCount); From c9c64be687f923c47a9ea8e9754560df81890d4b Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Thu, 2 Oct 2025 14:11:43 +0200 Subject: [PATCH 14/51] Clean up --- src/coreclr/vm/codeman.cpp | 12 +++--------- src/coreclr/vm/debuginfostore.cpp | 2 +- src/coreclr/vm/debuginfostore.h | 2 +- 3 files changed, 5 insertions(+), 11 deletions(-) diff --git a/src/coreclr/vm/codeman.cpp b/src/coreclr/vm/codeman.cpp index 18a2559db515f9..7fdd758de949b5 100644 --- a/src/coreclr/vm/codeman.cpp +++ b/src/coreclr/vm/codeman.cpp @@ -4207,15 +4207,9 @@ void CodeHeader::EnumMemoryRegions(CLRDataEnumMemoryFlags flags, IJitManager* pJ } #endif // FEATURE_EH_FUNCLETS -#ifdef FEATURE_ON_STACK_REPLACEMENT - BOOL hasFlagByte = TRUE; -#else - BOOL hasFlagByte = FALSE; -#endif - if (this->GetDebugInfo() != NULL) { - CompressDebugInfo::EnumMemoryRegions(flags, this->GetDebugInfo(), hasFlagByte); + CompressDebugInfo::EnumMemoryRegions(flags, this->GetDebugInfo()); } } @@ -4271,7 +4265,7 @@ void InterpreterCodeHeader::EnumMemoryRegions(CLRDataEnumMemoryFlags flags, IJit if (this->GetDebugInfo() != NULL) { - CompressDebugInfo::EnumMemoryRegions(flags, this->GetDebugInfo(), FALSE /* hasFlagByte */); + CompressDebugInfo::EnumMemoryRegions(flags, this->GetDebugInfo()); } } @@ -6482,7 +6476,7 @@ void ReadyToRunJitManager::EnumMemoryRegionsForMethodDebugInfo(CLRDataEnumMemory if (pDebugInfo == NULL) return; - CompressDebugInfo::EnumMemoryRegions(flags, pDebugInfo, FALSE); + CompressDebugInfo::EnumMemoryRegions(flags, pDebugInfo); } #endif diff --git a/src/coreclr/vm/debuginfostore.cpp b/src/coreclr/vm/debuginfostore.cpp index d0479081d72f4e..8cd10c29315c11 100644 --- a/src/coreclr/vm/debuginfostore.cpp +++ b/src/coreclr/vm/debuginfostore.cpp @@ -1416,7 +1416,7 @@ void CompressDebugInfo::RestoreRichDebugInfo( } #ifdef DACCESS_COMPILE -void CompressDebugInfo::EnumMemoryRegions(CLRDataEnumMemoryFlags flags, PTR_BYTE pDebugInfo, BOOL hasFlagByte) +void CompressDebugInfo::EnumMemoryRegions(CLRDataEnumMemoryFlags flags, PTR_BYTE pDebugInfo) { CONTRACTL { diff --git a/src/coreclr/vm/debuginfostore.h b/src/coreclr/vm/debuginfostore.h index 8a56b5b0a3300b..e926293ea6e1f2 100644 --- a/src/coreclr/vm/debuginfostore.h +++ b/src/coreclr/vm/debuginfostore.h @@ -169,7 +169,7 @@ class CompressDebugInfo OUT ULONG32* pNumRichMappings); #ifdef DACCESS_COMPILE - static void EnumMemoryRegions(CLRDataEnumMemoryFlags flags, PTR_BYTE pDebugInfo, BOOL hasFlagByte); + static void EnumMemoryRegions(CLRDataEnumMemoryFlags flags, PTR_BYTE pDebugInfo); #endif }; From db77446d9894c04dc04f7deeef93f336873f7e24 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Thu, 2 Oct 2025 14:54:58 +0200 Subject: [PATCH 15/51] Expose async debug info accessor APIs --- src/coreclr/vm/codeman.cpp | 133 +++++++++++++++++++++++++++++- src/coreclr/vm/codeman.h | 40 +++++++++ src/coreclr/vm/debuginfostore.cpp | 81 ++++++++++++++++++ src/coreclr/vm/debuginfostore.h | 17 ++++ 4 files changed, 270 insertions(+), 1 deletion(-) diff --git a/src/coreclr/vm/codeman.cpp b/src/coreclr/vm/codeman.cpp index 7fdd758de949b5..3753492d82b352 100644 --- a/src/coreclr/vm/codeman.cpp +++ b/src/coreclr/vm/codeman.cpp @@ -4099,6 +4099,56 @@ BOOL EEJitManager::GetRichDebugInfo( return GetRichDebugInfoWorker(pDebugInfo, fpNew, pNewData, ppInlineTree, pNumInlineTree, ppRichMappings, pNumRichMappings); } +BOOL EECodeGenManager::GetAsyncDebugInfoWorker( + PTR_BYTE pDebugInfo, + IN FP_IDS_NEW fpNew, IN void* pNewData, + OUT ICorDebugInfo::AsyncInfo* pAsyncInfo, + OUT ICorDebugInfo::AsyncSuspensionPoint** ppSuspensionPoints, + OUT ICorDebugInfo::AsyncContinuationVarInfo** ppAsyncVars, + OUT ULONG32* pcAsyncVars) +{ + CONTRACTL { + THROWS; // on OOM. + GC_NOTRIGGER; // getting debug info shouldn't trigger + SUPPORTS_DAC; + } CONTRACTL_END; + + // No header created, which means no jit information is available. + if (pDebugInfo == NULL) + return FALSE; + + CompressDebugInfo::RestoreAsyncDebugInfo( + fpNew, pNewData, + pDebugInfo, + pAsyncInfo, + ppSuspensionPoints, + ppAsyncVars, pcAsyncVars); + + return TRUE; +} + +BOOL EEJitManager::GetAsyncDebugInfo( + const DebugInfoRequest & request, + IN FP_IDS_NEW fpNew, IN void * pNewData, + OUT ICorDebugInfo::AsyncInfo* pAsyncInfo, + OUT ICorDebugInfo::AsyncSuspensionPoint** ppSuspensionPoints, + OUT ICorDebugInfo::AsyncContinuationVarInfo** ppAsyncVars, + OUT ULONG32* pcAsyncVars) +{ + CONTRACTL { + THROWS; // on OOM. + GC_NOTRIGGER; // getting debug info shouldn't trigger + SUPPORTS_DAC; + } CONTRACTL_END; + + CodeHeader * pHdr = GetCodeHeaderFromDebugInfoRequest(request); + _ASSERTE(pHdr != NULL); + + PTR_BYTE pDebugInfo = pHdr->GetDebugInfo(); + + return GetAsyncDebugInfoWorker(pDebugInfo, fpNew, pNewData, pAsyncInfo, ppSuspensionPoints, ppAsyncVars, pcAsyncVars); +} + #ifdef FEATURE_INTERPRETER BOOL InterpreterJitManager::GetBoundariesAndVars( const DebugInfoRequest & request, @@ -4182,6 +4232,29 @@ BOOL InterpreterJitManager::GetRichDebugInfo( return GetRichDebugInfoWorker(pDebugInfo, fpNew, pNewData, ppInlineTree, pNumInlineTree, ppRichMappings, pNumRichMappings); } + +BOOL InterpreterJitManager::GetAsyncDebugInfo( + const DebugInfoRequest & request, + IN FP_IDS_NEW fpNew, IN void * pNewData, + OUT ICorDebugInfo::AsyncInfo* pAsyncInfo, + OUT ICorDebugInfo::AsyncSuspensionPoint** ppSuspensionPoints, + OUT ICorDebugInfo::AsyncContinuationVarInfo** ppAsyncVars, + OUT ULONG32* pcAsyncVars) +{ + CONTRACTL { + THROWS; // on OOM. + GC_NOTRIGGER; // getting debug info shouldn't trigger + SUPPORTS_DAC; + } CONTRACTL_END; + + InterpreterCodeHeader * pHdr = GetCodeHeaderFromDebugInfoRequest(request); + _ASSERTE(pHdr != NULL); + + PTR_BYTE pDebugInfo = pHdr->GetDebugInfo(); + + return GetAsyncDebugInfoWorker(pDebugInfo, fpNew, pNewData, pAsyncInfo, ppSuspensionPoints, ppAsyncVars, pcAsyncVars); +} + #endif // FEATURE_INTERPRETER #ifdef DACCESS_COMPILE @@ -6455,7 +6528,65 @@ BOOL ReadyToRunJitManager::GetRichDebugInfo( OUT ICorDebugInfo::RichOffsetMapping** ppRichMappings, OUT ULONG32* pNumRichMappings) { - return FALSE; + CONTRACTL { + THROWS; // on OOM. + GC_NOTRIGGER; // getting vars shouldn't trigger + SUPPORTS_DAC; + } CONTRACTL_END; + + EECodeInfo codeInfo(request.GetStartAddress()); + if (!codeInfo.IsValid()) + return FALSE; + + ReadyToRunInfo * pReadyToRunInfo = JitTokenToReadyToRunInfo(codeInfo.GetMethodToken()); + PTR_RUNTIME_FUNCTION pRuntimeFunction = JitTokenToRuntimeFunction(codeInfo.GetMethodToken()); + + PTR_BYTE pDebugInfo = pReadyToRunInfo->GetDebugInfo(pRuntimeFunction); + if (pDebugInfo == NULL) + return FALSE; + + CompressDebugInfo::RestoreRichDebugInfo( + fpNew, pNewData, + pDebugInfo, + ppInlineTree, pNumInlineTree, + ppRichMappings, pNumRichMappings); + + return TRUE; +} + +BOOL ReadyToRunJitManager::GetAsyncDebugInfo( + const DebugInfoRequest & request, + IN FP_IDS_NEW fpNew, IN void * pNewData, + OUT ICorDebugInfo::AsyncInfo* pAsyncInfo, + OUT ICorDebugInfo::AsyncSuspensionPoint** ppSuspensionPoints, + OUT ICorDebugInfo::AsyncContinuationVarInfo** ppAsyncVars, + OUT ULONG32* pcAsyncVars) +{ + CONTRACTL { + THROWS; // on OOM. + GC_NOTRIGGER; // getting vars shouldn't trigger + SUPPORTS_DAC; + } CONTRACTL_END; + + EECodeInfo codeInfo(request.GetStartAddress()); + if (!codeInfo.IsValid()) + return FALSE; + + ReadyToRunInfo * pReadyToRunInfo = JitTokenToReadyToRunInfo(codeInfo.GetMethodToken()); + PTR_RUNTIME_FUNCTION pRuntimeFunction = JitTokenToRuntimeFunction(codeInfo.GetMethodToken()); + + PTR_BYTE pDebugInfo = pReadyToRunInfo->GetDebugInfo(pRuntimeFunction); + if (pDebugInfo == NULL) + return FALSE; + + CompressDebugInfo::RestoreAsyncDebugInfo( + fpNew, pNewData, + pDebugInfo, + pAsyncInfo, + ppSuspensionPoints, + ppAsyncVars, pcAsyncVars); + + return TRUE; } #ifdef DACCESS_COMPILE diff --git a/src/coreclr/vm/codeman.h b/src/coreclr/vm/codeman.h index 8d4c3fb425f4fb..1070db939ff923 100644 --- a/src/coreclr/vm/codeman.h +++ b/src/coreclr/vm/codeman.h @@ -1716,6 +1716,14 @@ class IJitManager OUT ICorDebugInfo::RichOffsetMapping** ppRichMappings, OUT ULONG32* pNumRichMappings) = 0; + virtual BOOL GetAsyncDebugInfo( + const DebugInfoRequest & request, + IN FP_IDS_NEW fpNew, IN void * pNewData, + OUT ICorDebugInfo::AsyncInfo* pAsyncInfo, + OUT ICorDebugInfo::AsyncSuspensionPoint** ppSuspensionPoints, + OUT ICorDebugInfo::AsyncContinuationVarInfo** ppAsyncVars, + OUT ULONG32* pcAsyncVars) = 0; + virtual PCODE GetCodeAddressForRelOffset(const METHODTOKEN& MethodToken, DWORD relOffset) = 0; virtual BOOL JitCodeToMethodInfo( @@ -1901,6 +1909,14 @@ class EECodeGenManager : public IJitManager OUT ICorDebugInfo::RichOffsetMapping** ppRichMappings, OUT ULONG32* pNumRichMappings); + BOOL GetAsyncDebugInfoWorker( + PTR_BYTE pDebugInfo, + IN FP_IDS_NEW fpNew, IN void* pNewData, + OUT ICorDebugInfo::AsyncInfo* pAsyncInfo, + OUT ICorDebugInfo::AsyncSuspensionPoint** ppSuspensionPoints, + OUT ICorDebugInfo::AsyncContinuationVarInfo** ppAsyncVars, + OUT ULONG32* pcAsyncVars); + template BOOL JitCodeToMethodInfoWorker( RangeSection * pRangeSection, @@ -2116,6 +2132,14 @@ class EEJitManager final : public EECodeGenManager OUT ICorDebugInfo::RichOffsetMapping** ppRichMappings, OUT ULONG32* pNumRichMappings); + virtual BOOL GetAsyncDebugInfo( + const DebugInfoRequest & request, + IN FP_IDS_NEW fpNew, IN void * pNewData, + OUT ICorDebugInfo::AsyncInfo* pAsyncInfo, + OUT ICorDebugInfo::AsyncSuspensionPoint** ppSuspensionPoints, + OUT ICorDebugInfo::AsyncContinuationVarInfo** ppAsyncVars, + OUT ULONG32* pcAsyncVars); + virtual PCODE GetCodeAddressForRelOffset(const METHODTOKEN& MethodToken, DWORD relOffset); virtual BOOL JitCodeToMethodInfo(RangeSection * pRangeSection, @@ -2668,6 +2692,14 @@ class ReadyToRunJitManager final : public IJitManager OUT ICorDebugInfo::RichOffsetMapping** ppRichMappings, OUT ULONG32* pNumRichMappings); + virtual BOOL GetAsyncDebugInfo( + const DebugInfoRequest & request, + IN FP_IDS_NEW fpNew, IN void * pNewData, + OUT ICorDebugInfo::AsyncInfo* pAsyncInfo, + OUT ICorDebugInfo::AsyncSuspensionPoint** ppSuspensionPoints, + OUT ICorDebugInfo::AsyncContinuationVarInfo** ppAsyncVars, + OUT ULONG32* pcAsyncVars); + virtual BOOL JitCodeToMethodInfo(RangeSection * pRangeSection, PCODE currentPC, MethodDesc** ppMethodDesc, @@ -2795,6 +2827,14 @@ class InterpreterJitManager final : public EECodeGenManager OUT ICorDebugInfo::RichOffsetMapping** ppRichMappings, OUT ULONG32* pNumRichMappings); + virtual BOOL GetAsyncDebugInfo( + const DebugInfoRequest & request, + IN FP_IDS_NEW fpNew, IN void * pNewData, + OUT ICorDebugInfo::AsyncInfo* pAsyncInfo, + OUT ICorDebugInfo::AsyncSuspensionPoint** ppSuspensionPoints, + OUT ICorDebugInfo::AsyncContinuationVarInfo** ppAsyncVars, + OUT ULONG32* pcAsyncVars); + #ifndef DACCESS_COMPILE virtual TypeHandle ResolveEHClause(EE_ILEXCEPTION_CLAUSE* pEHClause, CrawlFrame *pCf); diff --git a/src/coreclr/vm/debuginfostore.cpp b/src/coreclr/vm/debuginfostore.cpp index 8cd10c29315c11..f743a60eb7cd83 100644 --- a/src/coreclr/vm/debuginfostore.cpp +++ b/src/coreclr/vm/debuginfostore.cpp @@ -1395,6 +1395,15 @@ void CompressDebugInfo::RestoreRichDebugInfo( DebugInfoChunks chunks = DecodeChunks(pDebugInfo); + if (chunks.cbRichDebugInfo == 0) + { + *ppInlineTree = NULL; + *pNumInlineTree = 0; + *ppRichMappings = NULL; + *pNumRichMappings = 0; + return; + } + NibbleReader r(chunks.pRichDebugInfo, chunks.cbRichDebugInfo); *pNumInlineTree = r.ReadEncodedU32(); @@ -1415,6 +1424,53 @@ void CompressDebugInfo::RestoreRichDebugInfo( DoRichOffsetMappings(t, *pNumRichMappings, *ppRichMappings); } +void CompressDebugInfo::RestoreAsyncDebugInfo( + IN FP_IDS_NEW fpNew, + IN void* pNewData, + IN PTR_BYTE pDebugInfo, + OUT ICorDebugInfo::AsyncInfo* pAsyncInfo, + OUT ICorDebugInfo::AsyncSuspensionPoint** ppSuspensionPoints, + OUT ICorDebugInfo::AsyncContinuationVarInfo** ppAsyncVars, + OUT ULONG32* pNumAsyncVars) +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + + DebugInfoChunks chunks = DecodeChunks(pDebugInfo); + + if (chunks.cbAsyncInfo == 0) + { + *pAsyncInfo = {}; + *ppSuspensionPoints = NULL; + *ppAsyncVars = NULL; + *pNumAsyncVars = 0; + return; + } + + NibbleReader r(chunks.pAsyncInfo, chunks.cbAsyncInfo); + pAsyncInfo->NumSuspensionPoints = r.ReadEncodedU32(); + *pNumAsyncVars = r.ReadEncodedU32(); + + UINT32 cbSuspPoints = pAsyncInfo->NumSuspensionPoints * sizeof(ICorDebugInfo::AsyncSuspensionPoint); + *ppSuspensionPoints = reinterpret_cast(fpNew(pNewData, cbSuspPoints)); + if (*ppSuspensionPoints == NULL) + ThrowOutOfMemory(); + + UINT32 cbAsyncVars = *pNumAsyncVars * sizeof(ICorDebugInfo::AsyncContinuationVarInfo); + *ppAsyncVars = reinterpret_cast(fpNew(pNewData, cbAsyncVars)); + if (*ppAsyncVars == NULL) + ThrowOutOfMemory(); + + TransferReader t(r); + DoAsyncSuspensionPoints(t, pAsyncInfo->NumSuspensionPoints, *ppSuspensionPoints); + DoAsyncVars(t, *pNumAsyncVars, *ppAsyncVars); +} + #ifdef DACCESS_COMPILE void CompressDebugInfo::EnumMemoryRegions(CLRDataEnumMemoryFlags flags, PTR_BYTE pDebugInfo) { @@ -1513,6 +1569,31 @@ BOOL DebugInfoManager::GetRichDebugInfo( return pJitMan->GetRichDebugInfo(request, fpNew, pNewData, ppInlineTree, pNumInlineTree, ppRichMappings, pNumRichMappings); } +BOOL DebugInfoManager::GetAsyncDebugInfo( + const DebugInfoRequest & request, + IN FP_IDS_NEW fpNew, IN void * pNewData, + OUT ICorDebugInfo::AsyncInfo* pAsyncInfo, + OUT ICorDebugInfo::AsyncSuspensionPoint** ppSuspensionPoints, + OUT ICorDebugInfo::AsyncContinuationVarInfo** ppAsyncVars, + OUT ULONG32* pcAsyncVars) +{ + CONTRACTL + { + THROWS; + WRAPPER(GC_TRIGGERS); // depends on fpNew + SUPPORTS_DAC; + } + CONTRACTL_END; + + IJitManager* pJitMan = ExecutionManager::FindJitMan(request.GetStartAddress()); + if (pJitMan == NULL) + { + return FALSE; // no info available. + } + + return pJitMan->GetAsyncDebugInfo(request, fpNew, pNewData, pAsyncInfo, ppSuspensionPoints, ppAsyncVars, pcAsyncVars); +} + #ifdef DACCESS_COMPILE void DebugInfoManager::EnumMemoryRegionsForMethodDebugInfo(CLRDataEnumMemoryFlags flags, EECodeInfo * pCodeInfo) { diff --git a/src/coreclr/vm/debuginfostore.h b/src/coreclr/vm/debuginfostore.h index e926293ea6e1f2..abeac114249964 100644 --- a/src/coreclr/vm/debuginfostore.h +++ b/src/coreclr/vm/debuginfostore.h @@ -168,6 +168,15 @@ class CompressDebugInfo OUT ICorDebugInfo::RichOffsetMapping** ppRichMappings, OUT ULONG32* pNumRichMappings); + static void RestoreAsyncDebugInfo( + IN FP_IDS_NEW fpNew, + IN void* pNewData, + IN PTR_BYTE pDebugInfo, + OUT ICorDebugInfo::AsyncInfo* pAsyncInfo, + OUT ICorDebugInfo::AsyncSuspensionPoint** ppSuspensionPoints, + OUT ICorDebugInfo::AsyncContinuationVarInfo** ppAsyncVars, + OUT ULONG32* pNumAsyncVars); + #ifdef DACCESS_COMPILE static void EnumMemoryRegions(CLRDataEnumMemoryFlags flags, PTR_BYTE pDebugInfo); #endif @@ -199,6 +208,14 @@ class DebugInfoManager OUT ICorDebugInfo::RichOffsetMapping** ppRichMappings, OUT ULONG32* pNumRichMappings); + static BOOL GetAsyncDebugInfo( + const DebugInfoRequest & request, + IN FP_IDS_NEW fpNew, IN void * pNewData, + OUT ICorDebugInfo::AsyncInfo* pAsyncInfo, + OUT ICorDebugInfo::AsyncSuspensionPoint** ppSuspensionPoints, + OUT ICorDebugInfo::AsyncContinuationVarInfo** ppAsyncVars, + OUT ULONG32* pcAsyncVars); + #ifdef DACCESS_COMPILE static void EnumMemoryRegionsForMethodDebugInfo(CLRDataEnumMemoryFlags flags, EECodeInfo * pCodeInfo); #endif From c808390dd2ce5f174dddc388b7f85a834f3ef352 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Thu, 2 Oct 2025 16:18:46 +0200 Subject: [PATCH 16/51] Missed bumping R2R version for naot --- src/coreclr/nativeaot/Runtime/inc/ModuleHeaders.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/nativeaot/Runtime/inc/ModuleHeaders.h b/src/coreclr/nativeaot/Runtime/inc/ModuleHeaders.h index 59ea64bf324bb0..f634629cfbada3 100644 --- a/src/coreclr/nativeaot/Runtime/inc/ModuleHeaders.h +++ b/src/coreclr/nativeaot/Runtime/inc/ModuleHeaders.h @@ -11,7 +11,7 @@ struct ReadyToRunHeaderConstants { static const uint32_t Signature = 0x00525452; // 'RTR' - static const uint32_t CurrentMajorVersion = 16; + static const uint32_t CurrentMajorVersion = 17; static const uint32_t CurrentMinorVersion = 0; }; From 3ec516024c74129db7f55a76d79cdc38625f2201 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Thu, 2 Oct 2025 16:46:53 +0200 Subject: [PATCH 17/51] Fix reverse mapping to IL local nums --- src/coreclr/jit/lclvars.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/coreclr/jit/lclvars.cpp b/src/coreclr/jit/lclvars.cpp index 4e6b559c989baf..7f5152fc41a9e7 100644 --- a/src/coreclr/jit/lclvars.cpp +++ b/src/coreclr/jit/lclvars.cpp @@ -1139,6 +1139,11 @@ unsigned Compiler::compMap2ILvarNum(unsigned varNum) const varNum--; } + if (lvaAsyncContinuationArg != BAD_VAR_NUM && varNum > lvaAsyncContinuationArg) + { + varNum--; + } + /* Is there a hidden argument for the return buffer. Note that this code works because if the RetBuffArg is not present, compRetBuffArg will be BAD_VAR_NUM */ From bb12c776044f311895dbc6c3a94a7c02a471e498 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Thu, 2 Oct 2025 16:47:15 +0200 Subject: [PATCH 18/51] Fix monotonicity for async vars --- src/coreclr/vm/debuginfostore.cpp | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/src/coreclr/vm/debuginfostore.cpp b/src/coreclr/vm/debuginfostore.cpp index f743a60eb7cd83..5014d2e200ef28 100644 --- a/src/coreclr/vm/debuginfostore.cpp +++ b/src/coreclr/vm/debuginfostore.cpp @@ -490,19 +490,26 @@ static void DoAsyncSuspensionPoints( template static void DoAsyncVars( T trans, + ULONG32 cSuspensionPoints, + ICorDebugInfo::AsyncSuspensionPoint* suspensionPoints, ULONG32 cVars, ICorDebugInfo::AsyncContinuationVarInfo* vars) { - uint32_t lastOffset = 0; - for (uint32_t i = 0; i < cVars; i++) + uint32_t varIndex = 0; + for (uint32_t i = 0; i < cSuspensionPoints; i++) { - ICorDebugInfo::AsyncContinuationVarInfo* var = &vars[i]; - - trans.DoEncodedAdjustedU32(var->VarNumber, (DWORD) ICorDebugInfo::MAX_ILNUM); - - trans.DoEncodedDeltaU32(var->Offset, lastOffset); - lastOffset = var->Offset; + uint32_t lastOffset = 0; + uint32_t numVars = suspensionPoints[i].NumContinuationVars; + for (uint32_t j = 0; j < numVars; j++) + { + ICorDebugInfo::AsyncContinuationVarInfo* var = &vars[varIndex++]; + trans.DoEncodedAdjustedU32(var->VarNumber, (DWORD) ICorDebugInfo::MAX_ILNUM); + trans.DoEncodedDeltaU32(var->Offset, lastOffset); + lastOffset = var->Offset; + } } + + _ASSERTE(varIndex == cVars); } #ifndef DACCESS_COMPILE @@ -760,7 +767,7 @@ void CompressDebugInfo::CompressAsyncDebugInfo( TransferWriter t(*pWriter); DoAsyncSuspensionPoints(t, asyncInfo->NumSuspensionPoints, pSuspensionPoints); - DoAsyncVars(t, iAsyncVars, pAsyncVars); + DoAsyncVars(t, asyncInfo->NumSuspensionPoints, pSuspensionPoints, iAsyncVars, pAsyncVars); pWriter->Flush(); @@ -1468,7 +1475,7 @@ void CompressDebugInfo::RestoreAsyncDebugInfo( TransferReader t(r); DoAsyncSuspensionPoints(t, pAsyncInfo->NumSuspensionPoints, *ppSuspensionPoints); - DoAsyncVars(t, *pNumAsyncVars, *ppAsyncVars); + DoAsyncVars(t, pAsyncInfo->NumSuspensionPoints, *ppSuspensionPoints, *pNumAsyncVars, *ppAsyncVars); } #ifdef DACCESS_COMPILE From bf3364bd8a0e4e09ec3e7d51fe72dbb589efee17 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Thu, 2 Oct 2025 16:50:04 +0200 Subject: [PATCH 19/51] Code style --- src/coreclr/jit/lclvars.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/coreclr/jit/lclvars.cpp b/src/coreclr/jit/lclvars.cpp index 7f5152fc41a9e7..e05714a2e0a6d1 100644 --- a/src/coreclr/jit/lclvars.cpp +++ b/src/coreclr/jit/lclvars.cpp @@ -1109,14 +1109,14 @@ unsigned Compiler::compMap2ILvarNum(unsigned varNum) const } // Is this a varargs function? - if (info.compIsVarArgs && varNum == lvaVarargsHandleArg) + if (info.compIsVarArgs && (varNum == lvaVarargsHandleArg)) { return (unsigned)ICorDebugInfo::VARARGS_HND_ILNUM; } // We create an extra argument for the type context parameter // needed for shared generic code. - if ((info.compMethodInfo->args.callConv & CORINFO_CALLCONV_PARAMTYPE) && varNum == info.compTypeCtxtArg) + if (((info.compMethodInfo->args.callConv & CORINFO_CALLCONV_PARAMTYPE) != 0) && (varNum == info.compTypeCtxtArg)) { return (unsigned)ICorDebugInfo::TYPECTXT_ILNUM; } @@ -1129,25 +1129,25 @@ unsigned Compiler::compMap2ILvarNum(unsigned varNum) const #endif // FEATURE_FIXED_OUT_ARGS // Now mutate varNum to remove extra parameters from the count. - if ((info.compMethodInfo->args.callConv & CORINFO_CALLCONV_PARAMTYPE) && varNum > info.compTypeCtxtArg) + if (((info.compMethodInfo->args.callConv & CORINFO_CALLCONV_PARAMTYPE) != 0) && (varNum > info.compTypeCtxtArg)) { varNum--; } - if (info.compIsVarArgs && varNum > lvaVarargsHandleArg) + if (info.compIsVarArgs && (varNum > lvaVarargsHandleArg)) { varNum--; } - if (lvaAsyncContinuationArg != BAD_VAR_NUM && varNum > lvaAsyncContinuationArg) + if ((lvaAsyncContinuationArg != BAD_VAR_NUM) && (varNum > lvaAsyncContinuationArg)) { varNum--; } - /* Is there a hidden argument for the return buffer. - Note that this code works because if the RetBuffArg is not present, - compRetBuffArg will be BAD_VAR_NUM */ - if (info.compRetBuffArg != BAD_VAR_NUM && varNum > info.compRetBuffArg) + // Is there a hidden argument for the return buffer. Note that this code + // works because if the RetBuffArg is not present, compRetBuffArg will be + // BAD_VAR_NUM + if ((info.compRetBuffArg != BAD_VAR_NUM) && (varNum > info.compRetBuffArg)) { varNum--; } From b49f38f00dda1d60e3dc92ff06379ea79502c104 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Thu, 2 Oct 2025 16:57:42 +0200 Subject: [PATCH 20/51] Fix JIT-EE prompt tools from Egor's instructions, allow use of experimental APIs in async tests --- .github/prompts/add-new-jit-ee-api.prompt.md | 16 ++++++++-------- src/tests/async/Directory.Build.props | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/prompts/add-new-jit-ee-api.prompt.md b/.github/prompts/add-new-jit-ee-api.prompt.md index ce3f56b937f6f5..81c5de02328e65 100644 --- a/.github/prompts/add-new-jit-ee-api.prompt.md +++ b/.github/prompts/add-new-jit-ee-api.prompt.md @@ -1,23 +1,23 @@ --- mode: 'agent' -tools: ['githubRepo', 'codebase', 'terminalLastCommand'] +tools: ['fetch', 'codebase', 'runCommands', 'usages', 'search', 'think'] description: 'Add a new API to the JIT-VM (aka JIT-EE) interface in the codebase.' --- #### 1 — Goal -Implement **one** new JIT-VM (also known as JIT-EE) API and all supporting glue. +Implement **one** new JIT-VM (also known as JIT-EE) API and all supporting glue. The JIT-VM interface defines the APIs through which the JIT compiler communicates with the runtime (VM). #### 2 — Prerequisites for the model * You have full repo access -* You may run scripts (e.g., `.sh` or `.bat`) +* You may run scripts (e.g., `.sh` or `.bat`) * Ask **clarifying questions** before the first code change if anything (signature, types, platform constraints) is unclear. #### 3 — Required user inputs -Ask the user for a C-like signature of the new API if it's not provided. +Ask the user for a C-like signature of the new API if it's not provided. Suggest `/src/coreclr/tools/Common/JitInterface/ThunkGenerator/ThunkInput.txt` file as a reference. Example: ``` @@ -83,8 +83,8 @@ Use the correct directory for the script to run. +} ``` -6. Now implement the most complex part - SuperPMI. SuperPMI acts as a (de)serializer for JIT-VM queries in order -to then replay them without the actual VM to speed up jit-diffs and other scenarios. All parameters and return +6. Now implement the most complex part - SuperPMI. SuperPMI acts as a (de)serializer for JIT-VM queries in order +to then replay them without the actual VM to speed up jit-diffs and other scenarios. All parameters and return values recorded/restored using special primitve types and helpers. We need to update the following files: * `/src/coreclr/tools/superpmi/superpmi-shared/agnostic.h`: @@ -96,7 +96,7 @@ Go through each of them one by one. * `/src/coreclr/tools/superpmi/superpmi-shared/agnostic.h`: Define two `Agnostic_*` types for input arguments and another one for output parameters (return value, output arguments). - Do not create them if one of the generics ones can be re-used such as `DLD`, `DD`, `DLDL`, etc. Use `DWORD*` + Do not create them if one of the generics ones can be re-used such as `DLD`, `DD`, `DLDL`, etc. Use `DWORD*` like types for integers. Inspect the whole file to see how other APIs are defined. * `/src/coreclr/tools/superpmi/superpmi-shared/lwmlist.h`: @@ -126,7 +126,7 @@ Now add a new element to `enum mcPackets` enum in the same file. Example: ``` * `/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp`: -Add the implementation of the 3 methods to `methodcontext.cpp` at the end of it. +Add the implementation of the 3 methods to `methodcontext.cpp` at the end of it. Consider other similar methods in the file for reference. Do not change implementations of other methods in the file. Example: ```diff diff --git a/src/tests/async/Directory.Build.props b/src/tests/async/Directory.Build.props index a1f7d48cf60a7d..f505092384b1b1 100644 --- a/src/tests/async/Directory.Build.props +++ b/src/tests/async/Directory.Build.props @@ -5,7 +5,7 @@ true - $(NoWarn);xUnit1013;CS1998 + $(NoWarn);xUnit1013;CS1998;SYSLIB5007 false $(Features);runtime-async=on From d1bf49f767ccd120f4aa86675cbacdf2a685bf33 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Fri, 3 Oct 2025 11:24:16 +0200 Subject: [PATCH 21/51] Address feedback, make comment less misleading --- src/coreclr/inc/cordebuginfo.h | 2 +- src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs | 1 - src/coreclr/tools/superpmi/superpmi/icorjitinfo.cpp | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/coreclr/inc/cordebuginfo.h b/src/coreclr/inc/cordebuginfo.h index 1611dde47878f1..75f80f0c86af3e 100644 --- a/src/coreclr/inc/cordebuginfo.h +++ b/src/coreclr/inc/cordebuginfo.h @@ -434,7 +434,7 @@ class ICorDebugInfo struct AsyncContinuationVarInfo { - // IL number of variable (or one of the special IL numbers, like UNKNOWN_ILNUM) + // IL number of variable (or one of the special IL numbers, like TYPECTXT_ILNUM) uint32_t VarNumber; // Offset in continuation's data where this variable is stored uint32_t Offset; diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs index 2a8ce050d7329d..4b21f348999cce 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs @@ -3219,7 +3219,6 @@ private void reportRichMappings(InlineTreeNode* inlineTree, uint numInlineTree, private void reportAsyncDebugInfo(AsyncInfo* asyncInfo, AsyncSuspensionPoint* suspensionPoints, AsyncContinuationVarInfo* vars, uint numVars) #pragma warning restore CA1822 // Mark members as static { - Marshal.FreeHGlobal((IntPtr)asyncInfo); Marshal.FreeHGlobal((IntPtr)suspensionPoints); Marshal.FreeHGlobal((IntPtr)vars); } diff --git a/src/coreclr/tools/superpmi/superpmi/icorjitinfo.cpp b/src/coreclr/tools/superpmi/superpmi/icorjitinfo.cpp index 996e417a50e43d..410430119e847e 100644 --- a/src/coreclr/tools/superpmi/superpmi/icorjitinfo.cpp +++ b/src/coreclr/tools/superpmi/superpmi/icorjitinfo.cpp @@ -1065,7 +1065,6 @@ void MyICJI::reportAsyncDebugInfo( { jitInstance->mc->cr->AddCall("reportAsyncDebugInfo"); // Compile output that we do not currently save - freeArray(asyncInfo); freeArray(suspensionPoints); freeArray(vars); } From 579714e7db48ee32618fde2a7450645a2e57fcd7 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Tue, 7 Oct 2025 00:47:44 +0200 Subject: [PATCH 22/51] Add AsyncContinuationVarInfo.GCIndex --- src/coreclr/inc/cordebuginfo.h | 6 +++++- src/coreclr/jit/async.cpp | 3 ++- .../tools/Common/JitInterface/CorInfoTypes.cs | 1 + src/coreclr/vm/debuginfostore.cpp | 18 +++++------------- 4 files changed, 13 insertions(+), 15 deletions(-) diff --git a/src/coreclr/inc/cordebuginfo.h b/src/coreclr/inc/cordebuginfo.h index 75f80f0c86af3e..73b7f0134a7dd4 100644 --- a/src/coreclr/inc/cordebuginfo.h +++ b/src/coreclr/inc/cordebuginfo.h @@ -436,8 +436,12 @@ class ICorDebugInfo { // IL number of variable (or one of the special IL numbers, like TYPECTXT_ILNUM) uint32_t VarNumber; - // Offset in continuation's data where this variable is stored + // Index in continuation's byte[] data where this variable is stored, or 0xFFFFFFFF if the + // variable does not have any byte[] data uint32_t Offset; + // Index in continuation's object[] data where this variable's GC pointers are stored, or 0xFFFFFFFF + // if the variable does not have any GC pointers + uint32_t GCIndex; }; struct AsyncSuspensionPoint diff --git a/src/coreclr/jit/async.cpp b/src/coreclr/jit/async.cpp index 1e560842352827..62a53d60f42223 100644 --- a/src/coreclr/jit/async.cpp +++ b/src/coreclr/jit/async.cpp @@ -2233,7 +2233,8 @@ void AsyncTransformation::CreateDebugInfoForSuspensionPoint(GenTreeCall* asyncCa ICorDebugInfo::AsyncContinuationVarInfo varInf; varInf.VarNumber = ilVarNum; - varInf.Offset = local.DataOffset; + varInf.Offset = local.DataSize > 0 ? local.DataOffset : UINT_MAX; + varInf.GCIndex = local.GCDataCount > 0 ? local.GCDataIndex : UINT_MAX; m_dbgContinuationVars.push_back(varInf); numLocals++; } diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs b/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs index ed2a0b0b2d662f..1d7d3d6f6e80c3 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs @@ -1325,6 +1325,7 @@ public struct AsyncContinuationVarInfo public uint VarNumber; // Offset in continuation's data where this variable is stored public uint Offset; + public uint GCIndex; } public struct AsyncSuspensionPoint diff --git a/src/coreclr/vm/debuginfostore.cpp b/src/coreclr/vm/debuginfostore.cpp index 5014d2e200ef28..b6c7f08e9709c3 100644 --- a/src/coreclr/vm/debuginfostore.cpp +++ b/src/coreclr/vm/debuginfostore.cpp @@ -495,21 +495,13 @@ static void DoAsyncVars( ULONG32 cVars, ICorDebugInfo::AsyncContinuationVarInfo* vars) { - uint32_t varIndex = 0; - for (uint32_t i = 0; i < cSuspensionPoints; i++) + for (uint32_t i = 0; i < cVars; i++) { - uint32_t lastOffset = 0; - uint32_t numVars = suspensionPoints[i].NumContinuationVars; - for (uint32_t j = 0; j < numVars; j++) - { - ICorDebugInfo::AsyncContinuationVarInfo* var = &vars[varIndex++]; - trans.DoEncodedAdjustedU32(var->VarNumber, (DWORD) ICorDebugInfo::MAX_ILNUM); - trans.DoEncodedDeltaU32(var->Offset, lastOffset); - lastOffset = var->Offset; - } + ICorDebugInfo::AsyncContinuationVarInfo* var = &vars[i]; + trans.DoEncodedAdjustedU32(var->VarNumber, (DWORD) ICorDebugInfo::MAX_ILNUM); + trans.DoEncodedAdjustedU32(var->Offset, UINT_MAX); + trans.DoEncodedAdjustedU32(var->GCIndex, UINT_MAX); } - - _ASSERTE(varIndex == cVars); } #ifndef DACCESS_COMPILE From c056793a4d261e2792553b51492244968df557db Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Wed, 8 Oct 2025 18:03:08 +0200 Subject: [PATCH 23/51] Publish NextContinuation in TLS --- .../CompilerServices/AsyncHelpers.CoreCLR.cs | 49 ++++++++++++++----- 1 file changed, 37 insertions(+), 12 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs index ba703738d3c7c3..064d1e209b62e8 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs @@ -341,33 +341,53 @@ public static void PostToSyncContext(ThunkTask task, SynchronizationContext sync private static class ThunkTaskCore { + private unsafe struct NextContinuationData + { + public NextContinuationData* Next; + public Continuation* NextContinuation; + } + + // To be used for async stack walking + [ThreadStatic] + private static unsafe NextContinuationData* t_nextContinuation; + public static unsafe void MoveNext(T task) where T : Task where TOps : IThunkTaskOps { ExecutionAndSyncBlockStore contexts = default; contexts.Push(); Continuation continuation = TOps.GetContinuationState(task); + ref NextContinuationData* nextContRef = ref t_nextContinuation; + NextContinuationData nextContinuationData; + nextContinuationData.Next = nextContRef; + nextContinuationData.NextContinuation = &continuation; + + nextContRef = &nextContinuationData; + while (true) { try { - Continuation? newContinuation = continuation.Resume(continuation); + Continuation? curContinuation = continuation; + Debug.Assert(curContinuation != null); + Debug.Assert(curContinuation.Next != null); + continuation = curContinuation.Next; + + Continuation? newContinuation = curContinuation.Resume(curContinuation); if (newContinuation != null) { - newContinuation.Next = continuation.Next; + newContinuation.Next = continuation; HandleSuspended(task); contexts.Pop(); + t_nextContinuation = nextContinuationData.Next; return; } - - Debug.Assert(continuation.Next != null); - continuation = continuation.Next; } catch (Exception ex) { - Continuation nextContinuation = UnwindToPossibleHandler(continuation); - if (nextContinuation.Resume == null) + Continuation handlerContinuation = UnwindToPossibleHandler(continuation); + if (handlerContinuation.Resume == null) { // Tail of AsyncTaskMethodBuilderT.SetException bool successfullySet = ex is OperationCanceledException oce ? @@ -376,6 +396,8 @@ public static unsafe void MoveNext(T task) where T : Task where TOps : contexts.Pop(); + t_nextContinuation = nextContinuationData.Next; + if (!successfullySet) { ThrowHelper.ThrowInvalidOperationException(ExceptionResource.TaskT_TransitionToFinal_AlreadyCompleted); @@ -384,9 +406,8 @@ public static unsafe void MoveNext(T task) where T : Task where TOps : return; } - nextContinuation.SetException(ex); - - continuation = nextContinuation; + handlerContinuation.SetException(ex); + continuation = handlerContinuation; } if (continuation.Resume == null) @@ -395,6 +416,8 @@ public static unsafe void MoveNext(T task) where T : Task where TOps : contexts.Pop(); + t_nextContinuation = nextContinuationData.Next; + if (!successfullySet) { ThrowHelper.ThrowInvalidOperationException(ExceptionResource.TaskT_TransitionToFinal_AlreadyCompleted); @@ -406,6 +429,7 @@ public static unsafe void MoveNext(T task) where T : Task where TOps : if (QueueContinuationFollowUpActionIfNecessary(task, continuation)) { contexts.Pop(); + t_nextContinuation = nextContinuationData.Next; return; } } @@ -415,10 +439,11 @@ private static Continuation UnwindToPossibleHandler(Continuation continuation) { while (true) { - Debug.Assert(continuation.Next != null); - continuation = continuation.Next; if ((continuation.Flags & CorInfoContinuationFlags.CORINFO_CONTINUATION_NEEDS_EXCEPTION) != 0) return continuation; + + Debug.Assert(continuation.Next != null); + continuation = continuation.Next; } } From 9679f91bd160e676fb3c7e6e4c960da9a911f015 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Wed, 8 Oct 2025 18:05:07 +0200 Subject: [PATCH 24/51] Rename ThunkTask -> RuntimeAsyncTask --- .../CompilerServices/AsyncHelpers.CoreCLR.cs | 70 ++++++++++--------- 1 file changed, 36 insertions(+), 34 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs index 064d1e209b62e8..cde4c7ebcf11cb 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs @@ -203,7 +203,7 @@ private static unsafe object AllocContinuationResultBox(void* ptr) return RuntimeTypeHandle.InternalAllocNoChecks((MethodTable*)pMT); } - private interface IThunkTaskOps + private interface IRuntimeAsyncTaskOps { static abstract Action GetContinuationAction(T task); static abstract Continuation GetContinuationState(T task); @@ -212,9 +212,11 @@ private interface IThunkTaskOps static abstract void PostToSyncContext(T task, SynchronizationContext syncCtx); } - private sealed class ThunkTask : Task + // Represents execution of a chain of suspended and resuming runtime + // async functions. + private sealed class RuntimeAsyncTask : Task { - public ThunkTask() + public RuntimeAsyncTask() { // We use the base Task's state object field to store the Continuation while posting the task around. // Ensure that state object isn't published out for others to see. @@ -231,31 +233,31 @@ internal override void ExecuteFromThreadPool(Thread threadPoolThread) private void MoveNext() { - ThunkTaskCore.MoveNext, Ops>(this); + RuntimeAsyncTaskCore.DispatchContinuations, Ops>(this); } public void HandleSuspended() { - ThunkTaskCore.HandleSuspended, Ops>(this); + RuntimeAsyncTaskCore.HandleSuspended, Ops>(this); } private static readonly SendOrPostCallback s_postCallback = static state => { - Debug.Assert(state is ThunkTask); - ((ThunkTask)state).MoveNext(); + Debug.Assert(state is RuntimeAsyncTask); + ((RuntimeAsyncTask)state).MoveNext(); }; - private struct Ops : IThunkTaskOps> + private struct Ops : IRuntimeAsyncTaskOps> { - public static Action GetContinuationAction(ThunkTask task) => (Action)task.m_action!; - public static void MoveNext(ThunkTask task) => task.MoveNext(); - public static Continuation GetContinuationState(ThunkTask task) => (Continuation)task.m_stateObject!; - public static void SetContinuationState(ThunkTask task, Continuation value) + public static Action GetContinuationAction(RuntimeAsyncTask task) => (Action)task.m_action!; + public static void MoveNext(RuntimeAsyncTask task) => task.MoveNext(); + public static Continuation GetContinuationState(RuntimeAsyncTask task) => (Continuation)task.m_stateObject!; + public static void SetContinuationState(RuntimeAsyncTask task, Continuation value) { task.m_stateObject = value; } - public static bool SetCompleted(ThunkTask task, Continuation continuation) + public static bool SetCompleted(RuntimeAsyncTask task, Continuation continuation) { T result; if (RuntimeHelpers.IsReferenceOrContainsReferences()) @@ -277,16 +279,16 @@ public static bool SetCompleted(ThunkTask task, Continuation continuation) return task.TrySetResult(result); } - public static void PostToSyncContext(ThunkTask task, SynchronizationContext syncContext) + public static void PostToSyncContext(RuntimeAsyncTask task, SynchronizationContext syncContext) { syncContext.Post(s_postCallback, task); } } } - private sealed class ThunkTask : Task + private sealed class RuntimeAsyncTask : Task { - public ThunkTask() + public RuntimeAsyncTask() { // We use the base Task's state object field to store the Continuation while posting the task around. // Ensure that state object isn't published out for others to see. @@ -303,43 +305,43 @@ internal override void ExecuteFromThreadPool(Thread threadPoolThread) private void MoveNext() { - ThunkTaskCore.MoveNext(this); + RuntimeAsyncTaskCore.DispatchContinuations(this); } public void HandleSuspended() { - ThunkTaskCore.HandleSuspended(this); + RuntimeAsyncTaskCore.HandleSuspended(this); } private static readonly SendOrPostCallback s_postCallback = static state => { - Debug.Assert(state is ThunkTask); - ((ThunkTask)state).MoveNext(); + Debug.Assert(state is RuntimeAsyncTask); + ((RuntimeAsyncTask)state).MoveNext(); }; - private struct Ops : IThunkTaskOps + private struct Ops : IRuntimeAsyncTaskOps { - public static Action GetContinuationAction(ThunkTask task) => (Action)task.m_action!; - public static void MoveNext(ThunkTask task) => task.MoveNext(); - public static Continuation GetContinuationState(ThunkTask task) => (Continuation)task.m_stateObject!; - public static void SetContinuationState(ThunkTask task, Continuation value) + public static Action GetContinuationAction(RuntimeAsyncTask task) => (Action)task.m_action!; + public static void MoveNext(RuntimeAsyncTask task) => task.MoveNext(); + public static Continuation GetContinuationState(RuntimeAsyncTask task) => (Continuation)task.m_stateObject!; + public static void SetContinuationState(RuntimeAsyncTask task, Continuation value) { task.m_stateObject = value; } - public static bool SetCompleted(ThunkTask task, Continuation continuation) + public static bool SetCompleted(RuntimeAsyncTask task, Continuation continuation) { return task.TrySetResult(); } - public static void PostToSyncContext(ThunkTask task, SynchronizationContext syncContext) + public static void PostToSyncContext(RuntimeAsyncTask task, SynchronizationContext syncContext) { syncContext.Post(s_postCallback, task); } } } - private static class ThunkTaskCore + private static class RuntimeAsyncTaskCore { private unsafe struct NextContinuationData { @@ -351,7 +353,7 @@ private unsafe struct NextContinuationData [ThreadStatic] private static unsafe NextContinuationData* t_nextContinuation; - public static unsafe void MoveNext(T task) where T : Task where TOps : IThunkTaskOps + public static unsafe void DispatchContinuations(T task) where T : Task where TOps : IRuntimeAsyncTaskOps { ExecutionAndSyncBlockStore contexts = default; contexts.Push(); @@ -447,7 +449,7 @@ private static Continuation UnwindToPossibleHandler(Continuation continuation) } } - public static void HandleSuspended(T task) where T : Task where TOps : IThunkTaskOps + public static void HandleSuspended(T task) where T : Task where TOps : IRuntimeAsyncTaskOps { Continuation headContinuation = UnlinkHeadContinuation(out INotifyCompletion? notifier); @@ -491,7 +493,7 @@ private static Continuation UnlinkHeadContinuation(out INotifyCompletion? notifi return head; } - private static bool QueueContinuationFollowUpActionIfNecessary(T task, Continuation continuation) where T : Task where TOps : IThunkTaskOps + private static bool QueueContinuationFollowUpActionIfNecessary(T task, Continuation continuation) where T : Task where TOps : IRuntimeAsyncTaskOps { if ((continuation.Flags & CorInfoContinuationFlags.CORINFO_CONTINUATION_CONTINUE_ON_THREAD_POOL) != 0) { @@ -555,7 +557,7 @@ private static bool QueueContinuationFollowUpActionIfNecessary(T task, } } - // Change return type to ThunkTask -- no benefit since this is used for Task returning thunks only + // Change return type to RuntimeAsyncTask -- no benefit since this is used for Task returning thunks only #pragma warning disable CA1859 // When a Task-returning thunk gets a continuation result // it calls here to make a Task that awaits on the current async state. @@ -579,7 +581,7 @@ private static bool QueueContinuationFollowUpActionIfNecessary(T task, continuation.Next = finalContinuation; - ThunkTask result = new(); + RuntimeAsyncTask result = new(); result.HandleSuspended(); return result; } @@ -592,7 +594,7 @@ private static Task FinalizeTaskReturningThunk(Continuation continuation) }; continuation.Next = finalContinuation; - ThunkTask result = new(); + RuntimeAsyncTask result = new(); result.HandleSuspended(); return result; } From abd7d8b5472c71d9cef3189c1eb0e562d82f7033 Mon Sep 17 00:00:00 2001 From: rcj1 Date: Wed, 8 Oct 2025 14:28:33 -0700 Subject: [PATCH 25/51] testing --- .../CompilerServices/AsyncHelpers.CoreCLR.cs | 12 ++- src/coreclr/vm/clrex.h | 1 + src/coreclr/vm/debugdebugger.cpp | 102 +++++++++++++++--- src/coreclr/vm/debugdebugger.h | 15 +++ src/coreclr/vm/excep.cpp | 8 +- src/coreclr/vm/qcallentrypoints.cpp | 1 + 6 files changed, 123 insertions(+), 16 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs index ba703738d3c7c3..8e1bcdc891d8c6 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs @@ -10,6 +10,7 @@ using System.Runtime.Versioning; using System.Threading; using System.Threading.Tasks; +using System.Runtime.CompilerServices; namespace System.Runtime.CompilerServices { @@ -146,6 +147,12 @@ private struct RuntimeAsyncAwaitState [ThreadStatic] private static RuntimeAsyncAwaitState t_runtimeAsyncAwaitState; + [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "AsyncHelpers_AddContinuationToExInternal")] + private static unsafe partial void AddContinuationToExInternal(void* resume, uint state, ObjectHandleOnStack ex); + + internal static unsafe void AddContinuationToExInternal(Continuation continuation, Exception e) + => AddContinuationToExInternal(continuation.Resume, continuation.State, ObjectHandleOnStack.Create(ref e)); + private static Continuation AllocContinuation(Continuation prevContinuation, nuint numGCRefs, nuint dataSize) { Continuation newContinuation = new Continuation { Data = new byte[dataSize], GCData = new object[numGCRefs] }; @@ -366,7 +373,7 @@ public static unsafe void MoveNext(T task) where T : Task where TOps : } catch (Exception ex) { - Continuation nextContinuation = UnwindToPossibleHandler(continuation); + Continuation nextContinuation = UnwindToPossibleHandler(continuation, ex); if (nextContinuation.Resume == null) { // Tail of AsyncTaskMethodBuilderT.SetException @@ -411,7 +418,7 @@ public static unsafe void MoveNext(T task) where T : Task where TOps : } } - private static Continuation UnwindToPossibleHandler(Continuation continuation) + private static Continuation UnwindToPossibleHandler(Continuation continuation, Exception ex) { while (true) { @@ -419,6 +426,7 @@ private static Continuation UnwindToPossibleHandler(Continuation continuation) continuation = continuation.Next; if ((continuation.Flags & CorInfoContinuationFlags.CORINFO_CONTINUATION_NEEDS_EXCEPTION) != 0) return continuation; + AddContinuationToExInternal(continuation, ex); } } diff --git a/src/coreclr/vm/clrex.h b/src/coreclr/vm/clrex.h index 353804cb71d196..03353f9b0d949a 100644 --- a/src/coreclr/vm/clrex.h +++ b/src/coreclr/vm/clrex.h @@ -30,6 +30,7 @@ enum StackTraceElementFlags // Set if the element references a method that needs a keep alive object STEF_KEEPALIVE = 0x0004, + STEF_CONTINUATION = 0x0008, }; // This struct is used by SOS in the diagnostic repo. diff --git a/src/coreclr/vm/debugdebugger.cpp b/src/coreclr/vm/debugdebugger.cpp index 1d09d0d1a6b797..9deb22f306caca 100644 --- a/src/coreclr/vm/debugdebugger.cpp +++ b/src/coreclr/vm/debugdebugger.cpp @@ -284,6 +284,45 @@ static void GetStackFrames(DebugStackTrace::GetStackFramesData *pData) } } +extern "C" void QCALLTYPE AsyncHelpers_AddContinuationToExInternal( + void* resume, + uint32_t state, + QCall::ObjectHandleOnStack exception) +{ + QCALL_CONTRACT; + + BEGIN_QCALL; + + GCX_COOP(); + + struct + { + OBJECTREF pException; + } gc{}; + gc.pException = NULL; + GCPROTECT_BEGIN(gc); + gc.pException = (OBJECTREF)exception.Get(); + + _ASSERTE(gc.pException != NULL); + + // extract the information from the continuation object + // and populate the exception object + // get the state + OBJECTHANDLE handle = AppDomain::GetCurrentDomain()->CreateHandle(gc.pException); + MethodDesc* methodDesc = NonVirtualEntry2MethodDesc((PCODE)resume); + ILStubResolver *pResolver = methodDesc->AsDynamicMethodDesc()->GetILStubResolver(); + MethodDesc* pTargetMethodDesc = pResolver->GetStubTargetMethodDesc(); + StackTraceInfo::AppendElement( + handle, + (UINT_PTR)resume, + state, + pTargetMethodDesc, + NULL); + + GCPROTECT_END(); + END_QCALL; +} + extern "C" void QCALLTYPE StackTrace_GetStackFramesInternal( QCall::ObjectHandleOnStack stackFrameHelper, BOOL fNeedFileInfo, @@ -952,24 +991,33 @@ void DebugStackTrace::GetStackFramesFromException(OBJECTREF * e, // Currently such methods always return an IP of 0, so they're easy // to spot. DWORD dwNativeOffset; - UINT_PTR ip = cur.ip; -#if defined(DACCESS_COMPILE) && defined(TARGET_AMD64) - // Compensate for a bug in the old EH that for a frame that faulted - // has the ip pointing to an address before the faulting instruction - if ((i == 0) && ((cur.flags & STEF_IP_ADJUSTED) == 0)) - { - ip -= 1; - } -#endif // DACCESS_COMPILE && TARGET_AMD64 - if (ip) + if (cur.flags & STEF_CONTINUATION) { - EECodeInfo codeInfo(ip); - dwNativeOffset = codeInfo.GetRelOffset(); + // Continuation frames don't have a meaningful native offset. + // here we populate it with the continuation index which was stored in SP :( + dwNativeOffset = (DWORD)cur.sp; } + else { - dwNativeOffset = 0; +#if defined(DACCESS_COMPILE) && defined(TARGET_AMD64) + // Compensate for a bug in the old EH that for a frame that faulted + // has the ip pointing to an address before the faulting instruction + if ((i == 0) && ((cur.flags & STEF_IP_ADJUSTED) == 0)) + { + ip -= 1; + } +#endif // DACCESS_COMPILE && TARGET_AMD64 + if (ip) + { + EECodeInfo codeInfo(ip); + dwNativeOffset = codeInfo.GetRelOffset(); + } + else + { + dwNativeOffset = 0; + } } pData->pElements[i].InitPass1( @@ -1540,6 +1588,12 @@ void ValidateILOffsets(MethodDesc *pFunc, uint8_t* ipColdStart, size_t coldLen, // Initialization done outside the TSL. // This may need to call locking operations that aren't safe under the TSL. + +BYTE* DebugInfoStoreNew(void * pData, size_t cBytes) +{ + return new BYTE[cBytes]; +} + void DebugStackTrace::Element::InitPass2() { CONTRACTL @@ -1554,6 +1608,28 @@ void DebugStackTrace::Element::InitPass2() bool bRes = false; + if (this->flags & STEF_CONTINUATION) + { + PCODE addr = this->pFunc->GetNativeCode(); + if (addr != (PCODE)NULL) + { + EECodeInfo codeInfo(addr); + if (codeInfo.IsValid()) + { + DebugInfoRequest request; + request.InitFromStartingAddr(this->pFunc, addr); + ICorDebugInfo::AsyncInfo asyncInfo = {}; + NewArrayHolder asyncSuspensionPoints(NULL); + NewArrayHolder asyncVars(NULL); + ULONG32 cAsyncVars = 0; + DebugInfoManager::GetAsyncDebugInfo(request, DebugInfoStoreNew, nullptr, &asyncInfo, &asyncSuspensionPoints, &asyncVars, &cAsyncVars); + this->dwILOffset = asyncSuspensionPoints[this->dwOffset].RootILOffset; + // leave native offset TBD, Noah + } + } + return; + } + bool fAdjustOffset = (this->flags & STEF_IP_ADJUSTED) == 0 && this->dwOffset > 0; // Check the cache! diff --git a/src/coreclr/vm/debugdebugger.h b/src/coreclr/vm/debugdebugger.h index d2bdb47c070f19..21efdb8628754f 100644 --- a/src/coreclr/vm/debugdebugger.h +++ b/src/coreclr/vm/debugdebugger.h @@ -64,6 +64,16 @@ class StackFrameHelper : public Object }; +struct Continuation : public Object +{ + struct Continuation* next; + TADDR resume; + uint32_t state; + CorInfoContinuationFlags flags; + uint8_t* data; // byte[] -> byte* (managed arrays are just pointers in native) + void** gcData; // object[] -> void** (object references) +}; + #ifdef USE_CHECKED_OBJECTREFS typedef REF STACKFRAMEHELPERREF; #else @@ -135,6 +145,11 @@ extern "C" void QCALLTYPE StackTrace_GetStackFramesInternal( BOOL fNeedFileInfo, QCall::ObjectHandleOnStack exception); +extern "C" void QCALLTYPE AsyncHelpers_AddContinuationToExInternal( + void* resume, + uint32_t state, + QCall::ObjectHandleOnStack exception); + extern "C" MethodDesc* QCALLTYPE StackFrame_GetMethodDescFromNativeIP(LPVOID ip); diff --git a/src/coreclr/vm/excep.cpp b/src/coreclr/vm/excep.cpp index 9555397240830d..8c110f5ae2a4a5 100644 --- a/src/coreclr/vm/excep.cpp +++ b/src/coreclr/vm/excep.cpp @@ -3060,8 +3060,14 @@ void StackTraceInfo::AppendElement(OBJECTHANDLE hThrowable, UINT_PTR currentIP, } } + else + { + stackTraceElem.flags |= STEF_CONTINUATION; + } + #ifndef TARGET_UNIX // Watson is supported on Windows only - SetupWatsonBucket(currentIP, pCf); + if (pCf != NULL) + SetupWatsonBucket(currentIP, pCf); #endif // !TARGET_UNIX EX_TRY diff --git a/src/coreclr/vm/qcallentrypoints.cpp b/src/coreclr/vm/qcallentrypoints.cpp index 256182b0c1aac6..c7d6ebf5f04519 100644 --- a/src/coreclr/vm/qcallentrypoints.cpp +++ b/src/coreclr/vm/qcallentrypoints.cpp @@ -177,6 +177,7 @@ static const Entry s_QCall[] = DllImportEntry(RuntimeFieldHandle_GetFieldDataReference) DllImportEntry(UnsafeAccessors_ResolveGenericParamToTypeHandle) DllImportEntry(StackTrace_GetStackFramesInternal) + DllImportEntry(AsyncHelpers_AddContinuationToExInternal) DllImportEntry(StackFrame_GetMethodDescFromNativeIP) DllImportEntry(ModuleBuilder_GetStringConstant) DllImportEntry(ModuleBuilder_GetTypeRef) From e83940957498e0582eda6b1d0f5b58b7c24580cf Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Thu, 9 Oct 2025 14:03:58 +0200 Subject: [PATCH 26/51] Report native offsets instead --- src/coreclr/inc/cordebuginfo.h | 8 +--- src/coreclr/jit/async.cpp | 55 ++++++-------------------- src/coreclr/jit/async.h | 9 +---- src/coreclr/jit/codegen.h | 3 +- src/coreclr/jit/codegencommon.cpp | 46 +++++++++++++++++++++ src/coreclr/jit/codegenlinear.cpp | 8 ++++ src/coreclr/jit/codegenloongarch64.cpp | 1 + src/coreclr/jit/codegenriscv64.cpp | 1 + src/coreclr/jit/codegenxarch.cpp | 2 + src/coreclr/jit/compiler.h | 12 +++++- src/coreclr/jit/compiler.hpp | 1 + src/coreclr/jit/gentree.cpp | 6 +++ src/coreclr/jit/gentree.h | 23 +++++++++-- src/coreclr/jit/gtlist.h | 1 + src/coreclr/jit/gtstructs.h | 1 + src/coreclr/jit/importercalls.cpp | 9 ++--- src/coreclr/jit/liveness.cpp | 1 + src/coreclr/jit/lsraarm.cpp | 1 + src/coreclr/vm/debuginfostore.cpp | 14 ++----- 19 files changed, 123 insertions(+), 79 deletions(-) diff --git a/src/coreclr/inc/cordebuginfo.h b/src/coreclr/inc/cordebuginfo.h index 73b7f0134a7dd4..3a326b8091aaed 100644 --- a/src/coreclr/inc/cordebuginfo.h +++ b/src/coreclr/inc/cordebuginfo.h @@ -446,12 +446,8 @@ class ICorDebugInfo struct AsyncSuspensionPoint { - // IL offset in the root method that resulted in the creation of this suspension point. - uint32_t RootILOffset; - // Index of inline tree node containing the IL offset (0 for root) - uint32_t Inlinee; - // IL offset that resulted in the creation of the suspension point. - uint32_t ILOffset; + // Logical return address of the async call (join point of synchronous and resuming paths) + uint32_t NativeOffset; // Count of AsyncContinuationVarInfo in array of locals starting where // the previous suspension point's locals end. uint32_t NumContinuationVars; diff --git a/src/coreclr/jit/async.cpp b/src/coreclr/jit/async.cpp index 62a53d60f42223..1925d04819a986 100644 --- a/src/coreclr/jit/async.cpp +++ b/src/coreclr/jit/async.cpp @@ -642,6 +642,9 @@ PhaseStatus AsyncTransformation::Run() return PhaseStatus::MODIFIED_NOTHING; } + m_comp->compSuspensionPoints = new (m_comp, CMK_Async) jitstd::vector(m_comp->getAllocator(CMK_Async)); + m_comp->compAsyncVars = new (m_comp, CMK_Async) jitstd::vector(m_comp->getAllocator(CMK_Async)); + // Ask the VM to create a resumption stub for this specific version of the // code. It is stored in the continuation as a function pointer, so we need // the fixed entry point here. @@ -760,8 +763,6 @@ PhaseStatus AsyncTransformation::Run() m_comp->fgInvalidateDfsTree(); - ReportDebugInfo(); - return PhaseStatus::MODIFIED_EVERYTHING; } @@ -821,7 +822,7 @@ void AsyncTransformation::Transform( m_resumptionBBs.push_back(resumeBB); - CreateDebugInfoForSuspensionPoint(call, layout); + CreateDebugInfoForSuspensionPoint(call, layout, *remainder); } //------------------------------------------------------------------------ @@ -2212,10 +2213,10 @@ GenTreeStoreInd* AsyncTransformation::StoreAtOffset(GenTree* base, unsigned offs // // Parameters: // asyncCall - Call node resulting in the suspension point -// stateNum - State number that was assigned to the suspension point // layout - Layout of continuation +// joinBB - BB where the synchronous and resumption paths join // -void AsyncTransformation::CreateDebugInfoForSuspensionPoint(GenTreeCall* asyncCall, const ContinuationLayout& layout) +void AsyncTransformation::CreateDebugInfoForSuspensionPoint(GenTreeCall* asyncCall, const ContinuationLayout& layout, BasicBlock* joinBB) { if (!m_comp->opts.compDbgInfo) { @@ -2235,48 +2236,16 @@ void AsyncTransformation::CreateDebugInfoForSuspensionPoint(GenTreeCall* asyncCa varInf.VarNumber = ilVarNum; varInf.Offset = local.DataSize > 0 ? local.DataOffset : UINT_MAX; varInf.GCIndex = local.GCDataCount > 0 ? local.GCDataIndex : UINT_MAX; - m_dbgContinuationVars.push_back(varInf); + m_comp->compAsyncVars->push_back(varInf); numLocals++; } - ICorDebugInfo::AsyncSuspensionPoint suspensionPoint; - const DebugInfo& di = asyncCall->GetAsyncInfo().DebugInfo; - suspensionPoint.RootILOffset = di.GetRoot().GetLocation().GetOffset(); - suspensionPoint.Inlinee = di.GetInlineContext()->GetOrdinal(); - suspensionPoint.ILOffset = di.GetLocation().GetOffset(); - suspensionPoint.NumContinuationVars = numLocals; - - m_dbgSuspensionPoints.push_back(suspensionPoint); -} - -//------------------------------------------------------------------------ -// AsyncTransformation::ReportDebugInfo: -// Report debug info back to EE. -// -void AsyncTransformation::ReportDebugInfo() -{ - if (!m_comp->opts.compDbgInfo) - { - return; - } - - ICorDebugInfo::AsyncInfo asyncInfo; - asyncInfo.NumSuspensionPoints = static_cast(m_dbgSuspensionPoints.size()); - - ICorDebugInfo::AsyncSuspensionPoint* hostSuspensionPoints = - static_cast(m_comp->info.compCompHnd->allocateArray( - m_dbgSuspensionPoints.size() * sizeof(ICorDebugInfo::AsyncSuspensionPoint))); - for (size_t i = 0; i < m_dbgSuspensionPoints.size(); i++) - hostSuspensionPoints[i] = m_dbgSuspensionPoints[i]; - - ICorDebugInfo::AsyncContinuationVarInfo* hostVars = - static_cast(m_comp->info.compCompHnd->allocateArray( - m_dbgContinuationVars.size() * sizeof(ICorDebugInfo::AsyncContinuationVarInfo))); - for (size_t i = 0; i < m_dbgContinuationVars.size(); i++) - hostVars[i] = m_dbgContinuationVars[i]; + AsyncSuspensionPoint suspensionPoint; + suspensionPoint.numContinuationVars = numLocals; + m_comp->compSuspensionPoints->push_back(suspensionPoint); - m_comp->info.compCompHnd->reportAsyncDebugInfo(&asyncInfo, hostSuspensionPoints, hostVars, - static_cast(m_dbgContinuationVars.size())); + GenTree* recordOffset = new (m_comp, GT_RECORD_ASYNC_JOIN) GenTreeRecordAsyncJoin((int)(m_comp->compSuspensionPoints->size() - 1)); + LIR::AsRange(joinBB).InsertAtBeginning(recordOffset); } //------------------------------------------------------------------------ diff --git a/src/coreclr/jit/async.h b/src/coreclr/jit/async.h index b8c0459ef9055e..53ac2bc9274b68 100644 --- a/src/coreclr/jit/async.h +++ b/src/coreclr/jit/async.h @@ -63,10 +63,6 @@ class AsyncTransformation BasicBlock* m_lastResumptionBB = nullptr; BasicBlock* m_sharedReturnBB = nullptr; - // Debug info - jitstd::vector m_dbgContinuationVars; - jitstd::vector m_dbgSuspensionPoints; - bool IsLive(unsigned lclNum); void Transform(BasicBlock* block, GenTreeCall* call, @@ -136,8 +132,7 @@ class AsyncTransformation GenTreeFlags indirFlags = GTF_IND_NONFAULTING); GenTreeStoreInd* StoreAtOffset(GenTree* base, unsigned offset, GenTree* value, var_types storeType); - void CreateDebugInfoForSuspensionPoint(GenTreeCall* asyncCall, const ContinuationLayout& layout); - void ReportDebugInfo(); + void CreateDebugInfoForSuspensionPoint(GenTreeCall* asyncCall, const ContinuationLayout& layout, BasicBlock* joinBB); unsigned GetDataArrayVar(); unsigned GetGCDataArrayVar(); @@ -154,8 +149,6 @@ class AsyncTransformation : m_comp(comp) , m_liveLocalsScratch(comp->getAllocator(CMK_Async)) , m_resumptionBBs(comp->getAllocator(CMK_Async)) - , m_dbgContinuationVars(comp->getAllocator(CMK_Async)) - , m_dbgSuspensionPoints(comp->getAllocator(CMK_Async)) { } diff --git a/src/coreclr/jit/codegen.h b/src/coreclr/jit/codegen.h index e47fe8ab57bbe6..478a05d55678e1 100644 --- a/src/coreclr/jit/codegen.h +++ b/src/coreclr/jit/codegen.h @@ -646,7 +646,6 @@ class CodeGen final : public CodeGenInterface void genAddRichIPMappingHere(const DebugInfo& di); void genReportRichDebugInfo(); - void genRecordRichDebugInfoInlineTree(InlineContext* context, ICorDebugInfo::InlineTreeNode* tree); #ifdef DEBUG @@ -654,6 +653,8 @@ class CodeGen final : public CodeGenInterface void genReportRichDebugInfoInlineTreeToFile(FILE* file, InlineContext* context, bool* first); #endif + void genReportAsyncDebugInfo(); + void genEnsureCodeEmitted(const DebugInfo& di); //------------------------------------------------------------------------- diff --git a/src/coreclr/jit/codegencommon.cpp b/src/coreclr/jit/codegencommon.cpp index 0ffa6e8887f7cd..3edb9fef750789 100644 --- a/src/coreclr/jit/codegencommon.cpp +++ b/src/coreclr/jit/codegencommon.cpp @@ -2201,6 +2201,8 @@ void CodeGen::genEmitUnwindDebugGCandEH() genReportRichDebugInfo(); + genReportAsyncDebugInfo(); + /* Finalize the Local Var info in terms of generated code */ genSetScopeInfo(); @@ -6654,6 +6656,50 @@ void CodeGen::genAddRichIPMappingHere(const DebugInfo& di) compiler->genRichIPmappings.push_back(mapping); } +//------------------------------------------------------------------------ +// genReportAsyncDebugInfo: +// Report async debug info back to EE. +// +void CodeGen::genReportAsyncDebugInfo() +{ + jitstd::vector* suspPoints = compiler->compSuspensionPoints; + if (suspPoints == nullptr) + { + return; + } + + ICorDebugInfo::AsyncInfo asyncInfo; + asyncInfo.NumSuspensionPoints = static_cast(suspPoints->size()); + + ICorDebugInfo::AsyncSuspensionPoint* hostSuspensionPoints = + static_cast(compiler->info.compCompHnd->allocateArray( + suspPoints->size() * sizeof(ICorDebugInfo::AsyncSuspensionPoint))); + for (size_t i = 0; i < suspPoints->size(); i++) + { + AsyncSuspensionPoint& suspPoint = (*suspPoints)[i]; + if (suspPoint.nativeLoc.Valid()) + { + hostSuspensionPoints[i].NativeOffset = suspPoint.nativeLoc.CodeOffset(GetEmitter()); + hostSuspensionPoints[i].NumContinuationVars = suspPoint.numContinuationVars; + } + else + { + hostSuspensionPoints[i].NativeOffset = 0; + hostSuspensionPoints[i].NumContinuationVars = 0; + } + } + + jitstd::vector* asyncVars = compiler->compAsyncVars; + ICorDebugInfo::AsyncContinuationVarInfo* hostVars = + static_cast(compiler->info.compCompHnd->allocateArray( + asyncVars->size() * sizeof(ICorDebugInfo::AsyncContinuationVarInfo))); + for (size_t i = 0; i < asyncVars->size(); i++) + hostVars[i] = (*asyncVars)[i]; + + compiler->info.compCompHnd->reportAsyncDebugInfo(&asyncInfo, hostSuspensionPoints, hostVars, + static_cast(asyncVars->size())); +} + /*============================================================================ * * These are empty stubs to help the late dis-assembler to compile diff --git a/src/coreclr/jit/codegenlinear.cpp b/src/coreclr/jit/codegenlinear.cpp index 339ac1eab1610d..2223e003b92e24 100644 --- a/src/coreclr/jit/codegenlinear.cpp +++ b/src/coreclr/jit/codegenlinear.cpp @@ -462,6 +462,14 @@ void CodeGen::genCodeForBBlist() #endif // DEBUG } + else if (node->OperIs(GT_RECORD_ASYNC_JOIN)) + { + int index = node->AsRecordAsyncJoin()->gtSuspensionPointIndex; + assert(compiler->compSuspensionPoints != nullptr); + assert(index >= 0 && (size_t)index < compiler->compSuspensionPoints->size()); + + (*compiler->compSuspensionPoints)[index].nativeLoc.CaptureLocation(GetEmitter()); + } genCodeForTreeNode(node); if (node->gtHasReg(compiler) && node->IsUnusedValue()) diff --git a/src/coreclr/jit/codegenloongarch64.cpp b/src/coreclr/jit/codegenloongarch64.cpp index c0ecf124ee1da6..6c239f098506d5 100644 --- a/src/coreclr/jit/codegenloongarch64.cpp +++ b/src/coreclr/jit/codegenloongarch64.cpp @@ -4380,6 +4380,7 @@ void CodeGen::genCodeForTreeNode(GenTree* treeNode) break; case GT_IL_OFFSET: + case GT_RECORD_ASYNC_JOIN: // Do nothing; these nodes are simply markers for debug info. break; diff --git a/src/coreclr/jit/codegenriscv64.cpp b/src/coreclr/jit/codegenriscv64.cpp index e581bc44d7029b..c3db7a89653c30 100644 --- a/src/coreclr/jit/codegenriscv64.cpp +++ b/src/coreclr/jit/codegenriscv64.cpp @@ -4381,6 +4381,7 @@ void CodeGen::genCodeForTreeNode(GenTree* treeNode) break; case GT_IL_OFFSET: + case GT_RECORD_ASYNC_JOIN: // Do nothing; these nodes are simply markers for debug info. break; diff --git a/src/coreclr/jit/codegenxarch.cpp b/src/coreclr/jit/codegenxarch.cpp index bde2493ab0d137..58b980b44816ea 100644 --- a/src/coreclr/jit/codegenxarch.cpp +++ b/src/coreclr/jit/codegenxarch.cpp @@ -2343,9 +2343,11 @@ void CodeGen::genCodeForTreeNode(GenTree* treeNode) #endif case GT_IL_OFFSET: + case GT_RECORD_ASYNC_JOIN: // Do nothing; these nodes are simply markers for debug info. break; + #if defined(TARGET_AMD64) case GT_CCMP: genCodeForCCMP(treeNode->AsCCMP()); diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 263111e04ca82f..08b8e8aa656f5c 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -2397,6 +2397,12 @@ struct RichIPMapping DebugInfo debugInfo; }; +struct AsyncSuspensionPoint +{ + emitLocation nativeLoc; + unsigned numContinuationVars = 0; +}; + // Current kind of node threading stored in GenTree::gtPrev and GenTree::gtNext. // See fgNodeThreading for more information. enum class NodeThreading @@ -4601,7 +4607,7 @@ class Compiler CORINFO_CALL_INFO* callInfo, IL_OFFSET rawILOffset); - void impSetupAndSpillForAsyncCall(GenTreeCall* call, OPCODE opcode, unsigned prefixFlags, const DebugInfo& callInstDI); + void impSetupAndSpillForAsyncCall(GenTreeCall* call, OPCODE opcode, unsigned prefixFlags); void impInsertAsyncContinuationForLdvirtftnCall(GenTreeCall* call); @@ -8631,6 +8637,9 @@ class Compiler jitstd::list genIPmappings; jitstd::list genRichIPmappings; + jitstd::vector* compSuspensionPoints = nullptr; + jitstd::vector* compAsyncVars = nullptr; + // Managed RetVal - A side hash table meant to record the mapping from a // GT_CALL node to its debug info. This info is used to emit sequence points // that can be used by debugger to determine the native offset at which the @@ -11727,6 +11736,7 @@ class GenTreeVisitor case GT_PINVOKE_PROLOG: case GT_PINVOKE_EPILOG: case GT_IL_OFFSET: + case GT_RECORD_ASYNC_JOIN: case GT_NOP: case GT_SWIFT_ERROR: case GT_GCPOLL: diff --git a/src/coreclr/jit/compiler.hpp b/src/coreclr/jit/compiler.hpp index 12280cde617228..a1f64ec2cfa87b 100644 --- a/src/coreclr/jit/compiler.hpp +++ b/src/coreclr/jit/compiler.hpp @@ -4522,6 +4522,7 @@ GenTree::VisitResult GenTree::VisitOperands(TVisitor visitor) case GT_PINVOKE_PROLOG: case GT_PINVOKE_EPILOG: case GT_IL_OFFSET: + case GT_RECORD_ASYNC_JOIN: case GT_NOP: case GT_SWIFT_ERROR: case GT_GCPOLL: diff --git a/src/coreclr/jit/gentree.cpp b/src/coreclr/jit/gentree.cpp index dbdb7c7485108d..475e3b27e1906b 100644 --- a/src/coreclr/jit/gentree.cpp +++ b/src/coreclr/jit/gentree.cpp @@ -6724,6 +6724,7 @@ bool GenTree::TryGetUse(GenTree* operand, GenTree*** pUse) case GT_PINVOKE_PROLOG: case GT_PINVOKE_EPILOG: case GT_IL_OFFSET: + case GT_RECORD_ASYNC_JOIN: case GT_NOP: case GT_SWIFT_ERROR: case GT_GCPOLL: @@ -10357,6 +10358,7 @@ GenTreeUseEdgeIterator::GenTreeUseEdgeIterator(GenTree* node) case GT_PINVOKE_PROLOG: case GT_PINVOKE_EPILOG: case GT_IL_OFFSET: + case GT_RECORD_ASYNC_JOIN: case GT_NOP: case GT_SWIFT_ERROR: case GT_GCPOLL: @@ -12443,6 +12445,10 @@ void Compiler::gtDispLeaf(GenTree* tree, IndentStack* indentStack) tree->AsILOffset()->gtStmtDI.Dump(true); break; + case GT_RECORD_ASYNC_JOIN: + printf(" %d", tree->AsRecordAsyncJoin()->gtSuspensionPointIndex); + break; + case GT_JCC: case GT_SETCC: printf(" cond=%s", tree->AsCC()->gtCondition.Name()); diff --git a/src/coreclr/jit/gentree.h b/src/coreclr/jit/gentree.h index d348b04f95f099..51654b1bc572aa 100644 --- a/src/coreclr/jit/gentree.h +++ b/src/coreclr/jit/gentree.h @@ -4400,9 +4400,6 @@ struct AsyncCallInfo bool SaveAndRestoreSynchronizationContextField = false; bool HasSuspensionIndicatorDef = false; unsigned SynchronizationContextLclNum = BAD_VAR_NUM; - - // Exact debug info of call IL instruction - DebugInfo DebugInfo; }; // Return type descriptor of a GT_CALL node. @@ -8287,6 +8284,26 @@ struct GenTreeILOffset : public GenTree #endif }; +// No-op node that records the native offset into async debug info during +// codegen/emit. +struct GenTreeRecordAsyncJoin : public GenTree +{ + int gtSuspensionPointIndex; + + GenTreeRecordAsyncJoin(int suspensionPointIndex) + : GenTree(GT_RECORD_ASYNC_JOIN, TYP_VOID) + , gtSuspensionPointIndex(suspensionPointIndex) + { + } + +#if DEBUGGABLE_GENTREE + GenTreeRecordAsyncJoin() + : GenTree(GT_RECORD_ASYNC_JOIN, TYP_VOID) + { + } +#endif +}; + // GenTreeList: adapter class for forward iteration of the execution order GenTree linked list // using range-based `for`, normally used via Statement::TreeList(), e.g.: // for (GenTree* const tree : stmt->TreeList()) ... diff --git a/src/coreclr/jit/gtlist.h b/src/coreclr/jit/gtlist.h index 93fb4dfd358b37..13bd019a98f470 100644 --- a/src/coreclr/jit/gtlist.h +++ b/src/coreclr/jit/gtlist.h @@ -358,6 +358,7 @@ GTNODE(SWAP , GenTreeOp ,0,0,GTK_BINOP|GTK_NOVALUE|DBK_NOTH GTNODE(COPY , GenTreeCopyOrReload,0,0,GTK_UNOP|DBK_NOTHIR) // Copies a variable from its current location to a register that satisfies GTNODE(RELOAD , GenTreeCopyOrReload,0,0,GTK_UNOP|DBK_NOTHIR) // code generation constraints. The operand is the actual lclVar node. GTNODE(IL_OFFSET , GenTreeILOffset ,0,0,GTK_LEAF|GTK_NOVALUE|DBK_NOTHIR) // marks an IL offset for debugging purposes +GTNODE(RECORD_ASYNC_JOIN, GenTreeRecordAsyncJoin,0,0,GTK_LEAF|GTK_NOVALUE|DBK_NOTHIR) // records native offset of async suspension point for async stackwalking purposes /*****************************************************************************/ #undef GTNODE diff --git a/src/coreclr/jit/gtstructs.h b/src/coreclr/jit/gtstructs.h index b785279801c778..d31917752584f8 100644 --- a/src/coreclr/jit/gtstructs.h +++ b/src/coreclr/jit/gtstructs.h @@ -88,6 +88,7 @@ GTSTRUCT_2(MDArr , GT_MDARR_LENGTH, GT_MDARR_LOWER_BOUND) GTSTRUCT_1(ArrElem , GT_ARR_ELEM) GTSTRUCT_1(RetExpr , GT_RET_EXPR) GTSTRUCT_1(ILOffset , GT_IL_OFFSET) +GTSTRUCT_1(RecordAsyncJoin, GT_RECORD_ASYNC_JOIN) GTSTRUCT_2(CopyOrReload, GT_COPY, GT_RELOAD) GTSTRUCT_1(AddrMode , GT_LEA) GTSTRUCT_1(Qmark , GT_QMARK) diff --git a/src/coreclr/jit/importercalls.cpp b/src/coreclr/jit/importercalls.cpp index f9243f7eade3ba..22d06f00e62be6 100644 --- a/src/coreclr/jit/importercalls.cpp +++ b/src/coreclr/jit/importercalls.cpp @@ -386,7 +386,7 @@ var_types Compiler::impImportCall(OPCODE opcode, if (sig->isAsyncCall()) { - impSetupAndSpillForAsyncCall(call->AsCall(), opcode, prefixFlags, di); + impSetupAndSpillForAsyncCall(call->AsCall(), opcode, prefixFlags); } impPopCallArgs(sig, call->AsCall()); @@ -691,7 +691,7 @@ var_types Compiler::impImportCall(OPCODE opcode, if (sig->isAsyncCall()) { - impSetupAndSpillForAsyncCall(call->AsCall(), opcode, prefixFlags, di); + impSetupAndSpillForAsyncCall(call->AsCall(), opcode, prefixFlags); } // Now create the argument list. @@ -6814,15 +6814,12 @@ void Compiler::impCheckForPInvokeCall( // call - The call // opcode - The IL opcode for the call // prefixFlags - Flags containing context handling information from IL -// callInstDI - Debug info for the exact call instruction // void Compiler::impSetupAndSpillForAsyncCall(GenTreeCall* call, OPCODE opcode, - unsigned prefixFlags, - const DebugInfo& callInstDI) + unsigned prefixFlags) { AsyncCallInfo asyncInfo; - asyncInfo.DebugInfo = callInstDI; if ((prefixFlags & PREFIX_IS_TASK_AWAIT) != 0) { diff --git a/src/coreclr/jit/liveness.cpp b/src/coreclr/jit/liveness.cpp index 05786eb1a9045e..0c4994faf393c2 100644 --- a/src/coreclr/jit/liveness.cpp +++ b/src/coreclr/jit/liveness.cpp @@ -1501,6 +1501,7 @@ void Compiler::fgComputeLifeLIR(VARSET_TP& life, BasicBlock* block, VARSET_VALAR case GT_RETURNTRAP: case GT_PUTARG_STK: case GT_IL_OFFSET: + case GT_RECORD_ASYNC_JOIN: case GT_KEEPALIVE: case GT_SWIFT_ERROR_RET: case GT_GCPOLL: diff --git a/src/coreclr/jit/lsraarm.cpp b/src/coreclr/jit/lsraarm.cpp index 6fe1c76635dee3..58c320805ee8f4 100644 --- a/src/coreclr/jit/lsraarm.cpp +++ b/src/coreclr/jit/lsraarm.cpp @@ -689,6 +689,7 @@ int LinearScan::BuildNode(GenTree* tree) case GT_LCL_ADDR: case GT_PHYSREG: case GT_IL_OFFSET: + case GT_RECORD_ASYNC_JOIN: case GT_LABEL: case GT_PINVOKE_PROLOG: case GT_JCC: diff --git a/src/coreclr/vm/debuginfostore.cpp b/src/coreclr/vm/debuginfostore.cpp index b6c7f08e9709c3..e3f79891622853 100644 --- a/src/coreclr/vm/debuginfostore.cpp +++ b/src/coreclr/vm/debuginfostore.cpp @@ -467,21 +467,13 @@ static void DoAsyncSuspensionPoints( ICorDebugInfo::AsyncSuspensionPoint* suspensionPoints) { // Loop through and transfer each Entry in the Mapping. - uint32_t lastRootILOffset = 0; - uint32_t lastInlinee = 0; - uint32_t lastILOffset = 0; + uint32_t lastNativeOffset = 0; for (uint32_t i = 0; i < cSuspensionPoints; i++) { ICorDebugInfo::AsyncSuspensionPoint* sp = &suspensionPoints[i]; - trans.DoEncodedDeltaU32NonMonotonic(sp->RootILOffset, lastRootILOffset); - lastRootILOffset = sp->RootILOffset; - - trans.DoEncodedDeltaU32NonMonotonic(sp->Inlinee, lastInlinee); - lastInlinee = sp->Inlinee; - - trans.DoEncodedDeltaU32NonMonotonic(sp->ILOffset, lastILOffset); - lastILOffset = sp->ILOffset; + trans.DoEncodedDeltaU32NonMonotonic(sp->NativeOffset, lastNativeOffset); + lastNativeOffset = sp->NativeOffset; trans.DoEncodedU32(sp->NumContinuationVars); } From d8ad0c1ce96eb4a050474241d07f21271a46de3e Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Thu, 9 Oct 2025 14:04:32 +0200 Subject: [PATCH 27/51] Run jit-format --- src/coreclr/jit/async.cpp | 13 +++++++++---- src/coreclr/jit/async.h | 4 +++- src/coreclr/jit/codegencommon.cpp | 14 ++++++-------- src/coreclr/jit/codegenxarch.cpp | 1 - src/coreclr/jit/compiler.h | 4 ++-- src/coreclr/jit/importercalls.cpp | 4 +--- 6 files changed, 21 insertions(+), 19 deletions(-) diff --git a/src/coreclr/jit/async.cpp b/src/coreclr/jit/async.cpp index 1925d04819a986..559910ee81aff1 100644 --- a/src/coreclr/jit/async.cpp +++ b/src/coreclr/jit/async.cpp @@ -642,8 +642,10 @@ PhaseStatus AsyncTransformation::Run() return PhaseStatus::MODIFIED_NOTHING; } - m_comp->compSuspensionPoints = new (m_comp, CMK_Async) jitstd::vector(m_comp->getAllocator(CMK_Async)); - m_comp->compAsyncVars = new (m_comp, CMK_Async) jitstd::vector(m_comp->getAllocator(CMK_Async)); + m_comp->compSuspensionPoints = + new (m_comp, CMK_Async) jitstd::vector(m_comp->getAllocator(CMK_Async)); + m_comp->compAsyncVars = new (m_comp, CMK_Async) + jitstd::vector(m_comp->getAllocator(CMK_Async)); // Ask the VM to create a resumption stub for this specific version of the // code. It is stored in the continuation as a function pointer, so we need @@ -2216,7 +2218,9 @@ GenTreeStoreInd* AsyncTransformation::StoreAtOffset(GenTree* base, unsigned offs // layout - Layout of continuation // joinBB - BB where the synchronous and resumption paths join // -void AsyncTransformation::CreateDebugInfoForSuspensionPoint(GenTreeCall* asyncCall, const ContinuationLayout& layout, BasicBlock* joinBB) +void AsyncTransformation::CreateDebugInfoForSuspensionPoint(GenTreeCall* asyncCall, + const ContinuationLayout& layout, + BasicBlock* joinBB) { if (!m_comp->opts.compDbgInfo) { @@ -2244,7 +2248,8 @@ void AsyncTransformation::CreateDebugInfoForSuspensionPoint(GenTreeCall* asyncCa suspensionPoint.numContinuationVars = numLocals; m_comp->compSuspensionPoints->push_back(suspensionPoint); - GenTree* recordOffset = new (m_comp, GT_RECORD_ASYNC_JOIN) GenTreeRecordAsyncJoin((int)(m_comp->compSuspensionPoints->size() - 1)); + GenTree* recordOffset = + new (m_comp, GT_RECORD_ASYNC_JOIN) GenTreeRecordAsyncJoin((int)(m_comp->compSuspensionPoints->size() - 1)); LIR::AsRange(joinBB).InsertAtBeginning(recordOffset); } diff --git a/src/coreclr/jit/async.h b/src/coreclr/jit/async.h index 53ac2bc9274b68..55c2d0cc8653a6 100644 --- a/src/coreclr/jit/async.h +++ b/src/coreclr/jit/async.h @@ -132,7 +132,9 @@ class AsyncTransformation GenTreeFlags indirFlags = GTF_IND_NONFAULTING); GenTreeStoreInd* StoreAtOffset(GenTree* base, unsigned offset, GenTree* value, var_types storeType); - void CreateDebugInfoForSuspensionPoint(GenTreeCall* asyncCall, const ContinuationLayout& layout, BasicBlock* joinBB); + void CreateDebugInfoForSuspensionPoint(GenTreeCall* asyncCall, + const ContinuationLayout& layout, + BasicBlock* joinBB); unsigned GetDataArrayVar(); unsigned GetGCDataArrayVar(); diff --git a/src/coreclr/jit/codegencommon.cpp b/src/coreclr/jit/codegencommon.cpp index 3edb9fef750789..c12e6a325ed2e4 100644 --- a/src/coreclr/jit/codegencommon.cpp +++ b/src/coreclr/jit/codegencommon.cpp @@ -6671,28 +6671,26 @@ void CodeGen::genReportAsyncDebugInfo() ICorDebugInfo::AsyncInfo asyncInfo; asyncInfo.NumSuspensionPoints = static_cast(suspPoints->size()); - ICorDebugInfo::AsyncSuspensionPoint* hostSuspensionPoints = - static_cast(compiler->info.compCompHnd->allocateArray( - suspPoints->size() * sizeof(ICorDebugInfo::AsyncSuspensionPoint))); + ICorDebugInfo::AsyncSuspensionPoint* hostSuspensionPoints = static_cast( + compiler->info.compCompHnd->allocateArray(suspPoints->size() * sizeof(ICorDebugInfo::AsyncSuspensionPoint))); for (size_t i = 0; i < suspPoints->size(); i++) { AsyncSuspensionPoint& suspPoint = (*suspPoints)[i]; if (suspPoint.nativeLoc.Valid()) { - hostSuspensionPoints[i].NativeOffset = suspPoint.nativeLoc.CodeOffset(GetEmitter()); + hostSuspensionPoints[i].NativeOffset = suspPoint.nativeLoc.CodeOffset(GetEmitter()); hostSuspensionPoints[i].NumContinuationVars = suspPoint.numContinuationVars; } else { - hostSuspensionPoints[i].NativeOffset = 0; + hostSuspensionPoints[i].NativeOffset = 0; hostSuspensionPoints[i].NumContinuationVars = 0; } } jitstd::vector* asyncVars = compiler->compAsyncVars; - ICorDebugInfo::AsyncContinuationVarInfo* hostVars = - static_cast(compiler->info.compCompHnd->allocateArray( - asyncVars->size() * sizeof(ICorDebugInfo::AsyncContinuationVarInfo))); + ICorDebugInfo::AsyncContinuationVarInfo* hostVars = static_cast( + compiler->info.compCompHnd->allocateArray(asyncVars->size() * sizeof(ICorDebugInfo::AsyncContinuationVarInfo))); for (size_t i = 0; i < asyncVars->size(); i++) hostVars[i] = (*asyncVars)[i]; diff --git a/src/coreclr/jit/codegenxarch.cpp b/src/coreclr/jit/codegenxarch.cpp index 58b980b44816ea..1ebb7ad5d587e9 100644 --- a/src/coreclr/jit/codegenxarch.cpp +++ b/src/coreclr/jit/codegenxarch.cpp @@ -2347,7 +2347,6 @@ void CodeGen::genCodeForTreeNode(GenTree* treeNode) // Do nothing; these nodes are simply markers for debug info. break; - #if defined(TARGET_AMD64) case GT_CCMP: genCodeForCCMP(treeNode->AsCCMP()); diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 08b8e8aa656f5c..b9858aa0a019c8 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -8637,8 +8637,8 @@ class Compiler jitstd::list genIPmappings; jitstd::list genRichIPmappings; - jitstd::vector* compSuspensionPoints = nullptr; - jitstd::vector* compAsyncVars = nullptr; + jitstd::vector* compSuspensionPoints = nullptr; + jitstd::vector* compAsyncVars = nullptr; // Managed RetVal - A side hash table meant to record the mapping from a // GT_CALL node to its debug info. This info is used to emit sequence points diff --git a/src/coreclr/jit/importercalls.cpp b/src/coreclr/jit/importercalls.cpp index 22d06f00e62be6..2577343545f4a9 100644 --- a/src/coreclr/jit/importercalls.cpp +++ b/src/coreclr/jit/importercalls.cpp @@ -6815,9 +6815,7 @@ void Compiler::impCheckForPInvokeCall( // opcode - The IL opcode for the call // prefixFlags - Flags containing context handling information from IL // -void Compiler::impSetupAndSpillForAsyncCall(GenTreeCall* call, - OPCODE opcode, - unsigned prefixFlags) +void Compiler::impSetupAndSpillForAsyncCall(GenTreeCall* call, OPCODE opcode, unsigned prefixFlags) { AsyncCallInfo asyncInfo; From 9fdd8a70bab957ef4f0795292f82df9ab307af11 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Thu, 9 Oct 2025 14:49:53 +0200 Subject: [PATCH 28/51] Print reported async debug info, always report it --- src/coreclr/jit/async.cpp | 5 ----- src/coreclr/jit/codegencommon.cpp | 19 +++++++++++++++++++ 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/coreclr/jit/async.cpp b/src/coreclr/jit/async.cpp index 559910ee81aff1..830e038ca814f1 100644 --- a/src/coreclr/jit/async.cpp +++ b/src/coreclr/jit/async.cpp @@ -2222,11 +2222,6 @@ void AsyncTransformation::CreateDebugInfoForSuspensionPoint(GenTreeCall* const ContinuationLayout& layout, BasicBlock* joinBB) { - if (!m_comp->opts.compDbgInfo) - { - return; - } - uint32_t numLocals = 0; for (const LiveLocalInfo& local : layout.Locals) { diff --git a/src/coreclr/jit/codegencommon.cpp b/src/coreclr/jit/codegencommon.cpp index c12e6a325ed2e4..4153e2a3abe0a7 100644 --- a/src/coreclr/jit/codegencommon.cpp +++ b/src/coreclr/jit/codegencommon.cpp @@ -6696,6 +6696,25 @@ void CodeGen::genReportAsyncDebugInfo() compiler->info.compCompHnd->reportAsyncDebugInfo(&asyncInfo, hostSuspensionPoints, hostVars, static_cast(asyncVars->size())); + +#ifdef DEBUG + if (verbose) + { + printf("Reported async suspension points:\n"); + for (size_t i = 0; i < suspPoints->size(); i++) + { + printf(" [%zu] Offset = %x, NumAsyncVars = %u\n", i, hostSuspensionPoints[i].NativeOffset, + hostSuspensionPoints[i].NumContinuationVars); + } + + printf("Reported async vars:\n"); + for (size_t i = 0; i < asyncVars->size(); i++) + { + printf(" [%zu] VarNumber = %u, Offset = %x, GCIndex = %u\n", i, hostVars[i].VarNumber, hostVars[i].Offset, + hostVars[i].GCIndex); + } + } +#endif } /*============================================================================ From 1da796e5dbc19e3836ea2372e704fd9f0dd4a87d Mon Sep 17 00:00:00 2001 From: rcj1 Date: Fri, 10 Oct 2025 12:56:28 -0700 Subject: [PATCH 29/51] exception tostring --- .../CompilerServices/AsyncHelpers.CoreCLR.cs | 5 ++-- src/coreclr/vm/debugdebugger.cpp | 8 ++++++ src/coreclr/vm/debugdebugger.h | 10 ------- src/coreclr/vm/excep.cpp | 3 +- src/coreclr/vm/exceptionhandling.cpp | 4 +-- src/coreclr/vm/exceptionhandling.h | 3 +- src/coreclr/vm/exinfo.cpp | 1 + src/coreclr/vm/exinfo.h | 14 ---------- src/coreclr/vm/exkind.h | 28 +++++++++++++++++++ src/coreclr/vm/jithelpers.cpp | 4 +-- 10 files changed, 48 insertions(+), 32 deletions(-) create mode 100644 src/coreclr/vm/exkind.h diff --git a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs index 8e1bcdc891d8c6..08e2bf22419406 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs @@ -348,6 +348,7 @@ public static void PostToSyncContext(ThunkTask task, SynchronizationContext sync private static class ThunkTaskCore { + [StackTraceHidden] public static unsafe void MoveNext(T task) where T : Task where TOps : IThunkTaskOps { ExecutionAndSyncBlockStore contexts = default; @@ -418,15 +419,15 @@ public static unsafe void MoveNext(T task) where T : Task where TOps : } } - private static Continuation UnwindToPossibleHandler(Continuation continuation, Exception ex) + private static unsafe Continuation UnwindToPossibleHandler(Continuation continuation, Exception ex) { while (true) { Debug.Assert(continuation.Next != null); continuation = continuation.Next; + if (continuation.Resume != null) AddContinuationToExInternal(continuation, ex); if ((continuation.Flags & CorInfoContinuationFlags.CORINFO_CONTINUATION_NEEDS_EXCEPTION) != 0) return continuation; - AddContinuationToExInternal(continuation, ex); } } diff --git a/src/coreclr/vm/debugdebugger.cpp b/src/coreclr/vm/debugdebugger.cpp index 9deb22f306caca..a155177e43ddc9 100644 --- a/src/coreclr/vm/debugdebugger.cpp +++ b/src/coreclr/vm/debugdebugger.cpp @@ -1608,6 +1608,14 @@ void DebugStackTrace::Element::InitPass2() bool bRes = false; + if (this->pFunc->IsAsyncThunkMethod()) + { + if (this->pFunc->IsDynamicMethod()) + { + bRes = false; + } + } + if (this->flags & STEF_CONTINUATION) { PCODE addr = this->pFunc->GetNativeCode(); diff --git a/src/coreclr/vm/debugdebugger.h b/src/coreclr/vm/debugdebugger.h index 21efdb8628754f..b4f89ec04079f1 100644 --- a/src/coreclr/vm/debugdebugger.h +++ b/src/coreclr/vm/debugdebugger.h @@ -64,16 +64,6 @@ class StackFrameHelper : public Object }; -struct Continuation : public Object -{ - struct Continuation* next; - TADDR resume; - uint32_t state; - CorInfoContinuationFlags flags; - uint8_t* data; // byte[] -> byte* (managed arrays are just pointers in native) - void** gcData; // object[] -> void** (object references) -}; - #ifdef USE_CHECKED_OBJECTREFS typedef REF STACKFRAMEHELPERREF; #else diff --git a/src/coreclr/vm/excep.cpp b/src/coreclr/vm/excep.cpp index 8c110f5ae2a4a5..86b771f65e94c8 100644 --- a/src/coreclr/vm/excep.cpp +++ b/src/coreclr/vm/excep.cpp @@ -52,6 +52,7 @@ #endif // HAVE_GCCOVER #include "exinfo.h" +#include "exkind.h" //---------------------------------------------------------------------------- // @@ -3018,7 +3019,7 @@ void StackTraceInfo::AppendElement(OBJECTHANDLE hThrowable, UINT_PTR currentIP, LOG((LF_EH, LL_INFO10000, "StackTraceInfo::AppendElement IP = %p, SP = %p, %s::%s\n", currentIP, currentSP, pFunc ? pFunc->m_pszDebugClassName : "", pFunc ? pFunc->m_pszDebugMethodName : "" )); - if (pFunc != NULL && pFunc->IsILStub()) + if ((pFunc != NULL && pFunc->IsILStub()) || pFunc->IsAsyncThunkMethod()) return; // Do not save stacktrace to preallocated exception. These are shared. diff --git a/src/coreclr/vm/exceptionhandling.cpp b/src/coreclr/vm/exceptionhandling.cpp index 41b96489162b26..0fd67ad68bce2e 100644 --- a/src/coreclr/vm/exceptionhandling.cpp +++ b/src/coreclr/vm/exceptionhandling.cpp @@ -1530,7 +1530,7 @@ void FirstChanceExceptionNotification() #endif // TARGET_WINDOWS } -VOID DECLSPEC_NORETURN DispatchManagedException(OBJECTREF throwable, CONTEXT* pExceptionContext, EXCEPTION_RECORD* pExceptionRecord) +VOID DECLSPEC_NORETURN DispatchManagedException(OBJECTREF throwable, CONTEXT* pExceptionContext, EXCEPTION_RECORD* pExceptionRecord, ExKind exKind /* = ExKind::None */) { STATIC_CONTRACT_THROWS; STATIC_CONTRACT_GC_TRIGGERS; @@ -1558,7 +1558,7 @@ VOID DECLSPEC_NORETURN DispatchManagedException(OBJECTREF throwable, CONTEXT* pE newExceptionRecord.ExceptionRecord = NULL; } - ExInfo exInfo(pThread, &newExceptionRecord, pExceptionContext, ExKind::Throw); + ExInfo exInfo(pThread, &newExceptionRecord, pExceptionContext, (ExKind)((uint8_t)ExKind::Throw | (uint8_t)exKind)); #ifdef HOST_WINDOWS // On Windows, this enables the possibility to propagate a longjmp across managed frames. Longjmp diff --git a/src/coreclr/vm/exceptionhandling.h b/src/coreclr/vm/exceptionhandling.h index b135582d272e63..eafd2ae801308c 100644 --- a/src/coreclr/vm/exceptionhandling.h +++ b/src/coreclr/vm/exceptionhandling.h @@ -11,6 +11,7 @@ #include "eexcp.h" #include "exstatecommon.h" +#include "exkind.h" // This address lies in the NULL pointer partition of the process memory. // Accessing it will result in AV. @@ -28,7 +29,7 @@ CallDescrWorkerUnwindFrameChainHandler(IN PEXCEPTION_RECORD pExceptionRe IN OUT PT_CONTEXT pContextRecord, IN OUT PT_DISPATCHER_CONTEXT pDispatcherContext); -VOID DECLSPEC_NORETURN DispatchManagedException(OBJECTREF throwable, CONTEXT *pExceptionContext, EXCEPTION_RECORD *pExceptionRecord = NULL); +VOID DECLSPEC_NORETURN DispatchManagedException(OBJECTREF throwable, CONTEXT *pExceptionContext, EXCEPTION_RECORD *pExceptionRecord = NULL, ExKind exKind = ExKind::None); VOID DECLSPEC_NORETURN DispatchManagedException(OBJECTREF throwable); VOID DECLSPEC_NORETURN DispatchManagedException(RuntimeExceptionKind reKind); VOID DECLSPEC_NORETURN DispatchRethrownManagedException(); diff --git a/src/coreclr/vm/exinfo.cpp b/src/coreclr/vm/exinfo.cpp index 916910fb77e187..06b79c289e7121 100644 --- a/src/coreclr/vm/exinfo.cpp +++ b/src/coreclr/vm/exinfo.cpp @@ -6,6 +6,7 @@ #include "common.h" #include "exinfo.h" +#include "exkind.h" #include "dbginterface.h" #ifdef FEATURE_EH_FUNCLETS diff --git a/src/coreclr/vm/exinfo.h b/src/coreclr/vm/exinfo.h index 26ee34c393e942..090b298047d14e 100644 --- a/src/coreclr/vm/exinfo.h +++ b/src/coreclr/vm/exinfo.h @@ -190,20 +190,6 @@ struct RhEHClause } }; -enum class ExKind : uint8_t -{ - None = 0, - Throw = 1, - HardwareFault = 2, - KindMask = 3, - - RethrowFlag = 4, - - SupersededFlag = 8, - - InstructionFaultFlag = 0x10 -}; - struct PAL_SEHException; struct LastReportedFuncletInfo diff --git a/src/coreclr/vm/exkind.h b/src/coreclr/vm/exkind.h new file mode 100644 index 00000000000000..08f3251a3e74d5 --- /dev/null +++ b/src/coreclr/vm/exkind.h @@ -0,0 +1,28 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// RuntimeExceptionKind.h +// + +#ifndef __exkind_h__ +#define __exkind_h__ + +#include + +//========================================================================== +// Identifies exception kinds. +//========================================================================== +enum class ExKind : uint8_t +{ + None = 0, + Throw = 1, + HardwareFault = 2, + KindMask = 3, + + RethrowFlag = 4, + + SupersededFlag = 8, + + InstructionFaultFlag = 0x10 +}; + +#endif // __exkind_h__ diff --git a/src/coreclr/vm/jithelpers.cpp b/src/coreclr/vm/jithelpers.cpp index 58b418fe5fb309..30bf91a1055c99 100644 --- a/src/coreclr/vm/jithelpers.cpp +++ b/src/coreclr/vm/jithelpers.cpp @@ -58,6 +58,7 @@ #include "excep.h" #endif #include "exinfo.h" +#include "exkind.h" #include "arraynative.inl" using std::isfinite; @@ -934,7 +935,6 @@ HCIMPL1(void, IL_ThrowExact, Object* obj) ResetCurrentContext(); OBJECTREF oref = ObjectToOBJECTREF(obj); - GetThread()->GetExceptionState()->SetRaisingForeignException(); Thread *pThread = GetThread(); @@ -949,7 +949,7 @@ HCIMPL1(void, IL_ThrowExact, Object* obj) FC_CAN_TRIGGER_GC(); #ifdef FEATURE_EH_FUNCLETS - DispatchManagedException(oref, exceptionFrame.GetContext()); + DispatchManagedException(oref, exceptionFrame.GetContext(), NULL, ExKind::RethrowFlag); #elif defined(TARGET_X86) INSTALL_MANAGED_EXCEPTION_DISPATCHER; INSTALL_UNWIND_AND_CONTINUE_HANDLER; From 818b708c770ee4768b150f94517820e2579824c6 Mon Sep 17 00:00:00 2001 From: rcj1 Date: Fri, 10 Oct 2025 15:27:04 -0700 Subject: [PATCH 30/51] async2 test stuff --- rachel-tests/OneDrive_1_10-6-2025.sln | 30 +++++ rachel-tests/asyncRecursiveTest/Program.cs | 117 ++++++++++++++++++ .../asyncRecursiveTest.csproj | 17 +++ rachel-tests/asyncv1Assembly/Program.cs | 48 +++++++ .../asyncv1Assembly/asyncv1Assembly.csproj | 10 ++ 5 files changed, 222 insertions(+) create mode 100644 rachel-tests/OneDrive_1_10-6-2025.sln create mode 100644 rachel-tests/asyncRecursiveTest/Program.cs create mode 100644 rachel-tests/asyncRecursiveTest/asyncRecursiveTest.csproj create mode 100644 rachel-tests/asyncv1Assembly/Program.cs create mode 100644 rachel-tests/asyncv1Assembly/asyncv1Assembly.csproj diff --git a/rachel-tests/OneDrive_1_10-6-2025.sln b/rachel-tests/OneDrive_1_10-6-2025.sln new file mode 100644 index 00000000000000..f2b45c26fab291 --- /dev/null +++ b/rachel-tests/OneDrive_1_10-6-2025.sln @@ -0,0 +1,30 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.2.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "asyncRecursiveTest", "asyncRecursiveTest\asyncRecursiveTest.csproj", "{CFB42D5E-EEC7-431D-EE9C-A11E7A420350}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "asyncv1Assembly", "asyncv1Assembly\asyncv1Assembly.csproj", "{A9B6E874-A0E1-80B8-371E-98977B9DECCD}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {CFB42D5E-EEC7-431D-EE9C-A11E7A420350}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CFB42D5E-EEC7-431D-EE9C-A11E7A420350}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CFB42D5E-EEC7-431D-EE9C-A11E7A420350}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CFB42D5E-EEC7-431D-EE9C-A11E7A420350}.Release|Any CPU.Build.0 = Release|Any CPU + {A9B6E874-A0E1-80B8-371E-98977B9DECCD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A9B6E874-A0E1-80B8-371E-98977B9DECCD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A9B6E874-A0E1-80B8-371E-98977B9DECCD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A9B6E874-A0E1-80B8-371E-98977B9DECCD}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {B3AF8E94-B59F-4F8C-9578-AD0780CBAAFD} + EndGlobalSection +EndGlobal diff --git a/rachel-tests/asyncRecursiveTest/Program.cs b/rachel-tests/asyncRecursiveTest/Program.cs new file mode 100644 index 00000000000000..2d0efb3cbfd8d4 --- /dev/null +++ b/rachel-tests/asyncRecursiveTest/Program.cs @@ -0,0 +1,117 @@ +// using System.Runtime.CompilerServices; + +// class Program +// { +// static volatile uint _debuggerAttached = 0xDEADBEEF; +// static void Main() +// { +// AsyncMain(); +// } + +// static void AsyncMain() +// { +// RecursiveMethod(10).GetAwaiter().GetResult(); +// } + +// [MethodImpl(MethodImplOptions.NoInlining)] +// static unsafe void AttachWarning() +// { +// Console.WriteLine($"Attach debugger now to {Environment.ProcessId}!"); +// nint ptr = (nint)Unsafe.AsPointer(ref _debuggerAttached); +// Console.WriteLine($"Debugger flag at address: {ptr:X}"); +// while (_debuggerAttached == 0xDEADBEEF) +// { +// Thread.Sleep(10); +// } +// _debuggerAttached = 0xDEADBEEF; +// } + +// static async Task RecursiveMethod(int count) +// { +// Console.WriteLine($"Forward Count: {count}"); +// if (count != 0) +// { +// int result = await RecursiveMethod(count - 1); + +// Console.WriteLine($"Return Count: {result}"); +// if (count == 1) +// { +// AttachWarning(); +// } +// return count * 2; +// } + +// Console.WriteLine("Base case reached."); +// AttachWarning(); + +// await Task.Yield(); + +// Console.WriteLine("Returning from base case."); +// AttachWarning(); + +// return -1; +// } +// } + +namespace AsyncRecursiveTest; + +public class Program +{ + static async Task Main() + { + await Task.Yield(); + try + { + // await MyMethod2(3); + await V1Methods.Test(MyMethod); + } + catch (NotImplementedException ex) + { + Console.WriteLine($"Caught exception: {ex}"); + } + } + + private static async Task MyMethod2(int i) + { + await Task.Yield(); + try + { + await MyMethod(i); + return i * 2; + } + catch (NotImplementedException ex) + { + Console.WriteLine($"Caught exception in MyMethod2 with: {ex}"); + return -1; + } + } + + private static async Task MyMethod(int i) + { + try + { + await Task.Yield(); + await Task.Yield(); + await Task.Yield(); + for (int j = i; j > 0; j--) + { + await MyMethod(j - 1); + } + if (i == 0) await V1Methods.Test2(i); + // } + // catch (Exception ex) + // { + // Console.WriteLine($"Caught exception in MyMethod with: {ex}"); + // } + } + finally + { + Console.WriteLine($"In finally block of MyMethod with {i}"); + if (i == 2) throw new NotImplementedException("Not Found from MyMethod"); + // throw new NotImplementedException("Exception from finally block of MyMethod"); + } + + Console.WriteLine(3 * i); + return i * 2; + } +} \ No newline at end of file diff --git a/rachel-tests/asyncRecursiveTest/asyncRecursiveTest.csproj b/rachel-tests/asyncRecursiveTest/asyncRecursiveTest.csproj new file mode 100644 index 00000000000000..d843d003139cb2 --- /dev/null +++ b/rachel-tests/asyncRecursiveTest/asyncRecursiveTest.csproj @@ -0,0 +1,17 @@ + + + + Exe + net10.0 + enable + enable + true + $(Features);runtime-async=on + $(NoWarn);SYSLIB5007 + + + + + + + diff --git a/rachel-tests/asyncv1Assembly/Program.cs b/rachel-tests/asyncv1Assembly/Program.cs new file mode 100644 index 00000000000000..ce483a9c7237c0 --- /dev/null +++ b/rachel-tests/asyncv1Assembly/Program.cs @@ -0,0 +1,48 @@ +namespace AsyncRecursiveTest; + +public static class V1Methods +{ + public static async Task Test(Func method) + { + await Test1(method); + await Task.Yield(); + // Console.ReadKey(); + // throw new Exception("Exception from Test"); + } + + public static async Task Test1(Func method) + { + const int i = 3; + Console.WriteLine(i); + try + { + await method(i); + } + catch (Exception ex) when (ex.Message.Contains("404")) + { + Console.WriteLine($"Caught exception in Test1 with: {ex}"); + // throw new NotImplementedException("Not implemented exception from Test1"); + } + // finally + // { + // Console.WriteLine("In finally block of Test1"); + // throw new NotImplementedException("Exception from finally block of Test1"); + // } + Console.WriteLine(2 * i); + } + + public static async Task Test2(int i) + { + Console.WriteLine($"In Test2 with {i}"); + Console.ReadKey(); + throw new NullReferenceException("Exception from Test2"); + // await Test3(i); + } + + // public static async Task Test3(int i) + // { + // await Task.Yield(); + // Console.WriteLine($"In Test3 with {i}"); + // throw new Exception("Exception from Test3"); + // } +} \ No newline at end of file diff --git a/rachel-tests/asyncv1Assembly/asyncv1Assembly.csproj b/rachel-tests/asyncv1Assembly/asyncv1Assembly.csproj new file mode 100644 index 00000000000000..0a280f4b683df1 --- /dev/null +++ b/rachel-tests/asyncv1Assembly/asyncv1Assembly.csproj @@ -0,0 +1,10 @@ + + + + Library + net10.0 + enable + enable + + + From 69e02db8961eb7e2aee3db12ceed5e265b9b3736 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Tue, 14 Oct 2025 16:08:47 +0200 Subject: [PATCH 31/51] Store target IPs in AsyncResumeILStubResolver --- src/coreclr/vm/ilstubcache.cpp | 24 +++++++---- src/coreclr/vm/ilstubcache.h | 6 ++- src/coreclr/vm/ilstubresolver.cpp | 22 ++++++++++ src/coreclr/vm/ilstubresolver.h | 18 ++++++++ src/coreclr/vm/jitinterface.cpp | 71 ++++++++++++++++--------------- src/coreclr/vm/jitinterface.h | 4 +- 6 files changed, 99 insertions(+), 46 deletions(-) diff --git a/src/coreclr/vm/ilstubcache.cpp b/src/coreclr/vm/ilstubcache.cpp index 13f6891133f56a..e5422bd3ea3ee9 100644 --- a/src/coreclr/vm/ilstubcache.cpp +++ b/src/coreclr/vm/ilstubcache.cpp @@ -70,7 +70,7 @@ void CreateModuleIndependentSignature(LoaderHeap* pCreationHeap, // static MethodDesc* ILStubCache::CreateAndLinkNewILStubMethodDesc(LoaderAllocator* pAllocator, MethodTable* pMT, DWORD dwStubFlags, Module* pSigModule, PCCOR_SIGNATURE pSig, DWORD cbSig, SigTypeContext *pTypeContext, - ILStubLinker* pStubLinker, BOOL isAsync /* = FALSE */) + ILStubLinker* pStubLinker, BOOL isAsync /* = FALSE */, ILStubResolver* pResolver /* = NULL */) { CONTRACT (MethodDesc*) { @@ -88,13 +88,14 @@ MethodDesc* ILStubCache::CreateAndLinkNewILStubMethodDesc(LoaderAllocator* pAllo pSig, cbSig, isAsync, pTypeContext, - &amTracker); + &amTracker, + pResolver); amTracker.SuppressRelease(); pStubLinker->SetStubMethodDesc(pStubMD); - ILStubResolver *pResolver = pStubMD->AsDynamicMethodDesc()->GetILStubResolver(); + pResolver = pStubMD->AsDynamicMethodDesc()->GetILStubResolver(); pResolver->SetStubMethodDesc(pStubMD); @@ -148,7 +149,7 @@ namespace // static MethodDesc* ILStubCache::CreateNewMethodDesc(LoaderHeap* pCreationHeap, MethodTable* pMT, DWORD dwStubFlags, Module* pSigModule, PCCOR_SIGNATURE pSig, DWORD cbSig, BOOL isAsync, SigTypeContext *pTypeContext, - AllocMemTracker* pamTracker) + AllocMemTracker* pamTracker, ILStubResolver* pResolver) { CONTRACT (MethodDesc*) { @@ -216,12 +217,19 @@ MethodDesc* ILStubCache::CreateNewMethodDesc(LoaderHeap* pCreationHeap, MethodTa pMD->SetStatic(); } - pMD->m_pResolver = (ILStubResolver*)pamTracker->Track(pCreationHeap->AllocMem(S_SIZE_T(sizeof(ILStubResolver)))); + if (pResolver != NULL) + { + pMD->m_pResolver = pResolver; + } + else + { + pMD->m_pResolver = (ILStubResolver*)pamTracker->Track(pCreationHeap->AllocMem(S_SIZE_T(sizeof(ILStubResolver)))); #ifdef _DEBUG - // Poison the ILStubResolver storage - memset((void*)pMD->m_pResolver, 0xCC, sizeof(ILStubResolver)); + // Poison the ILStubResolver storage + memset((void*)pMD->m_pResolver, 0xCC, sizeof(ILStubResolver)); #endif // _DEBUG - pMD->m_pResolver = new (pMD->m_pResolver) ILStubResolver(); + pMD->m_pResolver = new (pMD->m_pResolver) ILStubResolver(); + } if (SF_IsArrayOpStub(dwStubFlags)) { diff --git a/src/coreclr/vm/ilstubcache.h b/src/coreclr/vm/ilstubcache.h index 52be99d9fb8ef1..873769d61b6aa7 100644 --- a/src/coreclr/vm/ilstubcache.h +++ b/src/coreclr/vm/ilstubcache.h @@ -74,7 +74,8 @@ class ILStubCache final DWORD cbSig, SigTypeContext *pTypeContext, ILStubLinker* pStubLinker, - BOOL isAsync = FALSE); + BOOL isAsync = FALSE, + class ILStubResolver* pResolver = NULL); MethodTable * GetStubMethodTable() { @@ -100,7 +101,8 @@ class ILStubCache final DWORD cbSig, BOOL isAsync, SigTypeContext *pTypeContext, - AllocMemTracker* pamTracker); + AllocMemTracker* pamTracker, + class ILStubResolver* pResolver = NULL); private: // Inner classes struct ILStubCacheEntry diff --git a/src/coreclr/vm/ilstubresolver.cpp b/src/coreclr/vm/ilstubresolver.cpp index 0ab3b1476bccbb..09d395f6c5af7d 100644 --- a/src/coreclr/vm/ilstubresolver.cpp +++ b/src/coreclr/vm/ilstubresolver.cpp @@ -559,3 +559,25 @@ void ILStubResolver::StubGenFailed(ILStubResolver* pResolver) pResolver->ClearCompileTimeState(ILNotYetGenerated); } + +PCODE AsyncResumeILStubResolver::GetFinalResumeMethodStartAddress() +{ + return m_resumeIP; +} + +#ifndef DACCESS_COMPILE +PCODE* AsyncResumeILStubResolver::GetAddrOfResumeMethodStartAddress() +{ + return &m_resumeIP; +} + +void AsyncResumeILStubResolver::SetResumeMethodStartAddress(PCODE ip) +{ + m_resumeIP = ip; +} + +void AsyncResumeILStubResolver::SetFinalResumeMethodStartAddress(PCODE ip) +{ + m_finalResumeIP = ip; +} +#endif diff --git a/src/coreclr/vm/ilstubresolver.h b/src/coreclr/vm/ilstubresolver.h index 4e970423a69da0..6d888cfba79665 100644 --- a/src/coreclr/vm/ilstubresolver.h +++ b/src/coreclr/vm/ilstubresolver.h @@ -108,6 +108,24 @@ class ILStubResolver : public DynamicResolver PTR_LoaderHeap m_loaderHeap; }; +class AsyncResumeILStubResolver : public ILStubResolver +{ +public: + PCODE GetFinalResumeMethodStartAddress(); + +#ifndef DACCESS_COMPILE + PCODE* GetAddrOfResumeMethodStartAddress(); + void SetResumeMethodStartAddress(PCODE ip); + void SetFinalResumeMethodStartAddress(PCODE ip); +#endif + +protected: + // IP to resume in. Can be tier0 code if we suspended in an OSR method. + PCODE m_resumeIP = NULL; + // Final IP resumed in. Will be OSR method IP if suspension happened there. + PCODE m_finalResumeIP = NULL; +}; + typedef Holder, ILStubResolver::StubGenFailed, 0> ILStubGenHolder; #endif // __ILSTUBRESOLVER_H__ diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index 654dfff271e5e3..ce1adaa7c2c24f 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -61,6 +61,7 @@ #include "tailcallhelp.h" #include "patchpointinfo.h" +#include "ilstubresolver.h" // The Stack Overflow probe takes place in the COOPERATIVE_TRANSITION_BEGIN() macro // @@ -11061,10 +11062,34 @@ void CEEJitInfo::PublishFinalCodeAddress(PCODE addr) { LIMITED_METHOD_CONTRACT; - if (m_finalCodeAddressSlot != NULL) + if (m_resumptionStubResolver == NULL) { - *m_finalCodeAddressSlot = addr; + return; } + + m_resumptionStubResolver->SetFinalResumeMethodStartAddress(addr); + m_resumptionStubResolver->SetResumeMethodStartAddress(addr); + +#ifdef FEATURE_TIERED_COMPILATION + // Resumption stubs are uniquely coupled to the code version (since the + // continuation is), so we need to make sure we always keep calling the + // same version here. + PrepareCodeConfig* config = GetThread()->GetCurrentPrepareCodeConfig(); + NativeCodeVersion ncv = config->GetCodeVersion(); + if (ncv.GetOptimizationTier() == NativeCodeVersion::OptimizationTier1OSR) + { +#ifdef FEATURE_ON_STACK_REPLACEMENT + // The OSR version needs to resume in the tier0 version. The tier0 + // version will handle setting up the frame that the OSR version + // expects and then delegating back into the OSR version (knowing to do + // so through information stored in the continuation). + _ASSERTE(m_pPatchpointInfoFromRuntime != NULL); + m_resumptionStubResolver->SetResumeMethodStartAddress((DWORD_PTR)m_pPatchpointInfoFromRuntime->GetTier0EntryPoint()); +#else // !FEATURE_ON_STACK_REPLACEMENT + _ASSERTE(!"Unexpected optimization tier with OSR disabled"); +#endif // FEATURE_ON_STACK_REPLACEMENT + } +#endif } template @@ -14667,35 +14692,10 @@ CORINFO_METHOD_HANDLE CEEJitInfo::getAsyncResumptionStub() numArgs++; #endif -#ifdef FEATURE_TIERED_COMPILATION - // Resumption stubs are uniquely coupled to the code version (since the - // continuation is), so we need to make sure we always keep calling the - // same version here. - PrepareCodeConfig* config = GetThread()->GetCurrentPrepareCodeConfig(); - NativeCodeVersion ncv = config->GetCodeVersion(); - if (ncv.GetOptimizationTier() == NativeCodeVersion::OptimizationTier1OSR) - { -#ifdef FEATURE_ON_STACK_REPLACEMENT - // The OSR version needs to resume in the tier0 version. The tier0 - // version will handle setting up the frame that the OSR version - // expects and then delegating back into the OSR version (knowing to do - // so through information stored in the continuation). - _ASSERTE(m_pPatchpointInfoFromRuntime != NULL); - pCode->EmitLDC((DWORD_PTR)m_pPatchpointInfoFromRuntime->GetTier0EntryPoint()); -#else // !FEATURE_ON_STACK_REPLACEMENT - _ASSERTE(!"Unexpected optimization tier with OSR disabled"); -#endif // FEATURE_ON_STACK_REPLACEMENT - } - else -#endif // FEATURE_TIERED_COMPILATION - { - { - m_finalCodeAddressSlot = (PCODE*)amTracker.Track(m_pMethodBeingCompiled->GetLoaderAllocator()->GetHighFrequencyHeap()->AllocMem(S_SIZE_T(sizeof(PCODE)))); - } - - pCode->EmitLDC((DWORD_PTR)m_finalCodeAddressSlot); - pCode->EmitLDIND_I(); - } + void* resolverMem = amTracker.Track(loaderAlloc->GetHighFrequencyHeap()->AllocMem(S_SIZE_T(sizeof(AsyncResumeILStubResolver)))); + m_resumptionStubResolver = new (resolverMem) AsyncResumeILStubResolver(); + pCode->EmitLDC((DWORD_PTR)m_resumptionStubResolver->GetAddrOfResumeMethodStartAddress()); + pCode->EmitLDIND_I(); pCode->EmitCALLI(pCode->GetSigToken(calliSig.GetRawSig(), calliSig.GetRawSigLen()), numArgs, msig.IsReturnTypeVoid() ? 0 : 1); @@ -14821,15 +14821,18 @@ CORINFO_METHOD_HANDLE CEEJitInfo::getAsyncResumptionStub() md->GetModule(), stubSig.GetRawSig(), stubSig.GetRawSigLen(), &emptyCtx, - &sl); + &sl, + FALSE, /* isAsync */ + m_resumptionStubResolver); amTracker.SuppressRelease(); - ILStubResolver *pResolver = result->AsDynamicMethodDesc()->GetILStubResolver(); - pResolver->SetStubTargetMethodDesc(m_pMethodBeingCompiled); + m_resumptionStubResolver->SetStubTargetMethodDesc(m_pMethodBeingCompiled); const char* optimizationTierName = "UnknownTier"; #ifdef FEATURE_TIERED_COMPILATION + PrepareCodeConfig* config = GetThread()->GetCurrentPrepareCodeConfig(); + NativeCodeVersion ncv = config->GetCodeVersion(); switch (ncv.GetOptimizationTier()) { case NativeCodeVersion::OptimizationTier0: optimizationTierName = "Tier0"; break; diff --git a/src/coreclr/vm/jitinterface.h b/src/coreclr/vm/jitinterface.h index 18ade9201b1600..0649f4dbd91f45 100644 --- a/src/coreclr/vm/jitinterface.h +++ b/src/coreclr/vm/jitinterface.h @@ -852,7 +852,7 @@ class CEEJitInfo final : public CEECodeGenInfo m_pPatchpointInfoFromRuntime(NULL), m_ilOffset(0) #endif - , m_finalCodeAddressSlot(NULL) + , m_resumptionStubResolver(NULL) { CONTRACTL { @@ -949,7 +949,7 @@ protected : PatchpointInfo * m_pPatchpointInfoFromRuntime; unsigned m_ilOffset; #endif - PCODE* m_finalCodeAddressSlot; + class AsyncResumeILStubResolver* m_resumptionStubResolver; }; From 68c245b874e1e5e5d82687e729143c9df58ea18b Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Tue, 14 Oct 2025 16:12:41 +0200 Subject: [PATCH 32/51] Fix bug --- src/coreclr/vm/ilstubresolver.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/vm/ilstubresolver.cpp b/src/coreclr/vm/ilstubresolver.cpp index 09d395f6c5af7d..5c2fa0fc966421 100644 --- a/src/coreclr/vm/ilstubresolver.cpp +++ b/src/coreclr/vm/ilstubresolver.cpp @@ -562,7 +562,7 @@ void ILStubResolver::StubGenFailed(ILStubResolver* pResolver) PCODE AsyncResumeILStubResolver::GetFinalResumeMethodStartAddress() { - return m_resumeIP; + return m_finalResumeIP; } #ifndef DACCESS_COMPILE From e6770f7ea578676c73921c8a73e05679a30a436e Mon Sep 17 00:00:00 2001 From: rcj1 Date: Tue, 14 Oct 2025 09:46:03 -0700 Subject: [PATCH 33/51] wip --- .../CompilerServices/AsyncHelpers.CoreCLR.cs | 7 +- src/coreclr/vm/debugdebugger.cpp | 65 +++++++------------ 2 files changed, 29 insertions(+), 43 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs index efc08c2f179306..9d9a4f647f7798 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs @@ -427,8 +427,8 @@ public static unsafe void DispatchContinuations(T task) where T : Task, } catch (Exception ex) { - Continuation nextContinuation = UnwindToPossibleHandler(continuation, ex); - if (nextContinuation.Resume == null) + Continuation handlerContinuation = UnwindToPossibleHandler(continuation, ex); + if (handlerContinuation.Resume == null) { // Tail of AsyncTaskMethodBuilderT.SetException bool successfullySet = ex is OperationCanceledException oce ? @@ -480,7 +480,8 @@ private static unsafe Continuation UnwindToPossibleHandler(Continuation continua { while (true) { - if (continuation.Resume != null) AddContinuationToExInternal(continuation, ex); + if (continuation.Resume != null) + AddContinuationToExInternal(continuation, ex); if ((continuation.Flags & CorInfoContinuationFlags.CORINFO_CONTINUATION_NEEDS_EXCEPTION) != 0) return continuation; diff --git a/src/coreclr/vm/debugdebugger.cpp b/src/coreclr/vm/debugdebugger.cpp index a155177e43ddc9..162297568def1f 100644 --- a/src/coreclr/vm/debugdebugger.cpp +++ b/src/coreclr/vm/debugdebugger.cpp @@ -918,6 +918,11 @@ extern "C" BOOL QCALLTYPE DebugDebugger_IsManagedDebuggerAttached() } #endif // !DACCESS_COMPILE +BYTE* DebugInfoStoreNew2(void * pData, size_t cBytes) +{ + return new BYTE[cBytes]; +} + void DebugStackTrace::GetStackFramesFromException(OBJECTREF * e, GetStackFramesData *pData, PTRARRAYREF * pDynamicMethodArray /*= NULL*/ @@ -990,13 +995,28 @@ void DebugStackTrace::GetStackFramesFromException(OBJECTREF * e, // push frames and the method body is therefore non-contiguous. // Currently such methods always return an IP of 0, so they're easy // to spot. - DWORD dwNativeOffset; + DWORD dwNativeOffset = 0; UINT_PTR ip = cur.ip; if (cur.flags & STEF_CONTINUATION) { - // Continuation frames don't have a meaningful native offset. - // here we populate it with the continuation index which was stored in SP :( - dwNativeOffset = (DWORD)cur.sp; + PCODE addr = 0; + if (ip != (PCODE)NULL) + { + EECodeInfo codeInfo(ip); + addr = codeInfo.GetStartAddress(); + if (codeInfo.IsValid()) + { + DebugInfoRequest request; + request.InitFromStartingAddr(pMD, addr); + ICorDebugInfo::AsyncInfo asyncInfo = {}; + NewArrayHolder asyncSuspensionPoints(NULL); + NewArrayHolder asyncVars(NULL); + ULONG32 cAsyncVars = 0; + DebugInfoManager::GetAsyncDebugInfo(request, DebugInfoStoreNew2, nullptr, &asyncInfo, &asyncSuspensionPoints, &asyncVars, &cAsyncVars); + dwNativeOffset = asyncSuspensionPoints[cur.sp].NativeOffset; + } + } + ip = addr + dwNativeOffset; } else @@ -1589,11 +1609,6 @@ void ValidateILOffsets(MethodDesc *pFunc, uint8_t* ipColdStart, size_t coldLen, // Initialization done outside the TSL. // This may need to call locking operations that aren't safe under the TSL. -BYTE* DebugInfoStoreNew(void * pData, size_t cBytes) -{ - return new BYTE[cBytes]; -} - void DebugStackTrace::Element::InitPass2() { CONTRACTL @@ -1608,37 +1623,7 @@ void DebugStackTrace::Element::InitPass2() bool bRes = false; - if (this->pFunc->IsAsyncThunkMethod()) - { - if (this->pFunc->IsDynamicMethod()) - { - bRes = false; - } - } - - if (this->flags & STEF_CONTINUATION) - { - PCODE addr = this->pFunc->GetNativeCode(); - if (addr != (PCODE)NULL) - { - EECodeInfo codeInfo(addr); - if (codeInfo.IsValid()) - { - DebugInfoRequest request; - request.InitFromStartingAddr(this->pFunc, addr); - ICorDebugInfo::AsyncInfo asyncInfo = {}; - NewArrayHolder asyncSuspensionPoints(NULL); - NewArrayHolder asyncVars(NULL); - ULONG32 cAsyncVars = 0; - DebugInfoManager::GetAsyncDebugInfo(request, DebugInfoStoreNew, nullptr, &asyncInfo, &asyncSuspensionPoints, &asyncVars, &cAsyncVars); - this->dwILOffset = asyncSuspensionPoints[this->dwOffset].RootILOffset; - // leave native offset TBD, Noah - } - } - return; - } - - bool fAdjustOffset = (this->flags & STEF_IP_ADJUSTED) == 0 && this->dwOffset > 0; + bool fAdjustOffset = (this->flags & STEF_IP_ADJUSTED) == 0 && this->dwOffset > 0 && !(this->flags & STEF_CONTINUATION); // Check the cache! uint32_t dwILOffsetFromCache; From 99b76f459f4327d31dea4348000b4bc6bfe0c339 Mon Sep 17 00:00:00 2001 From: rcj1 Date: Tue, 14 Oct 2025 10:25:26 -0700 Subject: [PATCH 34/51] wip --- src/coreclr/vm/debugdebugger.cpp | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/src/coreclr/vm/debugdebugger.cpp b/src/coreclr/vm/debugdebugger.cpp index 162297568def1f..373d9203480b05 100644 --- a/src/coreclr/vm/debugdebugger.cpp +++ b/src/coreclr/vm/debugdebugger.cpp @@ -312,9 +312,10 @@ extern "C" void QCALLTYPE AsyncHelpers_AddContinuationToExInternal( MethodDesc* methodDesc = NonVirtualEntry2MethodDesc((PCODE)resume); ILStubResolver *pResolver = methodDesc->AsDynamicMethodDesc()->GetILStubResolver(); MethodDesc* pTargetMethodDesc = pResolver->GetStubTargetMethodDesc(); + AsyncResumeILStubResolver* pAsyncResumeResolver = (AsyncResumeILStubResolver*)pResolver; StackTraceInfo::AppendElement( handle, - (UINT_PTR)resume, + (UINT_PTR)pAsyncResumeResolver->GetFinalResumeMethodStartAddress(), state, pTargetMethodDesc, NULL); @@ -999,24 +1000,18 @@ void DebugStackTrace::GetStackFramesFromException(OBJECTREF * e, UINT_PTR ip = cur.ip; if (cur.flags & STEF_CONTINUATION) { - PCODE addr = 0; if (ip != (PCODE)NULL) { - EECodeInfo codeInfo(ip); - addr = codeInfo.GetStartAddress(); - if (codeInfo.IsValid()) - { - DebugInfoRequest request; - request.InitFromStartingAddr(pMD, addr); - ICorDebugInfo::AsyncInfo asyncInfo = {}; - NewArrayHolder asyncSuspensionPoints(NULL); - NewArrayHolder asyncVars(NULL); - ULONG32 cAsyncVars = 0; - DebugInfoManager::GetAsyncDebugInfo(request, DebugInfoStoreNew2, nullptr, &asyncInfo, &asyncSuspensionPoints, &asyncVars, &cAsyncVars); - dwNativeOffset = asyncSuspensionPoints[cur.sp].NativeOffset; - } + DebugInfoRequest request; + request.InitFromStartingAddr(pMD, ip); + ICorDebugInfo::AsyncInfo asyncInfo = {}; + NewArrayHolder asyncSuspensionPoints(NULL); + NewArrayHolder asyncVars(NULL); + ULONG32 cAsyncVars = 0; + DebugInfoManager::GetAsyncDebugInfo(request, DebugInfoStoreNew2, nullptr, &asyncInfo, &asyncSuspensionPoints, &asyncVars, &cAsyncVars); + dwNativeOffset = asyncSuspensionPoints[cur.sp].NativeOffset; + ip += dwNativeOffset; } - ip = addr + dwNativeOffset; } else From dfe302021e877bc4536ad63ba1b3dd5e8013c74f Mon Sep 17 00:00:00 2001 From: rcj1 Date: Tue, 14 Oct 2025 10:54:05 -0700 Subject: [PATCH 35/51] wip --- rachel-tests/asyncRecursiveTest/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rachel-tests/asyncRecursiveTest/Program.cs b/rachel-tests/asyncRecursiveTest/Program.cs index 2d0efb3cbfd8d4..13dbd8872518d1 100644 --- a/rachel-tests/asyncRecursiveTest/Program.cs +++ b/rachel-tests/asyncRecursiveTest/Program.cs @@ -63,7 +63,7 @@ static async Task Main() try { // await MyMethod2(3); - await V1Methods.Test(MyMethod); + await V1Methods.Test(MyMethod2); } catch (NotImplementedException ex) { From 12a21c89eeb93a19cddd34ec35fd0448a274b780 Mon Sep 17 00:00:00 2001 From: rcj1 Date: Tue, 14 Oct 2025 16:15:01 -0700 Subject: [PATCH 36/51] wip --- rachel-tests/OneDrive_1_10-6-2025.sln | 30 ----- rachel-tests/asyncRecursiveTest/Program.cs | 117 ------------------ .../tests/AsyncAssembly/AsyncAssembly.csproj | 5 +- .../tests/AsyncAssembly/Program.cs | 93 ++++++++++++++ .../AsyncV1Assembly/AsyncV1Assembly.csproj | 0 .../tests/AsyncV1Assembly}/Program.cs | 19 +-- .../tests/StackTraceTests.cs | 58 +++++++++ .../CoreCLRTestLibrary/PlatformDetection.cs | 2 + 8 files changed, 157 insertions(+), 167 deletions(-) delete mode 100644 rachel-tests/OneDrive_1_10-6-2025.sln delete mode 100644 rachel-tests/asyncRecursiveTest/Program.cs rename rachel-tests/asyncRecursiveTest/asyncRecursiveTest.csproj => src/libraries/System.Diagnostics.StackTrace/tests/AsyncAssembly/AsyncAssembly.csproj (70%) create mode 100644 src/libraries/System.Diagnostics.StackTrace/tests/AsyncAssembly/Program.cs rename rachel-tests/asyncv1Assembly/asyncv1Assembly.csproj => src/libraries/System.Diagnostics.StackTrace/tests/AsyncV1Assembly/AsyncV1Assembly.csproj (100%) rename {rachel-tests/asyncv1Assembly => src/libraries/System.Diagnostics.StackTrace/tests/AsyncV1Assembly}/Program.cs (51%) diff --git a/rachel-tests/OneDrive_1_10-6-2025.sln b/rachel-tests/OneDrive_1_10-6-2025.sln deleted file mode 100644 index f2b45c26fab291..00000000000000 --- a/rachel-tests/OneDrive_1_10-6-2025.sln +++ /dev/null @@ -1,30 +0,0 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.5.2.0 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "asyncRecursiveTest", "asyncRecursiveTest\asyncRecursiveTest.csproj", "{CFB42D5E-EEC7-431D-EE9C-A11E7A420350}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "asyncv1Assembly", "asyncv1Assembly\asyncv1Assembly.csproj", "{A9B6E874-A0E1-80B8-371E-98977B9DECCD}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {CFB42D5E-EEC7-431D-EE9C-A11E7A420350}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CFB42D5E-EEC7-431D-EE9C-A11E7A420350}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CFB42D5E-EEC7-431D-EE9C-A11E7A420350}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CFB42D5E-EEC7-431D-EE9C-A11E7A420350}.Release|Any CPU.Build.0 = Release|Any CPU - {A9B6E874-A0E1-80B8-371E-98977B9DECCD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A9B6E874-A0E1-80B8-371E-98977B9DECCD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A9B6E874-A0E1-80B8-371E-98977B9DECCD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A9B6E874-A0E1-80B8-371E-98977B9DECCD}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {B3AF8E94-B59F-4F8C-9578-AD0780CBAAFD} - EndGlobalSection -EndGlobal diff --git a/rachel-tests/asyncRecursiveTest/Program.cs b/rachel-tests/asyncRecursiveTest/Program.cs deleted file mode 100644 index 13dbd8872518d1..00000000000000 --- a/rachel-tests/asyncRecursiveTest/Program.cs +++ /dev/null @@ -1,117 +0,0 @@ -// using System.Runtime.CompilerServices; - -// class Program -// { -// static volatile uint _debuggerAttached = 0xDEADBEEF; -// static void Main() -// { -// AsyncMain(); -// } - -// static void AsyncMain() -// { -// RecursiveMethod(10).GetAwaiter().GetResult(); -// } - -// [MethodImpl(MethodImplOptions.NoInlining)] -// static unsafe void AttachWarning() -// { -// Console.WriteLine($"Attach debugger now to {Environment.ProcessId}!"); -// nint ptr = (nint)Unsafe.AsPointer(ref _debuggerAttached); -// Console.WriteLine($"Debugger flag at address: {ptr:X}"); -// while (_debuggerAttached == 0xDEADBEEF) -// { -// Thread.Sleep(10); -// } -// _debuggerAttached = 0xDEADBEEF; -// } - -// static async Task RecursiveMethod(int count) -// { -// Console.WriteLine($"Forward Count: {count}"); -// if (count != 0) -// { -// int result = await RecursiveMethod(count - 1); - -// Console.WriteLine($"Return Count: {result}"); -// if (count == 1) -// { -// AttachWarning(); -// } -// return count * 2; -// } - -// Console.WriteLine("Base case reached."); -// AttachWarning(); - -// await Task.Yield(); - -// Console.WriteLine("Returning from base case."); -// AttachWarning(); - -// return -1; -// } -// } - -namespace AsyncRecursiveTest; - -public class Program -{ - static async Task Main() - { - await Task.Yield(); - try - { - // await MyMethod2(3); - await V1Methods.Test(MyMethod2); - } - catch (NotImplementedException ex) - { - Console.WriteLine($"Caught exception: {ex}"); - } - } - - private static async Task MyMethod2(int i) - { - await Task.Yield(); - try - { - await MyMethod(i); - return i * 2; - } - catch (NotImplementedException ex) - { - Console.WriteLine($"Caught exception in MyMethod2 with: {ex}"); - return -1; - } - } - - private static async Task MyMethod(int i) - { - try - { - await Task.Yield(); - await Task.Yield(); - await Task.Yield(); - for (int j = i; j > 0; j--) - { - await MyMethod(j - 1); - } - if (i == 0) await V1Methods.Test2(i); - // } - // catch (Exception ex) - // { - // Console.WriteLine($"Caught exception in MyMethod with: {ex}"); - // } - } - finally - { - Console.WriteLine($"In finally block of MyMethod with {i}"); - if (i == 2) throw new NotImplementedException("Not Found from MyMethod"); - // throw new NotImplementedException("Exception from finally block of MyMethod"); - } - - Console.WriteLine(3 * i); - return i * 2; - } -} \ No newline at end of file diff --git a/rachel-tests/asyncRecursiveTest/asyncRecursiveTest.csproj b/src/libraries/System.Diagnostics.StackTrace/tests/AsyncAssembly/AsyncAssembly.csproj similarity index 70% rename from rachel-tests/asyncRecursiveTest/asyncRecursiveTest.csproj rename to src/libraries/System.Diagnostics.StackTrace/tests/AsyncAssembly/AsyncAssembly.csproj index d843d003139cb2..d9e233d783cbbe 100644 --- a/rachel-tests/asyncRecursiveTest/asyncRecursiveTest.csproj +++ b/src/libraries/System.Diagnostics.StackTrace/tests/AsyncAssembly/AsyncAssembly.csproj @@ -1,8 +1,7 @@  - Exe - net10.0 + netstandard2.0 enable enable true @@ -11,7 +10,7 @@ - + diff --git a/src/libraries/System.Diagnostics.StackTrace/tests/AsyncAssembly/Program.cs b/src/libraries/System.Diagnostics.StackTrace/tests/AsyncAssembly/Program.cs new file mode 100644 index 00000000000000..961209d8f967ac --- /dev/null +++ b/src/libraries/System.Diagnostics.StackTrace/tests/AsyncAssembly/Program.cs @@ -0,0 +1,93 @@ + +namespace AsyncTest; + +public class Program +{ + // v2 -> v1 -> v2 -> v1 + static async Task Foo() + { + await Task.Yield(); + try + { + await V1Methods.Test0(Foo1); + } + catch (NotImplementedException ex) + { + throw; + } + } + + private static async Task Foo1(int i) + { + await Task.Yield(); + try + { + await Foo2(i); + return i * 2; + } + catch (NotImplementedException ex) + { + Console.WriteLine($"Caught exception in MyMethod2 with: {ex}"); + return -1; + } + } + + private static async Task Foo2(int i) + { + try + { + await Task.Yield(); + await Task.Yield(); + await Task.Yield(); + for (int j = i; j > 0; j--) + { + await Foo2(j - 1); + } + if (i == 0) await V1Methods.Test2(i); + } + finally + { + Console.WriteLine($"In finally block of Foo2 with {i}"); + if (i == 2) throw new NotImplementedException("Not Found from Foo2"); + } + return i * 2; + } + + static async Task Bar(int i) + { + if (i == 0) + throw new Exception("Exception from Bar"); + await Bar(i - 1); + } + + static async Task Baz() + { + throw new Exception("Exception from Baz"); + } + + static async Task Moin() + { + var task = Moin2(); + Console.WriteLine("hello from Moin"); + return await task; + } + + private static async Task Moin2() + { + await Task.Delay(100); + throw new Exception("Exception from Moin2"); + } + + static async Task Qux(int i) + { + await Task.Yield(); + try + { + return 9 / i; + } + catch (DivideByZeroException ex) + { + throw new DivideByZeroException("Exception from Qux"); + } + } +} \ No newline at end of file diff --git a/rachel-tests/asyncv1Assembly/asyncv1Assembly.csproj b/src/libraries/System.Diagnostics.StackTrace/tests/AsyncV1Assembly/AsyncV1Assembly.csproj similarity index 100% rename from rachel-tests/asyncv1Assembly/asyncv1Assembly.csproj rename to src/libraries/System.Diagnostics.StackTrace/tests/AsyncV1Assembly/AsyncV1Assembly.csproj diff --git a/rachel-tests/asyncv1Assembly/Program.cs b/src/libraries/System.Diagnostics.StackTrace/tests/AsyncV1Assembly/Program.cs similarity index 51% rename from rachel-tests/asyncv1Assembly/Program.cs rename to src/libraries/System.Diagnostics.StackTrace/tests/AsyncV1Assembly/Program.cs index ce483a9c7237c0..a606d13e033b31 100644 --- a/rachel-tests/asyncv1Assembly/Program.cs +++ b/src/libraries/System.Diagnostics.StackTrace/tests/AsyncV1Assembly/Program.cs @@ -2,12 +2,10 @@ public static class V1Methods { - public static async Task Test(Func method) + public static async Task Test0(Func method) { await Test1(method); await Task.Yield(); - // Console.ReadKey(); - // throw new Exception("Exception from Test"); } public static async Task Test1(Func method) @@ -21,13 +19,8 @@ public static async Task Test1(Func method) catch (Exception ex) when (ex.Message.Contains("404")) { Console.WriteLine($"Caught exception in Test1 with: {ex}"); - // throw new NotImplementedException("Not implemented exception from Test1"); } - // finally - // { - // Console.WriteLine("In finally block of Test1"); - // throw new NotImplementedException("Exception from finally block of Test1"); - // } + Console.WriteLine(2 * i); } @@ -36,13 +29,5 @@ public static async Task Test2(int i) Console.WriteLine($"In Test2 with {i}"); Console.ReadKey(); throw new NullReferenceException("Exception from Test2"); - // await Test3(i); } - - // public static async Task Test3(int i) - // { - // await Task.Yield(); - // Console.WriteLine($"In Test3 with {i}"); - // throw new Exception("Exception from Test3"); - // } } \ No newline at end of file diff --git a/src/libraries/System.Diagnostics.StackTrace/tests/StackTraceTests.cs b/src/libraries/System.Diagnostics.StackTrace/tests/StackTraceTests.cs index 1b2c9fb7274efd..3bb248f978865a 100644 --- a/src/libraries/System.Diagnostics.StackTrace/tests/StackTraceTests.cs +++ b/src/libraries/System.Diagnostics.StackTrace/tests/StackTraceTests.cs @@ -406,6 +406,64 @@ public void ToString_ShowILOffset() }, regPattern).Dispose(); } + public static Dictionary MethodExceptionStrings = new Dictionary + { + { "Foo", @"(?s)" + + @".+Not Found from Foo2" + + @".+AsyncTest\.Program\.Foo2\(Int32 i\).+Program\.cs:line 63" + + @".+AsyncTest\.Program\.Foo2\(Int32 i\).+Program\.cs:line 48" + + @".+AsyncTest\.Program\.Foo1\(Int32 i\).+Program\.cs:line 33" }, + { "Bar", @"(?s)" + + @".+Exception from Bar" + + @".+AsyncTest\.Program\.Bar\(Int32 i\).+Program\.cs:line 71" + + @".+AsyncTest\.Program\.Bar\(Int32 i\).+Program\.cs:line 72" + + @".+AsyncTest\.Program\.Bar\(Int32 i\).+Program\.cs:line 72" + + @".+AsyncTest\.Program\.Bar\(Int32 i\).+Program\.cs:line 72" }, + { "Baz", @"(?s)" + + @".+Exception from Baz" + + @".+AsyncTest\.Program\.Baz\(\).+Program\.cs:line 77"}, + { "Qux", @"(?s)" + + @".+Exception from Qux" + + @".+AsyncTest\.Program\.Qux\(Int32 i\).+Program\.cs:line 102"}, + { "Moin", @"(?s)" + + @".+Exception from Moin2" + + @".+AsyncTest\.Program\.Moin2\(\).+Program\.cs:line 90" + + @".+AsyncTest\.Program\.Moin\(\).+Program\.cs:line 84"} + }; + + public static IEnumerable Ctor_Async_TestData() + { + yield return new object[] { "Foo", null, MethodExceptionStrings["Foo"]}; + yield return new object[] { "Bar", 3, MethodExceptionStrings["Bar"]}; + yield return new object[] { "Baz", null, MethodExceptionStrings["Baz"]}; + yield return new object[] { "Qux", 0, MethodExceptionStrings["Qux"]}; + yield return new object[] { "Moin", null, MethodExceptionStrings["Moin"]}; + } + + // async stuff + [ConditionalTheory(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsRuntimeAsyncSupported))] + [MemberData(nameof(Ctor_Async_TestData))] + public void ToString_Async(string methodName, object arg1, string expected) + { + string AssemblyName = "AsyncAssembly.dll"; + string SourceTestAssemblyPath = Path.Combine(Environment.CurrentDirectory, AssemblyName); + RemoteInvokeOptions options = new RemoteInvokeOptions(); + options.StartInfo.EnvironmentVariables["DOTNET_RuntimeAsync"] = "1"; + RemoteExecutor.Invoke(() => + { + var asm = Assembly.LoadFrom(SourceTestAssemblyPath); + try + { + asm.GetType("Program").GetMethod(methodName).Invoke(null, new[] { arg1 }); + } + catch (Exception e) + { + Assert.Matches(expected, e.ToString()); + } + }, options).Dispose(); + } + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] private static StackTrace NoParameters() => new StackTrace(); [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] diff --git a/src/tests/Common/CoreCLRTestLibrary/PlatformDetection.cs b/src/tests/Common/CoreCLRTestLibrary/PlatformDetection.cs index fcc4a267dd3caa..e9655bbbbe42f3 100644 --- a/src/tests/Common/CoreCLRTestLibrary/PlatformDetection.cs +++ b/src/tests/Common/CoreCLRTestLibrary/PlatformDetection.cs @@ -65,5 +65,7 @@ public static bool IsNonZeroLowerBoundArraySupported public static bool PlatformDoesNotSupportNativeTestAssets => OperatingSystem.IsIOS() || OperatingSystem.IsTvOS() || OperatingSystem.IsAndroid() || OperatingSystem.IsBrowser() || OperatingSystem.IsWasi(); public static bool IsAppleMobile => OperatingSystem.IsIOS() || OperatingSystem.IsTvOS() || OperatingSystem.IsMacCatalyst(); + public static bool IsRuntimeAsyncSupported => !Utilities.IsCoreClrInterpreter && !IsMonoRuntime && !IsMonoMINIFULLAOT && !IsMonoLLVMFULLAOT + && !IsMonoLLVMAOT && !IsMonoInterpreter && !IsMonoFULLAOT && Utilities.IsNotNativeAot; } } From 2031f08b4f9bce28d7a2ab91a00785761631259e Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Wed, 15 Oct 2025 18:27:12 +0200 Subject: [PATCH 37/51] Remove BBF_INTERNAL from rethrow BB to avoid broken mappings --- src/coreclr/jit/async.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/coreclr/jit/async.cpp b/src/coreclr/jit/async.cpp index 830e038ca814f1..e60f111a4ff86b 100644 --- a/src/coreclr/jit/async.cpp +++ b/src/coreclr/jit/async.cpp @@ -1979,6 +1979,14 @@ BasicBlock* AsyncTransformation::RethrowExceptionOnResumption(BasicBlock* m_comp->fgNewBBinRegion(BBJ_THROW, block, /* runRarely */ true, /* insertAtEnd */ true); JITDUMP(" Created " FMT_BB " to rethrow exception on resumption\n", rethrowExceptionBB->bbNum); + // If this ends up placed after 'block' then ensure it does not break + // debugging. We split 'block' at the call, so a BBF_INTERNAL block after + // it would result in broken debug info. + if ((rethrowExceptionBB->Prev() == block) && !block->HasFlag(BBF_INTERNAL)) + { + rethrowExceptionBB->RemoveFlags(BBF_INTERNAL); + } + BasicBlock* storeResultBB = m_comp->fgNewBBafter(BBJ_ALWAYS, resumeBB, true); JITDUMP(" Created " FMT_BB " to store result when resuming with no exception\n", storeResultBB->bbNum); From 8cf7c088fb7abd27e127e64adceb3e65fd365557 Mon Sep 17 00:00:00 2001 From: rcj1 Date: Thu, 16 Oct 2025 10:15:05 -0700 Subject: [PATCH 38/51] unit tests --- .../CompilerServices/AsyncHelpers.CoreCLR.cs | 4 +- .../TestUtilities/System/PlatformDetection.cs | 14 +++ .../tests/AsyncAssembly/AsyncAssembly.csproj | 7 +- .../tests/AsyncAssembly/Program.cs | 88 ++++++++++----- .../AsyncV1Assembly/AsyncV1Assembly.csproj | 4 + .../tests/AsyncV1Assembly/Program.cs | 13 +-- .../tests/StackTraceTests.cs | 104 +++++++++++------- ...System.Diagnostics.StackTrace.Tests.csproj | 1 + .../CoreCLRTestLibrary/PlatformDetection.cs | 2 - 9 files changed, 162 insertions(+), 75 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs index 9d9a4f647f7798..f10987b6f3332f 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs @@ -391,7 +391,7 @@ private unsafe struct NextContinuationData // To be used for async stack walking [ThreadStatic] private static unsafe NextContinuationData* t_nextContinuation; - + [StackTraceHidden] public static unsafe void DispatchContinuations(T task) where T : Task, ITaskCompletionAction where TOps : IRuntimeAsyncTaskOps { ExecutionAndSyncBlockStore contexts = default; @@ -758,12 +758,14 @@ private static void CaptureContinuationContext(SynchronizationContext syncCtx, r flags |= CorInfoContinuationFlags.CORINFO_CONTINUATION_CONTINUE_ON_THREAD_POOL; } + [StackTraceHidden] internal static T CompletedTaskResult(Task task) { TaskAwaiter.ValidateEnd(task); return task.ResultOnSuccess; } + [StackTraceHidden] internal static void CompletedTask(Task task) { TaskAwaiter.ValidateEnd(task); diff --git a/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs b/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs index 75ee5a8a27d37a..5330a68afe2fd1 100644 --- a/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs +++ b/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs @@ -439,6 +439,20 @@ public static bool IsSubstAvailable } } + public static bool IsCoreClrInterpreter + { + get + { + if (!string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("Interpreter"))) + return true; + if (int.TryParse(Environment.GetEnvironmentVariable("InterpMode"), out int mode) && (mode > 0)) + return true; + return false; + } + } + + public static bool IsRuntimeAsyncSupported => !IsCoreClrInterpreter && !IsMonoRuntime && !IsMonoAOT && !IsMonoInterpreter && IsNotNativeAot; + private static Version GetICUVersion() { int version = 0; diff --git a/src/libraries/System.Diagnostics.StackTrace/tests/AsyncAssembly/AsyncAssembly.csproj b/src/libraries/System.Diagnostics.StackTrace/tests/AsyncAssembly/AsyncAssembly.csproj index d9e233d783cbbe..ef9ecfd4ecbdf8 100644 --- a/src/libraries/System.Diagnostics.StackTrace/tests/AsyncAssembly/AsyncAssembly.csproj +++ b/src/libraries/System.Diagnostics.StackTrace/tests/AsyncAssembly/AsyncAssembly.csproj @@ -1,7 +1,8 @@  - netstandard2.0 + net10.0 + Library enable enable true @@ -13,4 +14,8 @@ + + + + diff --git a/src/libraries/System.Diagnostics.StackTrace/tests/AsyncAssembly/Program.cs b/src/libraries/System.Diagnostics.StackTrace/tests/AsyncAssembly/Program.cs index 961209d8f967ac..02ce82b3fa8be0 100644 --- a/src/libraries/System.Diagnostics.StackTrace/tests/AsyncAssembly/Program.cs +++ b/src/libraries/System.Diagnostics.StackTrace/tests/AsyncAssembly/Program.cs @@ -1,17 +1,17 @@ - -namespace AsyncTest; - +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. public class Program { // v2 -> v1 -> v2 -> v1 - static async Task Foo() + public static async Task Foo() { await Task.Yield(); try { +#line 12 "Program.cs" await V1Methods.Test0(Foo1); } - catch (NotImplementedException ex) + catch (NotImplementedException) { throw; } @@ -22,13 +22,13 @@ private static async Task Foo1(int i) await Task.Yield(); try { +#line 26 "Program.cs" await Foo2(i); return i * 2; } - catch (NotImplementedException ex) + catch (NotImplementedException) { - Console.WriteLine($"Caught exception in MyMethod2 with: {ex}"); - return -1; + throw; } } @@ -43,51 +43,87 @@ private static async Task Foo2(int i) { await Foo2(j - 1); } - if (i == 0) await V1Methods.Test2(i); + if (i == 0) + await V1Methods.Test2(i); } finally { Console.WriteLine($"In finally block of Foo2 with {i}"); - if (i == 2) throw new NotImplementedException("Not Found from Foo2"); +#line 53 "Program.cs" + throw new NotImplementedException("Not Found from Foo2"); } - return i * 2; } - static async Task Bar(int i) + public static async Task Bar(int i) { if (i == 0) +#line 61 "Program.cs" throw new Exception("Exception from Bar"); +#line 63 "Program.cs" await Bar(i - 1); } - static async Task Baz() + public static async Task Baz() { +#line 69 "Program.cs" throw new Exception("Exception from Baz"); } - static async Task Moin() + public static async Task Qux(int i) { - var task = Moin2(); - Console.WriteLine("hello from Moin"); - return await task; + await Task.Yield(); + try + { + return 9 / i; + } + catch (DivideByZeroException) + { +#line 82 "Program.cs" + throw new DivideByZeroException("Exception from Qux"); + } } - private static async Task Moin2() + // also v2 v1 chaining but this time we don't have finally + public static async Task Quux() { - await Task.Delay(100); - throw new Exception("Exception from Moin2"); + await Task.Yield(); + try + { +#line 93 "Program.cs" + await V1Methods.Test0(Quux1); + } + catch (NotImplementedException) + { + throw; + } } - - static async Task Qux(int i) + + private static async Task Quux1(int i) { - await Task.Yield(); try { - return 9 / i; + await Task.Yield(); +#line 107 "Program.cs" + throw new NotImplementedException("Not Found from Quux1"); } - catch (DivideByZeroException ex) + catch (NotImplementedException) { - throw new DivideByZeroException("Exception from Qux"); + throw; } } + + public static async Task Quuux() + { + var task = Quuux2(); + Console.WriteLine("hello from Quuux"); +#line 120 "Program.cs" + return await task; + } + + private static async Task Quuux2() + { + await Task.Yield(); +#line 127 "Program.cs" + throw new Exception("Exception from Quuux2"); + } } \ No newline at end of file diff --git a/src/libraries/System.Diagnostics.StackTrace/tests/AsyncV1Assembly/AsyncV1Assembly.csproj b/src/libraries/System.Diagnostics.StackTrace/tests/AsyncV1Assembly/AsyncV1Assembly.csproj index 0a280f4b683df1..4fc55cd39a70be 100644 --- a/src/libraries/System.Diagnostics.StackTrace/tests/AsyncV1Assembly/AsyncV1Assembly.csproj +++ b/src/libraries/System.Diagnostics.StackTrace/tests/AsyncV1Assembly/AsyncV1Assembly.csproj @@ -7,4 +7,8 @@ enable + + + + diff --git a/src/libraries/System.Diagnostics.StackTrace/tests/AsyncV1Assembly/Program.cs b/src/libraries/System.Diagnostics.StackTrace/tests/AsyncV1Assembly/Program.cs index a606d13e033b31..f32c6481c32c45 100644 --- a/src/libraries/System.Diagnostics.StackTrace/tests/AsyncV1Assembly/Program.cs +++ b/src/libraries/System.Diagnostics.StackTrace/tests/AsyncV1Assembly/Program.cs @@ -1,33 +1,30 @@ -namespace AsyncRecursiveTest; - +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. public static class V1Methods { public static async Task Test0(Func method) { +#line 8 "Program.cs" await Test1(method); await Task.Yield(); } public static async Task Test1(Func method) { - const int i = 3; - Console.WriteLine(i); try { - await method(i); +#line 17 "Program.cs" + await method(3); } catch (Exception ex) when (ex.Message.Contains("404")) { Console.WriteLine($"Caught exception in Test1 with: {ex}"); } - - Console.WriteLine(2 * i); } public static async Task Test2(int i) { Console.WriteLine($"In Test2 with {i}"); - Console.ReadKey(); throw new NullReferenceException("Exception from Test2"); } } \ No newline at end of file diff --git a/src/libraries/System.Diagnostics.StackTrace/tests/StackTraceTests.cs b/src/libraries/System.Diagnostics.StackTrace/tests/StackTraceTests.cs index 3bb248f978865a..4e8936efae9149 100644 --- a/src/libraries/System.Diagnostics.StackTrace/tests/StackTraceTests.cs +++ b/src/libraries/System.Diagnostics.StackTrace/tests/StackTraceTests.cs @@ -11,6 +11,7 @@ using System.Runtime.CompilerServices; using System.Text.RegularExpressions; using Microsoft.DotNet.RemoteExecutor; +using System.Threading.Tasks; using Xunit; namespace System.Diagnostics @@ -406,62 +407,91 @@ public void ToString_ShowILOffset() }, regPattern).Dispose(); } - public static Dictionary MethodExceptionStrings = new Dictionary - { - { "Foo", @"(?s)" + - @".+Not Found from Foo2" + - @".+AsyncTest\.Program\.Foo2\(Int32 i\).+Program\.cs:line 63" + - @".+AsyncTest\.Program\.Foo2\(Int32 i\).+Program\.cs:line 48" + - @".+AsyncTest\.Program\.Foo1\(Int32 i\).+Program\.cs:line 33" }, - { "Bar", @"(?s)" + - @".+Exception from Bar" + - @".+AsyncTest\.Program\.Bar\(Int32 i\).+Program\.cs:line 71" + - @".+AsyncTest\.Program\.Bar\(Int32 i\).+Program\.cs:line 72" + - @".+AsyncTest\.Program\.Bar\(Int32 i\).+Program\.cs:line 72" + - @".+AsyncTest\.Program\.Bar\(Int32 i\).+Program\.cs:line 72" }, - { "Baz", @"(?s)" + - @".+Exception from Baz" + - @".+AsyncTest\.Program\.Baz\(\).+Program\.cs:line 77"}, - { "Qux", @"(?s)" + - @".+Exception from Qux" + - @".+AsyncTest\.Program\.Qux\(Int32 i\).+Program\.cs:line 102"}, - { "Moin", @"(?s)" + - @".+Exception from Moin2" + - @".+AsyncTest\.Program\.Moin2\(\).+Program\.cs:line 90" + - @".+AsyncTest\.Program\.Moin\(\).+Program\.cs:line 84"} + public static Dictionary MethodExceptionStrings = new() + { + { "Foo", new[] { + @"Not Found from Foo2", + @"Program\.Foo2\(Int32 i\).*Program\.cs:line 53", + @"Program\.Foo1\(Int32 i\).*Program\.cs:line 26", + @"V1Methods\.Test1\(Func`2 method\).*Program\.cs:line 17", + @"V1Methods\.Test0\(Func`2 method\).*Program\.cs:line 8", + @"Program\.Foo\(\).*Program\.cs:line 12" + }}, + { "Bar", new[] { + @"Exception from Bar", + @"Program\.Bar\(Int32 i\).*Program\.cs:line 61", + @"Program\.Bar\(Int32 i\).*Program\.cs:line 63" + }}, + { "Baz", new[] { + @"Exception from Baz", + @"Program\.Baz\(\).*Program\.cs:line 69" + }}, + { "Qux", new[] { + @"Exception from Qux", + @"Program\.Qux\(Int32 i\).*Program\.cs:line 82" + }}, + {"Quux", new[] { + @"Not Found from Quux1", + @"Program\.Quux1\(Int32 i\).*Program\.cs:line 107", + @"V1Methods\.Test1\(Func`2 method\).*Program\.cs:line 17", + @"V1Methods\.Test0\(Func`2 method\).*Program\.cs:line 8", + @"Program\.Quux\(\).*Program\.cs:line 93" + }}, + { "Quuux", new[] { + @"Exception from Quuux2", + @"Program\.Quuux2\(\).*Program\.cs:line 127", + @"Program\.Quuux\(\).*Program\.cs:line 120" + }} }; public static IEnumerable Ctor_Async_TestData() { - yield return new object[] { "Foo", null, MethodExceptionStrings["Foo"]}; - yield return new object[] { "Bar", 3, MethodExceptionStrings["Bar"]}; - yield return new object[] { "Baz", null, MethodExceptionStrings["Baz"]}; - yield return new object[] { "Qux", 0, MethodExceptionStrings["Qux"]}; - yield return new object[] { "Moin", null, MethodExceptionStrings["Moin"]}; + yield return new object[] { "Foo", null, MethodExceptionStrings["Foo"] }; + yield return new object[] { "Bar", 3, MethodExceptionStrings["Bar"] }; + yield return new object[] { "Baz", null, MethodExceptionStrings["Baz"] }; + yield return new object[] { "Qux", 0, MethodExceptionStrings["Qux"] }; + yield return new object[] { "Quux", null, MethodExceptionStrings["Quux"] }; + yield return new object[] { "Quuux", null, MethodExceptionStrings["Quuux"] }; } - // async stuff - [ConditionalTheory(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsRuntimeAsyncSupported))] + public static class RuntimeAsyncAndRemoteExecutor + { + public static bool IsSupported => RemoteExecutor.IsSupported && PlatformDetection.IsRuntimeAsyncSupported; + } + + [ConditionalTheory(typeof(RuntimeAsyncAndRemoteExecutor), nameof(RuntimeAsyncAndRemoteExecutor.IsSupported))] [MemberData(nameof(Ctor_Async_TestData))] - public void ToString_Async(string methodName, object arg1, string expected) + public void ToString_Async(string methodName, object arg1, string[] expectedPatterns) { string AssemblyName = "AsyncAssembly.dll"; string SourceTestAssemblyPath = Path.Combine(Environment.CurrentDirectory, AssemblyName); RemoteInvokeOptions options = new RemoteInvokeOptions(); options.StartInfo.EnvironmentVariables["DOTNET_RuntimeAsync"] = "1"; - RemoteExecutor.Invoke(() => + string joinedPatterns = string.Join("|", expectedPatterns); + string arg1String = arg1?.ToString() ?? "null"; + Func method = async (asmPath, methodName, arg1String, joinedPatterns) => { - var asm = Assembly.LoadFrom(SourceTestAssemblyPath); + var asm = Assembly.LoadFrom(asmPath); try { - asm.GetType("Program").GetMethod(methodName).Invoke(null, new[] { arg1 }); + object arg1 = arg1String == "null" ? null : int.Parse(arg1String, CultureInfo.InvariantCulture); + var result = asm.GetType("Program").GetMethod(methodName).Invoke(null, arg1 == null ? null : new[] { arg1 }); + if (result is Task task) + { + await task; + } } catch (Exception e) { - Assert.Matches(expected, e.ToString()); + string exceptionString = e.ToString(); + string[] patterns = joinedPatterns.Split('|'); + foreach (string pattern in patterns) + { + Assert.Matches(pattern, exceptionString); + } } - }, options).Dispose(); + }; + RemoteExecutor.Invoke(method, SourceTestAssemblyPath, methodName, arg1String, joinedPatterns, options).Dispose(); } [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] diff --git a/src/libraries/System.Diagnostics.StackTrace/tests/System.Diagnostics.StackTrace.Tests.csproj b/src/libraries/System.Diagnostics.StackTrace/tests/System.Diagnostics.StackTrace.Tests.csproj index 87f7d5be40d428..db1df29697f0ef 100644 --- a/src/libraries/System.Diagnostics.StackTrace/tests/System.Diagnostics.StackTrace.Tests.csproj +++ b/src/libraries/System.Diagnostics.StackTrace/tests/System.Diagnostics.StackTrace.Tests.csproj @@ -25,5 +25,6 @@ + diff --git a/src/tests/Common/CoreCLRTestLibrary/PlatformDetection.cs b/src/tests/Common/CoreCLRTestLibrary/PlatformDetection.cs index e9655bbbbe42f3..fcc4a267dd3caa 100644 --- a/src/tests/Common/CoreCLRTestLibrary/PlatformDetection.cs +++ b/src/tests/Common/CoreCLRTestLibrary/PlatformDetection.cs @@ -65,7 +65,5 @@ public static bool IsNonZeroLowerBoundArraySupported public static bool PlatformDoesNotSupportNativeTestAssets => OperatingSystem.IsIOS() || OperatingSystem.IsTvOS() || OperatingSystem.IsAndroid() || OperatingSystem.IsBrowser() || OperatingSystem.IsWasi(); public static bool IsAppleMobile => OperatingSystem.IsIOS() || OperatingSystem.IsTvOS() || OperatingSystem.IsMacCatalyst(); - public static bool IsRuntimeAsyncSupported => !Utilities.IsCoreClrInterpreter && !IsMonoRuntime && !IsMonoMINIFULLAOT && !IsMonoLLVMFULLAOT - && !IsMonoLLVMAOT && !IsMonoInterpreter && !IsMonoFULLAOT && Utilities.IsNotNativeAot; } } From 4c35cc35595cf86b9d10033821c1d5aabf84c36c Mon Sep 17 00:00:00 2001 From: rcj1 Date: Fri, 7 Nov 2025 11:00:42 -0800 Subject: [PATCH 39/51] wip --- .../CompilerServices/AsyncHelpers.CoreCLR.cs | 10 ++--- src/coreclr/vm/debugdebugger.cpp | 44 +++++++++---------- src/coreclr/vm/debugdebugger.h | 3 +- 3 files changed, 26 insertions(+), 31 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs index b5eebef0910c90..9a5402da10b223 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs @@ -169,10 +169,10 @@ private struct RuntimeAsyncAwaitState private static RuntimeAsyncAwaitState t_runtimeAsyncAwaitState; [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "AsyncHelpers_AddContinuationToExInternal")] - private static unsafe partial void AddContinuationToExInternal(void* resume, uint state, ObjectHandleOnStack ex); + private static unsafe partial void AddContinuationToExInternal(void* diagnosticIP, ObjectHandleOnStack ex); - internal static unsafe void AddContinuationToExInternal(Continuation continuation, Exception e) - => AddContinuationToExInternal(continuation.Resume, continuation.State, ObjectHandleOnStack.Create(ref e)); + internal static unsafe void AddContinuationToExInternal(void* diagnosticIP, Exception e) + => AddContinuationToExInternal(diagnosticIP, ObjectHandleOnStack.Create(ref e)); private static unsafe Continuation AllocContinuation(Continuation prevContinuation, MethodTable* contMT) { @@ -514,8 +514,8 @@ public static unsafe void DispatchContinuations(T task) where T : Task, { while (true) { - if (continuation.Resume != null) - AddContinuationToExInternal(continuation, ex); + if (continuation?.ResumeInfo != null && continuation.ResumeInfo->DiagnosticIP != null) + AddContinuationToExInternal(continuation.ResumeInfo->DiagnosticIP, ex); if (continuation == null || (continuation.Flags & ContinuationFlags.HasException) != 0) return continuation; diff --git a/src/coreclr/vm/debugdebugger.cpp b/src/coreclr/vm/debugdebugger.cpp index 373d9203480b05..4fd3348656ba48 100644 --- a/src/coreclr/vm/debugdebugger.cpp +++ b/src/coreclr/vm/debugdebugger.cpp @@ -285,8 +285,7 @@ static void GetStackFrames(DebugStackTrace::GetStackFramesData *pData) } extern "C" void QCALLTYPE AsyncHelpers_AddContinuationToExInternal( - void* resume, - uint32_t state, + void* diagnosticIP, QCall::ObjectHandleOnStack exception) { QCALL_CONTRACT; @@ -309,16 +308,17 @@ extern "C" void QCALLTYPE AsyncHelpers_AddContinuationToExInternal( // and populate the exception object // get the state OBJECTHANDLE handle = AppDomain::GetCurrentDomain()->CreateHandle(gc.pException); - MethodDesc* methodDesc = NonVirtualEntry2MethodDesc((PCODE)resume); - ILStubResolver *pResolver = methodDesc->AsDynamicMethodDesc()->GetILStubResolver(); - MethodDesc* pTargetMethodDesc = pResolver->GetStubTargetMethodDesc(); - AsyncResumeILStubResolver* pAsyncResumeResolver = (AsyncResumeILStubResolver*)pResolver; - StackTraceInfo::AppendElement( - handle, - (UINT_PTR)pAsyncResumeResolver->GetFinalResumeMethodStartAddress(), - state, - pTargetMethodDesc, - NULL); + EECodeInfo codeInfo((PCODE)diagnosticIP); + if (codeInfo.IsValid()) + { + MethodDesc* methodDesc = codeInfo.GetMethodDesc(); + StackTraceInfo::AppendElement( + handle, + (UINT_PTR)diagnosticIP, + NULL, + methodDesc, + NULL); + } GCPROTECT_END(); END_QCALL; @@ -1000,18 +1000,14 @@ void DebugStackTrace::GetStackFramesFromException(OBJECTREF * e, UINT_PTR ip = cur.ip; if (cur.flags & STEF_CONTINUATION) { - if (ip != (PCODE)NULL) - { - DebugInfoRequest request; - request.InitFromStartingAddr(pMD, ip); - ICorDebugInfo::AsyncInfo asyncInfo = {}; - NewArrayHolder asyncSuspensionPoints(NULL); - NewArrayHolder asyncVars(NULL); - ULONG32 cAsyncVars = 0; - DebugInfoManager::GetAsyncDebugInfo(request, DebugInfoStoreNew2, nullptr, &asyncInfo, &asyncSuspensionPoints, &asyncVars, &cAsyncVars); - dwNativeOffset = asyncSuspensionPoints[cur.sp].NativeOffset; - ip += dwNativeOffset; - } + DebugInfoRequest request; + request.InitFromStartingAddr(pMD, ip); + ICorDebugInfo::AsyncInfo asyncInfo = {}; + NewArrayHolder asyncSuspensionPoints(NULL); + NewArrayHolder asyncVars(NULL); + ULONG32 cAsyncVars = 0; + DebugInfoManager::GetAsyncDebugInfo(request, DebugInfoStoreNew2, nullptr, &asyncInfo, &asyncSuspensionPoints, &asyncVars, &cAsyncVars); + dwNativeOffset = asyncSuspensionPoints[cur.sp].DiagnosticNativeOffset; } else diff --git a/src/coreclr/vm/debugdebugger.h b/src/coreclr/vm/debugdebugger.h index b4f89ec04079f1..8cd17439db17cc 100644 --- a/src/coreclr/vm/debugdebugger.h +++ b/src/coreclr/vm/debugdebugger.h @@ -136,8 +136,7 @@ extern "C" void QCALLTYPE StackTrace_GetStackFramesInternal( QCall::ObjectHandleOnStack exception); extern "C" void QCALLTYPE AsyncHelpers_AddContinuationToExInternal( - void* resume, - uint32_t state, + void* diagnosticIP, QCall::ObjectHandleOnStack exception); extern "C" MethodDesc* QCALLTYPE StackFrame_GetMethodDescFromNativeIP(LPVOID ip); From 6226c60da3c450a99be6e94d2158408ded83eafc Mon Sep 17 00:00:00 2001 From: rcj1 Date: Sat, 8 Nov 2025 07:27:43 -0800 Subject: [PATCH 40/51] wip --- .../CompilerServices/AsyncHelpers.CoreCLR.cs | 6 +++- src/coreclr/jit/codegenlinear.cpp | 8 ----- src/coreclr/jit/gentree.h | 20 ----------- src/coreclr/jit/gtstructs.h | 1 - .../tools/Common/JitInterface/CorInfoImpl.cs | 8 ----- src/coreclr/vm/exceptionhandling.cpp | 35 ------------------- src/coreclr/vm/exceptionhandling.h | 1 - 7 files changed, 5 insertions(+), 74 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs index 9a5402da10b223..d97d4abd7ebeb2 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs @@ -167,12 +167,14 @@ private struct RuntimeAsyncAwaitState [ThreadStatic] private static RuntimeAsyncAwaitState t_runtimeAsyncAwaitState; +#if !NATIVEAOT [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "AsyncHelpers_AddContinuationToExInternal")] private static unsafe partial void AddContinuationToExInternal(void* diagnosticIP, ObjectHandleOnStack ex); internal static unsafe void AddContinuationToExInternal(void* diagnosticIP, Exception e) => AddContinuationToExInternal(diagnosticIP, ObjectHandleOnStack.Create(ref e)); +#endif private static unsafe Continuation AllocContinuation(Continuation prevContinuation, MethodTable* contMT) { @@ -514,8 +516,10 @@ public static unsafe void DispatchContinuations(T task) where T : Task, { while (true) { - if (continuation?.ResumeInfo != null && continuation.ResumeInfo->DiagnosticIP != null) + if (continuation != null && continuation.ResumeInfo != null && continuation.ResumeInfo->DiagnosticIP != null) +#if !NATIVEAOT AddContinuationToExInternal(continuation.ResumeInfo->DiagnosticIP, ex); +#endif if (continuation == null || (continuation.Flags & ContinuationFlags.HasException) != 0) return continuation; diff --git a/src/coreclr/jit/codegenlinear.cpp b/src/coreclr/jit/codegenlinear.cpp index c6fdd951e7c588..b5b978cfd99fab 100644 --- a/src/coreclr/jit/codegenlinear.cpp +++ b/src/coreclr/jit/codegenlinear.cpp @@ -468,14 +468,6 @@ void CodeGen::genCodeForBBlist() #endif // DEBUG } - else if (node->OperIs(GT_RECORD_ASYNC_JOIN)) - { - int index = node->AsRecordAsyncJoin()->gtSuspensionPointIndex; - assert(compiler->compSuspensionPoints != nullptr); - assert(index >= 0 && (size_t)index < compiler->compSuspensionPoints->size()); - - (*compiler->compSuspensionPoints)[index].nativeLoc.CaptureLocation(GetEmitter()); - } genCodeForTreeNode(node); if (node->gtHasReg(compiler) && node->IsUnusedValue()) diff --git a/src/coreclr/jit/gentree.h b/src/coreclr/jit/gentree.h index 041b039c88c2a2..794b6f66b09203 100644 --- a/src/coreclr/jit/gentree.h +++ b/src/coreclr/jit/gentree.h @@ -8289,26 +8289,6 @@ struct GenTreeILOffset : public GenTree #endif }; -// No-op node that records the native offset into async debug info during -// codegen/emit. -struct GenTreeRecordAsyncJoin : public GenTree -{ - int gtSuspensionPointIndex; - - GenTreeRecordAsyncJoin(int suspensionPointIndex) - : GenTree(GT_RECORD_ASYNC_JOIN, TYP_VOID) - , gtSuspensionPointIndex(suspensionPointIndex) - { - } - -#if DEBUGGABLE_GENTREE - GenTreeRecordAsyncJoin() - : GenTree(GT_RECORD_ASYNC_JOIN, TYP_VOID) - { - } -#endif -}; - // GenTreeList: adapter class for forward iteration of the execution order GenTree linked list // using range-based `for`, normally used via Statement::TreeList(), e.g.: // for (GenTree* const tree : stmt->TreeList()) ... diff --git a/src/coreclr/jit/gtstructs.h b/src/coreclr/jit/gtstructs.h index b605bc85749b97..b9cf9839f31f8d 100644 --- a/src/coreclr/jit/gtstructs.h +++ b/src/coreclr/jit/gtstructs.h @@ -88,7 +88,6 @@ GTSTRUCT_2(MDArr , GT_MDARR_LENGTH, GT_MDARR_LOWER_BOUND) GTSTRUCT_1(ArrElem , GT_ARR_ELEM) GTSTRUCT_1(RetExpr , GT_RET_EXPR) GTSTRUCT_1(ILOffset , GT_IL_OFFSET) -GTSTRUCT_1(RecordAsyncJoin, GT_RECORD_ASYNC_JOIN) GTSTRUCT_2(CopyOrReload, GT_COPY, GT_RELOAD) GTSTRUCT_1(AddrMode , GT_LEA) GTSTRUCT_1(Qmark , GT_QMARK) diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs index 8be6d8292555d1..7c5abaf079e7ed 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs @@ -3243,14 +3243,6 @@ private void reportAsyncDebugInfo(AsyncInfo* asyncInfo, AsyncSuspensionPoint* su NativeMemory.Free(vars); } -#pragma warning disable CA1822 // Mark members as static - private void reportAsyncDebugInfo(AsyncInfo* asyncInfo, AsyncSuspensionPoint* suspensionPoints, AsyncContinuationVarInfo* vars, uint numVars) -#pragma warning restore CA1822 // Mark members as static - { - Marshal.FreeHGlobal((IntPtr)suspensionPoints); - Marshal.FreeHGlobal((IntPtr)vars); - } - #pragma warning disable CA1822 // Mark members as static private void reportMetadata(byte* key, void* value, nuint length) #pragma warning restore CA1822 // Mark members as static diff --git a/src/coreclr/vm/exceptionhandling.cpp b/src/coreclr/vm/exceptionhandling.cpp index d38cbab0827729..f72fb5edebaaec 100644 --- a/src/coreclr/vm/exceptionhandling.cpp +++ b/src/coreclr/vm/exceptionhandling.cpp @@ -1563,41 +1563,6 @@ void NormalizeThrownObject(OBJECTREF *ppThrowable) } } -void NormalizeThrownObject(OBJECTREF *ppThrowable) -{ - CONTRACTL - { - THROWS; - GC_TRIGGERS; - MODE_COOPERATIVE; - } - CONTRACTL_END; - - Thread *pThread = GetThread(); - if (!IsException((*ppThrowable)->GetMethodTable())) - { - GCPROTECT_BEGIN(*ppThrowable); - - WrapNonCompliantException(ppThrowable); - - GCPROTECT_END(); - } - else - { // We know that the object derives from System.Exception - - // If the flag indicating ForeignExceptionRaise has been set, - // then do not clear the "_stackTrace" field of the exception object. - if (pThread->GetExceptionState()->IsRaisingForeignException()) - { - ((EXCEPTIONREF)(*ppThrowable))->SetStackTraceString(NULL); - } - else - { - ((EXCEPTIONREF)(*ppThrowable))->ClearStackTracePreservingRemoteStackTrace(); - } - } -} - VOID DECLSPEC_NORETURN DispatchManagedException(OBJECTREF throwable, CONTEXT* pExceptionContext, EXCEPTION_RECORD* pExceptionRecord, ExKind exKind /* = ExKind::None */) { STATIC_CONTRACT_THROWS; diff --git a/src/coreclr/vm/exceptionhandling.h b/src/coreclr/vm/exceptionhandling.h index 4e9b7d414f0d9d..b2fffb0ed2cade 100644 --- a/src/coreclr/vm/exceptionhandling.h +++ b/src/coreclr/vm/exceptionhandling.h @@ -32,7 +32,6 @@ CallDescrWorkerUnwindFrameChainHandler(IN PEXCEPTION_RECORD pExceptionRe void NormalizeThrownObject(OBJECTREF *ppThrowable); VOID DECLSPEC_NORETURN DispatchManagedException(OBJECTREF throwable, CONTEXT *pExceptionContext, EXCEPTION_RECORD *pExceptionRecord = NULL, ExKind exKind = ExKind::None); -VOID DECLSPEC_NORETURN DispatchManagedException(OBJECTREF throwable, CONTEXT *pExceptionContext, EXCEPTION_RECORD *pExceptionRecord = NULL); VOID DECLSPEC_NORETURN DispatchManagedException(OBJECTREF throwable); VOID DECLSPEC_NORETURN DispatchManagedException(RuntimeExceptionKind reKind); VOID DECLSPEC_NORETURN DispatchRethrownManagedException(); From e493a60bb5f000008cdc7a62c928b55027ff59d9 Mon Sep 17 00:00:00 2001 From: rcj1 Date: Sat, 8 Nov 2025 17:53:11 -0800 Subject: [PATCH 41/51] merge and fix --- .../CompilerServices/AsyncHelpers.CoreCLR.cs | 1 + src/coreclr/jit/compiler.h | 6 -- src/coreclr/vm/debugdebugger.cpp | 13 ++-- src/coreclr/vm/ilstubcache.cpp | 24 +++---- src/coreclr/vm/ilstubcache.h | 6 +- src/coreclr/vm/ilstubresolver.cpp | 22 ------ src/coreclr/vm/ilstubresolver.h | 18 ----- src/coreclr/vm/jitinterface.cpp | 67 +++++++++---------- src/coreclr/vm/jitinterface.h | 4 +- 9 files changed, 51 insertions(+), 110 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs index d97d4abd7ebeb2..07e56b9ae34f9f 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs @@ -430,6 +430,7 @@ private unsafe ref struct DispatcherInfo [ThreadStatic] private static unsafe DispatcherInfo* t_dispatcherInfo; + [StackTraceHidden] public static unsafe void DispatchContinuations(T task) where T : Task, ITaskCompletionAction where TOps : IRuntimeAsyncTaskOps { ExecutionAndSyncBlockStore contexts = default; diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 5a66bcbd25a1c1..5f51bd68e138db 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -2397,12 +2397,6 @@ struct RichIPMapping DebugInfo debugInfo; }; -struct AsyncSuspensionPoint -{ - emitLocation nativeLoc; - unsigned numContinuationVars = 0; -}; - // Current kind of node threading stored in GenTree::gtPrev and GenTree::gtNext. // See fgNodeThreading for more information. enum class NodeThreading diff --git a/src/coreclr/vm/debugdebugger.cpp b/src/coreclr/vm/debugdebugger.cpp index 4fd3348656ba48..fb66e8d24ea270 100644 --- a/src/coreclr/vm/debugdebugger.cpp +++ b/src/coreclr/vm/debugdebugger.cpp @@ -1001,13 +1001,12 @@ void DebugStackTrace::GetStackFramesFromException(OBJECTREF * e, if (cur.flags & STEF_CONTINUATION) { DebugInfoRequest request; - request.InitFromStartingAddr(pMD, ip); - ICorDebugInfo::AsyncInfo asyncInfo = {}; - NewArrayHolder asyncSuspensionPoints(NULL); - NewArrayHolder asyncVars(NULL); - ULONG32 cAsyncVars = 0; - DebugInfoManager::GetAsyncDebugInfo(request, DebugInfoStoreNew2, nullptr, &asyncInfo, &asyncSuspensionPoints, &asyncVars, &cAsyncVars); - dwNativeOffset = asyncSuspensionPoints[cur.sp].DiagnosticNativeOffset; + EECodeInfo codeInfo((PCODE)ip); + if (codeInfo.IsValid()) + { + PCODE startAddress = codeInfo.GetStartAddress(); + dwNativeOffset = (DWORD)(ip - startAddress); + } } else diff --git a/src/coreclr/vm/ilstubcache.cpp b/src/coreclr/vm/ilstubcache.cpp index a42a24b0bafb7a..dbd771eeaadb69 100644 --- a/src/coreclr/vm/ilstubcache.cpp +++ b/src/coreclr/vm/ilstubcache.cpp @@ -70,7 +70,7 @@ void CreateModuleIndependentSignature(LoaderHeap* pCreationHeap, // static MethodDesc* ILStubCache::CreateAndLinkNewILStubMethodDesc(LoaderAllocator* pAllocator, MethodTable* pMT, DWORD dwStubFlags, Module* pSigModule, PCCOR_SIGNATURE pSig, DWORD cbSig, SigTypeContext *pTypeContext, - ILStubLinker* pStubLinker, BOOL isAsync /* = FALSE */, ILStubResolver* pResolver /* = NULL */) + ILStubLinker* pStubLinker, BOOL isAsync /* = FALSE */) { CONTRACT (MethodDesc*) { @@ -88,14 +88,13 @@ MethodDesc* ILStubCache::CreateAndLinkNewILStubMethodDesc(LoaderAllocator* pAllo pSig, cbSig, isAsync, pTypeContext, - &amTracker, - pResolver); + &amTracker); amTracker.SuppressRelease(); pStubLinker->SetStubMethodDesc(pStubMD); - pResolver = pStubMD->AsDynamicMethodDesc()->GetILStubResolver(); + ILStubResolver *pResolver = pStubMD->AsDynamicMethodDesc()->GetILStubResolver(); pResolver->SetStubMethodDesc(pStubMD); @@ -147,7 +146,7 @@ namespace // static MethodDesc* ILStubCache::CreateNewMethodDesc(LoaderHeap* pCreationHeap, MethodTable* pMT, DWORD dwStubFlags, Module* pSigModule, PCCOR_SIGNATURE pSig, DWORD cbSig, BOOL isAsync, SigTypeContext *pTypeContext, - AllocMemTracker* pamTracker, ILStubResolver* pResolver) + AllocMemTracker* pamTracker) { CONTRACT (MethodDesc*) { @@ -215,19 +214,12 @@ MethodDesc* ILStubCache::CreateNewMethodDesc(LoaderHeap* pCreationHeap, MethodTa pMD->SetStatic(); } - if (pResolver != NULL) - { - pMD->m_pResolver = pResolver; - } - else - { - pMD->m_pResolver = (ILStubResolver*)pamTracker->Track(pCreationHeap->AllocMem(S_SIZE_T(sizeof(ILStubResolver)))); + pMD->m_pResolver = (ILStubResolver*)pamTracker->Track(pCreationHeap->AllocMem(S_SIZE_T(sizeof(ILStubResolver)))); #ifdef _DEBUG - // Poison the ILStubResolver storage - memset((void*)pMD->m_pResolver, 0xCC, sizeof(ILStubResolver)); + // Poison the ILStubResolver storage + memset((void*)pMD->m_pResolver, 0xCC, sizeof(ILStubResolver)); #endif // _DEBUG - pMD->m_pResolver = new (pMD->m_pResolver) ILStubResolver(); - } + pMD->m_pResolver = new (pMD->m_pResolver) ILStubResolver(); if (SF_IsArrayOpStub(dwStubFlags)) { diff --git a/src/coreclr/vm/ilstubcache.h b/src/coreclr/vm/ilstubcache.h index 873769d61b6aa7..52be99d9fb8ef1 100644 --- a/src/coreclr/vm/ilstubcache.h +++ b/src/coreclr/vm/ilstubcache.h @@ -74,8 +74,7 @@ class ILStubCache final DWORD cbSig, SigTypeContext *pTypeContext, ILStubLinker* pStubLinker, - BOOL isAsync = FALSE, - class ILStubResolver* pResolver = NULL); + BOOL isAsync = FALSE); MethodTable * GetStubMethodTable() { @@ -101,8 +100,7 @@ class ILStubCache final DWORD cbSig, BOOL isAsync, SigTypeContext *pTypeContext, - AllocMemTracker* pamTracker, - class ILStubResolver* pResolver = NULL); + AllocMemTracker* pamTracker); private: // Inner classes struct ILStubCacheEntry diff --git a/src/coreclr/vm/ilstubresolver.cpp b/src/coreclr/vm/ilstubresolver.cpp index 5c2fa0fc966421..0ab3b1476bccbb 100644 --- a/src/coreclr/vm/ilstubresolver.cpp +++ b/src/coreclr/vm/ilstubresolver.cpp @@ -559,25 +559,3 @@ void ILStubResolver::StubGenFailed(ILStubResolver* pResolver) pResolver->ClearCompileTimeState(ILNotYetGenerated); } - -PCODE AsyncResumeILStubResolver::GetFinalResumeMethodStartAddress() -{ - return m_finalResumeIP; -} - -#ifndef DACCESS_COMPILE -PCODE* AsyncResumeILStubResolver::GetAddrOfResumeMethodStartAddress() -{ - return &m_resumeIP; -} - -void AsyncResumeILStubResolver::SetResumeMethodStartAddress(PCODE ip) -{ - m_resumeIP = ip; -} - -void AsyncResumeILStubResolver::SetFinalResumeMethodStartAddress(PCODE ip) -{ - m_finalResumeIP = ip; -} -#endif diff --git a/src/coreclr/vm/ilstubresolver.h b/src/coreclr/vm/ilstubresolver.h index 6d888cfba79665..4e970423a69da0 100644 --- a/src/coreclr/vm/ilstubresolver.h +++ b/src/coreclr/vm/ilstubresolver.h @@ -108,24 +108,6 @@ class ILStubResolver : public DynamicResolver PTR_LoaderHeap m_loaderHeap; }; -class AsyncResumeILStubResolver : public ILStubResolver -{ -public: - PCODE GetFinalResumeMethodStartAddress(); - -#ifndef DACCESS_COMPILE - PCODE* GetAddrOfResumeMethodStartAddress(); - void SetResumeMethodStartAddress(PCODE ip); - void SetFinalResumeMethodStartAddress(PCODE ip); -#endif - -protected: - // IP to resume in. Can be tier0 code if we suspended in an OSR method. - PCODE m_resumeIP = NULL; - // Final IP resumed in. Will be OSR method IP if suspension happened there. - PCODE m_finalResumeIP = NULL; -}; - typedef Holder, ILStubResolver::StubGenFailed, 0> ILStubGenHolder; #endif // __ILSTUBRESOLVER_H__ diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index d5ca8d69e0950c..b0b894e29c24e3 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -11113,34 +11113,10 @@ void CEEJitInfo::PublishFinalCodeAddress(PCODE addr) { LIMITED_METHOD_CONTRACT; - if (m_resumptionStubResolver == NULL) + if (m_finalCodeAddressSlot != NULL) { - return; + *m_finalCodeAddressSlot = addr; } - - m_resumptionStubResolver->SetFinalResumeMethodStartAddress(addr); - m_resumptionStubResolver->SetResumeMethodStartAddress(addr); - -#ifdef FEATURE_TIERED_COMPILATION - // Resumption stubs are uniquely coupled to the code version (since the - // continuation is), so we need to make sure we always keep calling the - // same version here. - PrepareCodeConfig* config = GetThread()->GetCurrentPrepareCodeConfig(); - NativeCodeVersion ncv = config->GetCodeVersion(); - if (ncv.GetOptimizationTier() == NativeCodeVersion::OptimizationTier1OSR) - { -#ifdef FEATURE_ON_STACK_REPLACEMENT - // The OSR version needs to resume in the tier0 version. The tier0 - // version will handle setting up the frame that the OSR version - // expects and then delegating back into the OSR version (knowing to do - // so through information stored in the continuation). - _ASSERTE(m_pPatchpointInfoFromRuntime != NULL); - m_resumptionStubResolver->SetResumeMethodStartAddress((DWORD_PTR)m_pPatchpointInfoFromRuntime->GetTier0EntryPoint()); -#else // !FEATURE_ON_STACK_REPLACEMENT - _ASSERTE(!"Unexpected optimization tier with OSR disabled"); -#endif // FEATURE_ON_STACK_REPLACEMENT - } -#endif } template @@ -14745,10 +14721,35 @@ CORINFO_METHOD_HANDLE CEEJitInfo::getAsyncResumptionStub(void** entryPoint) numArgs++; #endif - void* resolverMem = amTracker.Track(loaderAlloc->GetHighFrequencyHeap()->AllocMem(S_SIZE_T(sizeof(AsyncResumeILStubResolver)))); - m_resumptionStubResolver = new (resolverMem) AsyncResumeILStubResolver(); - pCode->EmitLDC((DWORD_PTR)m_resumptionStubResolver->GetAddrOfResumeMethodStartAddress()); - pCode->EmitLDIND_I(); +#ifdef FEATURE_TIERED_COMPILATION + // Resumption stubs are uniquely coupled to the code version (since the + // continuation is), so we need to make sure we always keep calling the + // same version here. + PrepareCodeConfig* config = GetThread()->GetCurrentPrepareCodeConfig(); + NativeCodeVersion ncv = config->GetCodeVersion(); + if (ncv.GetOptimizationTier() == NativeCodeVersion::OptimizationTier1OSR) + { +#ifdef FEATURE_ON_STACK_REPLACEMENT + // The OSR version needs to resume in the tier0 version. The tier0 + // version will handle setting up the frame that the OSR version + // expects and then delegating back into the OSR version (knowing to do + // so through information stored in the continuation). + _ASSERTE(m_pPatchpointInfoFromRuntime != NULL); + pCode->EmitLDC((DWORD_PTR)m_pPatchpointInfoFromRuntime->GetTier0EntryPoint()); +#else // !FEATURE_ON_STACK_REPLACEMENT + _ASSERTE(!"Unexpected optimization tier with OSR disabled"); +#endif // FEATURE_ON_STACK_REPLACEMENT + } + else +#endif // FEATURE_TIERED_COMPILATION + { + { + m_finalCodeAddressSlot = (PCODE*)amTracker.Track(m_pMethodBeingCompiled->GetLoaderAllocator()->GetHighFrequencyHeap()->AllocMem(S_SIZE_T(sizeof(PCODE)))); + } + + pCode->EmitLDC((DWORD_PTR)m_finalCodeAddressSlot); + pCode->EmitLDIND_I(); + } pCode->EmitCALLI(pCode->GetSigToken(calliSig.GetRawSig(), calliSig.GetRawSigLen()), numArgs, msig.IsReturnTypeVoid() ? 0 : 1); @@ -14790,9 +14791,7 @@ CORINFO_METHOD_HANDLE CEEJitInfo::getAsyncResumptionStub(void** entryPoint) md->GetModule(), stubSig.GetRawSig(), stubSig.GetRawSigLen(), &emptyCtx, - &sl, - FALSE, /* isAsync */ - m_resumptionStubResolver); + &sl); amTracker.SuppressRelease(); @@ -14801,8 +14800,6 @@ CORINFO_METHOD_HANDLE CEEJitInfo::getAsyncResumptionStub(void** entryPoint) const char* optimizationTierName = "UnknownTier"; #ifdef FEATURE_TIERED_COMPILATION - PrepareCodeConfig* config = GetThread()->GetCurrentPrepareCodeConfig(); - NativeCodeVersion ncv = config->GetCodeVersion(); switch (ncv.GetOptimizationTier()) { case NativeCodeVersion::OptimizationTier0: optimizationTierName = "Tier0"; break; diff --git a/src/coreclr/vm/jitinterface.h b/src/coreclr/vm/jitinterface.h index bef34cffdf0854..33f9be74558e90 100644 --- a/src/coreclr/vm/jitinterface.h +++ b/src/coreclr/vm/jitinterface.h @@ -849,7 +849,7 @@ class CEEJitInfo final : public CEECodeGenInfo m_pPatchpointInfoFromRuntime(NULL), m_ilOffset(0) #endif - , m_resumptionStubResolver(NULL) + , m_finalCodeAddressSlot(NULL) { CONTRACTL { @@ -946,7 +946,7 @@ protected : PatchpointInfo * m_pPatchpointInfoFromRuntime; unsigned m_ilOffset; #endif - class AsyncResumeILStubResolver* m_resumptionStubResolver; + PCODE* m_finalCodeAddressSlot; }; From f78758506a552a6d41987d315147c2fb3c8cee2c Mon Sep 17 00:00:00 2001 From: rcj1 Date: Thu, 11 Dec 2025 15:23:12 -0800 Subject: [PATCH 42/51] fix --- Directory.Build.targets | 1 - eng/pruning.targets | 19 ------------------- eng/testing/linker/project.csproj.template | 1 - 3 files changed, 21 deletions(-) delete mode 100644 eng/pruning.targets diff --git a/Directory.Build.targets b/Directory.Build.targets index d5a2643dfcbceb..f9bbf7348e125b 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -210,5 +210,4 @@ - diff --git a/eng/pruning.targets b/eng/pruning.targets deleted file mode 100644 index 845e99f7e1bfee..00000000000000 --- a/eng/pruning.targets +++ /dev/null @@ -1,19 +0,0 @@ - - - - true - 10.0 - - - - - - - diff --git a/eng/testing/linker/project.csproj.template b/eng/testing/linker/project.csproj.template index 2a4bbd96f9c179..bee554760c7e94 100644 --- a/eng/testing/linker/project.csproj.template +++ b/eng/testing/linker/project.csproj.template @@ -85,7 +85,6 @@ - From 5f32166f74a0a48f3d1e6cd6734ae9f9ce74e67a Mon Sep 17 00:00:00 2001 From: rcj1 Date: Thu, 11 Dec 2025 16:28:19 -0800 Subject: [PATCH 43/51] fix --- .../CompilerServices/AsyncHelpers.CoreCLR.cs | 22 ++++++++++++++++++- .../TestUtilities/System/PlatformDetection.cs | 12 ---------- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs index 3a6dfe3e9d69cd..00eae7f26b3b11 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs @@ -420,6 +420,7 @@ internal void HandleSuspended() } } + [StackTraceHidden] private unsafe void DispatchContinuations() { ExecutionAndSyncBlockStore contexts = default; @@ -504,13 +505,32 @@ private unsafe void DispatchContinuations() private ref byte GetResultStorage() => ref Unsafe.As(ref m_result); - private unsafe static Continuation? UnwindToPossibleHandler(Continuation? continuation, Exception ex) + private static unsafe Continuation? UnwindToPossibleHandler(Continuation? continuation, Exception ex) { while (true) { if (continuation != null && continuation.ResumeInfo != null && continuation.ResumeInfo->DiagnosticIP != null) #if !NATIVEAOT AddContinuationToExInternal(continuation.ResumeInfo->DiagnosticIP, ex); +#else + // { + // IntPtr ip = (IntPtr)continuation.ResumeInfo->DiagnosticIP; + // int flags = 0; + // IntPtr pAppendStackFrame = (IntPtr)InternalCalls.RhpGetClasslibFunctionFromCodeAddress(ip, + // ClassLibFunctionId.AppendExceptionStackFrame); + + // if (pAppendStackFrame != IntPtr.Zero) + // { + // try + // { + // ((delegate*)pAppendStackFrame)(ex, ip, flags); + // } + // catch + // { + // // disallow all exceptions leaking out of callbacks + // } + // } + // } #endif if (continuation == null || (continuation.Flags & ContinuationFlags.HasException) != 0) return continuation; diff --git a/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs b/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs index 5ecf182f212776..29d4d6c7980195 100644 --- a/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs +++ b/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs @@ -443,18 +443,6 @@ public static bool IsSubstAvailable } } - public static bool IsCoreClrInterpreter - { - get - { - if (!string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("Interpreter"))) - return true; - if (int.TryParse(Environment.GetEnvironmentVariable("InterpMode"), out int mode) && (mode > 0)) - return true; - return false; - } - } - public static bool IsRuntimeAsyncSupported => !IsCoreClrInterpreter && !IsMonoRuntime && !IsMonoAOT && !IsMonoInterpreter && IsNotNativeAot; private static Version GetICUVersion() From a33a73890b0a981e3e6982fe39e3be0ccf0d0606 Mon Sep 17 00:00:00 2001 From: rcj1 Date: Mon, 15 Dec 2025 14:09:10 -0800 Subject: [PATCH 44/51] ee --- .../pkg/sfx/Microsoft.NETCore.App/Directory.Build.props | 1 + .../Microsoft.NETCore.App/Microsoft.NETCore.App.Runtime.props | 1 + 2 files changed, 2 insertions(+) diff --git a/src/installer/pkg/sfx/Microsoft.NETCore.App/Directory.Build.props b/src/installer/pkg/sfx/Microsoft.NETCore.App/Directory.Build.props index 4b649df6a6b8b7..fdb7523803add8 100644 --- a/src/installer/pkg/sfx/Microsoft.NETCore.App/Directory.Build.props +++ b/src/installer/pkg/sfx/Microsoft.NETCore.App/Directory.Build.props @@ -105,6 +105,7 @@ + diff --git a/src/installer/pkg/sfx/Microsoft.NETCore.App/Microsoft.NETCore.App.Runtime.props b/src/installer/pkg/sfx/Microsoft.NETCore.App/Microsoft.NETCore.App.Runtime.props index db017617784bac..7cc53f3a153a81 100644 --- a/src/installer/pkg/sfx/Microsoft.NETCore.App/Microsoft.NETCore.App.Runtime.props +++ b/src/installer/pkg/sfx/Microsoft.NETCore.App/Microsoft.NETCore.App.Runtime.props @@ -8,6 +8,7 @@ true true + true The .NET Shared Framework From bb5ce5b408e0477152aa2e0a5f4362839ea35b6e Mon Sep 17 00:00:00 2001 From: rcj1 Date: Tue, 16 Dec 2025 16:33:14 -0800 Subject: [PATCH 45/51] aot test --- .../src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs index 00eae7f26b3b11..c72c741e83e94e 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs @@ -531,6 +531,7 @@ private unsafe void DispatchContinuations() // } // } // } + Debug.Fail("Exception stack frame appending not implemented in NativeAOT"); #endif if (continuation == null || (continuation.Flags & ContinuationFlags.HasException) != 0) return continuation; From 4699d90d3a44471f7bdcd0efddf854bbf40230c4 Mon Sep 17 00:00:00 2001 From: rcj1 Date: Tue, 16 Dec 2025 17:10:19 -0800 Subject: [PATCH 46/51] Revert "fix" This reverts commit f78758506a552a6d41987d315147c2fb3c8cee2c. --- Directory.Build.targets | 1 + eng/pruning.targets | 19 +++++++++++++++++++ eng/testing/linker/project.csproj.template | 1 + 3 files changed, 21 insertions(+) create mode 100644 eng/pruning.targets diff --git a/Directory.Build.targets b/Directory.Build.targets index c13672d5639af6..c89bc354683465 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -217,4 +217,5 @@ + diff --git a/eng/pruning.targets b/eng/pruning.targets new file mode 100644 index 00000000000000..845e99f7e1bfee --- /dev/null +++ b/eng/pruning.targets @@ -0,0 +1,19 @@ + + + + true + 10.0 + + + + + + + diff --git a/eng/testing/linker/project.csproj.template b/eng/testing/linker/project.csproj.template index bee554760c7e94..2a4bbd96f9c179 100644 --- a/eng/testing/linker/project.csproj.template +++ b/eng/testing/linker/project.csproj.template @@ -85,6 +85,7 @@ + From 82a9d452a0eff2614728250af39bbcbd06798c62 Mon Sep 17 00:00:00 2001 From: rcj1 Date: Thu, 18 Dec 2025 07:19:50 -0800 Subject: [PATCH 47/51] nativeaot exceptions --- .../CompilerServices/AsyncHelpers.CoreCLR.cs | 38 +++++++++---------- .../Directory.Build.props | 1 + 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs index c72c741e83e94e..e13fd902a1e879 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs @@ -513,25 +513,25 @@ private unsafe void DispatchContinuations() #if !NATIVEAOT AddContinuationToExInternal(continuation.ResumeInfo->DiagnosticIP, ex); #else - // { - // IntPtr ip = (IntPtr)continuation.ResumeInfo->DiagnosticIP; - // int flags = 0; - // IntPtr pAppendStackFrame = (IntPtr)InternalCalls.RhpGetClasslibFunctionFromCodeAddress(ip, - // ClassLibFunctionId.AppendExceptionStackFrame); - - // if (pAppendStackFrame != IntPtr.Zero) - // { - // try - // { - // ((delegate*)pAppendStackFrame)(ex, ip, flags); - // } - // catch - // { - // // disallow all exceptions leaking out of callbacks - // } - // } - // } - Debug.Fail("Exception stack frame appending not implemented in NativeAOT"); + { + IntPtr ip = (IntPtr)continuation.ResumeInfo->DiagnosticIP; + int flags = 0; + IntPtr pAppendStackFrame = (IntPtr)InternalCalls.RhpGetClasslibFunctionFromCodeAddress(ip, + ClassLibFunctionId.AppendExceptionStackFrame); + + if (pAppendStackFrame != IntPtr.Zero) + { + try + { + ((delegate*)pAppendStackFrame)(ex, ip, flags); + } + catch + { + // disallow all exceptions leaking out of callbacks + } + } + } + #endif if (continuation == null || (continuation.Flags & ContinuationFlags.HasException) != 0) return continuation; diff --git a/src/installer/pkg/sfx/Microsoft.NETCore.App/Directory.Build.props b/src/installer/pkg/sfx/Microsoft.NETCore.App/Directory.Build.props index fdb7523803add8..f02de50e045b30 100644 --- a/src/installer/pkg/sfx/Microsoft.NETCore.App/Directory.Build.props +++ b/src/installer/pkg/sfx/Microsoft.NETCore.App/Directory.Build.props @@ -158,6 +158,7 @@ + From df8d178e0cf10686f753b99779b7f834a8e3ef18 Mon Sep 17 00:00:00 2001 From: rcj1 Date: Thu, 18 Dec 2025 16:10:06 -0800 Subject: [PATCH 48/51] ilc emit async stacktrace data throw exact naot --- docs/design/coreclr/botr/readytorun-format.md | 1 + .../CompilerServices/AsyncHelpers.CoreCLR.cs | 7 +- src/coreclr/inc/readytorun.h | 1 + .../nativeaot/Runtime/StackFrameIterator.cpp | 4 +- .../Runtime/amd64/ExceptionHandling.S | 82 ++++++++++++++ .../Runtime/amd64/ExceptionHandling.asm | 100 ++++++++++++++++ .../nativeaot/Runtime/arm/ExceptionHandling.S | 107 ++++++++++++++++++ .../Runtime/arm64/ExceptionHandling.S | 87 ++++++++++++++ .../Runtime/arm64/ExceptionHandling.asm | 91 +++++++++++++++ .../Runtime/i386/ExceptionHandling.asm | 79 +++++++++++++ .../Runtime/loongarch64/ExceptionHandling.S | 86 ++++++++++++++ .../Runtime/riscv64/ExceptionHandling.S | 79 +++++++++++++ .../src/System/Exception.NativeAot.cs | 6 +- .../Internal/Runtime/ReadyToRunConstants.cs | 1 + .../IL/Stubs/AsyncResumptionStub.cs | 2 + .../ILCompiler.Compiler/Compiler/JitHelper.cs | 3 + .../Compiler/MetadataManager.cs | 16 ++- .../Compiler/StackTraceEmissionPolicy.cs | 6 + .../JitInterface/CorInfoImpl.ReadyToRun.cs | 3 + .../ReadyToRunSignature.cs | 4 + .../JitInterface/CorInfoImpl.RyuJit.cs | 4 +- 21 files changed, 762 insertions(+), 7 deletions(-) diff --git a/docs/design/coreclr/botr/readytorun-format.md b/docs/design/coreclr/botr/readytorun-format.md index 08d58deb0ab7bc..aaede3419780fd 100644 --- a/docs/design/coreclr/botr/readytorun-format.md +++ b/docs/design/coreclr/botr/readytorun-format.md @@ -858,6 +858,7 @@ enum ReadyToRunHelper READYTORUN_HELPER_FailFast = 0x24, READYTORUN_HELPER_ThrowNullRef = 0x25, READYTORUN_HELPER_ThrowDivZero = 0x26, + READYTORUN_HELPER_ThrowExact = 0x27, // Write barriers READYTORUN_HELPER_WriteBarrier = 0x30, diff --git a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs index cd9b5571a1f4f8..2eca4dedede00c 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs @@ -87,6 +87,11 @@ internal enum ContinuationFlags ContinueOnCapturedTaskScheduler = 64, } + internal enum RhEHFrameType + { + RH_EH_RUNTIME_ASYNC_FRAME = 4, + } + // Keep in sync with CORINFO_AsyncResumeInfo in corinfo.h internal unsafe struct ResumeInfo { @@ -537,7 +542,7 @@ private unsafe void DispatchContinuations() #else { IntPtr ip = (IntPtr)continuation.ResumeInfo->DiagnosticIP; - int flags = 0; + int flags = (int)RhEHFrameType.RH_EH_RUNTIME_ASYNC_FRAME; IntPtr pAppendStackFrame = (IntPtr)InternalCalls.RhpGetClasslibFunctionFromCodeAddress(ip, ClassLibFunctionId.AppendExceptionStackFrame); diff --git a/src/coreclr/inc/readytorun.h b/src/coreclr/inc/readytorun.h index db88393b101576..230ef919346560 100644 --- a/src/coreclr/inc/readytorun.h +++ b/src/coreclr/inc/readytorun.h @@ -336,6 +336,7 @@ enum ReadyToRunHelper READYTORUN_HELPER_FailFast = 0x24, READYTORUN_HELPER_ThrowNullRef = 0x25, READYTORUN_HELPER_ThrowDivZero = 0x26, + READYTORUN_HELPER_ThrowExact = 0x27, // Write barriers READYTORUN_HELPER_WriteBarrier = 0x30, diff --git a/src/coreclr/nativeaot/Runtime/StackFrameIterator.cpp b/src/coreclr/nativeaot/Runtime/StackFrameIterator.cpp index 5762e817a88904..c2f8ed7eabdfbe 100644 --- a/src/coreclr/nativeaot/Runtime/StackFrameIterator.cpp +++ b/src/coreclr/nativeaot/Runtime/StackFrameIterator.cpp @@ -44,6 +44,7 @@ EXTERN_C CODE_LOCATION RhpCallFilterFunclet2; EXTERN_C CODE_LOCATION RhpThrowEx2; EXTERN_C CODE_LOCATION RhpThrowHwEx2; EXTERN_C CODE_LOCATION RhpRethrow2; +EXTERN_C CODE_LOCATION RhpThrowExact2; #endif // !defined(FEATURE_PORTABLE_HELPERS) // Addresses of functions in the DAC won't match their runtime counterparts so we @@ -2241,7 +2242,8 @@ StackFrameIterator::ReturnAddressCategory StackFrameIterator::CategorizeUnadjust if (EQUALS_RETURN_ADDRESS(returnAddress, RhpThrowEx2) || EQUALS_RETURN_ADDRESS(returnAddress, RhpThrowHwEx2) || - EQUALS_RETURN_ADDRESS(returnAddress, RhpRethrow2)) + EQUALS_RETURN_ADDRESS(returnAddress, RhpRethrow2) || + EQUALS_RETURN_ADDRESS(returnAddress, RhpThrowExact2)) { return InThrowSiteThunk; } diff --git a/src/coreclr/nativeaot/Runtime/amd64/ExceptionHandling.S b/src/coreclr/nativeaot/Runtime/amd64/ExceptionHandling.S index d2bce874cecaca..fbb68a4b826a3f 100644 --- a/src/coreclr/nativeaot/Runtime/amd64/ExceptionHandling.S +++ b/src/coreclr/nativeaot/Runtime/amd64/ExceptionHandling.S @@ -158,6 +158,88 @@ ALTERNATE_ENTRY RhpThrowEx2 NESTED_END RhpThrowEx, _TEXT +////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// RhpThrowExact +// +// SUMMARY: Similar to RhpThrowEx, except that it sets the rethrow flag +// +// INPUT: RDI: exception object +// +// OUTPUT: +// +////////////////////////////////////////////////////////////////////////////////////////////////////////////// +NESTED_ENTRY RhpThrowExact, _TEXT, NoHandler + + STACKSIZEOF_ExInfo = ((SIZEOF__ExInfo + 15) & (~ 15)) + rsp_offsetof_Context = STACKSIZEOF_ExInfo + + lea rax, [rsp+8] // save the RSP of the throw site + mov rsi, [rsp] // get return address + + xor rdx, rdx + push_register rdx // padding + +// struct PAL_LIMITED_CONTEXT +// { + push_nonvol_reg r15 + push_nonvol_reg r14 + push_nonvol_reg r13 + push_nonvol_reg r12 + push_register rdx // rdx set to 0 + push_nonvol_reg rbx + push_register rdx // rax set to 0 + push_nonvol_reg rbp + push_register rax // 'faulting' RSP + push_register rsi // 'faulting' IP +// } + + // allocate space for the ExInfo + alloc_stack STACKSIZEOF_ExInfo + + END_PROLOGUE + + mov rbx, rdi + INLINE_GETTHREAD + mov rdi, rbx + + lea rbx, [rsp + rsp_offsetof_Context + SIZEOF__PAL_LIMITED_CONTEXT + 0x8] // rbx <- addr of return address + + // There is runtime C# code that can tail call to RhpThrowExact using a binder intrinsic. So the return + // address could have been hijacked when we were in that C# code and we must remove the hijack and + // reflect the correct return address in our exception context record. The other throw helpers don't + // need this because they cannot be tail-called from C#. + INLINE_THREAD_UNHIJACK rax, rcx, rsi // trashes RCX, RSI + mov rsi, [rbx] // rdx <- return address + mov [rsp + rsp_offsetof_Context + OFFSETOF__PAL_LIMITED_CONTEXT__IP], rsi // set 'faulting' IP after unhijack + + mov rsi, rsp // rsi <- ExInfo* + + mov [rsi + OFFSETOF__ExInfo__m_exception], rdx // init the exception object to null + mov byte ptr [rsi + OFFSETOF__ExInfo__m_passNumber], 1 // init to the first pass + mov dword ptr [rsi + OFFSETOF__ExInfo__m_idxCurClause], 0xFFFFFFFF + mov byte ptr [rsi + OFFSETOF__ExInfo__m_kind], 4 // ExKind.RethrowFlag + + // link the ExInfo into the thread's ExInfo chain + mov rdx, [rax + OFFSETOF__Thread__m_pExInfoStackHead] + mov [rsi + OFFSETOF__ExInfo__m_pPrevExInfo], rdx // pExInfo->m_pPrevExInfo = m_pExInfoStackHead + mov [rax + OFFSETOF__Thread__m_pExInfoStackHead], rsi // m_pExInfoStackHead = pExInfo + + // set the exception context field on the ExInfo + lea rdx, [rsp + rsp_offsetof_Context] // rdx <- PAL_LIMITED_CONTEXT* + mov [rsi + OFFSETOF__ExInfo__m_pExContext], rdx // init ExInfo.m_pExContext + + // rdi still contains the exception object + // rsi contains the address of the ExInfo + call EXTERNAL_C_FUNC(RhThrowEx) + +ALTERNATE_ENTRY RhpThrowExact2 + + // no return + int 3 + +NESTED_END RhpThrowExact, _TEXT + ////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // void FASTCALL RhpRethrow() diff --git a/src/coreclr/nativeaot/Runtime/amd64/ExceptionHandling.asm b/src/coreclr/nativeaot/Runtime/amd64/ExceptionHandling.asm index 0e63c8808439fd..3516a3271edd89 100644 --- a/src/coreclr/nativeaot/Runtime/amd64/ExceptionHandling.asm +++ b/src/coreclr/nativeaot/Runtime/amd64/ExceptionHandling.asm @@ -203,6 +203,106 @@ ALTERNATE_ENTRY RhpThrowEx2 NESTED_END RhpThrowEx, _TEXT +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; RhpThrowExact +;; +;; SUMMARY: Similar to RhpThrowEx, except that it sets the rethrow flag +;; +;; INPUT: RCX: exception object +;; +;; OUTPUT: +;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +NESTED_ENTRY RhpThrowExact, _TEXT + + SIZEOF_XmmSaves equ SIZEOF__PAL_LIMITED_CONTEXT - OFFSETOF__PAL_LIMITED_CONTEXT__Xmm6 + STACKSIZEOF_ExInfo equ ((SIZEOF__ExInfo + 15) AND (NOT 15)) + + SIZEOF_OutgoingScratch equ 20h + rsp_offsetof_ExInfo equ SIZEOF_OutgoingScratch + rsp_offsetof_Context equ SIZEOF_OutgoingScratch + STACKSIZEOF_ExInfo + + lea rax, [rsp+8] ;; save the RSP of the throw site + mov rdx, [rsp] ;; get return address + + xor r8, r8 + + alloc_stack SIZEOF_XmmSaves + 8h ;; reserve stack for the xmm saves (+8h to realign stack) + rdsspq r8 ;; nop if SSP is not implemented, 0 if not enabled + test r8, r8 + je @f + add r8, 8 ;; Move SSP to match RSP of the throw site + @@: + push_vol_reg r8 ;; SSP + xor r8, r8 + push_nonvol_reg r15 + push_nonvol_reg r14 + push_nonvol_reg r13 + push_nonvol_reg r12 + push_nonvol_reg rbx + push_vol_reg r8 + push_nonvol_reg rsi + push_nonvol_reg rdi + push_nonvol_reg rbp + push_vol_reg rax ;; 'faulting' RSP + push_vol_reg rdx ;; 'faulting' IP + + ;; allocate outgoing args area and space for the ExInfo + alloc_stack SIZEOF_OutgoingScratch + STACKSIZEOF_ExInfo + + save_xmm128_postrsp Xmm6 , rsp_offsetof_Context + OFFSETOF__PAL_LIMITED_CONTEXT__Xmm6 + save_xmm128_postrsp Xmm7 , rsp_offsetof_Context + OFFSETOF__PAL_LIMITED_CONTEXT__Xmm7 + save_xmm128_postrsp Xmm8 , rsp_offsetof_Context + OFFSETOF__PAL_LIMITED_CONTEXT__Xmm8 + save_xmm128_postrsp Xmm9 , rsp_offsetof_Context + OFFSETOF__PAL_LIMITED_CONTEXT__Xmm9 + save_xmm128_postrsp Xmm10, rsp_offsetof_Context + OFFSETOF__PAL_LIMITED_CONTEXT__Xmm10 + save_xmm128_postrsp Xmm11, rsp_offsetof_Context + OFFSETOF__PAL_LIMITED_CONTEXT__Xmm11 + save_xmm128_postrsp Xmm12, rsp_offsetof_Context + OFFSETOF__PAL_LIMITED_CONTEXT__Xmm12 + save_xmm128_postrsp Xmm13, rsp_offsetof_Context + OFFSETOF__PAL_LIMITED_CONTEXT__Xmm13 + save_xmm128_postrsp Xmm14, rsp_offsetof_Context + OFFSETOF__PAL_LIMITED_CONTEXT__Xmm14 + save_xmm128_postrsp Xmm15, rsp_offsetof_Context + OFFSETOF__PAL_LIMITED_CONTEXT__Xmm15 + + END_PROLOGUE + + INLINE_GETTHREAD rax, rbx ;; rax <- Thread*, rbx is trashed + + lea rbx, [rsp + rsp_offsetof_Context + SIZEOF__PAL_LIMITED_CONTEXT + 8h] ;; rbx <- addr of return address + + ;; There is runtime C# code that can tail call to RhpThrowExact using a binder intrinsic. So the return + ;; address could have been hijacked when we were in that C# code and we must remove the hijack and + ;; reflect the correct return address in our exception context record. The other throw helpers don't + ;; need this because they cannot be tail-called from C#. + INLINE_THREAD_UNHIJACK rax, r9, rdx ;; trashes R9, RDX + mov rdx, [rbx] ;; rdx <- return address + mov [rsp + rsp_offsetof_Context + OFFSETOF__PAL_LIMITED_CONTEXT__IP], rdx ;; set 'faulting' IP after unhijack + + lea rdx, [rsp + rsp_offsetof_ExInfo] ;; rdx <- ExInfo* + + mov [rdx + OFFSETOF__ExInfo__m_exception], r8 ;; init the exception object to null + mov byte ptr [rdx + OFFSETOF__ExInfo__m_passNumber], 1 ;; init to the first pass + mov dword ptr [rdx + OFFSETOF__ExInfo__m_idxCurClause], 0FFFFFFFFh + mov byte ptr [rdx + OFFSETOF__ExInfo__m_kind], 4 ;; ExKind.RethrowFlag + + ;; link the ExInfo into the thread's ExInfo chain + mov r8, [rax + OFFSETOF__Thread__m_pExInfoStackHead] + mov [rdx + OFFSETOF__ExInfo__m_pPrevExInfo], r8 ;; pExInfo->m_pPrevExInfo = m_pExInfoStackHead + mov [rax + OFFSETOF__Thread__m_pExInfoStackHead], rdx ;; m_pExInfoStackHead = pExInfo + + ;; set the exception context field on the ExInfo + lea r8, [rsp + rsp_offsetof_Context] ;; r8 <- PAL_LIMITED_CONTEXT* + mov [rdx + OFFSETOF__ExInfo__m_pExContext], r8 ;; init ExInfo.m_pExContext + + ;; rcx still contains the exception object + ;; rdx contains the address of the ExInfo + call RhThrowEx + +ALTERNATE_ENTRY RhpThrowExact2 + + ;; no return + int 3 + +NESTED_END RhpThrowExact, _TEXT + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; void FASTCALL RhpRethrow() diff --git a/src/coreclr/nativeaot/Runtime/arm/ExceptionHandling.S b/src/coreclr/nativeaot/Runtime/arm/ExceptionHandling.S index 9be4b24b7a2305..e7835386b0fcc2 100644 --- a/src/coreclr/nativeaot/Runtime/arm/ExceptionHandling.S +++ b/src/coreclr/nativeaot/Runtime/arm/ExceptionHandling.S @@ -183,6 +183,113 @@ GLOBAL_LABEL RhpThrowEx2 NESTED_END RhpThrowEx, _TEXT +////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// RhpThrowExact +// +// SUMMARY: Similar to RhpThrowEx, except that it sets the rethrow flag +// +// INPUT: R0: exception object +// +// OUTPUT: +// +////////////////////////////////////////////////////////////////////////////////////////////////////////////// +NESTED_ENTRY RhpThrowExact, _TEXT, NoHandler + + // Setup a PAL_LIMITED_CONTEXT on the stack { + PROLOG_VPUSH {d8-d15} + PROLOG_PUSH "{r0,lr}" // Reserve space for SP and store LR + PROLOG_PUSH "{r0,r4-r11,lr}" + // } end PAL_LIMITED_CONTEXT + + PROLOG_STACK_ALLOC STACKSIZEOF_ExInfo + + // Calculate SP at callsite and save into the PAL_LIMITED_CONTEXT + add r4, sp, #(STACKSIZEOF_ExInfo + SIZEOF__PAL_LIMITED_CONTEXT) + str r4, [sp, #(rsp_offsetof_Context + OFFSETOF__PAL_LIMITED_CONTEXT__SP)] + + mov r4, r0 // Save exception object + // r0 = GetThread() + INLINE_GETTHREAD + + add r2, sp, #(rsp_offsetof_Context + SIZEOF__PAL_LIMITED_CONTEXT + 0x8) // r2 <- addr of return address + + // There is runtime C# code that can tail call to RhpThrowExact using a binder intrinsic. So the return + // address could have been hijacked when we were in that C# code and we must remove the hijack and + // reflect the correct return address in our exception context record. The other throw helpers don't + // need this because they cannot be tail-called from C#. + // NOTE: we cannot use INLINE_THREAD_UNHIJACK because it will write into the stack at the location + // where the tail-calling thread had saved LR, which may not match where we have saved LR. + + ldr r1, [r0, #OFFSETOF__Thread__m_pvHijackedReturnAddress] + cbz r1, LOCAL_LABEL(NotHiJacked) + + ldr r3, [r0, #OFFSETOF__Thread__m_ppvHijackedReturnAddressLocation] + + // r4: exception object + // r1: hijacked return address + // r0: pThread + // r3: hijacked return address location + + add r12, sp, #(STACKSIZEOF_ExInfo + SIZEOF__PAL_LIMITED_CONTEXT) // re-compute SP at callsite + cmp r3, r12 // if (m_ppvHijackedReturnAddressLocation < SP at callsite) + blo LOCAL_LABEL(TailCallWasHijacked) + + // normal case where a valid return address location is hijacked + str r1, [r3] + b LOCAL_LABEL(ClearThreadState) + +LOCAL_LABEL(TailCallWasHijacked): + + // Abnormal case where the return address location is now invalid because we ended up here via a tail + // call. In this case, our hijacked return address should be the correct caller of this method. + // + + // stick the previous return address in LR as well as in the right spots in our PAL_LIMITED_CONTEXT. + mov lr, r1 + str lr, [sp, #(rsp_offsetof_Context + OFFSETOF__PAL_LIMITED_CONTEXT__LR)] + str lr, [sp, #(rsp_offsetof_Context + OFFSETOF__PAL_LIMITED_CONTEXT__IP)] + +LOCAL_LABEL(ClearThreadState): + + // clear the Thread's hijack state + mov r3, #0 + str r3, [r0, #OFFSETOF__Thread__m_ppvHijackedReturnAddressLocation] + str r3, [r0, #OFFSETOF__Thread__m_pvHijackedReturnAddress] + +LOCAL_LABEL(NotHiJacked): + + add r1, sp, #rsp_offsetof_ExInfo // r1 <- ExInfo* + mov r3, #0 + str r3, [r1, #OFFSETOF__ExInfo__m_exception] // init the exception object to null + mov r3, #1 + strb r3, [r1, #OFFSETOF__ExInfo__m_passNumber] // init to the first pass + mov r3, #4 + strb r3, [r1, #OFFSETOF__ExInfo__m_kind] // ExKind.RethrowFlag + mov r3, #0xFFFFFFFF + str r3, [r1, #OFFSETOF__ExInfo__m_idxCurClause] + + // link the ExInfo into the thread's ExInfo chain + ldr r3, [r0, #OFFSETOF__Thread__m_pExInfoStackHead] + str r3, [r1, #OFFSETOF__ExInfo__m_pPrevExInfo] // pExInfo->m_pPrevExInfo = m_pExInfoStackHead + str r1, [r0, #OFFSETOF__Thread__m_pExInfoStackHead] // m_pExInfoStackHead = pExInfo + + // set the exception context field on the ExInfo + add r3, sp, #rsp_offsetof_Context // r3 <- PAL_LIMITED_CONTEXT* + str r3, [r1, #OFFSETOF__ExInfo__m_pExContext] // init ExInfo.m_pExContext + + mov r0, r4 // Restore exception object + // r0 contains the exception object + // r1 contains the address of the new ExInfo + bl C_FUNC(RhThrowEx) + +GLOBAL_LABEL RhpThrowExact2 + + // no return + EMIT_BREAKPOINT + +NESTED_END RhpThrowExact, _TEXT + ////////////////////////////////////////////////////////////////////////////////////////////////////////////// // diff --git a/src/coreclr/nativeaot/Runtime/arm64/ExceptionHandling.S b/src/coreclr/nativeaot/Runtime/arm64/ExceptionHandling.S index 5c992615f66637..95ae9c42e2f2bf 100644 --- a/src/coreclr/nativeaot/Runtime/arm64/ExceptionHandling.S +++ b/src/coreclr/nativeaot/Runtime/arm64/ExceptionHandling.S @@ -338,6 +338,93 @@ LOCAL_LABEL(NotHijacked): EMIT_BREAKPOINT NESTED_END RhpThrowEx, _TEXT +// +// RhpThrowExact +// +// SUMMARY: Similar to RhpThrowEx, except that it sets the rethrow flag +// +// INPUT: X0: exception object +// +// OUTPUT: +// + + NESTED_ENTRY RhpThrowExact, _TEXT, NoHandler + + ALLOC_THROW_FRAME SOFTWARE_EXCEPTION + + GetThreadX2 + + // There is runtime C# code that can tail call to RhpThrowExact using a binder intrinsic. So the return + // address could have been hijacked when we were in that C# code and we must remove the hijack and + // reflect the correct return address in our exception context record. The other throw helpers don't + // need this because they cannot be tail-called from C#. + + // NOTE: we cannot use INLINE_THREAD_UNHIJACK because it will write into the stack at the location + // where the tail-calling thread had saved LR, which may not match where we have saved LR. + + ldr x1, [x2, #OFFSETOF__Thread__m_pvHijackedReturnAddress] + cbz x1, LOCAL_LABEL(NotHijacked) + + ldr x3, [x2, #OFFSETOF__Thread__m_ppvHijackedReturnAddressLocation] + + // x0: exception object + // x1: hijacked return address + // x2: pThread + // x3: hijacked return address location + + add x12, sp, #(STACKSIZEOF_ExInfo + SIZEOF__PAL_LIMITED_CONTEXT) // re-compute SP at callsite + cmp x3, x12 // if (m_ppvHijackedReturnAddressLocation < SP at callsite) + blo LOCAL_LABEL(TailCallWasHijacked) + + // normal case where a valid return address location is hijacked + str x1, [x3] + b LOCAL_LABEL(ClearThreadState) + +LOCAL_LABEL(TailCallWasHijacked): + + // Abnormal case where the return address location is now invalid because we ended up here via a tail + // call. In this case, our hijacked return address should be the correct caller of this method. + + // stick the previous return address in LR as well as in the right spots in our PAL_LIMITED_CONTEXT. + mov lr, x1 + str lr, [sp, #(rsp_offsetof_Context + OFFSETOF__PAL_LIMITED_CONTEXT__LR)] + str lr, [sp, #(rsp_offsetof_Context + OFFSETOF__PAL_LIMITED_CONTEXT__IP)] + +LOCAL_LABEL(ClearThreadState): + + // clear the Thread's hijack state + str xzr, [x2, #OFFSETOF__Thread__m_ppvHijackedReturnAddressLocation] + str xzr, [x2, #OFFSETOF__Thread__m_pvHijackedReturnAddress] + +LOCAL_LABEL(NotHijacked): + + add x1, sp, #rsp_offsetof_ExInfo // x1 <- ExInfo* + str xzr, [x1, #OFFSETOF__ExInfo__m_exception] // pExInfo->m_exception = null + mov w3, #1 + strb w3, [x1, #OFFSETOF__ExInfo__m_passNumber] // pExInfo->m_passNumber = 1 + mov w3, #0xFFFFFFFF + str w3, [x1, #OFFSETOF__ExInfo__m_idxCurClause] // pExInfo->m_idxCurClause = MaxTryRegionIdx + mov w3, #4 + strb w3, [x1, #OFFSETOF__ExInfo__m_kind] // pExInfo->m_kind = ExKind.RethrowFlag + + // link the ExInfo into the thread's ExInfo chain + ldr x3, [x2, #OFFSETOF__Thread__m_pExInfoStackHead] + str x3, [x1, #OFFSETOF__ExInfo__m_pPrevExInfo] // pExInfo->m_pPrevExInfo = m_pExInfoStackHead + str x1, [x2, #OFFSETOF__Thread__m_pExInfoStackHead] // m_pExInfoStackHead = pExInfo + + // set the exception context field on the ExInfo + add x2, sp, #rsp_offsetof_Context // x2 <- PAL_LIMITED_CONTEXT* + str x2, [x1, #OFFSETOF__ExInfo__m_pExContext] // pExInfo->m_pExContext = pContext + + // x0: exception object + // x1: ExInfo* + bl C_FUNC(RhThrowEx) + + ALTERNATE_ENTRY RhpThrowExact2 + + // no return + EMIT_BREAKPOINT + NESTED_END RhpThrowExact, _TEXT // // void FASTCALL RhpRethrow() diff --git a/src/coreclr/nativeaot/Runtime/arm64/ExceptionHandling.asm b/src/coreclr/nativeaot/Runtime/arm64/ExceptionHandling.asm index 15fe87b22fc358..514d97a77265b5 100644 --- a/src/coreclr/nativeaot/Runtime/arm64/ExceptionHandling.asm +++ b/src/coreclr/nativeaot/Runtime/arm64/ExceptionHandling.asm @@ -339,6 +339,97 @@ NotHijacked EMIT_BREAKPOINT NESTED_END RhpThrowEx +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; RhpThrowExact +;; +;; SUMMARY: Similar to RhpThrowEx, except that it sets the rethrow flag +;; +;; INPUT: X0: exception object +;; +;; OUTPUT: +;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + NESTED_ENTRY RhpThrowExact + + ALLOC_THROW_FRAME SOFTWARE_EXCEPTION + + ;; x2 = GetThread(), TRASHES x1 + INLINE_GETTHREAD x2, x1 + + ;; There is runtime C# code that can tail call to RhpThrowExact using a binder intrinsic. So the return + ;; address could have been hijacked when we were in that C# code and we must remove the hijack and + ;; reflect the correct return address in our exception context record. The other throw helpers don't + ;; need this because they cannot be tail-called from C#. + + ;; NOTE: we cannot use INLINE_THREAD_UNHIJACK because it will write into the stack at the location + ;; where the tail-calling thread had saved LR, which may not match where we have saved LR. + + ldr x1, [x2, #OFFSETOF__Thread__m_pvHijackedReturnAddress] + cbz x1, NotHijacked + + ldr x3, [x2, #OFFSETOF__Thread__m_ppvHijackedReturnAddressLocation] + + ;; x0: exception object + ;; x1: hijacked return address + ;; x2: pThread + ;; x3: hijacked return address location + + add x12, sp, #(STACKSIZEOF_ExInfo + SIZEOF__PAL_LIMITED_CONTEXT) ;; re-compute SP at callsite + cmp x3, x12 ;; if (m_ppvHijackedReturnAddressLocation < SP at callsite) + blo TailCallWasHijacked + + ;; normal case where a valid return address location is hijacked + str x1, [x3] + b ClearThreadState + +TailCallWasHijacked + + ;; Abnormal case where the return address location is now invalid because we ended up here via a tail + ;; call. In this case, our hijacked return address should be the correct caller of this method. + ;; + + ;; stick the previous return address in LR as well as in the right spots in our PAL_LIMITED_CONTEXT. + mov lr, x1 + str lr, [sp, #(rsp_offsetof_Context + OFFSETOF__PAL_LIMITED_CONTEXT__LR)] + str lr, [sp, #(rsp_offsetof_Context + OFFSETOF__PAL_LIMITED_CONTEXT__IP)] + +ClearThreadState + + ;; clear the Thread's hijack state + str xzr, [x2, #OFFSETOF__Thread__m_ppvHijackedReturnAddressLocation] + str xzr, [x2, #OFFSETOF__Thread__m_pvHijackedReturnAddress] + +NotHijacked + + add x1, sp, #rsp_offsetof_ExInfo ;; x1 <- ExInfo* + str xzr, [x1, #OFFSETOF__ExInfo__m_exception] ;; pExInfo->m_exception = null + mov w3, #1 + strb w3, [x1, #OFFSETOF__ExInfo__m_passNumber] ;; pExInfo->m_passNumber = 1 + mov w3, #0xFFFFFFFF + str w3, [x1, #OFFSETOF__ExInfo__m_idxCurClause] ;; pExInfo->m_idxCurClause = MaxTryRegionIdx + mov w3, #4 + strb w3, [x1, #OFFSETOF__ExInfo__m_kind] ;; pExInfo->m_kind = ExKind.RethrowFlag + + ;; link the ExInfo into the thread's ExInfo chain + ldr x3, [x2, #OFFSETOF__Thread__m_pExInfoStackHead] + str x3, [x1, #OFFSETOF__ExInfo__m_pPrevExInfo] ;; pExInfo->m_pPrevExInfo = m_pExInfoStackHead + str x1, [x2, #OFFSETOF__Thread__m_pExInfoStackHead] ;; m_pExInfoStackHead = pExInfo + + ;; set the exception context field on the ExInfo + add x2, sp, #rsp_offsetof_Context ;; x2 <- PAL_LIMITED_CONTEXT* + str x2, [x1, #OFFSETOF__ExInfo__m_pExContext] ;; pExInfo->m_pExContext = pContext + + ;; x0: exception object + ;; x1: ExInfo* + bl RhThrowEx + + ALTERNATE_ENTRY RhpThrowExact2 + + ;; no return + EMIT_BREAKPOINT + NESTED_END RhpThrowExact + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; void FASTCALL RhpRethrow() diff --git a/src/coreclr/nativeaot/Runtime/i386/ExceptionHandling.asm b/src/coreclr/nativeaot/Runtime/i386/ExceptionHandling.asm index 810058235b7d32..53776f01d802a7 100644 --- a/src/coreclr/nativeaot/Runtime/i386/ExceptionHandling.asm +++ b/src/coreclr/nativeaot/Runtime/i386/ExceptionHandling.asm @@ -166,6 +166,85 @@ ALTERNATE_ENTRY _RhpThrowEx2 FASTCALL_ENDFUNC +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; RhpThrowExact +;; +;; SUMMARY: Similar to RhpThrowEx, except that it sets the rethrow flag +;; +;; INPUT: ECX: exception object +;; +;; OUTPUT: +;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +FASTCALL_FUNC RhpThrowExact, 4 + + esp_offsetof_ExInfo textequ %0 + esp_offsetof_Context textequ %SIZEOF__ExInfo + + push ebp + mov ebp, esp + + lea eax, [esp+8] ;; calculate the RSP of the throw site + mov edx, [esp+4] ;; get the throw site IP via the return address + +;; struct PAL_LIMITED_CONTEXT +;; { + push ebx + push eax + push esi + push edi + mov ebx, [ebp] + push ebx ;; 'faulting' Rbp + push eax ;; 'faulting' Rsp + push edx ;; 'faulting' IP +;; }; + + sub esp, SIZEOF__ExInfo + + ;; ------------------------- + + lea ebx, [eax-4] ;; ebx <- addr of return address + INLINE_GETTHREAD eax, edx ;; eax <- thread, edx <- trashed + + ;; There is runtime C# code that can tail call to RhpThrowExact using a binder intrinsic. So the return + ;; address could have been hijacked when we were in that C# code and we must remove the hijack and + ;; reflect the correct return address in our exception context record. The other throw helpers don't + ;; need this because they cannot be tail-called from C#. + + INLINE_THREAD_UNHIJACK eax, esi, edx ;; trashes esi, edx + + mov edx, [ebx] ;; edx <- return address + mov [esp + esp_offsetof_Context + OFFSETOF__PAL_LIMITED_CONTEXT__IP], edx ;; set 'faulting' IP after unhijack + + lea edx, [esp + esp_offsetof_ExInfo] ;; edx <- ExInfo* + + xor esi, esi + mov [edx + OFFSETOF__ExInfo__m_exception], esi ;; init the exception object to null + mov byte ptr [edx + OFFSETOF__ExInfo__m_passNumber], 1 ;; init to the first pass + mov dword ptr [edx + OFFSETOF__ExInfo__m_idxCurClause], 0FFFFFFFFh + mov byte ptr [edx + OFFSETOF__ExInfo__m_kind], 4 ;; ExKind.RethrowFlag + + ;; link the ExInfo into the thread's ExInfo chain + mov ebx, [eax + OFFSETOF__Thread__m_pExInfoStackHead] + mov [edx + OFFSETOF__ExInfo__m_pPrevExInfo], ebx ;; pExInfo->m_pPrevExInfo = m_pExInfoStackHead + mov [eax + OFFSETOF__Thread__m_pExInfoStackHead], edx ;; m_pExInfoStackHead = pExInfo + + ;; set the exception context field on the ExInfo + lea ebx, [esp + esp_offsetof_Context] ;; ebx <- PAL_LIMITED_CONTEXT* + mov [edx + OFFSETOF__ExInfo__m_pExContext], ebx ;; init ExInfo.m_pExContext + + ;; ecx still contains the exception object + ;; edx contains the address of the ExInfo + call RhThrowEx + +ALTERNATE_ENTRY _RhpThrowExact2 + + ;; no return + int 3 + +FASTCALL_ENDFUNC + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; void FASTCALL RhpRethrow() diff --git a/src/coreclr/nativeaot/Runtime/loongarch64/ExceptionHandling.S b/src/coreclr/nativeaot/Runtime/loongarch64/ExceptionHandling.S index 93f2afd3ea786b..856c69616fdcbb 100644 --- a/src/coreclr/nativeaot/Runtime/loongarch64/ExceptionHandling.S +++ b/src/coreclr/nativeaot/Runtime/loongarch64/ExceptionHandling.S @@ -353,6 +353,92 @@ LOCAL_LABEL(NotHijacked): EMIT_BREAKPOINT NESTED_END RhpThrowEx, _TEXT +// +// RhpThrowExact +// +// SUMMARY: Similar to RhpThrowEx, except that it sets the rethrow flag +// +// INPUT: a0: exception object +// +// OUTPUT: +// + + NESTED_ENTRY RhpThrowExact, _TEXT, NoHandler + + ALLOC_THROW_FRAME SOFTWARE_EXCEPTION + + GetThreadA2 + + // There is runtime C# code that can tail call to RhpThrowExact using a binder intrinsic. So the return + // address could have been hijacked when we were in that C# code and we must remove the hijack and + // reflect the correct return address in our exception context record. The other throw helpers don't + // need this because they cannot be tail-called from C#. + + // NOTE: we cannot use INLINE_THREAD_UNHIJACK because it will write into the stack at the location + // where the tail-calling thread had saved RA, which may not match where we have saved RA. + + ld.d $a1, $a2, OFFSETOF__Thread__m_pvHijackedReturnAddress + beqz $a1, LOCAL_LABEL(NotHijacked) + + ld.d $a3, $a2, OFFSETOF__Thread__m_ppvHijackedReturnAddressLocation + + // a0: exception object + // a1: hijacked return address + // a2: pThread + // a3: hijacked return address location + + addi.d $t3, $sp, (STACKSIZEOF_ExInfo + SIZEOF__PAL_LIMITED_CONTEXT) // re-compute SP at callsite + bltu $a3, $t3, LOCAL_LABEL(TailCallWasHijacked) // if (m_ppvHijackedReturnAddressLocation < SP at callsite) + + // normal case where a valid return address location is hijacked + st.d $a1, $a3, 0 + b LOCAL_LABEL(ClearThreadState) + +LOCAL_LABEL(TailCallWasHijacked): + + // Abnormal case where the return address location is now invalid because we ended up here via a tail + // call. In this case, our hijacked return address should be the correct caller of this method. + + // stick the previous return address in RA as well as in the right spots in our PAL_LIMITED_CONTEXT. + ori $ra, $a1, 0 + st.d $ra, $sp, (rsp_offsetof_Context + OFFSETOF__PAL_LIMITED_CONTEXT__RA) + st.d $ra, $sp, (rsp_offsetof_Context + OFFSETOF__PAL_LIMITED_CONTEXT__IP) + +LOCAL_LABEL(ClearThreadState): + + // clear the Thread's hijack state + st.d $zero, $a2, OFFSETOF__Thread__m_ppvHijackedReturnAddressLocation + st.d $zero, $a2, OFFSETOF__Thread__m_pvHijackedReturnAddress + +LOCAL_LABEL(NotHijacked): + + addi.d $a1, $sp, rsp_offsetof_ExInfo // a1 <- ExInfo* + st.d $zero, $a1, OFFSETOF__ExInfo__m_exception // pExInfo->m_exception = null + ori $a3, $zero, 1 + st.b $a3, $a1, OFFSETOF__ExInfo__m_passNumber // pExInfo->m_passNumber = 1 + addi.w $a3, $zero, -1 + st.w $a3, $a1, OFFSETOF__ExInfo__m_idxCurClause // pExInfo->m_idxCurClause = MaxTryRegionIdx + ori $a3, $zero, 4 + st.b $a3, $a1, OFFSETOF__ExInfo__m_kind // pExInfo->m_kind = ExKind.RethrowFlag + + // link the ExInfo into the thread's ExInfo chain + ld.d $a3, $a2, OFFSETOF__Thread__m_pExInfoStackHead + st.d $a3, $a1, OFFSETOF__ExInfo__m_pPrevExInfo // pExInfo->m_pPrevExInfo = m_pExInfoStackHead + st.d $a1, $a2, OFFSETOF__Thread__m_pExInfoStackHead // m_pExInfoStackHead = pExInfo + + // set the exception context field on the ExInfo + addi.d $a2, $sp, rsp_offsetof_Context // a2 <- PAL_LIMITED_CONTEXT* + st.d $a2, $a1, OFFSETOF__ExInfo__m_pExContext // pExInfo->m_pExContext = pContext + + // a0: exception object + // a1: ExInfo* + bl C_FUNC(RhThrowEx) + + ALTERNATE_ENTRY RhpThrowExact2 + + // no return + EMIT_BREAKPOINT + NESTED_END RhpThrowExact, _TEXT // // void FASTCALL RhpRethrow() diff --git a/src/coreclr/nativeaot/Runtime/riscv64/ExceptionHandling.S b/src/coreclr/nativeaot/Runtime/riscv64/ExceptionHandling.S index 15aed3af521ea4..fdc724de918b34 100644 --- a/src/coreclr/nativeaot/Runtime/riscv64/ExceptionHandling.S +++ b/src/coreclr/nativeaot/Runtime/riscv64/ExceptionHandling.S @@ -380,6 +380,85 @@ LOCAL_LABEL(NotHijacked): NESTED_END RhpThrowEx, _TEXT +// +// RhpThrowExact +// +// SUMMARY: Similar to RhpThrowEx, except that it sets the rethrow flag +// +// INPUT: a0: exception object +// +// OUTPUT: +// + + NESTED_ENTRY RhpThrowExact, _TEXT, NoHandler + + ALLOC_THROW_FRAME SOFTWARE_EXCEPTION + + GetThreadA2 + + ld a1, OFFSETOF__Thread__m_pvHijackedReturnAddress(a2) + beq a1, zero, LOCAL_LABEL(NotHijacked) + + ld a3, OFFSETOF__Thread__m_ppvHijackedReturnAddressLocation(a2) + + // Recompute SP at callsite + addi t3, sp, (STACKSIZEOF_ExInfo + SIZEOF__PAL_LIMITED_CONTEXT) + bltu a3, t3, LOCAL_LABEL(TailCallWasHijacked) // if (m_ppvHijackedReturnAddressLocation < SP at callsite) + + // Normal case where a valid return address location is hijacked + sd a1, 0(a3) + tail LOCAL_LABEL(ClearThreadState) + +LOCAL_LABEL(TailCallWasHijacked): + + // Abnormal case where the return address location is now invalid because we ended up here via a tail + // call. In this case, our hijacked return address should be the correct caller of this method. + + // Stick the previous return address in RA as well as in the right spots in our PAL_LIMITED_CONTEXT. + mv ra, a1 + + // Compute offsets for PAL_LIMITED_CONTEXT + sd ra, (rsp_offsetof_Context + OFFSETOF__PAL_LIMITED_CONTEXT__RA)(sp) + sd ra, (rsp_offsetof_Context + OFFSETOF__PAL_LIMITED_CONTEXT__IP)(sp) + +LOCAL_LABEL(ClearThreadState): + + // Clear the Thread's hijack state + sd zero, OFFSETOF__Thread__m_ppvHijackedReturnAddressLocation(a2) + sd zero, OFFSETOF__Thread__m_pvHijackedReturnAddress(a2) + +LOCAL_LABEL(NotHijacked): + + // Compute the offset for ExInfo + addi a1, sp, rsp_offsetof_ExInfo // a1 <- ExInfo* + sd zero, OFFSETOF__ExInfo__m_exception(a1) // pExInfo->m_exception = null + li a3, 1 + sb a3, OFFSETOF__ExInfo__m_passNumber(a1) // pExInfo->m_passNumber = 1 + addiw a3, zero, -1 + sw a3, OFFSETOF__ExInfo__m_idxCurClause(a1) // pExInfo->m_idxCurClause = MaxTryRegionIdx + li a3, 4 + sb a3, OFFSETOF__ExInfo__m_kind(a1) // pExInfo->m_kind = ExKind.RethrowFlag + + // Link the ExInfo into the thread's ExInfo chain + ld a3, OFFSETOF__Thread__m_pExInfoStackHead(a2) + sd a3, OFFSETOF__ExInfo__m_pPrevExInfo(a1) // pExInfo->m_pPrevExInfo = m_pExInfoStackHead + sd a1, OFFSETOF__Thread__m_pExInfoStackHead(a2) // m_pExInfoStackHead = pExInfo + + // Set the exception context field on the ExInfo + addi a2, sp, rsp_offsetof_Context // a2 <- PAL_LIMITED_CONTEXT* + sd a2, OFFSETOF__ExInfo__m_pExContext(a1) // pExInfo->m_pExContext = pContext + + // a0: exception object + // a1: ExInfo* + call C_FUNC(RhThrowEx) + + ALTERNATE_ENTRY RhpThrowExact2 + + // No return + EMIT_BREAKPOINT + + NESTED_END RhpThrowExact, _TEXT + // // void FASTCALL RhpRethrow() // diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Exception.NativeAot.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Exception.NativeAot.cs index 9fbfcc9302c562..d1d6fff879b50a 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Exception.NativeAot.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Exception.NativeAot.cs @@ -95,6 +95,7 @@ private enum RhEHFrameType { RH_EH_FIRST_FRAME = 1, RH_EH_FIRST_RETHROW_FRAME = 2, + RH_EH_RUNTIME_ASYNC_FRAME = 4, } // Performance metric to count the number of exceptions thrown @@ -117,9 +118,10 @@ private static void AppendExceptionStackFrame(object exceptionObj, IntPtr IP, in bool isFirstFrame = (flags & (int)RhEHFrameType.RH_EH_FIRST_FRAME) != 0; bool isFirstRethrowFrame = (flags & (int)RhEHFrameType.RH_EH_FIRST_RETHROW_FRAME) != 0; + bool isRuntimeAsyncFrame = (flags & (int)RhEHFrameType.RH_EH_RUNTIME_ASYNC_FRAME) != 0; // track count for metrics - if (isFirstFrame && !isFirstRethrowFrame) + if ((isFirstFrame && !isFirstRethrowFrame) || isRuntimeAsyncFrame) Interlocked.Increment(ref s_exceptionCount); // When we're throwing an exception object, we first need to clear its stacktrace with two exceptions: @@ -137,7 +139,7 @@ private static void AppendExceptionStackFrame(object exceptionObj, IntPtr IP, in ex.AppendStackIP(IP, isFirstRethrowFrame); #if FEATURE_PERFTRACING - if (isFirstFrame && NativeRuntimeEventSource.Log.IsEnabled()) + if ((isFirstFrame || isRuntimeAsyncFrame) && NativeRuntimeEventSource.Log.IsEnabled()) { string typeName = !fatalOutOfMemory ? ex.GetType().ToString() : "System.OutOfMemoryException"; string message = !fatalOutOfMemory ? ex.Message : diff --git a/src/coreclr/tools/Common/Internal/Runtime/ReadyToRunConstants.cs b/src/coreclr/tools/Common/Internal/Runtime/ReadyToRunConstants.cs index 250e316e1d37ce..da985df738d319 100644 --- a/src/coreclr/tools/Common/Internal/Runtime/ReadyToRunConstants.cs +++ b/src/coreclr/tools/Common/Internal/Runtime/ReadyToRunConstants.cs @@ -229,6 +229,7 @@ public enum ReadyToRunHelper FailFast = 0x24, ThrowNullRef = 0x25, ThrowDivZero = 0x26, + ThrowExact = 0x27, // Write barriers WriteBarrier = 0x30, diff --git a/src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncResumptionStub.cs b/src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncResumptionStub.cs index 6297eab0b03814..16154f87c97d25 100644 --- a/src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncResumptionStub.cs +++ b/src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncResumptionStub.cs @@ -34,6 +34,8 @@ public AsyncResumptionStub(MethodDesc targetMethod, TypeDesc owningType) public override TypeSystemContext Context => _targetMethod.Context; + public MethodDesc TargetMethod => _targetMethod; + private MethodSignature InitializeSignature() { TypeDesc objectType = Context.GetWellKnownType(WellKnownType.Object); diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/JitHelper.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/JitHelper.cs index 3c0f11a65c9af6..8b3a191f9862c9 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/JitHelper.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/JitHelper.cs @@ -28,6 +28,9 @@ public static void GetEntryPoint(TypeSystemContext context, ReadyToRunHelper id, case ReadyToRunHelper.Rethrow: mangledName = "RhpRethrow"; break; + case ReadyToRunHelper.ThrowExact: + mangledName = "RhpThrowExact"; + break; case ReadyToRunHelper.Overflow: methodDesc = context.GetHelperEntryPoint("ThrowHelpers"u8, "ThrowOverflowException"u8); diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/MetadataManager.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/MetadataManager.cs index ad923111add5a6..84d4fead7cce6a 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/MetadataManager.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/MetadataManager.cs @@ -778,8 +778,10 @@ protected void ComputeMetadata( flags |= StackTraceRecordFlags.IsHidden; if ((stackVisibility & MethodStackTraceVisibilityFlags.HasLineNumbers) != 0) flags |= StackTraceRecordFlags.HasLineNumbers; + if ((stackVisibility & MethodStackTraceVisibilityFlags.RuntimeAsync) != 0) + flags |= StackTraceRecordFlags.RuntimeAsync; - if ((stackVisibility & MethodStackTraceVisibilityFlags.HasMetadata) != 0) + if ((stackVisibility & (MethodStackTraceVisibilityFlags.HasMetadata | MethodStackTraceVisibilityFlags.RuntimeAsync)) != 0) { StackTraceRecordData record = CreateStackTraceRecord(transform, method, flags); @@ -789,6 +791,11 @@ protected void ComputeMetadata( writer.AdditionalRootRecords.Add(record.MethodName); writer.AdditionalRootRecords.Add(record.MethodSignature); writer.AdditionalRootRecords.Add(record.MethodInstantiationArgumentCollection); + // hide the resumption stubs + if ((flags & StackTraceRecordFlags.RuntimeAsync) != 0) + { + stackTraceRecords.Add(new StackTraceRecordData(method, null, null, null, null, StackTraceRecordFlags.IsHidden)); + } } else if ((stackVisibility & MethodStackTraceVisibilityFlags.IsHidden) != 0) { @@ -930,7 +937,11 @@ record ??= transformed.GetTransformedTypeReference(definition); protected StackTraceRecordData CreateStackTraceRecord(Metadata.MetadataTransform transform, MethodDesc method, StackTraceRecordFlags flags) { // In the metadata, we only represent the generic definition - MethodDesc methodToGenerateMetadataFor = method.GetTypicalMethodDefinition(); + if ((flags & StackTraceRecordFlags.RuntimeAsync) != 0) + { + method = ((ILCompiler.AsyncResumptionStub)method).TargetMethod; + } + MethodDesc methodToGenerateMetadataFor = method.GetTypicalMethodDefinition(); ConstantStringValue name = (ConstantStringValue)methodToGenerateMetadataFor.GetName(); MetadataRecord signature = transform.HandleMethodSignature(methodToGenerateMetadataFor.Signature); @@ -1349,6 +1360,7 @@ public enum StackTraceRecordFlags None = 0, IsHidden = 1, HasLineNumbers = 2, + RuntimeAsync = 4, } public readonly struct StackTraceRecordData diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/StackTraceEmissionPolicy.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/StackTraceEmissionPolicy.cs index 256d9a81c53230..cbed05a63b80a0 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/StackTraceEmissionPolicy.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/StackTraceEmissionPolicy.cs @@ -45,6 +45,11 @@ public override MethodStackTraceVisibilityFlags GetMethodVisibility(MethodDesc m result |= MethodStackTraceVisibilityFlags.IsHidden; } + if (method is ILCompiler.AsyncResumptionStub asyncStub && asyncStub.TargetMethod.IsAsyncVariant()) + { + result |= MethodStackTraceVisibilityFlags.RuntimeAsync; + } + return method.GetTypicalMethodDefinition() is Internal.TypeSystem.Ecma.EcmaMethod ? result | MethodStackTraceVisibilityFlags.HasMetadata : result; @@ -57,5 +62,6 @@ public enum MethodStackTraceVisibilityFlags HasMetadata = 0x1, IsHidden = 0x2, HasLineNumbers = 0x4, + RuntimeAsync = 0x8, } } diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs index f8a2b4092baf72..6d3b8b689a47c2 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs @@ -987,6 +987,9 @@ private ISymbolNode GetHelperFtnUncached(CorInfoHelpFunc ftnNum) case CorInfoHelpFunc.CORINFO_HELP_RETHROW: id = ReadyToRunHelper.Rethrow; break; + case CorInfoHelpFunc.CORINFO_HELP_THROWEXACT: + id = ReadyToRunHelper.ThrowExact; + break; case CorInfoHelpFunc.CORINFO_HELP_OVERFLOW: id = ReadyToRunHelper.Overflow; break; diff --git a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ReadyToRunSignature.cs b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ReadyToRunSignature.cs index 6cf54e7854c1d5..1b5eb5d6fc0bf7 100644 --- a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ReadyToRunSignature.cs +++ b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ReadyToRunSignature.cs @@ -1639,6 +1639,10 @@ private void ParseHelper(StringBuilder builder) builder.Append("RETHROW"); break; + case ReadyToRunHelper.ThrowExact: + builder.Append("THROW_EXACT"); + break; + case ReadyToRunHelper.Overflow: builder.Append("OVERFLOW"); break; diff --git a/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs b/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs index 4f705b6453141a..7845d8232d86d4 100644 --- a/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs +++ b/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs @@ -467,12 +467,14 @@ private ISymbolNode GetHelperFtnUncached(CorInfoHelpFunc ftnNum) switch (ftnNum) { case CorInfoHelpFunc.CORINFO_HELP_THROW: - case CorInfoHelpFunc.CORINFO_HELP_THROWEXACT: // TODO: (async): THROWEXACT id = ReadyToRunHelper.Throw; break; case CorInfoHelpFunc.CORINFO_HELP_RETHROW: id = ReadyToRunHelper.Rethrow; break; + case CorInfoHelpFunc.CORINFO_HELP_THROWEXACT: // TODO: (async): THROWEXACT + id = ReadyToRunHelper.ThrowExact; + break; case CorInfoHelpFunc.CORINFO_HELP_USER_BREAKPOINT: id = ReadyToRunHelper.DebugBreak; break; From ed850640dfff95a93b6b0ec27c2634887db7a9b1 Mon Sep 17 00:00:00 2001 From: rcj1 Date: Tue, 23 Dec 2025 21:46:00 -0800 Subject: [PATCH 49/51] reduce diff etc --- .../Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs | 1 - .../JitInterface/CorInfoImpl.ReadyToRun.cs | 12 ++++++++++-- .../JitInterface/CorInfoImpl.RyuJit.cs | 2 +- src/coreclr/vm/debugdebugger.cpp | 6 ------ src/coreclr/vm/jitinterface.cpp | 1 - .../sfx/Microsoft.NETCore.App/Directory.Build.props | 2 -- .../Microsoft.NETCore.App.Runtime.props | 1 - .../tests/TestUtilities/System/PlatformDetection.cs | 2 +- .../tests/AsyncAssembly/AsyncAssembly.csproj | 1 + .../tests/AsyncAssembly/Program.cs | 2 +- .../tests/AsyncV1Assembly/Program.cs | 2 +- 11 files changed, 15 insertions(+), 17 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs index 2eca4dedede00c..a91112d2db47ee 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs @@ -564,7 +564,6 @@ private unsafe void DispatchContinuations() return continuation; continuation = continuation.Next; - } } diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs index 6d3b8b689a47c2..addb3a67c196f2 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs @@ -1531,7 +1531,11 @@ private void setVars(CORINFO_METHOD_STRUCT_* ftn, uint cVars, NativeVarInfo* var if (cVars == 0) return; - _debugVarInfos = new Span(vars, (int)cVars).ToArray(); + _debugVarInfos = new NativeVarInfo[cVars]; + for (int i = 0; i < cVars; i++) + { + _debugVarInfos[i] = vars[i]; + } // JIT gave the ownership of this to us, so need to free this. freeArray(vars); @@ -1544,7 +1548,11 @@ private void setBoundaries(CORINFO_METHOD_STRUCT_* ftn, uint cMap, OffsetMapping { Debug.Assert(_debugLocInfos == null); - _debugLocInfos = new Span(pMap, (int)cMap).ToArray(); + _debugLocInfos = new OffsetMapping[cMap]; + for (int i = 0; i < cMap; i++) + { + _debugLocInfos[i] = pMap[i]; + } // JIT gave the ownership of this to us, so need to free this. freeArray(pMap); diff --git a/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs b/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs index 7845d8232d86d4..381b6d0e9b03ba 100644 --- a/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs +++ b/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs @@ -472,7 +472,7 @@ private ISymbolNode GetHelperFtnUncached(CorInfoHelpFunc ftnNum) case CorInfoHelpFunc.CORINFO_HELP_RETHROW: id = ReadyToRunHelper.Rethrow; break; - case CorInfoHelpFunc.CORINFO_HELP_THROWEXACT: // TODO: (async): THROWEXACT + case CorInfoHelpFunc.CORINFO_HELP_THROWEXACT: id = ReadyToRunHelper.ThrowExact; break; case CorInfoHelpFunc.CORINFO_HELP_USER_BREAKPOINT: diff --git a/src/coreclr/vm/debugdebugger.cpp b/src/coreclr/vm/debugdebugger.cpp index fb66e8d24ea270..7fdb0f67d20b5d 100644 --- a/src/coreclr/vm/debugdebugger.cpp +++ b/src/coreclr/vm/debugdebugger.cpp @@ -919,11 +919,6 @@ extern "C" BOOL QCALLTYPE DebugDebugger_IsManagedDebuggerAttached() } #endif // !DACCESS_COMPILE -BYTE* DebugInfoStoreNew2(void * pData, size_t cBytes) -{ - return new BYTE[cBytes]; -} - void DebugStackTrace::GetStackFramesFromException(OBJECTREF * e, GetStackFramesData *pData, PTRARRAYREF * pDynamicMethodArray /*= NULL*/ @@ -1598,7 +1593,6 @@ void ValidateILOffsets(MethodDesc *pFunc, uint8_t* ipColdStart, size_t coldLen, // Initialization done outside the TSL. // This may need to call locking operations that aren't safe under the TSL. - void DebugStackTrace::Element::InitPass2() { CONTRACTL diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index 4b756d87831e13..c1b7cf068f34c4 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -61,7 +61,6 @@ #include "tailcallhelp.h" #include "patchpointinfo.h" -#include "ilstubresolver.h" // The Stack Overflow probe takes place in the COOPERATIVE_TRANSITION_BEGIN() macro // diff --git a/src/installer/pkg/sfx/Microsoft.NETCore.App/Directory.Build.props b/src/installer/pkg/sfx/Microsoft.NETCore.App/Directory.Build.props index 7c1cad4b6110c9..e733a15e13bb8c 100644 --- a/src/installer/pkg/sfx/Microsoft.NETCore.App/Directory.Build.props +++ b/src/installer/pkg/sfx/Microsoft.NETCore.App/Directory.Build.props @@ -105,7 +105,6 @@ - @@ -159,7 +158,6 @@ - diff --git a/src/installer/pkg/sfx/Microsoft.NETCore.App/Microsoft.NETCore.App.Runtime.props b/src/installer/pkg/sfx/Microsoft.NETCore.App/Microsoft.NETCore.App.Runtime.props index 7cc53f3a153a81..db017617784bac 100644 --- a/src/installer/pkg/sfx/Microsoft.NETCore.App/Microsoft.NETCore.App.Runtime.props +++ b/src/installer/pkg/sfx/Microsoft.NETCore.App/Microsoft.NETCore.App.Runtime.props @@ -8,7 +8,6 @@ true true - true The .NET Shared Framework diff --git a/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs b/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs index 259919ec9bf928..d090b6b2057ff1 100644 --- a/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs +++ b/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs @@ -442,7 +442,7 @@ public static bool IsSubstAvailable } } - public static bool IsRuntimeAsyncSupported => !IsCoreClrInterpreter && !IsMonoRuntime && !IsMonoAOT && !IsMonoInterpreter && IsNotNativeAot; + public static bool IsRuntimeAsyncSupported => !IsCoreClrInterpreter && !IsMonoRuntime && !IsMonoAOT && !IsMonoInterpreter; private static Version GetICUVersion() { diff --git a/src/libraries/System.Diagnostics.StackTrace/tests/AsyncAssembly/AsyncAssembly.csproj b/src/libraries/System.Diagnostics.StackTrace/tests/AsyncAssembly/AsyncAssembly.csproj index ef9ecfd4ecbdf8..da2ed04bb7294e 100644 --- a/src/libraries/System.Diagnostics.StackTrace/tests/AsyncAssembly/AsyncAssembly.csproj +++ b/src/libraries/System.Diagnostics.StackTrace/tests/AsyncAssembly/AsyncAssembly.csproj @@ -6,6 +6,7 @@ enable enable true + true $(Features);runtime-async=on $(NoWarn);SYSLIB5007 diff --git a/src/libraries/System.Diagnostics.StackTrace/tests/AsyncAssembly/Program.cs b/src/libraries/System.Diagnostics.StackTrace/tests/AsyncAssembly/Program.cs index 02ce82b3fa8be0..1ee0d9d8f94646 100644 --- a/src/libraries/System.Diagnostics.StackTrace/tests/AsyncAssembly/Program.cs +++ b/src/libraries/System.Diagnostics.StackTrace/tests/AsyncAssembly/Program.cs @@ -126,4 +126,4 @@ private static async Task Quuux2() #line 127 "Program.cs" throw new Exception("Exception from Quuux2"); } -} \ No newline at end of file +} diff --git a/src/libraries/System.Diagnostics.StackTrace/tests/AsyncV1Assembly/Program.cs b/src/libraries/System.Diagnostics.StackTrace/tests/AsyncV1Assembly/Program.cs index f32c6481c32c45..73171d6bbe64a2 100644 --- a/src/libraries/System.Diagnostics.StackTrace/tests/AsyncV1Assembly/Program.cs +++ b/src/libraries/System.Diagnostics.StackTrace/tests/AsyncV1Assembly/Program.cs @@ -27,4 +27,4 @@ public static async Task Test2(int i) Console.WriteLine($"In Test2 with {i}"); throw new NullReferenceException("Exception from Test2"); } -} \ No newline at end of file +} From 54cc8c9e05ae511f81c9f2af0c98f633aa53ffcd Mon Sep 17 00:00:00 2001 From: Adeel <3840695+am11@users.noreply.github.com> Date: Wed, 24 Dec 2025 14:29:45 +0200 Subject: [PATCH 50/51] Dedup asm --- .../Runtime/amd64/ExceptionHandling.S | 93 +++----------- .../Runtime/amd64/ExceptionHandling.asm | 113 +++-------------- .../nativeaot/Runtime/arm/ExceptionHandling.S | 118 +++--------------- .../Runtime/arm64/ExceptionHandling.S | 101 +++------------ .../Runtime/arm64/ExceptionHandling.asm | 103 +++------------ .../Runtime/i386/ExceptionHandling.asm | 86 +++---------- .../Runtime/loongarch64/ExceptionHandling.S | 99 +++------------ .../Runtime/riscv64/ExceptionHandling.S | 92 +++----------- 8 files changed, 135 insertions(+), 670 deletions(-) diff --git a/src/coreclr/nativeaot/Runtime/amd64/ExceptionHandling.S b/src/coreclr/nativeaot/Runtime/amd64/ExceptionHandling.S index fbb68a4b826a3f..1578a4cdcba41b 100644 --- a/src/coreclr/nativeaot/Runtime/amd64/ExceptionHandling.S +++ b/src/coreclr/nativeaot/Runtime/amd64/ExceptionHandling.S @@ -80,96 +80,36 @@ NESTED_END RhpThrowHwEx, _TEXT ////////////////////////////////////////////////////////////////////////////////////////////////////////////// // -// RhpThrowEx +// RhpThrowExact +// +// SUMMARY: Similar to RhpThrowEx, except that it sets the rethrow flag // // INPUT: RDI: exception object // // OUTPUT: // ////////////////////////////////////////////////////////////////////////////////////////////////////////////// -NESTED_ENTRY RhpThrowEx, _TEXT, NoHandler - - STACKSIZEOF_ExInfo = ((SIZEOF__ExInfo + 15) & (~ 15)) - rsp_offsetof_Context = STACKSIZEOF_ExInfo - - lea rax, [rsp+8] // save the RSP of the throw site - mov rsi, [rsp] // get return address - - xor rdx, rdx - push_register rdx // padding - -// struct PAL_LIMITED_CONTEXT -// { - push_nonvol_reg r15 - push_nonvol_reg r14 - push_nonvol_reg r13 - push_nonvol_reg r12 - push_register rdx // rdx set to 0 - push_nonvol_reg rbx - push_register rdx // rax set to 0 - push_nonvol_reg rbp - push_register rax // 'faulting' RSP - push_register rsi // 'faulting' IP -// } - - // allocate space for the ExInfo - alloc_stack STACKSIZEOF_ExInfo - - END_PROLOGUE - - mov rbx, rdi - INLINE_GETTHREAD - mov rdi, rbx - - lea rbx, [rsp + rsp_offsetof_Context + SIZEOF__PAL_LIMITED_CONTEXT + 0x8] // rbx <- addr of return address - - // There is runtime C# code that can tail call to RhpThrowEx using a binder intrinsic. So the return - // address could have been hijacked when we were in that C# code and we must remove the hijack and - // reflect the correct return address in our exception context record. The other throw helpers don't - // need this because they cannot be tail-called from C#. - INLINE_THREAD_UNHIJACK rax, rcx, rsi // trashes RCX, RSI - mov rsi, [rbx] // rdx <- return address - mov [rsp + rsp_offsetof_Context + OFFSETOF__PAL_LIMITED_CONTEXT__IP], rsi // set 'faulting' IP after unhijack - - mov rsi, rsp // rsi <- ExInfo* - - mov [rsi + OFFSETOF__ExInfo__m_exception], rdx // init the exception object to null - mov byte ptr [rsi + OFFSETOF__ExInfo__m_passNumber], 1 // init to the first pass - mov dword ptr [rsi + OFFSETOF__ExInfo__m_idxCurClause], 0xFFFFFFFF - mov byte ptr [rsi + OFFSETOF__ExInfo__m_kind], 1 // ExKind.Throw - - // link the ExInfo into the thread's ExInfo chain - mov rdx, [rax + OFFSETOF__Thread__m_pExInfoStackHead] - mov [rsi + OFFSETOF__ExInfo__m_pPrevExInfo], rdx // pExInfo->m_pPrevExInfo = m_pExInfoStackHead - mov [rax + OFFSETOF__Thread__m_pExInfoStackHead], rsi // m_pExInfoStackHead = pExInfo - - // set the exception context field on the ExInfo - lea rdx, [rsp + rsp_offsetof_Context] // rdx <- PAL_LIMITED_CONTEXT* - mov [rsi + OFFSETOF__ExInfo__m_pExContext], rdx // init ExInfo.m_pExContext - - // rdi still contains the exception object - // rsi contains the address of the ExInfo - call EXTERNAL_C_FUNC(RhThrowEx) +NESTED_ENTRY RhpThrowExact, _TEXT, NoHandler -ALTERNATE_ENTRY RhpThrowEx2 + mov r8d, 4 // r8d = ExKind.RethrowFlag + jmp LOCAL_LABEL(RhpThrowExImpl) - // no return - int 3 - -NESTED_END RhpThrowEx, _TEXT +NESTED_END RhpThrowExact, _TEXT ////////////////////////////////////////////////////////////////////////////////////////////////////////////// // -// RhpThrowExact -// -// SUMMARY: Similar to RhpThrowEx, except that it sets the rethrow flag +// RhpThrowEx // // INPUT: RDI: exception object // // OUTPUT: // ////////////////////////////////////////////////////////////////////////////////////////////////////////////// -NESTED_ENTRY RhpThrowExact, _TEXT, NoHandler +NESTED_ENTRY RhpThrowEx, _TEXT, NoHandler + + mov r8d, 1 // r8d = ExKind.Throw + +LOCAL_LABEL(RhpThrowExImpl): STACKSIZEOF_ExInfo = ((SIZEOF__ExInfo + 15) & (~ 15)) rsp_offsetof_Context = STACKSIZEOF_ExInfo @@ -205,7 +145,7 @@ NESTED_ENTRY RhpThrowExact, _TEXT, NoHandler lea rbx, [rsp + rsp_offsetof_Context + SIZEOF__PAL_LIMITED_CONTEXT + 0x8] // rbx <- addr of return address - // There is runtime C# code that can tail call to RhpThrowExact using a binder intrinsic. So the return + // There is runtime C# code that can tail call to RhpThrowEx using a binder intrinsic. So the return // address could have been hijacked when we were in that C# code and we must remove the hijack and // reflect the correct return address in our exception context record. The other throw helpers don't // need this because they cannot be tail-called from C#. @@ -218,7 +158,7 @@ NESTED_ENTRY RhpThrowExact, _TEXT, NoHandler mov [rsi + OFFSETOF__ExInfo__m_exception], rdx // init the exception object to null mov byte ptr [rsi + OFFSETOF__ExInfo__m_passNumber], 1 // init to the first pass mov dword ptr [rsi + OFFSETOF__ExInfo__m_idxCurClause], 0xFFFFFFFF - mov byte ptr [rsi + OFFSETOF__ExInfo__m_kind], 4 // ExKind.RethrowFlag + mov byte ptr [rsi + OFFSETOF__ExInfo__m_kind], r8b // ExKind (from r8b) // link the ExInfo into the thread's ExInfo chain mov rdx, [rax + OFFSETOF__Thread__m_pExInfoStackHead] @@ -233,12 +173,13 @@ NESTED_ENTRY RhpThrowExact, _TEXT, NoHandler // rsi contains the address of the ExInfo call EXTERNAL_C_FUNC(RhThrowEx) +ALTERNATE_ENTRY RhpThrowEx2 ALTERNATE_ENTRY RhpThrowExact2 // no return int 3 -NESTED_END RhpThrowExact, _TEXT +NESTED_END RhpThrowEx, _TEXT ////////////////////////////////////////////////////////////////////////////////////////////////////////////// // diff --git a/src/coreclr/nativeaot/Runtime/amd64/ExceptionHandling.asm b/src/coreclr/nativeaot/Runtime/amd64/ExceptionHandling.asm index 3516a3271edd89..05b192a796d5ec 100644 --- a/src/coreclr/nativeaot/Runtime/amd64/ExceptionHandling.asm +++ b/src/coreclr/nativeaot/Runtime/amd64/ExceptionHandling.asm @@ -107,114 +107,36 @@ NESTED_END RhpThrowHwEx, _TEXT ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; -;; RhpThrowEx +;; RhpThrowExact +;; +;; SUMMARY: Similar to RhpThrowEx, except that it sets the rethrow flag ;; ;; INPUT: RCX: exception object ;; ;; OUTPUT: ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -NESTED_ENTRY RhpThrowEx, _TEXT - - SIZEOF_XmmSaves equ SIZEOF__PAL_LIMITED_CONTEXT - OFFSETOF__PAL_LIMITED_CONTEXT__Xmm6 - STACKSIZEOF_ExInfo equ ((SIZEOF__ExInfo + 15) AND (NOT 15)) - - SIZEOF_OutgoingScratch equ 20h - rsp_offsetof_ExInfo equ SIZEOF_OutgoingScratch - rsp_offsetof_Context equ SIZEOF_OutgoingScratch + STACKSIZEOF_ExInfo - - lea rax, [rsp+8] ;; save the RSP of the throw site - mov rdx, [rsp] ;; get return address - - xor r8, r8 - - alloc_stack SIZEOF_XmmSaves + 8h ;; reserve stack for the xmm saves (+8h to realign stack) - rdsspq r8 ;; nop if SSP is not implemented, 0 if not enabled - test r8, r8 - je @f - add r8, 8 ;; Move SSP to match RSP of the throw site - @@: - push_vol_reg r8 ;; SSP - xor r8, r8 - push_nonvol_reg r15 - push_nonvol_reg r14 - push_nonvol_reg r13 - push_nonvol_reg r12 - push_nonvol_reg rbx - push_vol_reg r8 - push_nonvol_reg rsi - push_nonvol_reg rdi - push_nonvol_reg rbp - push_vol_reg rax ;; 'faulting' RSP - push_vol_reg rdx ;; 'faulting' IP - - ;; allocate outgoing args area and space for the ExInfo - alloc_stack SIZEOF_OutgoingScratch + STACKSIZEOF_ExInfo - - save_xmm128_postrsp Xmm6 , rsp_offsetof_Context + OFFSETOF__PAL_LIMITED_CONTEXT__Xmm6 - save_xmm128_postrsp Xmm7 , rsp_offsetof_Context + OFFSETOF__PAL_LIMITED_CONTEXT__Xmm7 - save_xmm128_postrsp Xmm8 , rsp_offsetof_Context + OFFSETOF__PAL_LIMITED_CONTEXT__Xmm8 - save_xmm128_postrsp Xmm9 , rsp_offsetof_Context + OFFSETOF__PAL_LIMITED_CONTEXT__Xmm9 - save_xmm128_postrsp Xmm10, rsp_offsetof_Context + OFFSETOF__PAL_LIMITED_CONTEXT__Xmm10 - save_xmm128_postrsp Xmm11, rsp_offsetof_Context + OFFSETOF__PAL_LIMITED_CONTEXT__Xmm11 - save_xmm128_postrsp Xmm12, rsp_offsetof_Context + OFFSETOF__PAL_LIMITED_CONTEXT__Xmm12 - save_xmm128_postrsp Xmm13, rsp_offsetof_Context + OFFSETOF__PAL_LIMITED_CONTEXT__Xmm13 - save_xmm128_postrsp Xmm14, rsp_offsetof_Context + OFFSETOF__PAL_LIMITED_CONTEXT__Xmm14 - save_xmm128_postrsp Xmm15, rsp_offsetof_Context + OFFSETOF__PAL_LIMITED_CONTEXT__Xmm15 - - END_PROLOGUE - - INLINE_GETTHREAD rax, rbx ;; rax <- Thread*, rbx is trashed - - lea rbx, [rsp + rsp_offsetof_Context + SIZEOF__PAL_LIMITED_CONTEXT + 8h] ;; rbx <- addr of return address - - ;; There is runtime C# code that can tail call to RhpThrowEx using a binder intrinsic. So the return - ;; address could have been hijacked when we were in that C# code and we must remove the hijack and - ;; reflect the correct return address in our exception context record. The other throw helpers don't - ;; need this because they cannot be tail-called from C#. - INLINE_THREAD_UNHIJACK rax, r9, rdx ;; trashes R9, RDX - mov rdx, [rbx] ;; rdx <- return address - mov [rsp + rsp_offsetof_Context + OFFSETOF__PAL_LIMITED_CONTEXT__IP], rdx ;; set 'faulting' IP after unhijack - - lea rdx, [rsp + rsp_offsetof_ExInfo] ;; rdx <- ExInfo* - - mov [rdx + OFFSETOF__ExInfo__m_exception], r8 ;; init the exception object to null - mov byte ptr [rdx + OFFSETOF__ExInfo__m_passNumber], 1 ;; init to the first pass - mov dword ptr [rdx + OFFSETOF__ExInfo__m_idxCurClause], 0FFFFFFFFh - mov byte ptr [rdx + OFFSETOF__ExInfo__m_kind], 1 ;; ExKind.Throw - - ;; link the ExInfo into the thread's ExInfo chain - mov r8, [rax + OFFSETOF__Thread__m_pExInfoStackHead] - mov [rdx + OFFSETOF__ExInfo__m_pPrevExInfo], r8 ;; pExInfo->m_pPrevExInfo = m_pExInfoStackHead - mov [rax + OFFSETOF__Thread__m_pExInfoStackHead], rdx ;; m_pExInfoStackHead = pExInfo - - ;; set the exception context field on the ExInfo - lea r8, [rsp + rsp_offsetof_Context] ;; r8 <- PAL_LIMITED_CONTEXT* - mov [rdx + OFFSETOF__ExInfo__m_pExContext], r8 ;; init ExInfo.m_pExContext - - ;; rcx still contains the exception object - ;; rdx contains the address of the ExInfo - call RhThrowEx - -ALTERNATE_ENTRY RhpThrowEx2 +NESTED_ENTRY RhpThrowExact, _TEXT - ;; no return - int 3 + mov r9d, 4 ;; r9d = ExKind.RethrowFlag + jmp RhpThrowExImpl -NESTED_END RhpThrowEx, _TEXT +NESTED_END RhpThrowExact, _TEXT ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; -;; RhpThrowExact -;; -;; SUMMARY: Similar to RhpThrowEx, except that it sets the rethrow flag +;; RhpThrowEx ;; ;; INPUT: RCX: exception object ;; ;; OUTPUT: ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -NESTED_ENTRY RhpThrowExact, _TEXT +NESTED_ENTRY RhpThrowEx, _TEXT + + mov r9d, 1 ;; r9d = ExKind.Throw + +ALTERNATE_ENTRY RhpThrowExImpl SIZEOF_XmmSaves equ SIZEOF__PAL_LIMITED_CONTEXT - OFFSETOF__PAL_LIMITED_CONTEXT__Xmm6 STACKSIZEOF_ExInfo equ ((SIZEOF__ExInfo + 15) AND (NOT 15)) @@ -268,11 +190,11 @@ NESTED_ENTRY RhpThrowExact, _TEXT lea rbx, [rsp + rsp_offsetof_Context + SIZEOF__PAL_LIMITED_CONTEXT + 8h] ;; rbx <- addr of return address - ;; There is runtime C# code that can tail call to RhpThrowExact using a binder intrinsic. So the return + ;; There is runtime C# code that can tail call to RhpThrowEx using a binder intrinsic. So the return ;; address could have been hijacked when we were in that C# code and we must remove the hijack and ;; reflect the correct return address in our exception context record. The other throw helpers don't ;; need this because they cannot be tail-called from C#. - INLINE_THREAD_UNHIJACK rax, r9, rdx ;; trashes R9, RDX + INLINE_THREAD_UNHIJACK rax, r10, rdx ;; trashes R10, RDX (use r10 instead of r9 to preserve ExKind) mov rdx, [rbx] ;; rdx <- return address mov [rsp + rsp_offsetof_Context + OFFSETOF__PAL_LIMITED_CONTEXT__IP], rdx ;; set 'faulting' IP after unhijack @@ -281,7 +203,7 @@ NESTED_ENTRY RhpThrowExact, _TEXT mov [rdx + OFFSETOF__ExInfo__m_exception], r8 ;; init the exception object to null mov byte ptr [rdx + OFFSETOF__ExInfo__m_passNumber], 1 ;; init to the first pass mov dword ptr [rdx + OFFSETOF__ExInfo__m_idxCurClause], 0FFFFFFFFh - mov byte ptr [rdx + OFFSETOF__ExInfo__m_kind], 4 ;; ExKind.RethrowFlag + mov byte ptr [rdx + OFFSETOF__ExInfo__m_kind], r9b ;; ExKind (from r9b) ;; link the ExInfo into the thread's ExInfo chain mov r8, [rax + OFFSETOF__Thread__m_pExInfoStackHead] @@ -296,12 +218,13 @@ NESTED_ENTRY RhpThrowExact, _TEXT ;; rdx contains the address of the ExInfo call RhThrowEx +ALTERNATE_ENTRY RhpThrowEx2 ALTERNATE_ENTRY RhpThrowExact2 ;; no return int 3 -NESTED_END RhpThrowExact, _TEXT +NESTED_END RhpThrowEx, _TEXT ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; diff --git a/src/coreclr/nativeaot/Runtime/arm/ExceptionHandling.S b/src/coreclr/nativeaot/Runtime/arm/ExceptionHandling.S index e7835386b0fcc2..e90606f752f0f2 100644 --- a/src/coreclr/nativeaot/Runtime/arm/ExceptionHandling.S +++ b/src/coreclr/nativeaot/Runtime/arm/ExceptionHandling.S @@ -81,120 +81,36 @@ NESTED_END RhpThrowHwEx ////////////////////////////////////////////////////////////////////////////////////////////////////////////// // -// RhpThrowEx +// RhpThrowExact +// +// SUMMARY: Similar to RhpThrowEx, except that it sets the rethrow flag // // INPUT: R0: exception object // // OUTPUT: // ////////////////////////////////////////////////////////////////////////////////////////////////////////////// -NESTED_ENTRY RhpThrowEx, _TEXT, NoHandler - - // Setup a PAL_LIMITED_CONTEXT on the stack { - PROLOG_VPUSH {d8-d15} - PROLOG_PUSH "{r0,lr}" // Reserve space for SP and store LR - PROLOG_PUSH "{r0,r4-r11,lr}" - // } end PAL_LIMITED_CONTEXT - - PROLOG_STACK_ALLOC STACKSIZEOF_ExInfo - - // Calculate SP at callsite and save into the PAL_LIMITED_CONTEXT - add r4, sp, #(STACKSIZEOF_ExInfo + SIZEOF__PAL_LIMITED_CONTEXT) - str r4, [sp, #(rsp_offsetof_Context + OFFSETOF__PAL_LIMITED_CONTEXT__SP)] - - mov r4, r0 // Save exception object - // r0 = GetThread() - INLINE_GETTHREAD - - add r2, sp, #(rsp_offsetof_Context + SIZEOF__PAL_LIMITED_CONTEXT + 0x8) // r2 <- addr of return address - - // There is runtime C# code that can tail call to RhpThrowEx using a binder intrinsic. So the return - // address could have been hijacked when we were in that C# code and we must remove the hijack and - // reflect the correct return address in our exception context record. The other throw helpers don't - // need this because they cannot be tail-called from C#. - // NOTE: we cannot use INLINE_THREAD_UNHIJACK because it will write into the stack at the location - // where the tail-calling thread had saved LR, which may not match where we have saved LR. - - ldr r1, [r0, #OFFSETOF__Thread__m_pvHijackedReturnAddress] - cbz r1, LOCAL_LABEL(NotHiJacked) - - ldr r3, [r0, #OFFSETOF__Thread__m_ppvHijackedReturnAddressLocation] - - // r4: exception object - // r1: hijacked return address - // r0: pThread - // r3: hijacked return address location - - add r12, sp, #(STACKSIZEOF_ExInfo + SIZEOF__PAL_LIMITED_CONTEXT) // re-compute SP at callsite - cmp r3, r12 // if (m_ppvHijackedReturnAddressLocation < SP at callsite) - blo LOCAL_LABEL(TailCallWasHijacked) - - // normal case where a valid return address location is hijacked - str r1, [r3] - b LOCAL_LABEL(ClearThreadState) - -LOCAL_LABEL(TailCallWasHijacked): - - // Abnormal case where the return address location is now invalid because we ended up here via a tail - // call. In this case, our hijacked return address should be the correct caller of this method. - // - - // stick the previous return address in LR as well as in the right spots in our PAL_LIMITED_CONTEXT. - mov lr, r1 - str lr, [sp, #(rsp_offsetof_Context + OFFSETOF__PAL_LIMITED_CONTEXT__LR)] - str lr, [sp, #(rsp_offsetof_Context + OFFSETOF__PAL_LIMITED_CONTEXT__IP)] - -LOCAL_LABEL(ClearThreadState): - - // clear the Thread's hijack state - mov r3, #0 - str r3, [r0, #OFFSETOF__Thread__m_ppvHijackedReturnAddressLocation] - str r3, [r0, #OFFSETOF__Thread__m_pvHijackedReturnAddress] - -LOCAL_LABEL(NotHiJacked): - - add r1, sp, #rsp_offsetof_ExInfo // r1 <- ExInfo* - mov r3, #0 - str r3, [r1, #OFFSETOF__ExInfo__m_exception] // init the exception object to null - mov r3, #1 - strb r3, [r1, #OFFSETOF__ExInfo__m_passNumber] // init to the first pass - strb r3, [r1, #OFFSETOF__ExInfo__m_kind] - mov r3, #0xFFFFFFFF - str r3, [r1, #OFFSETOF__ExInfo__m_idxCurClause] // ExKind.Throw - - // link the ExInfo into the thread's ExInfo chain - ldr r3, [r0, #OFFSETOF__Thread__m_pExInfoStackHead] - str r3, [r1, #OFFSETOF__ExInfo__m_pPrevExInfo] // pExInfo->m_pPrevExInfo = m_pExInfoStackHead - str r1, [r0, #OFFSETOF__Thread__m_pExInfoStackHead] // m_pExInfoStackHead = pExInfo - - // set the exception context field on the ExInfo - add r3, sp, #rsp_offsetof_Context // r3 <- PAL_LIMITED_CONTEXT* - str r3, [r1, #OFFSETOF__ExInfo__m_pExContext] // init ExInfo.m_pExContext - - mov r0, r4 // Restore exception object - // r0 contains the exception object - // r1 contains the address of the new ExInfo - bl C_FUNC(RhThrowEx) +NESTED_ENTRY RhpThrowExact, _TEXT, NoHandler -GLOBAL_LABEL RhpThrowEx2 + mov r5, #4 // r5 = ExKind.RethrowFlag + b LOCAL_LABEL(RhpThrowExImpl) - // no return - EMIT_BREAKPOINT - -NESTED_END RhpThrowEx, _TEXT +NESTED_END RhpThrowExact, _TEXT ////////////////////////////////////////////////////////////////////////////////////////////////////////////// // -// RhpThrowExact -// -// SUMMARY: Similar to RhpThrowEx, except that it sets the rethrow flag +// RhpThrowEx // // INPUT: R0: exception object // // OUTPUT: // ////////////////////////////////////////////////////////////////////////////////////////////////////////////// -NESTED_ENTRY RhpThrowExact, _TEXT, NoHandler +NESTED_ENTRY RhpThrowEx, _TEXT, NoHandler + + mov r5, #1 // r5 = ExKind.Throw + +LOCAL_LABEL(RhpThrowExImpl): // Setup a PAL_LIMITED_CONTEXT on the stack { PROLOG_VPUSH {d8-d15} @@ -214,7 +130,7 @@ NESTED_ENTRY RhpThrowExact, _TEXT, NoHandler add r2, sp, #(rsp_offsetof_Context + SIZEOF__PAL_LIMITED_CONTEXT + 0x8) // r2 <- addr of return address - // There is runtime C# code that can tail call to RhpThrowExact using a binder intrinsic. So the return + // There is runtime C# code that can tail call to RhpThrowEx using a binder intrinsic. So the return // address could have been hijacked when we were in that C# code and we must remove the hijack and // reflect the correct return address in our exception context record. The other throw helpers don't // need this because they cannot be tail-called from C#. @@ -264,8 +180,7 @@ LOCAL_LABEL(NotHiJacked): str r3, [r1, #OFFSETOF__ExInfo__m_exception] // init the exception object to null mov r3, #1 strb r3, [r1, #OFFSETOF__ExInfo__m_passNumber] // init to the first pass - mov r3, #4 - strb r3, [r1, #OFFSETOF__ExInfo__m_kind] // ExKind.RethrowFlag + strb r5, [r1, #OFFSETOF__ExInfo__m_kind] // ExKind (from r5) mov r3, #0xFFFFFFFF str r3, [r1, #OFFSETOF__ExInfo__m_idxCurClause] @@ -283,12 +198,13 @@ LOCAL_LABEL(NotHiJacked): // r1 contains the address of the new ExInfo bl C_FUNC(RhThrowEx) +GLOBAL_LABEL RhpThrowEx2 GLOBAL_LABEL RhpThrowExact2 // no return EMIT_BREAKPOINT -NESTED_END RhpThrowExact, _TEXT +NESTED_END RhpThrowEx, _TEXT ////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/src/coreclr/nativeaot/Runtime/arm64/ExceptionHandling.S b/src/coreclr/nativeaot/Runtime/arm64/ExceptionHandling.S index 95ae9c42e2f2bf..643bc934a62c0d 100644 --- a/src/coreclr/nativeaot/Runtime/arm64/ExceptionHandling.S +++ b/src/coreclr/nativeaot/Runtime/arm64/ExceptionHandling.S @@ -253,108 +253,41 @@ NESTED_END RhpThrowHwEx, _TEXT // -// RhpThrowEx +// RhpThrowExact +// +// SUMMARY: Similar to RhpThrowEx, except that it sets the rethrow flag // // INPUT: X0: exception object // // OUTPUT: // - NESTED_ENTRY RhpThrowEx, _TEXT, NoHandler - - ALLOC_THROW_FRAME SOFTWARE_EXCEPTION - - GetThreadX2 - - // There is runtime C# code that can tail call to RhpThrowEx using a binder intrinsic. So the return - // address could have been hijacked when we were in that C# code and we must remove the hijack and - // reflect the correct return address in our exception context record. The other throw helpers don't - // need this because they cannot be tail-called from C#. - - // NOTE: we cannot use INLINE_THREAD_UNHIJACK because it will write into the stack at the location - // where the tail-calling thread had saved LR, which may not match where we have saved LR. - - ldr x1, [x2, #OFFSETOF__Thread__m_pvHijackedReturnAddress] - cbz x1, LOCAL_LABEL(NotHijacked) - - ldr x3, [x2, #OFFSETOF__Thread__m_ppvHijackedReturnAddressLocation] - - // x0: exception object - // x1: hijacked return address - // x2: pThread - // x3: hijacked return address location - - add x12, sp, #(STACKSIZEOF_ExInfo + SIZEOF__PAL_LIMITED_CONTEXT) // re-compute SP at callsite - cmp x3, x12 // if (m_ppvHijackedReturnAddressLocation < SP at callsite) - blo LOCAL_LABEL(TailCallWasHijacked) - - // normal case where a valid return address location is hijacked - str x1, [x3] - b LOCAL_LABEL(ClearThreadState) - -LOCAL_LABEL(TailCallWasHijacked): - - // Abnormal case where the return address location is now invalid because we ended up here via a tail - // call. In this case, our hijacked return address should be the correct caller of this method. - - // stick the previous return address in LR as well as in the right spots in our PAL_LIMITED_CONTEXT. - mov lr, x1 - str lr, [sp, #(rsp_offsetof_Context + OFFSETOF__PAL_LIMITED_CONTEXT__LR)] - str lr, [sp, #(rsp_offsetof_Context + OFFSETOF__PAL_LIMITED_CONTEXT__IP)] - -LOCAL_LABEL(ClearThreadState): - - // clear the Thread's hijack state - str xzr, [x2, #OFFSETOF__Thread__m_ppvHijackedReturnAddressLocation] - str xzr, [x2, #OFFSETOF__Thread__m_pvHijackedReturnAddress] - -LOCAL_LABEL(NotHijacked): - - add x1, sp, #rsp_offsetof_ExInfo // x1 <- ExInfo* - str xzr, [x1, #OFFSETOF__ExInfo__m_exception] // pExInfo->m_exception = null - mov w3, #1 - strb w3, [x1, #OFFSETOF__ExInfo__m_passNumber] // pExInfo->m_passNumber = 1 - mov w3, #0xFFFFFFFF - str w3, [x1, #OFFSETOF__ExInfo__m_idxCurClause] // pExInfo->m_idxCurClause = MaxTryRegionIdx - mov w3, #1 - strb w3, [x1, #OFFSETOF__ExInfo__m_kind] // pExInfo->m_kind = ExKind.Throw - - // link the ExInfo into the thread's ExInfo chain - ldr x3, [x2, #OFFSETOF__Thread__m_pExInfoStackHead] - str x3, [x1, #OFFSETOF__ExInfo__m_pPrevExInfo] // pExInfo->m_pPrevExInfo = m_pExInfoStackHead - str x1, [x2, #OFFSETOF__Thread__m_pExInfoStackHead] // m_pExInfoStackHead = pExInfo - - // set the exception context field on the ExInfo - add x2, sp, #rsp_offsetof_Context // x2 <- PAL_LIMITED_CONTEXT* - str x2, [x1, #OFFSETOF__ExInfo__m_pExContext] // pExInfo->m_pExContext = pContext - - // x0: exception object - // x1: ExInfo* - bl C_FUNC(RhThrowEx) + NESTED_ENTRY RhpThrowExact, _TEXT, NoHandler - ALTERNATE_ENTRY RhpThrowEx2 + mov w4, #4 // w4 = ExKind.RethrowFlag + b LOCAL_LABEL(RhpThrowExImpl) - // no return - EMIT_BREAKPOINT - NESTED_END RhpThrowEx, _TEXT + NESTED_END RhpThrowExact, _TEXT // -// RhpThrowExact -// -// SUMMARY: Similar to RhpThrowEx, except that it sets the rethrow flag +// RhpThrowEx // // INPUT: X0: exception object // // OUTPUT: // - NESTED_ENTRY RhpThrowExact, _TEXT, NoHandler + NESTED_ENTRY RhpThrowEx, _TEXT, NoHandler + + mov w4, #1 // w4 = ExKind.Throw + +LOCAL_LABEL(RhpThrowExImpl): ALLOC_THROW_FRAME SOFTWARE_EXCEPTION GetThreadX2 - // There is runtime C# code that can tail call to RhpThrowExact using a binder intrinsic. So the return + // There is runtime C# code that can tail call to RhpThrowEx using a binder intrinsic. So the return // address could have been hijacked when we were in that C# code and we must remove the hijack and // reflect the correct return address in our exception context record. The other throw helpers don't // need this because they cannot be tail-called from C#. @@ -404,8 +337,7 @@ LOCAL_LABEL(NotHijacked): strb w3, [x1, #OFFSETOF__ExInfo__m_passNumber] // pExInfo->m_passNumber = 1 mov w3, #0xFFFFFFFF str w3, [x1, #OFFSETOF__ExInfo__m_idxCurClause] // pExInfo->m_idxCurClause = MaxTryRegionIdx - mov w3, #4 - strb w3, [x1, #OFFSETOF__ExInfo__m_kind] // pExInfo->m_kind = ExKind.RethrowFlag + strb w4, [x1, #OFFSETOF__ExInfo__m_kind] // pExInfo->m_kind = ExKind (from w4) // link the ExInfo into the thread's ExInfo chain ldr x3, [x2, #OFFSETOF__Thread__m_pExInfoStackHead] @@ -420,11 +352,12 @@ LOCAL_LABEL(NotHijacked): // x1: ExInfo* bl C_FUNC(RhThrowEx) + ALTERNATE_ENTRY RhpThrowEx2 ALTERNATE_ENTRY RhpThrowExact2 // no return EMIT_BREAKPOINT - NESTED_END RhpThrowExact, _TEXT + NESTED_END RhpThrowEx, _TEXT // // void FASTCALL RhpRethrow() diff --git a/src/coreclr/nativeaot/Runtime/arm64/ExceptionHandling.asm b/src/coreclr/nativeaot/Runtime/arm64/ExceptionHandling.asm index 514d97a77265b5..13824b07f4d4e9 100644 --- a/src/coreclr/nativeaot/Runtime/arm64/ExceptionHandling.asm +++ b/src/coreclr/nativeaot/Runtime/arm64/ExceptionHandling.asm @@ -252,112 +252,43 @@ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; -;; RhpThrowEx +;; RhpThrowExact +;; +;; SUMMARY: Similar to RhpThrowEx, except that it sets the rethrow flag ;; ;; INPUT: X0: exception object ;; ;; OUTPUT: ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - NESTED_ENTRY RhpThrowEx - - ALLOC_THROW_FRAME SOFTWARE_EXCEPTION - - ;; x2 = GetThread(), TRASHES x1 - INLINE_GETTHREAD x2, x1 - - ;; There is runtime C# code that can tail call to RhpThrowEx using a binder intrinsic. So the return - ;; address could have been hijacked when we were in that C# code and we must remove the hijack and - ;; reflect the correct return address in our exception context record. The other throw helpers don't - ;; need this because they cannot be tail-called from C#. - - ;; NOTE: we cannot use INLINE_THREAD_UNHIJACK because it will write into the stack at the location - ;; where the tail-calling thread had saved LR, which may not match where we have saved LR. - - ldr x1, [x2, #OFFSETOF__Thread__m_pvHijackedReturnAddress] - cbz x1, NotHijacked - - ldr x3, [x2, #OFFSETOF__Thread__m_ppvHijackedReturnAddressLocation] - - ;; x0: exception object - ;; x1: hijacked return address - ;; x2: pThread - ;; x3: hijacked return address location - - add x12, sp, #(STACKSIZEOF_ExInfo + SIZEOF__PAL_LIMITED_CONTEXT) ;; re-compute SP at callsite - cmp x3, x12 ;; if (m_ppvHijackedReturnAddressLocation < SP at callsite) - blo TailCallWasHijacked - - ;; normal case where a valid return address location is hijacked - str x1, [x3] - b ClearThreadState - -TailCallWasHijacked - - ;; Abnormal case where the return address location is now invalid because we ended up here via a tail - ;; call. In this case, our hijacked return address should be the correct caller of this method. - ;; - - ;; stick the previous return address in LR as well as in the right spots in our PAL_LIMITED_CONTEXT. - mov lr, x1 - str lr, [sp, #(rsp_offsetof_Context + OFFSETOF__PAL_LIMITED_CONTEXT__LR)] - str lr, [sp, #(rsp_offsetof_Context + OFFSETOF__PAL_LIMITED_CONTEXT__IP)] - -ClearThreadState - - ;; clear the Thread's hijack state - str xzr, [x2, #OFFSETOF__Thread__m_ppvHijackedReturnAddressLocation] - str xzr, [x2, #OFFSETOF__Thread__m_pvHijackedReturnAddress] - -NotHijacked - - add x1, sp, #rsp_offsetof_ExInfo ;; x1 <- ExInfo* - str xzr, [x1, #OFFSETOF__ExInfo__m_exception] ;; pExInfo->m_exception = null - mov w3, #1 - strb w3, [x1, #OFFSETOF__ExInfo__m_passNumber] ;; pExInfo->m_passNumber = 1 - mov w3, #0xFFFFFFFF - str w3, [x1, #OFFSETOF__ExInfo__m_idxCurClause] ;; pExInfo->m_idxCurClause = MaxTryRegionIdx - mov w3, #1 - strb w3, [x1, #OFFSETOF__ExInfo__m_kind] ;; pExInfo->m_kind = ExKind.Throw - - ;; link the ExInfo into the thread's ExInfo chain - ldr x3, [x2, #OFFSETOF__Thread__m_pExInfoStackHead] - str x3, [x1, #OFFSETOF__ExInfo__m_pPrevExInfo] ;; pExInfo->m_pPrevExInfo = m_pExInfoStackHead - str x1, [x2, #OFFSETOF__Thread__m_pExInfoStackHead] ;; m_pExInfoStackHead = pExInfo - - ;; set the exception context field on the ExInfo - add x2, sp, #rsp_offsetof_Context ;; x2 <- PAL_LIMITED_CONTEXT* - str x2, [x1, #OFFSETOF__ExInfo__m_pExContext] ;; pExInfo->m_pExContext = pContext - - ;; x0: exception object - ;; x1: ExInfo* - bl RhThrowEx + NESTED_ENTRY RhpThrowExact - ALTERNATE_ENTRY RhpThrowEx2 + mov w4, #4 ;; w4 = ExKind.RethrowFlag + b RhpThrowExImpl - ;; no return - EMIT_BREAKPOINT - NESTED_END RhpThrowEx + NESTED_END RhpThrowExact ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; -;; RhpThrowExact -;; -;; SUMMARY: Similar to RhpThrowEx, except that it sets the rethrow flag +;; RhpThrowEx ;; ;; INPUT: X0: exception object ;; ;; OUTPUT: ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - NESTED_ENTRY RhpThrowExact + NESTED_ENTRY RhpThrowEx + + mov w4, #1 ;; w4 = ExKind.Throw + + ALTERNATE_ENTRY RhpThrowExImpl ALLOC_THROW_FRAME SOFTWARE_EXCEPTION ;; x2 = GetThread(), TRASHES x1 INLINE_GETTHREAD x2, x1 - ;; There is runtime C# code that can tail call to RhpThrowExact using a binder intrinsic. So the return + ;; There is runtime C# code that can tail call to RhpThrowEx using a binder intrinsic. So the return ;; address could have been hijacked when we were in that C# code and we must remove the hijack and ;; reflect the correct return address in our exception context record. The other throw helpers don't ;; need this because they cannot be tail-called from C#. @@ -408,8 +339,7 @@ NotHijacked strb w3, [x1, #OFFSETOF__ExInfo__m_passNumber] ;; pExInfo->m_passNumber = 1 mov w3, #0xFFFFFFFF str w3, [x1, #OFFSETOF__ExInfo__m_idxCurClause] ;; pExInfo->m_idxCurClause = MaxTryRegionIdx - mov w3, #4 - strb w3, [x1, #OFFSETOF__ExInfo__m_kind] ;; pExInfo->m_kind = ExKind.RethrowFlag + strb w4, [x1, #OFFSETOF__ExInfo__m_kind] ;; pExInfo->m_kind = ExKind (from w4) ;; link the ExInfo into the thread's ExInfo chain ldr x3, [x2, #OFFSETOF__Thread__m_pExInfoStackHead] @@ -424,11 +354,12 @@ NotHijacked ;; x1: ExInfo* bl RhThrowEx + ALTERNATE_ENTRY RhpThrowEx2 ALTERNATE_ENTRY RhpThrowExact2 ;; no return EMIT_BREAKPOINT - NESTED_END RhpThrowExact + NESTED_END RhpThrowEx ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; diff --git a/src/coreclr/nativeaot/Runtime/i386/ExceptionHandling.asm b/src/coreclr/nativeaot/Runtime/i386/ExceptionHandling.asm index 53776f01d802a7..35a3aecb3e91f6 100644 --- a/src/coreclr/nativeaot/Runtime/i386/ExceptionHandling.asm +++ b/src/coreclr/nativeaot/Runtime/i386/ExceptionHandling.asm @@ -91,93 +91,36 @@ FASTCALL_ENDFUNC ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; -;; RhpThrowEx +;; RhpThrowExact +;; +;; SUMMARY: Similar to RhpThrowEx, except that it sets the rethrow flag ;; ;; INPUT: ECX: exception object ;; ;; OUTPUT: ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -FASTCALL_FUNC RhpThrowEx, 4 - - esp_offsetof_ExInfo textequ %0 - esp_offsetof_Context textequ %SIZEOF__ExInfo - - push ebp - mov ebp, esp - - lea eax, [esp+8] ;; calculate the RSP of the throw site - mov edx, [esp+4] ;; get the throw site IP via the return address - -;; struct PAL_LIMITED_CONTEXT -;; { - push ebx - push eax - push esi - push edi - mov ebx, [ebp] - push ebx ;; 'faulting' Rbp - push eax ;; 'faulting' Rsp - push edx ;; 'faulting' IP -;; }; - - sub esp, SIZEOF__ExInfo - - ;; ------------------------- - - lea ebx, [eax-4] ;; ebx <- addr of return address - INLINE_GETTHREAD eax, edx ;; eax <- thread, edx <- trashed - - ;; There is runtime C# code that can tail call to RhpThrowEx using a binder intrinsic. So the return - ;; address could have been hijacked when we were in that C# code and we must remove the hijack and - ;; reflect the correct return address in our exception context record. The other throw helpers don't - ;; need this because they cannot be tail-called from C#. - - INLINE_THREAD_UNHIJACK eax, esi, edx ;; trashes esi, edx - - mov edx, [ebx] ;; edx <- return address - mov [esp + esp_offsetof_Context + OFFSETOF__PAL_LIMITED_CONTEXT__IP], edx ;; set 'faulting' IP after unhijack - - lea edx, [esp + esp_offsetof_ExInfo] ;; edx <- ExInfo* - - xor esi, esi - mov [edx + OFFSETOF__ExInfo__m_exception], esi ;; init the exception object to null - mov byte ptr [edx + OFFSETOF__ExInfo__m_passNumber], 1 ;; init to the first pass - mov dword ptr [edx + OFFSETOF__ExInfo__m_idxCurClause], 0FFFFFFFFh - mov byte ptr [edx + OFFSETOF__ExInfo__m_kind], 1 ;; ExKind.Throw - - ;; link the ExInfo into the thread's ExInfo chain - mov ebx, [eax + OFFSETOF__Thread__m_pExInfoStackHead] - mov [edx + OFFSETOF__ExInfo__m_pPrevExInfo], ebx ;; pExInfo->m_pPrevExInfo = m_pExInfoStackHead - mov [eax + OFFSETOF__Thread__m_pExInfoStackHead], edx ;; m_pExInfoStackHead = pExInfo - - ;; set the exception context field on the ExInfo - lea ebx, [esp + esp_offsetof_Context] ;; ebx <- PAL_LIMITED_CONTEXT* - mov [edx + OFFSETOF__ExInfo__m_pExContext], ebx ;; init ExInfo.m_pExContext - - ;; ecx still contains the exception object - ;; edx contains the address of the ExInfo - call RhThrowEx - -ALTERNATE_ENTRY _RhpThrowEx2 +FASTCALL_FUNC RhpThrowExact, 4 - ;; no return - int 3 + mov edi, 4 ;; edi = ExKind.RethrowFlag + jmp RhpThrowExImpl FASTCALL_ENDFUNC ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; -;; RhpThrowExact -;; -;; SUMMARY: Similar to RhpThrowEx, except that it sets the rethrow flag +;; RhpThrowEx ;; ;; INPUT: ECX: exception object ;; ;; OUTPUT: ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -FASTCALL_FUNC RhpThrowExact, 4 +FASTCALL_FUNC RhpThrowEx, 4 + + mov edi, 1 ;; edi = ExKind.Throw + +ALTERNATE_ENTRY RhpThrowExImpl esp_offsetof_ExInfo textequ %0 esp_offsetof_Context textequ %SIZEOF__ExInfo @@ -207,7 +150,7 @@ FASTCALL_FUNC RhpThrowExact, 4 lea ebx, [eax-4] ;; ebx <- addr of return address INLINE_GETTHREAD eax, edx ;; eax <- thread, edx <- trashed - ;; There is runtime C# code that can tail call to RhpThrowExact using a binder intrinsic. So the return + ;; There is runtime C# code that can tail call to RhpThrowEx using a binder intrinsic. So the return ;; address could have been hijacked when we were in that C# code and we must remove the hijack and ;; reflect the correct return address in our exception context record. The other throw helpers don't ;; need this because they cannot be tail-called from C#. @@ -223,7 +166,7 @@ FASTCALL_FUNC RhpThrowExact, 4 mov [edx + OFFSETOF__ExInfo__m_exception], esi ;; init the exception object to null mov byte ptr [edx + OFFSETOF__ExInfo__m_passNumber], 1 ;; init to the first pass mov dword ptr [edx + OFFSETOF__ExInfo__m_idxCurClause], 0FFFFFFFFh - mov byte ptr [edx + OFFSETOF__ExInfo__m_kind], 4 ;; ExKind.RethrowFlag + mov byte ptr [edx + OFFSETOF__ExInfo__m_kind], dil ;; ExKind (from edi/dil) ;; link the ExInfo into the thread's ExInfo chain mov ebx, [eax + OFFSETOF__Thread__m_pExInfoStackHead] @@ -238,6 +181,7 @@ FASTCALL_FUNC RhpThrowExact, 4 ;; edx contains the address of the ExInfo call RhThrowEx +ALTERNATE_ENTRY _RhpThrowEx2 ALTERNATE_ENTRY _RhpThrowExact2 ;; no return diff --git a/src/coreclr/nativeaot/Runtime/loongarch64/ExceptionHandling.S b/src/coreclr/nativeaot/Runtime/loongarch64/ExceptionHandling.S index 856c69616fdcbb..46a72f1ed06a5c 100644 --- a/src/coreclr/nativeaot/Runtime/loongarch64/ExceptionHandling.S +++ b/src/coreclr/nativeaot/Runtime/loongarch64/ExceptionHandling.S @@ -269,107 +269,41 @@ NESTED_END RhpThrowHwEx, _TEXT // -// RhpThrowEx +// RhpThrowExact +// +// SUMMARY: Similar to RhpThrowEx, except that it sets the rethrow flag // // INPUT: a0: exception object // // OUTPUT: // - NESTED_ENTRY RhpThrowEx, _TEXT, NoHandler - - ALLOC_THROW_FRAME SOFTWARE_EXCEPTION - - GetThreadA2 - - // There is runtime C# code that can tail call to RhpThrowEx using a binder intrinsic. So the return - // address could have been hijacked when we were in that C# code and we must remove the hijack and - // reflect the correct return address in our exception context record. The other throw helpers don't - // need this because they cannot be tail-called from C#. - - // NOTE: we cannot use INLINE_THREAD_UNHIJACK because it will write into the stack at the location - // where the tail-calling thread had saved RA, which may not match where we have saved RA. - - ld.d $a1, $a2, OFFSETOF__Thread__m_pvHijackedReturnAddress - beqz $a1, LOCAL_LABEL(NotHijacked) - - ld.d $a3, $a2, OFFSETOF__Thread__m_ppvHijackedReturnAddressLocation - - // a0: exception object - // a1: hijacked return address - // a2: pThread - // a3: hijacked return address location - - addi.d $t3, $sp, (STACKSIZEOF_ExInfo + SIZEOF__PAL_LIMITED_CONTEXT) // re-compute SP at callsite - bltu $a3, $t3, LOCAL_LABEL(TailCallWasHijacked) // if (m_ppvHijackedReturnAddressLocation < SP at callsite) - - // normal case where a valid return address location is hijacked - st.d $a1, $a3, 0 - b LOCAL_LABEL(ClearThreadState) - -LOCAL_LABEL(TailCallWasHijacked): - - // Abnormal case where the return address location is now invalid because we ended up here via a tail - // call. In this case, our hijacked return address should be the correct caller of this method. - - // stick the previous return address in RA as well as in the right spots in our PAL_LIMITED_CONTEXT. - ori $ra, $a1, 0 - st.d $ra, $sp, (rsp_offsetof_Context + OFFSETOF__PAL_LIMITED_CONTEXT__RA) - st.d $ra, $sp, (rsp_offsetof_Context + OFFSETOF__PAL_LIMITED_CONTEXT__IP) - -LOCAL_LABEL(ClearThreadState): - - // clear the Thread's hijack state - st.d $zero, $a2, OFFSETOF__Thread__m_ppvHijackedReturnAddressLocation - st.d $zero, $a2, OFFSETOF__Thread__m_pvHijackedReturnAddress - -LOCAL_LABEL(NotHijacked): - - addi.d $a1, $sp, rsp_offsetof_ExInfo // a1 <- ExInfo* - st.d $zero, $a1, OFFSETOF__ExInfo__m_exception // pExInfo->m_exception = null - ori $a3, $zero, 1 - st.b $a3, $a1, OFFSETOF__ExInfo__m_passNumber // pExInfo->m_passNumber = 1 - addi.w $a3, $zero, -1 - st.w $a3, $a1, OFFSETOF__ExInfo__m_idxCurClause // pExInfo->m_idxCurClause = MaxTryRegionIdx - ori $a3, $zero, 1 - st.b $a3, $a1, OFFSETOF__ExInfo__m_kind // pExInfo->m_kind = ExKind.Throw - - // link the ExInfo into the thread's ExInfo chain - ld.d $a3, $a2, OFFSETOF__Thread__m_pExInfoStackHead - st.d $a3, $a1, OFFSETOF__ExInfo__m_pPrevExInfo // pExInfo->m_pPrevExInfo = m_pExInfoStackHead - st.d $a1, $a2, OFFSETOF__Thread__m_pExInfoStackHead // m_pExInfoStackHead = pExInfo - - // set the exception context field on the ExInfo - addi.d $a2, $sp, rsp_offsetof_Context // a2 <- PAL_LIMITED_CONTEXT* - st.d $a2, $a1, OFFSETOF__ExInfo__m_pExContext // pExInfo->m_pExContext = pContext + NESTED_ENTRY RhpThrowExact, _TEXT, NoHandler - // a0: exception object - // a1: ExInfo* - bl C_FUNC(RhThrowEx) + ori $a4, $zero, 4 // a4 = ExKind.RethrowFlag + b LOCAL_LABEL(RhpThrowExImpl) - ALTERNATE_ENTRY RhpThrowEx2 - - // no return - EMIT_BREAKPOINT - NESTED_END RhpThrowEx, _TEXT + NESTED_END RhpThrowExact, _TEXT // -// RhpThrowExact -// -// SUMMARY: Similar to RhpThrowEx, except that it sets the rethrow flag +// RhpThrowEx // // INPUT: a0: exception object // // OUTPUT: // - NESTED_ENTRY RhpThrowExact, _TEXT, NoHandler + NESTED_ENTRY RhpThrowEx, _TEXT, NoHandler + + ori $a4, $zero, 1 // a4 = ExKind.Throw + +LOCAL_LABEL(RhpThrowExImpl): ALLOC_THROW_FRAME SOFTWARE_EXCEPTION GetThreadA2 - // There is runtime C# code that can tail call to RhpThrowExact using a binder intrinsic. So the return + // There is runtime C# code that can tail call to RhpThrowEx using a binder intrinsic. So the return // address could have been hijacked when we were in that C# code and we must remove the hijack and // reflect the correct return address in our exception context record. The other throw helpers don't // need this because they cannot be tail-called from C#. @@ -418,8 +352,7 @@ LOCAL_LABEL(NotHijacked): st.b $a3, $a1, OFFSETOF__ExInfo__m_passNumber // pExInfo->m_passNumber = 1 addi.w $a3, $zero, -1 st.w $a3, $a1, OFFSETOF__ExInfo__m_idxCurClause // pExInfo->m_idxCurClause = MaxTryRegionIdx - ori $a3, $zero, 4 - st.b $a3, $a1, OFFSETOF__ExInfo__m_kind // pExInfo->m_kind = ExKind.RethrowFlag + st.b $a4, $a1, OFFSETOF__ExInfo__m_kind // pExInfo->m_kind = ExKind (from a4) // link the ExInfo into the thread's ExInfo chain ld.d $a3, $a2, OFFSETOF__Thread__m_pExInfoStackHead @@ -434,10 +367,12 @@ LOCAL_LABEL(NotHijacked): // a1: ExInfo* bl C_FUNC(RhThrowEx) + ALTERNATE_ENTRY RhpThrowEx2 ALTERNATE_ENTRY RhpThrowExact2 // no return EMIT_BREAKPOINT + NESTED_END RhpThrowEx, _TEXT NESTED_END RhpThrowExact, _TEXT // diff --git a/src/coreclr/nativeaot/Runtime/riscv64/ExceptionHandling.S b/src/coreclr/nativeaot/Runtime/riscv64/ExceptionHandling.S index fdc724de918b34..cd6f21e11b2a3d 100644 --- a/src/coreclr/nativeaot/Runtime/riscv64/ExceptionHandling.S +++ b/src/coreclr/nativeaot/Runtime/riscv64/ExceptionHandling.S @@ -304,93 +304,35 @@ NESTED_END RhpThrowHwEx, _TEXT // -// RhpThrowEx +// RhpThrowExact +// +// SUMMARY: Similar to RhpThrowEx, except that it sets the rethrow flag // // INPUT: a0: exception object // // OUTPUT: // - NESTED_ENTRY RhpThrowEx, _TEXT, NoHandler - - ALLOC_THROW_FRAME SOFTWARE_EXCEPTION - - GetThreadA2 - - ld a1, OFFSETOF__Thread__m_pvHijackedReturnAddress(a2) - beq a1, zero, LOCAL_LABEL(NotHijacked) - - ld a3, OFFSETOF__Thread__m_ppvHijackedReturnAddressLocation(a2) - - // Recompute SP at callsite - addi t3, sp, (STACKSIZEOF_ExInfo + SIZEOF__PAL_LIMITED_CONTEXT) - bltu a3, t3, LOCAL_LABEL(TailCallWasHijacked) // if (m_ppvHijackedReturnAddressLocation < SP at callsite) - - // Normal case where a valid return address location is hijacked - sd a1, 0(a3) - tail LOCAL_LABEL(ClearThreadState) - -LOCAL_LABEL(TailCallWasHijacked): - - // Abnormal case where the return address location is now invalid because we ended up here via a tail - // call. In this case, our hijacked return address should be the correct caller of this method. - - // Stick the previous return address in RA as well as in the right spots in our PAL_LIMITED_CONTEXT. - mv ra, a1 - - // Compute offsets for PAL_LIMITED_CONTEXT - sd ra, (rsp_offsetof_Context + OFFSETOF__PAL_LIMITED_CONTEXT__RA)(sp) - sd ra, (rsp_offsetof_Context + OFFSETOF__PAL_LIMITED_CONTEXT__IP)(sp) - -LOCAL_LABEL(ClearThreadState): - - // Clear the Thread's hijack state - sd zero, OFFSETOF__Thread__m_ppvHijackedReturnAddressLocation(a2) - sd zero, OFFSETOF__Thread__m_pvHijackedReturnAddress(a2) - -LOCAL_LABEL(NotHijacked): - - // Compute the offset for ExInfo - addi a1, sp, rsp_offsetof_ExInfo // a1 <- ExInfo* - sd zero, OFFSETOF__ExInfo__m_exception(a1) // pExInfo->m_exception = null - li a3, 1 - sb a3, OFFSETOF__ExInfo__m_passNumber(a1) // pExInfo->m_passNumber = 1 - addiw a3, zero, -1 - sw a3, OFFSETOF__ExInfo__m_idxCurClause(a1) // pExInfo->m_idxCurClause = MaxTryRegionIdx - li a3, 1 - sb a3, OFFSETOF__ExInfo__m_kind(a1) // pExInfo->m_kind = ExKind.Throw - - // Link the ExInfo into the thread's ExInfo chain - ld a3, OFFSETOF__Thread__m_pExInfoStackHead(a2) - sd a3, OFFSETOF__ExInfo__m_pPrevExInfo(a1) // pExInfo->m_pPrevExInfo = m_pExInfoStackHead - sd a1, OFFSETOF__Thread__m_pExInfoStackHead(a2) // m_pExInfoStackHead = pExInfo - - // Set the exception context field on the ExInfo - addi a2, sp, rsp_offsetof_Context // a2 <- PAL_LIMITED_CONTEXT* - sd a2, OFFSETOF__ExInfo__m_pExContext(a1) // pExInfo->m_pExContext = pContext - - // a0: exception object - // a1: ExInfo* - call C_FUNC(RhThrowEx) - - ALTERNATE_ENTRY RhpThrowEx2 + NESTED_ENTRY RhpThrowExact, _TEXT, NoHandler - // No return - EMIT_BREAKPOINT + li a4, 4 // a4 = ExKind.RethrowFlag + j LOCAL_LABEL(RhpThrowExImpl) - NESTED_END RhpThrowEx, _TEXT + NESTED_END RhpThrowExact, _TEXT // -// RhpThrowExact -// -// SUMMARY: Similar to RhpThrowEx, except that it sets the rethrow flag +// RhpThrowEx // // INPUT: a0: exception object // // OUTPUT: // - NESTED_ENTRY RhpThrowExact, _TEXT, NoHandler + NESTED_ENTRY RhpThrowEx, _TEXT, NoHandler + + li a4, 1 // a4 = ExKind.Throw + +LOCAL_LABEL(RhpThrowExImpl): ALLOC_THROW_FRAME SOFTWARE_EXCEPTION @@ -407,7 +349,7 @@ LOCAL_LABEL(NotHijacked): // Normal case where a valid return address location is hijacked sd a1, 0(a3) - tail LOCAL_LABEL(ClearThreadState) + j LOCAL_LABEL(ClearThreadState) LOCAL_LABEL(TailCallWasHijacked): @@ -436,8 +378,7 @@ LOCAL_LABEL(NotHijacked): sb a3, OFFSETOF__ExInfo__m_passNumber(a1) // pExInfo->m_passNumber = 1 addiw a3, zero, -1 sw a3, OFFSETOF__ExInfo__m_idxCurClause(a1) // pExInfo->m_idxCurClause = MaxTryRegionIdx - li a3, 4 - sb a3, OFFSETOF__ExInfo__m_kind(a1) // pExInfo->m_kind = ExKind.RethrowFlag + sb a4, OFFSETOF__ExInfo__m_kind(a1) // pExInfo->m_kind = ExKind (from a4) // Link the ExInfo into the thread's ExInfo chain ld a3, OFFSETOF__Thread__m_pExInfoStackHead(a2) @@ -452,12 +393,13 @@ LOCAL_LABEL(NotHijacked): // a1: ExInfo* call C_FUNC(RhThrowEx) + ALTERNATE_ENTRY RhpThrowEx2 ALTERNATE_ENTRY RhpThrowExact2 // No return EMIT_BREAKPOINT - NESTED_END RhpThrowExact, _TEXT + NESTED_END RhpThrowEx, _TEXT // // void FASTCALL RhpRethrow() From 7e28e0bcb6b736933ead329922fbade09b199b0f Mon Sep 17 00:00:00 2001 From: rcj1 Date: Wed, 24 Dec 2025 07:01:23 -0800 Subject: [PATCH 51/51] fb --- src/coreclr/vm/debugdebugger.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/coreclr/vm/debugdebugger.cpp b/src/coreclr/vm/debugdebugger.cpp index 7fdb0f67d20b5d..cde08139faa938 100644 --- a/src/coreclr/vm/debugdebugger.cpp +++ b/src/coreclr/vm/debugdebugger.cpp @@ -995,7 +995,6 @@ void DebugStackTrace::GetStackFramesFromException(OBJECTREF * e, UINT_PTR ip = cur.ip; if (cur.flags & STEF_CONTINUATION) { - DebugInfoRequest request; EECodeInfo codeInfo((PCODE)ip); if (codeInfo.IsValid()) {