From 0beb3f0fad2cd98332fddf63d42f541f57ae9390 Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Mon, 18 Sep 2023 12:05:08 -0700 Subject: [PATCH 001/203] More updates --- src/coreclr/vm/array.cpp | 2 +- src/coreclr/vm/class.cpp | 4 +- src/coreclr/vm/clsload.cpp | 2 +- src/coreclr/vm/dynamicmethod.cpp | 2 +- src/coreclr/vm/genmeth.cpp | 10 + src/coreclr/vm/method.cpp | 42 +++- src/coreclr/vm/method.hpp | 44 +++- src/coreclr/vm/methodtablebuilder.cpp | 339 ++++++++++++++++++++++++-- src/coreclr/vm/methodtablebuilder.h | 57 ++++- 9 files changed, 454 insertions(+), 48 deletions(-) diff --git a/src/coreclr/vm/array.cpp b/src/coreclr/vm/array.cpp index 3670c7e8446bda..7a2bb145faadbe 100644 --- a/src/coreclr/vm/array.cpp +++ b/src/coreclr/vm/array.cpp @@ -500,7 +500,7 @@ MethodTable* Module::CreateArrayMethodTable(TypeHandle elemTypeHnd, CorElementTy + 3; // for rank specific Get, Set, Address MethodDescChunk * pChunks = MethodDescChunk::CreateChunk(pAllocator->GetHighFrequencyHeap(), - dwMethodDescs, mcArray, FALSE /* fNonVtableSlot*/, FALSE /* fNativeCodeSlot */, + dwMethodDescs, mcArray, FALSE /* fNonVtableSlot*/, FALSE /* fNativeCodeSlot */, FALSE /* IsAsyncThunkMethod */, pMT, pamTracker); pClass->SetChunks(pChunks); diff --git a/src/coreclr/vm/class.cpp b/src/coreclr/vm/class.cpp index 0062632396c955..296c6686529dbd 100644 --- a/src/coreclr/vm/class.cpp +++ b/src/coreclr/vm/class.cpp @@ -767,6 +767,7 @@ HRESULT EEClass::AddMethodDesc( classification, TRUE, // fNonVtableSlot TRUE, // fNativeCodeSlot + FALSE, /* IsAsyncThunkMethod */ pMT, &dummyAmTracker); @@ -814,7 +815,8 @@ HRESULT EEClass::AddMethodDesc( TRUE, // fEnC 0, // RVA - non-zero only for NDirect pImport, - NULL + NULL, + Signature() COMMA_INDEBUG(debug_szMethodName) COMMA_INDEBUG(pMT->GetDebugClassName()) COMMA_INDEBUG(NULL) diff --git a/src/coreclr/vm/clsload.cpp b/src/coreclr/vm/clsload.cpp index 06775e004629d6..8df5691cbafbe7 100644 --- a/src/coreclr/vm/clsload.cpp +++ b/src/coreclr/vm/clsload.cpp @@ -3083,7 +3083,7 @@ TypeHandle ClassLoader::PublishType(TypeKey *pTypeKey, TypeHandle typeHnd) { MethodDesc * pMD = it.GetMethodDesc(); CONSISTENCY_CHECK(pMD != NULL && pMD->GetMethodTable() == pMT); - if (!pMD->IsUnboxingStub()) + if (!pMD->IsUnboxingStub() && (!pMD->IsEEImpl() || pMD->GetMethodTable()->IsDelegate())) { pModule->EnsuredStoreMethodDef(pMD->GetMemberDef(), pMD); } diff --git a/src/coreclr/vm/dynamicmethod.cpp b/src/coreclr/vm/dynamicmethod.cpp index 6bb94a6144c769..a7499e0708fac8 100644 --- a/src/coreclr/vm/dynamicmethod.cpp +++ b/src/coreclr/vm/dynamicmethod.cpp @@ -162,7 +162,7 @@ void DynamicMethodTable::AddMethodsToList() // allocate as many chunks as needed to hold the methods // MethodDescChunk* pChunk = MethodDescChunk::CreateChunk(pHeap, 0 /* one chunk of maximum size */, - mcDynamic, TRUE /* fNonVtableSlot */, TRUE /* fNativeCodeSlot */, m_pMethodTable, &amt); + mcDynamic, TRUE /* fNonVtableSlot */, TRUE /* fNativeCodeSlot */, FALSE /* IsAsyncThunkMethod */, m_pMethodTable, &amt); if (m_DynamicMethodList) RETURN; int methodCount = pChunk->GetCount(); diff --git a/src/coreclr/vm/genmeth.cpp b/src/coreclr/vm/genmeth.cpp index 96bbc53a25e484..029e379305e4c2 100644 --- a/src/coreclr/vm/genmeth.cpp +++ b/src/coreclr/vm/genmeth.cpp @@ -91,6 +91,7 @@ static MethodDesc* CreateMethodDesc(LoaderAllocator *pAllocator, classification, TRUE /* fNonVtableSlot*/, fNativeCodeSlot, + pTemplateMD->IsAsyncThunkMethod(), pMT, pamTracker); @@ -117,6 +118,10 @@ static MethodDesc* CreateMethodDesc(LoaderAllocator *pAllocator, { pMD->SetIsIntrinsic(); } + if (pTemplateMD->IsAsyncThunkMethod()) + { + pMD->SetIsAsyncThunkMethod(); + } #ifdef FEATURE_METADATA_UPDATER if (pTemplateMD->IsEnCAddedMethod()) @@ -128,6 +133,11 @@ static MethodDesc* CreateMethodDesc(LoaderAllocator *pAllocator, pMD->SetMemberDef(token); pMD->SetSlot(pTemplateMD->GetSlot()); + if (pTemplateMD->IsAsyncThunkMethod()) + { + *pMD->GetAddrOfAsyncThunkData() = pTemplateMD->GetAsyncThunkData(); + } + #ifdef _DEBUG // more info here pMD->m_pszDebugMethodSignature = ""; diff --git a/src/coreclr/vm/method.cpp b/src/coreclr/vm/method.cpp index e34ee89ac8ae58..63b099dc54db58 100644 --- a/src/coreclr/vm/method.cpp +++ b/src/coreclr/vm/method.cpp @@ -82,6 +82,7 @@ const BYTE MethodDesc::s_ClassificationSizeTable[] = { // This extended part of the table is used for faster MethodDesc size lookup. // We index using optional slot flags into it METHOD_DESC_SIZES(sizeof(NonVtableSlot)), + METHOD_DESC_SIZES(sizeof(MethodImpl)), METHOD_DESC_SIZES(sizeof(NonVtableSlot) + sizeof(MethodImpl)), @@ -90,17 +91,14 @@ const BYTE MethodDesc::s_ClassificationSizeTable[] = { METHOD_DESC_SIZES(sizeof(MethodImpl) + sizeof(NativeCodeSlot)), METHOD_DESC_SIZES(sizeof(NonVtableSlot) + sizeof(MethodImpl) + sizeof(NativeCodeSlot)), -#ifdef FEATURE_COMINTEROP - METHOD_DESC_SIZES(sizeof(ComPlusCallInfo)), - METHOD_DESC_SIZES(sizeof(NonVtableSlot) + sizeof(ComPlusCallInfo)), - METHOD_DESC_SIZES(sizeof(MethodImpl) + sizeof(ComPlusCallInfo)), - METHOD_DESC_SIZES(sizeof(NonVtableSlot) + sizeof(MethodImpl) + sizeof(ComPlusCallInfo)), - - METHOD_DESC_SIZES(sizeof(NativeCodeSlot) + sizeof(ComPlusCallInfo)), - METHOD_DESC_SIZES(sizeof(NonVtableSlot) + sizeof(NativeCodeSlot) + sizeof(ComPlusCallInfo)), - METHOD_DESC_SIZES(sizeof(MethodImpl) + sizeof(NativeCodeSlot) + sizeof(ComPlusCallInfo)), - METHOD_DESC_SIZES(sizeof(NonVtableSlot) + sizeof(MethodImpl) + sizeof(NativeCodeSlot) + sizeof(ComPlusCallInfo)) -#endif + METHOD_DESC_SIZES(sizeof(AsyncThunkData)), + METHOD_DESC_SIZES(sizeof(NonVtableSlot) + sizeof(AsyncThunkData)), + METHOD_DESC_SIZES(sizeof(MethodImpl) + sizeof(AsyncThunkData)), + METHOD_DESC_SIZES(sizeof(NonVtableSlot) + sizeof(MethodImpl) + sizeof(AsyncThunkData)), + METHOD_DESC_SIZES(sizeof(NativeCodeSlot) + sizeof(AsyncThunkData)), + METHOD_DESC_SIZES(sizeof(NonVtableSlot) + sizeof(NativeCodeSlot) + sizeof(AsyncThunkData)), + METHOD_DESC_SIZES(sizeof(MethodImpl) + sizeof(NativeCodeSlot) + sizeof(AsyncThunkData)), + METHOD_DESC_SIZES(sizeof(NonVtableSlot) + sizeof(MethodImpl) + sizeof(NativeCodeSlot) + sizeof(AsyncThunkData)), }; #ifndef FEATURE_COMINTEROP @@ -135,7 +133,8 @@ SIZE_T MethodDesc::SizeOf() (mdcClassification | mdcHasNonVtableSlot | mdcMethodImpl - | mdcHasNativeCodeSlot)]; + | mdcHasNativeCodeSlot + | mdcIsAsyncThunkMethod)]; return size; } @@ -978,6 +977,18 @@ PTR_PCODE MethodDesc::GetAddrOfNativeCodeSlot() return (PTR_PCODE)(dac_cast(this) + size); } +//******************************************************************************* +AsyncThunkData* MethodDesc::GetAddrOfAsyncThunkData() +{ + WRAPPER_NO_CONTRACT; + + _ASSERTE(IsAsyncThunkMethod()); + + SIZE_T size = s_ClassificationSizeTable[m_wFlags & (mdcClassification | mdcHasNonVtableSlot | mdcMethodImpl | mdcHasNativeCodeSlot)]; + + return (AsyncThunkData*)(dac_cast(this) + size); +} + //******************************************************************************* BOOL MethodDesc::IsVoid() { @@ -1689,7 +1700,7 @@ MethodDesc* MethodDesc::StripMethodInstantiation() //******************************************************************************* MethodDescChunk *MethodDescChunk::CreateChunk(LoaderHeap *pHeap, DWORD methodDescCount, - DWORD classification, BOOL fNonVtableSlot, BOOL fNativeCodeSlot, MethodTable *pInitialMT, AllocMemTracker *pamTracker) + DWORD classification, BOOL fNonVtableSlot, BOOL fNativeCodeSlot, BOOL fAsyncThunkData, MethodTable *pInitialMT, AllocMemTracker *pamTracker) { CONTRACT(MethodDescChunk *) { @@ -1713,6 +1724,9 @@ MethodDescChunk *MethodDescChunk::CreateChunk(LoaderHeap *pHeap, DWORD methodDes if (fNativeCodeSlot) oneSize += sizeof(MethodDesc::NativeCodeSlot); + if (fAsyncThunkData) + oneSize += sizeof(AsyncThunkData); + _ASSERTE((oneSize & MethodDesc::ALIGNMENT_MASK) == 0); DWORD maxMethodDescsPerChunk = (DWORD)(MethodDescChunk::MaxSizeOfMethodDescs / oneSize); @@ -1753,6 +1767,8 @@ MethodDescChunk *MethodDescChunk::CreateChunk(LoaderHeap *pHeap, DWORD methodDes pMD->SetHasNonVtableSlot(); if (fNativeCodeSlot) pMD->SetHasNativeCodeSlot(); + if (fAsyncThunkData) + pMD->SetIsAsyncThunkMethod(); _ASSERTE(pMD->SizeOf() == oneSize); diff --git a/src/coreclr/vm/method.hpp b/src/coreclr/vm/method.hpp index 411129c6c6393e..21cf16c3e61f9f 100644 --- a/src/coreclr/vm/method.hpp +++ b/src/coreclr/vm/method.hpp @@ -51,11 +51,25 @@ GVAL_DECL(TADDR, g_MiniMetaDataBuffAddress); EXTERN_C VOID STDCALL NDirectImportThunk(); -#define METHOD_TOKEN_REMAINDER_BIT_COUNT 12 +#define METHOD_TOKEN_REMAINDER_BIT_COUNT 13 #define METHOD_TOKEN_REMAINDER_MASK ((1 << METHOD_TOKEN_REMAINDER_BIT_COUNT) - 1) #define METHOD_TOKEN_RANGE_BIT_COUNT (24 - METHOD_TOKEN_REMAINDER_BIT_COUNT) #define METHOD_TOKEN_RANGE_MASK ((1 << METHOD_TOKEN_RANGE_BIT_COUNT) - 1) +enum class AsyncThunkType +{ + NotAThunk, + TaskToAsync, + AsyncToTask +}; + +struct AsyncThunkData +{ + AsyncThunkType type; + Signature sig; +}; + + //============================================================= // Splits methoddef token into two pieces for // storage inside a methoddesc. @@ -138,8 +152,8 @@ enum MethodDescClassification // Has slot for native code mdcHasNativeCodeSlot = 0x0020, - // Method was added via Edit And Continue - mdcEnCAddedMethod = 0x0040, + // IsAsyncThunkMethod + mdcIsAsyncThunkMethod = 0x0040, // Method is static mdcStatic = 0x0080, @@ -1384,6 +1398,8 @@ class MethodDesc BOOL SetNativeCodeInterlocked(PCODE addr, PCODE pExpected = NULL); PTR_PCODE GetAddrOfNativeCodeSlot(); + AsyncThunkData *GetAddrOfAsyncThunkData(); + const AsyncThunkData& GetAsyncThunkData() { _ASSERTE(IsAsyncThunkMethod()); return *GetAddrOfAsyncThunkData(); } BOOL MayHaveNativeCode(); @@ -1622,8 +1638,11 @@ class MethodDesc enum { // There are flags available for use here (currently 4 flags bits are available); however, new bits are hard to come by, so any new flags bits should // have a fairly strong justification for existence. - enum_flag3_TokenRemainderMask = 0x0FFF, // This must equal METHOD_TOKEN_REMAINDER_MASK calculated higher in this file. + enum_flag3_TokenRemainderMask = 0x07FF, // This must equal METHOD_TOKEN_REMAINDER_MASK calculated higher in this file. // for this method. + // Method was added via Edit And Continue + enum_flag3_EnCAddedMethod = 0x0800, + // enum_flag3_HasPrecode implies that enum_flag3_HasStableEntryPoint is set. enum_flag3_HasStableEntryPoint = 0x1000, // The method entrypoint is stable (either precode or actual code) enum_flag3_HasPrecode = 0x2000, // Precode has been allocated for this method @@ -1683,17 +1702,29 @@ class MethodDesc m_wFlags |= mdcHasNativeCodeSlot; } + inline BOOL IsAsyncThunkMethod() + { + LIMITED_METHOD_DAC_CONTRACT; + return (m_wFlags & mdcIsAsyncThunkMethod) != 0; + } + + inline void SetIsAsyncThunkMethod() + { + LIMITED_METHOD_CONTRACT; + m_wFlags |= mdcIsAsyncThunkMethod; + } + #ifdef FEATURE_METADATA_UPDATER inline BOOL IsEnCAddedMethod() { LIMITED_METHOD_DAC_CONTRACT; - return (m_wFlags & mdcEnCAddedMethod) != 0; + return (m_wFlags3AndTokenRemainder & enum_flag3_EnCAddedMethod) != 0; } inline void SetIsEnCAddedMethod() { LIMITED_METHOD_CONTRACT; - m_wFlags |= mdcEnCAddedMethod; + m_wFlags3AndTokenRemainder |= enum_flag3_EnCAddedMethod; } #else inline BOOL IsEnCAddedMethod() @@ -2143,6 +2174,7 @@ class MethodDescChunk DWORD classification, BOOL fNonVtableSlot, BOOL fNativeCodeSlot, + BOOL fAsyncThunkData, MethodTable *initialMT, class AllocMemTracker *pamTracker); diff --git a/src/coreclr/vm/methodtablebuilder.cpp b/src/coreclr/vm/methodtablebuilder.cpp index 44ddcf546064bb..f19b584842bf6c 100644 --- a/src/coreclr/vm/methodtablebuilder.cpp +++ b/src/coreclr/vm/methodtablebuilder.cpp @@ -994,9 +994,14 @@ MethodTableBuilder::bmtRTMethod::bmtRTMethod( MethodDesc * pMD) : m_pOwningType(pOwningType), m_pMD(pMD), - m_methodSig(pMD->GetModule(), - pMD->GetMemberDef(), - &pOwningType->GetSubstitution()) + m_methodSig(pMD->IsEEImpl() // Handle Async2 methods + ? MethodSignature(pMD->GetModule(), + pMD->GetMemberDef(), + pMD->GetSignature(), + &pOwningType->GetSubstitution()) + : MethodSignature(pMD->GetModule(), + pMD->GetMemberDef(), + &pOwningType->GetSubstitution())) { CONTRACTL { @@ -1038,6 +1043,39 @@ MethodTableBuilder::bmtMDMethod::bmtMDMethod( } CONTRACTL_END; } + + MethodTableBuilder::bmtMDMethod::bmtMDMethod( + bmtMDType * pOwningType, + mdMethodDef tok, + DWORD dwDeclAttrs, + DWORD dwImplAttrs, + DWORD dwRVA, + Signature sig, + MethodClassification type, + METHOD_IMPL_TYPE implType) + : m_pOwningType(pOwningType), + m_dwDeclAttrs(dwDeclAttrs), + m_dwImplAttrs(dwImplAttrs), + m_dwRVA(dwRVA), + m_type(type), + m_implType(implType), + m_methodSig(pOwningType->GetModule(), + tok, + sig, + &pOwningType->GetSubstitution()), + m_pMD(NULL), + m_pUnboxedMD(NULL), + m_slotIndex(INVALID_SLOT_INDEX), + m_unboxedSlotIndex(INVALID_SLOT_INDEX) + { + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + } //******************************************************************************* void MethodTableBuilder::ImportParentMethods() @@ -2644,6 +2682,179 @@ HRESULT MethodTableBuilder::FindMethodDeclarationForMethodImpl( #pragma warning(push) #pragma warning(disable:21000) // Suppress PREFast warning about overly large function #endif // _PREFAST_ + +enum class AsyncTaskMethod +{ + TaskReturningMethod, + Async2Method, + Async2MethodThatCannotBeImplementedByTask, + NormalMethod +}; + +void GetNameOfTypeDefOrRef(Module* pModule, mdToken tk, LPCSTR* pName, LPCSTR* pNamespace) +{ + *pName = ""; + *pNamespace = ""; + if (TypeFromToken(tk) == mdtTypeDef) + { + IfFailThrow(pModule->GetMDImport()->GetNameOfTypeDef(tk, pName, pNamespace)); + } + else if (TypeFromToken(tk) == mdtTypeRef) + { + IfFailThrow(pModule->GetMDImport()->GetNameOfTypeRef(tk, pName, pNamespace)); + } +} + +bool IsTypeDefOrRefImplementedInSystemModule(Module* pModule, mdToken tk) +{ + if (TypeFromToken(tk) == mdtTypeDef) + { + if (pModule->IsSystem()) + { + return true; + } + } + else if (TypeFromToken(tk) == mdtTypeRef) + { + mdToken tkTypeDef; + Module* pModuleOfTypeDef; + + ClassLoader::ResolveTokenToTypeDefThrowing(pModule, tk, &pModuleOfTypeDef, &tkTypeDef); + if (pModuleOfTypeDef->IsSystem()) + { + return true; + } + } + + return false; +} + +bool IsTypeDefOrRefAByRefStruct(Module* pModule, mdToken tk) +{ + if (TypeFromToken(tk) == mdtTypeRef) + { + if(!ClassLoader::ResolveTokenToTypeDefThrowing(pModule, tk, &pModule, &tk)) + { + return false; + } + } + + HRESULT hr = pModule->GetCustomAttribute(tk, + WellKnownAttribute::IsByRefLike, + NULL, NULL); + + if (hr == S_OK) + return true; + + return false; +} + +AsyncTaskMethod ClassifyAsyncMethod(SigPointer sig, Module* pModule, ULONG* offsetOfAsyncDetails) +{ + PCCOR_SIGNATURE initialSig = sig.GetPtr(); + uint32_t data; + IfFailThrow(sig.GetCallingConvInfo(&data)); + if (data & IMAGE_CEE_CS_CALLCONV_GENERIC) + { + // Skip over generic argument count + IfFailThrow(sig.GetData(&data)); + } + + // Return argument count + IfFailThrow(sig.GetData(&data)); + + // Now we should be parsing the return type + + // If the first custommodifier is a MOD_OPT to CallConvAsync2Call + // Then this is a async2 function + CorElementType elemType; + *offsetOfAsyncDetails = (ULONG)(sig.GetPtr() - initialSig); + IfFailThrow(sig.GetElemType(&elemType)); + LPCSTR name, _namespace; + mdToken tk; + while ((elemType == ELEMENT_TYPE_CMOD_OPT) || (elemType == ELEMENT_TYPE_CMOD_REQD)) + { + IfFailThrow(sig.GetToken(&tk)); + GetNameOfTypeDefOrRef(pModule, tk, &name, &_namespace); + *offsetOfAsyncDetails = (ULONG)(sig.GetPtr() - initialSig); + IfFailThrow(sig.GetElemType(&elemType)); + if (strcmp(name, "Task`1") == 0 && strcmp(_namespace, "System.Threading.Tasks") == 0 && IsTypeDefOrRefImplementedInSystemModule(pModule, tk)) + { + // This must have been the last CMOD before the element type, and the element type MUST be one which can be expressed structurally as a generic parameter + switch (elemType) + { + case ELEMENT_TYPE_CLASS: + case ELEMENT_TYPE_VALUETYPE: + case ELEMENT_TYPE_STRING: + case ELEMENT_TYPE_OBJECT: + case ELEMENT_TYPE_I: + case ELEMENT_TYPE_U: + case ELEMENT_TYPE_I1: + case ELEMENT_TYPE_U1: + case ELEMENT_TYPE_I2: + case ELEMENT_TYPE_U2: + case ELEMENT_TYPE_I4: + case ELEMENT_TYPE_U4: + case ELEMENT_TYPE_I8: + case ELEMENT_TYPE_U8: + case ELEMENT_TYPE_R4: + case ELEMENT_TYPE_R8: + case ELEMENT_TYPE_BOOLEAN: + case ELEMENT_TYPE_CHAR: + case ELEMENT_TYPE_ARRAY: + case ELEMENT_TYPE_SZARRAY: + case ELEMENT_TYPE_VAR: + case ELEMENT_TYPE_MVAR: + return AsyncTaskMethod::Async2Method; + case ELEMENT_TYPE_TYPEDBYREF: + return AsyncTaskMethod::Async2MethodThatCannotBeImplementedByTask; + case ELEMENT_TYPE_GENERICINST: + IfFailThrow(sig.GetElemType(&elemType)); + if (elemType == ELEMENT_TYPE_CLASS) + { + return AsyncTaskMethod::Async2Method; + } + else + { + IfFailThrow(sig.GetToken(&tk)); + if (IsTypeDefOrRefAByRefStruct(pModule, tk)) + { + return AsyncTaskMethod::Async2MethodThatCannotBeImplementedByTask; + } + else + { + return AsyncTaskMethod::Async2Method; + } + } + default: + ThrowHR(COR_E_BADIMAGEFORMAT); + } + } + } + + if (elemType == ELEMENT_TYPE_GENERICINST) + { + IfFailThrow(sig.GetElemType(&elemType)); + if (elemType == ELEMENT_TYPE_VALUETYPE) + { + return AsyncTaskMethod::NormalMethod; // Task is a class, so we can't be that if we get here + } + IfFailThrow(sig.GetToken(&tk)); + IfFailThrow(sig.GetData(&data)); + if (data == 1) + { + // This might be System.Threading.Tasks.Task`1 + GetNameOfTypeDefOrRef(pModule, tk, &name, &_namespace); + if (strcmp(name, "Task`1") == 0 && strcmp(_namespace, "System.Threading.Tasks") == 0) + { + if (IsTypeDefOrRefImplementedInSystemModule(pModule, tk)) + return AsyncTaskMethod::TaskReturningMethod; + } + } + } + return AsyncTaskMethod::NormalMethod; +} + //--------------------------------------------------------------------------------------- // // Used by BuildMethodTable @@ -2699,7 +2910,7 @@ MethodTableBuilder::EnumerateClassMethods() if ((DWORD)MAX_SLOT_INDEX <= cMethAndGaps) BuildMethodTableThrowException(IDS_CLASSLOAD_TOO_MANY_METHODS); - bmtMethod->m_cMaxDeclaredMethods = (SLOT_INDEX)cMethAndGaps; + bmtMethod->m_cMaxDeclaredMethods = (SLOT_INDEX)cMethAndGaps * 2; bmtMethod->m_cDeclaredMethods = 0; bmtMethod->m_rgDeclaredMethods = new (GetStackingAllocator()) bmtMDMethod *[bmtMethod->m_cMaxDeclaredMethods]; @@ -2728,7 +2939,7 @@ MethodTableBuilder::EnumerateClassMethods() { BuildMethodTableThrowException(BFA_METHOD_TOKEN_OUT_OF_RANGE); } - if (!bmtProp->fNoSanityChecks && FAILED(pMDInternalImport->GetSigOfMethodDef(tok, &cMemberSignature, &pMemberSignature))) + if (FAILED(pMDInternalImport->GetSigOfMethodDef(tok, &cMemberSignature, &pMemberSignature))) { BuildMethodTableThrowException(hr, BFA_BAD_SIGNATURE, mdMethodDefNil); } @@ -2774,6 +2985,12 @@ MethodTableBuilder::EnumerateClassMethods() } } + SigParser sig(pMemberSignature, cMemberSignature); + ULONG offsetOfAsyncDetails = 0; + AsyncTaskMethod asyncMethodType; + + asyncMethodType = IsDelegate() ? AsyncTaskMethod::NormalMethod : ClassifyAsyncMethod(sig, GetModule(), &offsetOfAsyncDetails); + bool hasGenericMethodArgsComputed; bool hasGenericMethodArgs = this->GetModule()->m_pMethodIsGenericMap->IsGeneric(tok, &hasGenericMethodArgsComputed); if (!hasGenericMethodArgsComputed) @@ -3310,28 +3527,79 @@ MethodTableBuilder::EnumerateClassMethods() // Create a new bmtMDMethod representing this method and add it to the // declared method list. // + for (int insertCount = 0; insertCount < 2; insertCount++) + { + bmtMDMethod * pNewMethod; + if (insertCount == 0) + { + pNewMethod = new (GetStackingAllocator()) bmtMDMethod( + bmtInternal->pType, + tok, + dwMemberAttrs, + dwImplFlags, + dwMethodRVA, + type, + implType); + } + else + { + ULONG cAsyncThunkMemberSignature = cMemberSignature; - bmtMDMethod * pNewMethod = new (GetStackingAllocator()) bmtMDMethod( - bmtInternal->pType, - tok, - dwMemberAttrs, - dwImplFlags, - dwMethodRVA, - type, - implType); + if (asyncMethodType == AsyncTaskMethod::Async2Method) + cAsyncThunkMemberSignature += 1; + else + cAsyncThunkMemberSignature -= 1; - bmtMethod->AddDeclaredMethod(pNewMethod); + BYTE* pNewMemberSignature = AllocateFromHighFrequencyHeap(S_SIZE_T(cAsyncThunkMemberSignature)); + ULONG tokenLen = CorSigUncompressedDataSize(&pMemberSignature[offsetOfAsyncDetails + 1]); + ULONG initialCopyLen = offsetOfAsyncDetails + 1 + tokenLen; + memcpy(pNewMemberSignature, pMemberSignature, initialCopyLen); - // - // Update the count of the various types of methods. - // + if (asyncMethodType == AsyncTaskMethod::Async2Method) + { + // Replace the ELEMENT_TYPE_CMOD with ELEMENT_TYPE_GENERICINST, and then add a 1 after the token which refers to Task + pNewMemberSignature[offsetOfAsyncDetails] = ELEMENT_TYPE_GENERICINST; + pNewMemberSignature[initialCopyLen] = 1; + memcpy(pNewMemberSignature + initialCopyLen + 1, pMemberSignature + initialCopyLen, cMemberSignature - initialCopyLen); + } + else + { + // Replace the ELEMENT_TYPE_GENERICINST with ELEMENT_TYPE_CMOD_OPT, and then remove the 1 which specifies the generic arg count for Task + pNewMemberSignature[offsetOfAsyncDetails] = ELEMENT_TYPE_CMOD_OPT; + memcpy(pNewMemberSignature + initialCopyLen, pMemberSignature + initialCopyLen + 1, cMemberSignature - (initialCopyLen + 1)); + } + + Signature newMemberSig(pNewMemberSignature, cAsyncThunkMemberSignature); + pNewMethod = new (GetStackingAllocator()) bmtMDMethod( + bmtInternal->pType, + tok, + dwMemberAttrs, + dwImplFlags, + dwMethodRVA, + newMemberSig, + type, + implType); + } - bmtVT->dwMaxVtableSize++; + bmtMethod->AddDeclaredMethod(pNewMethod); - // Increment the number of non-abstract declared methods - if (!IsMdAbstract(dwMemberAttrs)) - { - bmtMethod->dwNumDeclaredNonAbstractMethods++; + // + // Update the count of the various types of methods. + // + + bmtVT->dwMaxVtableSize++; + + // Increment the number of non-abstract declared methods + if (!IsMdAbstract(dwMemberAttrs)) + { + bmtMethod->dwNumDeclaredNonAbstractMethods++; + } + + // Normal methods only insert a single method + if ((asyncMethodType == AsyncTaskMethod::NormalMethod) || (asyncMethodType == AsyncTaskMethod::Async2MethodThatCannotBeImplementedByTask)) + { + break; + } } } @@ -5162,6 +5430,9 @@ MethodTableBuilder::InitNewMethodDesc( if (NeedsNativeCodeSlot(pMethod)) pNewMD->SetHasNativeCodeSlot(); + if (pMethod->GetAsyncThunkType() != AsyncThunkType::NotAThunk) + pNewMD->SetIsAsyncThunkMethod(); + // Now we know the classification we can allocate the correct type of // method desc and perform any classification specific initialization. @@ -5189,6 +5460,12 @@ MethodTableBuilder::InitNewMethodDesc( strcpy_s((char *) pszDebugMethodNameCopy, len, pszDebugMethodName); #endif // _DEBUG + Signature sig; + if (pMethod->GetAsyncThunkType() != AsyncThunkType::NotAThunk) + { + sig = pMethod->GetMethodSignature().GetSignatureClass(); + } + // Do the init specific to each classification of MethodDesc & assign some common fields InitMethodDesc(pNewMD, pMethod->GetMethodType(), @@ -5198,7 +5475,9 @@ MethodTableBuilder::InitNewMethodDesc( FALSE, // fEnC pMethod->GetRVA(), GetMDImport(), - pName + pName, + sig, + pMethod->GetAsyncThunkType() COMMA_INDEBUG(pszDebugMethodNameCopy) COMMA_INDEBUG(GetDebugClassName()) COMMA_INDEBUG("") // FIX this happens on global methods, give better info @@ -6070,7 +6349,9 @@ MethodTableBuilder::InitMethodDesc( BOOL fEnC, DWORD RVA, // Only needed for NDirect case IMDInternalImport * pIMDII, // Needed for NDirect, EEImpl(Delegate) cases - LPCSTR pMethodName // Only needed for mcEEImpl (Delegate) case + LPCSTR pMethodName, // Only needed for mcEEImpl (Delegate) case + Signature sig, // Only needed for the Async2 Thunk case + AsyncThunkType thunkType COMMA_INDEBUG(LPCUTF8 pszDebugMethodName) COMMA_INDEBUG(LPCUTF8 pszDebugClassName) COMMA_INDEBUG(LPCUTF8 pszDebugMethodSignature) @@ -6249,6 +6530,13 @@ MethodTableBuilder::InitMethodDesc( else pNewMD->m_pszDebugMethodSignature = pszDebugMethodSignature; #endif // _DEBUG + + if (thunkType != AsyncThunkType::NotAThunk) + { + AsyncThunkData* pThunkData = pNewMD->GetAddrOfAsyncThunkData(); + pThunkData->sig = sig; + pThunkData->type = thunkType; + } } // MethodTableBuilder::InitMethodDesc //******************************************************************************* @@ -6973,6 +7261,9 @@ VOID MethodTableBuilder::AllocAndInitMethodDescs() if (NeedsNativeCodeSlot(*it)) size += sizeof(MethodDesc::NativeCodeSlot); + + if (it->GetAsyncThunkType() != AsyncThunkType::NotAThunk) + size += sizeof(AsyncThunkData); // See comment in AllocAndInitMethodDescChunk if (NeedsTightlyBoundUnboxingStub(*it)) diff --git a/src/coreclr/vm/methodtablebuilder.h b/src/coreclr/vm/methodtablebuilder.h index 29889045b44795..a83402618e3ad3 100644 --- a/src/coreclr/vm/methodtablebuilder.h +++ b/src/coreclr/vm/methodtablebuilder.h @@ -674,6 +674,31 @@ class MethodTableBuilder INDEBUG(CheckGetMethodAttributes();) } + //----------------------------------------------------------------------------------------- + // This constructor can be used with hard-coded signatures that are used for + // locating .ctor and .cctor methods. + MethodSignature( + Module * pModule, + mdToken tok, + Signature sig, + const Substitution * pSubst) + : m_pModule(pModule), + m_tok(tok), + m_szName(NULL), + m_pSig(sig.GetRawSig()), + m_cSig(sig.GetRawSigLen()), + m_pSubst(pSubst), + m_nameHash(INVALID_NAME_HASH) + { + CONTRACTL { + PRECONDITION(CheckPointer(pModule)); + PRECONDITION(TypeFromToken(tok) == mdtMethodDef || + TypeFromToken(tok) == mdtMemberRef); + PRECONDITION(CheckPointer(m_pSig)); + PRECONDITION(m_cSig != 0); + } CONTRACTL_END; + } + //----------------------------------------------------------------------------------------- // This constructor can be used with hard-coded signatures that are used for // locating .ctor and .cctor methods. @@ -745,6 +770,11 @@ class MethodTableBuilder GetSignature() const { WRAPPER_NO_CONTRACT; CheckGetMethodAttributes(); return m_pSig; } + //----------------------------------------------------------------------------------------- + // Returns the metadata signature for the method. + inline Signature GetSignatureClass() const + { WRAPPER_NO_CONTRACT; CheckGetMethodAttributes(); return Signature(m_pSig, (ULONG)m_cSig); } + //----------------------------------------------------------------------------------------- // Returns the signature length. inline size_t @@ -916,6 +946,22 @@ class MethodTableBuilder MethodClassification type, METHOD_IMPL_TYPE implType); + //----------------------------------------------------------------------------------------- + // Constructor. This takes all the information already extracted from metadata interface + // because the place that creates these types already has this data. Alternatively, + // a constructor could be written to take a token and metadata scope instead. Also, + // it might be interesting to move MethodClassification and METHOD_IMPL_TYPE to setter functions. + bmtMDMethod( + bmtMDType * pOwningType, + mdMethodDef tok, + DWORD dwDeclAttrs, + DWORD dwImplAttrs, + DWORD dwRVA, + Signature sig, + AsyncThunkType thunkType, + MethodClassification type, + METHOD_IMPL_TYPE implType); + //----------------------------------------------------------------------------------------- // Returns the type that owns the *declaration* of this method. This makes sure that a // method can be properly interpreted in the context of substitutions at any time. @@ -1016,6 +1062,12 @@ class MethodTableBuilder GetRVA() const { LIMITED_METHOD_CONTRACT; return m_dwRVA; } + AsyncThunkType GetAsyncThunkType() const + { + LIMITED_METHOD_CONTRACT; + return m_asyncThunkType; + } + private: //----------------------------------------------------------------------------------------- bmtMDType * m_pOwningType; @@ -1024,6 +1076,7 @@ class MethodTableBuilder DWORD m_dwImplAttrs; DWORD m_dwRVA; MethodClassification m_type; // Specific MethodDesc flavour + AsyncThunkType m_asyncThunkType; METHOD_IMPL_TYPE m_implType; // Whether or not the method is a methodImpl body MethodSignature m_methodSig; @@ -2613,7 +2666,9 @@ class MethodTableBuilder BOOL fEnC, DWORD RVA, // Only needed for NDirect case IMDInternalImport * pIMDII, // Needed for NDirect, EEImpl(Delegate) cases - LPCSTR pMethodName // Only needed for mcEEImpl (Delegate) case + LPCSTR pMethodName, // Only needed for mcEEImpl (Delegate) case + Signature sig, // Only needed for the async thunk (Async2 Thunk) case + AsyncThunkType thunkType COMMA_INDEBUG(LPCUTF8 pszDebugMethodName) COMMA_INDEBUG(LPCUTF8 pszDebugClassName) COMMA_INDEBUG(LPCUTF8 pszDebugMethodSignature)); From 783754de771547aa26cd85f9428b32c08ab383f3 Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Mon, 18 Sep 2023 13:30:07 -0700 Subject: [PATCH 002/203] more tweaks --- src/coreclr/vm/method.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/coreclr/vm/method.hpp b/src/coreclr/vm/method.hpp index 21cf16c3e61f9f..9a9931d26b00a8 100644 --- a/src/coreclr/vm/method.hpp +++ b/src/coreclr/vm/method.hpp @@ -51,7 +51,7 @@ GVAL_DECL(TADDR, g_MiniMetaDataBuffAddress); EXTERN_C VOID STDCALL NDirectImportThunk(); -#define METHOD_TOKEN_REMAINDER_BIT_COUNT 13 +#define METHOD_TOKEN_REMAINDER_BIT_COUNT 11 #define METHOD_TOKEN_REMAINDER_MASK ((1 << METHOD_TOKEN_REMAINDER_BIT_COUNT) - 1) #define METHOD_TOKEN_RANGE_BIT_COUNT (24 - METHOD_TOKEN_REMAINDER_BIT_COUNT) #define METHOD_TOKEN_RANGE_MASK ((1 << METHOD_TOKEN_RANGE_BIT_COUNT) - 1) @@ -2157,7 +2157,7 @@ class MethodDescChunk friend class CheckAsmOffsets; enum { - enum_flag_TokenRangeMask = 0x0FFF, // This must equal METHOD_TOKEN_RANGE_MASK calculated higher in this file + enum_flag_TokenRangeMask = 0x1FFF, // This must equal METHOD_TOKEN_RANGE_MASK calculated higher in this file // These are separate to allow the flags space available and used to be obvious here // and for the logic that splits the token to be algorithmically generated based on the // #define From 8c43a9a211a9a182e8019dc1aea70883ce1e8e4c Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Mon, 18 Sep 2023 15:40:04 -0700 Subject: [PATCH 003/203] It builds --- src/coreclr/vm/class.cpp | 3 ++- src/coreclr/vm/ilstubcache.cpp | 1 + src/coreclr/vm/methodtablebuilder.cpp | 11 +++++++++++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/coreclr/vm/class.cpp b/src/coreclr/vm/class.cpp index 296c6686529dbd..cda2d4ebd18f87 100644 --- a/src/coreclr/vm/class.cpp +++ b/src/coreclr/vm/class.cpp @@ -816,7 +816,8 @@ HRESULT EEClass::AddMethodDesc( 0, // RVA - non-zero only for NDirect pImport, NULL, - Signature() + Signature(), + AsyncThunkType::NotAThunk COMMA_INDEBUG(debug_szMethodName) COMMA_INDEBUG(pMT->GetDebugClassName()) COMMA_INDEBUG(NULL) diff --git a/src/coreclr/vm/ilstubcache.cpp b/src/coreclr/vm/ilstubcache.cpp index 4c955e322bb73e..c467281ba830f7 100644 --- a/src/coreclr/vm/ilstubcache.cpp +++ b/src/coreclr/vm/ilstubcache.cpp @@ -185,6 +185,7 @@ MethodDesc* ILStubCache::CreateNewMethodDesc(LoaderHeap* pCreationHeap, MethodTa mcDynamic, TRUE /* fNonVtableSlot */, TRUE /* fNativeCodeSlot */, + FALSE /* IsAsyncThunkMethod */, pMT, pamTracker); diff --git a/src/coreclr/vm/methodtablebuilder.cpp b/src/coreclr/vm/methodtablebuilder.cpp index f19b584842bf6c..4bf429f6f85b97 100644 --- a/src/coreclr/vm/methodtablebuilder.cpp +++ b/src/coreclr/vm/methodtablebuilder.cpp @@ -1026,6 +1026,7 @@ MethodTableBuilder::bmtMDMethod::bmtMDMethod( m_dwImplAttrs(dwImplAttrs), m_dwRVA(dwRVA), m_type(type), + m_asyncThunkType(AsyncThunkType::NotAThunk), m_implType(implType), m_methodSig(pOwningType->GetModule(), tok, @@ -1051,6 +1052,7 @@ MethodTableBuilder::bmtMDMethod::bmtMDMethod( DWORD dwImplAttrs, DWORD dwRVA, Signature sig, + AsyncThunkType thunkType, MethodClassification type, METHOD_IMPL_TYPE implType) : m_pOwningType(pOwningType), @@ -1058,6 +1060,7 @@ MethodTableBuilder::bmtMDMethod::bmtMDMethod( m_dwImplAttrs(dwImplAttrs), m_dwRVA(dwRVA), m_type(type), + m_asyncThunkType(thunkType), m_implType(implType), m_methodSig(pOwningType->GetModule(), tok, @@ -3544,11 +3547,18 @@ MethodTableBuilder::EnumerateClassMethods() else { ULONG cAsyncThunkMemberSignature = cMemberSignature; + AsyncThunkType thunkType; if (asyncMethodType == AsyncTaskMethod::Async2Method) + { cAsyncThunkMemberSignature += 1; + thunkType = AsyncThunkType::AsyncToTask; + } else + { cAsyncThunkMemberSignature -= 1; + thunkType = AsyncThunkType::TaskToAsync; + } BYTE* pNewMemberSignature = AllocateFromHighFrequencyHeap(S_SIZE_T(cAsyncThunkMemberSignature)); ULONG tokenLen = CorSigUncompressedDataSize(&pMemberSignature[offsetOfAsyncDetails + 1]); @@ -3577,6 +3587,7 @@ MethodTableBuilder::EnumerateClassMethods() dwImplFlags, dwMethodRVA, newMemberSig, + thunkType, type, implType); } From 545509cd35208eaad1b1166481c01fbde6a8e7e3 Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Tue, 19 Sep 2023 16:17:17 -0700 Subject: [PATCH 004/203] ItCompilesButConvertingFromAsyncVariantsIsntRight --- src/coreclr/debug/ee/functioninfo.cpp | 2 +- src/coreclr/inc/corcompile.h | 1 + src/coreclr/vm/clrtocomcall.cpp | 10 +++++ src/coreclr/vm/clsload.cpp | 2 +- src/coreclr/vm/commodule.cpp | 4 +- src/coreclr/vm/commtmemberinfomap.cpp | 7 ++++ src/coreclr/vm/comtoclrcall.cpp | 5 ++- src/coreclr/vm/dispatchinfo.cpp | 15 +++++++ src/coreclr/vm/dllimport.cpp | 22 +++++++++++ src/coreclr/vm/encee.cpp | 1 + src/coreclr/vm/genericdict.cpp | 17 ++++++++ src/coreclr/vm/genmeth.cpp | 56 ++++++++++++++++++--------- src/coreclr/vm/instmethhash.cpp | 10 ++++- src/coreclr/vm/instmethhash.h | 3 +- src/coreclr/vm/interoputil.cpp | 7 ++++ src/coreclr/vm/jitinterface.cpp | 4 ++ src/coreclr/vm/memberload.cpp | 1 + src/coreclr/vm/method.cpp | 10 ++++- src/coreclr/vm/method.hpp | 17 +++++++- src/coreclr/vm/methoditer.cpp | 19 ++++++++- src/coreclr/vm/methoditer.h | 4 ++ src/coreclr/vm/methodtable.cpp | 56 ++++++++++++++++++++++++--- src/coreclr/vm/methodtable.h | 3 +- src/coreclr/vm/methodtablebuilder.cpp | 13 ++++++- src/coreclr/vm/methodtablebuilder.h | 27 ++++++++++++- src/coreclr/vm/multicorejit.cpp | 6 +++ src/coreclr/vm/readytoruninfo.cpp | 8 ++++ src/coreclr/vm/zapsig.cpp | 5 ++- 28 files changed, 295 insertions(+), 40 deletions(-) diff --git a/src/coreclr/debug/ee/functioninfo.cpp b/src/coreclr/debug/ee/functioninfo.cpp index fad9ac786ba714..91c5753ea432a2 100644 --- a/src/coreclr/debug/ee/functioninfo.cpp +++ b/src/coreclr/debug/ee/functioninfo.cpp @@ -2035,7 +2035,7 @@ void DebuggerMethodInfo::CreateDJIsForNativeBlobs(AppDomain * pAppDomain, Module // have DJIs for every verision of a method that was EnCed. // This also handles the possibility of getting the same methoddesc back from the iterator. // It also lets EnC + generics play nice together (including if an generic method was EnC-ed) - LoadedMethodDescIterator it(pAppDomain, m_module, m_token); + LoadedMethodDescIterator it(pAppDomain, m_module, m_token, /* fIsAsyncThunk */false); // TODO! Debugger doesn't handle async thunks now CollectibleAssemblyHolder pDomainAssembly; while (it.Next(pDomainAssembly.This())) { diff --git a/src/coreclr/inc/corcompile.h b/src/coreclr/inc/corcompile.h index e4baf3423fca28..f819dec6d3feb6 100644 --- a/src/coreclr/inc/corcompile.h +++ b/src/coreclr/inc/corcompile.h @@ -182,6 +182,7 @@ enum EncodeMethodSigFlags ENCODE_METHOD_SIG_Constrained = 0x20, ENCODE_METHOD_SIG_OwnerType = 0x40, ENCODE_METHOD_SIG_UpdateContext = 0x80, + ENCODE_METHOD_SIG_AsyncThunk = 0x100, }; enum EncodeFieldSigFlags diff --git a/src/coreclr/vm/clrtocomcall.cpp b/src/coreclr/vm/clrtocomcall.cpp index c604a6c8a90116..d415db47f5febe 100644 --- a/src/coreclr/vm/clrtocomcall.cpp +++ b/src/coreclr/vm/clrtocomcall.cpp @@ -230,6 +230,11 @@ I4ARRAYREF SetUpWrapperInfo(MethodDesc *pMD) GCX_PREEMP(); + if (pMD->IsAsyncThunkMethod()) + { + ThrowHR(COR_E_NOTSUPPORTED); + } + // Collects ParamDef information in an indexed array where element 0 represents // the return type. mdParamDef *params = (mdParamDef*)_alloca((numArgs+1) * sizeof(mdParamDef)); @@ -504,6 +509,11 @@ UINT32 CLRToCOMLateBoundWorker( LPCUTF8 strMemberName; ULONG uSemantic; + if (pItfMD->IsAsyncThunkMethod()) + { + ThrowHR(COR_E_NOTSUPPORTED); + } + // See if there is property information for this member. hr = pItfMT->GetModule()->GetPropertyInfoForMethodDef(pItfMD->GetMemberDef(), &propToken, &strMemberName, &uSemantic); if (hr != S_OK) diff --git a/src/coreclr/vm/clsload.cpp b/src/coreclr/vm/clsload.cpp index 8df5691cbafbe7..2fd13442f2ae1a 100644 --- a/src/coreclr/vm/clsload.cpp +++ b/src/coreclr/vm/clsload.cpp @@ -3083,7 +3083,7 @@ TypeHandle ClassLoader::PublishType(TypeKey *pTypeKey, TypeHandle typeHnd) { MethodDesc * pMD = it.GetMethodDesc(); CONSISTENCY_CHECK(pMD != NULL && pMD->GetMethodTable() == pMT); - if (!pMD->IsUnboxingStub() && (!pMD->IsEEImpl() || pMD->GetMethodTable()->IsDelegate())) + if (!pMD->IsUnboxingStub() && !pMD->IsAsyncThunkMethod()) { pModule->EnsuredStoreMethodDef(pMD->GetMemberDef(), pMD); } diff --git a/src/coreclr/vm/commodule.cpp b/src/coreclr/vm/commodule.cpp index 46e66b5e4a6d75..6096eb74409fc3 100644 --- a/src/coreclr/vm/commodule.cpp +++ b/src/coreclr/vm/commodule.cpp @@ -302,7 +302,7 @@ extern "C" INT32 QCALLTYPE ModuleBuilder_GetMemberRefOfMethodInfo(QCall::ModuleH COMPlusThrow(kNotSupportedException); } - if (pMeth->GetMethodTable()->GetModule() == pModule) + if ((pMeth->GetMethodTable()->GetModule() == pModule) && !pMeth->IsAsyncThunkMethod()) { // If the passed in method is defined in the same module, just return the MethodDef token memberRefE = pMeth->GetMemberDef(); @@ -317,7 +317,7 @@ extern "C" INT32 QCALLTYPE ModuleBuilder_GetMemberRefOfMethodInfo(QCall::ModuleH ULONG cbComSig; PCCOR_SIGNATURE pvComSig; - IfFailThrow(pMeth->GetMDImport()->GetSigOfMethodDef(pMeth->GetMemberDef(), &cbComSig, &pvComSig)); + pMeth->GetSig(&pvComSig, &cbComSig); // Translate the method sig into this scope Assembly * pRefedAssembly = pMeth->GetModule()->GetAssembly(); diff --git a/src/coreclr/vm/commtmemberinfomap.cpp b/src/coreclr/vm/commtmemberinfomap.cpp index 8bc185e9d81a4c..015aab987d5aa4 100644 --- a/src/coreclr/vm/commtmemberinfomap.cpp +++ b/src/coreclr/vm/commtmemberinfomap.cpp @@ -688,6 +688,9 @@ void ComMTMemberInfoMap::GetMethodPropsForMeth( // Generally don't munge function into a getter. rProps[ix].bFunction2Getter = FALSE; + if (pMeth->IsAsyncThunkMethod()) + ThrowHR(COR_E_NOTSUPPORTED); + // See if there is property information for this member. hr = pMeth->GetModule()->GetPropertyInfoForMethodDef(pMeth->GetMemberDef(), &pd, &pPropName, &uSemantic); IfFailThrow(hr); @@ -1604,6 +1607,10 @@ void ComMTMemberInfoMap::PopulateMemberHashtable() // We are dealing with a method. MethodDesc *pMD = pProps->pMeth; + if (pMD->IsAsyncThunkMethod()) + { + ThrowHR(COR_E_NOTSUPPORTED); // Probably this isn't right, and instead should be a skip, but a throw makes it easier to find if this is wrong + } EEModuleTokenPair Key(pMD->GetMemberDef(), pMD->GetModule()); m_TokenToComMTMethodPropsMap.InsertValue(&Key, (HashDatum)pProps); } diff --git a/src/coreclr/vm/comtoclrcall.cpp b/src/coreclr/vm/comtoclrcall.cpp index 215b6c61bb5812..74a68831ce759b 100644 --- a/src/coreclr/vm/comtoclrcall.cpp +++ b/src/coreclr/vm/comtoclrcall.cpp @@ -1047,7 +1047,10 @@ void ComCallMethodDesc::InitNativeInfo() MethodTable * pMT = pMD->GetMethodTable(); IMDInternalImport * pInternalImport = pMT->GetMDImport(); - + if (pMD->IsAsyncThunkMethod()) + { + ThrowHR(COR_E_NOTSUPPORTED); + } mdMethodDef md = pMD->GetMemberDef(); ULONG ulCodeRVA; diff --git a/src/coreclr/vm/dispatchinfo.cpp b/src/coreclr/vm/dispatchinfo.cpp index eb0c83f7a6ce56..673aa783d532de 100644 --- a/src/coreclr/vm/dispatchinfo.cpp +++ b/src/coreclr/vm/dispatchinfo.cpp @@ -448,7 +448,13 @@ ComMTMethodProps * DispatchMemberInfo::GetMemberProps(OBJECTREF MemberInfoObj, C ARG_SLOT GetMethodHandleArg = ObjToArgSlot(MemberInfoObj); MethodDesc* pMeth = (MethodDesc*) getMethodHandle.Call_RetLPVOID(&GetMethodHandleArg); if (pMeth) + { + if (pMeth->IsAsyncThunkMethod()) + { + ThrowHR(COR_E_NOTSUPPORTED); + } pMemberProps = pMemberMap->GetMethodProps(pMeth->GetMemberDef(), pMeth->GetModule()); + } } else if (CoreLibBinder::IsClass(pMemberInfoClass, CLASS__RT_FIELD_INFO)) { @@ -844,6 +850,10 @@ void DispatchMemberInfo::SetUpMethodMarshalerInfo(MethodDesc *pMD, BOOL bReturnV // // Initialize the parameter definition enum. // + if (pMD->IsAsyncThunkMethod()) + { + ThrowHR(COR_E_NOTSUPPORTED); + } hEnumParams.EnumInit(mdtParamDef, pMD->GetMemberDef()); // @@ -2579,6 +2589,11 @@ bool DispatchInfo::IsPropertyAccessorVisible(bool fIsSetter, OBJECTREF* pMemberI // Check to see if the new method is a property accessor. mdToken tkMember = mdTokenNil; MethodTable *pDeclaringMT = pMDForProperty->GetMethodTable(); + if (pMDForProperty->IsAsyncThunkMethod()) + { + return false; + } + if (pMDForProperty->GetModule()->GetPropertyInfoForMethodDef(pMDForProperty->GetMemberDef(), &tkMember, NULL, NULL) == S_OK) { if (IsMemberVisibleFromCom(pDeclaringMT, tkMember, pMDForProperty->GetMemberDef())) diff --git a/src/coreclr/vm/dllimport.cpp b/src/coreclr/vm/dllimport.cpp index c0f90289a01682..a21796d7b1d5d9 100644 --- a/src/coreclr/vm/dllimport.cpp +++ b/src/coreclr/vm/dllimport.cpp @@ -105,6 +105,8 @@ StubSigDesc::StubSigDesc(MethodDesc *pMD) m_sig = pMD->GetSignature(); m_pModule = pMD->GetModule(); // Used for token resolution. + _ASSERTE(!pMD->IsAsyncThunkMethod()); + m_tkMethodDef = pMD->GetMemberDef(); SigTypeContext::InitTypeContext(pMD, &m_typeContext); m_pMetadataModule = pMD->GetModule(); @@ -132,6 +134,7 @@ StubSigDesc::StubSigDesc(MethodDesc* pMD, const Signature& sig, Module* pModule) if (pMD != NULL) { + _ASSERTE(!pMD->IsAsyncThunkMethod()); m_tkMethodDef = pMD->GetMemberDef(); SigTypeContext::InitTypeContext(pMD, &m_typeContext); m_pMetadataModule = pMD->GetModule(); @@ -1055,7 +1058,10 @@ class ILStubState : public StubState DWORD dwToken = 0; if (pTargetMD) + { + _ASSERTE(!pTargetMD->IsAsyncThunkMethod()); dwToken = pTargetMD->GetMemberDef(); + } // @@ -2713,6 +2719,10 @@ void PInvokeStaticSigInfo::DllImportInit( IMDInternalImport *pInternalImport = pMD->GetMDImport(); CorPinvokeMap mappingFlags = pmMaxValue; mdModuleRef modref = mdModuleRefNil; + if (pMD->IsAsyncThunkMethod()) + { + ThrowHR(COR_E_NOTSUPPORTED); + } if (FAILED(pInternalImport->GetPinvokeMap(pMD->GetMemberDef(), (DWORD*)&mappingFlags, ppEntryPointName, &modref))) { InitCallConv(CallConvWinApiSentinel, pMD); @@ -2998,6 +3008,10 @@ namespace CorInfoCallConvExtension callConvLocal; IMDInternalImport* pInternalImport = pMD->GetMDImport(); CorPinvokeMap mappingFlags = pmMaxValue; + if (pMD->IsAsyncThunkMethod()) + { + ThrowHR(COR_E_NOTSUPPORTED); + } HRESULT hr = pInternalImport->GetPinvokeMap(pMD->GetMemberDef(), (DWORD*)&mappingFlags, NULL /*pszImportName*/, NULL /*pmrImportDLL*/); if (FAILED(hr)) return false; @@ -3262,6 +3276,10 @@ BOOL NDirect::MarshalingRequired( mdMethodDef methodToken = mdMethodDefNil; if (pMD != NULL) { + if (pMD->IsAsyncThunkMethod()) + { + ThrowHR(COR_E_NOTSUPPORTED); + } methodToken = pMD->GetMemberDef(); } CollateParamTokens(pMDImport, methodToken, numArgs - 1, pParamTokenArray); @@ -6051,6 +6069,10 @@ PCODE GetILStubForCalli(VASigCookie *pVASigCookie, MethodDesc *pMD) { PInvokeStaticSigInfo sigInfo(pMD); + if (pMD->IsAsyncThunkMethod()) + { + ThrowHR(COR_E_NOTSUPPORTED); + } md = pMD->GetMemberDef(); nlFlags = sigInfo.GetLinkFlags(); nlType = sigInfo.GetCharSet(); diff --git a/src/coreclr/vm/encee.cpp b/src/coreclr/vm/encee.cpp index eef50b18936a2d..3c9054c002f6f5 100644 --- a/src/coreclr/vm/encee.cpp +++ b/src/coreclr/vm/encee.cpp @@ -343,6 +343,7 @@ HRESULT EditAndContinueModule::UpdateMethod(MethodDesc *pMethod) appDomain, module, tkMethod, + pMethod->IsAsyncThunkMethod(), AssemblyIterationFlags(kIncludeLoaded | kIncludeExecution)); CollectibleAssemblyHolder pDomainAssembly; while (it.Next(pDomainAssembly.This())) diff --git a/src/coreclr/vm/genericdict.cpp b/src/coreclr/vm/genericdict.cpp index 09dcda4b11ebc3..abd477d0401ec1 100644 --- a/src/coreclr/vm/genericdict.cpp +++ b/src/coreclr/vm/genericdict.cpp @@ -891,6 +891,7 @@ Dictionary::PopulateEntry( uint32_t methodSlot = -1; BOOL fRequiresDispatchStub = 0; + BOOL isAsyncThunk = 0; if (isReadyToRunModule) { @@ -902,6 +903,7 @@ Dictionary::PopulateEntry( isInstantiatingStub = ((methodFlags & ENCODE_METHOD_SIG_InstantiatingStub) != 0) || (kind == MethodEntrySlot); isUnboxingStub = ((methodFlags & ENCODE_METHOD_SIG_UnboxingStub) != 0); fMethodNeedsInstantiation = ((methodFlags & ENCODE_METHOD_SIG_MethodInstantiation) != 0); + isAsyncThunk = ((methodFlags & ENCODE_METHOD_SIG_AsyncThunk) != 0); if (methodFlags & ENCODE_METHOD_SIG_OwnerType) { @@ -952,6 +954,10 @@ Dictionary::PopulateEntry( _ASSERTE(pZapSigContext->pInfoModule->IsFullModule()); pMethod = MemberLoader::GetMethodDescFromMethodDef(static_cast(pZapSigContext->pInfoModule), TokenFromRid(rid, mdtMethodDef), FALSE); } + if (isAsyncThunk) + { + pMethod = pMethod->GetAsyncOtherVariant(); + } } if (ownerType.IsNull()) @@ -995,6 +1001,7 @@ Dictionary::PopulateEntry( isInstantiatingStub = ((methodFlags & ENCODE_METHOD_SIG_InstantiatingStub) != 0); isUnboxingStub = ((methodFlags & ENCODE_METHOD_SIG_UnboxingStub) != 0); fMethodNeedsInstantiation = ((methodFlags & ENCODE_METHOD_SIG_MethodInstantiation) != 0); + isAsyncThunk = ((methodFlags & ENCODE_METHOD_SIG_AsyncThunk) != 0); if ((methodFlags & ENCODE_METHOD_SIG_SlotInsteadOfToken) != 0) { @@ -1036,11 +1043,19 @@ Dictionary::PopulateEntry( // The RID map should have been filled out if we fully loaded the class pMethod = pMethodDefMT->GetModule()->LookupMethodDef(token); + + if (isAsyncThunk) + { + pMethod = pMethod->GetAsyncOtherVariant(); + } + _ASSERTE(pMethod != NULL); pMethod->CheckRestore(); } } + _ASSERTE((!!isAsyncThunk) == pMethod->IsAsyncThunkMethod()); + if (fRequiresDispatchStub) { // Generate a dispatch stub and store it in the dictionary. @@ -1116,6 +1131,8 @@ Dictionary::PopulateEntry( inst, (!isInstantiatingStub && !isUnboxingStub)); + _ASSERTE((!!isAsyncThunk) == pMethod->IsAsyncThunkMethod()); + if (kind == ConstrainedMethodEntrySlot) { if (isReadyToRunModule) diff --git a/src/coreclr/vm/genmeth.cpp b/src/coreclr/vm/genmeth.cpp index 029e379305e4c2..021d9e48016595 100644 --- a/src/coreclr/vm/genmeth.cpp +++ b/src/coreclr/vm/genmeth.cpp @@ -245,6 +245,7 @@ static MethodDesc * FindTightlyBoundWrappedMethodDesc_DEBUG(MethodDesc * pMD) mdMethodDef methodDef = pMD->GetMemberDef(); Module *pModule = pMD->GetModule(); + bool isAsyncThunkMethod = pMD->IsAsyncThunkMethod(); MethodTable::MethodIterator it(pMD->GetCanonicalMethodTable()); it.MoveToEnd(); @@ -255,7 +256,8 @@ static MethodDesc * FindTightlyBoundWrappedMethodDesc_DEBUG(MethodDesc * pMD) if (pCurMethod && !pCurMethod->IsUnboxingStub()) { if ((pCurMethod->GetMemberDef() == methodDef) && - (pCurMethod->GetModule() == pModule)) + (pCurMethod->GetModule() == pModule) && + (pCurMethod->IsAsyncThunkMethod() == isAsyncThunkMethod)) { return pCurMethod; } @@ -283,6 +285,7 @@ static MethodDesc * FindTightlyBoundUnboxingStub_DEBUG(MethodDesc * pMD) mdMethodDef methodDef = pMD->GetMemberDef(); Module *pModule = pMD->GetModule(); + bool isAsyncThunkMethod = pMD->IsAsyncThunkMethod(); MethodTable::MethodIterator it(pMD->GetCanonicalMethodTable()); it.MoveToEnd(); @@ -291,7 +294,8 @@ static MethodDesc * FindTightlyBoundUnboxingStub_DEBUG(MethodDesc * pMD) MethodDesc* pCurMethod = it.GetMethodDesc(); if (pCurMethod && pCurMethod->IsUnboxingStub()) { if ((pCurMethod->GetMemberDef() == methodDef) && - (pCurMethod->GetModule() == pModule)) { + (pCurMethod->GetModule() == pModule) && + (pCurMethod->IsAsyncThunkMethod() == isAsyncThunkMethod)) { return pCurMethod; } } @@ -357,7 +361,8 @@ InstantiatedMethodDesc::NewInstantiatedMethodDesc(MethodTable *pExactMT, pNewMD = FindLoadedInstantiatedMethodDesc(pExactMT, pGenericMDescInRepMT->GetMemberDef(), methodInst, - getWrappedCode); + getWrappedCode, + pGenericMDescInRepMT->IsAsyncThunkMethod()); // Crst goes out of scope here // We don't need to hold the crst while we build the MethodDesc, but we reacquire it later @@ -476,7 +481,8 @@ InstantiatedMethodDesc::NewInstantiatedMethodDesc(MethodTable *pExactMT, InstantiatedMethodDesc *pOldMD = FindLoadedInstantiatedMethodDesc(pExactMT, pGenericMDescInRepMT->GetMemberDef(), methodInst, - getWrappedCode); + getWrappedCode, + pGenericMDescInRepMT->IsAsyncThunkMethod()); if (pOldMD == NULL) { @@ -550,7 +556,8 @@ InstantiatedMethodDesc::FindOrCreateExactClassMethod(MethodTable *pExactMT, InstantiatedMethodDesc *pInstMD = FindLoadedInstantiatedMethodDesc(pExactMT, pCanonicalMD->GetMemberDef(), Instantiation(), - FALSE); + FALSE, + pCanonicalMD->IsAsyncThunkMethod()); if (pInstMD == NULL) { @@ -572,7 +579,8 @@ InstantiatedMethodDesc* InstantiatedMethodDesc::FindLoadedInstantiatedMethodDesc(MethodTable *pExactOrRepMT, mdMethodDef methodDef, Instantiation methodInst, - BOOL getWrappedCode) + BOOL getWrappedCode, + BOOL asyncThunk) { CONTRACT(InstantiatedMethodDesc *) { @@ -606,7 +614,8 @@ InstantiatedMethodDesc::FindLoadedInstantiatedMethodDesc(MethodTable *pExactOrRe methodDef, FALSE /* not forceBoxedEntryPoint */, methodInst, - getWrappedCode); + getWrappedCode, + asyncThunk); if (resultMD != NULL) RETURN((InstantiatedMethodDesc*) resultMD); @@ -731,6 +740,7 @@ MethodDesc::FindOrCreateAssociatedMethodDesc(MethodDesc* pDefMD, BOOL allowInstParam, BOOL forceRemotableMethod, BOOL allowCreate, + AsyncVariantLookup asyncVariantLookup, ClassLoadLevel level) { CONTRACT(MethodDesc*) @@ -767,7 +777,8 @@ MethodDesc::FindOrCreateAssociatedMethodDesc(MethodDesc* pDefMD, if (!pDefMD->HasClassOrMethodInstantiation() && methodInst.IsEmpty() && !forceBoxedEntryPoint && - !pDefMD->IsUnboxingStub()) + !pDefMD->IsUnboxingStub() && + asyncVariantLookup == AsyncVariantLookup::MatchingAsyncVariant) { // Make sure that pDefMD->GetMethodTable() and pExactMT are related types even // if we took the fast path. @@ -796,7 +807,7 @@ MethodDesc::FindOrCreateAssociatedMethodDesc(MethodDesc* pDefMD, COMPlusThrowHR(COR_E_TYPELOAD); } - if (pDefMD->HasClassOrMethodInstantiation() || !methodInst.IsEmpty()) + if (pDefMD->HasClassOrMethodInstantiation() || !methodInst.IsEmpty() || asyncVariantLookup == AsyncVariantLookup::AsyncOtherVariant) { // General checks related to generics: arity (if any) must match and generic method // instantiation (if any) must be well-formed. @@ -806,7 +817,7 @@ MethodDesc::FindOrCreateAssociatedMethodDesc(MethodDesc* pDefMD, COMPlusThrowHR(COR_E_BADIMAGEFORMAT); } - pMDescInCanonMT = pExactMT->GetCanonicalMethodTable()->GetParallelMethodDesc(pDefMD); + pMDescInCanonMT = pExactMT->GetCanonicalMethodTable()->GetParallelMethodDesc(pDefMD, asyncVariantLookup); if (!allowCreate && !pMDescInCanonMT->GetMethodTable()->IsFullyLoaded()) { @@ -882,7 +893,8 @@ MethodDesc::FindOrCreateAssociatedMethodDesc(MethodDesc* pDefMD, methodDef, TRUE /* forceBoxedEntryPoint */, Instantiation(), - FALSE /* no inst param */); + FALSE /* no inst param */, + pMDescInCanonMT->IsAsyncThunkMethod()); // If we didn't find it then create it... if (!pResultMD) @@ -900,7 +912,8 @@ MethodDesc::FindOrCreateAssociatedMethodDesc(MethodDesc* pDefMD, methodDef, TRUE, Instantiation(), - FALSE); + FALSE, + pMDescInCanonMT->IsAsyncThunkMethod()); if (pResultMD == NULL) { AllocMemTracker amt; @@ -945,7 +958,8 @@ MethodDesc::FindOrCreateAssociatedMethodDesc(MethodDesc* pDefMD, methodDef, TRUE, /* forceBoxedEntryPoint */ methodInst, - FALSE /* no inst param */); + FALSE /* no inst param */, + pMDescInCanonMT->IsAsyncThunkMethod()); if (!pResultMD) { @@ -962,11 +976,12 @@ MethodDesc::FindOrCreateAssociatedMethodDesc(MethodDesc* pDefMD, pExactMT, FALSE /* not Unboxing */, methodInst, - FALSE); + FALSE, FALSE, TRUE, asyncVariantLookup); _ASSERTE(pNonUnboxingStub->GetClassification() == mcInstantiated); _ASSERTE(!pNonUnboxingStub->RequiresInstArg()); _ASSERTE(!pNonUnboxingStub->IsUnboxingStub()); + _ASSERTE(pNonUnboxingStub->IsAsyncThunkMethod() == pMDescInCanonMT->IsAsyncThunkMethod()); // Enter the critical section *after* we've found or created the non-unboxing instantiating stub (else we'd have a race, // and its possible that the non-unboxing instantiating stub may be in a different loader module than pLoaderModule @@ -978,7 +993,8 @@ MethodDesc::FindOrCreateAssociatedMethodDesc(MethodDesc* pDefMD, methodDef, TRUE, /* forceBoxedEntryPoint */ methodInst, - FALSE /* no inst param */); + FALSE /* no inst param */, + pNonUnboxingStub->IsAsyncThunkMethod()); if (pResultMD == NULL) { @@ -1118,7 +1134,8 @@ MethodDesc::FindOrCreateAssociatedMethodDesc(MethodDesc* pDefMD, InstantiatedMethodDesc::FindLoadedInstantiatedMethodDesc(pExactMT->GetCanonicalMethodTable(), methodDef, Instantiation(repInst, methodInst.GetNumArgs()), - TRUE); + TRUE, + pMDescInCanonMT->IsAsyncThunkMethod()); // No - so create one. if (pInstMD == NULL) @@ -1142,7 +1159,8 @@ MethodDesc::FindOrCreateAssociatedMethodDesc(MethodDesc* pDefMD, InstantiatedMethodDesc::FindLoadedInstantiatedMethodDesc(pExactMT, methodDef, methodInst, - FALSE); + FALSE, + pMDescInCanonMT->IsAsyncThunkMethod()); // No - so create one. Go fetch the shared one first if (pInstMD == NULL) @@ -1161,6 +1179,7 @@ MethodDesc::FindOrCreateAssociatedMethodDesc(MethodDesc* pDefMD, /* allowInstParam */ TRUE, /* forceRemotableMethod */ FALSE, /* allowCreate */ TRUE, + asyncVariantLookup, /* level */ level); _ASSERTE(pWrappedMD->IsSharedByGenericInstantiations()); @@ -1181,7 +1200,8 @@ MethodDesc::FindOrCreateAssociatedMethodDesc(MethodDesc* pDefMD, InstantiatedMethodDesc::FindLoadedInstantiatedMethodDesc(pExactMT, methodDef, methodInst, - FALSE); + FALSE, + pMDescInCanonMT->IsAsyncThunkMethod()); // No - so create one. if (pInstMD == NULL) diff --git a/src/coreclr/vm/instmethhash.cpp b/src/coreclr/vm/instmethhash.cpp index 2cb27b86fa9df4..ac563cb7c498fe 100644 --- a/src/coreclr/vm/instmethhash.cpp +++ b/src/coreclr/vm/instmethhash.cpp @@ -129,7 +129,8 @@ MethodDesc* InstMethodHashTable::FindMethodDesc(TypeHandle declaringType, mdMethodDef token, BOOL unboxingStub, Instantiation inst, - BOOL getSharedNotStub) + BOOL getSharedNotStub, + bool isAsyncThunk) { CONTRACTL { @@ -171,6 +172,11 @@ MethodDesc* InstMethodHashTable::FindMethodDesc(TypeHandle declaringType, continue; // Next iteration of the for loop } + if (pMD->IsAsyncThunkMethod() != isAsyncThunk) + { + continue; + } + if (!inst.IsEmpty()) { Instantiation candidateInst = pMD->GetMethodInstantiation(); @@ -213,7 +219,7 @@ BOOL InstMethodHashTable::ContainsMethodDesc(MethodDesc* pMD) return FindMethodDesc( pMD->GetMethodTable(), pMD->GetMemberDef(), pMD->IsUnboxingStub(), - pMD->GetMethodInstantiation(), pMD->RequiresInstArg()) != NULL; + pMD->GetMethodInstantiation(), pMD->RequiresInstArg(), pMD->IsAsyncThunkMethod()) != NULL; } #endif // #ifndef DACCESS_COMPILE diff --git a/src/coreclr/vm/instmethhash.h b/src/coreclr/vm/instmethhash.h index 153bbe4740846b..8d03d000c3d5ff 100644 --- a/src/coreclr/vm/instmethhash.h +++ b/src/coreclr/vm/instmethhash.h @@ -109,7 +109,8 @@ class InstMethodHashTable : public DacEnumerableHashTable &rDef { pDeclaringMT = pProps->pMeth->GetMethodTable(); tkMb = pProps->pMeth->GetMemberDef(); + if (pProps->pMeth->IsAsyncThunkMethod()) + { + ThrowHR(COR_E_NOTSUPPORTED); + } cbCur = GetStringizedMethodDef(pDeclaringMT, tkMb, rDef, cbCur); } else @@ -2555,6 +2559,9 @@ BOOL IsMethodVisibleFromCom(MethodDesc *pMD) mdProperty pd; LPCUTF8 pPropName; ULONG uSemantic; + if (pMD->IsAsyncThunkMethod()) + return false; + mdMethodDef md = pMD->GetMemberDef(); // See if there is property information for this member. diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index 9471239e25a87a..57eb2a0f04c296 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -3353,6 +3353,10 @@ void CEEInfo::ComputeRuntimeLookupForSharedGenericToken(DictionaryEntryKind entr methodFlags |= ENCODE_METHOD_SIG_SlotInsteadOfToken; } + if (pTemplateMD->IsAsyncThunkMethod()) + { + methodFlags |= ENCODE_METHOD_SIG_AsyncThunk; + } sigBuilder.AppendData(methodFlags); diff --git a/src/coreclr/vm/memberload.cpp b/src/coreclr/vm/memberload.cpp index a329702e3a56c0..08ea7703c85a40 100644 --- a/src/coreclr/vm/memberload.cpp +++ b/src/coreclr/vm/memberload.cpp @@ -781,6 +781,7 @@ MemberLoader::GetMethodDescFromMemberDefOrRefOrSpec( allowInstParam, /* forceRemotableMethod */ FALSE, /* allowCreate */ TRUE, + AsyncVariantLookup::MatchingAsyncVariant, /* level */ owningTypeLoadLevel); } // MemberLoader::GetMethodDescFromMemberDefOrRefOrSpec diff --git a/src/coreclr/vm/method.cpp b/src/coreclr/vm/method.cpp index 63b099dc54db58..bf1ad0ed150b1a 100644 --- a/src/coreclr/vm/method.cpp +++ b/src/coreclr/vm/method.cpp @@ -405,6 +405,13 @@ void MethodDesc::GetSig(PCCOR_SIGNATURE *ppSig, DWORD *pcSig) return; } } + if (IsAsyncThunkMethod()) + { + Signature sig = GetAsyncThunkData().sig; + *ppSig = sig.GetRawSig(); + *pcSig = sig.GetRawSigLen(); + return; + } GetSigFromMetadata(GetMDImport(), ppSig, pcSig); PREFIX_ASSUME(*ppSig != NULL); @@ -433,6 +440,7 @@ void MethodDesc::GetSigFromMetadata(IMDInternalImport * importer, } CONTRACTL_END + _ASSERTE(!IsAsyncThunkMethod()); if (FAILED(importer->GetSigOfMethodDef(GetMemberDef(), pcSig, ppSig))) { // Class loader already asked for signature, so this should always succeed (unless there's a // bug or a new code path) @@ -680,7 +688,7 @@ BOOL MethodDesc::HasSameMethodDefAs(MethodDesc * pMD) if (this == pMD) return TRUE; - return (GetMemberDef() == pMD->GetMemberDef()) && (GetModule() == pMD->GetModule()); + return (GetMemberDef() == pMD->GetMemberDef()) && (GetModule() == pMD->GetModule() && pMD->IsAsyncThunkMethod() == IsAsyncThunkMethod()); } //******************************************************************************* diff --git a/src/coreclr/vm/method.hpp b/src/coreclr/vm/method.hpp index 9a9931d26b00a8..f4ae0b4342b028 100644 --- a/src/coreclr/vm/method.hpp +++ b/src/coreclr/vm/method.hpp @@ -184,6 +184,12 @@ enum MethodDescClassification #define METHOD_MAX_RVA 0x7FFFFFFF +enum class AsyncVariantLookup +{ + MatchingAsyncVariant = 0, + AsyncOtherVariant +}; + // The size of this structure needs to be a multiple of MethodDesc::ALIGNMENT // @@ -1524,6 +1530,7 @@ class MethodDesc BOOL allowInstParam, BOOL forceRemotableMethod = FALSE, BOOL allowCreate = TRUE, + AsyncVariantLookup variantLookup = AsyncVariantLookup::MatchingAsyncVariant, ClassLoadLevel level = CLASS_LOADED); // Normalize methoddesc for reflection @@ -1531,6 +1538,11 @@ class MethodDesc TypeHandle instType, Instantiation methodInst); + MethodDesc* GetAsyncOtherVariant() + { + return GetMethodTable()->GetParallelMethodDesc(this, AsyncVariantLookup::AsyncOtherVariant); + } + // True if a MD is an funny BoxedEntryPointStub (not from the method table) or // an MD for a generic instantiation...In other words the MethodDescs and the // MethodTable are guaranteed to be "tightly-knit", i.e. if one is present in @@ -1702,7 +1714,7 @@ class MethodDesc m_wFlags |= mdcHasNativeCodeSlot; } - inline BOOL IsAsyncThunkMethod() + inline bool IsAsyncThunkMethod() { LIMITED_METHOD_DAC_CONTRACT; return (m_wFlags & mdcIsAsyncThunkMethod) != 0; @@ -3430,7 +3442,8 @@ class InstantiatedMethodDesc final : public MethodDesc static InstantiatedMethodDesc* FindLoadedInstantiatedMethodDesc(MethodTable *pMT, mdMethodDef methodDef, Instantiation methodInst, - BOOL getSharedNotStub); + BOOL getSharedNotStub, + BOOL asyncThunk); private: diff --git a/src/coreclr/vm/methoditer.cpp b/src/coreclr/vm/methoditer.cpp index 82def6a1bec50a..14387754ed40f3 100644 --- a/src/coreclr/vm/methoditer.cpp +++ b/src/coreclr/vm/methoditer.cpp @@ -57,6 +57,17 @@ BOOL LoadedMethodDescIterator::Next( return FALSE; } + if (m_fIsAsyncThunk) + { + m_mainMD = m_mainMD->GetMethodTable()->GetParallelMethodDesc(m_mainMD, AsyncVariantLookup::AsyncOtherVariant); + + if (m_mainMD == NULL) + { + *pDomainAssemblyHolder = NULL; + return FALSE; + } + } + // Needs to work w/ non-generic methods too. if (!m_mainMD->HasClassOrMethodInstantiation()) { @@ -146,6 +157,8 @@ BOOL LoadedMethodDescIterator::Next( goto ADVANCE_METHOD; if (m_methodIteratorEntry->GetMethod()->GetMemberDef() != m_md) goto ADVANCE_METHOD; + if (m_methodIteratorEntry->GetMethod()->IsAsyncThunkMethod() != m_fIsAsyncThunk) + goto ADVANCE_METHOD; } else if (m_startedNonGenericMethod) { @@ -213,6 +226,7 @@ LoadedMethodDescIterator::Start( AppDomain * pAppDomain, Module *pModule, mdMethodDef md, + bool fIsAsyncThunk, AssemblyIterationFlags assemblyIterationFlags) { CONTRACTL @@ -224,6 +238,8 @@ LoadedMethodDescIterator::Start( } CONTRACTL_END; + m_fIsAsyncThunk = fIsAsyncThunk; + m_assemIterationFlags = assemblyIterationFlags; m_mainMD = NULL; m_module = pModule; @@ -244,7 +260,7 @@ LoadedMethodDescIterator::Start( mdMethodDef md, MethodDesc *pMethodDesc) { - Start(pAppDomain, pModule, md); + Start(pAppDomain, pModule, md, pMethodDesc->IsAsyncThunkMethod()); m_mainMD = pMethodDesc; } @@ -255,4 +271,5 @@ LoadedMethodDescIterator::LoadedMethodDescIterator(void) m_module = NULL; m_md = mdTokenNil; m_pAppDomain = NULL; + m_fIsAsyncThunk = false; } diff --git a/src/coreclr/vm/methoditer.h b/src/coreclr/vm/methoditer.h index fdc49e2ce59710..5017b6c8991629 100644 --- a/src/coreclr/vm/methoditer.h +++ b/src/coreclr/vm/methoditer.h @@ -34,6 +34,8 @@ class LoadedMethodDescIterator mdMethodDef m_md; MethodDesc * m_mainMD; AppDomain * m_pAppDomain; + bool m_fIsAsyncThunk; + // The following hold the state of the iteration.... // Yes we iterate everything for the moment - we need @@ -68,6 +70,7 @@ class LoadedMethodDescIterator void Start(AppDomain * pAppDomain, Module *pModule, mdMethodDef md, + bool fIsAsyncThunk, AssemblyIterationFlags assemIterationFlags = (AssemblyIterationFlags)(kIncludeLoaded | kIncludeExecution)); void Start(AppDomain * pAppDomain, Module *pModule, mdMethodDef md, MethodDesc *pDesc); @@ -75,6 +78,7 @@ class LoadedMethodDescIterator AppDomain * pAppDomain, Module *pModule, mdMethodDef md, + bool fIsAsyncThunk, AssemblyIterationFlags assemblyIterationFlags = (AssemblyIterationFlags)(kIncludeLoaded | kIncludeExecution)) { LIMITED_METHOD_CONTRACT; diff --git a/src/coreclr/vm/methodtable.cpp b/src/coreclr/vm/methodtable.cpp index 32fb17a629d80c..55b5b63a2c8778 100644 --- a/src/coreclr/vm/methodtable.cpp +++ b/src/coreclr/vm/methodtable.cpp @@ -6423,6 +6423,7 @@ namespace FALSE, // allowInstParam TRUE, // forceRemoteableMethod TRUE, // allowCreate + AsyncVariantLookup::MatchingAsyncVariant, level // level ); } @@ -8720,7 +8721,8 @@ namespace { MethodDesc* pMD = it.GetMethodDesc(); if (pMD->GetMemberDef() == tkMethod - && pMD->GetModule() == mod) + && pMD->GetModule() == mod + && pMD->IsAsyncThunkMethod() == pDefMD->IsAsyncThunkMethod()) { return pMD; } @@ -8730,7 +8732,7 @@ namespace } } -MethodDesc* MethodTable::GetParallelMethodDesc(MethodDesc* pDefMD) +MethodDesc* MethodTable::GetParallelMethodDesc(MethodDesc* pDefMD, AsyncVariantLookup asyncVariantLookup) { CONTRACTL { @@ -8740,12 +8742,36 @@ MethodDesc* MethodTable::GetParallelMethodDesc(MethodDesc* pDefMD) } CONTRACTL_END; + if (asyncVariantLookup == AsyncVariantLookup::MatchingAsyncVariant) + { #ifdef FEATURE_METADATA_UPDATER - if (pDefMD->IsEnCAddedMethod()) - return GetParallelMethodDescForEnC(this, pDefMD); + if (pDefMD->IsEnCAddedMethod()) + return GetParallelMethodDescForEnC(this, pDefMD); #endif // FEATURE_METADATA_UPDATER - return GetMethodDescForSlot(pDefMD->GetSlot()); + return GetMethodDescForSlot(pDefMD->GetSlot()); + } + else + { + // Slow path for finding the asyncThunk (or not the AsyncThunk) + // This could be optimized with some trickery around slot numbers, but doing so is ... confusing, so I'm not implementing this yet + mdMethodDef tkMethod = pDefMD->GetMemberDef(); + Module* mod = pDefMD->GetModule(); + bool isAsyncThunkMethod = pDefMD->IsAsyncThunkMethod(); + + MethodTable::IntroducedMethodIterator it(this); + for (; it.IsValid(); it.Next()) + { + MethodDesc* pMD = it.GetMethodDesc(); + if (pMD->GetMemberDef() == tkMethod + && pMD->GetModule() == mod + && pMD->IsAsyncThunkMethod() != isAsyncThunkMethod) + { + return pMD; + } + } + return NULL; + } } #ifndef DACCESS_COMPILE @@ -9112,9 +9138,21 @@ MethodTable::TryResolveVirtualStaticMethodOnThisType(MethodTable* pInterfaceType { COMPlusThrow(kTypeLoadException, E_FAIL); } + + bool differsByAsyncVariant = false; if (!pMethodDecl->HasSameMethodDefAs(pInterfaceMD)) { - continue; + if (pMethodDecl->GetMemberDef() == pInterfaceMD->GetMemberDef() && + pMethodDecl->GetModule() == pInterfaceMD->GetModule() && + pMethodDecl->IsAsyncThunkMethod() != pInterfaceMD->IsAsyncThunkMethod()) + { + differsByAsyncVariant = true; + pMethodDecl = pMethodDecl->GetAsyncOtherVariant(); + } + else + { + continue; + } } // Spec requires that all body token for MethodImpls that refer to static virtual implementation methods must be MethodDef tokens. @@ -9140,6 +9178,11 @@ MethodTable::TryResolveVirtualStaticMethodOnThisType(MethodTable* pInterfaceType COMPlusThrow(kTypeLoadException, E_FAIL); } + if (differsByAsyncVariant) + { + pMethodImpl = pMethodImpl->GetAsyncOtherVariant(); + } + if (!verifyImplemented && instantiateMethodParameters) { pMethodImpl = pMethodImpl->FindOrCreateAssociatedMethodDesc( @@ -9150,6 +9193,7 @@ MethodTable::TryResolveVirtualStaticMethodOnThisType(MethodTable* pInterfaceType /* allowInstParam */ FALSE, /* forceRemotableMethod */ FALSE, /* allowCreate */ TRUE, + AsyncVariantLookup::MatchingAsyncVariant, /* level */ level); } if (pMethodImpl != nullptr) diff --git a/src/coreclr/vm/methodtable.h b/src/coreclr/vm/methodtable.h index 9c06faf6c43236..fd4b2a7e45eab1 100644 --- a/src/coreclr/vm/methodtable.h +++ b/src/coreclr/vm/methodtable.h @@ -63,6 +63,7 @@ class ClassFactoryBase; #endif // FEATURE_COMINTEROP_UNMANAGED_ACTIVATION class ArgDestination; enum class WellKnownAttribute : DWORD; +enum class AsyncVariantLookup; enum class ResolveVirtualStaticMethodFlags { @@ -1451,7 +1452,7 @@ class MethodTable MethodTable * GetRestoredSlotMT(DWORD slot); // Used to map methods on the same slot between instantiations. - MethodDesc * GetParallelMethodDesc(MethodDesc * pDefMD); + MethodDesc * GetParallelMethodDesc(MethodDesc * pDefMD, AsyncVariantLookup asyncVariantLookup = (AsyncVariantLookup)0); //------------------------------------------------------------------- // BoxedEntryPoint MethodDescs. diff --git a/src/coreclr/vm/methodtablebuilder.cpp b/src/coreclr/vm/methodtablebuilder.cpp index 4bf429f6f85b97..c0ae96533160ca 100644 --- a/src/coreclr/vm/methodtablebuilder.cpp +++ b/src/coreclr/vm/methodtablebuilder.cpp @@ -994,7 +994,7 @@ MethodTableBuilder::bmtRTMethod::bmtRTMethod( MethodDesc * pMD) : m_pOwningType(pOwningType), m_pMD(pMD), - m_methodSig(pMD->IsEEImpl() // Handle Async2 methods + m_methodSig(pMD->IsAsyncThunkMethod() ? MethodSignature(pMD->GetModule(), pMD->GetMemberDef(), pMD->GetSignature(), @@ -3530,6 +3530,7 @@ MethodTableBuilder::EnumerateClassMethods() // Create a new bmtMDMethod representing this method and add it to the // declared method list. // + bmtMDMethod *pDeclaredMethod = NULL; for (int insertCount = 0; insertCount < 2; insertCount++) { bmtMDMethod * pNewMethod; @@ -3543,6 +3544,7 @@ MethodTableBuilder::EnumerateClassMethods() dwMethodRVA, type, implType); + pDeclaredMethod = pNewMethod; } else { @@ -3590,6 +3592,9 @@ MethodTableBuilder::EnumerateClassMethods() thunkType, type, implType); + + pNewMethod->SetAsyncOtherVariant(pDeclaredMethod); + pDeclaredMethod->SetAsyncOtherVariant(pNewMethod); } bmtMethod->AddDeclaredMethod(pNewMethod); @@ -6221,6 +6226,12 @@ MethodTableBuilder::ProcessMethodImpls() BuildMethodTableThrowException(IDS_CLASSLOAD_MI_DECLARATIONNOTFOUND, it.Token()); } + // When working with the thunk, we want the opposite type of async method + if (it->GetAsyncThunkType() != AsyncThunkType::NotAThunk) + { + declMethod = declMethod.GetAsyncOtherVariant();; + } + if (!IsMdVirtual(declMethod.GetDeclAttrs())) { // Make sure the decl is virtual BuildMethodTableThrowException(IDS_CLASSLOAD_MI_MUSTBEVIRTUAL, it.Token()); diff --git a/src/coreclr/vm/methodtablebuilder.h b/src/coreclr/vm/methodtablebuilder.h index a83402618e3ad3..4e967d4cf26c63 100644 --- a/src/coreclr/vm/methodtablebuilder.h +++ b/src/coreclr/vm/methodtablebuilder.h @@ -676,7 +676,7 @@ class MethodTableBuilder //----------------------------------------------------------------------------------------- // This constructor can be used with hard-coded signatures that are used for - // locating .ctor and .cctor methods. + // representing async thunk methods MethodSignature( Module * pModule, mdToken tok, @@ -1068,6 +1068,9 @@ class MethodTableBuilder return m_asyncThunkType; } + bmtMDMethod * GetAsyncOtherVariant() const { return m_asyncOtherVariant; } + void SetAsyncOtherVariant(bmtMDMethod* pAsyncOtherVariant) { m_asyncOtherVariant = pAsyncOtherVariant; } + private: //----------------------------------------------------------------------------------------- bmtMDType * m_pOwningType; @@ -1079,6 +1082,7 @@ class MethodTableBuilder AsyncThunkType m_asyncThunkType; METHOD_IMPL_TYPE m_implType; // Whether or not the method is a methodImpl body MethodSignature m_methodSig; + bmtMDMethod* m_asyncOtherVariant = NULL; MethodDesc * m_pMD; // MethodDesc created and assigned to this method MethodDesc * m_pUnboxedMD; // Unboxing MethodDesc if this is a virtual method on a valuetype @@ -1208,6 +1212,27 @@ class MethodTableBuilder MethodDesc * GetMethodDesc() const; + bmtMethodHandle GetAsyncOtherVariant() const + { + if (IsRTMethod()) + { + bmtRTMethod *pRTMethod = AsRTMethod(); + MethodDesc *pMD = pRTMethod->GetMethodDesc(); + MethodDesc *pRTOtherMethod = pMD->GetAsyncOtherVariant(); + _ASSERTE(pRTOtherMethod != NULL); + _ASSERTE(pRTOtherMethod->IsAsyncThunkMethod() || pRTMethod->GetMethodDesc()->IsAsyncThunkMethod()); + _ASSERTE(FALSE); + return bmtMethodHandle((bmtRTMethod*)NULL); // TODO! Fix this to do the right thing here + } + else + { + bmtMDMethod* pMDOtherVariant = AsMDMethod()->GetAsyncOtherVariant(); + _ASSERTE(pMDOtherVariant != NULL); + _ASSERTE(pMDOtherVariant->GetAsyncThunkType() != AsyncThunkType::NotAThunk || AsMDMethod()->GetAsyncThunkType() != AsyncThunkType::NotAThunk); + return pMDOtherVariant; + } + } + protected: //----------------------------------------------------------------------------------------- static const UINT_PTR RTMETHOD_FLAG = 0x1; diff --git a/src/coreclr/vm/multicorejit.cpp b/src/coreclr/vm/multicorejit.cpp index d052cbeb3d9031..b3017f914eb4e5 100644 --- a/src/coreclr/vm/multicorejit.cpp +++ b/src/coreclr/vm/multicorejit.cpp @@ -398,6 +398,12 @@ HRESULT MulticoreJitRecorder::WriteOutput(IStream * pStream) } MethodDesc * pMethod = m_JitInfoArray[i].GetMethodDescAndClean(); + if (pMethod->IsAsyncThunkMethod()) + { + // TODO consider adding support for async thunks in the future + skipped++; + continue; + } if (m_JitInfoArray[i].IsGenericMethodInfo()) { diff --git a/src/coreclr/vm/readytoruninfo.cpp b/src/coreclr/vm/readytoruninfo.cpp index e75373db8855aa..be8f814344e3b3 100644 --- a/src/coreclr/vm/readytoruninfo.cpp +++ b/src/coreclr/vm/readytoruninfo.cpp @@ -945,6 +945,8 @@ static bool SigMatchesMethodDesc(MethodDesc* pMD, SigPointer &sig, ModuleBase * { STANDARD_VM_CONTRACT; + _ASSERTE(!pMD->IsAsyncThunkMethod()); + ModuleBase *pOrigModule = pModule; ZapSig::Context zapSigContext(pModule, (void *)pModule, ZapSig::NormalTokens); ZapSig::Context * pZapSigContext = &zapSigContext; @@ -1053,6 +1055,9 @@ bool ReadyToRunInfo::GetPgoInstrumentationData(MethodDesc * pMD, BYTE** pAllocat if (ReadyToRunCodeDisabled()) return false; + if (pMD->IsAsyncThunkMethod()) + return false; + if (m_pgoInstrumentationDataHashtable.IsNull()) return false; @@ -1125,6 +1130,9 @@ PCODE ReadyToRunInfo::GetEntryPoint(MethodDesc * pMD, PrepareCodeConfig* pConfig if (ReadyToRunCodeDisabled()) goto done; + if (pMD->IsAsyncThunkMethod()) + goto done; + ETW::MethodLog::GetR2RGetEntryPointStart(pMD); uint offset; diff --git a/src/coreclr/vm/zapsig.cpp b/src/coreclr/vm/zapsig.cpp index bbbab9a51c84d4..8b8c6a3cda5b28 100644 --- a/src/coreclr/vm/zapsig.cpp +++ b/src/coreclr/vm/zapsig.cpp @@ -927,12 +927,13 @@ MethodDesc *ZapSig::DecodeMethod(ModuleBase *pInfoModule, // in non-generic structs. BOOL isInstantiatingStub = (methodFlags & ENCODE_METHOD_SIG_InstantiatingStub); BOOL isUnboxingStub = (methodFlags & ENCODE_METHOD_SIG_UnboxingStub); + bool isAsyncThunk = (methodFlags & ENCODE_METHOD_SIG_AsyncThunk) != 0; pMethod = MethodDesc::FindOrCreateAssociatedMethodDesc(pMethod, thOwner.GetMethodTable(), isUnboxingStub, inst, !(isInstantiatingStub || isUnboxingStub) && !actualOwnerRequired, - actualOwnerRequired); + actualOwnerRequired, TRUE, isAsyncThunk == pMethod->IsAsyncThunkMethod() ? AsyncVariantLookup::MatchingAsyncVariant : AsyncVariantLookup::AsyncOtherVariant); if (methodFlags & ENCODE_METHOD_SIG_Constrained) { @@ -1236,6 +1237,8 @@ BOOL ZapSig::EncodeMethod( methodFlags |= ENCODE_METHOD_SIG_InstantiatingStub; if (fMethodNeedsInstantiation) methodFlags |= ENCODE_METHOD_SIG_MethodInstantiation; + if (pMethod->IsAsyncThunkMethod()) + methodFlags |= ENCODE_METHOD_SIG_AsyncThunk; // Assume that the owner type is going to be needed methodFlags |= ENCODE_METHOD_SIG_OwnerType; From 20eb76eeda51bf0f9bc32bf462762f53904f4de8 Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Wed, 20 Sep 2023 14:05:20 -0700 Subject: [PATCH 005/203] It builds and it should work... but doesn't --- src/coreclr/vm/methodtablebuilder.cpp | 46 ++++++++++++++++++++------- src/coreclr/vm/methodtablebuilder.h | 21 ++++++++---- 2 files changed, 48 insertions(+), 19 deletions(-) diff --git a/src/coreclr/vm/methodtablebuilder.cpp b/src/coreclr/vm/methodtablebuilder.cpp index c0ae96533160ca..d0f64636802283 100644 --- a/src/coreclr/vm/methodtablebuilder.cpp +++ b/src/coreclr/vm/methodtablebuilder.cpp @@ -5775,7 +5775,7 @@ MethodTableBuilder::PlaceVirtualMethods() // that the name+signature corresponds to. Used by ProcessMethodImpls and ProcessInexactMethodImpls // Always returns the first match that it finds. Affects the ambiguities in code:#ProcessInexactMethodImpls_Ambiguities MethodTableBuilder::bmtMethodHandle -MethodTableBuilder::FindDeclMethodOnInterfaceEntry(bmtInterfaceEntry *pItfEntry, MethodSignature &declSig, bool searchForStaticMethods) +MethodTableBuilder::FindDeclMethodOnInterfaceEntry(bmtInterfaceEntry *pItfEntry, MethodSignature &declSig, AsyncVariantLookup variantLookup, bool searchForStaticMethods) { STANDARD_VM_CONTRACT; @@ -5812,6 +5812,26 @@ MethodTableBuilder::FindDeclMethodOnInterfaceEntry(bmtInterfaceEntry *pItfEntry, } } + if (variantLookup == AsyncVariantLookup::AsyncOtherVariant) + { + bmtRTMethod* declRTMethod = declMethod.AsRTMethod(); + bool foundOtherVariant = false; + for (; !slotIt.AtEnd(); slotIt.Next()) + { + bmtRTMethod* slotDeclMethod = slotIt->Decl().AsRTMethod(); + + if ((slotDeclMethod->GetOwningType() == declRTMethod->GetOwningType()) && + (slotDeclMethod->GetMethodDesc()->GetMethodTable() == declRTMethod->GetMethodDesc()->GetMethodTable()) && + (slotDeclMethod->GetMethodDesc()->GetMemberDef() == declRTMethod->GetMethodDesc()->GetMemberDef()) && + (slotDeclMethod->GetMethodDesc()->IsAsyncThunkMethod() == declRTMethod->GetMethodDesc()->IsAsyncThunkMethod())) + { + declMethod = slotIt->Decl(); + } + } + + _ASSERTE(foundOtherVariant); + } + return declMethod; } @@ -5880,6 +5900,8 @@ MethodTableBuilder::ProcessInexactMethodImpls() continue; } + AsyncVariantLookup asyncVariantOfDeclToFind = it->GetAsyncThunkType() == AsyncThunkType::NotAThunk ? AsyncVariantLookup::MatchingAsyncVariant : AsyncVariantLookup::AsyncOtherVariant; + // If this method serves as the BODY of a MethodImpl specification, then // we should iterate all the MethodImpl's for this class and see just how many // of them this method participates in as the BODY. @@ -5934,7 +5956,7 @@ MethodTableBuilder::ProcessInexactMethodImpls() pItfEntry = &bmtInterface->pInterfaceMap[i]; // Search for declmethod on this interface - declMethod = FindDeclMethodOnInterfaceEntry(pItfEntry, declSig); + declMethod = FindDeclMethodOnInterfaceEntry(pItfEntry, declSig, asyncVariantOfDeclToFind); // If we didn't find a match, continue on to next interface in the equivalence set if (declMethod.IsNull()) @@ -6021,6 +6043,8 @@ MethodTableBuilder::ProcessMethodImpls() continue; } + AsyncVariantLookup asyncVariantOfDeclToFind = it->GetAsyncThunkType() == AsyncThunkType::NotAThunk ? AsyncVariantLookup::MatchingAsyncVariant : AsyncVariantLookup::AsyncOtherVariant; + // If this method serves as the BODY of a MethodImpl specification, then // we should iterate all the MethodImpl's for this class and see just how many // of them this method participates in as the BODY. @@ -6062,7 +6086,7 @@ MethodTableBuilder::ProcessMethodImpls() } CONSISTENCY_CHECK(TypeFromToken(mdDecl) == mdtMethodDef); - declMethod = bmtMethod->FindDeclaredMethodByToken(mdDecl); + declMethod = bmtMethod->FindDeclaredMethodByToken(mdDecl, asyncVariantOfDeclToFind); } else { // We can't call GetDescFromMemberDefOrRef here because this @@ -6196,12 +6220,12 @@ MethodTableBuilder::ProcessMethodImpls() } // 3. Find the matching method. - declMethod = FindDeclMethodOnInterfaceEntry(pItfEntry, declSig, isVirtualStaticOverride); // Search for statics when the impl is non-virtual + declMethod = FindDeclMethodOnInterfaceEntry(pItfEntry, declSig, asyncVariantOfDeclToFind, isVirtualStaticOverride); // Search for statics when the impl is non-virtual } else { GetHalfBakedClass()->SetHasVTableMethodImpl(); - declMethod = FindDeclMethodOnClassInHierarchy(it, pDeclMT, declSig); + declMethod = FindDeclMethodOnClassInHierarchy(it, pDeclMT, declSig, asyncVariantOfDeclToFind); } if (declMethod.IsNull()) @@ -6226,12 +6250,6 @@ MethodTableBuilder::ProcessMethodImpls() BuildMethodTableThrowException(IDS_CLASSLOAD_MI_DECLARATIONNOTFOUND, it.Token()); } - // When working with the thunk, we want the opposite type of async method - if (it->GetAsyncThunkType() != AsyncThunkType::NotAThunk) - { - declMethod = declMethod.GetAsyncOtherVariant();; - } - if (!IsMdVirtual(declMethod.GetDeclAttrs())) { // Make sure the decl is virtual BuildMethodTableThrowException(IDS_CLASSLOAD_MI_MUSTBEVIRTUAL, it.Token()); @@ -6258,7 +6276,7 @@ MethodTableBuilder::ProcessMethodImpls() } -MethodTableBuilder::bmtMethodHandle MethodTableBuilder::FindDeclMethodOnClassInHierarchy(const DeclaredMethodIterator& it, MethodTable * pDeclMT, MethodSignature &declSig) +MethodTableBuilder::bmtMethodHandle MethodTableBuilder::FindDeclMethodOnClassInHierarchy(const DeclaredMethodIterator& it, MethodTable * pDeclMT, MethodSignature &declSig, AsyncVariantLookup variantLookup) { bmtRTType * pDeclType = NULL; bmtMethodHandle declMethod; @@ -6344,7 +6362,11 @@ MethodTableBuilder::bmtMethodHandle MethodTableBuilder::FindDeclMethodOnClassInH FALSE, iPass == 0 ? &newVisited : NULL)) { + if (variantLookup == AsyncVariantLookup::AsyncOtherVariant) + pCurMD = pCurMD->GetAsyncOtherVariant(); + declMethod = (*bmtParent->pSlotTable)[pCurMD->GetSlot()].Decl(); + break; } } diff --git a/src/coreclr/vm/methodtablebuilder.h b/src/coreclr/vm/methodtablebuilder.h index 4e967d4cf26c63..4b34c5df88702c 100644 --- a/src/coreclr/vm/methodtablebuilder.h +++ b/src/coreclr/vm/methodtablebuilder.h @@ -1212,7 +1212,7 @@ class MethodTableBuilder MethodDesc * GetMethodDesc() const; - bmtMethodHandle GetAsyncOtherVariant() const +/* bmtMethodHandle GetAsyncOtherVariant() const { if (IsRTMethod()) { @@ -1222,7 +1222,6 @@ class MethodTableBuilder _ASSERTE(pRTOtherMethod != NULL); _ASSERTE(pRTOtherMethod->IsAsyncThunkMethod() || pRTMethod->GetMethodDesc()->IsAsyncThunkMethod()); _ASSERTE(FALSE); - return bmtMethodHandle((bmtRTMethod*)NULL); // TODO! Fix this to do the right thing here } else { @@ -1231,7 +1230,7 @@ class MethodTableBuilder _ASSERTE(pMDOtherVariant->GetAsyncThunkType() != AsyncThunkType::NotAThunk || AsMDMethod()->GetAsyncThunkType() != AsyncThunkType::NotAThunk); return pMDOtherVariant; } - } + }*/ protected: //----------------------------------------------------------------------------------------- @@ -1997,14 +1996,22 @@ class MethodTableBuilder // Searches the declared methods for a method with a token value equal to tok. bmtMDMethod * FindDeclaredMethodByToken( - mdMethodDef tok) + mdMethodDef tok, AsyncVariantLookup variantLookup) { LIMITED_METHOD_CONTRACT; for (SLOT_INDEX i = 0; i < m_cDeclaredMethods; ++i) { if ((*this)[i]->GetMethodSignature().GetToken() == tok) { - return (*this)[i]; + auto result = (*this)[i]; + if (variantLookup == AsyncVariantLookup::AsyncOtherVariant) + { + return result->GetAsyncOtherVariant(); + } + else + { + return result; + } } } return NULL; @@ -2770,13 +2777,13 @@ class MethodTableBuilder // Find the decl method on a given interface entry that matches the method name+signature specified // If none is found, return a null method handle bmtMethodHandle - FindDeclMethodOnInterfaceEntry(bmtInterfaceEntry *pItfEntry, MethodSignature &declSig, bool searchForStaticMethods = false); + FindDeclMethodOnInterfaceEntry(bmtInterfaceEntry *pItfEntry, MethodSignature &declSig, AsyncVariantLookup variantLookup, bool searchForStaticMethods = false); // -------------------------------------------------------------------------------------------- // Find the decl method within the class hierarchy method name+signature specified // If none is found, return a null method handle bmtMethodHandle - FindDeclMethodOnClassInHierarchy(const DeclaredMethodIterator& it, MethodTable * pDeclMT, MethodSignature &declSig); + FindDeclMethodOnClassInHierarchy(const DeclaredMethodIterator& it, MethodTable * pDeclMT, MethodSignature &declSig, AsyncVariantLookup variantLookup); // -------------------------------------------------------------------------------------------- // Throws if an entry already exists that has been MethodImpl'd. Adds the interface slot and From ad0bd7148842c43f480545258d4e234733de4c83 Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Wed, 20 Sep 2023 14:56:22 -0700 Subject: [PATCH 006/203] Oops pMethod can be NULL --- src/coreclr/vm/genericdict.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/coreclr/vm/genericdict.cpp b/src/coreclr/vm/genericdict.cpp index abd477d0401ec1..9d07cb7ec7de8c 100644 --- a/src/coreclr/vm/genericdict.cpp +++ b/src/coreclr/vm/genericdict.cpp @@ -1054,7 +1054,6 @@ Dictionary::PopulateEntry( } } - _ASSERTE((!!isAsyncThunk) == pMethod->IsAsyncThunkMethod()); if (fRequiresDispatchStub) { @@ -1087,6 +1086,8 @@ Dictionary::PopulateEntry( break; } + _ASSERTE((!!isAsyncThunk) == pMethod->IsAsyncThunkMethod()); + Instantiation inst; // Instantiate the method if needed, or create a stub to a static method in a generic class. From 8d416cbc5477d9a180caa922681ff598c7bcb36e Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Thu, 21 Sep 2023 11:04:03 -0700 Subject: [PATCH 007/203] We are able to load Task methods which are virtual again, next to actually write some "async" and start breaking things again --- src/coreclr/vm/methodtablebuilder.cpp | 75 ++++++++++++++++++++------- 1 file changed, 56 insertions(+), 19 deletions(-) diff --git a/src/coreclr/vm/methodtablebuilder.cpp b/src/coreclr/vm/methodtablebuilder.cpp index d0f64636802283..6e67ca5ba377ec 100644 --- a/src/coreclr/vm/methodtablebuilder.cpp +++ b/src/coreclr/vm/methodtablebuilder.cpp @@ -916,22 +916,26 @@ MethodTableBuilder::MethodSignature::GetMethodAttributes() const STANDARD_VM_CONTRACT; IMDInternalImport * pIMD = GetModule()->GetMDImport(); + DWORD cSig; + PCCOR_SIGNATURE pSig; if (TypeFromToken(GetToken()) == mdtMethodDef) { - DWORD cSig; - if (FAILED(pIMD->GetNameAndSigOfMethodDef(GetToken(), &m_pSig, &cSig, &m_szName))) + if (FAILED(pIMD->GetNameAndSigOfMethodDef(GetToken(), &pSig, &cSig, &m_szName))) { // We have empty name or signature on error, do nothing } - m_cSig = static_cast(cSig); } else { CONSISTENCY_CHECK(TypeFromToken(m_tok) == mdtMemberRef); - DWORD cSig; - if (FAILED(pIMD->GetNameAndSigOfMemberRef(GetToken(), &m_pSig, &cSig, &m_szName))) + if (FAILED(pIMD->GetNameAndSigOfMemberRef(GetToken(), &pSig, &cSig, &m_szName))) { // We have empty name or signature on error, do nothing } + } + // Don't overwrite signature that may have already been provided for AsyncThunk method + if (m_cSig == 0) + { m_cSig = static_cast(cSig); + m_pSig = pSig; } } @@ -3550,35 +3554,68 @@ MethodTableBuilder::EnumerateClassMethods() { ULONG cAsyncThunkMemberSignature = cMemberSignature; AsyncThunkType thunkType; + ULONG originalTokenOffsetFromAsyncDetailsOffset; + ULONG newTokenOffsetFromAsyncDetailsOffset; + ULONG originalPrefixSize; + ULONG originalSuffixSize; + ULONG newSuffixSize; + ULONG newPrefixSize; if (asyncMethodType == AsyncTaskMethod::Async2Method) { - cAsyncThunkMemberSignature += 1; + cAsyncThunkMemberSignature += 2; + originalTokenOffsetFromAsyncDetailsOffset = 1; + newTokenOffsetFromAsyncDetailsOffset = 2; thunkType = AsyncThunkType::AsyncToTask; + originalPrefixSize = 1; + newPrefixSize = 2; + originalSuffixSize = 0; + newSuffixSize = 1; } else { - cAsyncThunkMemberSignature -= 1; + cAsyncThunkMemberSignature -= 2; + originalTokenOffsetFromAsyncDetailsOffset = 2; + newTokenOffsetFromAsyncDetailsOffset = 1; thunkType = AsyncThunkType::TaskToAsync; + originalPrefixSize = 2; + newPrefixSize = 1; + originalSuffixSize = 1; + newSuffixSize = 0; } BYTE* pNewMemberSignature = AllocateFromHighFrequencyHeap(S_SIZE_T(cAsyncThunkMemberSignature)); - ULONG tokenLen = CorSigUncompressedDataSize(&pMemberSignature[offsetOfAsyncDetails + 1]); - ULONG initialCopyLen = offsetOfAsyncDetails + 1 + tokenLen; + ULONG tokenLen = CorSigUncompressedDataSize(&pMemberSignature[offsetOfAsyncDetails + originalTokenOffsetFromAsyncDetailsOffset]); + ULONG originalTokenOffset = offsetOfAsyncDetails + originalTokenOffsetFromAsyncDetailsOffset; + ULONG newTokenOffset = offsetOfAsyncDetails + newTokenOffsetFromAsyncDetailsOffset; + ULONG originalRemainingSigOffset = offsetOfAsyncDetails + originalPrefixSize + tokenLen + originalSuffixSize; + ULONG newRemainingSigOffset = offsetOfAsyncDetails + newPrefixSize + tokenLen + newSuffixSize; + + ULONG initialCopyLen = offsetOfAsyncDetails; memcpy(pNewMemberSignature, pMemberSignature, initialCopyLen); + memcpy(pNewMemberSignature + newTokenOffset, pMemberSignature + originalTokenOffset, tokenLen); + + _ASSERTE((cMemberSignature - originalRemainingSigOffset) == (cAsyncThunkMemberSignature - newRemainingSigOffset)); + memcpy(pNewMemberSignature + newRemainingSigOffset, pMemberSignature + originalRemainingSigOffset, cMemberSignature - originalRemainingSigOffset); if (asyncMethodType == AsyncTaskMethod::Async2Method) { + // Incoming sig will look something like ... E_T_CMOD_OPT E_T_I4 .... + // And needs to be translated to E_T_GENERICINST E_T_CLASS 1 E_T_I4 + // Replace the ELEMENT_TYPE_CMOD with ELEMENT_TYPE_GENERICINST, and then add a 1 after the token which refers to Task + pNewMemberSignature[offsetOfAsyncDetails] = ELEMENT_TYPE_GENERICINST; - pNewMemberSignature[initialCopyLen] = 1; - memcpy(pNewMemberSignature + initialCopyLen + 1, pMemberSignature + initialCopyLen, cMemberSignature - initialCopyLen); + pNewMemberSignature[offsetOfAsyncDetails + 1] = ELEMENT_TYPE_CLASS; + pNewMemberSignature[newRemainingSigOffset - 1] = 1; } else { + // Incoming sig will look something like ... E_T_GENERICINST E_T_CLASS 1 E_T_I4 .... + // And needs to be translated to E_T_CMOD_OPT E_T_I4 + // Replace the ELEMENT_TYPE_GENERICINST with ELEMENT_TYPE_CMOD_OPT, and then remove the 1 which specifies the generic arg count for Task pNewMemberSignature[offsetOfAsyncDetails] = ELEMENT_TYPE_CMOD_OPT; - memcpy(pNewMemberSignature + initialCopyLen, pMemberSignature + initialCopyLen + 1, cMemberSignature - (initialCopyLen + 1)); } Signature newMemberSig(pNewMemberSignature, cAsyncThunkMemberSignature); @@ -6564,6 +6601,13 @@ MethodTableBuilder::InitMethodDesc( #endif // !_DEBUG pNewMD->SetSynchronized(); + if (thunkType != AsyncThunkType::NotAThunk) + { + AsyncThunkData* pThunkData = pNewMD->GetAddrOfAsyncThunkData(); + pThunkData->sig = sig; + pThunkData->type = thunkType; + } + #ifdef _DEBUG pNewMD->m_pszDebugMethodName = (LPUTF8)pszDebugMethodName; pNewMD->m_pszDebugClassName = (LPUTF8)pszDebugClassName; @@ -6574,13 +6618,6 @@ MethodTableBuilder::InitMethodDesc( else pNewMD->m_pszDebugMethodSignature = pszDebugMethodSignature; #endif // _DEBUG - - if (thunkType != AsyncThunkType::NotAThunk) - { - AsyncThunkData* pThunkData = pNewMD->GetAddrOfAsyncThunkData(); - pThunkData->sig = sig; - pThunkData->type = thunkType; - } } // MethodTableBuilder::InitMethodDesc //******************************************************************************* From 0fbd60cec88b7c2f8638ced817b94aca95d7b584 Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Thu, 21 Sep 2023 13:54:00 -0700 Subject: [PATCH 008/203] Add some simple benchmarks of the existing situation --- .../Loader/async/syncfibonacci-with-yields.cs | 48 +++++++++++++++++++ .../async/syncfibonacci-with-yields.csproj | 8 ++++ .../async/syncfibonacci-without-yields.cs | 48 +++++++++++++++++++ .../async/syncfibonacci-without-yields.csproj | 8 ++++ .../taskbased-asyncfibonacci-with-yields.cs | 45 +++++++++++++++++ ...askbased-asyncfibonacci-with-yields.csproj | 8 ++++ ...taskbased-asyncfibonacci-without-yields.cs | 41 ++++++++++++++++ ...based-asyncfibonacci-without-yields.csproj | 8 ++++ ...luetaskbased-asyncfibonacci-with-yields.cs | 46 ++++++++++++++++++ ...askbased-asyncfibonacci-with-yields.csproj | 8 ++++ ...taskbased-asyncfibonacci-without-yields.cs | 41 ++++++++++++++++ ...based-asyncfibonacci-without-yields.csproj | 8 ++++ 12 files changed, 317 insertions(+) create mode 100644 src/tests/Loader/async/syncfibonacci-with-yields.cs create mode 100644 src/tests/Loader/async/syncfibonacci-with-yields.csproj create mode 100644 src/tests/Loader/async/syncfibonacci-without-yields.cs create mode 100644 src/tests/Loader/async/syncfibonacci-without-yields.csproj create mode 100644 src/tests/Loader/async/taskbased-asyncfibonacci-with-yields.cs create mode 100644 src/tests/Loader/async/taskbased-asyncfibonacci-with-yields.csproj create mode 100644 src/tests/Loader/async/taskbased-asyncfibonacci-without-yields.cs create mode 100644 src/tests/Loader/async/taskbased-asyncfibonacci-without-yields.csproj create mode 100644 src/tests/Loader/async/valuetaskbased-asyncfibonacci-with-yields.cs create mode 100644 src/tests/Loader/async/valuetaskbased-asyncfibonacci-with-yields.csproj create mode 100644 src/tests/Loader/async/valuetaskbased-asyncfibonacci-without-yields.cs create mode 100644 src/tests/Loader/async/valuetaskbased-asyncfibonacci-without-yields.csproj diff --git a/src/tests/Loader/async/syncfibonacci-with-yields.cs b/src/tests/Loader/async/syncfibonacci-with-yields.cs new file mode 100644 index 00000000000000..9d668c9568367d --- /dev/null +++ b/src/tests/Loader/async/syncfibonacci-with-yields.cs @@ -0,0 +1,48 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Diagnostics; +using System.Threading; +using System.Runtime.CompilerServices; + +const uint Threshold = 1_000; + +for (int i = 0; i < 10; i++) +{ + var sw = new Stopwatch(); + sw.Start(); + uint result = A(100_000_000); + Console.WriteLine($"{sw.ElapsedMilliseconds} ms result={result}"); +} + +static uint A(uint n) +{ + uint result = n; + for (uint i = 0; i < n; i++) + result = B(result); + return result; +} + +static uint B(uint n) +{ + uint result = n; + + result = result * 1_999_999_981; + if (result < Threshold) + MyYield(); + + result = result * 1_999_999_981; + if (result < Threshold) + MyYield(); + + result = result * 1_999_999_981; + if (result < Threshold) + MyYield(); + + return result; +} + +// Workaround for inlining of Thread.Yield inflating the caller's frame. +[MethodImpl(MethodImplOptions.NoInlining)] +static void MyYield() => Thread.Yield(); \ No newline at end of file diff --git a/src/tests/Loader/async/syncfibonacci-with-yields.csproj b/src/tests/Loader/async/syncfibonacci-with-yields.csproj new file mode 100644 index 00000000000000..92548675762273 --- /dev/null +++ b/src/tests/Loader/async/syncfibonacci-with-yields.csproj @@ -0,0 +1,8 @@ + + + 1 + + + + + diff --git a/src/tests/Loader/async/syncfibonacci-without-yields.cs b/src/tests/Loader/async/syncfibonacci-without-yields.cs new file mode 100644 index 00000000000000..9d668c9568367d --- /dev/null +++ b/src/tests/Loader/async/syncfibonacci-without-yields.cs @@ -0,0 +1,48 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Diagnostics; +using System.Threading; +using System.Runtime.CompilerServices; + +const uint Threshold = 1_000; + +for (int i = 0; i < 10; i++) +{ + var sw = new Stopwatch(); + sw.Start(); + uint result = A(100_000_000); + Console.WriteLine($"{sw.ElapsedMilliseconds} ms result={result}"); +} + +static uint A(uint n) +{ + uint result = n; + for (uint i = 0; i < n; i++) + result = B(result); + return result; +} + +static uint B(uint n) +{ + uint result = n; + + result = result * 1_999_999_981; + if (result < Threshold) + MyYield(); + + result = result * 1_999_999_981; + if (result < Threshold) + MyYield(); + + result = result * 1_999_999_981; + if (result < Threshold) + MyYield(); + + return result; +} + +// Workaround for inlining of Thread.Yield inflating the caller's frame. +[MethodImpl(MethodImplOptions.NoInlining)] +static void MyYield() => Thread.Yield(); \ No newline at end of file diff --git a/src/tests/Loader/async/syncfibonacci-without-yields.csproj b/src/tests/Loader/async/syncfibonacci-without-yields.csproj new file mode 100644 index 00000000000000..b2d4498b9b2347 --- /dev/null +++ b/src/tests/Loader/async/syncfibonacci-without-yields.csproj @@ -0,0 +1,8 @@ + + + 1 + + + + + diff --git a/src/tests/Loader/async/taskbased-asyncfibonacci-with-yields.cs b/src/tests/Loader/async/taskbased-asyncfibonacci-with-yields.cs new file mode 100644 index 00000000000000..8fe98070a93f52 --- /dev/null +++ b/src/tests/Loader/async/taskbased-asyncfibonacci-with-yields.cs @@ -0,0 +1,45 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Threading.Tasks; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +const uint Threshold = 1_000; + +for (int i = 0; i < 10; i++) +{ + var sw = new Stopwatch(); + sw.Start(); + uint result = await A(100_000_000); + Console.WriteLine($"{sw.ElapsedMilliseconds} ms result={result}"); +} + +return 100; + +static async Task A(uint n) +{ + uint result = n; + for (uint i = 0; i < n; i++) + result = await B(result); + return result; +} + +static async Task B(uint n){ + uint result = n; + + result = result * 1_999_999_981; + if (result < Threshold) + await Task.Yield(); + + result = result * 1_999_999_981; + if (result < Threshold) + await Task.Yield(); + + result = result * 1_999_999_981; + if (result < Threshold) + await Task.Yield(); + + return result; +} \ No newline at end of file diff --git a/src/tests/Loader/async/taskbased-asyncfibonacci-with-yields.csproj b/src/tests/Loader/async/taskbased-asyncfibonacci-with-yields.csproj new file mode 100644 index 00000000000000..0ce5b85c6dda70 --- /dev/null +++ b/src/tests/Loader/async/taskbased-asyncfibonacci-with-yields.csproj @@ -0,0 +1,8 @@ + + + 1 + + + + + diff --git a/src/tests/Loader/async/taskbased-asyncfibonacci-without-yields.cs b/src/tests/Loader/async/taskbased-asyncfibonacci-without-yields.cs new file mode 100644 index 00000000000000..409586183a849f --- /dev/null +++ b/src/tests/Loader/async/taskbased-asyncfibonacci-without-yields.cs @@ -0,0 +1,41 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; + +const uint Threshold = 1_000; + +for (int i = 0; i < 10; i++) +{ + var sw = new Stopwatch(); + sw.Start(); + uint result = await A(100_000_000); + Console.WriteLine($"{sw.ElapsedMilliseconds} ms result={result}"); +} + +return 100; + +static async Task A(uint n) +{ + uint result = n; + for (uint i = 0; i < n; i++) + result = await B(result); + return result; +} + +#pragma warning disable CS1998 +static async Task B(uint n) +{ + uint result = n; + + result = result * 1_999_999_981; + + result = result * 1_999_999_981; + + result = result * 1_999_999_981; + + return result; +} diff --git a/src/tests/Loader/async/taskbased-asyncfibonacci-without-yields.csproj b/src/tests/Loader/async/taskbased-asyncfibonacci-without-yields.csproj new file mode 100644 index 00000000000000..42d69ec59b1cb5 --- /dev/null +++ b/src/tests/Loader/async/taskbased-asyncfibonacci-without-yields.csproj @@ -0,0 +1,8 @@ + + + 1 + + + + + diff --git a/src/tests/Loader/async/valuetaskbased-asyncfibonacci-with-yields.cs b/src/tests/Loader/async/valuetaskbased-asyncfibonacci-with-yields.cs new file mode 100644 index 00000000000000..29f4d892cb41dc --- /dev/null +++ b/src/tests/Loader/async/valuetaskbased-asyncfibonacci-with-yields.cs @@ -0,0 +1,46 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Threading.Tasks; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +const uint Threshold = 1_000; + +for (int i = 0; i < 10; i++) +{ + var sw = new Stopwatch(); + sw.Start(); + uint result = await A(100_000_000); + Console.WriteLine($"{sw.ElapsedMilliseconds} ms result={result}"); +} + +return 100; + +static async ValueTask A(uint n) +{ + uint result = n; + for (uint i = 0; i < n; i++) + result = await B(result); + return result; +} + +static async ValueTask B(uint n) +{ + uint result = n; + + result = result * 1_999_999_981; + if (result < Threshold) + await Task.Yield(); + + result = result * 1_999_999_981; + if (result < Threshold) + await Task.Yield(); + + result = result * 1_999_999_981; + if (result < Threshold) + await Task.Yield(); + + return result; +} \ No newline at end of file diff --git a/src/tests/Loader/async/valuetaskbased-asyncfibonacci-with-yields.csproj b/src/tests/Loader/async/valuetaskbased-asyncfibonacci-with-yields.csproj new file mode 100644 index 00000000000000..07ce3d1c562473 --- /dev/null +++ b/src/tests/Loader/async/valuetaskbased-asyncfibonacci-with-yields.csproj @@ -0,0 +1,8 @@ + + + 1 + + + + + diff --git a/src/tests/Loader/async/valuetaskbased-asyncfibonacci-without-yields.cs b/src/tests/Loader/async/valuetaskbased-asyncfibonacci-without-yields.cs new file mode 100644 index 00000000000000..ce28a34ed45d49 --- /dev/null +++ b/src/tests/Loader/async/valuetaskbased-asyncfibonacci-without-yields.cs @@ -0,0 +1,41 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; + +const uint Threshold = 1_000; + +for (int i = 0; i < 10; i++) +{ + var sw = new Stopwatch(); + sw.Start(); + uint result = await A(100_000_000); + Console.WriteLine($"{sw.ElapsedMilliseconds} ms result={result}"); +} + +return 100; + +static async ValueTask A(uint n) +{ + uint result = n; + for (uint i = 0; i < n; i++) + result = await B(result); + return result; +} + +#pragma warning disable CS1998 +static async ValueTask B(uint n) +{ + uint result = n; + + result = result * 1_999_999_981; + + result = result * 1_999_999_981; + + result = result * 1_999_999_981; + + return result; +} diff --git a/src/tests/Loader/async/valuetaskbased-asyncfibonacci-without-yields.csproj b/src/tests/Loader/async/valuetaskbased-asyncfibonacci-without-yields.csproj new file mode 100644 index 00000000000000..d13c6b884d0595 --- /dev/null +++ b/src/tests/Loader/async/valuetaskbased-asyncfibonacci-without-yields.csproj @@ -0,0 +1,8 @@ + + + 1 + + + + + From 3270fdf67fcc15e825f97245d06fececf6ab18c6 Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Thu, 21 Sep 2023 16:35:06 -0700 Subject: [PATCH 009/203] Add Test case for synchronous behavior --- ...ntimetask-asyncfibonacci-without-yields.il | 377 ++++++++++++++++++ ...etask-asyncfibonacci-without-yields.ilproj | 8 + .../async/syncfibonacci-without-yields.cs | 10 - 3 files changed, 385 insertions(+), 10 deletions(-) create mode 100644 src/tests/Loader/async/runtimetask-asyncfibonacci-without-yields.il create mode 100644 src/tests/Loader/async/runtimetask-asyncfibonacci-without-yields.ilproj diff --git a/src/tests/Loader/async/runtimetask-asyncfibonacci-without-yields.il b/src/tests/Loader/async/runtimetask-asyncfibonacci-without-yields.il new file mode 100644 index 00000000000000..b48f72bb7ea9f8 --- /dev/null +++ b/src/tests/Loader/async/runtimetask-asyncfibonacci-without-yields.il @@ -0,0 +1,377 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + + +// .NET IL Disassembler. Version 9.0.0-dev + + + +// Metadata version: v4.0.30319 +.assembly extern System.Runtime +{ + .publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A ) // .?_....: + .ver 9:0:0:0 +} +.assembly extern System.Console +{ + .publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A ) // .?_....: + .ver 9:0:0:0 +} +.assembly 'runtimetask-asyncfibonacci-without-yields' +{ + .custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilationRelaxationsAttribute::.ctor(int32) = ( 01 00 08 00 00 00 00 00 ) + .custom instance void [System.Runtime]System.Runtime.CompilerServices.RuntimeCompatibilityAttribute::.ctor() = ( 01 00 01 00 54 02 16 57 72 61 70 4E 6F 6E 45 78 // ....T..WrapNonEx + 63 65 70 74 69 6F 6E 54 68 72 6F 77 73 01 ) // ceptionThrows. + + // --- The following custom attribute is added automatically, do not uncomment ------- + // .custom instance void [System.Runtime]System.Diagnostics.DebuggableAttribute::.ctor(valuetype [System.Runtime]System.Diagnostics.DebuggableAttribute/DebuggingModes) = ( 01 00 07 01 00 00 00 00 ) + + .hash algorithm 0x00008004 + .ver 0:0:0:0 +} +.module 'runtimetask-asyncfibonacci-without-yields.dll' +// MVID: {ca42840a-c801-47a9-8471-ea986ae2c60d} +.custom instance void [System.Runtime]System.Runtime.CompilerServices.RefSafetyRulesAttribute::.ctor(int32) = ( 01 00 0B 00 00 00 00 00 ) +.imagebase 0x00400000 +.file alignment 0x00000200 +.stackreserve 0x00100000 +.subsystem 0x0003 // WINDOWS_CUI +.corflags 0x00000001 // ILONLY +// Image base: 0x000001E00BDE0000 + + +// =============== CLASS MEMBERS DECLARATION =================== + +.class private auto ansi beforefieldinit Program + extends [System.Runtime]System.Object +{ + .class auto ansi sealed nested private beforefieldinit '<
$>d__0' + extends [System.Runtime]System.Object + implements [System.Runtime]System.Runtime.CompilerServices.IAsyncStateMachine + { + .custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) + .field public int32 '<>1__state' + .field public valuetype [System.Runtime]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1 '<>t__builder' + .field public string[] args + .field private int32 '5__1' + .field private class [System.Runtime]System.Diagnostics.Stopwatch '5__2' + .field private uint32 '5__3' + .field private uint32 '<>s__4' + .field private valuetype [System.Runtime]System.Runtime.CompilerServices.TaskAwaiter`1 '<>u__1' + .method public hidebysig specialname rtspecialname + instance void .ctor() cil managed + { + // Code size 8 (0x8) + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: call instance void [System.Runtime]System.Object::.ctor() + IL_0006: nop + IL_0007: ret + } // end of method '<
$>d__0'::.ctor + + .method private hidebysig newslot virtual final + instance void MoveNext() cil managed + { + .override [System.Runtime]System.Runtime.CompilerServices.IAsyncStateMachine::MoveNext + // Code size 334 (0x14e) + .maxstack 3 + .locals init (int32 V_0, + int32 V_1, + valuetype [System.Runtime]System.Runtime.CompilerServices.TaskAwaiter`1 V_2, + class Program/'<
$>d__0' V_3, + valuetype [System.Runtime]System.Runtime.CompilerServices.DefaultInterpolatedStringHandler V_4, + int32 V_5, + bool V_6, + class [System.Runtime]System.Exception V_7) + IL_0000: ldarg.0 + IL_0001: ldfld int32 Program/'<
$>d__0'::'<>1__state' + IL_0006: stloc.0 + .try + { + IL_0007: ldloc.0 + IL_0008: brfalse.s IL_000c + + IL_000a: br.s IL_000e + + IL_000c: br.s IL_0072 + + IL_000e: ldarg.0 + IL_000f: ldc.i4.0 + IL_0010: stfld int32 Program/'<
$>d__0'::'5__1' + IL_0015: br IL_0106 + + IL_001a: nop + IL_001b: ldarg.0 + IL_001c: newobj instance void [System.Runtime]System.Diagnostics.Stopwatch::.ctor() + IL_0021: stfld class [System.Runtime]System.Diagnostics.Stopwatch Program/'<
$>d__0'::'5__2' + IL_0026: ldarg.0 + IL_0027: ldfld class [System.Runtime]System.Diagnostics.Stopwatch Program/'<
$>d__0'::'5__2' + IL_002c: callvirt instance void [System.Runtime]System.Diagnostics.Stopwatch::Start() + IL_0031: nop + IL_0032: ldc.i4 0x5f5e100 + IL_0037: call class [System.Runtime]System.Threading.Tasks.Task`1 Program::'<
$>g__A|0_0'(uint32) + IL_003c: callvirt instance valuetype [System.Runtime]System.Runtime.CompilerServices.TaskAwaiter`1 class [System.Runtime]System.Threading.Tasks.Task`1::GetAwaiter() + IL_0041: stloc.2 + IL_0042: ldloca.s V_2 + IL_0044: call instance bool valuetype [System.Runtime]System.Runtime.CompilerServices.TaskAwaiter`1::get_IsCompleted() + IL_0049: brtrue.s IL_008e + + IL_004b: ldarg.0 + IL_004c: ldc.i4.0 + IL_004d: dup + IL_004e: stloc.0 + IL_004f: stfld int32 Program/'<
$>d__0'::'<>1__state' + IL_0054: ldarg.0 + IL_0055: ldloc.2 + IL_0056: stfld valuetype [System.Runtime]System.Runtime.CompilerServices.TaskAwaiter`1 Program/'<
$>d__0'::'<>u__1' + IL_005b: ldarg.0 + IL_005c: stloc.3 + IL_005d: ldarg.0 + IL_005e: ldflda valuetype [System.Runtime]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1 Program/'<
$>d__0'::'<>t__builder' + IL_0063: ldloca.s V_2 + IL_0065: ldloca.s V_3 + IL_0067: call instance void valuetype [System.Runtime]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1::AwaitUnsafeOnCompleted,class Program/'<
$>d__0'>(!!0&, + !!1&) + IL_006c: nop + IL_006d: leave IL_014d + + IL_0072: ldarg.0 + IL_0073: ldfld valuetype [System.Runtime]System.Runtime.CompilerServices.TaskAwaiter`1 Program/'<
$>d__0'::'<>u__1' + IL_0078: stloc.2 + IL_0079: ldarg.0 + IL_007a: ldflda valuetype [System.Runtime]System.Runtime.CompilerServices.TaskAwaiter`1 Program/'<
$>d__0'::'<>u__1' + IL_007f: initobj valuetype [System.Runtime]System.Runtime.CompilerServices.TaskAwaiter`1 + IL_0085: ldarg.0 + IL_0086: ldc.i4.m1 + IL_0087: dup + IL_0088: stloc.0 + IL_0089: stfld int32 Program/'<
$>d__0'::'<>1__state' + IL_008e: ldarg.0 + IL_008f: ldloca.s V_2 + IL_0091: call instance !0 valuetype [System.Runtime]System.Runtime.CompilerServices.TaskAwaiter`1::GetResult() + IL_0096: stfld uint32 Program/'<
$>d__0'::'<>s__4' + IL_009b: ldarg.0 + IL_009c: ldarg.0 + IL_009d: ldfld uint32 Program/'<
$>d__0'::'<>s__4' + IL_00a2: stfld uint32 Program/'<
$>d__0'::'5__3' + IL_00a7: ldc.i4.s 11 + IL_00a9: ldc.i4.2 + IL_00aa: newobj instance void [System.Runtime]System.Runtime.CompilerServices.DefaultInterpolatedStringHandler::.ctor(int32, + int32) + IL_00af: stloc.s V_4 + IL_00b1: ldloca.s V_4 + IL_00b3: ldarg.0 + IL_00b4: ldfld class [System.Runtime]System.Diagnostics.Stopwatch Program/'<
$>d__0'::'5__2' + IL_00b9: callvirt instance int64 [System.Runtime]System.Diagnostics.Stopwatch::get_ElapsedMilliseconds() + IL_00be: call instance void [System.Runtime]System.Runtime.CompilerServices.DefaultInterpolatedStringHandler::AppendFormatted(!!0) + IL_00c3: nop + IL_00c4: ldloca.s V_4 + IL_00c6: ldstr " ms result=" + IL_00cb: call instance void [System.Runtime]System.Runtime.CompilerServices.DefaultInterpolatedStringHandler::AppendLiteral(string) + IL_00d0: nop + IL_00d1: ldloca.s V_4 + IL_00d3: ldarg.0 + IL_00d4: ldfld uint32 Program/'<
$>d__0'::'5__3' + IL_00d9: call instance void [System.Runtime]System.Runtime.CompilerServices.DefaultInterpolatedStringHandler::AppendFormatted(!!0) + IL_00de: nop + IL_00df: ldloca.s V_4 + IL_00e1: call instance string [System.Runtime]System.Runtime.CompilerServices.DefaultInterpolatedStringHandler::ToStringAndClear() + IL_00e6: call void [System.Console]System.Console::WriteLine(string) + IL_00eb: nop + IL_00ec: nop + IL_00ed: ldarg.0 + IL_00ee: ldnull + IL_00ef: stfld class [System.Runtime]System.Diagnostics.Stopwatch Program/'<
$>d__0'::'5__2' + IL_00f4: ldarg.0 + IL_00f5: ldfld int32 Program/'<
$>d__0'::'5__1' + IL_00fa: stloc.s V_5 + IL_00fc: ldarg.0 + IL_00fd: ldloc.s V_5 + IL_00ff: ldc.i4.1 + IL_0100: add + IL_0101: stfld int32 Program/'<
$>d__0'::'5__1' + IL_0106: ldarg.0 + IL_0107: ldfld int32 Program/'<
$>d__0'::'5__1' + IL_010c: ldc.i4.s 10 + IL_010e: clt + IL_0110: stloc.s V_6 + IL_0112: ldloc.s V_6 + IL_0114: brtrue IL_001a + + IL_0119: ldc.i4.s 100 + IL_011b: stloc.1 + IL_011c: leave.s IL_0138 + + } // end .try + catch [System.Runtime]System.Exception + { + IL_011e: stloc.s V_7 + IL_0120: ldarg.0 + IL_0121: ldc.i4.s -2 + IL_0123: stfld int32 Program/'<
$>d__0'::'<>1__state' + IL_0128: ldarg.0 + IL_0129: ldflda valuetype [System.Runtime]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1 Program/'<
$>d__0'::'<>t__builder' + IL_012e: ldloc.s V_7 + IL_0130: call instance void valuetype [System.Runtime]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1::SetException(class [System.Runtime]System.Exception) + IL_0135: nop + IL_0136: leave.s IL_014d + + } // end handler + IL_0138: ldarg.0 + IL_0139: ldc.i4.s -2 + IL_013b: stfld int32 Program/'<
$>d__0'::'<>1__state' + IL_0140: ldarg.0 + IL_0141: ldflda valuetype [System.Runtime]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1 Program/'<
$>d__0'::'<>t__builder' + IL_0146: ldloc.1 + IL_0147: call instance void valuetype [System.Runtime]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1::SetResult(!0) + IL_014c: nop + IL_014d: ret + } // end of method '<
$>d__0'::MoveNext + + .method private hidebysig newslot virtual final + instance void SetStateMachine(class [System.Runtime]System.Runtime.CompilerServices.IAsyncStateMachine stateMachine) cil managed + { + .custom instance void [System.Runtime]System.Diagnostics.DebuggerHiddenAttribute::.ctor() = ( 01 00 00 00 ) + .override [System.Runtime]System.Runtime.CompilerServices.IAsyncStateMachine::SetStateMachine + // Code size 1 (0x1) + .maxstack 8 + IL_0000: ret + } // end of method '<
$>d__0'::SetStateMachine + + } // end of class '<
$>d__0' + + .method private hidebysig static class [System.Runtime]System.Threading.Tasks.Task`1 + '
$'(string[] args) cil managed + { + .custom instance void [System.Runtime]System.Runtime.CompilerServices.AsyncStateMachineAttribute::.ctor(class [System.Runtime]System.Type) = ( 01 00 15 50 72 6F 67 72 61 6D 2B 3C 3C 4D 61 69 // ...Program+<$>d__0.. + .custom instance void [System.Runtime]System.Diagnostics.DebuggerStepThroughAttribute::.ctor() = ( 01 00 00 00 ) + // Code size 57 (0x39) + .maxstack 2 + .locals init (class Program/'<
$>d__0' V_0) + IL_0000: newobj instance void Program/'<
$>d__0'::.ctor() + IL_0005: stloc.0 + IL_0006: ldloc.0 + IL_0007: call valuetype [System.Runtime]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1 valuetype [System.Runtime]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1::Create() + IL_000c: stfld valuetype [System.Runtime]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1 Program/'<
$>d__0'::'<>t__builder' + IL_0011: ldloc.0 + IL_0012: ldarg.0 + IL_0013: stfld string[] Program/'<
$>d__0'::args + IL_0018: ldloc.0 + IL_0019: ldc.i4.m1 + IL_001a: stfld int32 Program/'<
$>d__0'::'<>1__state' + IL_001f: ldloc.0 + IL_0020: ldflda valuetype [System.Runtime]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1 Program/'<
$>d__0'::'<>t__builder' + IL_0025: ldloca.s V_0 + IL_0027: call instance void valuetype [System.Runtime]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1::Start$>d__0'>(!!0&) + IL_002c: nop + IL_002d: ldloc.0 + IL_002e: ldflda valuetype [System.Runtime]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1 Program/'<
$>d__0'::'<>t__builder' + IL_0033: call instance class [System.Runtime]System.Threading.Tasks.Task`1 valuetype [System.Runtime]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1::get_Task() + IL_0038: ret + } // end of method Program::'
$' + + .method public hidebysig specialname rtspecialname + instance void .ctor() cil managed + { + // Code size 8 (0x8) + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: call instance void [System.Runtime]System.Object::.ctor() + IL_0006: nop + IL_0007: ret + } // end of method Program::.ctor + + .method private hidebysig specialname static + int32 '
'(string[] args) cil managed + { + .entrypoint + .custom instance void [System.Runtime]System.Diagnostics.DebuggerStepThroughAttribute::.ctor() = ( 01 00 00 00 ) + // Code size 20 (0x14) + .maxstack 1 + .locals init (valuetype [System.Runtime]System.Runtime.CompilerServices.TaskAwaiter`1 V_0) + IL_0000: ldarg.0 + IL_0001: call class [System.Runtime]System.Threading.Tasks.Task`1 Program::'
$'(string[]) + IL_0006: callvirt instance valuetype [System.Runtime]System.Runtime.CompilerServices.TaskAwaiter`1 class [System.Runtime]System.Threading.Tasks.Task`1::GetAwaiter() + IL_000b: stloc.0 + IL_000c: ldloca.s V_0 + IL_000e: call instance !0 valuetype [System.Runtime]System.Runtime.CompilerServices.TaskAwaiter`1::GetResult() + IL_0013: ret + } // end of method Program::'
' + + .method assembly hidebysig static uint32 modopt([System.Runtime]System.Threading.Tasks.Task`1) + '<
$>g__A|0_0'(uint32 n) cil managed + { + .custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) + // Code size 32 (0x20) + .maxstack 2 + .locals init (uint32 V_0, + uint32 V_1, + bool V_2, + uint32 V_3) + IL_0000: nop + IL_0001: ldarg.0 + IL_0002: stloc.0 + IL_0003: ldc.i4.0 + IL_0004: stloc.1 + IL_0005: br.s IL_0012 + + IL_0007: ldloc.0 + IL_0008: call uint32 modopt([System.Runtime]System.Threading.Tasks.Task`1) Program::'<
$>g__B|0_1'(uint32) + IL_000d: stloc.0 + IL_000e: ldloc.1 + IL_000f: ldc.i4.1 + IL_0010: add + IL_0011: stloc.1 + IL_0012: ldloc.1 + IL_0013: ldarg.0 + IL_0014: clt.un + IL_0016: stloc.2 + IL_0017: ldloc.2 + IL_0018: brtrue.s IL_0007 + + IL_001a: ldloc.0 + IL_001b: stloc.3 + IL_001c: br.s IL_001e + + IL_001e: ldloc.3 + IL_001f: ret + } // end of method Program::'<
$>g__A|0_0' + + .method assembly hidebysig static uint32 modopt([System.Runtime]System.Threading.Tasks.Task`1) + '<
$>g__B|0_1'(uint32 n) cil managed + { + .custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) + // Code size 33 (0x21) + .maxstack 2 + .locals init (uint32 V_0, + uint32 V_1) + IL_0000: nop + IL_0001: ldarg.0 + IL_0002: stloc.0 + IL_0003: ldloc.0 + IL_0004: ldc.i4 0x773593ed + IL_0009: mul + IL_000a: stloc.0 + IL_000b: ldloc.0 + IL_000c: ldc.i4 0x773593ed + IL_0011: mul + IL_0012: stloc.0 + IL_0013: ldloc.0 + IL_0014: ldc.i4 0x773593ed + IL_0019: mul + IL_001a: stloc.0 + IL_001b: ldloc.0 + IL_001c: stloc.1 + IL_001d: br.s IL_001f + + IL_001f: ldloc.1 + IL_0020: ret + } // end of method Program::'<
$>g__B|0_1' + +} // end of class Program + + +// ============================================================= + +// *********** DISASSEMBLY COMPLETE *********************** diff --git a/src/tests/Loader/async/runtimetask-asyncfibonacci-without-yields.ilproj b/src/tests/Loader/async/runtimetask-asyncfibonacci-without-yields.ilproj new file mode 100644 index 00000000000000..bd17dd2bad5a7e --- /dev/null +++ b/src/tests/Loader/async/runtimetask-asyncfibonacci-without-yields.ilproj @@ -0,0 +1,8 @@ + + + 1 + + + + + diff --git a/src/tests/Loader/async/syncfibonacci-without-yields.cs b/src/tests/Loader/async/syncfibonacci-without-yields.cs index 9d668c9568367d..9f9c480660f038 100644 --- a/src/tests/Loader/async/syncfibonacci-without-yields.cs +++ b/src/tests/Loader/async/syncfibonacci-without-yields.cs @@ -29,20 +29,10 @@ static uint B(uint n) uint result = n; result = result * 1_999_999_981; - if (result < Threshold) - MyYield(); result = result * 1_999_999_981; - if (result < Threshold) - MyYield(); result = result * 1_999_999_981; - if (result < Threshold) - MyYield(); return result; } - -// Workaround for inlining of Thread.Yield inflating the caller's frame. -[MethodImpl(MethodImplOptions.NoInlining)] -static void MyYield() => Thread.Yield(); \ No newline at end of file From 84ae54db03ae64ee8df60978be2ecf9dec0e1530 Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Thu, 21 Sep 2023 17:29:31 -0700 Subject: [PATCH 010/203] Now we should be forcing transient lookup which wont' work --- src/coreclr/vm/method.cpp | 3 +++ src/coreclr/vm/method.hpp | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/coreclr/vm/method.cpp b/src/coreclr/vm/method.cpp index bf1ad0ed150b1a..f1f0d70368d8ad 100644 --- a/src/coreclr/vm/method.cpp +++ b/src/coreclr/vm/method.cpp @@ -1090,6 +1090,9 @@ COR_ILMETHOD* MethodDesc::GetILHeader(BOOL fAllowOverrides /*=FALSE*/) } CONTRACTL_END + if (IsRuntimeSupplied()) + return NULL; + Module *pModule = GetModule(); // Always pickup 'permanent' overrides like reflection emit, EnC, etc. diff --git a/src/coreclr/vm/method.hpp b/src/coreclr/vm/method.hpp index f4ae0b4342b028..09f8836f3ef080 100644 --- a/src/coreclr/vm/method.hpp +++ b/src/coreclr/vm/method.hpp @@ -611,7 +611,7 @@ class MethodDesc { LIMITED_METHOD_DAC_CONTRACT; return mcFCall == GetClassification() - || mcArray == GetClassification(); + || mcArray == GetClassification() && !IsAsyncThunkMethod(); } inline DWORD IsArray() const @@ -804,7 +804,7 @@ class MethodDesc MODE_ANY; } CONTRACTL_END; - return IsIL() && !IsUnboxingStub() && GetRVA(); + return IsIL() && !IsUnboxingStub() && GetRVA() && !IsRuntimeSupplied(); } COR_ILMETHOD* GetILHeader(BOOL fAllowOverrides = FALSE); From 84f7148e30b27f1d05106602a890c2ff8d5cc46f Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Fri, 22 Sep 2023 10:06:05 -0700 Subject: [PATCH 011/203] Fix method classification for async2 methods --- src/coreclr/vm/methodtablebuilder.cpp | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/coreclr/vm/methodtablebuilder.cpp b/src/coreclr/vm/methodtablebuilder.cpp index 6e67ca5ba377ec..59f4c00971976e 100644 --- a/src/coreclr/vm/methodtablebuilder.cpp +++ b/src/coreclr/vm/methodtablebuilder.cpp @@ -2708,7 +2708,7 @@ void GetNameOfTypeDefOrRef(Module* pModule, mdToken tk, LPCSTR* pName, LPCSTR* p } else if (TypeFromToken(tk) == mdtTypeRef) { - IfFailThrow(pModule->GetMDImport()->GetNameOfTypeRef(tk, pName, pNamespace)); + IfFailThrow(pModule->GetMDImport()->GetNameOfTypeRef(tk, pNamespace, pName)); } } @@ -2776,16 +2776,21 @@ AsyncTaskMethod ClassifyAsyncMethod(SigPointer sig, Module* pModule, ULONG* offs // Then this is a async2 function CorElementType elemType; *offsetOfAsyncDetails = (ULONG)(sig.GetPtr() - initialSig); - IfFailThrow(sig.GetElemType(&elemType)); + BYTE elemTypeByte; + IfFailThrow(sig.GetByte(&elemTypeByte)); // Don't use GetElemType as it skips custom modifiers + elemType = (CorElementType)elemTypeByte; + LPCSTR name, _namespace; mdToken tk; while ((elemType == ELEMENT_TYPE_CMOD_OPT) || (elemType == ELEMENT_TYPE_CMOD_REQD)) { + CorElementType elemTypeCmod = elemType; + IfFailThrow(sig.GetToken(&tk)); GetNameOfTypeDefOrRef(pModule, tk, &name, &_namespace); - *offsetOfAsyncDetails = (ULONG)(sig.GetPtr() - initialSig); - IfFailThrow(sig.GetElemType(&elemType)); - if (strcmp(name, "Task`1") == 0 && strcmp(_namespace, "System.Threading.Tasks") == 0 && IsTypeDefOrRefImplementedInSystemModule(pModule, tk)) + IfFailThrow(sig.GetByte(&elemTypeByte)); // Don't use GetElemType as it skips custom modifiers + elemType = (CorElementType)elemTypeByte; + if (strcmp(name, "Task`1") == 0 && strcmp(_namespace, "System.Threading.Tasks") == 0 && IsTypeDefOrRefImplementedInSystemModule(pModule, tk) && (elemTypeCmod == ELEMENT_TYPE_CMOD_OPT)) { // This must have been the last CMOD before the element type, and the element type MUST be one which can be expressed structurally as a generic parameter switch (elemType) @@ -2837,6 +2842,8 @@ AsyncTaskMethod ClassifyAsyncMethod(SigPointer sig, Module* pModule, ULONG* offs ThrowHR(COR_E_BADIMAGEFORMAT); } } + + *offsetOfAsyncDetails = (ULONG)(sig.GetPtr() - initialSig) - 1; } if (elemType == ELEMENT_TYPE_GENERICINST) From 51e20116fbcbc37dc2a149b6d9a289cef67e0949 Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Fri, 22 Sep 2023 10:49:43 -0700 Subject: [PATCH 012/203] We are now triggering the transient IL compilation for the thunks --- src/coreclr/vm/method.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/coreclr/vm/method.hpp b/src/coreclr/vm/method.hpp index 09f8836f3ef080..5fe3b0c4181caf 100644 --- a/src/coreclr/vm/method.hpp +++ b/src/coreclr/vm/method.hpp @@ -611,7 +611,8 @@ class MethodDesc { LIMITED_METHOD_DAC_CONTRACT; return mcFCall == GetClassification() - || mcArray == GetClassification() && !IsAsyncThunkMethod(); + || mcArray == GetClassification() + || IsAsyncThunkMethod(); } inline DWORD IsArray() const From 6fc166a00b2e9a4cdcf06892e2aa8d842a7988b2 Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Fri, 22 Sep 2023 16:13:32 -0700 Subject: [PATCH 013/203] Calling Sample Works! Now to implement an actual async implementation and see if suspension works NOTE: No methods with generic parameters will work with the current implementation, generics may require a bunch of infrastructure work, separate from the async work --- src/coreclr/vm/corelib.h | 8 + src/coreclr/vm/jitinterface.cpp | 2 +- src/coreclr/vm/metasig.h | 3 + src/coreclr/vm/method.cpp | 6 +- src/coreclr/vm/method.hpp | 14 +- src/coreclr/vm/methodtablebuilder.cpp | 9 - src/coreclr/vm/namespace.h | 1 + src/coreclr/vm/prestub.cpp | 179 ++++++++++++++++++ src/coreclr/vm/stubgen.cpp | 19 +- .../src/System/Threading/Tasks/Future.cs | 11 ++ 10 files changed, 238 insertions(+), 14 deletions(-) diff --git a/src/coreclr/vm/corelib.h b/src/coreclr/vm/corelib.h index 0dd79fb124f820..ba23da832bd834 100644 --- a/src/coreclr/vm/corelib.h +++ b/src/coreclr/vm/corelib.h @@ -350,6 +350,14 @@ DEFINE_METHOD(TYPE_INIT_EXCEPTION, STR_EX_CTOR, .ctor, DEFINE_CLASS(THREAD_START_EXCEPTION,Threading, ThreadStartException) DEFINE_METHOD(THREAD_START_EXCEPTION,EX_CTOR, .ctor, IM_Exception_RetVoid) +DEFINE_CLASS(TASK_1, Tasks, Task`1) + +DEFINE_CLASS(RUNTIME_TASK_STATE_1, Tasks, RuntimeTaskState`1) +DEFINE_METHOD(RUNTIME_TASK_STATE_1, PUSH, Push, IM_RetVoid) +DEFINE_METHOD(RUNTIME_TASK_STATE_1, POP, Pop, IM_RetVoid) +DEFINE_METHOD(RUNTIME_TASK_STATE_1, FROM_EXCEPTION, FromException, IM_Exception_RetTaskOfT) +DEFINE_METHOD(RUNTIME_TASK_STATE_1, FROM_RESULT, FromResult, IM_T_RetTaskOfT) + DEFINE_CLASS(TYPE_HANDLE, System, RuntimeTypeHandle) DEFINE_CLASS(RT_TYPE_HANDLE, System, RuntimeTypeHandle) DEFINE_METHOD(RT_TYPE_HANDLE, PVOID_CTOR, .ctor, IM_RuntimeType_RetVoid) diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index 57eb2a0f04c296..d577d15c37c36a 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -7665,7 +7665,7 @@ static void getMethodInfoHelper( } else { - if (!ftn->TryGenerateUnsafeAccessor(&cxt.TransientResolver, &cxt.Header)) + if (!ftn->TryGenerateTransientILImplementation(&cxt.TransientResolver, &cxt.Header)) ThrowHR(COR_E_BADIMAGEFORMAT); scopeHnd = cxt.CreateScopeHandle(); diff --git a/src/coreclr/vm/metasig.h b/src/coreclr/vm/metasig.h index dc2173f0440211..dff10ce40b8d49 100644 --- a/src/coreclr/vm/metasig.h +++ b/src/coreclr/vm/metasig.h @@ -574,6 +574,9 @@ DEFINE_METASIG_T(IM(Dec_RetVoid, g(DECIMAL), v)) DEFINE_METASIG_T(IM(Currency_RetVoid, g(CURRENCY), v)) DEFINE_METASIG_T(SM(RefDec_RetVoid, r(g(DECIMAL)), v)) +DEFINE_METASIG_T(IM(Exception_RetTaskOfT, C(EXCEPTION), GI(C(TASK_1), 1, G(0)))) +DEFINE_METASIG_T(IM(T_RetTaskOfT, G(0), GI(C(TASK_1), 1, G(0)))) + DEFINE_METASIG(GM(RefT_T_T_RetT, IMAGE_CEE_CS_CALLCONV_DEFAULT, 1, r(M(0)) M(0) M(0), M(0))) DEFINE_METASIG(SM(RefObject_Object_Object_RetObject, r(j) j j, j)) diff --git a/src/coreclr/vm/method.cpp b/src/coreclr/vm/method.cpp index f1f0d70368d8ad..f8d5bf993a1f02 100644 --- a/src/coreclr/vm/method.cpp +++ b/src/coreclr/vm/method.cpp @@ -3066,7 +3066,11 @@ bool MethodDesc::DetermineAndSetIsEligibleForTieredCompilation() !IsWrapperStub() && // Functions with NoOptimization or AggressiveOptimization don't participate in tiering - !IsJitOptimizationLevelRequested()) + !IsJitOptimizationLevelRequested() && + + // Tiering the async thunk methods doesn't make sense + !IsAsyncThunkMethod() + ) { m_wFlags3AndTokenRemainder |= enum_flag3_IsEligibleForTieredCompilation; _ASSERTE(IsVersionable()); diff --git a/src/coreclr/vm/method.hpp b/src/coreclr/vm/method.hpp index 5fe3b0c4181caf..7b0274e6057049 100644 --- a/src/coreclr/vm/method.hpp +++ b/src/coreclr/vm/method.hpp @@ -1541,6 +1541,7 @@ class MethodDesc MethodDesc* GetAsyncOtherVariant() { + // TODO This should be FindOrCreateAssociatedMethodDesc with some set of params return GetMethodTable()->GetParallelMethodDesc(this, AsyncVariantLookup::AsyncOtherVariant); } @@ -1866,8 +1867,10 @@ class MethodDesc PCODE JitCompileCodeLockedEventWrapper(PrepareCodeConfig* pConfig, JitListLockEntry* pEntry); PCODE JitCompileCodeLocked(PrepareCodeConfig* pConfig, COR_ILMETHOD_DECODER* pilHeader, JitListLockEntry* pLockEntry, ULONG* pSizeOfCode); -public: + bool TryGenerateAsyncThunk(DynamicResolver** resolver, COR_ILMETHOD_DECODER** methodILDecoder); bool TryGenerateUnsafeAccessor(DynamicResolver** resolver, COR_ILMETHOD_DECODER** methodILDecoder); +public: + bool TryGenerateTransientILImplementation(DynamicResolver** resolver, COR_ILMETHOD_DECODER** methodILDecoder); #endif // DACCESS_COMPILE #ifdef HAVE_GCCOVER @@ -3641,6 +3644,15 @@ ReadyToRunStandaloneMethodMetadata* GetReadyToRunStandaloneMethodMetadata(Method void InitReadyToRunStandaloneMethodMetadata(); #endif // FEATURE_READYTORUN +enum class AsyncTaskMethod +{ + TaskReturningMethod, + Async2Method, + Async2MethodThatCannotBeImplementedByTask, + NormalMethod +}; +AsyncTaskMethod ClassifyAsyncMethod(SigPointer sig, Module* pModule, ULONG* offsetOfAsyncDetails); + #include "method.inl" #endif // !_METHOD_H diff --git a/src/coreclr/vm/methodtablebuilder.cpp b/src/coreclr/vm/methodtablebuilder.cpp index 59f4c00971976e..c29c2f2554daa4 100644 --- a/src/coreclr/vm/methodtablebuilder.cpp +++ b/src/coreclr/vm/methodtablebuilder.cpp @@ -2690,14 +2690,6 @@ HRESULT MethodTableBuilder::FindMethodDeclarationForMethodImpl( #pragma warning(disable:21000) // Suppress PREFast warning about overly large function #endif // _PREFAST_ -enum class AsyncTaskMethod -{ - TaskReturningMethod, - Async2Method, - Async2MethodThatCannotBeImplementedByTask, - NormalMethod -}; - void GetNameOfTypeDefOrRef(Module* pModule, mdToken tk, LPCSTR* pName, LPCSTR* pNamespace) { *pName = ""; @@ -7528,7 +7520,6 @@ MethodTableBuilder::NeedsNativeCodeSlot(bmtMDMethod * pMDMethod) { LIMITED_METHOD_CONTRACT; - #ifdef FEATURE_TIERED_COMPILATION // Keep in-sync with MethodDesc::DetermineAndSetIsEligibleForTieredCompilation() if ((g_pConfig->TieredCompilation() && diff --git a/src/coreclr/vm/namespace.h b/src/coreclr/vm/namespace.h index 8b51f9ca623915..da1e97180d8769 100644 --- a/src/coreclr/vm/namespace.h +++ b/src/coreclr/vm/namespace.h @@ -16,6 +16,7 @@ #define g_RuntimeNS g_SystemNS ".Runtime" #define g_IONS g_SystemNS ".IO" #define g_ThreadingNS g_SystemNS ".Threading" +#define g_TasksNS g_ThreadingNS ".Tasks" #define g_CollectionsNS g_SystemNS ".Collections" #define g_ResourcesNS g_SystemNS ".Resources" #define g_DiagnosticsNS g_SystemNS ".Diagnostics" diff --git a/src/coreclr/vm/prestub.cpp b/src/coreclr/vm/prestub.cpp index 90768a47b51578..c3ae6666c1622d 100644 --- a/src/coreclr/vm/prestub.cpp +++ b/src/coreclr/vm/prestub.cpp @@ -1428,6 +1428,185 @@ namespace } } +bool MethodDesc::TryGenerateTransientILImplementation(DynamicResolver** resolver, COR_ILMETHOD_DECODER** methodILDecoder) +{ + if (TryGenerateAsyncThunk(resolver, methodILDecoder)) + { + return true; + } + + if (TryGenerateUnsafeAccessor(resolver, methodILDecoder)) + { + return true; + } + + return false; +} + +bool MethodDesc::TryGenerateAsyncThunk(DynamicResolver** resolver, COR_ILMETHOD_DECODER** methodILDecoder) +{ + STANDARD_VM_CONTRACT; + _ASSERTE(resolver != NULL); + _ASSERTE(methodILDecoder != NULL); + _ASSERTE(*resolver == NULL && *methodILDecoder == NULL); + _ASSERTE(IsIL()); + _ASSERTE(GetRVA() == 0); + + if (!IsAsyncThunkMethod()) + { + return false; + } + + // Create a MetaSig for the given method's sig. (Easier than + // picking the sig apart ourselves.) + PCCOR_SIGNATURE pCallSig; + DWORD cbCallSigSize; + + GetSig(&pCallSig, &cbCallSigSize); + + if (pCallSig == NULL) + { + _ASSERTE(FALSE); + return false; + } + + ULONG offsetOfAsyncDetailsUnused; + AsyncTaskMethod asyncType = ClassifyAsyncMethod(GetSigPointer(), GetModule(), &offsetOfAsyncDetailsUnused); + if (asyncType != AsyncTaskMethod::TaskReturningMethod) + { + // Async to task helpers not yet implemented + _ASSERTE(FALSE); + return false; + } + + MethodDesc *pAsyncOtherVariant = this->GetAsyncOtherVariant(); + + MetaSig msig(pCallSig, cbCallSigSize, this->GetModule(), NULL, MetaSig::sigMember); + + TypeHandle thTaskRet = msig.GetRetTypeHandleThrowing(); + TypeHandle thLogicalRetType = thTaskRet.GetMethodTable()->GetInstantiation()[0]; + MethodTable* pMTRuntimeTaskStateOpen = CoreLibBinder::GetClass(CLASS__RUNTIME_TASK_STATE_1); + + MethodTable* pMTRuntimeTaskState = ClassLoader::LoadGenericInstantiationThrowing(pMTRuntimeTaskStateOpen->GetModule(), pMTRuntimeTaskStateOpen->GetCl(), Instantiation(&thLogicalRetType, 1)).GetMethodTable(); + NewHolder ilResolver = new ILStubResolver(); + + // Initialize the resolver target details. + ilResolver->SetStubMethodDesc(this); + ilResolver->SetStubTargetMethodDesc(pAsyncOtherVariant); + + // [TODO] Handle generics + SigTypeContext emptyContext; + ILStubLinker sl( + GetModule(), + GetSignature(), + &emptyContext, + pAsyncOtherVariant, + (ILStubLinkerFlags)ILSTUB_LINKER_FLAG_NONE); + + ILCodeStream* pCode = sl.NewCodeStream(ILStubLinker::kDispatch); + LocalDesc rtsLocalDesc(pMTRuntimeTaskState); + DWORD rtsLocal = pCode->NewLocal(rtsLocalDesc); + + LocalDesc resultLocalDesc(thLogicalRetType); + DWORD resultLocal = pCode->NewLocal(resultLocalDesc); + + LocalDesc exceptionLocalDesc(CoreLibBinder::GetClass(CLASS__EXCEPTION)); + DWORD exceptionLocal = pCode->NewLocal(exceptionLocalDesc); + + LocalDesc returnLocalDesc(thTaskRet); + DWORD returnLocal = pCode->NewLocal(returnLocalDesc); + + auto pNoExceptionLabel = pCode->NewCodeLabel(); + auto pReturnResultLabel = pCode->NewCodeLabel(); + + pCode->EmitLDLOCA(rtsLocal); + pCode->EmitINITOBJ(pCode->GetToken(pMTRuntimeTaskState)); + pCode->EmitLDLOCA(resultLocal); + pCode->EmitINITOBJ(pCode->GetToken(thLogicalRetType)); + pCode->EmitLDLOCA(exceptionLocal); + pCode->EmitINITOBJ(pCode->GetToken(CoreLibBinder::GetClass(CLASS__EXCEPTION))); + pCode->EmitLDLOCA(returnLocal); + pCode->EmitINITOBJ(pCode->GetToken(thTaskRet)); + pCode->EmitLDLOCA(rtsLocal); + pCode->EmitCALL(pCode->GetToken(pMTRuntimeTaskState->GetParallelMethodDesc(CoreLibBinder::GetMethod(METHOD__RUNTIME_TASK_STATE_1__PUSH))), 1, 0); + { + pCode->BeginTryBlock(); + pCode->EmitNOP("Separate try blocks"); + { + pCode->BeginTryBlock(); + + DWORD localArg = 0; + if (msig.HasThis()) + { + // Struct async thunks not yet implemented + _ASSERTE(!this->GetMethodTable()->IsValueType()); + pCode->EmitLDARG(localArg++); + } + for (UINT iArg = 0; iArg < msig.NumFixedArgs(); iArg++) + { + pCode->EmitLDARG(localArg++); + } + pCode->EmitCALL(pCode->GetToken(pAsyncOtherVariant), localArg, 1); + pCode->EmitSTLOC(resultLocal); + pCode->EmitLEAVE(pNoExceptionLabel); + pCode->EndTryBlock(); + } + // Catch + { + pCode->BeginCatchBlock(pCode->GetToken(CoreLibBinder::GetClass(CLASS__EXCEPTION))); + pCode->EmitSTLOC(exceptionLocal); + pCode->EmitLDLOCA(rtsLocal); + pCode->EmitLDLOC(exceptionLocal); + pCode->EmitCALL(pCode->GetToken(pMTRuntimeTaskState->GetParallelMethodDesc(CoreLibBinder::GetMethod(METHOD__RUNTIME_TASK_STATE_1__FROM_EXCEPTION))), 2, 1); + pCode->EmitSTLOC(returnLocal); + pCode->EmitLEAVE(pReturnResultLabel); + pCode->EndCatchBlock(); + } + pCode->EmitLabel(pNoExceptionLabel); + pCode->EmitLDLOCA(rtsLocal); + pCode->EmitLDLOC(resultLocal); + pCode->EmitCALL(pCode->GetToken(pMTRuntimeTaskState->GetParallelMethodDesc(CoreLibBinder::GetMethod(METHOD__RUNTIME_TASK_STATE_1__FROM_RESULT))), 2, 1); + pCode->EmitSTLOC(returnLocal); + pCode->EmitLEAVE(pReturnResultLabel); + pCode->EndTryBlock(); + } + // Finally + { + pCode->BeginFinallyBlock(); + pCode->EmitLDLOCA(rtsLocal); + pCode->EmitCALL(pCode->GetToken(pMTRuntimeTaskState->GetParallelMethodDesc(CoreLibBinder::GetMethod(METHOD__RUNTIME_TASK_STATE_1__POP))), 1, 0); + pCode->EmitENDFINALLY(); + pCode->EndFinallyBlock(); + } + pCode->EmitLabel(pReturnResultLabel); + pCode->EmitLDLOC(returnLocal); + pCode->EmitRET(); + + // Generate all IL associated data for JIT + { + UINT maxStack; + size_t cbCode = sl.Link(&maxStack); + DWORD cbSig = sl.GetLocalSigSize(); + + COR_ILMETHOD_DECODER* pILHeader = ilResolver->AllocGeneratedIL(cbCode, cbSig, maxStack); + BYTE* pbBuffer = (BYTE*)pILHeader->Code; + BYTE* pbLocalSig = (BYTE*)pILHeader->LocalVarSig; + _ASSERTE(cbSig == pILHeader->cbLocalVarSig); + sl.GenerateCode(pbBuffer, cbCode); + sl.GetLocalSig(pbLocalSig, cbSig); + + // Store the token lookup map + ilResolver->SetTokenLookupMap(sl.GetTokenLookupMap()); + ilResolver->SetJitFlags(CORJIT_FLAGS(CORJIT_FLAGS::CORJIT_FLAG_IL_STUB)); + + *resolver = (DynamicResolver*)ilResolver; + *methodILDecoder = pILHeader; + } + + ilResolver.SuppressRelease(); + return true; +} + bool MethodDesc::TryGenerateUnsafeAccessor(DynamicResolver** resolver, COR_ILMETHOD_DECODER** methodILDecoder) { STANDARD_VM_CONTRACT; diff --git a/src/coreclr/vm/stubgen.cpp b/src/coreclr/vm/stubgen.cpp index 78e7fb621c90d5..7c8be127608431 100644 --- a/src/coreclr/vm/stubgen.cpp +++ b/src/coreclr/vm/stubgen.cpp @@ -173,6 +173,21 @@ void ILCodeStream::Emit(ILInstrEnum instr, INT16 iStackDelta, UINT_PTR uArg) pInstrBuffer[idxCurInstr].uInstruction = static_cast(instr); pInstrBuffer[idxCurInstr].iStackDelta = iStackDelta; pInstrBuffer[idxCurInstr].uArg = uArg; + + if(m_buildingEHClauses.GetCount() > 0) + { + ILStubEHClauseBuilder& clause = m_buildingEHClauses[m_buildingEHClauses.GetCount() - 1]; + + if (clause.tryBeginLabel != NULL && clause.tryEndLabel != NULL && + clause.handlerBeginLabel != NULL && clause.kind == ILStubEHClause::kTypedCatch) + { + if (clause.handlerBeginLabel->m_idxLabeledInstruction == idxCurInstr) + { + // Catch clauses start with an exception on the stack + pInstrBuffer[idxCurInstr].iStackDelta++; + } + } + } } ILCodeLabel* ILStubLinker::NewCodeLabel() @@ -819,10 +834,10 @@ size_t ILStubLinker::Link(UINT* puMaxStack) } #ifdef _DEBUG - if (fStackUnderflow) + if (fStackUnderflow || this->GetNumEHClauses() != 0) { LogILStub(CORJIT_FLAGS()); - CONSISTENCY_CHECK_MSG(false, "IL stack underflow! -- see logging output"); +// CONSISTENCY_CHECK_MSG(false, "IL stack underflow! -- see logging output"); } #endif // _DEBUG diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Future.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Future.cs index 827d6915dd3252..19f359a333383a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Future.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Future.cs @@ -15,6 +15,17 @@ namespace System.Threading.Tasks { +#pragma warning disable CA1822 + // Used to implement Runtime implemented Task suspension handling + internal struct RuntimeTaskState + { + public void Push() {} + public void Pop() {} + public Task FromResult(TResult result) { return Task.FromResult(result); } + public Task FromException(Exception e) { return Task.FromException(e); } + } +#pragma warning restore CA1822 + /// /// Represents an asynchronous operation that produces a result at some time in the future. /// From 0b10933e1a2eebeb453f05ec4e6e52b912bc09e4 Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Mon, 25 Sep 2023 14:56:10 -0700 Subject: [PATCH 014/203] WithYield runtime task test --- .../runtimetask-asyncfibonacci-with-yields.il | 535 ++++++++++++++++++ ...timetask-asyncfibonacci-with-yields.ilproj | 8 + 2 files changed, 543 insertions(+) create mode 100644 src/tests/Loader/async/runtimetask-asyncfibonacci-with-yields.il create mode 100644 src/tests/Loader/async/runtimetask-asyncfibonacci-with-yields.ilproj diff --git a/src/tests/Loader/async/runtimetask-asyncfibonacci-with-yields.il b/src/tests/Loader/async/runtimetask-asyncfibonacci-with-yields.il new file mode 100644 index 00000000000000..2d8008483407e1 --- /dev/null +++ b/src/tests/Loader/async/runtimetask-asyncfibonacci-with-yields.il @@ -0,0 +1,535 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + + +// .NET IL Disassembler. Version 9.0.0-dev + + + +// Metadata version: v4.0.30319 +.assembly extern System.Runtime +{ + .publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A ) // .?_....: + .ver 9:0:0:0 +} +.assembly extern System.Console +{ + .publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A ) // .?_....: + .ver 9:0:0:0 +} +.assembly 'runtimetask-asyncfibonacci-with-yields' +{ + .custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilationRelaxationsAttribute::.ctor(int32) = ( 01 00 08 00 00 00 00 00 ) + .custom instance void [System.Runtime]System.Runtime.CompilerServices.RuntimeCompatibilityAttribute::.ctor() = ( 01 00 01 00 54 02 16 57 72 61 70 4E 6F 6E 45 78 // ....T..WrapNonEx + 63 65 70 74 69 6F 6E 54 68 72 6F 77 73 01 ) // ceptionThrows. + + // --- The following custom attribute is added automatically, do not uncomment ------- + // .custom instance void [System.Runtime]System.Diagnostics.DebuggableAttribute::.ctor(valuetype [System.Runtime]System.Diagnostics.DebuggableAttribute/DebuggingModes) = ( 01 00 07 01 00 00 00 00 ) + + .hash algorithm 0x00008004 + .ver 0:0:0:0 +} +.module 'runtimetask-asyncfibonacci-with-yields.dll' +// MVID: {ca42840a-c801-47a9-8471-ea986ae2c60d} +.custom instance void [System.Runtime]System.Runtime.CompilerServices.RefSafetyRulesAttribute::.ctor(int32) = ( 01 00 0B 00 00 00 00 00 ) +.imagebase 0x00400000 +.file alignment 0x00000200 +.stackreserve 0x00100000 +.subsystem 0x0003 // WINDOWS_CUI +.corflags 0x00000001 // ILONLY +// Image base: 0x000001E00BDE0000 + + +// =============== CLASS MEMBERS DECLARATION =================== + +.class private auto ansi beforefieldinit Program + extends [System.Runtime]System.Object +{ + .class auto ansi sealed nested private beforefieldinit '<
$>d__0' + extends [System.Runtime]System.Object + implements [System.Runtime]System.Runtime.CompilerServices.IAsyncStateMachine + { + .custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) + .field public int32 '<>1__state' + .field public valuetype [System.Runtime]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1 '<>t__builder' + .field public string[] args + .field private int32 '5__1' + .field private class [System.Runtime]System.Diagnostics.Stopwatch '5__2' + .field private uint32 '5__3' + .field private uint32 '<>s__4' + .field private valuetype [System.Runtime]System.Runtime.CompilerServices.TaskAwaiter`1 '<>u__1' + .method public hidebysig specialname rtspecialname + instance void .ctor() cil managed + { + // Code size 8 (0x8) + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: call instance void [System.Runtime]System.Object::.ctor() + IL_0006: nop + IL_0007: ret + } // end of method '<
$>d__0'::.ctor + + .method private hidebysig newslot virtual final + instance void MoveNext() cil managed + { + .override [System.Runtime]System.Runtime.CompilerServices.IAsyncStateMachine::MoveNext + // Code size 334 (0x14e) + .maxstack 3 + .locals init (int32 V_0, + int32 V_1, + valuetype [System.Runtime]System.Runtime.CompilerServices.TaskAwaiter`1 V_2, + class Program/'<
$>d__0' V_3, + valuetype [System.Runtime]System.Runtime.CompilerServices.DefaultInterpolatedStringHandler V_4, + int32 V_5, + bool V_6, + class [System.Runtime]System.Exception V_7) + IL_0000: ldarg.0 + IL_0001: ldfld int32 Program/'<
$>d__0'::'<>1__state' + IL_0006: stloc.0 + .try + { + IL_0007: ldloc.0 + IL_0008: brfalse.s IL_000c + + IL_000a: br.s IL_000e + + IL_000c: br.s IL_0072 + + IL_000e: ldarg.0 + IL_000f: ldc.i4.0 + IL_0010: stfld int32 Program/'<
$>d__0'::'5__1' + IL_0015: br IL_0106 + + IL_001a: nop + IL_001b: ldarg.0 + IL_001c: newobj instance void [System.Runtime]System.Diagnostics.Stopwatch::.ctor() + IL_0021: stfld class [System.Runtime]System.Diagnostics.Stopwatch Program/'<
$>d__0'::'5__2' + IL_0026: ldarg.0 + IL_0027: ldfld class [System.Runtime]System.Diagnostics.Stopwatch Program/'<
$>d__0'::'5__2' + IL_002c: callvirt instance void [System.Runtime]System.Diagnostics.Stopwatch::Start() + IL_0031: nop + IL_0032: ldc.i4 0x5f5e100 + IL_0037: call class [System.Runtime]System.Threading.Tasks.Task`1 Program::'<
$>g__A|0_0'(uint32) + IL_003c: callvirt instance valuetype [System.Runtime]System.Runtime.CompilerServices.TaskAwaiter`1 class [System.Runtime]System.Threading.Tasks.Task`1::GetAwaiter() + IL_0041: stloc.2 + IL_0042: ldloca.s V_2 + IL_0044: call instance bool valuetype [System.Runtime]System.Runtime.CompilerServices.TaskAwaiter`1::get_IsCompleted() + IL_0049: brtrue.s IL_008e + + IL_004b: ldarg.0 + IL_004c: ldc.i4.0 + IL_004d: dup + IL_004e: stloc.0 + IL_004f: stfld int32 Program/'<
$>d__0'::'<>1__state' + IL_0054: ldarg.0 + IL_0055: ldloc.2 + IL_0056: stfld valuetype [System.Runtime]System.Runtime.CompilerServices.TaskAwaiter`1 Program/'<
$>d__0'::'<>u__1' + IL_005b: ldarg.0 + IL_005c: stloc.3 + IL_005d: ldarg.0 + IL_005e: ldflda valuetype [System.Runtime]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1 Program/'<
$>d__0'::'<>t__builder' + IL_0063: ldloca.s V_2 + IL_0065: ldloca.s V_3 + IL_0067: call instance void valuetype [System.Runtime]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1::AwaitUnsafeOnCompleted,class Program/'<
$>d__0'>(!!0&, + !!1&) + IL_006c: nop + IL_006d: leave IL_014d + + IL_0072: ldarg.0 + IL_0073: ldfld valuetype [System.Runtime]System.Runtime.CompilerServices.TaskAwaiter`1 Program/'<
$>d__0'::'<>u__1' + IL_0078: stloc.2 + IL_0079: ldarg.0 + IL_007a: ldflda valuetype [System.Runtime]System.Runtime.CompilerServices.TaskAwaiter`1 Program/'<
$>d__0'::'<>u__1' + IL_007f: initobj valuetype [System.Runtime]System.Runtime.CompilerServices.TaskAwaiter`1 + IL_0085: ldarg.0 + IL_0086: ldc.i4.m1 + IL_0087: dup + IL_0088: stloc.0 + IL_0089: stfld int32 Program/'<
$>d__0'::'<>1__state' + IL_008e: ldarg.0 + IL_008f: ldloca.s V_2 + IL_0091: call instance !0 valuetype [System.Runtime]System.Runtime.CompilerServices.TaskAwaiter`1::GetResult() + IL_0096: stfld uint32 Program/'<
$>d__0'::'<>s__4' + IL_009b: ldarg.0 + IL_009c: ldarg.0 + IL_009d: ldfld uint32 Program/'<
$>d__0'::'<>s__4' + IL_00a2: stfld uint32 Program/'<
$>d__0'::'5__3' + IL_00a7: ldc.i4.s 11 + IL_00a9: ldc.i4.2 + IL_00aa: newobj instance void [System.Runtime]System.Runtime.CompilerServices.DefaultInterpolatedStringHandler::.ctor(int32, + int32) + IL_00af: stloc.s V_4 + IL_00b1: ldloca.s V_4 + IL_00b3: ldarg.0 + IL_00b4: ldfld class [System.Runtime]System.Diagnostics.Stopwatch Program/'<
$>d__0'::'5__2' + IL_00b9: callvirt instance int64 [System.Runtime]System.Diagnostics.Stopwatch::get_ElapsedMilliseconds() + IL_00be: call instance void [System.Runtime]System.Runtime.CompilerServices.DefaultInterpolatedStringHandler::AppendFormatted(!!0) + IL_00c3: nop + IL_00c4: ldloca.s V_4 + IL_00c6: ldstr " ms result=" + IL_00cb: call instance void [System.Runtime]System.Runtime.CompilerServices.DefaultInterpolatedStringHandler::AppendLiteral(string) + IL_00d0: nop + IL_00d1: ldloca.s V_4 + IL_00d3: ldarg.0 + IL_00d4: ldfld uint32 Program/'<
$>d__0'::'5__3' + IL_00d9: call instance void [System.Runtime]System.Runtime.CompilerServices.DefaultInterpolatedStringHandler::AppendFormatted(!!0) + IL_00de: nop + IL_00df: ldloca.s V_4 + IL_00e1: call instance string [System.Runtime]System.Runtime.CompilerServices.DefaultInterpolatedStringHandler::ToStringAndClear() + IL_00e6: call void [System.Console]System.Console::WriteLine(string) + IL_00eb: nop + IL_00ec: nop + IL_00ed: ldarg.0 + IL_00ee: ldnull + IL_00ef: stfld class [System.Runtime]System.Diagnostics.Stopwatch Program/'<
$>d__0'::'5__2' + IL_00f4: ldarg.0 + IL_00f5: ldfld int32 Program/'<
$>d__0'::'5__1' + IL_00fa: stloc.s V_5 + IL_00fc: ldarg.0 + IL_00fd: ldloc.s V_5 + IL_00ff: ldc.i4.1 + IL_0100: add + IL_0101: stfld int32 Program/'<
$>d__0'::'5__1' + IL_0106: ldarg.0 + IL_0107: ldfld int32 Program/'<
$>d__0'::'5__1' + IL_010c: ldc.i4.s 10 + IL_010e: clt + IL_0110: stloc.s V_6 + IL_0112: ldloc.s V_6 + IL_0114: brtrue IL_001a + + IL_0119: ldc.i4.s 100 + IL_011b: stloc.1 + IL_011c: leave.s IL_0138 + + } // end .try + catch [System.Runtime]System.Exception + { + IL_011e: stloc.s V_7 + IL_0120: ldarg.0 + IL_0121: ldc.i4.s -2 + IL_0123: stfld int32 Program/'<
$>d__0'::'<>1__state' + IL_0128: ldarg.0 + IL_0129: ldflda valuetype [System.Runtime]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1 Program/'<
$>d__0'::'<>t__builder' + IL_012e: ldloc.s V_7 + IL_0130: call instance void valuetype [System.Runtime]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1::SetException(class [System.Runtime]System.Exception) + IL_0135: nop + IL_0136: leave.s IL_014d + + } // end handler + IL_0138: ldarg.0 + IL_0139: ldc.i4.s -2 + IL_013b: stfld int32 Program/'<
$>d__0'::'<>1__state' + IL_0140: ldarg.0 + IL_0141: ldflda valuetype [System.Runtime]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1 Program/'<
$>d__0'::'<>t__builder' + IL_0146: ldloc.1 + IL_0147: call instance void valuetype [System.Runtime]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1::SetResult(!0) + IL_014c: nop + IL_014d: ret + } // end of method '<
$>d__0'::MoveNext + + .method private hidebysig newslot virtual final + instance void SetStateMachine(class [System.Runtime]System.Runtime.CompilerServices.IAsyncStateMachine stateMachine) cil managed + { + .custom instance void [System.Runtime]System.Diagnostics.DebuggerHiddenAttribute::.ctor() = ( 01 00 00 00 ) + .override [System.Runtime]System.Runtime.CompilerServices.IAsyncStateMachine::SetStateMachine + // Code size 1 (0x1) + .maxstack 8 + IL_0000: ret + } // end of method '<
$>d__0'::SetStateMachine + + } // end of class '<
$>d__0' + + .method private hidebysig static class [System.Runtime]System.Threading.Tasks.Task`1 + '
$'(string[] args) cil managed + { + .custom instance void [System.Runtime]System.Runtime.CompilerServices.AsyncStateMachineAttribute::.ctor(class [System.Runtime]System.Type) = ( 01 00 15 50 72 6F 67 72 61 6D 2B 3C 3C 4D 61 69 // ...Program+<$>d__0.. + .custom instance void [System.Runtime]System.Diagnostics.DebuggerStepThroughAttribute::.ctor() = ( 01 00 00 00 ) + // Code size 57 (0x39) + .maxstack 2 + .locals init (class Program/'<
$>d__0' V_0) + IL_0000: newobj instance void Program/'<
$>d__0'::.ctor() + IL_0005: stloc.0 + IL_0006: ldloc.0 + IL_0007: call valuetype [System.Runtime]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1 valuetype [System.Runtime]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1::Create() + IL_000c: stfld valuetype [System.Runtime]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1 Program/'<
$>d__0'::'<>t__builder' + IL_0011: ldloc.0 + IL_0012: ldarg.0 + IL_0013: stfld string[] Program/'<
$>d__0'::args + IL_0018: ldloc.0 + IL_0019: ldc.i4.m1 + IL_001a: stfld int32 Program/'<
$>d__0'::'<>1__state' + IL_001f: ldloc.0 + IL_0020: ldflda valuetype [System.Runtime]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1 Program/'<
$>d__0'::'<>t__builder' + IL_0025: ldloca.s V_0 + IL_0027: call instance void valuetype [System.Runtime]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1::Start$>d__0'>(!!0&) + IL_002c: nop + IL_002d: ldloc.0 + IL_002e: ldflda valuetype [System.Runtime]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1 Program/'<
$>d__0'::'<>t__builder' + IL_0033: call instance class [System.Runtime]System.Threading.Tasks.Task`1 valuetype [System.Runtime]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1::get_Task() + IL_0038: ret + } // end of method Program::'
$' + + .method public hidebysig specialname rtspecialname + instance void .ctor() cil managed + { + // Code size 8 (0x8) + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: call instance void [System.Runtime]System.Object::.ctor() + IL_0006: nop + IL_0007: ret + } // end of method Program::.ctor + + .method private hidebysig specialname static + int32 '
'(string[] args) cil managed + { + .entrypoint + .custom instance void [System.Runtime]System.Diagnostics.DebuggerStepThroughAttribute::.ctor() = ( 01 00 00 00 ) + // Code size 20 (0x14) + .maxstack 1 + .locals init (valuetype [System.Runtime]System.Runtime.CompilerServices.TaskAwaiter`1 V_0) + IL_0000: ldarg.0 + IL_0001: call class [System.Runtime]System.Threading.Tasks.Task`1 Program::'
$'(string[]) + IL_0006: callvirt instance valuetype [System.Runtime]System.Runtime.CompilerServices.TaskAwaiter`1 class [System.Runtime]System.Threading.Tasks.Task`1::GetAwaiter() + IL_000b: stloc.0 + IL_000c: ldloca.s V_0 + IL_000e: call instance !0 valuetype [System.Runtime]System.Runtime.CompilerServices.TaskAwaiter`1::GetResult() + IL_0013: ret + } // end of method Program::'
' + + .method assembly hidebysig static uint32 modopt([System.Runtime]System.Threading.Tasks.Task`1) + '<
$>g__A|0_0'(uint32 n) cil managed + { + .custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) + // Code size 32 (0x20) + .maxstack 2 + .locals init (uint32 V_0, + uint32 V_1, + bool V_2, + uint32 V_3) + IL_0000: nop + IL_0001: ldarg.0 + IL_0002: stloc.0 + IL_0003: ldc.i4.0 + IL_0004: stloc.1 + IL_0005: br.s IL_0012 + + IL_0007: ldloc.0 + IL_0008: call uint32 modopt([System.Runtime]System.Threading.Tasks.Task`1) Program::'<
$>g__B|0_1'(uint32) + IL_000d: stloc.0 + IL_000e: ldloc.1 + IL_000f: ldc.i4.1 + IL_0010: add + IL_0011: stloc.1 + IL_0012: ldloc.1 + IL_0013: ldarg.0 + IL_0014: clt.un + IL_0016: stloc.2 + IL_0017: ldloc.2 + IL_0018: brtrue.s IL_0007 + + IL_001a: ldloc.0 + IL_001b: stloc.3 + IL_001c: br.s IL_001e + + IL_001e: ldloc.3 + IL_001f: ret + } // end of method Program::'<
$>g__A|0_0' + + .method assembly hidebysig static uint32 modopt([System.Runtime]System.Threading.Tasks.Task`1) + '<
$>g__B|0_1'(uint32 n) cil managed + { + .custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) + // Code size 89 (0x59) + .maxstack 2 + .locals init (uint32 V_0, + bool V_1, + bool V_2, + bool V_3, + uint32 V_4) + IL_0000: nop + IL_0001: ldarg.0 + IL_0002: stloc.0 + IL_0003: ldloc.0 + IL_0004: ldc.i4 0x773593ed + IL_0009: mul + IL_000a: stloc.0 + IL_000b: ldloc.0 + IL_000c: ldc.i4 0x3e8 + IL_0011: clt.un + IL_0013: stloc.1 + IL_0014: ldloc.1 + IL_0015: brfalse.s IL_001d + IL_0017: call int modopt([System.Runtime]System.Threading.Tasks.Task`1) Program::'<
$>g__MyYield|0_2'() + IL_001c: pop + IL_001d: ldloc.0 + IL_001e: ldc.i4 0x773593ed + IL_0023: mul + IL_0024: stloc.0 + IL_0025: ldloc.0 + IL_0026: ldc.i4 0x3e8 + IL_002b: clt.un + IL_002d: stloc.2 + IL_002e: ldloc.2 + IL_002f: brfalse.s IL_0037 + IL_0031: call int modopt([System.Runtime]System.Threading.Tasks.Task`1) Program::'<
$>g__MyYield|0_2'() + IL_0036: pop + IL_0037: ldloc.0 + IL_0038: ldc.i4 0x773593ed + IL_003d: mul + IL_003e: stloc.0 + IL_003f: ldloc.0 + IL_0040: ldc.i4 0x3e8 + IL_0045: clt.un + IL_0047: stloc.3 + IL_0048: ldloc.3 + IL_0049: brfalse.s IL_0051 + IL_004b: call int modopt([System.Runtime]System.Threading.Tasks.Task`1) Program::'<
$>g__MyYield|0_2'() + IL_0050: pop + IL_0051: ldloc.0 + IL_0052: stloc.s V_4 + IL_0054: br.s IL_0056 + IL_0056: ldloc.s V_4 + IL_0058: ret + } // end of method Program::'<
$>g__B|0_1' + +.class auto ansi sealed nested private beforefieldinit '<<
$>g__MyYield|0_2>d' + extends [System.Runtime]System.ValueType + implements [System.Runtime]System.Runtime.CompilerServices.IAsyncStateMachine +{ + .custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) + .field public int32 '<>1__state' + .field public valuetype [System.Runtime]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1 '<>t__builder' + .field private valuetype [System.Runtime]System.Runtime.CompilerServices.YieldAwaitable/YieldAwaiter '<>u__1' + +.method private hidebysig newslot virtual final + instance void MoveNext() cil managed +{ + .override [System.Runtime]System.Runtime.CompilerServices.IAsyncStateMachine::MoveNext + // Code size 150 (0x96) + .maxstack 3 + .locals init (int32 V_0, + int32 V_1, + valuetype [System.Runtime]System.Runtime.CompilerServices.YieldAwaitable/YieldAwaiter V_2, + valuetype [System.Runtime]System.Runtime.CompilerServices.YieldAwaitable V_3, + class [System.Runtime]System.Exception V_4) + IL_0000: ldarg.0 + IL_0001: ldfld int32 Program/'<<
$>g__MyYield|0_2>d'::'<>1__state' + IL_0006: stloc.0 + .try + { + IL_0007: ldloc.0 + IL_0008: brfalse.s IL_0041 + IL_000a: call valuetype [System.Runtime]System.Runtime.CompilerServices.YieldAwaitable [System.Runtime]System.Threading.Tasks.Task::Yield() + IL_000f: stloc.3 + IL_0010: ldloca.s V_3 + IL_0012: call instance valuetype [System.Runtime]System.Runtime.CompilerServices.YieldAwaitable/YieldAwaiter [System.Runtime]System.Runtime.CompilerServices.YieldAwaitable::GetAwaiter() + IL_0017: stloc.2 + IL_0018: ldloca.s V_2 + IL_001a: call instance bool [System.Runtime]System.Runtime.CompilerServices.YieldAwaitable/YieldAwaiter::get_IsCompleted() + IL_001f: brtrue.s IL_005d + IL_0021: ldarg.0 + IL_0022: ldc.i4.0 + IL_0023: dup + IL_0024: stloc.0 + IL_0025: stfld int32 Program/'<<
$>g__MyYield|0_2>d'::'<>1__state' + IL_002a: ldarg.0 + IL_002b: ldloc.2 + IL_002c: stfld valuetype [System.Runtime]System.Runtime.CompilerServices.YieldAwaitable/YieldAwaiter Program/'<<
$>g__MyYield|0_2>d'::'<>u__1' + IL_0031: ldarg.0 + IL_0032: ldflda valuetype [System.Runtime]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1 Program/'<<
$>g__MyYield|0_2>d'::'<>t__builder' + IL_0037: ldloca.s V_2 + IL_0039: ldarg.0 + IL_003a: call instance void valuetype [System.Runtime]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1::AwaitUnsafeOnCompleted$>g__MyYield|0_2>d'>(!!0&, + !!1&) + IL_003f: leave.s IL_0095 + IL_0041: ldarg.0 + IL_0042: ldfld valuetype [System.Runtime]System.Runtime.CompilerServices.YieldAwaitable/YieldAwaiter Program/'<<
$>g__MyYield|0_2>d'::'<>u__1' + IL_0047: stloc.2 + IL_0048: ldarg.0 + IL_0049: ldflda valuetype [System.Runtime]System.Runtime.CompilerServices.YieldAwaitable/YieldAwaiter Program/'<<
$>g__MyYield|0_2>d'::'<>u__1' + IL_004e: initobj [System.Runtime]System.Runtime.CompilerServices.YieldAwaitable/YieldAwaiter + IL_0054: ldarg.0 + IL_0055: ldc.i4.m1 + IL_0056: dup + IL_0057: stloc.0 + IL_0058: stfld int32 Program/'<<
$>g__MyYield|0_2>d'::'<>1__state' + IL_005d: ldloca.s V_2 + IL_005f: call instance void [System.Runtime]System.Runtime.CompilerServices.YieldAwaitable/YieldAwaiter::GetResult() + IL_0064: ldc.i4.0 + IL_0065: stloc.1 + IL_0066: leave.s IL_0081 + } // end .try + catch [System.Runtime]System.Exception + { + IL_0068: stloc.s V_4 + IL_006a: ldarg.0 + IL_006b: ldc.i4.s -2 + IL_006d: stfld int32 Program/'<<
$>g__MyYield|0_2>d'::'<>1__state' + IL_0072: ldarg.0 + IL_0073: ldflda valuetype [System.Runtime]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1 Program/'<<
$>g__MyYield|0_2>d'::'<>t__builder' + IL_0078: ldloc.s V_4 + IL_007a: call instance void valuetype [System.Runtime]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1::SetException(class [System.Runtime]System.Exception) + IL_007f: leave.s IL_0095 + } // end handler + IL_0081: ldarg.0 + IL_0082: ldc.i4.s -2 + IL_0084: stfld int32 Program/'<<
$>g__MyYield|0_2>d'::'<>1__state' + IL_0089: ldarg.0 + IL_008a: ldflda valuetype [System.Runtime]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1 Program/'<<
$>g__MyYield|0_2>d'::'<>t__builder' + IL_008f: ldloc.1 + IL_0090: call instance void valuetype [System.Runtime]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1::SetResult(!0) + IL_0095: ret +} // end of method '<<
$>g__MyYield|0_2>d'::MoveNext +.method private hidebysig newslot virtual final + instance void SetStateMachine(class [System.Runtime]System.Runtime.CompilerServices.IAsyncStateMachine stateMachine) cil managed +{ + .custom instance void [System.Runtime]System.Diagnostics.DebuggerHiddenAttribute::.ctor() = ( 01 00 00 00 ) + .param [1] + .custom instance void System.Runtime.CompilerServices.NullableAttribute::.ctor(uint8) = ( 01 00 01 00 00 ) + .override [System.Runtime]System.Runtime.CompilerServices.IAsyncStateMachine::SetStateMachine + // Code size 13 (0xd) + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: ldflda valuetype [System.Runtime]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1 Program/'<<
$>g__MyYield|0_2>d'::'<>t__builder' + IL_0006: ldarg.1 + IL_0007: call instance void valuetype [System.Runtime]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1::SetStateMachine(class [System.Runtime]System.Runtime.CompilerServices.IAsyncStateMachine) + IL_000c: ret +} // end of method '<<
$>g__MyYield|0_2>d'::SetStateMachine +} // end of class '<<
$>g__MyYield|0_2>d' + +.method assembly hidebysig static class [System.Runtime]System.Threading.Tasks.Task`1 + '<
$>g__MyYield|0_2'() cil managed +{ + .custom instance void System.Runtime.CompilerServices.NullableContextAttribute::.ctor(uint8) = ( 01 00 01 00 00 ) + .custom instance void [System.Runtime]System.Runtime.CompilerServices.AsyncStateMachineAttribute::.ctor(class [System.Runtime]System.Type) = ( 01 00 22 50 72 6F 67 72 61 6D 2B 3C 3C 3C 4D 61 // .."Program+<<$>g__MyYield| + 30 5F 32 3E 64 00 00 ) // 0_2>d.. + .custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) + // Code size 47 (0x2f) + .maxstack 2 + .locals init (valuetype Program/'<<
$>g__MyYield|0_2>d' V_0) + IL_0000: ldloca.s V_0 + IL_0002: call valuetype [System.Runtime]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1 valuetype [System.Runtime]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1::Create() + IL_0007: stfld valuetype [System.Runtime]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1 Program/'<<
$>g__MyYield|0_2>d'::'<>t__builder' + IL_000c: ldloca.s V_0 + IL_000e: ldc.i4.m1 + IL_000f: stfld int32 Program/'<<
$>g__MyYield|0_2>d'::'<>1__state' + IL_0014: ldloca.s V_0 + IL_0016: ldflda valuetype [System.Runtime]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1 Program/'<<
$>g__MyYield|0_2>d'::'<>t__builder' + IL_001b: ldloca.s V_0 + IL_001d: call instance void valuetype [System.Runtime]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1::Start$>g__MyYield|0_2>d'>(!!0&) + IL_0022: ldloca.s V_0 + IL_0024: ldflda valuetype [System.Runtime]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1 Program/'<<
$>g__MyYield|0_2>d'::'<>t__builder' + IL_0029: call instance class [System.Runtime]System.Threading.Tasks.Task`1 valuetype [System.Runtime]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1::get_Task() + IL_002e: ret +} // end of method Program::'<
$>g__MyYield|0_2' + +} // end of class Program + + +// ============================================================= + +// *********** DISASSEMBLY COMPLETE *********************** diff --git a/src/tests/Loader/async/runtimetask-asyncfibonacci-with-yields.ilproj b/src/tests/Loader/async/runtimetask-asyncfibonacci-with-yields.ilproj new file mode 100644 index 00000000000000..bd17dd2bad5a7e --- /dev/null +++ b/src/tests/Loader/async/runtimetask-asyncfibonacci-with-yields.ilproj @@ -0,0 +1,8 @@ + + + 1 + + + + + From 34a2219a664a0789fc968af9b0f4ff14a4bccdc0 Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Tue, 26 Sep 2023 10:20:26 -0700 Subject: [PATCH 015/203] - Add async to Task stub - Public apis for directly awaiting from async2 code - Update test case to use the new apis and now the code is lovely and simple --- src/coreclr/vm/corelib.h | 8 + src/coreclr/vm/metasig.h | 2 + src/coreclr/vm/prestub.cpp | 176 ++++++++++-------- .../Strategies/FileStreamHelpers.Windows.cs | 2 +- .../ConfiguredValueTaskAwaitable.cs | 4 +- .../CompilerServices/INotifyCompletion.cs | 28 +++ .../CompilerServices/RuntimeHelpers.cs | 161 ++++++++++++++++ .../Runtime/CompilerServices/TaskAwaiter.cs | 8 +- .../CompilerServices/ValueTaskAwaiter.cs | 4 +- .../CompilerServices/YieldAwaitable.cs | 2 +- .../System.Runtime/ref/System.Runtime.cs | 42 ++++- .../runtimetask-asyncfibonacci-with-yields.il | 144 +------------- 12 files changed, 351 insertions(+), 230 deletions(-) diff --git a/src/coreclr/vm/corelib.h b/src/coreclr/vm/corelib.h index ba23da832bd834..f06a9aa6847a9d 100644 --- a/src/coreclr/vm/corelib.h +++ b/src/coreclr/vm/corelib.h @@ -351,6 +351,7 @@ DEFINE_CLASS(THREAD_START_EXCEPTION,Threading, ThreadStartException DEFINE_METHOD(THREAD_START_EXCEPTION,EX_CTOR, .ctor, IM_Exception_RetVoid) DEFINE_CLASS(TASK_1, Tasks, Task`1) +DEFINE_METHOD(TASK_1, GET_AWAITER, GetAwaiter, NoSig) DEFINE_CLASS(RUNTIME_TASK_STATE_1, Tasks, RuntimeTaskState`1) DEFINE_METHOD(RUNTIME_TASK_STATE_1, PUSH, Push, IM_RetVoid) @@ -358,6 +359,8 @@ DEFINE_METHOD(RUNTIME_TASK_STATE_1, POP, Pop, IM_RetVoid) DEFINE_METHOD(RUNTIME_TASK_STATE_1, FROM_EXCEPTION, FromException, IM_Exception_RetTaskOfT) DEFINE_METHOD(RUNTIME_TASK_STATE_1, FROM_RESULT, FromResult, IM_T_RetTaskOfT) +DEFINE_CLASS(TASK_AWAITER_1, CompilerServices, TaskAwaiter`1) + DEFINE_CLASS(TYPE_HANDLE, System, RuntimeTypeHandle) DEFINE_CLASS(RT_TYPE_HANDLE, System, RuntimeTypeHandle) DEFINE_METHOD(RT_TYPE_HANDLE, PVOID_CTOR, .ctor, IM_RuntimeType_RetVoid) @@ -675,6 +678,11 @@ DEFINE_METHOD(RUNTIME_HELPERS, ALLOC_TAILCALL_ARG_BUFFER, AllocTailCallArgB DEFINE_METHOD(RUNTIME_HELPERS, GET_TAILCALL_INFO, GetTailCallInfo, NoSig) DEFINE_METHOD(RUNTIME_HELPERS, DISPATCH_TAILCALLS, DispatchTailCalls, NoSig) +DEFINE_METHOD(RUNTIME_HELPERS, GET_OR_CREATE_RESUMPTION_DELEGATE, GetOrCreateResumptionDelegate, NoSig) +DEFINE_METHOD(RUNTIME_HELPERS, ABORT_SUSPEND, AbortSuspend, NoSig) +DEFINE_METHOD(RUNTIME_HELPERS, SUSPEND_IF_SUSPENSION_NOT_ABORTED, SuspendIfSuspensionNotAborted, NoSig) +DEFINE_METHOD(RUNTIME_HELPERS, UNSAFE_AWAIT_AWAITER_FROM_RUNTIME_ASYNC_2, UnsafeAwaitAwaiterFromRuntimeAsync, GM_U_RetT) + DEFINE_CLASS(UNSAFE, CompilerServices, Unsafe) DEFINE_METHOD(UNSAFE, AS_POINTER, AsPointer, NoSig) DEFINE_METHOD(UNSAFE, BYREF_IS_NULL, IsNullRef, NoSig) diff --git a/src/coreclr/vm/metasig.h b/src/coreclr/vm/metasig.h index dff10ce40b8d49..b0bdd9ccce04bb 100644 --- a/src/coreclr/vm/metasig.h +++ b/src/coreclr/vm/metasig.h @@ -619,6 +619,8 @@ DEFINE_METASIG(SM(PtrByte_RetStr, P(b), s)) DEFINE_METASIG(SM(Str_RetPtrByte, s, P(b))) DEFINE_METASIG(SM(PtrByte_RetVoid, P(b), v)) +DEFINE_METASIG(GM(U_RetT, IMAGE_CEE_CS_CALLCONV_DEFAULT, 1, M(1), M(0))) + // Undefine macros in case we include the file again in the compilation unit #undef DEFINE_METASIG diff --git a/src/coreclr/vm/prestub.cpp b/src/coreclr/vm/prestub.cpp index c3ae6666c1622d..759117e95203b8 100644 --- a/src/coreclr/vm/prestub.cpp +++ b/src/coreclr/vm/prestub.cpp @@ -1472,24 +1472,9 @@ bool MethodDesc::TryGenerateAsyncThunk(DynamicResolver** resolver, COR_ILMETHOD_ ULONG offsetOfAsyncDetailsUnused; AsyncTaskMethod asyncType = ClassifyAsyncMethod(GetSigPointer(), GetModule(), &offsetOfAsyncDetailsUnused); - if (asyncType != AsyncTaskMethod::TaskReturningMethod) - { - // Async to task helpers not yet implemented - _ASSERTE(FALSE); - return false; - } - MethodDesc *pAsyncOtherVariant = this->GetAsyncOtherVariant(); - MetaSig msig(pCallSig, cbCallSigSize, this->GetModule(), NULL, MetaSig::sigMember); - - TypeHandle thTaskRet = msig.GetRetTypeHandleThrowing(); - TypeHandle thLogicalRetType = thTaskRet.GetMethodTable()->GetInstantiation()[0]; - MethodTable* pMTRuntimeTaskStateOpen = CoreLibBinder::GetClass(CLASS__RUNTIME_TASK_STATE_1); - - MethodTable* pMTRuntimeTaskState = ClassLoader::LoadGenericInstantiationThrowing(pMTRuntimeTaskStateOpen->GetModule(), pMTRuntimeTaskStateOpen->GetCl(), Instantiation(&thLogicalRetType, 1)).GetMethodTable(); NewHolder ilResolver = new ILStubResolver(); - // Initialize the resolver target details. ilResolver->SetStubMethodDesc(this); ilResolver->SetStubTargetMethodDesc(pAsyncOtherVariant); @@ -1504,83 +1489,128 @@ bool MethodDesc::TryGenerateAsyncThunk(DynamicResolver** resolver, COR_ILMETHOD_ (ILStubLinkerFlags)ILSTUB_LINKER_FLAG_NONE); ILCodeStream* pCode = sl.NewCodeStream(ILStubLinker::kDispatch); - LocalDesc rtsLocalDesc(pMTRuntimeTaskState); - DWORD rtsLocal = pCode->NewLocal(rtsLocalDesc); - LocalDesc resultLocalDesc(thLogicalRetType); - DWORD resultLocal = pCode->NewLocal(resultLocalDesc); - LocalDesc exceptionLocalDesc(CoreLibBinder::GetClass(CLASS__EXCEPTION)); - DWORD exceptionLocal = pCode->NewLocal(exceptionLocalDesc); + if (asyncType == AsyncTaskMethod::TaskReturningMethod) + { + TypeHandle thTaskRet = msig.GetRetTypeHandleThrowing(); + TypeHandle thLogicalRetType = thTaskRet.GetMethodTable()->GetInstantiation()[0]; + MethodTable* pMTRuntimeTaskStateOpen = CoreLibBinder::GetClass(CLASS__RUNTIME_TASK_STATE_1); + MethodTable* pMTRuntimeTaskState = ClassLoader::LoadGenericInstantiationThrowing(pMTRuntimeTaskStateOpen->GetModule(), pMTRuntimeTaskStateOpen->GetCl(), Instantiation(&thLogicalRetType, 1)).GetMethodTable(); - LocalDesc returnLocalDesc(thTaskRet); - DWORD returnLocal = pCode->NewLocal(returnLocalDesc); + LocalDesc rtsLocalDesc(pMTRuntimeTaskState); + DWORD rtsLocal = pCode->NewLocal(rtsLocalDesc); - auto pNoExceptionLabel = pCode->NewCodeLabel(); - auto pReturnResultLabel = pCode->NewCodeLabel(); + LocalDesc resultLocalDesc(thLogicalRetType); + DWORD resultLocal = pCode->NewLocal(resultLocalDesc); - pCode->EmitLDLOCA(rtsLocal); - pCode->EmitINITOBJ(pCode->GetToken(pMTRuntimeTaskState)); - pCode->EmitLDLOCA(resultLocal); - pCode->EmitINITOBJ(pCode->GetToken(thLogicalRetType)); - pCode->EmitLDLOCA(exceptionLocal); - pCode->EmitINITOBJ(pCode->GetToken(CoreLibBinder::GetClass(CLASS__EXCEPTION))); - pCode->EmitLDLOCA(returnLocal); - pCode->EmitINITOBJ(pCode->GetToken(thTaskRet)); - pCode->EmitLDLOCA(rtsLocal); - pCode->EmitCALL(pCode->GetToken(pMTRuntimeTaskState->GetParallelMethodDesc(CoreLibBinder::GetMethod(METHOD__RUNTIME_TASK_STATE_1__PUSH))), 1, 0); - { - pCode->BeginTryBlock(); - pCode->EmitNOP("Separate try blocks"); + LocalDesc exceptionLocalDesc(CoreLibBinder::GetClass(CLASS__EXCEPTION)); + DWORD exceptionLocal = pCode->NewLocal(exceptionLocalDesc); + + LocalDesc returnLocalDesc(thTaskRet); + DWORD returnLocal = pCode->NewLocal(returnLocalDesc); + + auto pNoExceptionLabel = pCode->NewCodeLabel(); + auto pReturnResultLabel = pCode->NewCodeLabel(); + + pCode->EmitLDLOCA(rtsLocal); + pCode->EmitINITOBJ(pCode->GetToken(pMTRuntimeTaskState)); + pCode->EmitLDLOCA(resultLocal); + pCode->EmitINITOBJ(pCode->GetToken(thLogicalRetType)); + pCode->EmitLDLOCA(exceptionLocal); + pCode->EmitINITOBJ(pCode->GetToken(CoreLibBinder::GetClass(CLASS__EXCEPTION))); + pCode->EmitLDLOCA(returnLocal); + pCode->EmitINITOBJ(pCode->GetToken(thTaskRet)); + pCode->EmitLDLOCA(rtsLocal); + pCode->EmitCALL(pCode->GetToken(pMTRuntimeTaskState->GetParallelMethodDesc(CoreLibBinder::GetMethod(METHOD__RUNTIME_TASK_STATE_1__PUSH))), 1, 0); { pCode->BeginTryBlock(); - - DWORD localArg = 0; - if (msig.HasThis()) + pCode->EmitNOP("Separate try blocks"); { - // Struct async thunks not yet implemented - _ASSERTE(!this->GetMethodTable()->IsValueType()); - pCode->EmitLDARG(localArg++); + pCode->BeginTryBlock(); + + DWORD localArg = 0; + if (msig.HasThis()) + { + // Struct async thunks not yet implemented + _ASSERTE(!this->GetMethodTable()->IsValueType()); + pCode->EmitLDARG(localArg++); + } + for (UINT iArg = 0; iArg < msig.NumFixedArgs(); iArg++) + { + pCode->EmitLDARG(localArg++); + } + pCode->EmitCALL(pCode->GetToken(pAsyncOtherVariant), localArg, 1); + pCode->EmitSTLOC(resultLocal); + pCode->EmitLEAVE(pNoExceptionLabel); + pCode->EndTryBlock(); } - for (UINT iArg = 0; iArg < msig.NumFixedArgs(); iArg++) + // Catch { - pCode->EmitLDARG(localArg++); + pCode->BeginCatchBlock(pCode->GetToken(CoreLibBinder::GetClass(CLASS__EXCEPTION))); + pCode->EmitSTLOC(exceptionLocal); + pCode->EmitLDLOCA(rtsLocal); + pCode->EmitLDLOC(exceptionLocal); + pCode->EmitCALL(pCode->GetToken(pMTRuntimeTaskState->GetParallelMethodDesc(CoreLibBinder::GetMethod(METHOD__RUNTIME_TASK_STATE_1__FROM_EXCEPTION))), 2, 1); + pCode->EmitSTLOC(returnLocal); + pCode->EmitLEAVE(pReturnResultLabel); + pCode->EndCatchBlock(); } - pCode->EmitCALL(pCode->GetToken(pAsyncOtherVariant), localArg, 1); - pCode->EmitSTLOC(resultLocal); - pCode->EmitLEAVE(pNoExceptionLabel); + pCode->EmitLabel(pNoExceptionLabel); + pCode->EmitLDLOCA(rtsLocal); + pCode->EmitLDLOC(resultLocal); + pCode->EmitCALL(pCode->GetToken(pMTRuntimeTaskState->GetParallelMethodDesc(CoreLibBinder::GetMethod(METHOD__RUNTIME_TASK_STATE_1__FROM_RESULT))), 2, 1); + pCode->EmitSTLOC(returnLocal); + pCode->EmitLEAVE(pReturnResultLabel); pCode->EndTryBlock(); } - // Catch + // Finally { - pCode->BeginCatchBlock(pCode->GetToken(CoreLibBinder::GetClass(CLASS__EXCEPTION))); - pCode->EmitSTLOC(exceptionLocal); + pCode->BeginFinallyBlock(); pCode->EmitLDLOCA(rtsLocal); - pCode->EmitLDLOC(exceptionLocal); - pCode->EmitCALL(pCode->GetToken(pMTRuntimeTaskState->GetParallelMethodDesc(CoreLibBinder::GetMethod(METHOD__RUNTIME_TASK_STATE_1__FROM_EXCEPTION))), 2, 1); - pCode->EmitSTLOC(returnLocal); - pCode->EmitLEAVE(pReturnResultLabel); - pCode->EndCatchBlock(); + pCode->EmitCALL(pCode->GetToken(pMTRuntimeTaskState->GetParallelMethodDesc(CoreLibBinder::GetMethod(METHOD__RUNTIME_TASK_STATE_1__POP))), 1, 0); + pCode->EmitENDFINALLY(); + pCode->EndFinallyBlock(); } - pCode->EmitLabel(pNoExceptionLabel); - pCode->EmitLDLOCA(rtsLocal); - pCode->EmitLDLOC(resultLocal); - pCode->EmitCALL(pCode->GetToken(pMTRuntimeTaskState->GetParallelMethodDesc(CoreLibBinder::GetMethod(METHOD__RUNTIME_TASK_STATE_1__FROM_RESULT))), 2, 1); - pCode->EmitSTLOC(returnLocal); - pCode->EmitLEAVE(pReturnResultLabel); - pCode->EndTryBlock(); + pCode->EmitLabel(pReturnResultLabel); + pCode->EmitLDLOC(returnLocal); + pCode->EmitRET(); } - // Finally + else { - pCode->BeginFinallyBlock(); - pCode->EmitLDLOCA(rtsLocal); - pCode->EmitCALL(pCode->GetToken(pMTRuntimeTaskState->GetParallelMethodDesc(CoreLibBinder::GetMethod(METHOD__RUNTIME_TASK_STATE_1__POP))), 1, 0); - pCode->EmitENDFINALLY(); - pCode->EndFinallyBlock(); + _ASSERTE(asyncType == AsyncTaskMethod::Async2Method); +// Implement IL that is effectively the following +/* +{ + // Call target method and await + return RuntimeHelpers.UnsafeAwaitAwaiterFromRuntimeAsync(Thunk(arg).GetAwaiter()); +} +*/ + TypeHandle thLogicalRetType = msig.GetRetTypeHandleThrowing(); + MethodTable* pMTTaskOpen = CoreLibBinder::GetClass(CLASS__TASK_1); + MethodTable* pMTTask = ClassLoader::LoadGenericInstantiationThrowing(pMTTaskOpen->GetModule(), pMTTaskOpen->GetCl(), Instantiation(&thLogicalRetType, 1)).GetMethodTable(); + MethodTable* pMTTaskAwaiterOpen = CoreLibBinder::GetClass(CLASS__TASK_AWAITER_1); + TypeHandle thTaskAwaiter = ClassLoader::LoadGenericInstantiationThrowing(pMTTaskAwaiterOpen->GetModule(), pMTTaskAwaiterOpen->GetCl(), Instantiation(&thLogicalRetType, 1)); + DWORD localArg = 0; + if (msig.HasThis()) + { + pCode->EmitLDARG(localArg++); + } + for (UINT iArg = 0; iArg < msig.NumFixedArgs(); iArg++) + { + pCode->EmitLDARG(localArg++); + } + pCode->EmitCALL(pCode->GetToken(pAsyncOtherVariant), localArg, 1); + pCode->EmitCALLVIRT(pCode->GetToken(pMTTask->GetParallelMethodDesc(CoreLibBinder::GetMethod(METHOD__TASK_1__GET_AWAITER))), 1, 1); + MethodDesc *pMagicAwaitFunc = CoreLibBinder::GetMethod(METHOD__RUNTIME_HELPERS__UNSAFE_AWAIT_AWAITER_FROM_RUNTIME_ASYNC_2); + TypeHandle thInstantiations[] {thLogicalRetType, thTaskAwaiter}; + + pMagicAwaitFunc = FindOrCreateAssociatedMethodDesc(pMagicAwaitFunc, pMagicAwaitFunc->GetMethodTable(), FALSE, Instantiation(thInstantiations, 2), FALSE); + + pCode->EmitCALL(pCode->GetToken(pMagicAwaitFunc), 1, 1); + + pCode->EmitRET(); } - pCode->EmitLabel(pReturnResultLabel); - pCode->EmitLDLOC(returnLocal); - pCode->EmitRET(); // Generate all IL associated data for JIT { diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.Windows.cs index ce1dedaa8889d8..9abfdf66571bf3 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.Windows.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.Windows.cs @@ -310,7 +310,7 @@ internal static async Task AsyncModeCopyToAsync(SafeFileHandle handle, bool canS } /// Used by AsyncWindowsFileStreamStrategy.CopyToAsync to enable awaiting the result of an overlapped I/O operation with minimal overhead. - private sealed unsafe class AsyncCopyToAwaitable : ICriticalNotifyCompletion + private sealed unsafe class AsyncCopyToAwaitable : ICriticalNotifyCompletion2 { /// Sentinel object used to indicate that the I/O operation has completed before being awaited. private static readonly Action s_sentinel = () => { }; diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/ConfiguredValueTaskAwaitable.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/ConfiguredValueTaskAwaitable.cs index 1700fb0cca8aa6..9a1e250a7b5a87 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/ConfiguredValueTaskAwaitable.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/ConfiguredValueTaskAwaitable.cs @@ -27,7 +27,7 @@ public readonly struct ConfiguredValueTaskAwaitable /// Provides an awaiter for a . [StructLayout(LayoutKind.Auto)] - public readonly struct ConfiguredValueTaskAwaiter : ICriticalNotifyCompletion, IStateMachineBoxAwareAwaiter + public readonly struct ConfiguredValueTaskAwaiter : ICriticalNotifyCompletion2, IStateMachineBoxAwareAwaiter { /// The value being awaited. private readonly ValueTask _value; @@ -132,7 +132,7 @@ public readonly struct ConfiguredValueTaskAwaitable /// Provides an awaiter for a . [StructLayout(LayoutKind.Auto)] - public readonly struct ConfiguredValueTaskAwaiter : ICriticalNotifyCompletion, IStateMachineBoxAwareAwaiter + public readonly struct ConfiguredValueTaskAwaiter : ICriticalNotifyCompletion2, IStateMachineBoxAwareAwaiter { /// The value being awaited. private readonly ValueTask _value; diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/INotifyCompletion.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/INotifyCompletion.cs index ff00892da828f8..d97d93ed869db3 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/INotifyCompletion.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/INotifyCompletion.cs @@ -32,4 +32,32 @@ public interface ICriticalNotifyCompletion : INotifyCompletion /// Unlike OnCompleted, UnsafeOnCompleted need not propagate ExecutionContext information. void UnsafeOnCompleted(Action continuation); } + + /// + /// Represents an awaiter used to schedule continuations when an await operation completes. Provides access to the complete api surface for awaiting + /// + public interface ICriticalNotifyCompletion2 : ICriticalNotifyCompletion + { + bool IsCompleted { get; } + void GetResult(); + } + public interface ICriticalNotifyCompletion2 : ICriticalNotifyCompletion + { + bool IsCompleted { get; } + TResult GetResult(); + } + + /// + /// Represents an awaiter used to schedule continuations when an await operation completes. Provides access to the complete api surface for awaiting + /// + public interface INotifyCompletion2 : INotifyCompletion + { + bool IsCompleted { get; } + void GetResult(); + } + public interface INotifyCompletion2 : INotifyCompletion + { + bool IsCompleted { get; } + TResult GetResult(); + } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs index 47014ec5d718f5..0665687bace867 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs @@ -115,5 +115,166 @@ internal static bool IsPrimitiveType(this CorElementType et) [Intrinsic] internal static bool IsKnownConstant(int t) => false; #pragma warning restore IDE0060 + + // TODO, this method should be marked so that it is only callable from a runtime async method + public static TResult UnsafeAwaitAwaiterFromRuntimeAsync(TAwaiter awaiter) where TAwaiter : ICriticalNotifyCompletion2 + { + if (!awaiter.IsCompleted) + { + // Create resumption delegate, wrapping task, and create tasklets to represent each stack frame on the stack. + // RuntimeTaskSuspender.GetOrCreateResumptionDelegate() works like a POSIX fork call in that calls to it will return a + // delegate if they are the initial call to GetOrCreateResumptionDelegate, but once the thread is resumed, + // it will resume with a return value of null. + Action? resumption = RuntimeHelpers.GetOrCreateResumptionDelegate(); + if (resumption != null) + { + // We are trying to suspend + bool threwException = true; + try + { + // Call the UnsafeOnCompleted api under a try block, as registering the suspension may cause + // an exception to occur. + awaiter.UnsafeOnCompleted(resumption); + threwException = false; + } + finally + { + // If UnsafeOnCompleted itself threw, we should bubble the error up, but we need + // to destroy any allocated tasklets that were created as part of the GetOrCreateResumptionDelegate api + // as that state will never be useable. + if (threwException) + RuntimeHelpers.AbortSuspend(); + } + // If we reach here, the only way that we actually run follow on code is for the continuation to actually run, + // and return from GetOrCreateResumptionDelegate with a null return value. + RuntimeHelpers.SuspendIfSuspensionNotAborted(); + } + } + + // Get the result from the awaiter, or throw the exception stored in the Task + return awaiter.GetResult(); + } + + // TODO, this method should be marked so that it is only callable from a runtime async method + public static TResult AwaitAwaiterFromRuntimeAsync(TAwaiter awaiter) where TAwaiter : INotifyCompletion2 + { + if (!awaiter.IsCompleted) + { + // Create resumption delegate, wrapping task, and create tasklets to represent each stack frame on the stack. + // RuntimeTaskSuspender.GetOrCreateResumptionDelegate() works like a POSIX fork call in that calls to it will return a + // delegate if they are the initial call to GetOrCreateResumptionDelegate, but once the thread is resumed, + // it will resume with a return value of null. + Action? resumption = RuntimeHelpers.GetOrCreateResumptionDelegate(); + if (resumption != null) + { + // We are trying to suspend + bool threwException = true; + try + { + // Call the OnCompleted api under a try block, as registering the suspension may cause + // an exception to occur. + awaiter.OnCompleted(resumption); + threwException = false; + } + finally + { + // If OnCompleted itself threw, we should bubble the error up, but we need + // to destroy any allocated tasklets that were created as part of the GetOrCreateResumptionDelegate api + // as that state will never be useable. + if (threwException) + RuntimeHelpers.AbortSuspend(); + } + // If we reach here, the only way that we actually run follow on code is for the continuation to actually run, + // and return from GetOrCreateResumptionDelegate with a null return value. + RuntimeHelpers.SuspendIfSuspensionNotAborted(); + } + } + + // Get the result from the awaiter, or throw the exception stored in the Task + return awaiter.GetResult(); + + } + + // TODO, this method should be marked so that it is only callable from a runtime async method + public static void UnsafeAwaitAwaiterFromRuntimeAsync(TAwaiter awaiter) where TAwaiter : ICriticalNotifyCompletion2 + { + if (!awaiter.IsCompleted) + { + // Create resumption delegate, wrapping task, and create tasklets to represent each stack frame on the stack. + // RuntimeTaskSuspender.GetOrCreateResumptionDelegate() works like a POSIX fork call in that calls to it will return a + // delegate if they are the initial call to GetOrCreateResumptionDelegate, but once the thread is resumed, + // it will resume with a return value of null. + Action? resumption = RuntimeHelpers.GetOrCreateResumptionDelegate(); + if (resumption != null) + { + // We are trying to suspend + bool threwException = true; + try + { + // Call the UnsafeOnCompleted api under a try block, as registering the suspension may cause + // an exception to occur. + awaiter.UnsafeOnCompleted(resumption); + threwException = false; + } + finally + { + // If UnsafeOnCompleted itself threw, we should bubble the error up, but we need + // to destroy any allocated tasklets that were created as part of the GetOrCreateResumptionDelegate api + // as that state will never be useable. + if (threwException) + RuntimeHelpers.AbortSuspend(); + } + // If we reach here, the only way that we actually run follow on code is for the continuation to actually run, + // and return from GetOrCreateResumptionDelegate with a null return value. + RuntimeHelpers.SuspendIfSuspensionNotAborted(); + } + } + + // Get the result from the awaiter, or throw the exception stored in the Task + awaiter.GetResult(); + } + + // TODO, this method should be marked so that it is only callable from a runtime async method + public static void AwaitAwaiterFromRuntimeAsync(TAwaiter awaiter) where TAwaiter : INotifyCompletion2 + { + if (!awaiter.IsCompleted) + { + // Create resumption delegate, wrapping task, and create tasklets to represent each stack frame on the stack. + // RuntimeTaskSuspender.GetOrCreateResumptionDelegate() works like a POSIX fork call in that calls to it will return a + // delegate if they are the initial call to GetOrCreateResumptionDelegate, but once the thread is resumed, + // it will resume with a return value of null. + Action? resumption = RuntimeHelpers.GetOrCreateResumptionDelegate(); + if (resumption != null) + { + // We are trying to suspend + bool threwException = true; + try + { + // Call the OnCompleted api under a try block, as registering the suspension may cause + // an exception to occur. + awaiter.OnCompleted(resumption); + threwException = false; + } + finally + { + // If OnCompleted itself threw, we should bubble the error up, but we need + // to destroy any allocated tasklets that were created as part of the GetOrCreateResumptionDelegate api + // as that state will never be useable. + if (threwException) + RuntimeHelpers.AbortSuspend(); + } + // If we reach here, the only way that we actually run follow on code is for the continuation to actually run, + // and return from GetOrCreateResumptionDelegate with a null return value. + RuntimeHelpers.SuspendIfSuspensionNotAborted(); + } + } + + // Get the result from the awaiter, or throw the exception stored in the Task + awaiter.GetResult(); + } + + private static void SuspendIfSuspensionNotAborted() {} + private static Action? GetOrCreateResumptionDelegate() { return null; } + private static void AbortSuspend() {} } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/TaskAwaiter.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/TaskAwaiter.cs index 0e3f0ec71b6b90..8a5b77ee05c9bc 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/TaskAwaiter.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/TaskAwaiter.cs @@ -17,7 +17,7 @@ namespace System.Runtime.CompilerServices { /// Provides an awaiter for awaiting a . - public readonly struct TaskAwaiter : ICriticalNotifyCompletion, ITaskAwaiter + public readonly struct TaskAwaiter : ICriticalNotifyCompletion2, ITaskAwaiter { // WARNING: Unsafe.As is used to access the generic TaskAwaiter<> as TaskAwaiter. // Its layout must remain the same. @@ -283,7 +283,7 @@ private static Action OutputWaitEtwEvents(Task task, Action continuation) } /// Provides an awaiter for awaiting a . - public readonly struct TaskAwaiter : ICriticalNotifyCompletion, ITaskAwaiter + public readonly struct TaskAwaiter : ICriticalNotifyCompletion2, ITaskAwaiter { // WARNING: Unsafe.As is used to access TaskAwaiter<> as the non-generic TaskAwaiter. // Its layout must remain the same. @@ -376,7 +376,7 @@ public ConfiguredTaskAwaiter GetAwaiter() /// Provides an awaiter for a . /// This type is intended for compiler use only. - public readonly struct ConfiguredTaskAwaiter : ICriticalNotifyCompletion, IConfiguredTaskAwaiter + public readonly struct ConfiguredTaskAwaiter : ICriticalNotifyCompletion2, IConfiguredTaskAwaiter { // WARNING: Unsafe.As is used to access the generic ConfiguredTaskAwaiter as this. // Its layout must remain the same. @@ -458,7 +458,7 @@ public ConfiguredTaskAwaiter GetAwaiter() /// Provides an awaiter for a . /// This type is intended for compiler use only. - public readonly struct ConfiguredTaskAwaiter : ICriticalNotifyCompletion, IConfiguredTaskAwaiter + public readonly struct ConfiguredTaskAwaiter : ICriticalNotifyCompletion2, IConfiguredTaskAwaiter { // WARNING: Unsafe.As is used to access this as the non-generic ConfiguredTaskAwaiter. // Its layout must remain the same. diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/ValueTaskAwaiter.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/ValueTaskAwaiter.cs index c242ad3f2ad203..ba5aa6f10872a7 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/ValueTaskAwaiter.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/ValueTaskAwaiter.cs @@ -9,7 +9,7 @@ namespace System.Runtime.CompilerServices { /// Provides an awaiter for a . - public readonly struct ValueTaskAwaiter : ICriticalNotifyCompletion, IStateMachineBoxAwareAwaiter + public readonly struct ValueTaskAwaiter : ICriticalNotifyCompletion2, IStateMachineBoxAwareAwaiter { /// Shim used to invoke an passed as the state argument to a . internal static readonly Action s_invokeActionDelegate = static state => @@ -104,7 +104,7 @@ void IStateMachineBoxAwareAwaiter.AwaitUnsafeOnCompleted(IAsyncStateMachineBox b } /// Provides an awaiter for a . - public readonly struct ValueTaskAwaiter : ICriticalNotifyCompletion, IStateMachineBoxAwareAwaiter + public readonly struct ValueTaskAwaiter : ICriticalNotifyCompletion2, IStateMachineBoxAwareAwaiter { /// The value being awaited. private readonly ValueTask _value; diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/YieldAwaitable.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/YieldAwaitable.cs index d9bea6410babbb..5ddd4fd00f3628 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/YieldAwaitable.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/YieldAwaitable.cs @@ -43,7 +43,7 @@ public readonly struct YieldAwaitable /// Provides an awaiter that switches into a target environment. /// This type is intended for compiler use only. - public readonly struct YieldAwaiter : ICriticalNotifyCompletion, IStateMachineBoxAwareAwaiter + public readonly struct YieldAwaiter : ICriticalNotifyCompletion2, IStateMachineBoxAwareAwaiter { /// Gets whether a yield is not required. /// This property is intended for compiler user rather than use directly in code. diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index 5dd254b9701232..b7e9ba512a1ec1 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -12699,7 +12699,7 @@ public readonly partial struct ConfiguredTaskAwaitable private readonly object _dummy; private readonly int _dummyPrimitive; public System.Runtime.CompilerServices.ConfiguredTaskAwaitable.ConfiguredTaskAwaiter GetAwaiter() { throw null; } - public readonly partial struct ConfiguredTaskAwaiter : System.Runtime.CompilerServices.ICriticalNotifyCompletion, System.Runtime.CompilerServices.INotifyCompletion + public readonly partial struct ConfiguredTaskAwaiter : System.Runtime.CompilerServices.ICriticalNotifyCompletion2, System.Runtime.CompilerServices.INotifyCompletion { private readonly object _dummy; private readonly int _dummyPrimitive; @@ -12714,7 +12714,7 @@ public readonly partial struct ConfiguredTaskAwaitable private readonly object _dummy; private readonly int _dummyPrimitive; public System.Runtime.CompilerServices.ConfiguredTaskAwaitable.ConfiguredTaskAwaiter GetAwaiter() { throw null; } - public readonly partial struct ConfiguredTaskAwaiter : System.Runtime.CompilerServices.ICriticalNotifyCompletion, System.Runtime.CompilerServices.INotifyCompletion + public readonly partial struct ConfiguredTaskAwaiter : System.Runtime.CompilerServices.ICriticalNotifyCompletion2, System.Runtime.CompilerServices.INotifyCompletion { private readonly object _dummy; private readonly int _dummyPrimitive; @@ -12729,7 +12729,7 @@ public readonly partial struct ConfiguredValueTaskAwaitable private readonly object _dummy; private readonly int _dummyPrimitive; public System.Runtime.CompilerServices.ConfiguredValueTaskAwaitable.ConfiguredValueTaskAwaiter GetAwaiter() { throw null; } - public readonly partial struct ConfiguredValueTaskAwaiter : System.Runtime.CompilerServices.ICriticalNotifyCompletion, System.Runtime.CompilerServices.INotifyCompletion + public readonly partial struct ConfiguredValueTaskAwaiter : System.Runtime.CompilerServices.ICriticalNotifyCompletion2, System.Runtime.CompilerServices.INotifyCompletion { private readonly object _dummy; private readonly int _dummyPrimitive; @@ -12744,7 +12744,7 @@ public readonly partial struct ConfiguredValueTaskAwaitable private readonly object _dummy; private readonly int _dummyPrimitive; public System.Runtime.CompilerServices.ConfiguredValueTaskAwaitable.ConfiguredValueTaskAwaiter GetAwaiter() { throw null; } - public readonly partial struct ConfiguredValueTaskAwaiter : System.Runtime.CompilerServices.ICriticalNotifyCompletion, System.Runtime.CompilerServices.INotifyCompletion + public readonly partial struct ConfiguredValueTaskAwaiter : System.Runtime.CompilerServices.ICriticalNotifyCompletion2, System.Runtime.CompilerServices.INotifyCompletion { private readonly object _dummy; private readonly int _dummyPrimitive; @@ -12859,6 +12859,16 @@ public partial interface ICriticalNotifyCompletion : System.Runtime.CompilerServ { void UnsafeOnCompleted(System.Action continuation); } + public partial interface ICriticalNotifyCompletion2 : ICriticalNotifyCompletion + { + bool IsCompleted { get; } + void GetResult(); + } + public partial interface ICriticalNotifyCompletion2 : ICriticalNotifyCompletion + { + bool IsCompleted { get; } + TResult GetResult(); + } [System.AttributeUsageAttribute(System.AttributeTargets.Property, Inherited=true)] public sealed partial class IndexerNameAttribute : System.Attribute { @@ -12868,6 +12878,16 @@ public partial interface INotifyCompletion { void OnCompleted(System.Action continuation); } + public partial interface INotifyCompletion2 : INotifyCompletion + { + bool IsCompleted { get; } + void GetResult(); + } + public partial interface INotifyCompletion2 : INotifyCompletion + { + bool IsCompleted { get; } + TResult GetResult(); + } [System.AttributeUsageAttribute(System.AttributeTargets.Assembly, AllowMultiple=true, Inherited=false)] public sealed partial class InternalsVisibleToAttribute : System.Attribute { @@ -13109,6 +13129,10 @@ public static void RunModuleConstructor(System.ModuleHandle module) { } public static bool TryEnsureSufficientExecutionStack() { throw null; } public delegate void CleanupCode(object? userData, bool exceptionThrown); public delegate void TryCode(object? userData); + public static TResult UnsafeAwaitAwaiterFromRuntimeAsync(TAwaiter awaiter) where TAwaiter : ICriticalNotifyCompletion2 { throw null; } + public static void UnsafeAwaitAwaiterFromRuntimeAsync(TAwaiter awaiter) where TAwaiter : ICriticalNotifyCompletion2 { } + public static TResult AwaitAwaiterFromRuntimeAsync(TAwaiter awaiter) where TAwaiter : INotifyCompletion2 { throw null; } + public static void AwaitAwaiterFromRuntimeAsync(TAwaiter awaiter) where TAwaiter : INotifyCompletion2 { } } public sealed partial class RuntimeWrappedException : System.Exception { @@ -13171,7 +13195,7 @@ public SwitchExpressionException(string? message, System.Exception? innerExcepti [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] public override void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } } - public readonly partial struct TaskAwaiter : System.Runtime.CompilerServices.ICriticalNotifyCompletion, System.Runtime.CompilerServices.INotifyCompletion + public readonly partial struct TaskAwaiter : System.Runtime.CompilerServices.ICriticalNotifyCompletion2, System.Runtime.CompilerServices.INotifyCompletion { private readonly object _dummy; private readonly int _dummyPrimitive; @@ -13180,7 +13204,7 @@ public void GetResult() { } public void OnCompleted(System.Action continuation) { } public void UnsafeOnCompleted(System.Action continuation) { } } - public readonly partial struct TaskAwaiter : System.Runtime.CompilerServices.ICriticalNotifyCompletion, System.Runtime.CompilerServices.INotifyCompletion + public readonly partial struct TaskAwaiter : System.Runtime.CompilerServices.ICriticalNotifyCompletion2, System.Runtime.CompilerServices.INotifyCompletion { private readonly object _dummy; private readonly int _dummyPrimitive; @@ -13297,7 +13321,7 @@ public sealed partial class UnsafeValueTypeAttribute : System.Attribute { public UnsafeValueTypeAttribute() { } } - public readonly partial struct ValueTaskAwaiter : System.Runtime.CompilerServices.ICriticalNotifyCompletion, System.Runtime.CompilerServices.INotifyCompletion + public readonly partial struct ValueTaskAwaiter : System.Runtime.CompilerServices.ICriticalNotifyCompletion2, System.Runtime.CompilerServices.INotifyCompletion { private readonly object _dummy; private readonly int _dummyPrimitive; @@ -13306,7 +13330,7 @@ public void GetResult() { } public void OnCompleted(System.Action continuation) { } public void UnsafeOnCompleted(System.Action continuation) { } } - public readonly partial struct ValueTaskAwaiter : System.Runtime.CompilerServices.ICriticalNotifyCompletion, System.Runtime.CompilerServices.INotifyCompletion + public readonly partial struct ValueTaskAwaiter : System.Runtime.CompilerServices.ICriticalNotifyCompletion2, System.Runtime.CompilerServices.INotifyCompletion { private readonly object _dummy; private readonly int _dummyPrimitive; @@ -13318,7 +13342,7 @@ public void UnsafeOnCompleted(System.Action continuation) { } public readonly partial struct YieldAwaitable { public System.Runtime.CompilerServices.YieldAwaitable.YieldAwaiter GetAwaiter() { throw null; } - public readonly partial struct YieldAwaiter : System.Runtime.CompilerServices.ICriticalNotifyCompletion, System.Runtime.CompilerServices.INotifyCompletion + public readonly partial struct YieldAwaiter : System.Runtime.CompilerServices.ICriticalNotifyCompletion2, System.Runtime.CompilerServices.INotifyCompletion { public bool IsCompleted { get { throw null; } } public void GetResult() { } diff --git a/src/tests/Loader/async/runtimetask-asyncfibonacci-with-yields.il b/src/tests/Loader/async/runtimetask-asyncfibonacci-with-yields.il index 2d8008483407e1..bfe9aece7c9a77 100644 --- a/src/tests/Loader/async/runtimetask-asyncfibonacci-with-yields.il +++ b/src/tests/Loader/async/runtimetask-asyncfibonacci-with-yields.il @@ -362,8 +362,8 @@ IL_0013: stloc.1 IL_0014: ldloc.1 IL_0015: brfalse.s IL_001d - IL_0017: call int modopt([System.Runtime]System.Threading.Tasks.Task`1) Program::'<
$>g__MyYield|0_2'() - IL_001c: pop + IL_0017: call valuetype [System.Runtime]System.Runtime.CompilerServices.YieldAwaitable [System.Runtime]System.Threading.Tasks.Task::Yield() + IL_001c: call void [System.Runtime]System.Runtime.CompilerServices.RuntimeHelpers::UnsafeAwaitAwaiterFromRuntimeAsync(!!0) IL_001d: ldloc.0 IL_001e: ldc.i4 0x773593ed IL_0023: mul @@ -374,8 +374,8 @@ IL_002d: stloc.2 IL_002e: ldloc.2 IL_002f: brfalse.s IL_0037 - IL_0031: call int modopt([System.Runtime]System.Threading.Tasks.Task`1) Program::'<
$>g__MyYield|0_2'() - IL_0036: pop + IL_0031: call valuetype [System.Runtime]System.Runtime.CompilerServices.YieldAwaitable [System.Runtime]System.Threading.Tasks.Task::Yield() + IL_0036: call void [System.Runtime]System.Runtime.CompilerServices.RuntimeHelpers::UnsafeAwaitAwaiterFromRuntimeAsync(!!0) IL_0037: ldloc.0 IL_0038: ldc.i4 0x773593ed IL_003d: mul @@ -386,8 +386,8 @@ IL_0047: stloc.3 IL_0048: ldloc.3 IL_0049: brfalse.s IL_0051 - IL_004b: call int modopt([System.Runtime]System.Threading.Tasks.Task`1) Program::'<
$>g__MyYield|0_2'() - IL_0050: pop + IL_004b: call valuetype [System.Runtime]System.Runtime.CompilerServices.YieldAwaitable [System.Runtime]System.Threading.Tasks.Task::Yield() + IL_0050: call void [System.Runtime]System.Runtime.CompilerServices.RuntimeHelpers::UnsafeAwaitAwaiterFromRuntimeAsync(!!0) IL_0051: ldloc.0 IL_0052: stloc.s V_4 IL_0054: br.s IL_0056 @@ -395,138 +395,6 @@ IL_0058: ret } // end of method Program::'<
$>g__B|0_1' -.class auto ansi sealed nested private beforefieldinit '<<
$>g__MyYield|0_2>d' - extends [System.Runtime]System.ValueType - implements [System.Runtime]System.Runtime.CompilerServices.IAsyncStateMachine -{ - .custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) - .field public int32 '<>1__state' - .field public valuetype [System.Runtime]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1 '<>t__builder' - .field private valuetype [System.Runtime]System.Runtime.CompilerServices.YieldAwaitable/YieldAwaiter '<>u__1' - -.method private hidebysig newslot virtual final - instance void MoveNext() cil managed -{ - .override [System.Runtime]System.Runtime.CompilerServices.IAsyncStateMachine::MoveNext - // Code size 150 (0x96) - .maxstack 3 - .locals init (int32 V_0, - int32 V_1, - valuetype [System.Runtime]System.Runtime.CompilerServices.YieldAwaitable/YieldAwaiter V_2, - valuetype [System.Runtime]System.Runtime.CompilerServices.YieldAwaitable V_3, - class [System.Runtime]System.Exception V_4) - IL_0000: ldarg.0 - IL_0001: ldfld int32 Program/'<<
$>g__MyYield|0_2>d'::'<>1__state' - IL_0006: stloc.0 - .try - { - IL_0007: ldloc.0 - IL_0008: brfalse.s IL_0041 - IL_000a: call valuetype [System.Runtime]System.Runtime.CompilerServices.YieldAwaitable [System.Runtime]System.Threading.Tasks.Task::Yield() - IL_000f: stloc.3 - IL_0010: ldloca.s V_3 - IL_0012: call instance valuetype [System.Runtime]System.Runtime.CompilerServices.YieldAwaitable/YieldAwaiter [System.Runtime]System.Runtime.CompilerServices.YieldAwaitable::GetAwaiter() - IL_0017: stloc.2 - IL_0018: ldloca.s V_2 - IL_001a: call instance bool [System.Runtime]System.Runtime.CompilerServices.YieldAwaitable/YieldAwaiter::get_IsCompleted() - IL_001f: brtrue.s IL_005d - IL_0021: ldarg.0 - IL_0022: ldc.i4.0 - IL_0023: dup - IL_0024: stloc.0 - IL_0025: stfld int32 Program/'<<
$>g__MyYield|0_2>d'::'<>1__state' - IL_002a: ldarg.0 - IL_002b: ldloc.2 - IL_002c: stfld valuetype [System.Runtime]System.Runtime.CompilerServices.YieldAwaitable/YieldAwaiter Program/'<<
$>g__MyYield|0_2>d'::'<>u__1' - IL_0031: ldarg.0 - IL_0032: ldflda valuetype [System.Runtime]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1 Program/'<<
$>g__MyYield|0_2>d'::'<>t__builder' - IL_0037: ldloca.s V_2 - IL_0039: ldarg.0 - IL_003a: call instance void valuetype [System.Runtime]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1::AwaitUnsafeOnCompleted$>g__MyYield|0_2>d'>(!!0&, - !!1&) - IL_003f: leave.s IL_0095 - IL_0041: ldarg.0 - IL_0042: ldfld valuetype [System.Runtime]System.Runtime.CompilerServices.YieldAwaitable/YieldAwaiter Program/'<<
$>g__MyYield|0_2>d'::'<>u__1' - IL_0047: stloc.2 - IL_0048: ldarg.0 - IL_0049: ldflda valuetype [System.Runtime]System.Runtime.CompilerServices.YieldAwaitable/YieldAwaiter Program/'<<
$>g__MyYield|0_2>d'::'<>u__1' - IL_004e: initobj [System.Runtime]System.Runtime.CompilerServices.YieldAwaitable/YieldAwaiter - IL_0054: ldarg.0 - IL_0055: ldc.i4.m1 - IL_0056: dup - IL_0057: stloc.0 - IL_0058: stfld int32 Program/'<<
$>g__MyYield|0_2>d'::'<>1__state' - IL_005d: ldloca.s V_2 - IL_005f: call instance void [System.Runtime]System.Runtime.CompilerServices.YieldAwaitable/YieldAwaiter::GetResult() - IL_0064: ldc.i4.0 - IL_0065: stloc.1 - IL_0066: leave.s IL_0081 - } // end .try - catch [System.Runtime]System.Exception - { - IL_0068: stloc.s V_4 - IL_006a: ldarg.0 - IL_006b: ldc.i4.s -2 - IL_006d: stfld int32 Program/'<<
$>g__MyYield|0_2>d'::'<>1__state' - IL_0072: ldarg.0 - IL_0073: ldflda valuetype [System.Runtime]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1 Program/'<<
$>g__MyYield|0_2>d'::'<>t__builder' - IL_0078: ldloc.s V_4 - IL_007a: call instance void valuetype [System.Runtime]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1::SetException(class [System.Runtime]System.Exception) - IL_007f: leave.s IL_0095 - } // end handler - IL_0081: ldarg.0 - IL_0082: ldc.i4.s -2 - IL_0084: stfld int32 Program/'<<
$>g__MyYield|0_2>d'::'<>1__state' - IL_0089: ldarg.0 - IL_008a: ldflda valuetype [System.Runtime]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1 Program/'<<
$>g__MyYield|0_2>d'::'<>t__builder' - IL_008f: ldloc.1 - IL_0090: call instance void valuetype [System.Runtime]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1::SetResult(!0) - IL_0095: ret -} // end of method '<<
$>g__MyYield|0_2>d'::MoveNext -.method private hidebysig newslot virtual final - instance void SetStateMachine(class [System.Runtime]System.Runtime.CompilerServices.IAsyncStateMachine stateMachine) cil managed -{ - .custom instance void [System.Runtime]System.Diagnostics.DebuggerHiddenAttribute::.ctor() = ( 01 00 00 00 ) - .param [1] - .custom instance void System.Runtime.CompilerServices.NullableAttribute::.ctor(uint8) = ( 01 00 01 00 00 ) - .override [System.Runtime]System.Runtime.CompilerServices.IAsyncStateMachine::SetStateMachine - // Code size 13 (0xd) - .maxstack 8 - IL_0000: ldarg.0 - IL_0001: ldflda valuetype [System.Runtime]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1 Program/'<<
$>g__MyYield|0_2>d'::'<>t__builder' - IL_0006: ldarg.1 - IL_0007: call instance void valuetype [System.Runtime]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1::SetStateMachine(class [System.Runtime]System.Runtime.CompilerServices.IAsyncStateMachine) - IL_000c: ret -} // end of method '<<
$>g__MyYield|0_2>d'::SetStateMachine -} // end of class '<<
$>g__MyYield|0_2>d' - -.method assembly hidebysig static class [System.Runtime]System.Threading.Tasks.Task`1 - '<
$>g__MyYield|0_2'() cil managed -{ - .custom instance void System.Runtime.CompilerServices.NullableContextAttribute::.ctor(uint8) = ( 01 00 01 00 00 ) - .custom instance void [System.Runtime]System.Runtime.CompilerServices.AsyncStateMachineAttribute::.ctor(class [System.Runtime]System.Type) = ( 01 00 22 50 72 6F 67 72 61 6D 2B 3C 3C 3C 4D 61 // .."Program+<<$>g__MyYield| - 30 5F 32 3E 64 00 00 ) // 0_2>d.. - .custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) - // Code size 47 (0x2f) - .maxstack 2 - .locals init (valuetype Program/'<<
$>g__MyYield|0_2>d' V_0) - IL_0000: ldloca.s V_0 - IL_0002: call valuetype [System.Runtime]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1 valuetype [System.Runtime]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1::Create() - IL_0007: stfld valuetype [System.Runtime]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1 Program/'<<
$>g__MyYield|0_2>d'::'<>t__builder' - IL_000c: ldloca.s V_0 - IL_000e: ldc.i4.m1 - IL_000f: stfld int32 Program/'<<
$>g__MyYield|0_2>d'::'<>1__state' - IL_0014: ldloca.s V_0 - IL_0016: ldflda valuetype [System.Runtime]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1 Program/'<<
$>g__MyYield|0_2>d'::'<>t__builder' - IL_001b: ldloca.s V_0 - IL_001d: call instance void valuetype [System.Runtime]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1::Start$>g__MyYield|0_2>d'>(!!0&) - IL_0022: ldloca.s V_0 - IL_0024: ldflda valuetype [System.Runtime]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1 Program/'<<
$>g__MyYield|0_2>d'::'<>t__builder' - IL_0029: call instance class [System.Runtime]System.Threading.Tasks.Task`1 valuetype [System.Runtime]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1::get_Task() - IL_002e: ret -} // end of method Program::'<
$>g__MyYield|0_2' - } // end of class Program From 38b2087687b478d14255af963a03255e842815ad Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Wed, 27 Sep 2023 14:33:30 -0700 Subject: [PATCH 016/203] Runtime handled tasks markdown for experiment --- docs/design/features/runtime-handled-tasks.md | 244 ++++++++++++++++++ 1 file changed, 244 insertions(+) create mode 100644 docs/design/features/runtime-handled-tasks.md diff --git a/docs/design/features/runtime-handled-tasks.md b/docs/design/features/runtime-handled-tasks.md new file mode 100644 index 00000000000000..49bc2983affb01 --- /dev/null +++ b/docs/design/features/runtime-handled-tasks.md @@ -0,0 +1,244 @@ +# Runtime Handled Tasks Experiment + +The .NET Runtime has historically supported a programming model of developing async programs through use of the System.Threading.Tasks library provided by the runtime as well as the Roslyn async method support, which interacts with that tasks library to make it simple to write the callback based state machines that the runtime can use. Over the years several attempts have been made to optimize the performance and other characteristics of this scheme, but fundamentally those arrangements have relied on either tweaks to the core library's code base or adjustments to the IL code generated. This experiment seeks to identify what improvements to the async model can be achieved by moving the state machine computation and handling to being handled internally in the runtime. + +## Experimental Goals + +1. Find out if runtime generated async state machines are substantially better in performance than the existing async state machine model +2. Find out if we can implement a variation on async which has some subtle semantic behavior changes, but remains compatible with existing code which is written in the original style + +## Experimental Approach + +1. Develop new semantics which allow the runtime to implement state machines as part of the runtime instead of relying on the existing state machine generation. +2. Develop 1 or more implementations of the runtime which implement said semantics +3. Develop a version of the Roslyn compiler which is capable of building code using the new semantics +4. Develop a set of microbenchmarks for measuring the different performance characteristics of the new model +5. Develop a set of application scenario benchmarks + +Throughput this document, any references to the term async2 refer exclusively to the new model. + +## New Semantics + +This section is a work in progress, but describes the current expected semantic changes. + +### Specification of a method as following async2 semantics + +To specify that a method follows async2 rules, a custom modifier to either `System.Threading.Tasks.Task`, ``System.Threading.Tasks.Task`1``, `System.Threading.Tasks.ValueTask`, or ``System.Threading.Tasks.ValueTask`1`` shall be placed as the last custom modifier before the return type in the signature of a method. + +### Flowing `SyncronizationContext` and `ExecutionContext` and other behavior such as AsyncLocal +Unlike traditional C# compiler generated async, async2 methods shall not save/restore `SyncronizationContext` and `ExecutionContext` at function boundaries. Instead, changes to them can be observed by their callers (as long as the caller is not a Task to async2 thunk). + +#### Integration with `SyncronizationContext` and resumption + +A new attribute `System.Runtime.CompilerServices.ConfigureAwaitAttribute` will be defined. It can be applied at the assembly/module/type/method level. It shall be defined as +``` +namespace System.Runtime.CompilerServices +{ + class ConfigureAwaitAttribute : Attribute + { + public ConfigureAwaitAttribute(bool continueOnCapturedContext) { ContinueOnCapturedContext = continueOnCapturedContext; } + public bool ContinueOnCapturedContext { get; } + } +} +``` + +The behavior of this attribute shall be to apply configure await semantics for resuming suspended frames of execution. Notably, though, the semantics of exactly which `SynchronizationContext` to use is subtly different from that of traditional async. In traditional async since each async method saves/restores the `SynchronizationContext` an async method which suspended at an ConfigureAwait(true) await point awaiting async method `A` will always continue on the Synchronization context that existed before the call to async method `A`, where in the async2 model the context used will be that which is current just before calling into method `A`, but in the async2 model, the SynchronizationContext will be the context that was current just before the return statement of method `A`. In practice, we expect this to be a distinction without a difference, as all known intentional uses of `SyncrhonizationContext` are implemented via a pattern where the context is set in a function, and restored via a try/finally to its original state before returning from the function. + +#### Implications of flowing `ExecutionContext` and async locals + +`ExecutionContext` is used to represent the current values of the `AsyncLocal`. This feature is colloquially known as async locals. The semantics of these locals is surprising to developers today, in that while any function can modify the current state of an aync local, if an async function returns, any mutations to the async local are lost. In contrast, this proposal changes to the model such that when an async2 function returns, the async local state is **not** reverted to its previous state. + +### Integration with the existing api surface of Task and ValueTask based apis + +Any MethodDef that is an async2 based method can also be called as a `Task`/`ValueTask` returning method. Likewise any MethodDef which returns `Task` or `ValueTask` can be called via an async2 entrypoint. In addition, MethodImpl records and virtual method override rules shall be adjusted so that if an interface or base type defines a virtual method using a signature of `Task`/`ValueTask` then it can be overriden via a method that is an async2 api. The same is true vice versa. For overrides implemented via `MethodImpl` the MethodImpl must describe the Body and Decl using signatures which are either both async2 or `Task`/`ValueTask` returning, but the MethodImpl will also apply to the other async variant. These overrides are permitted to be interleaved, so for example + +``` +class Base +{ + public virtual async Task Method() { ... } +} +class Derived : Base +{ + public override async2 int Method() { ... } +} + +class MoreDerived : Derived +{ + public override async Task Method() { ... } +} +``` + +Any attempt to call an entrypoint will resolve to either an implementation which matches the signature, or in case of an async variant mismatch, will resolve to a runtime generated thunk which will call into the developer provided implementation. + +Methods that return Task via generics do not follow this rule. For instance +``` +class MyGeneric +{ + T DoSomething() { ... } +} +``` + +`MyGeneric.DoSomething()` is not callable via an async2 entrypoint. + +#### Thunk from an async2 api surface to Task based implementation + +A thunk from an async2 api surface to a Task based implementation will have the following psuedocode, if a legal thunk can be made. + +``` +async2 ReturnType ThunkAsync(ParameterType param1, ParameterType2 param2, ...) +{ + return RuntimeHelpers.UnsafeAwaitAwaiterFromRuntimeAsync, ReturnType>(TargetMethod(param1, param2, ...)); +} +``` + +If any of the parameter types to the target method are ref parameters, or ref structures, then the Thunk generated will be +``` +async2 ReturnType ThunkAsync(ParameterType param1, ParameterType2 param2, ...) +{ + throw new InvalidProgramException(); +} +``` + +#### Thunk from the Task api surface to async2 based implementation + +If any of the parameter types to the target method are ref parameters, or ref structures, then any thunk invoked will throw `InvalidProgramException`. Otherwise it will perform whatever runtime actions are necessary to transition to the async2 implementation, and return the appropriate Task object type on return. + +Thunks are required to save and restore the `ExecutionContext` and `SynchronizationContext` such that callers of thunks cannot observe changes to them. + +### Specifying custom scheduling of async2 +Async2 shall integrate with `TaskScheduler.Current`. + +### Semantics of async2 based IL code + +#### IL Semantics + +Async2 IL is broadly similar to the existing IL semantics with the following restrictions. + +1. Usage of the `localloc` instruction is forbidden +3. The `ldloca` and `ldarga` instructions are redefined to return managed pointers instead of pointers. +4. A pinning local cannot be validly used with a local variable. +5. As an initial restriction that is not fundamental, the `tail.` prefix is not permitted + +Notably, the design permits byrefs and ref structs to be used within async2 methods. + +#### EH Semantics +No call to a method with an async2 modreq will be permitted in a finally, fault, filter, or catch clause. If such a thing exists, the program is invalid. + +The StackTrace of a exception thrown within an async2 method shall include any async2 frames which are logically on the call stack up until the thunk which transfers execution to the root async2 method. + +### Api surface for interacting with non Task/ValueTask apis which async from within async2 code + +The runtime shall provide the following apis + +``` + public interface ICriticalNotifyCompletion2 : ICriticalNotifyCompletion + { + bool IsCompleted { get; } + void GetResult(); + } + public interface ICriticalNotifyCompletion2 : ICriticalNotifyCompletion + { + bool IsCompleted { get; } + TResult GetResult(); + } + public interface INotifyCompletion2 : INotifyCompletion + { + bool IsCompleted { get; } + void GetResult(); + } + public interface INotifyCompletion2 : INotifyCompletion + { + bool IsCompleted { get; } + TResult GetResult(); + } + + public static async2 void AwaitAwaiterFromRuntimeAsync(TAwaiter awaiter) where TAwaiter : INotifyCompletion2 + public static async2 TResult AwaitAwaiterFromRuntimeAsync(TAwaiter awaiter) where TAwaiter : INotifyCompletion2 + public static async2 void UnsafeAwaitAwaiterFromRuntimeAsync(TAwaiter awaiter) where TAwaiter : ICriticalNotifyCompletion2 + public static async2 TResult UnsafeAwaitAwaiterFromRuntimeAsync(TAwaiter awaiter) where TAwaiter : ICriticalNotifyCompletion2 + +``` +As well as define that `Task`/`ValueTask` and other awaiters provide awaiters provided implement the `INotifyCompletion2`/`ICriticalNotification2` interfaces. The AwaitAwaiterFromRuntimeAsync provides a means for code generators to await on anything that is awaitable today with a simple set of IL. + +### Utilizing async2 based code from non-async2 based code + +Non-async2 code is never permitted to directly invoke async2 code. Instead any and all calls into async2 functions shall be done by means of one of the thunks. + +### Interaction with Reflection + +Async2 methods will not be visible in reflection via the `Type.GetMethod`, `TypeInfo.DeclaredMethods`, `TypeInfo.DeclaredMembers` , or `Type.GetInterfaceMap()` apis. + +`Type.GetMembers` and `Type.GetMethods` will be able to find the async2 methods if and only if the `BindingFlags.Async2Visible` flag is set. + +`Type.GetMemberWithSameMetadataDefinitionAs` will find the matching async variant method. + +### Modification of existing async infrastructure + +The only known modifications are to add the various new `INotifyCompletion2` and `INotifyCompletion3` interfaces to the awaitable types in the core libraries. + +## Implementation of async2 design 1 + +TBD, design in progress... there are some notes, but this is very incomplete at this moment. + +The general design is to leverage the notion that a normal code generated function at a function call point is effectively a state machine. The index for resumption is the IP that returning to the function will set, and the stackframe + saved registers are the current state of the state machine. I believe we can achieve performance comparable to synchronous code with this scheme for non-suspended scenarios, and we may achieve acceptable overall performance as an async mechanism if suspension is relatively rare. + +#### Thunk from the Task api surface to async2 based implementation + +A thunk from the Task api surface to an async2 based implementation will have the following psuedocode if it is not required to throw `InvalidProgramException` + +``` +Task ThunkAsync(ParameterType param1, ParameterType2 param2, ...) +{ + // A variant on this helper type will be defined for each type of `Task`/`ValueTask`/`Task`/`ValueTask` + System.Runtime.CompilerServices.RuntimeTaskState runtimeTaskState = new; + + // If the Task is parameterized there shall be a return type to work with. + ReturnType result; + + runtimeTaskState.Push(); + try + { + try + { + // NOTE there is a special case for instance methods on valuetypes. For that case, + // the thunk will construct a boxed instance of the this value, and then invoke the method + // on that. + result = TargetMethod(param1, param2, ...); + } + catch (Exception ex) + { + return runtimeTaskState.FromException(ex); + } + return runtimeTaskState.FromResult(result); + } + finally + { + runtimeTaskState.Pop(); + } +} +``` + +## C# Language changes + +TBD + +Major identified concerns are + +1. What about code which today looks like `public async Task>> Method()` What would the new syntax be? +2. It is desirable to be able to implement methods that return `ValueTask` as well as just `Task` with an async2 method. How do we distinguish? +3. There is an existing concept for `async void` methods. Does that model need to exist in the new system. + +## Microbenchmarks + +1. Async computation of fibonacci via recursive algorithm, with and without various amounts of yielding +2. Suspension with stacks of various depths +3. Suspension with stacks where the operation of the task operates in a sawtooth pattern. For instance, suspend first at depth 20, then reach depth 30, return to depth 10 and suspend, reach depth 30, return to depth 10 and suspend, etc. Measure the impact of suspending with greater and lesser depths. +4. Measure the performance of thunks to async2 functions which do effectively nothing. +5. Benchmark the effect of long lived suspended state vs short lived suspended state +6. Benchmark the performance of EH at various depths. + +## Scenario benchmarks + +1. Serialization/Deserialization of JSON data to an asynchronous stream +2. ASP.NET servicing of requests where must of the pipeline is converted to the async2 model + From b7b00ded3d7bc6d1f909d837a19ffc7a8b02e77c Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Wed, 27 Sep 2023 17:06:52 -0700 Subject: [PATCH 017/203] Writeup managed side of runtime driven suspension. Need to write details into doc, but managed code may have the right logic :) --- docs/design/features/runtime-handled-tasks.md | 7 + .../CompilerServices/RuntimeHelpers.cs | 306 +++++++++++++++++- .../src/System/Threading/Tasks/Future.cs | 19 +- 3 files changed, 326 insertions(+), 6 deletions(-) diff --git a/docs/design/features/runtime-handled-tasks.md b/docs/design/features/runtime-handled-tasks.md index 49bc2983affb01..64accff939822f 100644 --- a/docs/design/features/runtime-handled-tasks.md +++ b/docs/design/features/runtime-handled-tasks.md @@ -182,6 +182,13 @@ TBD, design in progress... there are some notes, but this is very incomplete at The general design is to leverage the notion that a normal code generated function at a function call point is effectively a state machine. The index for resumption is the IP that returning to the function will set, and the stackframe + saved registers are the current state of the state machine. I believe we can achieve performance comparable to synchronous code with this scheme for non-suspended scenarios, and we may achieve acceptable overall performance as an async mechanism if suspension is relatively rare. +#### JIT Code changes +1. Do not allow InlinedCallFrames to be linked to the frame chain across a suspend (Or disable p/invoke inlining in these methods) +2. Report frame pointers as ByRef +3. Report all pointers to locals as ByRef +4. (If possible) tweak codegen for return processing to not use the address returned in RAX when doing byref returns +5. Force the AwaitAwaiterFromRuntimeAsync to be treated as an async2 method even though it isn't marked as such (due to compiler limitations) + #### Thunk from the Task api surface to async2 based implementation A thunk from the Task api surface to an async2 based implementation will have the following psuedocode if it is not required to throw `InvalidProgramException` diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs index 0665687bace867..b4530098b67919 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs @@ -3,6 +3,8 @@ using System.Runtime.InteropServices; using System.Reflection; +using System.Threading; +using System.Threading.Tasks; namespace System.Runtime.CompilerServices { @@ -273,8 +275,306 @@ public static void AwaitAwaiterFromRuntimeAsync(TAwaiter awaiter) wher awaiter.GetResult(); } - private static void SuspendIfSuspensionNotAborted() {} - private static Action? GetOrCreateResumptionDelegate() { return null; } - private static void AbortSuspend() {} + [ThreadStatic] + private static unsafe void* t_asyncData; + + internal struct RuntimeAsyncReturnValue + { + public RuntimeAsyncReturnValue(object? obj) + { + _obj = obj; + _ptr = IntPtr.Zero; + _isObj = true; + } + public RuntimeAsyncReturnValue(IntPtr ptr) + { + _obj = null; + _ptr = ptr; + _isObj = false; + } + public IntPtr _ptr; + public object? _obj; + public bool _isObj; + } + + internal abstract unsafe class RuntimeAsyncMaintainedData + { + public Action? _resumption; + public Exception? _exception; + public bool _suspendActive; + public bool _initialTaskEntry = true; + public bool _completed; + public byte _dummy; + public bool _abortSuspend; + + public Tasklet *_nextTasklet; + public Tasklet* _oldTaskletNext; + + public RuntimeAsyncReturnValue _retValue; + public virtual ref byte GetReturnPointer() { return ref _dummy; } + + public Task? _task; + public abstract Task GetTask(); + } + + + // These are all implemented by the same assembly helper that will setup the tasklet in its new home on the stack + // and then tail-call into it. We will need a different entrypoint name for each type of register based return that can happen + [MethodImpl(MethodImplOptions.InternalCall)] + internal static extern unsafe object ResumeTaskletReferenceReturn(Tasklet* pTasklet, ref RuntimeAsyncReturnValue retValue); + [MethodImpl(MethodImplOptions.InternalCall)] + internal static extern unsafe IntPtr ResumeTaskletIntegerRegisterReturn(Tasklet* pTasklet, ref RuntimeAsyncReturnValue retValue); + + internal sealed class RuntimeAsyncMaintainedData : RuntimeAsyncMaintainedData, ICriticalNotifyCompletion + { + public RuntimeAsyncMaintainedData() + { + _task = CompletionTask(); + _resumption = ResumptionFunc; + } + + public unsafe void ResumptionFunc() + { + if (HasCurrentAsyncDataFrame() && GetCurrentAsyncDataFrame()._maintainedData == this) + { + _abortSuspend = true; + return; + } + // Once we perform a resumption we no longer need to worry about handling the ultimate return data from the run of Tasklets + _initialTaskEntry = false; + + int collectiveStackAllocsPerformed = 0; + + try + { + AsyncDataFrame dataFrame = new AsyncDataFrame(this); + PushAsyncData(ref dataFrame); + try + { + while (_nextTasklet != null) + { + int maxStackNeeded = _nextTasklet->GetMaxStackNeeded(); + if (maxStackNeeded > collectiveStackAllocsPerformed) + { +#pragma warning disable CA2014 + // This won't stack overflow unless MaxStackNeeded is actually too high, as the extra allocation is controlled by collectiveStackAllocsPerformed + // TODO This is doing terrible things with the ABI, so we may need to be more careful here + int stackToAlloc = maxStackNeeded - collectiveStackAllocsPerformed; + byte *pStackAlloc = stackalloc byte[stackToAlloc]; + collectiveStackAllocsPerformed += stackToAlloc; +#pragma warning restore CA2014 +// The optimizer does nothing with variable sized StackAlloc KeepStackAllocAlive(pStackAlloc); + } + + try + { + switch (_nextTasklet->taskletReturnType) + { + case TaskletReturnType.ObjectReference: + _retValue = new RuntimeAsyncReturnValue(ResumeTaskletReferenceReturn(_nextTasklet, ref _retValue)); + break; + case TaskletReturnType.Integer: + _retValue = new RuntimeAsyncReturnValue(ResumeTaskletIntegerRegisterReturn(_nextTasklet, ref _retValue)); + break; + case TaskletReturnType.ByReference: + throw new NotImplementedException(); // This will be awkward (but not impossible) to implement. Hold off for now + } + } + finally + { + DeleteTasklet(_nextTasklet); + } + _nextTasklet = _nextTasklet->pTaskletNextInStack; + } + } + finally + { + PopAsyncData(); + } + } + catch (Exception e) + { + SetException(e); + } + } + + private Action? _taskResumer; + private T? _returnData; + public override ref byte GetReturnPointer() + { + return ref Unsafe.As(ref _returnData!); + } + + public RuntimeAsyncMaintainedData GetAwaiter() + { + return this; + } + + public bool IsCompleted => _completed; + + public override Task GetTask() + { + return _task!; + } + + public void SetException(Exception exception) + { + _exception = exception; + _completed = true; + _taskResumer!(); + } + + public void SetResultDone() + { + _completed = true; + _taskResumer!(); + } + + public void OnCompleted(Action resumer) { throw new NotSupportedException(); } + public void UnsafeOnCompleted(Action resumer) { _taskResumer = resumer; } + public T? GetResult() { if (_exception != null) throw _exception; return _returnData; } + + private async Task CompletionTask() + { + return await this; + } + } + + internal unsafe struct AsyncDataFrame + { + public AsyncDataFrame(RuntimeAsyncMaintainedData maintainedData) + { + _maintainedData = maintainedData; + _crawlMark = StackCrawlMark.LookForMe; + _next = null; + _createRuntimeMaintainedData = null; + } + + public AsyncDataFrame(Func getMaintainedData) + { + _maintainedData = null; + _crawlMark = StackCrawlMark.LookForMe; + _next = null; + _createRuntimeMaintainedData = getMaintainedData; + } + + public StackCrawlMark _crawlMark; + public void* _next; + public Func? _createRuntimeMaintainedData; + public RuntimeAsyncMaintainedData? _maintainedData; + } + + internal enum TaskletReturnType + { + // These return types are OS/architecture specific. For instance, Arm64 supports returning structs in a register pair + Integer, + ObjectReference, + ByReference + } + internal unsafe struct Tasklet + { + public Tasklet* pTaskletNextInStack; + public Tasklet* pTaskletNextInLiveList; + public Tasklet* pTaskletPrevInLiveList; + public byte* pStackData; + public byte* pStackDataInfo; + public int maxStackNeeded; + public TaskletReturnType taskletReturnType; + + public int GetMaxStackNeeded() { return maxStackNeeded; } + } + + internal static unsafe void PushAsyncData(ref AsyncDataFrame asyncData) + { + asyncData._next = t_asyncData; + t_asyncData = Unsafe.AsPointer(ref asyncData); + } + + internal static unsafe void PopAsyncData() + { + t_asyncData = Unsafe.AsRef(t_asyncData)._next; + } + + internal static unsafe bool HasCurrentAsyncDataFrame() + { + return t_asyncData != null; + } + + private static unsafe ref AsyncDataFrame GetCurrentAsyncDataFrame() + { + return ref Unsafe.AsRef(t_asyncData); + } + + // Capture stack into a series of tasklets (one per stack frame) + // These tasklets hold the stack data for a particular frame, as well as the contents of the saved registers as needed by that frame, GC data for reporting the frame, and data for restoring the frame. + // To make this work. + // 1. All addresses of locals are to be used as byrefs + // 2. Frame pointers are to be reported as byrefs + // 3. Return values are to be returned by reference in all cases where the return value is not a simple object return or return of a simple value in the return value register (this makes the resumption function reasonable to write. Notably, floating point, and ref return will be returned by reference as well as generalized struct return, and return which would normally involve multiple return value registers) + // 4. There are to be no refs to the outermost caller function exceptn for the valuetype return address (methods which begin on an instance valuetype will have the thunk box the valuetype and the runtime async method on the boxed instance) + [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "RuntimeSuspension_CaptureTasklets")] + private static unsafe partial Tasklet *CaptureCurrentStackIntoTasklets(StackCrawlMarkHandle stackMarkTop, ref byte returnValueHandle, [MarshalAs(UnmanagedType.U1)] bool useReturnValueHandle, void* taskAsyncData, out Tasklet* lastTasklet); + + [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "RuntimeSuspension_DeleteTasklet")] + private static unsafe partial void DeleteTasklet(Tasklet *tasklet); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal static extern void UnwindToFunctionWithAsyncFrame(ref AsyncDataFrame dataFrame); + + private static void SuspendIfSuspensionNotAborted() + { + ref AsyncDataFrame asyncFrame = ref GetCurrentAsyncDataFrame(); + RuntimeAsyncMaintainedData maintainedData = asyncFrame._maintainedData!; + if (maintainedData._abortSuspend) + { + AbortSuspend(); + } + else + { + UnwindToFunctionWithAsyncFrame(ref asyncFrame); + } + } + + [System.Security.DynamicSecurityMethod] // Methods containing StackCrawlMark local var has to be marked DynamicSecurityMethod + private static unsafe Action? GetOrCreateResumptionDelegate() + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + ref AsyncDataFrame asyncFrame = ref GetCurrentAsyncDataFrame(); + + asyncFrame._maintainedData ??= asyncFrame._createRuntimeMaintainedData!(); + + RuntimeAsyncMaintainedData maintainedData = asyncFrame._maintainedData; + + Tasklet* lastTasklet = null; + Tasklet* nextTaskletInStack = CaptureCurrentStackIntoTasklets(new StackCrawlMarkHandle(ref stackMark), ref maintainedData.GetReturnPointer(), maintainedData._initialTaskEntry, t_asyncData, out lastTasklet); + if (nextTaskletInStack == null) + throw new OutOfMemoryException(); + + maintainedData._oldTaskletNext = maintainedData._nextTasklet; + lastTasklet->pTaskletNextInStack = maintainedData._nextTasklet; + maintainedData._nextTasklet = nextTaskletInStack; + + maintainedData._abortSuspend = false; + maintainedData._suspendActive = true; + + return maintainedData._resumption; + } + + private static unsafe void AbortSuspend() + { + ref AsyncDataFrame asyncFrame = ref GetCurrentAsyncDataFrame(); + RuntimeAsyncMaintainedData maintainedData = asyncFrame._maintainedData!; + + Tasklet* pTaskletCur = maintainedData._nextTasklet; + while (pTaskletCur != maintainedData._oldTaskletNext) + { + Tasklet* pTaskletPrev = pTaskletCur; + pTaskletCur = pTaskletPrev; + DeleteTasklet(pTaskletPrev); + } + maintainedData._nextTasklet = maintainedData._oldTaskletNext; + maintainedData._oldTaskletNext = null; + maintainedData._suspendActive = false; + } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Future.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Future.cs index 19f359a333383a..71a771fb23a9c3 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Future.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Future.cs @@ -19,9 +19,22 @@ namespace System.Threading.Tasks // Used to implement Runtime implemented Task suspension handling internal struct RuntimeTaskState { - public void Push() {} - public void Pop() {} - public Task FromResult(TResult result) { return Task.FromResult(result); } + private RuntimeHelpers.AsyncDataFrame dataFrame; + + public void Push() + { + dataFrame = new RuntimeHelpers.AsyncDataFrame(()=> new RuntimeHelpers.RuntimeAsyncMaintainedData()); + RuntimeHelpers.PushAsyncData(ref dataFrame); + } + + public void Pop() { RuntimeHelpers.PopAsyncData(); } + public Task FromResult(TResult result) + { + if (dataFrame._maintainedData == null || !dataFrame._maintainedData._suspendActive) + return Task.FromResult(result); + return (Task)dataFrame._maintainedData.GetTask(); + } + public Task FromException(Exception e) { return Task.FromException(e); } } #pragma warning restore CA1822 From 8353bd8945904960dee7096fd8d4efcf4449a5bc Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Thu, 28 Sep 2023 12:00:12 -0700 Subject: [PATCH 018/203] Add the logic to try to flow ExecutionContext and SynchronizationContext as appropriate --- .../CompilerServices/RuntimeHelpers.cs | 45 ++++++++++++++++++- .../src/System/Threading/Tasks/Future.cs | 2 +- 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs index b4530098b67919..2cb3b1bfa388dc 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs @@ -307,6 +307,10 @@ internal abstract unsafe class RuntimeAsyncMaintainedData public byte _dummy; public bool _abortSuspend; + public ExecutionContext? _executionCtx; + public SynchronizationContext? _syncCtx; + + public Tasklet *_nextTasklet; public Tasklet* _oldTaskletNext; @@ -351,6 +355,16 @@ public unsafe void ResumptionFunc() PushAsyncData(ref dataFrame); try { + // PushAsyncData will fill in the value of _previousExecutionCtx and _previousSyncCtx + if (_syncCtx != dataFrame._previousSyncCtx) + { + dataFrame._currentThread._synchronizationContext = _syncCtx; + } + if (_executionCtx != dataFrame._previousExecutionCtx) + { + ExecutionContext.RestoreChangedContextToThread(dataFrame._currentThread, _executionCtx, dataFrame._previousExecutionCtx); + } + while (_nextTasklet != null) { int maxStackNeeded = _nextTasklet->GetMaxStackNeeded(); @@ -389,7 +403,7 @@ public unsafe void ResumptionFunc() } finally { - PopAsyncData(); + PopAsyncData(ref dataFrame); } } catch (Exception e) @@ -462,6 +476,13 @@ public AsyncDataFrame(Func getMaintainedData) public void* _next; public Func? _createRuntimeMaintainedData; public RuntimeAsyncMaintainedData? _maintainedData; + public Thread _currentThread = Thread.CurrentThread; + + // Store current ExecutionContext and SynchronizationContext as "previousXxx". + // This allows us to restore them and undo any Context changes made in stateMachine.MoveNext + // so that they won't "leak" out of the first await. + public ExecutionContext? _previousExecutionCtx; + public SynchronizationContext? _previousSyncCtx; } internal enum TaskletReturnType @@ -487,12 +508,28 @@ internal unsafe struct Tasklet internal static unsafe void PushAsyncData(ref AsyncDataFrame asyncData) { asyncData._next = t_asyncData; + asyncData._previousExecutionCtx = asyncData._currentThread._executionContext; + asyncData._previousSyncCtx = asyncData._currentThread._synchronizationContext; + t_asyncData = Unsafe.AsPointer(ref asyncData); } - internal static unsafe void PopAsyncData() + internal static unsafe void PopAsyncData(ref AsyncDataFrame asyncData) { t_asyncData = Unsafe.AsRef(t_asyncData)._next; + + // The common case is that these have not changed, so avoid the cost of a write barrier if not needed. + if (asyncData._previousSyncCtx != asyncData._currentThread._synchronizationContext) + { + // Restore changed SynchronizationContext back to previous + asyncData._currentThread._synchronizationContext = asyncData._previousSyncCtx; + } + + ExecutionContext? currentExecutionCtx = asyncData._currentThread._executionContext; + if (asyncData._previousExecutionCtx != currentExecutionCtx) + { + ExecutionContext.RestoreChangedContextToThread(asyncData._currentThread, asyncData._previousExecutionCtx, currentExecutionCtx); + } } internal static unsafe bool HasCurrentAsyncDataFrame() @@ -557,6 +594,10 @@ private static void SuspendIfSuspensionNotAborted() maintainedData._abortSuspend = false; maintainedData._suspendActive = true; + maintainedData._executionCtx = asyncFrame._currentThread._executionContext; + maintainedData._syncCtx = asyncFrame._currentThread._synchronizationContext; + + return maintainedData._resumption; } diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Future.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Future.cs index 71a771fb23a9c3..69f8bb93534906 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Future.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Future.cs @@ -27,7 +27,7 @@ public void Push() RuntimeHelpers.PushAsyncData(ref dataFrame); } - public void Pop() { RuntimeHelpers.PopAsyncData(); } + public void Pop() { RuntimeHelpers.PopAsyncData(ref dataFrame); } public Task FromResult(TResult result) { if (dataFrame._maintainedData == null || !dataFrame._maintainedData._suspendActive) From 2aac618e5bc204668f8502daa5d39a8112e20f15 Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Mon, 9 Oct 2023 13:00:27 -0700 Subject: [PATCH 019/203] It compiles, but doesn't link --- src/coreclr/inc/corhlprpriv.h | 10 + src/coreclr/inc/regdisp.h | 7 + src/coreclr/vm/CMakeLists.txt | 2 + src/coreclr/vm/corelib.cpp | 2 + src/coreclr/vm/ecalllist.h | 3 + src/coreclr/vm/method.hpp | 2 + src/coreclr/vm/qcallentrypoints.cpp | 4 + .../vm/restoreregs_for_runtimesuspension.h | 22 + src/coreclr/vm/runtimesuspension.cpp | 502 ++++++++++++++++++ src/coreclr/vm/runtimesuspension.h | 14 + .../CompilerServices/RuntimeHelpers.cs | 18 +- 11 files changed, 580 insertions(+), 6 deletions(-) create mode 100644 src/coreclr/vm/restoreregs_for_runtimesuspension.h create mode 100644 src/coreclr/vm/runtimesuspension.cpp create mode 100644 src/coreclr/vm/runtimesuspension.h diff --git a/src/coreclr/inc/corhlprpriv.h b/src/coreclr/inc/corhlprpriv.h index ffa249543329e3..4c9f31b3223128 100644 --- a/src/coreclr/inc/corhlprpriv.h +++ b/src/coreclr/inc/corhlprpriv.h @@ -576,6 +576,16 @@ class CQuickArrayList : protected CQuickArray return m_curSize; } + T* Ptr() + { + return (T*) CQuickBytesBase::Ptr(); + } + + const T* Ptr() const + { + return (T*) CQuickBytesBase::Ptr(); + } + void Shrink() { CQuickArray::Shrink(m_curSize); diff --git a/src/coreclr/inc/regdisp.h b/src/coreclr/inc/regdisp.h index c3b4b4431bec54..48dd82a1db6519 100644 --- a/src/coreclr/inc/regdisp.h +++ b/src/coreclr/inc/regdisp.h @@ -270,6 +270,13 @@ inline BOOL IsInCalleesFrames(REGDISPLAY *display, LPVOID stackPointer) return stackPointer < ((LPVOID)(display->SP)); } +inline BOOL IsInCurrentFrame(REGDISPLAY *display, LPVOID stackPointer) +{ + LIMITED_METHOD_CONTRACT; + _ASSERTE(display->IsCallerContextValid); + return stackPointer < ((LPVOID)(::GetSP(display->pCallerContext))); +} + inline TADDR GetRegdisplayStackMark(REGDISPLAY *display) { #if defined(TARGET_AMD64) diff --git a/src/coreclr/vm/CMakeLists.txt b/src/coreclr/vm/CMakeLists.txt index ce94e6323da855..73a22a2e60f2a9 100644 --- a/src/coreclr/vm/CMakeLists.txt +++ b/src/coreclr/vm/CMakeLists.txt @@ -359,6 +359,7 @@ set(VM_SOURCES_WKS reflectclasswriter.cpp reflectioninvocation.cpp runtimehandles.cpp + runtimesuspension.cpp safehandle.cpp simplerwlock.cpp stackingallocator.cpp @@ -458,6 +459,7 @@ set(VM_HEADERS_WKS reflectclasswriter.h reflectioninvocation.h runtimehandles.h + runtimesuspension.h simplerwlock.hpp stackingallocator.h stringliteralmap.h diff --git a/src/coreclr/vm/corelib.cpp b/src/coreclr/vm/corelib.cpp index b6ae49b2a1d1ca..22869b70e15cf4 100644 --- a/src/coreclr/vm/corelib.cpp +++ b/src/coreclr/vm/corelib.cpp @@ -77,6 +77,8 @@ #include "tailcallhelp.h" +#include "runtimesuspension.h" + /////////////////////////////////////////////////////////////////////////////// // // Hardcoded Meta-Sig diff --git a/src/coreclr/vm/ecalllist.h b/src/coreclr/vm/ecalllist.h index 2f1f27bdf3eee3..2c1a596f8cfbd0 100644 --- a/src/coreclr/vm/ecalllist.h +++ b/src/coreclr/vm/ecalllist.h @@ -557,6 +557,9 @@ FCFuncStart(gRuntimeHelpers) FCFuncElement("AllocTailCallArgBuffer", TailCallHelp::AllocTailCallArgBuffer) FCFuncElement("GetTailCallInfo", TailCallHelp::GetTailCallInfo) FCFuncElement("Box", JIT_Box) + FCFuncElement("UnwindToFunctionWithAsyncFrame", RuntimeSuspension_UnwindToFunctionWithAsyncFrame) + FCFuncElement("ResumeTaskletReferenceReturn", RuntimeSuspension_ResumeTaskletReferenceReturn) + FCFuncElement("ResumeTaskletIntegerRegisterReturn", RuntimeSuspension_ResumeTaskletIntegerRegisterReturn) FCFuncEnd() FCFuncStart(gMethodTableFuncs) diff --git a/src/coreclr/vm/method.hpp b/src/coreclr/vm/method.hpp index 7b0274e6057049..d16182d8e54273 100644 --- a/src/coreclr/vm/method.hpp +++ b/src/coreclr/vm/method.hpp @@ -1722,6 +1722,8 @@ class MethodDesc return (m_wFlags & mdcIsAsyncThunkMethod) != 0; } + bool IsAsync2Method(); + inline void SetIsAsyncThunkMethod() { LIMITED_METHOD_CONTRACT; diff --git a/src/coreclr/vm/qcallentrypoints.cpp b/src/coreclr/vm/qcallentrypoints.cpp index 3682a4cef1691d..8f5df385e1b17c 100644 --- a/src/coreclr/vm/qcallentrypoints.cpp +++ b/src/coreclr/vm/qcallentrypoints.cpp @@ -76,8 +76,12 @@ #include "exceptionhandlingqcalls.h" +#include "runtimesuspension.h" + static const Entry s_QCall[] = { + DllImportEntry(RuntimeSuspension_DeleteTasklet) + DllImportEntry(RuntimeSuspension_CaptureTasklets) DllImportEntry(Enum_GetValuesAndNames) DllImportEntry(DebugDebugger_Launch) DllImportEntry(DebugDebugger_Log) diff --git a/src/coreclr/vm/restoreregs_for_runtimesuspension.h b/src/coreclr/vm/restoreregs_for_runtimesuspension.h new file mode 100644 index 00000000000000..689be7bf3fbf0e --- /dev/null +++ b/src/coreclr/vm/restoreregs_for_runtimesuspension.h @@ -0,0 +1,22 @@ +#if defined(TARGET_AMD64) + DISCOVER_RESTORED_REG(Rbx) + DISCOVER_RESTORED_REG(Rbp) + DISCOVER_RESTORED_REG(Rdi) + DISCOVER_RESTORED_REG(Rsi) + DISCOVER_RESTORED_REG(R12) + DISCOVER_RESTORED_REG(R13) + DISCOVER_RESTORED_REG(R14) + DISCOVER_RESTORED_REG(R15) + DISCOVER_RESTORED_REG(Xmm6) + DISCOVER_RESTORED_REG(Xmm7) + DISCOVER_RESTORED_REG(Xmm8) + DISCOVER_RESTORED_REG(Xmm9) + DISCOVER_RESTORED_REG(Xmm10) + DISCOVER_RESTORED_REG(Xmm11) + DISCOVER_RESTORED_REG(Xmm12) + DISCOVER_RESTORED_REG(Xmm13) + DISCOVER_RESTORED_REG(Xmm14) + DISCOVER_RESTORED_REG(Xmm15) +#else + PORTABILITY_ASSERT(); +#endif diff --git a/src/coreclr/vm/runtimesuspension.cpp b/src/coreclr/vm/runtimesuspension.cpp new file mode 100644 index 00000000000000..fdfc254368e348 --- /dev/null +++ b/src/coreclr/vm/runtimesuspension.cpp @@ -0,0 +1,502 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include "common.h" +#include "runtimesuspension.h" +#include "siginfo.hpp" + +enum class TaskletReturnType : int32_t +{ + // These return types are OS/architecture specific. For instance, Arm64 supports returning structs in a register pair. This is also incomplete and doesn't handle floating point, vector registers, etc. + Integer, + ObjectReference, + ByReference +}; + +enum RegisterToRestore +{ + Rbx, + Rbp, + Rdi, + Rsi, + R12, + R13, + R14, + R15, + Xmm6, + Xmm7, + Xmm8, + Xmm9, + Xmm10, + Xmm11, + Xmm12, + Xmm13, + Xmm14, + Xmm15, + ReturnRegisters, // End sequence marker. Associated offset is for the return register, but the actual return value is stashed after the register data +}; + +struct RegRestore +{ + RegisterToRestore reg; + uint32_t offset; +}; + +struct StackDataInfo +{ + void CleanupStackDataInfo() + { + if (ByRefOffsets != NULL) + free(ByRefOffsets); + if (ObjectRefOffsets != NULL) + free(ByRefOffsets); + if (RegistersToRestore != NULL) + free(ByRefOffsets); + } + uint32_t StackRequirement; + uint32_t UnrecordedDataSize; // From the restored RSP to the data chunk, how many bytes are skipped + uint32_t StackDataSize; + uint32_t ReturnAddressOffset; + int32_t* ByRefOffsets = NULL; // Negative numbers indicate pinned byrefs + uint32_t cByRefs; + uint32_t* ObjectRefOffsets = NULL; + uint32_t cObjectRefs; + RegRestore* RegistersToRestore = NULL; +}; + +struct Tasklet +{ + Tasklet* pTaskletNextInStack; + Tasklet* pTaskletNextInLiveList; + Tasklet* pTaskletPrevInLiveList; + uint8_t* pStackData; + uintptr_t restoreIPAddress; + StackDataInfo* pStackDataInfo; + TaskletReturnType taskletReturnType; +}; + +struct RuntimeAsyncReturnValue +{ + uintptr_t _ptr; + uintptr_t _obj; + TaskletReturnType _returnType; +}; + +struct ByRefAdjustment +{ + uint8_t* pOldLocation; + uint32_t Size; + uintptr_t Adjustment; +}; + +bool RelocAtAddress(ByRefAdjustment* pAdjustment, uintptr_t* pData); +void RelocAtAddress(ByRefAdjustment* pAdjustment, uint8_t* pDataToAdjustAddress) +{ + uintptr_t *pData = (uintptr_t*)pDataToAdjustAddress; + RelocAtAddress(pAdjustment, pData); +} + +bool RelocAtAddress(ByRefAdjustment* pAdjustment, uintptr_t* pData) +{ + if ((*pData >= (uintptr_t)pAdjustment->pOldLocation) && ((*pData - (uintptr_t)pAdjustment->pOldLocation) < (uintptr_t)pAdjustment->Size)) + { + *pData += pAdjustment->Adjustment; + return true; + } + return false; +} + +struct RestoreFunctionLocals +{ + // Anonymous union of data for return value registers + union + { + uintptr_t integerRegister; + }; + uintptr_t AddressInMethodToRestoreTo; +private: + uint8_t *pFutureRSPLocation; + uintptr_t ReturnAddress; +public: + uint8_t *GetFutureRSPLocation() { return pFutureRSPLocation; } // TODO This should be some simple math off of the this pointer + uintptr_t GetReturnAddress() { return ReturnAddress; } // TODO This should be some simple math off of the this pointer +}; + +// The actual restoration function which needs to be written in assembly, sets up enough of a frame on top of the current RSP to call this function +// Then, once this function returns, iterates through the +extern "C" RegRestore* PlatformIndependentRestore(Tasklet* tasklet, RuntimeAsyncReturnValue* returnValueToFillIn, RestoreFunctionLocals *restoreLocals) +{ + // Compute what needs to be adjusted in terms of byrefs for + ByRefAdjustment adjustment; + adjustment.pOldLocation = tasklet->pStackData; + adjustment.Size = tasklet->pStackDataInfo->StackDataSize; + + uint8_t* pNewLocation = restoreLocals->GetFutureRSPLocation() + tasklet->pStackDataInfo->UnrecordedDataSize; + adjustment.Adjustment = ((uintptr_t)pNewLocation) - ((uintptr_t)adjustment.pOldLocation); + + // Adjust all pointers to the stack data that we are about to move, both in this frame, and in the caller frames + Tasklet *pTaskletToAdjustByrefsOn = tasklet; + do + { + uint32_t cByRefs = pTaskletToAdjustByrefsOn->pStackDataInfo->cByRefs; + uint32_t* byRefOffsets = (uint32_t*)pTaskletToAdjustByrefsOn->pStackDataInfo->ByRefOffsets; + uint8_t* taskletData = pTaskletToAdjustByrefsOn->pStackData; + for (uint32_t iByRef = 0; iByRef < cByRefs; iByRef++) + { + RelocAtAddress(&adjustment, taskletData + byRefOffsets[iByRef]); + } + pTaskletToAdjustByrefsOn = pTaskletToAdjustByrefsOn->pTaskletNextInStack; + } while (pTaskletToAdjustByrefsOn != NULL); + + StackDataInfo *pStackDataInfo = tasklet->pStackDataInfo; + + // Copy most of the memory + memcpy(restoreLocals->GetFutureRSPLocation() + pStackDataInfo->UnrecordedDataSize, tasklet->pStackData, pStackDataInfo->StackDataSize); + + // Update the return address on the stack + *(uintptr_t *)(restoreLocals->GetFutureRSPLocation() + pStackDataInfo->ReturnAddressOffset) = restoreLocals->GetReturnAddress(); + + restoreLocals->AddressInMethodToRestoreTo = tasklet->restoreIPAddress; + + switch (returnValueToFillIn->_returnType) + { + case TaskletReturnType::Integer: + restoreLocals->integerRegister = returnValueToFillIn->_ptr; + break; + case TaskletReturnType::ObjectReference: + restoreLocals->integerRegister = returnValueToFillIn->_obj; + break; + + default: + // Not yet implemented + _ASSERTE(FALSE); + } + + return pStackDataInfo->RegistersToRestore; + + // The rest of the code will do something like... + // This is written for X86 which is ... not the initial target +/* + mov ecx, [eax + offsetof(pStackData)] + mov edx, [eax + offsetof(pStackDataInfo)] + mov edx, [eax + offsetof(RegistersToRestore)] ; EDX now points at a list of registers to restore, terminated by the RegisterToRestore::ReturnRegisters value + add ecx, [edx + offsetof(StackDataSize)] ; ECX now points at the data to copy into the various restored register + ; We have eax as a scratch register if we need it + .data +tbl dq ReturnRegisters, CheckEBX, CheckESI, ... ;table + .code +RegRestoreLoop: + mov eax, [edx] + jmp [tbl + eax*4] +ReturnRegisters: + mov eax, [esp-X] + mov edx, [esp-X+4] + MOVDQU xmm0, [esp-X] + mov ecx, [esp-Y] ; Load address of where to jmp into ecx + add esp, Z + jmp ecx + // Repeat for every saved register type +CheckEBX: + cmp EBX, [edx] + mov eax, [edx+4] + mov [edx + 4], ebx + mov ebx, eax + add edx, 8 + jmp RegRestoreLoop + +CheckRBX: + + ends with + cmp +*/ +} + +struct TaskletCaptureData +{ + bool inRunOfAsyncMethods; + StackCrawlMark* stackMark; + Tasklet* firstTasklet = NULL; + Tasklet* lastTasklet = NULL; + uintptr_t stackLimit; + uintptr_t stackToIgnoreFromPreviousFrame = 0; // Portions of the stack which have been copied into a previous frame + uintptr_t returnStructSize = 0; + CQuickArrayList ActiveByRefsToStack; + void AddCopiedByRef(uintptr_t currentStackTop, uintptr_t *newByRef) + { + if ((*newByRef >= currentStackTop) && (*newByRef < stackLimit)) + { + ActiveByRefsToStack.Push(newByRef); + } + } + + void ApplyByRefRelocsToNewlyAllocatedTasklet(ByRefAdjustment adjustment) + { + if (ActiveByRefsToStack.Size() > 0) + { + for (SIZE_T iByRef = ActiveByRefsToStack.Size() - 1; iByRef >= 0; iByRef--) + { + if (RelocAtAddress(&adjustment, ActiveByRefsToStack[iByRef])) + { + // ByRef was reloc'd, and therefore doesn't need to be handled anymore + ActiveByRefsToStack[iByRef] = ActiveByRefsToStack[ActiveByRefsToStack.Size() - 1]; + ActiveByRefsToStack.Pop(); + } + } + } + } +}; + +struct RuntimeSuspensionEnumData +{ + RuntimeSuspensionEnumData(const CQuickArrayList& restoreRegLocations, REGDISPLAY* pRD_) : + RestoreRegLocations(restoreRegLocations), + pRD(pRD_) + { + } + + CQuickArrayList ObjectRefOffsets; + CQuickArrayList ByRefOffsets; + const CQuickArrayList& RestoreRegLocations; + REGDISPLAY* pRD; + + uint32_t GetOffsetForReg(RegisterToRestore nonVolatileReg) + { + for (SIZE_T i = 0; i < RestoreRegLocations.Size(); i++) + { + if (RestoreRegLocations[i].reg == nonVolatileReg) + { + return RestoreRegLocations[i].offset; + } + } + _ASSERTE(!"Attempting to get offset for reg which doesn't have an offset recorded!"); + return 0; + } +}; + +StackWalkAction CaptureTaskletsCore(CrawlFrame* pCf, VOID* data) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACTL_END; + + + MethodDesc *pFunc = pCf->GetFunction(); + + /* We asked to be called back only for functions */ + _ASSERTE(pFunc); + + TaskletCaptureData* taskletCaptureData = (TaskletCaptureData*) data; + if (taskletCaptureData->firstTasklet == NULL) + { + if (!IsInCurrentFrame(pCf->GetRegisterSet(), taskletCaptureData->stackMark)) + { + // Move to next frame, we haven't found anything + return SWA_CONTINUE; + } + } + else if (!pCf->GetFunction()->IsAsync2Method()) + { + // We must be in the wrapper thunk + _ASSERTE(pCf->GetFunction()->IsAsyncThunkMethod()); + + if (taskletCaptureData->ActiveByRefsToStack.Size() != 0) + { + // We must have to deal with a return value managed as a byref + _ASSERTE(!"This is not yet implemented, the thought is to require that the return address be stored somewhere with a pointer to the start of the return value, and then scan the ActiveByRefsToStack to find the lowest address, and treat that as where to find the start of the return buffer. Or maybe we should record it somewhere?"); + } + return SWA_ABORT; + } + + + uint8_t* pTopOfStackInFunction = (uint8_t*)pCf->GetRegisterSet()->SP; + uint32_t sizeofArgStack = (uint32_t)pCf->GetFunction()->SizeOfArgStack(); + uint8_t* pBottomOfStackInFunction = (uint8_t*)::GetSP(pCf->GetRegisterSet()->pCallerContext) + sizeofArgStack; + uint32_t sizeofEntireMeaningfulStack = (uint32_t)(pBottomOfStackInFunction - pTopOfStackInFunction); + sizeofEntireMeaningfulStack -= (uint32_t)taskletCaptureData->stackToIgnoreFromPreviousFrame; + + uint8_t *pStackData = (uint8_t*)malloc(sizeofEntireMeaningfulStack); + memcpy(pStackData, pTopOfStackInFunction + taskletCaptureData->stackToIgnoreFromPreviousFrame, sizeofEntireMeaningfulStack); + + ByRefAdjustment byrefAdjustment; + byrefAdjustment.pOldLocation = pTopOfStackInFunction + taskletCaptureData->stackToIgnoreFromPreviousFrame; + byrefAdjustment.Adjustment = ((uintptr_t)pStackData) - ((uintptr_t)byrefAdjustment.pOldLocation); + byrefAdjustment.Size = sizeofEntireMeaningfulStack; + + StackDataInfo stackDataInfo; + stackDataInfo.StackRequirement = (uint32_t)(::GetSP(pCf->GetRegisterSet()->pCallerContext) - pCf->GetRegisterSet()->SP); + stackDataInfo.StackDataSize = sizeofEntireMeaningfulStack; + +#if defined(TARGET_X86) || defined(TARGET_AMD64) + uintptr_t returnAddressLocation = (uintptr_t) (EECodeManager::GetCallerSp(pCf->GetRegisterSet()) - sizeof(void*)); +#elif defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64) + uintptr_t returnAddressLocation = (uintptr_t) pCf->GetRegisterSet()->pCallerContextPointers->Ra; +#elif defined(TARGET_ARM) || defined(TARGET_ARM64) + uintptr_t returnAddressLocation = (uintptr_t) pCf->GetRegisterSet()->pCallerContextPointers->Lr; +#else + PORTABILITY_ASSERT("Platform NYI"); +#endif + stackDataInfo.ReturnAddressOffset = (uint32_t)(returnAddressLocation - (uintptr_t)pCf->GetRegisterSet()->SP); + + CQuickArrayList savedRegRestoreData; + +#define DISCOVER_RESTORED_REG(REGNAME) if (pCf->GetRegisterSet()->pCallerContextPointers->REGNAME != pCf->GetRegisterSet()->pCurrentContextPointers->REGNAME) { RegRestore restoreData; restoreData.reg = RegisterToRestore::REGNAME; restoreData.offset = (uint32_t)((uint8_t*)pCf->GetRegisterSet()->pCallerContextPointers->REGNAME - (uint8_t*)pCf->GetRegisterSet()->SP); savedRegRestoreData.Push(restoreData); } +#include "restoreregs_for_runtimesuspension.h" +#undef DISCOVER_RESTORED_REG + + RegRestore returnData; + returnData.reg = RegisterToRestore::ReturnRegisters; + returnData.offset = 0; + savedRegRestoreData.Push(returnData); + + + ICodeManager * pCM = pCf->GetCodeManager(); + _ASSERTE(pCM != NULL); + + unsigned flags = pCf->GetCodeManagerFlags(); + + GCEnumCallback enumGCRefs = []( + LPVOID hCallback, // callback data + OBJECTREF* pObject, // address of object-reference we are reporting + uint32_t flags ) + { + RuntimeSuspensionEnumData *pEnumData = (RuntimeSuspensionEnumData*)hCallback; + + // Determine if pObject points at a non-volatile register slot + uint32_t offset; + + if (false) {} +#define DISCOVER_RESTORED_REG(REGNAME) else if ((void*)pEnumData->pRD->pCallerContextPointers->REGNAME == (void*)pObject) { offset = pEnumData->GetOffsetForReg(RegisterToRestore::REGNAME); } +#include "restoreregs_for_runtimesuspension.h" +#undef DISCOVER_RESTORED_REG + else + { + // Must be within the stack frame itself + offset = (uint32_t)((uint8_t*)pObject - (uint8_t*)pEnumData->pRD->SP); + } + + bool isInterior = !!(flags & GC_CALL_INTERIOR); + bool isPinned = !!(flags & GC_CALL_PINNED); + _ASSERTE(!isPinned || isInterior); + + if (!isInterior) + { + pEnumData->ObjectRefOffsets.Push(offset); + } + else + { + int32_t byRefOffset = (int32_t)offset; + if (isPinned) + { + byRefOffset = -byRefOffset; + } + + pEnumData->ByRefOffsets.Push(offset); + } + }; + + RuntimeSuspensionEnumData enumData(savedRegRestoreData, pCf->GetRegisterSet()); + + pCM->EnumGcRefs(pCf->GetRegisterSet(), + pCf->GetCodeInfo(), + flags, + enumGCRefs, + &enumData, + NO_OVERRIDE_OFFSET); + + stackDataInfo.ByRefOffsets = (int32_t*)malloc(enumData.ByRefOffsets.Size() * sizeof(int32_t)); + memcpy(stackDataInfo.ByRefOffsets, enumData.ByRefOffsets.Ptr(), enumData.ByRefOffsets.Size() * sizeof(int32_t)); + stackDataInfo.ObjectRefOffsets = (uint32_t*)malloc(enumData.ObjectRefOffsets.Size() * sizeof(uint32_t)); + memcpy(stackDataInfo.ObjectRefOffsets, enumData.ObjectRefOffsets.Ptr(), enumData.ObjectRefOffsets.Size() * sizeof(uint32_t)); + stackDataInfo.RegistersToRestore = (RegRestore*)malloc(savedRegRestoreData.Size() * sizeof(RegRestore)); + memcpy(stackDataInfo.RegistersToRestore, savedRegRestoreData.Ptr(), savedRegRestoreData.Size() * sizeof(RegRestore)); + + StackDataInfo *pStackDataInfo = (StackDataInfo*)malloc(sizeof(StackDataInfo)); + *pStackDataInfo = stackDataInfo; + + MetaSig msig(pCf->GetFunction()); + ArgIterator argit(&msig); + TaskletReturnType taskletReturnType; +#if TARGET_AMD64 + if (argit.HasRetBuffArg()) + { + taskletReturnType = TaskletReturnType::ByReference; // In this case, on Windows AMD64 the abi specifies that the return value in RAX is the address of the ret buffer + } + else + { + TypeHandle thRet = msig.GetRetTypeHandleThrowing(); + CorElementType corElementType = thRet.GetInternalCorElementType(); + if (thRet.IsTypeDesc()) + { + taskletReturnType = TaskletReturnType::Integer; + } + else + { + if (thRet.AsMethodTable()->IsValueType() || thRet.AsMethodTable()->ContainsPointers()) + { + taskletReturnType = TaskletReturnType::ObjectReference; + } + else + { + // These asserts don't check all the FP ret cases, but they cover at least some of it + _ASSERTE(thRet.GetInternalCorElementType() != ELEMENT_TYPE_R4); + _ASSERTE(thRet.GetInternalCorElementType() != ELEMENT_TYPE_R8); + taskletReturnType = TaskletReturnType::Integer; + } + } + } +#else +PORTABILIT_ASSERT() +#endif + + Tasklet *pTasklet = (Tasklet*)malloc(sizeof(Tasklet)); + memset(pTasklet, 0, sizeof(Tasklet)); + pTasklet->pStackData = pStackData; + pTasklet->restoreIPAddress = (uintptr_t)GetControlPC(pCf->GetRegisterSet()); + pTasklet->pStackDataInfo; + pTasklet->taskletReturnType = taskletReturnType; + + if (taskletCaptureData->firstTasklet == NULL) + { + taskletCaptureData->firstTasklet = pTasklet; + taskletCaptureData->lastTasklet = pTasklet; + } + else + { + taskletCaptureData->lastTasklet->pTaskletNextInStack = pTasklet; + taskletCaptureData->lastTasklet = pTasklet; + } + + _ASSERTE(!"Needs to implement doing byref update of the data already captured.. maybe?"); + + return SWA_CONTINUE; +} + + +extern "C" Tasklet* QCALLTYPE RuntimeSuspension_CaptureTasklets(QCall::StackCrawlMarkHandle stackMark, uint8_t* returnValue, uint8_t useReturnValueHandle, void* taskAsyncData, Tasklet** lastTasklet) +{ + GCX_COOP(); + + TaskletCaptureData cdata; + cdata.stackMark = stackMark; + GetThread()->StackWalkFrames(CaptureTaskletsCore, &cdata, FUNCTIONSONLY); + + // We should have captured a full stack here. + *lastTasklet = cdata.lastTasklet; + _ASSERTE(FALSE); + return cdata.firstTasklet; +} + +extern "C" void QCALLTYPE RuntimeSuspension_DeleteTasklet(Tasklet* tasklet) +{ + // Well, leaking isn't a good plan in the long term, but for now it'll be fine +} + +EXTERN_C FCDECL1(void, RuntimeSuspension_UnwindToFunctionWithAsyncFrame, AsyncDataFrame* frame) +{ + _ASSERTE(!"Not Yet Implemented"); +} diff --git a/src/coreclr/vm/runtimesuspension.h b/src/coreclr/vm/runtimesuspension.h new file mode 100644 index 00000000000000..f48c583d33c44a --- /dev/null +++ b/src/coreclr/vm/runtimesuspension.h @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +struct Tasklet; +struct AsyncDataFrame; +struct RuntimeAsyncReturnValue; + +extern "C" Tasklet* QCALLTYPE RuntimeSuspension_CaptureTasklets(QCall::StackCrawlMarkHandle stackMark, uint8_t* returnValue, uint8_t useReturnValueHandle, void* taskAsyncData, Tasklet** lastTasklet); +extern "C" void QCALLTYPE RuntimeSuspension_DeleteTasklet(Tasklet* tasklet); + +EXTERN_C FCDECL1(void, RuntimeSuspension_UnwindToFunctionWithAsyncFrame, AsyncDataFrame* frame); + +EXTERN_C FCDECL2(Object*, RuntimeSuspension_ResumeTaskletReferenceReturn, Tasklet* tasklet, RuntimeAsyncReturnValue *resumptionReturnValue); +EXTERN_C FCDECL2(Object*, RuntimeSuspension_ResumeTaskletIntegerRegisterReturn, Tasklet* tasklet, RuntimeAsyncReturnValue *resumptionReturnValue); diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs index 2cb3b1bfa388dc..6b1fca886dae69 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs @@ -284,17 +284,17 @@ public RuntimeAsyncReturnValue(object? obj) { _obj = obj; _ptr = IntPtr.Zero; - _isObj = true; + _returnType = TaskletReturnType.ObjectReference; } public RuntimeAsyncReturnValue(IntPtr ptr) { _obj = null; _ptr = ptr; - _isObj = false; + _returnType = TaskletReturnType.Integer; } public IntPtr _ptr; public object? _obj; - public bool _isObj; + public TaskletReturnType _returnType; } internal abstract unsafe class RuntimeAsyncMaintainedData @@ -492,17 +492,23 @@ internal enum TaskletReturnType ObjectReference, ByReference } + + internal struct StackDataInfo + { + public int StackRequirement; + // And native has a bunch of other fields that managed does not use. + } internal unsafe struct Tasklet { public Tasklet* pTaskletNextInStack; public Tasklet* pTaskletNextInLiveList; public Tasklet* pTaskletPrevInLiveList; public byte* pStackData; - public byte* pStackDataInfo; - public int maxStackNeeded; + public IntPtr restoreIPAddress; + public StackDataInfo* pStackDataInfo; public TaskletReturnType taskletReturnType; - public int GetMaxStackNeeded() { return maxStackNeeded; } + public int GetMaxStackNeeded() { return pStackDataInfo->StackRequirement; } } internal static unsafe void PushAsyncData(ref AsyncDataFrame asyncData) From 0e82446313ec76fe9418a7952ef074534649be7e Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Mon, 9 Oct 2023 14:47:33 -0700 Subject: [PATCH 020/203] C++ portion builds --- src/coreclr/vm/array.cpp | 2 +- src/coreclr/vm/class.cpp | 2 +- src/coreclr/vm/dynamicmethod.cpp | 2 +- src/coreclr/vm/genmeth.cpp | 10 +++---- src/coreclr/vm/ilstubcache.cpp | 2 +- src/coreclr/vm/method.cpp | 36 +++++++++++----------- src/coreclr/vm/method.hpp | 43 ++++++++++++++++++--------- src/coreclr/vm/methodtablebuilder.cpp | 40 ++++++++++++++----------- src/coreclr/vm/methodtablebuilder.h | 40 ++++++++++--------------- 9 files changed, 94 insertions(+), 83 deletions(-) diff --git a/src/coreclr/vm/array.cpp b/src/coreclr/vm/array.cpp index 7a2bb145faadbe..d5a7de115517d1 100644 --- a/src/coreclr/vm/array.cpp +++ b/src/coreclr/vm/array.cpp @@ -500,7 +500,7 @@ MethodTable* Module::CreateArrayMethodTable(TypeHandle elemTypeHnd, CorElementTy + 3; // for rank specific Get, Set, Address MethodDescChunk * pChunks = MethodDescChunk::CreateChunk(pAllocator->GetHighFrequencyHeap(), - dwMethodDescs, mcArray, FALSE /* fNonVtableSlot*/, FALSE /* fNativeCodeSlot */, FALSE /* IsAsyncThunkMethod */, + dwMethodDescs, mcArray, FALSE /* fNonVtableSlot*/, FALSE /* fNativeCodeSlot */, FALSE /* HasAsyncMethodData */, pMT, pamTracker); pClass->SetChunks(pChunks); diff --git a/src/coreclr/vm/class.cpp b/src/coreclr/vm/class.cpp index cda2d4ebd18f87..c3337890683ef0 100644 --- a/src/coreclr/vm/class.cpp +++ b/src/coreclr/vm/class.cpp @@ -817,7 +817,7 @@ HRESULT EEClass::AddMethodDesc( pImport, NULL, Signature(), - AsyncThunkType::NotAThunk + AsyncMethodType::NotAsync COMMA_INDEBUG(debug_szMethodName) COMMA_INDEBUG(pMT->GetDebugClassName()) COMMA_INDEBUG(NULL) diff --git a/src/coreclr/vm/dynamicmethod.cpp b/src/coreclr/vm/dynamicmethod.cpp index a7499e0708fac8..290eadde211387 100644 --- a/src/coreclr/vm/dynamicmethod.cpp +++ b/src/coreclr/vm/dynamicmethod.cpp @@ -162,7 +162,7 @@ void DynamicMethodTable::AddMethodsToList() // allocate as many chunks as needed to hold the methods // MethodDescChunk* pChunk = MethodDescChunk::CreateChunk(pHeap, 0 /* one chunk of maximum size */, - mcDynamic, TRUE /* fNonVtableSlot */, TRUE /* fNativeCodeSlot */, FALSE /* IsAsyncThunkMethod */, m_pMethodTable, &amt); + mcDynamic, TRUE /* fNonVtableSlot */, TRUE /* fNativeCodeSlot */, FALSE /* HasAsyncMethodData */, m_pMethodTable, &amt); if (m_DynamicMethodList) RETURN; int methodCount = pChunk->GetCount(); diff --git a/src/coreclr/vm/genmeth.cpp b/src/coreclr/vm/genmeth.cpp index 021d9e48016595..8cb4eb34a75fc8 100644 --- a/src/coreclr/vm/genmeth.cpp +++ b/src/coreclr/vm/genmeth.cpp @@ -91,7 +91,7 @@ static MethodDesc* CreateMethodDesc(LoaderAllocator *pAllocator, classification, TRUE /* fNonVtableSlot*/, fNativeCodeSlot, - pTemplateMD->IsAsyncThunkMethod(), + pTemplateMD->HasAsyncMethodData(), pMT, pamTracker); @@ -118,9 +118,9 @@ static MethodDesc* CreateMethodDesc(LoaderAllocator *pAllocator, { pMD->SetIsIntrinsic(); } - if (pTemplateMD->IsAsyncThunkMethod()) + if (pTemplateMD->HasAsyncMethodData()) { - pMD->SetIsAsyncThunkMethod(); + pMD->SetHasAsyncMethodData(); } #ifdef FEATURE_METADATA_UPDATER @@ -133,9 +133,9 @@ static MethodDesc* CreateMethodDesc(LoaderAllocator *pAllocator, pMD->SetMemberDef(token); pMD->SetSlot(pTemplateMD->GetSlot()); - if (pTemplateMD->IsAsyncThunkMethod()) + if (pTemplateMD->HasAsyncMethodData()) { - *pMD->GetAddrOfAsyncThunkData() = pTemplateMD->GetAsyncThunkData(); + *pMD->GetAddrOfAsyncMethodData() = pTemplateMD->GetAsyncMethodData(); } #ifdef _DEBUG diff --git a/src/coreclr/vm/ilstubcache.cpp b/src/coreclr/vm/ilstubcache.cpp index c467281ba830f7..cb029595955016 100644 --- a/src/coreclr/vm/ilstubcache.cpp +++ b/src/coreclr/vm/ilstubcache.cpp @@ -185,7 +185,7 @@ MethodDesc* ILStubCache::CreateNewMethodDesc(LoaderHeap* pCreationHeap, MethodTa mcDynamic, TRUE /* fNonVtableSlot */, TRUE /* fNativeCodeSlot */, - FALSE /* IsAsyncThunkMethod */, + FALSE /* HasAsyncMethodData */, pMT, pamTracker); diff --git a/src/coreclr/vm/method.cpp b/src/coreclr/vm/method.cpp index f8d5bf993a1f02..803301dd19749b 100644 --- a/src/coreclr/vm/method.cpp +++ b/src/coreclr/vm/method.cpp @@ -91,14 +91,14 @@ const BYTE MethodDesc::s_ClassificationSizeTable[] = { METHOD_DESC_SIZES(sizeof(MethodImpl) + sizeof(NativeCodeSlot)), METHOD_DESC_SIZES(sizeof(NonVtableSlot) + sizeof(MethodImpl) + sizeof(NativeCodeSlot)), - METHOD_DESC_SIZES(sizeof(AsyncThunkData)), - METHOD_DESC_SIZES(sizeof(NonVtableSlot) + sizeof(AsyncThunkData)), - METHOD_DESC_SIZES(sizeof(MethodImpl) + sizeof(AsyncThunkData)), - METHOD_DESC_SIZES(sizeof(NonVtableSlot) + sizeof(MethodImpl) + sizeof(AsyncThunkData)), - METHOD_DESC_SIZES(sizeof(NativeCodeSlot) + sizeof(AsyncThunkData)), - METHOD_DESC_SIZES(sizeof(NonVtableSlot) + sizeof(NativeCodeSlot) + sizeof(AsyncThunkData)), - METHOD_DESC_SIZES(sizeof(MethodImpl) + sizeof(NativeCodeSlot) + sizeof(AsyncThunkData)), - METHOD_DESC_SIZES(sizeof(NonVtableSlot) + sizeof(MethodImpl) + sizeof(NativeCodeSlot) + sizeof(AsyncThunkData)), + METHOD_DESC_SIZES(sizeof(AsyncMethodData)), + METHOD_DESC_SIZES(sizeof(NonVtableSlot) + sizeof(AsyncMethodData)), + METHOD_DESC_SIZES(sizeof(MethodImpl) + sizeof(AsyncMethodData)), + METHOD_DESC_SIZES(sizeof(NonVtableSlot) + sizeof(MethodImpl) + sizeof(AsyncMethodData)), + METHOD_DESC_SIZES(sizeof(NativeCodeSlot) + sizeof(AsyncMethodData)), + METHOD_DESC_SIZES(sizeof(NonVtableSlot) + sizeof(NativeCodeSlot) + sizeof(AsyncMethodData)), + METHOD_DESC_SIZES(sizeof(MethodImpl) + sizeof(NativeCodeSlot) + sizeof(AsyncMethodData)), + METHOD_DESC_SIZES(sizeof(NonVtableSlot) + sizeof(MethodImpl) + sizeof(NativeCodeSlot) + sizeof(AsyncMethodData)), }; #ifndef FEATURE_COMINTEROP @@ -134,7 +134,7 @@ SIZE_T MethodDesc::SizeOf() | mdcHasNonVtableSlot | mdcMethodImpl | mdcHasNativeCodeSlot - | mdcIsAsyncThunkMethod)]; + | mdcHasAsyncMethodData)]; return size; } @@ -407,7 +407,7 @@ void MethodDesc::GetSig(PCCOR_SIGNATURE *ppSig, DWORD *pcSig) } if (IsAsyncThunkMethod()) { - Signature sig = GetAsyncThunkData().sig; + Signature sig = GetAsyncMethodData().sig; *ppSig = sig.GetRawSig(); *pcSig = sig.GetRawSigLen(); return; @@ -986,15 +986,15 @@ PTR_PCODE MethodDesc::GetAddrOfNativeCodeSlot() } //******************************************************************************* -AsyncThunkData* MethodDesc::GetAddrOfAsyncThunkData() +AsyncMethodData* MethodDesc::GetAddrOfAsyncMethodData() { WRAPPER_NO_CONTRACT; - _ASSERTE(IsAsyncThunkMethod()); + _ASSERTE(IsAsyncThunkMethod() || IsAsync2Method()); SIZE_T size = s_ClassificationSizeTable[m_wFlags & (mdcClassification | mdcHasNonVtableSlot | mdcMethodImpl | mdcHasNativeCodeSlot)]; - return (AsyncThunkData*)(dac_cast(this) + size); + return (AsyncMethodData*)(dac_cast(this) + size); } //******************************************************************************* @@ -1711,7 +1711,7 @@ MethodDesc* MethodDesc::StripMethodInstantiation() //******************************************************************************* MethodDescChunk *MethodDescChunk::CreateChunk(LoaderHeap *pHeap, DWORD methodDescCount, - DWORD classification, BOOL fNonVtableSlot, BOOL fNativeCodeSlot, BOOL fAsyncThunkData, MethodTable *pInitialMT, AllocMemTracker *pamTracker) + DWORD classification, BOOL fNonVtableSlot, BOOL fNativeCodeSlot, BOOL fAsyncMethodData, MethodTable *pInitialMT, AllocMemTracker *pamTracker) { CONTRACT(MethodDescChunk *) { @@ -1735,8 +1735,8 @@ MethodDescChunk *MethodDescChunk::CreateChunk(LoaderHeap *pHeap, DWORD methodDes if (fNativeCodeSlot) oneSize += sizeof(MethodDesc::NativeCodeSlot); - if (fAsyncThunkData) - oneSize += sizeof(AsyncThunkData); + if (fAsyncMethodData) + oneSize += sizeof(AsyncMethodData); _ASSERTE((oneSize & MethodDesc::ALIGNMENT_MASK) == 0); @@ -1778,8 +1778,8 @@ MethodDescChunk *MethodDescChunk::CreateChunk(LoaderHeap *pHeap, DWORD methodDes pMD->SetHasNonVtableSlot(); if (fNativeCodeSlot) pMD->SetHasNativeCodeSlot(); - if (fAsyncThunkData) - pMD->SetIsAsyncThunkMethod(); + if (fAsyncMethodData) + pMD->SetHasAsyncMethodData(); _ASSERTE(pMD->SizeOf() == oneSize); diff --git a/src/coreclr/vm/method.hpp b/src/coreclr/vm/method.hpp index d16182d8e54273..7fc41ad768568e 100644 --- a/src/coreclr/vm/method.hpp +++ b/src/coreclr/vm/method.hpp @@ -56,16 +56,17 @@ EXTERN_C VOID STDCALL NDirectImportThunk(); #define METHOD_TOKEN_RANGE_BIT_COUNT (24 - METHOD_TOKEN_REMAINDER_BIT_COUNT) #define METHOD_TOKEN_RANGE_MASK ((1 << METHOD_TOKEN_RANGE_BIT_COUNT) - 1) -enum class AsyncThunkType +enum class AsyncMethodType { - NotAThunk, + NotAsync, TaskToAsync, - AsyncToTask + AsyncToTask, + Async }; -struct AsyncThunkData +struct AsyncMethodData { - AsyncThunkType type; + AsyncMethodType type; Signature sig; }; @@ -152,8 +153,8 @@ enum MethodDescClassification // Has slot for native code mdcHasNativeCodeSlot = 0x0020, - // IsAsyncThunkMethod - mdcIsAsyncThunkMethod = 0x0040, + // HasAsyncMethodData + mdcHasAsyncMethodData = 0x0040, // Method is static mdcStatic = 0x0080, @@ -1405,8 +1406,8 @@ class MethodDesc BOOL SetNativeCodeInterlocked(PCODE addr, PCODE pExpected = NULL); PTR_PCODE GetAddrOfNativeCodeSlot(); - AsyncThunkData *GetAddrOfAsyncThunkData(); - const AsyncThunkData& GetAsyncThunkData() { _ASSERTE(IsAsyncThunkMethod()); return *GetAddrOfAsyncThunkData(); } + AsyncMethodData *GetAddrOfAsyncMethodData(); + const AsyncMethodData& GetAsyncMethodData() { _ASSERTE(IsAsyncThunkMethod()); return *GetAddrOfAsyncMethodData(); } BOOL MayHaveNativeCode(); @@ -1719,15 +1720,29 @@ class MethodDesc inline bool IsAsyncThunkMethod() { LIMITED_METHOD_DAC_CONTRACT; - return (m_wFlags & mdcIsAsyncThunkMethod) != 0; + if (!HasAsyncMethodData()) + return false; + auto asyncType = GetAddrOfAsyncMethodData()->type; + return asyncType == AsyncMethodType::AsyncToTask || asyncType == AsyncMethodType::TaskToAsync; } - bool IsAsync2Method(); + inline bool IsAsync2Method() + { + if (!HasAsyncMethodData()) + return false; + auto asyncType = GetAddrOfAsyncMethodData()->type; + return asyncType == AsyncMethodType::Async || asyncType == AsyncMethodType::AsyncToTask; + } + + inline bool HasAsyncMethodData() + { + return (m_wFlags & mdcHasAsyncMethodData) != 0; + } - inline void SetIsAsyncThunkMethod() + inline void SetHasAsyncMethodData() { LIMITED_METHOD_CONTRACT; - m_wFlags |= mdcIsAsyncThunkMethod; + m_wFlags |= mdcHasAsyncMethodData; } #ifdef FEATURE_METADATA_UPDATER @@ -2192,7 +2207,7 @@ class MethodDescChunk DWORD classification, BOOL fNonVtableSlot, BOOL fNativeCodeSlot, - BOOL fAsyncThunkData, + BOOL fAsyncMethodData, MethodTable *initialMT, class AllocMemTracker *pamTracker); diff --git a/src/coreclr/vm/methodtablebuilder.cpp b/src/coreclr/vm/methodtablebuilder.cpp index c29c2f2554daa4..bdffbf4cb19028 100644 --- a/src/coreclr/vm/methodtablebuilder.cpp +++ b/src/coreclr/vm/methodtablebuilder.cpp @@ -1030,7 +1030,7 @@ MethodTableBuilder::bmtMDMethod::bmtMDMethod( m_dwImplAttrs(dwImplAttrs), m_dwRVA(dwRVA), m_type(type), - m_asyncThunkType(AsyncThunkType::NotAThunk), + m_asyncMethodType(AsyncMethodType::NotAsync), m_implType(implType), m_methodSig(pOwningType->GetModule(), tok, @@ -1056,7 +1056,7 @@ MethodTableBuilder::bmtMDMethod::bmtMDMethod( DWORD dwImplAttrs, DWORD dwRVA, Signature sig, - AsyncThunkType thunkType, + AsyncMethodType thunkType, MethodClassification type, METHOD_IMPL_TYPE implType) : m_pOwningType(pOwningType), @@ -1064,7 +1064,7 @@ MethodTableBuilder::bmtMDMethod::bmtMDMethod( m_dwImplAttrs(dwImplAttrs), m_dwRVA(dwRVA), m_type(type), - m_asyncThunkType(thunkType), + m_asyncMethodType(thunkType), m_implType(implType), m_methodSig(pOwningType->GetModule(), tok, @@ -3547,12 +3547,18 @@ MethodTableBuilder::EnumerateClassMethods() dwMethodRVA, type, implType); + + if ((asyncMethodType == AsyncTaskMethod::Async2MethodThatCannotBeImplementedByTask) || (asyncMethodType == AsyncTaskMethod::Async2Method)) + { + pNewMethod->SetIsAsync2Method(); + } + pDeclaredMethod = pNewMethod; } else { ULONG cAsyncThunkMemberSignature = cMemberSignature; - AsyncThunkType thunkType; + AsyncMethodType thunkType; ULONG originalTokenOffsetFromAsyncDetailsOffset; ULONG newTokenOffsetFromAsyncDetailsOffset; ULONG originalPrefixSize; @@ -3565,7 +3571,7 @@ MethodTableBuilder::EnumerateClassMethods() cAsyncThunkMemberSignature += 2; originalTokenOffsetFromAsyncDetailsOffset = 1; newTokenOffsetFromAsyncDetailsOffset = 2; - thunkType = AsyncThunkType::AsyncToTask; + thunkType = AsyncMethodType::AsyncToTask; originalPrefixSize = 1; newPrefixSize = 2; originalSuffixSize = 0; @@ -3576,7 +3582,7 @@ MethodTableBuilder::EnumerateClassMethods() cAsyncThunkMemberSignature -= 2; originalTokenOffsetFromAsyncDetailsOffset = 2; newTokenOffsetFromAsyncDetailsOffset = 1; - thunkType = AsyncThunkType::TaskToAsync; + thunkType = AsyncMethodType::TaskToAsync; originalPrefixSize = 2; newPrefixSize = 1; originalSuffixSize = 1; @@ -5482,8 +5488,8 @@ MethodTableBuilder::InitNewMethodDesc( if (NeedsNativeCodeSlot(pMethod)) pNewMD->SetHasNativeCodeSlot(); - if (pMethod->GetAsyncThunkType() != AsyncThunkType::NotAThunk) - pNewMD->SetIsAsyncThunkMethod(); + if (pMethod->GetAsyncMethodType() != AsyncMethodType::NotAsync) + pNewMD->SetHasAsyncMethodData(); // Now we know the classification we can allocate the correct type of // method desc and perform any classification specific initialization. @@ -5513,7 +5519,7 @@ MethodTableBuilder::InitNewMethodDesc( #endif // _DEBUG Signature sig; - if (pMethod->GetAsyncThunkType() != AsyncThunkType::NotAThunk) + if (pMethod->IsAsyncThunk()) { sig = pMethod->GetMethodSignature().GetSignatureClass(); } @@ -5529,7 +5535,7 @@ MethodTableBuilder::InitNewMethodDesc( GetMDImport(), pName, sig, - pMethod->GetAsyncThunkType() + pMethod->GetAsyncMethodType() COMMA_INDEBUG(pszDebugMethodNameCopy) COMMA_INDEBUG(GetDebugClassName()) COMMA_INDEBUG("") // FIX this happens on global methods, give better info @@ -5936,7 +5942,7 @@ MethodTableBuilder::ProcessInexactMethodImpls() continue; } - AsyncVariantLookup asyncVariantOfDeclToFind = it->GetAsyncThunkType() == AsyncThunkType::NotAThunk ? AsyncVariantLookup::MatchingAsyncVariant : AsyncVariantLookup::AsyncOtherVariant; + AsyncVariantLookup asyncVariantOfDeclToFind = !it->IsAsyncThunk() ? AsyncVariantLookup::MatchingAsyncVariant : AsyncVariantLookup::AsyncOtherVariant; // If this method serves as the BODY of a MethodImpl specification, then // we should iterate all the MethodImpl's for this class and see just how many @@ -6079,7 +6085,7 @@ MethodTableBuilder::ProcessMethodImpls() continue; } - AsyncVariantLookup asyncVariantOfDeclToFind = it->GetAsyncThunkType() == AsyncThunkType::NotAThunk ? AsyncVariantLookup::MatchingAsyncVariant : AsyncVariantLookup::AsyncOtherVariant; + AsyncVariantLookup asyncVariantOfDeclToFind = !it->IsAsyncThunk() ? AsyncVariantLookup::MatchingAsyncVariant : AsyncVariantLookup::AsyncOtherVariant; // If this method serves as the BODY of a MethodImpl specification, then // we should iterate all the MethodImpl's for this class and see just how many @@ -6431,7 +6437,7 @@ MethodTableBuilder::InitMethodDesc( IMDInternalImport * pIMDII, // Needed for NDirect, EEImpl(Delegate) cases LPCSTR pMethodName, // Only needed for mcEEImpl (Delegate) case Signature sig, // Only needed for the Async2 Thunk case - AsyncThunkType thunkType + AsyncMethodType thunkType COMMA_INDEBUG(LPCUTF8 pszDebugMethodName) COMMA_INDEBUG(LPCUTF8 pszDebugClassName) COMMA_INDEBUG(LPCUTF8 pszDebugMethodSignature) @@ -6600,9 +6606,9 @@ MethodTableBuilder::InitMethodDesc( #endif // !_DEBUG pNewMD->SetSynchronized(); - if (thunkType != AsyncThunkType::NotAThunk) + if (thunkType != AsyncMethodType::NotAsync) { - AsyncThunkData* pThunkData = pNewMD->GetAddrOfAsyncThunkData(); + AsyncMethodData* pThunkData = pNewMD->GetAddrOfAsyncMethodData(); pThunkData->sig = sig; pThunkData->type = thunkType; } @@ -7342,8 +7348,8 @@ VOID MethodTableBuilder::AllocAndInitMethodDescs() if (NeedsNativeCodeSlot(*it)) size += sizeof(MethodDesc::NativeCodeSlot); - if (it->GetAsyncThunkType() != AsyncThunkType::NotAThunk) - size += sizeof(AsyncThunkData); + if (it->GetAsyncMethodType() != AsyncMethodType::NotAsync) + size += sizeof(AsyncMethodData); // See comment in AllocAndInitMethodDescChunk if (NeedsTightlyBoundUnboxingStub(*it)) diff --git a/src/coreclr/vm/methodtablebuilder.h b/src/coreclr/vm/methodtablebuilder.h index 4b34c5df88702c..4392e6417a980d 100644 --- a/src/coreclr/vm/methodtablebuilder.h +++ b/src/coreclr/vm/methodtablebuilder.h @@ -958,7 +958,7 @@ class MethodTableBuilder DWORD dwImplAttrs, DWORD dwRVA, Signature sig, - AsyncThunkType thunkType, + AsyncMethodType thunkType, MethodClassification type, METHOD_IMPL_TYPE implType); @@ -1062,10 +1062,20 @@ class MethodTableBuilder GetRVA() const { LIMITED_METHOD_CONTRACT; return m_dwRVA; } - AsyncThunkType GetAsyncThunkType() const + bool IsAsyncThunk() const + { + return GetAsyncMethodType() == AsyncMethodType::AsyncToTask || GetAsyncMethodType() == AsyncMethodType::TaskToAsync; + } + + void SetIsAsync2Method() + { + m_asyncMethodType = AsyncMethodType::Async; + } + + AsyncMethodType GetAsyncMethodType() const { LIMITED_METHOD_CONTRACT; - return m_asyncThunkType; + return m_asyncMethodType; } bmtMDMethod * GetAsyncOtherVariant() const { return m_asyncOtherVariant; } @@ -1079,7 +1089,7 @@ class MethodTableBuilder DWORD m_dwImplAttrs; DWORD m_dwRVA; MethodClassification m_type; // Specific MethodDesc flavour - AsyncThunkType m_asyncThunkType; + AsyncMethodType m_asyncMethodType; METHOD_IMPL_TYPE m_implType; // Whether or not the method is a methodImpl body MethodSignature m_methodSig; bmtMDMethod* m_asyncOtherVariant = NULL; @@ -1212,26 +1222,6 @@ class MethodTableBuilder MethodDesc * GetMethodDesc() const; -/* bmtMethodHandle GetAsyncOtherVariant() const - { - if (IsRTMethod()) - { - bmtRTMethod *pRTMethod = AsRTMethod(); - MethodDesc *pMD = pRTMethod->GetMethodDesc(); - MethodDesc *pRTOtherMethod = pMD->GetAsyncOtherVariant(); - _ASSERTE(pRTOtherMethod != NULL); - _ASSERTE(pRTOtherMethod->IsAsyncThunkMethod() || pRTMethod->GetMethodDesc()->IsAsyncThunkMethod()); - _ASSERTE(FALSE); - } - else - { - bmtMDMethod* pMDOtherVariant = AsMDMethod()->GetAsyncOtherVariant(); - _ASSERTE(pMDOtherVariant != NULL); - _ASSERTE(pMDOtherVariant->GetAsyncThunkType() != AsyncThunkType::NotAThunk || AsMDMethod()->GetAsyncThunkType() != AsyncThunkType::NotAThunk); - return pMDOtherVariant; - } - }*/ - protected: //----------------------------------------------------------------------------------------- static const UINT_PTR RTMETHOD_FLAG = 0x1; @@ -2700,7 +2690,7 @@ class MethodTableBuilder IMDInternalImport * pIMDII, // Needed for NDirect, EEImpl(Delegate) cases LPCSTR pMethodName, // Only needed for mcEEImpl (Delegate) case Signature sig, // Only needed for the async thunk (Async2 Thunk) case - AsyncThunkType thunkType + AsyncMethodType thunkType COMMA_INDEBUG(LPCUTF8 pszDebugMethodName) COMMA_INDEBUG(LPCUTF8 pszDebugClassName) COMMA_INDEBUG(LPCUTF8 pszDebugMethodSignature)); From 718aa39bab9c8ed205a68687b10054c01fb3cedc Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Wed, 11 Oct 2023 15:39:28 -0700 Subject: [PATCH 021/203] In theory, this should work? --- src/coreclr/vm/amd64/AsmHelpers.asm | 177 +++++++++++++++++- src/coreclr/vm/runtimesuspension.cpp | 37 ++-- .../CompilerServices/RuntimeHelpers.cs | 2 +- 3 files changed, 201 insertions(+), 15 deletions(-) diff --git a/src/coreclr/vm/amd64/AsmHelpers.asm b/src/coreclr/vm/amd64/AsmHelpers.asm index aa1c443cf56f1c..6abe642d1e7e1a 100644 --- a/src/coreclr/vm/amd64/AsmHelpers.asm +++ b/src/coreclr/vm/amd64/AsmHelpers.asm @@ -682,5 +682,180 @@ NESTED_END OnCallCountThresholdReachedStub, _TEXT endif ; FEATURE_TIERED_COMPILATION - end +extern PlatformIndependentRestore:proc + +regRestoreCase macro RegisterName +Check&RegisterName&: + mov edx, [rax+4] + mov RegisterName, [rcx+rdx] + add rax, 8 + jmp RegRestoreLoop + endm + +regRestoreCaseXmm macro RegisterName +Check&RegisterName&: + mov edx, [rax+4] + movdqu RegisterName, [rcx+rdx] + add rax, 8 + jmp RegRestoreLoop + endm +NESTED_ENTRY RuntimeSuspension_ResumeTaskletReferenceReturn, _TEXT + alloc_stack 48h + END_PROLOGUE + + lea r8, [rsp+28h] + + ; Zero out integerRegister and AddressInMethodToRestoreTo + ; Probably not needed in production code, but convenient for now + xor rax, rax + mov [r8], rax + mov [r8+8h], rax + + ; Fill in pFutureRSPLocation + lea rax, [rsp+50] + mov [r8+10h], rax + + ; Capture return address + mov rax, [rax] + mov [r8+18h], rax + + call PlatformIndependentRestore + ; RAX now points at the RegRestore array + mov rcx, [rsp+28h + 10h] + ; RCX now has the value of the future RSP + + .data +switchTable qword Checkrbx, Checkrbp, Checkrdi, Checkrsi, Checkr12, Checkr13, Checkr14, Checkr15, Checkxmm6, Checkxmm7, Checkxmm8, Checkxmm9, Checkxmm10, Checkxmm11, Checkxmm12, Checkxmm13, Checkxmm14, Checkxmm15, ReturnRegisters + .code +RegRestoreLoop: + mov edx, [rax] + ; edx now has the value of the reg field of the RegRestore structure + lea r8, [switchTable] + mov r8, [r8 + rdx*8] + jmp r8 +ReturnRegisters: + mov rax, [rsp+28h + 0h] + MOVDQU xmm0, [rsp+28h + 0h] + mov rdx, [rsp+28h + 0h] ; Load address of where to jmp into ecx + mov rsp, rcx + jmp rdx +regRestoreCase rbx +regRestoreCase rbp +regRestoreCase rdi +regRestoreCase rsi +regRestoreCase r12 +regRestoreCase r13 +regRestoreCase r14 +regRestoreCase r15 +regRestoreCaseXmm xmm6 +regRestoreCaseXmm xmm7 +regRestoreCaseXmm xmm8 +regRestoreCaseXmm xmm9 +regRestoreCaseXmm xmm10 +regRestoreCaseXmm xmm11 +regRestoreCaseXmm xmm12 +regRestoreCaseXmm xmm13 +regRestoreCaseXmm xmm14 +regRestoreCaseXmm xmm15 +NESTED_END RuntimeSuspension_ResumeTaskletReferenceReturn, _TEXT + +LEAF_ENTRY RuntimeSuspension_ResumeTaskletIntegerRegisterReturn, _TEXT + jmp RuntimeSuspension_ResumeTaskletReferenceReturn +LEAF_END RuntimeSuspension_ResumeTaskletIntegerRegisterReturn, _TEXT + +unwindRegCase macro RegisterName +Unwind&RegisterName&: + mov edx, [r8+4] + mov RegisterName, [rsp+rdx] + add r8, 8 + jmp RegUnwindLoop + endm + +unwindRegCaseXmm macro RegisterName +Unwind&RegisterName&: + mov edx, [r8+4] + movdqu RegisterName, [rsp+rdx] + add r8, 8 + jmp RegUnwindLoop + endm + +LEAF_ENTRY RuntimeSuspension_UnwindToFunctionWithAsyncFrame, _TEXT + +; // Psuedocode... +; Tasklet* pCurTasklet = topTasklet; +; uintptr_t curSP = SP + offset; +; uintptr_t ip = NULL; +; for (uint32_t i = 0; i < count; i++) +; { +; RegRestore* pRegRestore = pCurTasklet->pStackDataInfo->RegistersToRestore; +; while (pRegRestore->reg != RegisterToRestore::ReturnRegisters) +; { +; RestoreReg(pRegRestore); +; } +; ip = *(uintptr_t*)(curSP + pCurTasklet->pStackDataInfo->ReturnAddressOffset); +; curSP += pCurTasklet->pStackDataInfo->StackRequirement; +; } +; Jmp ip + + +; rcx is pCurTasklet +; rdx is count of tasklets to unwind +; rsp is current stack pointer +; rax is the putative new ip +; rax is also the scratch Register +; r10 is second scratch register +; r8 is pRegRestore +; r9 is pStackDataInfo + pop rax; Pop off saved IP, so that we start with the right SP set_frame +NewTaskletUnwind: + mov r9, [rcx+28h] ; Load r9 with the pStackDataInfo value on the current tasklet + mov r8, [r9+30h] ; Load r8 with the pRegRestore value + + .data +unwindSwitchTable qword Unwindrbx, Unwindrbp, Unwindrdi, Unwindrsi, Unwindr12, Unwindr13, Unwindr14, Unwindr15, Unwindxmm6, Unwindxmm7, Unwindxmm8, Unwindxmm9, Unwindxmm10, Unwindxmm11, Unwindxmm12, Unwindxmm13, Unwindxmm14, Unwindxmm15, UnwindReturnRegisters + .code +RegUnwindLoop: + mov eax, [r8] + ; eax now has the value of the reg field of the RegRestore structure + lea r10, [unwindSwitchTable] + mov r10, [r10 + rax*8] + jmp r10 +unwindRegCase rbx +unwindRegCase rbp +unwindRegCase rdi +unwindRegCase rsi +unwindRegCase r12 +unwindRegCase r13 +unwindRegCase r14 +unwindRegCase r15 +unwindRegCaseXmm xmm6 +unwindRegCaseXmm xmm7 +unwindRegCaseXmm xmm8 +unwindRegCaseXmm xmm9 +unwindRegCaseXmm xmm10 +unwindRegCaseXmm xmm11 +unwindRegCaseXmm xmm12 +unwindRegCaseXmm xmm13 +unwindRegCaseXmm xmm14 +unwindRegCaseXmm xmm15 +UnwindReturnRegisters: + ; Load return address into RAX in case we are done + mov eax, [r9 + 0ch] ; Put ReturnAddressOffset into rax + mov rax, [rsp+rax] + + ; Adjust rsp to callers rsp + mov r10, [r9] ; Load StackDataInfo.StackRequirement + sub r10, [r9 + 18h] ; Subtract off the CbArgs (stack args amount) + add rsp, r10 ; Adjust rsp as requested + + mov rcx, [rcx] ; Adjust to callers tasklet + ; Adjust count of tasklets to unwind + sub edx, 1 + jnz NewTaskletUnwind +TaskletUnwindDone: + mov rcx, rax ; Move return address into rcx + xor rax, rax ; Clear return register in case it is supposed to be an object reference or something -- TODO, note that for structure returns this will mean we need to slightly adjust the calling convention so that we don't rely on this being a pointer to the return buffer + jmp rcx +LEAF_END RuntimeSuspension_UnwindToFunctionWithAsyncFrame, _TEXT + end \ No newline at end of file diff --git a/src/coreclr/vm/runtimesuspension.cpp b/src/coreclr/vm/runtimesuspension.cpp index fdfc254368e348..61f626bd236e80 100644 --- a/src/coreclr/vm/runtimesuspension.cpp +++ b/src/coreclr/vm/runtimesuspension.cpp @@ -57,10 +57,11 @@ struct StackDataInfo uint32_t UnrecordedDataSize; // From the restored RSP to the data chunk, how many bytes are skipped uint32_t StackDataSize; uint32_t ReturnAddressOffset; - int32_t* ByRefOffsets = NULL; // Negative numbers indicate pinned byrefs uint32_t cByRefs; - uint32_t* ObjectRefOffsets = NULL; uint32_t cObjectRefs; + uint32_t CbArgs; + int32_t* ByRefOffsets = NULL; // Negative numbers indicate pinned byrefs + uint32_t* ObjectRefOffsets = NULL; RegRestore* RegistersToRestore = NULL; }; @@ -123,7 +124,7 @@ struct RestoreFunctionLocals }; // The actual restoration function which needs to be written in assembly, sets up enough of a frame on top of the current RSP to call this function -// Then, once this function returns, iterates through the +// Then, once this function returns, iterates through the RegRestore to change over the saved registers to the new values extern "C" RegRestore* PlatformIndependentRestore(Tasklet* tasklet, RuntimeAsyncReturnValue* returnValueToFillIn, RestoreFunctionLocals *restoreLocals) { // Compute what needs to be adjusted in terms of byrefs for @@ -248,9 +249,13 @@ struct TaskletCaptureData struct RuntimeSuspensionEnumData { - RuntimeSuspensionEnumData(const CQuickArrayList& restoreRegLocations, REGDISPLAY* pRD_) : + RuntimeSuspensionEnumData(const CQuickArrayList& restoreRegLocations, REGDISPLAY* pRD_, TaskletCaptureData *taskletCaptureData, CrawlFrame *_pCf, StackDataInfo* _pStackDataInfo, uint8_t* _pStackData) : RestoreRegLocations(restoreRegLocations), - pRD(pRD_) + pRD(pRD_), + pTaskletCaptureData(taskletCaptureData), + pCf(_pCf), + pStackDataInfo(_pStackDataInfo), + pStackData(_pStackData) { } @@ -258,6 +263,10 @@ struct RuntimeSuspensionEnumData CQuickArrayList ByRefOffsets; const CQuickArrayList& RestoreRegLocations; REGDISPLAY* pRD; + TaskletCaptureData *pTaskletCaptureData; + CrawlFrame *pCf; + StackDataInfo* pStackDataInfo; + uint8_t* pStackData; uint32_t GetOffsetForReg(RegisterToRestore nonVolatileReg) { @@ -328,8 +337,9 @@ StackWalkAction CaptureTaskletsCore(CrawlFrame* pCf, VOID* data) byrefAdjustment.Size = sizeofEntireMeaningfulStack; StackDataInfo stackDataInfo; - stackDataInfo.StackRequirement = (uint32_t)(::GetSP(pCf->GetRegisterSet()->pCallerContext) - pCf->GetRegisterSet()->SP); + stackDataInfo.StackRequirement = (uint32_t)(::GetSP(pCf->GetRegisterSet()->pCallerContext) - pCf->GetRegisterSet()->SP) + sizeofArgStack; stackDataInfo.StackDataSize = sizeofEntireMeaningfulStack; + stackDataInfo.UnrecordedDataSize = (uint32_t)taskletCaptureData->stackToIgnoreFromPreviousFrame #if defined(TARGET_X86) || defined(TARGET_AMD64) uintptr_t returnAddressLocation = (uintptr_t) (EECodeManager::GetCallerSp(pCf->GetRegisterSet()) - sizeof(void*)); @@ -390,6 +400,8 @@ StackWalkAction CaptureTaskletsCore(CrawlFrame* pCf, VOID* data) else { int32_t byRefOffset = (int32_t)offset; + + pEnumData->pTaskletCaptureData->AddCopiedByRef(pEnumData->pCf->GetRegisterSet()->SP, (uintptr_t*)(pEnumData->pStackData + offset - pEnumData->pStackDataInfo->UnrecordedDataSize)); if (isPinned) { byRefOffset = -byRefOffset; @@ -399,7 +411,7 @@ StackWalkAction CaptureTaskletsCore(CrawlFrame* pCf, VOID* data) } }; - RuntimeSuspensionEnumData enumData(savedRegRestoreData, pCf->GetRegisterSet()); + RuntimeSuspensionEnumData enumData(savedRegRestoreData, pCf->GetRegisterSet(), taskletCaptureData, pCf, &stackDataInfo, pStackData); pCM->EnumGcRefs(pCf->GetRegisterSet(), pCf->GetCodeInfo(), @@ -453,6 +465,8 @@ StackWalkAction CaptureTaskletsCore(CrawlFrame* pCf, VOID* data) PORTABILIT_ASSERT() #endif + + Tasklet *pTasklet = (Tasklet*)malloc(sizeof(Tasklet)); memset(pTasklet, 0, sizeof(Tasklet)); pTasklet->pStackData = pStackData; @@ -471,7 +485,8 @@ PORTABILIT_ASSERT() taskletCaptureData->lastTasklet = pTasklet; } - _ASSERTE(!"Needs to implement doing byref update of the data already captured.. maybe?"); + // Apply byrefs to newly allocated stuff + taskletCaptureData->ApplyByRefRelocsToNewlyAllocatedTasklet(byrefAdjustment); return SWA_CONTINUE; } @@ -483,6 +498,7 @@ extern "C" Tasklet* QCALLTYPE RuntimeSuspension_CaptureTasklets(QCall::StackCraw TaskletCaptureData cdata; cdata.stackMark = stackMark; + cdata.stackLimit = (uintptr_t)taskAsyncData; GetThread()->StackWalkFrames(CaptureTaskletsCore, &cdata, FUNCTIONSONLY); // We should have captured a full stack here. @@ -495,8 +511,3 @@ extern "C" void QCALLTYPE RuntimeSuspension_DeleteTasklet(Tasklet* tasklet) { // Well, leaking isn't a good plan in the long term, but for now it'll be fine } - -EXTERN_C FCDECL1(void, RuntimeSuspension_UnwindToFunctionWithAsyncFrame, AsyncDataFrame* frame) -{ - _ASSERTE(!"Not Yet Implemented"); -} diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs index 6b1fca886dae69..ea4def03effee9 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs @@ -472,10 +472,10 @@ public AsyncDataFrame(Func getMaintainedData) _createRuntimeMaintainedData = getMaintainedData; } + public RuntimeAsyncMaintainedData? _maintainedData; public StackCrawlMark _crawlMark; public void* _next; public Func? _createRuntimeMaintainedData; - public RuntimeAsyncMaintainedData? _maintainedData; public Thread _currentThread = Thread.CurrentThread; // Store current ExecutionContext and SynchronizationContext as "previousXxx". From 6d1e7b7a49cfba8594581d63bd4c3f6d8bdf314a Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Thu, 12 Oct 2023 11:29:45 -0700 Subject: [PATCH 022/203] native part builds --- src/coreclr/vm/runtimesuspension.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/vm/runtimesuspension.cpp b/src/coreclr/vm/runtimesuspension.cpp index 61f626bd236e80..2d910e512057d2 100644 --- a/src/coreclr/vm/runtimesuspension.cpp +++ b/src/coreclr/vm/runtimesuspension.cpp @@ -339,7 +339,7 @@ StackWalkAction CaptureTaskletsCore(CrawlFrame* pCf, VOID* data) StackDataInfo stackDataInfo; stackDataInfo.StackRequirement = (uint32_t)(::GetSP(pCf->GetRegisterSet()->pCallerContext) - pCf->GetRegisterSet()->SP) + sizeofArgStack; stackDataInfo.StackDataSize = sizeofEntireMeaningfulStack; - stackDataInfo.UnrecordedDataSize = (uint32_t)taskletCaptureData->stackToIgnoreFromPreviousFrame + stackDataInfo.UnrecordedDataSize = (uint32_t)taskletCaptureData->stackToIgnoreFromPreviousFrame; #if defined(TARGET_X86) || defined(TARGET_AMD64) uintptr_t returnAddressLocation = (uintptr_t) (EECodeManager::GetCallerSp(pCf->GetRegisterSet()) - sizeof(void*)); From d20bb17a71a1d90dfda093f1b60242dade31e8fd Mon Sep 17 00:00:00 2001 From: Vladimir Sadov Date: Thu, 12 Oct 2023 18:29:29 +0000 Subject: [PATCH 023/203] Merged PR 34323: make things build make things build --- .../src/CompatibilitySuppressions.xml | 40 +++++++++++++++++++ .../CompilerServices/RuntimeHelpers.cs | 3 ++ .../src/System/Threading/Tasks/Future.cs | 3 ++ 3 files changed, 46 insertions(+) diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml b/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml index d3fbd808a6d691..376c3ecb1ee8d7 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml @@ -1,6 +1,10 @@  + + CP0001 + T:Internal.Console + CP0001 T:Internal.DeveloperExperience.DeveloperExperience @@ -869,6 +873,10 @@ CP0001 T:System.Runtime.CompilerServices.ForceLazyDictionaryAttribute + + CP0001 + T:System.Runtime.CompilerServices.ICastable + CP0001 T:System.Runtime.CompilerServices.StaticClassConstructionContext @@ -929,6 +937,14 @@ CP0001 T:System.Threading.LockHolder + + CP0002 + F:System.Resources.ResourceManager.BaseNameField + + + CP0002 + F:System.Resources.ResourceSet.Reader + CP0002 M:System.ModuleHandle.#ctor(System.Reflection.Module) @@ -945,6 +961,30 @@ CP0002 M:System.TypedReference.get_IsNull + + CP0002 + M:System.Runtime.CompilerServices.RuntimeHelpers.AwaitAwaiterFromRuntimeAsync``1(``0) + ref/net9.0/System.Private.CoreLib.dll + lib/net9.0/System.Private.CoreLib.dll + + + CP0002 + M:System.Runtime.CompilerServices.RuntimeHelpers.AwaitAwaiterFromRuntimeAsync``2(``1) + ref/net9.0/System.Private.CoreLib.dll + lib/net9.0/System.Private.CoreLib.dll + + + CP0002 + M:System.Runtime.CompilerServices.RuntimeHelpers.UnsafeAwaitAwaiterFromRuntimeAsync``1(``0) + ref/net9.0/System.Private.CoreLib.dll + lib/net9.0/System.Private.CoreLib.dll + + + CP0002 + M:System.Runtime.CompilerServices.RuntimeHelpers.UnsafeAwaitAwaiterFromRuntimeAsync``2(``1) + ref/net9.0/System.Private.CoreLib.dll + lib/net9.0/System.Private.CoreLib.dll + CP0014 M:System.Runtime.CompilerServices.RuntimeHelpers.GetObjectValue(System.Object)->object?:[T:System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute] diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs index ea4def03effee9..e8f89f00833555 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs @@ -118,6 +118,8 @@ internal static bool IsPrimitiveType(this CorElementType et) internal static bool IsKnownConstant(int t) => false; #pragma warning restore IDE0060 +#if !NATIVEAOT + // TODO, this method should be marked so that it is only callable from a runtime async method public static TResult UnsafeAwaitAwaiterFromRuntimeAsync(TAwaiter awaiter) where TAwaiter : ICriticalNotifyCompletion2 { @@ -623,5 +625,6 @@ private static unsafe void AbortSuspend() maintainedData._oldTaskletNext = null; maintainedData._suspendActive = false; } +#endif } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Future.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Future.cs index 69f8bb93534906..394609486f3c3c 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Future.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Future.cs @@ -15,6 +15,8 @@ namespace System.Threading.Tasks { + +#if !NATIVEAOT #pragma warning disable CA1822 // Used to implement Runtime implemented Task suspension handling internal struct RuntimeTaskState @@ -38,6 +40,7 @@ public Task FromResult(TResult result) public Task FromException(Exception e) { return Task.FromException(e); } } #pragma warning restore CA1822 +#endif /// /// Represents an asynchronous operation that produces a result at some time in the future. From aee9b9f17c335c347ed9f557212763f348ce4fe2 Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Fri, 13 Oct 2023 14:28:54 -0700 Subject: [PATCH 024/203] It successfully captures... and unwind doesn't quite work --- src/coreclr/inc/eetwain.h | 1 + src/coreclr/vm/eetwain.cpp | 12 ++- src/coreclr/vm/gcinfodecoder.cpp | 2 +- src/coreclr/vm/method.cpp | 2 +- src/coreclr/vm/methodtablebuilder.cpp | 4 +- src/coreclr/vm/runtimesuspension.cpp | 35 +++++-- src/coreclr/vm/runtimesuspension.h | 2 +- .../CompilerServices/RuntimeHelpers.cs | 97 +++++++++++++------ .../src/System/Threading/Tasks/Future.cs | 2 +- .../runtimetask-asyncfibonacci-with-yields.il | 18 +++- ...timetask-asyncfibonacci-with-yields.ilproj | 2 +- 11 files changed, 125 insertions(+), 52 deletions(-) diff --git a/src/coreclr/inc/eetwain.h b/src/coreclr/inc/eetwain.h index 9beca3f3729007..be07459dc6e3fe 100644 --- a/src/coreclr/inc/eetwain.h +++ b/src/coreclr/inc/eetwain.h @@ -103,6 +103,7 @@ enum ICodeManagerFlags NoReportUntracked = 0x0080, // EnumGCRefs/EnumerateLiveSlots should *not* include // any untracked slots + NoGcDecoderValidation = 0x0100, // Turn off GCDecoder validation }; //***************************************************************************** diff --git a/src/coreclr/vm/eetwain.cpp b/src/coreclr/vm/eetwain.cpp index b0886fcadebee4..cd3a21434bcf08 100644 --- a/src/coreclr/vm/eetwain.cpp +++ b/src/coreclr/vm/eetwain.cpp @@ -5327,9 +5327,19 @@ bool EECodeManager::EnumGcRefs( PREGDISPLAY pRD, reportScratchSlots = (flags & ActiveStackFrame) != 0; + GcInfoDecoderFlags decoderFlags; + if (flags & NoGcDecoderValidation) + { + decoderFlags = GcInfoDecoderFlags (DECODE_GC_LIFETIMES | DECODE_SECURITY_OBJECT | DECODE_VARARG | DECODE_NO_VALIDATION); + } + else + { + decoderFlags = GcInfoDecoderFlags (DECODE_GC_LIFETIMES | DECODE_SECURITY_OBJECT | DECODE_VARARG); + } + GcInfoDecoder gcInfoDecoder( gcInfoToken, - GcInfoDecoderFlags (DECODE_GC_LIFETIMES | DECODE_SECURITY_OBJECT | DECODE_VARARG), + decoderFlags, curOffs ); diff --git a/src/coreclr/vm/gcinfodecoder.cpp b/src/coreclr/vm/gcinfodecoder.cpp index d738f8fb67a909..8ea647e376074e 100644 --- a/src/coreclr/vm/gcinfodecoder.cpp +++ b/src/coreclr/vm/gcinfodecoder.cpp @@ -55,7 +55,7 @@ #ifndef LOG_PIPTR #define LOG_PIPTR(pObjRef, gcFlags, hCallBack) \ - { \ + if (!(m_Flags & DECODE_NO_VALIDATION)) { \ GCCONTEXT* pGCCtx = (GCCONTEXT*)(hCallBack); \ if (pGCCtx->sc->promotion) \ { \ diff --git a/src/coreclr/vm/method.cpp b/src/coreclr/vm/method.cpp index 803301dd19749b..3935c0d5c842ed 100644 --- a/src/coreclr/vm/method.cpp +++ b/src/coreclr/vm/method.cpp @@ -990,7 +990,7 @@ AsyncMethodData* MethodDesc::GetAddrOfAsyncMethodData() { WRAPPER_NO_CONTRACT; - _ASSERTE(IsAsyncThunkMethod() || IsAsync2Method()); + _ASSERTE(HasAsyncMethodData()); SIZE_T size = s_ClassificationSizeTable[m_wFlags & (mdcClassification | mdcHasNonVtableSlot | mdcMethodImpl | mdcHasNativeCodeSlot)]; diff --git a/src/coreclr/vm/methodtablebuilder.cpp b/src/coreclr/vm/methodtablebuilder.cpp index bdffbf4cb19028..7419681a103dea 100644 --- a/src/coreclr/vm/methodtablebuilder.cpp +++ b/src/coreclr/vm/methodtablebuilder.cpp @@ -3571,7 +3571,7 @@ MethodTableBuilder::EnumerateClassMethods() cAsyncThunkMemberSignature += 2; originalTokenOffsetFromAsyncDetailsOffset = 1; newTokenOffsetFromAsyncDetailsOffset = 2; - thunkType = AsyncMethodType::AsyncToTask; + thunkType = AsyncMethodType::TaskToAsync; originalPrefixSize = 1; newPrefixSize = 2; originalSuffixSize = 0; @@ -3582,7 +3582,7 @@ MethodTableBuilder::EnumerateClassMethods() cAsyncThunkMemberSignature -= 2; originalTokenOffsetFromAsyncDetailsOffset = 2; newTokenOffsetFromAsyncDetailsOffset = 1; - thunkType = AsyncMethodType::TaskToAsync; + thunkType = AsyncMethodType::AsyncToTask; originalPrefixSize = 2; newPrefixSize = 1; originalSuffixSize = 1; diff --git a/src/coreclr/vm/runtimesuspension.cpp b/src/coreclr/vm/runtimesuspension.cpp index 2d910e512057d2..54dbf000abb829 100644 --- a/src/coreclr/vm/runtimesuspension.cpp +++ b/src/coreclr/vm/runtimesuspension.cpp @@ -215,6 +215,7 @@ tbl dq ReturnRegisters, CheckEBX, CheckESI, ... ;table struct TaskletCaptureData { bool inRunOfAsyncMethods; + int framesCaptured = 0; StackCrawlMark* stackMark; Tasklet* firstTasklet = NULL; Tasklet* lastTasklet = NULL; @@ -226,7 +227,8 @@ struct TaskletCaptureData { if ((*newByRef >= currentStackTop) && (*newByRef < stackLimit)) { - ActiveByRefsToStack.Push(newByRef); + ActiveByRefsToStack.PushNoThrow(newByRef); + // TODO PushNoThrow has a return value } } @@ -287,7 +289,7 @@ StackWalkAction CaptureTaskletsCore(CrawlFrame* pCf, VOID* data) CONTRACTL { THROWS; - GC_TRIGGERS; +; GC_NOTRIGGER; MODE_COOPERATIVE; INJECT_FAULT(COMPlusThrowOM();); } @@ -380,7 +382,7 @@ StackWalkAction CaptureTaskletsCore(CrawlFrame* pCf, VOID* data) uint32_t offset; if (false) {} -#define DISCOVER_RESTORED_REG(REGNAME) else if ((void*)pEnumData->pRD->pCallerContextPointers->REGNAME == (void*)pObject) { offset = pEnumData->GetOffsetForReg(RegisterToRestore::REGNAME); } +#define DISCOVER_RESTORED_REG(REGNAME) else if ((void*)pEnumData->pRD->pCurrentContextPointers->REGNAME == (void*)pObject) { offset = pEnumData->GetOffsetForReg(RegisterToRestore::REGNAME); } #include "restoreregs_for_runtimesuspension.h" #undef DISCOVER_RESTORED_REG else @@ -395,7 +397,8 @@ StackWalkAction CaptureTaskletsCore(CrawlFrame* pCf, VOID* data) if (!isInterior) { - pEnumData->ObjectRefOffsets.Push(offset); + pEnumData->ObjectRefOffsets.PushNoThrow(offset); + // TODO PushNoThrow has a return value } else { @@ -407,7 +410,8 @@ StackWalkAction CaptureTaskletsCore(CrawlFrame* pCf, VOID* data) byRefOffset = -byRefOffset; } - pEnumData->ByRefOffsets.Push(offset); + pEnumData->ByRefOffsets.PushNoThrow(offset); + // TODO PushNoThrow has a return value } }; @@ -415,7 +419,7 @@ StackWalkAction CaptureTaskletsCore(CrawlFrame* pCf, VOID* data) pCM->EnumGcRefs(pCf->GetRegisterSet(), pCf->GetCodeInfo(), - flags, + flags | NoGcDecoderValidation, enumGCRefs, &enumData, NO_OVERRIDE_OFFSET); @@ -427,6 +431,10 @@ StackWalkAction CaptureTaskletsCore(CrawlFrame* pCf, VOID* data) stackDataInfo.RegistersToRestore = (RegRestore*)malloc(savedRegRestoreData.Size() * sizeof(RegRestore)); memcpy(stackDataInfo.RegistersToRestore, savedRegRestoreData.Ptr(), savedRegRestoreData.Size() * sizeof(RegRestore)); + stackDataInfo.cByRefs = (uint32_t)enumData.ByRefOffsets.Size(); + stackDataInfo.cObjectRefs = (uint32_t)enumData.ObjectRefOffsets.Size(); + stackDataInfo.CbArgs = sizeofArgStack; + StackDataInfo *pStackDataInfo = (StackDataInfo*)malloc(sizeof(StackDataInfo)); *pStackDataInfo = stackDataInfo; @@ -448,7 +456,7 @@ StackWalkAction CaptureTaskletsCore(CrawlFrame* pCf, VOID* data) } else { - if (thRet.AsMethodTable()->IsValueType() || thRet.AsMethodTable()->ContainsPointers()) + if (thRet.AsMethodTable()->IsValueType() && thRet.AsMethodTable()->ContainsPointers()) { taskletReturnType = TaskletReturnType::ObjectReference; } @@ -471,7 +479,7 @@ PORTABILIT_ASSERT() memset(pTasklet, 0, sizeof(Tasklet)); pTasklet->pStackData = pStackData; pTasklet->restoreIPAddress = (uintptr_t)GetControlPC(pCf->GetRegisterSet()); - pTasklet->pStackDataInfo; + pTasklet->pStackDataInfo = pStackDataInfo; pTasklet->taskletReturnType = taskletReturnType; if (taskletCaptureData->firstTasklet == NULL) @@ -484,6 +492,7 @@ PORTABILIT_ASSERT() taskletCaptureData->lastTasklet->pTaskletNextInStack = pTasklet; taskletCaptureData->lastTasklet = pTasklet; } + taskletCaptureData->framesCaptured++; // Apply byrefs to newly allocated stuff taskletCaptureData->ApplyByRefRelocsToNewlyAllocatedTasklet(byrefAdjustment); @@ -492,10 +501,15 @@ PORTABILIT_ASSERT() } -extern "C" Tasklet* QCALLTYPE RuntimeSuspension_CaptureTasklets(QCall::StackCrawlMarkHandle stackMark, uint8_t* returnValue, uint8_t useReturnValueHandle, void* taskAsyncData, Tasklet** lastTasklet) +extern "C" Tasklet* QCALLTYPE RuntimeSuspension_CaptureTasklets(QCall::StackCrawlMarkHandle stackMark, uint8_t* returnValue, uint8_t useReturnValueHandle, void* taskAsyncData, Tasklet** lastTasklet, int32_t* pFramesCaptured) { GCX_COOP(); + BEGINFORBIDGC(); +#ifdef _DEBUG + GCForbidLoaderUseHolder forbidLoaderUse; +#endif + TaskletCaptureData cdata; cdata.stackMark = stackMark; cdata.stackLimit = (uintptr_t)taskAsyncData; @@ -503,7 +517,8 @@ extern "C" Tasklet* QCALLTYPE RuntimeSuspension_CaptureTasklets(QCall::StackCraw // We should have captured a full stack here. *lastTasklet = cdata.lastTasklet; - _ASSERTE(FALSE); + *pFramesCaptured = cdata.framesCaptured; + ENDFORBIDGC(); return cdata.firstTasklet; } diff --git a/src/coreclr/vm/runtimesuspension.h b/src/coreclr/vm/runtimesuspension.h index f48c583d33c44a..d06e7a149d7720 100644 --- a/src/coreclr/vm/runtimesuspension.h +++ b/src/coreclr/vm/runtimesuspension.h @@ -5,7 +5,7 @@ struct Tasklet; struct AsyncDataFrame; struct RuntimeAsyncReturnValue; -extern "C" Tasklet* QCALLTYPE RuntimeSuspension_CaptureTasklets(QCall::StackCrawlMarkHandle stackMark, uint8_t* returnValue, uint8_t useReturnValueHandle, void* taskAsyncData, Tasklet** lastTasklet); +extern "C" Tasklet* QCALLTYPE RuntimeSuspension_CaptureTasklets(QCall::StackCrawlMarkHandle stackMark, uint8_t* returnValue, uint8_t useReturnValueHandle, void* taskAsyncData, Tasklet** lastTasklet, int32_t* pFramesCaptured); extern "C" void QCALLTYPE RuntimeSuspension_DeleteTasklet(Tasklet* tasklet); EXTERN_C FCDECL1(void, RuntimeSuspension_UnwindToFunctionWithAsyncFrame, AsyncDataFrame* frame); diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs index e8f89f00833555..9c504bfa6cdd92 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs @@ -125,11 +125,13 @@ public static TResult UnsafeAwaitAwaiterFromRuntimeAsync(TAwa { if (!awaiter.IsCompleted) { + StackCrawlMark stackMark = StackCrawlMark.LookForMe; + // Create resumption delegate, wrapping task, and create tasklets to represent each stack frame on the stack. // RuntimeTaskSuspender.GetOrCreateResumptionDelegate() works like a POSIX fork call in that calls to it will return a // delegate if they are the initial call to GetOrCreateResumptionDelegate, but once the thread is resumed, // it will resume with a return value of null. - Action? resumption = RuntimeHelpers.GetOrCreateResumptionDelegate(); + Action? resumption = RuntimeHelpers.GetOrCreateResumptionDelegate(ref stackMark); if (resumption != null) { // We are trying to suspend @@ -151,7 +153,17 @@ public static TResult UnsafeAwaitAwaiterFromRuntimeAsync(TAwa } // If we reach here, the only way that we actually run follow on code is for the continuation to actually run, // and return from GetOrCreateResumptionDelegate with a null return value. - RuntimeHelpers.SuspendIfSuspensionNotAborted(); + ref AsyncDataFrame asyncFrame = ref GetCurrentAsyncDataFrame(); + RuntimeAsyncMaintainedData maintainedData = asyncFrame._maintainedData!; + if (maintainedData._abortSuspend) + { + AbortSuspend(); + } + else + { + // This function must be called from the same function that has the stackmark in it. + unsafe { UnwindToFunctionWithAsyncFrame(maintainedData._nextTasklet, maintainedData._suspendActive); } + } } } @@ -164,11 +176,13 @@ public static TResult AwaitAwaiterFromRuntimeAsync(TAwaiter a { if (!awaiter.IsCompleted) { + StackCrawlMark stackMark = StackCrawlMark.LookForMe; + // Create resumption delegate, wrapping task, and create tasklets to represent each stack frame on the stack. // RuntimeTaskSuspender.GetOrCreateResumptionDelegate() works like a POSIX fork call in that calls to it will return a // delegate if they are the initial call to GetOrCreateResumptionDelegate, but once the thread is resumed, // it will resume with a return value of null. - Action? resumption = RuntimeHelpers.GetOrCreateResumptionDelegate(); + Action? resumption = RuntimeHelpers.GetOrCreateResumptionDelegate(ref stackMark); if (resumption != null) { // We are trying to suspend @@ -190,7 +204,17 @@ public static TResult AwaitAwaiterFromRuntimeAsync(TAwaiter a } // If we reach here, the only way that we actually run follow on code is for the continuation to actually run, // and return from GetOrCreateResumptionDelegate with a null return value. - RuntimeHelpers.SuspendIfSuspensionNotAborted(); + ref AsyncDataFrame asyncFrame = ref GetCurrentAsyncDataFrame(); + RuntimeAsyncMaintainedData maintainedData = asyncFrame._maintainedData!; + if (maintainedData._abortSuspend) + { + AbortSuspend(); + } + else + { + // This function must be called from the same function that has the stackmark in it. + unsafe { UnwindToFunctionWithAsyncFrame(maintainedData._nextTasklet, maintainedData._suspendActive); } + } } } @@ -204,11 +228,13 @@ public static void UnsafeAwaitAwaiterFromRuntimeAsync(TAwaiter awaiter { if (!awaiter.IsCompleted) { + StackCrawlMark stackMark = StackCrawlMark.LookForMe; + // Create resumption delegate, wrapping task, and create tasklets to represent each stack frame on the stack. // RuntimeTaskSuspender.GetOrCreateResumptionDelegate() works like a POSIX fork call in that calls to it will return a // delegate if they are the initial call to GetOrCreateResumptionDelegate, but once the thread is resumed, // it will resume with a return value of null. - Action? resumption = RuntimeHelpers.GetOrCreateResumptionDelegate(); + Action? resumption = RuntimeHelpers.GetOrCreateResumptionDelegate(ref stackMark); if (resumption != null) { // We are trying to suspend @@ -230,7 +256,17 @@ public static void UnsafeAwaitAwaiterFromRuntimeAsync(TAwaiter awaiter } // If we reach here, the only way that we actually run follow on code is for the continuation to actually run, // and return from GetOrCreateResumptionDelegate with a null return value. - RuntimeHelpers.SuspendIfSuspensionNotAborted(); + ref AsyncDataFrame asyncFrame = ref GetCurrentAsyncDataFrame(); + RuntimeAsyncMaintainedData maintainedData = asyncFrame._maintainedData!; + if (maintainedData._abortSuspend) + { + AbortSuspend(); + } + else + { + // This function must be called from the same function that has the stackmark in it. + unsafe { UnwindToFunctionWithAsyncFrame(maintainedData._nextTasklet, maintainedData._suspendActive); } + } } } @@ -243,11 +279,13 @@ public static void AwaitAwaiterFromRuntimeAsync(TAwaiter awaiter) wher { if (!awaiter.IsCompleted) { + StackCrawlMark stackMark = StackCrawlMark.LookForMe; + // Create resumption delegate, wrapping task, and create tasklets to represent each stack frame on the stack. // RuntimeTaskSuspender.GetOrCreateResumptionDelegate() works like a POSIX fork call in that calls to it will return a // delegate if they are the initial call to GetOrCreateResumptionDelegate, but once the thread is resumed, // it will resume with a return value of null. - Action? resumption = RuntimeHelpers.GetOrCreateResumptionDelegate(); + Action? resumption = RuntimeHelpers.GetOrCreateResumptionDelegate(ref stackMark); if (resumption != null) { // We are trying to suspend @@ -269,7 +307,17 @@ public static void AwaitAwaiterFromRuntimeAsync(TAwaiter awaiter) wher } // If we reach here, the only way that we actually run follow on code is for the continuation to actually run, // and return from GetOrCreateResumptionDelegate with a null return value. - RuntimeHelpers.SuspendIfSuspensionNotAborted(); + ref AsyncDataFrame asyncFrame = ref GetCurrentAsyncDataFrame(); + RuntimeAsyncMaintainedData maintainedData = asyncFrame._maintainedData!; + if (maintainedData._abortSuspend) + { + AbortSuspend(); + } + else + { + // This function must be called from the same function that has the stackmark in it. + unsafe { UnwindToFunctionWithAsyncFrame(maintainedData._nextTasklet, maintainedData._suspendActive); } + } } } @@ -303,7 +351,7 @@ internal abstract unsafe class RuntimeAsyncMaintainedData { public Action? _resumption; public Exception? _exception; - public bool _suspendActive; + public int _suspendActive; public bool _initialTaskEntry = true; public bool _completed; public byte _dummy; @@ -346,6 +394,10 @@ public unsafe void ResumptionFunc() _abortSuspend = true; return; } + + // Suspension has finished and we are resuming + _suspendActive = 0; + // Once we perform a resumption we no longer need to worry about handling the ultimate return data from the run of Tasklets _initialTaskEntry = false; @@ -558,32 +610,17 @@ private static unsafe ref AsyncDataFrame GetCurrentAsyncDataFrame() // 3. Return values are to be returned by reference in all cases where the return value is not a simple object return or return of a simple value in the return value register (this makes the resumption function reasonable to write. Notably, floating point, and ref return will be returned by reference as well as generalized struct return, and return which would normally involve multiple return value registers) // 4. There are to be no refs to the outermost caller function exceptn for the valuetype return address (methods which begin on an instance valuetype will have the thunk box the valuetype and the runtime async method on the boxed instance) [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "RuntimeSuspension_CaptureTasklets")] - private static unsafe partial Tasklet *CaptureCurrentStackIntoTasklets(StackCrawlMarkHandle stackMarkTop, ref byte returnValueHandle, [MarshalAs(UnmanagedType.U1)] bool useReturnValueHandle, void* taskAsyncData, out Tasklet* lastTasklet); + private static unsafe partial Tasklet *CaptureCurrentStackIntoTasklets(StackCrawlMarkHandle stackMarkTop, ref byte returnValueHandle, [MarshalAs(UnmanagedType.U1)] bool useReturnValueHandle, void* taskAsyncData, out Tasklet* lastTasklet, out int framesCaptured); [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "RuntimeSuspension_DeleteTasklet")] private static unsafe partial void DeleteTasklet(Tasklet *tasklet); [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void UnwindToFunctionWithAsyncFrame(ref AsyncDataFrame dataFrame); - - private static void SuspendIfSuspensionNotAborted() - { - ref AsyncDataFrame asyncFrame = ref GetCurrentAsyncDataFrame(); - RuntimeAsyncMaintainedData maintainedData = asyncFrame._maintainedData!; - if (maintainedData._abortSuspend) - { - AbortSuspend(); - } - else - { - UnwindToFunctionWithAsyncFrame(ref asyncFrame); - } - } + internal static extern unsafe void UnwindToFunctionWithAsyncFrame(Tasklet *topTasklet, nint framesToUnwind); [System.Security.DynamicSecurityMethod] // Methods containing StackCrawlMark local var has to be marked DynamicSecurityMethod - private static unsafe Action? GetOrCreateResumptionDelegate() + private static unsafe Action? GetOrCreateResumptionDelegate(ref StackCrawlMark stackMark) { - StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; ref AsyncDataFrame asyncFrame = ref GetCurrentAsyncDataFrame(); asyncFrame._maintainedData ??= asyncFrame._createRuntimeMaintainedData!(); @@ -591,7 +628,7 @@ private static void SuspendIfSuspensionNotAborted() RuntimeAsyncMaintainedData maintainedData = asyncFrame._maintainedData; Tasklet* lastTasklet = null; - Tasklet* nextTaskletInStack = CaptureCurrentStackIntoTasklets(new StackCrawlMarkHandle(ref stackMark), ref maintainedData.GetReturnPointer(), maintainedData._initialTaskEntry, t_asyncData, out lastTasklet); + Tasklet* nextTaskletInStack = CaptureCurrentStackIntoTasklets(new StackCrawlMarkHandle(ref stackMark), ref maintainedData.GetReturnPointer(), maintainedData._initialTaskEntry, t_asyncData, out lastTasklet, out var framesCaptured); if (nextTaskletInStack == null) throw new OutOfMemoryException(); @@ -600,7 +637,7 @@ private static void SuspendIfSuspensionNotAborted() maintainedData._nextTasklet = nextTaskletInStack; maintainedData._abortSuspend = false; - maintainedData._suspendActive = true; + maintainedData._suspendActive = framesCaptured; maintainedData._executionCtx = asyncFrame._currentThread._executionContext; maintainedData._syncCtx = asyncFrame._currentThread._synchronizationContext; @@ -623,7 +660,7 @@ private static unsafe void AbortSuspend() } maintainedData._nextTasklet = maintainedData._oldTaskletNext; maintainedData._oldTaskletNext = null; - maintainedData._suspendActive = false; + maintainedData._suspendActive = 0; } #endif } diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Future.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Future.cs index 394609486f3c3c..4d1917ddcfe202 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Future.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Future.cs @@ -32,7 +32,7 @@ public void Push() public void Pop() { RuntimeHelpers.PopAsyncData(ref dataFrame); } public Task FromResult(TResult result) { - if (dataFrame._maintainedData == null || !dataFrame._maintainedData._suspendActive) + if (dataFrame._maintainedData == null || (dataFrame._maintainedData._suspendActive == 0)) return Task.FromResult(result); return (Task)dataFrame._maintainedData.GetTask(); } diff --git a/src/tests/Loader/async/runtimetask-asyncfibonacci-with-yields.il b/src/tests/Loader/async/runtimetask-asyncfibonacci-with-yields.il index bfe9aece7c9a77..b081a484996394 100644 --- a/src/tests/Loader/async/runtimetask-asyncfibonacci-with-yields.il +++ b/src/tests/Loader/async/runtimetask-asyncfibonacci-with-yields.il @@ -348,7 +348,8 @@ bool V_1, bool V_2, bool V_3, - uint32 V_4) + uint32 V_4, + valuetype [System.Runtime]System.Runtime.CompilerServices.YieldAwaitable V_5) IL_0000: nop IL_0001: ldarg.0 IL_0002: stloc.0 @@ -363,7 +364,10 @@ IL_0014: ldloc.1 IL_0015: brfalse.s IL_001d IL_0017: call valuetype [System.Runtime]System.Runtime.CompilerServices.YieldAwaitable [System.Runtime]System.Threading.Tasks.Task::Yield() - IL_001c: call void [System.Runtime]System.Runtime.CompilerServices.RuntimeHelpers::UnsafeAwaitAwaiterFromRuntimeAsync(!!0) + stloc 5 + ldloca 5 + call instance valuetype [System.Runtime]System.Runtime.CompilerServices.YieldAwaitable/YieldAwaiter [System.Runtime]System.Runtime.CompilerServices.YieldAwaitable::GetAwaiter() + IL_001c: call void [System.Runtime]System.Runtime.CompilerServices.RuntimeHelpers::UnsafeAwaitAwaiterFromRuntimeAsync(!!0) IL_001d: ldloc.0 IL_001e: ldc.i4 0x773593ed IL_0023: mul @@ -375,7 +379,10 @@ IL_002e: ldloc.2 IL_002f: brfalse.s IL_0037 IL_0031: call valuetype [System.Runtime]System.Runtime.CompilerServices.YieldAwaitable [System.Runtime]System.Threading.Tasks.Task::Yield() - IL_0036: call void [System.Runtime]System.Runtime.CompilerServices.RuntimeHelpers::UnsafeAwaitAwaiterFromRuntimeAsync(!!0) + stloc 5 + ldloca 5 + call instance valuetype [System.Runtime]System.Runtime.CompilerServices.YieldAwaitable/YieldAwaiter [System.Runtime]System.Runtime.CompilerServices.YieldAwaitable::GetAwaiter() + IL_0036: call void [System.Runtime]System.Runtime.CompilerServices.RuntimeHelpers::UnsafeAwaitAwaiterFromRuntimeAsync(!!0) IL_0037: ldloc.0 IL_0038: ldc.i4 0x773593ed IL_003d: mul @@ -387,7 +394,10 @@ IL_0048: ldloc.3 IL_0049: brfalse.s IL_0051 IL_004b: call valuetype [System.Runtime]System.Runtime.CompilerServices.YieldAwaitable [System.Runtime]System.Threading.Tasks.Task::Yield() - IL_0050: call void [System.Runtime]System.Runtime.CompilerServices.RuntimeHelpers::UnsafeAwaitAwaiterFromRuntimeAsync(!!0) + stloc 5 + ldloca 5 + call instance valuetype [System.Runtime]System.Runtime.CompilerServices.YieldAwaitable/YieldAwaiter [System.Runtime]System.Runtime.CompilerServices.YieldAwaitable::GetAwaiter() + IL_0050: call void [System.Runtime]System.Runtime.CompilerServices.RuntimeHelpers::UnsafeAwaitAwaiterFromRuntimeAsync(!!0) IL_0051: ldloc.0 IL_0052: stloc.s V_4 IL_0054: br.s IL_0056 diff --git a/src/tests/Loader/async/runtimetask-asyncfibonacci-with-yields.ilproj b/src/tests/Loader/async/runtimetask-asyncfibonacci-with-yields.ilproj index bd17dd2bad5a7e..24afb2e5b72dc1 100644 --- a/src/tests/Loader/async/runtimetask-asyncfibonacci-with-yields.ilproj +++ b/src/tests/Loader/async/runtimetask-asyncfibonacci-with-yields.ilproj @@ -3,6 +3,6 @@ 1 - + From df91ac6d663bd6cfe075a835f696648b41df82ff Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Tue, 17 Oct 2023 15:06:52 -0700 Subject: [PATCH 025/203] Basic unwind support mostly works. Expected that it crashes unpredictably in the presence of Suspend operations, but other stuff seems to work. Now to rework Task generation to avoid multithreaded races --- src/coreclr/debug/di/module.cpp | 4 +- src/coreclr/utilcode/collections.cpp | 7 +++ src/coreclr/vm/amd64/AsmHelpers.asm | 60 +++++++++++++------ src/coreclr/vm/method.cpp | 6 +- src/coreclr/vm/method.hpp | 5 +- src/coreclr/vm/runtimesuspension.cpp | 41 +++++++++++-- .../CompilerServices/RuntimeHelpers.cs | 13 ++-- 7 files changed, 103 insertions(+), 33 deletions(-) diff --git a/src/coreclr/debug/di/module.cpp b/src/coreclr/debug/di/module.cpp index ca8314db533922..1f4216908637ad 100644 --- a/src/coreclr/debug/di/module.cpp +++ b/src/coreclr/debug/di/module.cpp @@ -4250,12 +4250,12 @@ HRESULT CordbNativeCode::GetILToNativeMapping(ULONG32 cMap, LoadNativeInfo(); SequencePoints * pSeqPts = GetSequencePoints(); - DebuggerILToNativeMap * rgMapInt = pSeqPts->GetMapAddr(); ULONG32 cMapIntCount = pSeqPts->GetEntryCount(); // If they gave us space to copy into... - if (map != NULL) + if (map != NULL && cMapIntCount != 0) { + DebuggerILToNativeMap * rgMapInt = pSeqPts->GetMapAddr(); // Only copy as much as either they gave us or we have to copy. ULONG32 cMapToCopy = min(cMap, cMapIntCount); diff --git a/src/coreclr/utilcode/collections.cpp b/src/coreclr/utilcode/collections.cpp index ed5271fde77f85..3f8513d38fc535 100644 --- a/src/coreclr/utilcode/collections.cpp +++ b/src/coreclr/utilcode/collections.cpp @@ -268,6 +268,13 @@ BYTE *CHashTable::FindNextEntry( // The next entry, or0 for end of list. if (psSrch->iNext != UINT32_MAX) { psEntry = EntryPtr(psSrch->iNext); +#ifdef DACCESS_COMPILE + // If this happens, it will trigger an infinite loop. Don't block the debugger on this + if (psSrch->iNext == psEntry->iNext) + { + return 0; + } +#endif psSrch->iNext = psEntry->iNext; return ((BYTE *) psEntry); } diff --git a/src/coreclr/vm/amd64/AsmHelpers.asm b/src/coreclr/vm/amd64/AsmHelpers.asm index 6abe642d1e7e1a..7b1120ece20830 100644 --- a/src/coreclr/vm/amd64/AsmHelpers.asm +++ b/src/coreclr/vm/amd64/AsmHelpers.asm @@ -684,10 +684,15 @@ endif ; FEATURE_TIERED_COMPILATION extern PlatformIndependentRestore:proc +; regRestore swaps whats in the register for the value in the restoration location. The idea is that the +; value in the restoration location is the value that has been stored away for use in this function +; and the current value of the saved register needs to be available once the function returns. regRestoreCase macro RegisterName Check&RegisterName&: mov edx, [rax+4] + mov r10, RegisterName mov RegisterName, [rcx+rdx] + mov [rcx+rdx], r10 add rax, 8 jmp RegRestoreLoop endm @@ -695,16 +700,25 @@ Check&RegisterName&: regRestoreCaseXmm macro RegisterName Check&RegisterName&: mov edx, [rax+4] + movsd xmm0, RegisterName movdqu RegisterName, [rcx+rdx] + movdqu [rcx+rdx], xmm0 add rax, 8 jmp RegRestoreLoop endm NESTED_ENTRY RuntimeSuspension_ResumeTaskletReferenceReturn, _TEXT - alloc_stack 48h + push_nonvol_reg rbp ; We capture RBP to create a stack frame so that we can safely adjust the RSP register, This means that since we always capture this, async2 functions must ALWAYS be compiled with a frame pointer. + alloc_stack 50h + set_frame rbp, 50h END_PROLOGUE + mov r10, [rcx + 8h*5] ; Load pStackDataInfo from Tasklet* + mov rax, [r10] ; Load StackRequirement from StackDataInfo + mov r10d, [r10 + 4h*6] ; Load CbArgs from StackDataInfo + sub rax, r10 ; Compute size of Caller SP to Callee SP + sub rsp, rax ; Allocate enough space for Callee stack - lea r8, [rsp+28h] + lea r8, [rsp+20h] ; Zero out integerRegister and AddressInMethodToRestoreTo ; Probably not needed in production code, but convenient for now @@ -713,20 +727,24 @@ NESTED_ENTRY RuntimeSuspension_ResumeTaskletReferenceReturn, _TEXT mov [r8+8h], rax ; Fill in pFutureRSPLocation - lea rax, [rsp+50] + lea rax, [rsp+60h] mov [r8+10h], rax ; Capture return address - mov rax, [rax] + mov rax, [rbp + 8h] mov [r8+18h], rax + ; Capture old rbp so that the stack overwrite doesn't stomp it + mov rax, [rbp] + mov [rsp+40h], rax + call PlatformIndependentRestore ; RAX now points at the RegRestore array - mov rcx, [rsp+28h + 10h] + mov rcx, [rsp+20h + 10h] ; RCX now has the value of the future RSP .data -switchTable qword Checkrbx, Checkrbp, Checkrdi, Checkrsi, Checkr12, Checkr13, Checkr14, Checkr15, Checkxmm6, Checkxmm7, Checkxmm8, Checkxmm9, Checkxmm10, Checkxmm11, Checkxmm12, Checkxmm13, Checkxmm14, Checkxmm15, ReturnRegisters +switchTable qword Checkrbx, Checkrbp, Checkrdi, Checkrsi, Checkr12, Checkr13, Checkr14, Checkr15, Checkxmm6, Checkxmm7, Checkxmm8, Checkxmm9, Checkxmm10, Checkxmm11, Checkxmm12, Checkxmm13, Checkxmm14, Checkxmm15, ReturnRegisters, ReturnRegistersNoFrame .code RegRestoreLoop: mov edx, [rax] @@ -734,14 +752,22 @@ RegRestoreLoop: lea r8, [switchTable] mov r8, [r8 + rdx*8] jmp r8 +ReturnRegistersNoFrame: + mov rbp, [rsp + 40h] ; Capture RBP old value from the stored location instead of from the current register (this is because we actually use RBP as part of this helper thunk function) ReturnRegisters: - mov rax, [rsp+28h + 0h] - MOVDQU xmm0, [rsp+28h + 0h] - mov rdx, [rsp+28h + 0h] ; Load address of where to jmp into ecx + mov rax, [rsp+20h + 0h] + MOVDQU xmm0, [rsp+20h + 0h] + mov rdx, [rsp+20h + 8h] ; Load address of where to jmp into ecx mov rsp, rcx jmp rdx +Checkrbp: + mov edx, [rax+4] + mov r10, [rsp + 40h] ; Capture RBP old value from the stored location instead of from the current register (this is because we actually use RBP as part of this helper thunk function) + mov rbp, [rcx+rdx] + mov [rcx+rdx], r10 + add rax, 8 + jmp RegRestoreLoop regRestoreCase rbx -regRestoreCase rbp regRestoreCase rdi regRestoreCase rsi regRestoreCase r12 @@ -766,16 +792,16 @@ LEAF_END RuntimeSuspension_ResumeTaskletIntegerRegisterReturn, _TEXT unwindRegCase macro RegisterName Unwind&RegisterName&: - mov edx, [r8+4] - mov RegisterName, [rsp+rdx] + mov eax, [r8+4] + mov RegisterName, [rsp+rax] add r8, 8 jmp RegUnwindLoop endm unwindRegCaseXmm macro RegisterName Unwind&RegisterName&: - mov edx, [r8+4] - movdqu RegisterName, [rsp+rdx] + mov eax, [r8+4] + movdqu RegisterName, [rsp+rax] add r8, 8 jmp RegUnwindLoop endm @@ -813,7 +839,7 @@ NewTaskletUnwind: mov r8, [r9+30h] ; Load r8 with the pRegRestore value .data -unwindSwitchTable qword Unwindrbx, Unwindrbp, Unwindrdi, Unwindrsi, Unwindr12, Unwindr13, Unwindr14, Unwindr15, Unwindxmm6, Unwindxmm7, Unwindxmm8, Unwindxmm9, Unwindxmm10, Unwindxmm11, Unwindxmm12, Unwindxmm13, Unwindxmm14, Unwindxmm15, UnwindReturnRegisters +unwindSwitchTable qword Unwindrbx, Unwindrbp, Unwindrdi, Unwindrsi, Unwindr12, Unwindr13, Unwindr14, Unwindr15, Unwindxmm6, Unwindxmm7, Unwindxmm8, Unwindxmm9, Unwindxmm10, Unwindxmm11, Unwindxmm12, Unwindxmm13, Unwindxmm14, Unwindxmm15, UnwindReturnRegisters, UnwindReturnRegisters .code RegUnwindLoop: mov eax, [r8] @@ -845,8 +871,8 @@ UnwindReturnRegisters: mov rax, [rsp+rax] ; Adjust rsp to callers rsp - mov r10, [r9] ; Load StackDataInfo.StackRequirement - sub r10, [r9 + 18h] ; Subtract off the CbArgs (stack args amount) + mov r10d, [r9] ; Load StackDataInfo.StackRequirement + sub r10d, [r9 + 18h] ; Subtract off the CbArgs (stack args amount) add rsp, r10 ; Adjust rsp as requested mov rcx, [rcx] ; Adjust to callers tasklet diff --git a/src/coreclr/vm/method.cpp b/src/coreclr/vm/method.cpp index 3935c0d5c842ed..bd15803076869a 100644 --- a/src/coreclr/vm/method.cpp +++ b/src/coreclr/vm/method.cpp @@ -407,7 +407,7 @@ void MethodDesc::GetSig(PCCOR_SIGNATURE *ppSig, DWORD *pcSig) } if (IsAsyncThunkMethod()) { - Signature sig = GetAsyncMethodData().sig; + Signature sig = GetAddrOfAsyncMethodData()->sig; *ppSig = sig.GetRawSig(); *pcSig = sig.GetRawSigLen(); return; @@ -986,7 +986,7 @@ PTR_PCODE MethodDesc::GetAddrOfNativeCodeSlot() } //******************************************************************************* -AsyncMethodData* MethodDesc::GetAddrOfAsyncMethodData() +PTR_AsyncMethodData MethodDesc::GetAddrOfAsyncMethodData() { WRAPPER_NO_CONTRACT; @@ -994,7 +994,7 @@ AsyncMethodData* MethodDesc::GetAddrOfAsyncMethodData() SIZE_T size = s_ClassificationSizeTable[m_wFlags & (mdcClassification | mdcHasNonVtableSlot | mdcMethodImpl | mdcHasNativeCodeSlot)]; - return (AsyncMethodData*)(dac_cast(this) + size); + return dac_cast(dac_cast(this) + size); } //******************************************************************************* diff --git a/src/coreclr/vm/method.hpp b/src/coreclr/vm/method.hpp index 7fc41ad768568e..25c16852c12986 100644 --- a/src/coreclr/vm/method.hpp +++ b/src/coreclr/vm/method.hpp @@ -70,6 +70,7 @@ struct AsyncMethodData Signature sig; }; +typedef DPTR(struct AsyncMethodData) PTR_AsyncMethodData; //============================================================= // Splits methoddef token into two pieces for @@ -1406,8 +1407,10 @@ class MethodDesc BOOL SetNativeCodeInterlocked(PCODE addr, PCODE pExpected = NULL); PTR_PCODE GetAddrOfNativeCodeSlot(); - AsyncMethodData *GetAddrOfAsyncMethodData(); + PTR_AsyncMethodData GetAddrOfAsyncMethodData(); +#ifndef DACCESS_COMPILE const AsyncMethodData& GetAsyncMethodData() { _ASSERTE(IsAsyncThunkMethod()); return *GetAddrOfAsyncMethodData(); } +#endif BOOL MayHaveNativeCode(); diff --git a/src/coreclr/vm/runtimesuspension.cpp b/src/coreclr/vm/runtimesuspension.cpp index 54dbf000abb829..af35884f42bbdf 100644 --- a/src/coreclr/vm/runtimesuspension.cpp +++ b/src/coreclr/vm/runtimesuspension.cpp @@ -34,6 +34,7 @@ enum RegisterToRestore Xmm14, Xmm15, ReturnRegisters, // End sequence marker. Associated offset is for the return register, but the actual return value is stashed after the register data + ReturnRegistersNoFrame, // End sequence marker. Associated offset is for the return register, but the actual return value is stashed after the register data }; struct RegRestore @@ -236,8 +237,10 @@ struct TaskletCaptureData { if (ActiveByRefsToStack.Size() > 0) { - for (SIZE_T iByRef = ActiveByRefsToStack.Size() - 1; iByRef >= 0; iByRef--) + for (SIZE_T iByRef = ActiveByRefsToStack.Size(); iByRef > 0;) { + iByRef--; + if (RelocAtAddress(&adjustment, ActiveByRefsToStack[iByRef])) { // ByRef was reloc'd, and therefore doesn't need to be handled anymore @@ -313,7 +316,7 @@ StackWalkAction CaptureTaskletsCore(CrawlFrame* pCf, VOID* data) else if (!pCf->GetFunction()->IsAsync2Method()) { // We must be in the wrapper thunk - _ASSERTE(pCf->GetFunction()->IsAsyncThunkMethod()); + _ASSERTE(pCf->GetFunction()->IsAsyncThunkMethod() || (strcmp(pCf->GetFunction()->GetName(), "ResumptionFunc") == 0)); if (taskletCaptureData->ActiveByRefsToStack.Size() != 0) { @@ -355,13 +358,33 @@ StackWalkAction CaptureTaskletsCore(CrawlFrame* pCf, VOID* data) stackDataInfo.ReturnAddressOffset = (uint32_t)(returnAddressLocation - (uintptr_t)pCf->GetRegisterSet()->SP); CQuickArrayList savedRegRestoreData; + bool hasRBPFrame = false; + + +// Find restored reg, and record its location in the frame, so that it can be properly restored. Then update the copied value to be the "current" value of the register, not what we saved off on entry to the function +#define DISCOVER_RESTORED_REG(REGNAME) \ + if (pCf->GetRegisterSet()->pCallerContextPointers->REGNAME != pCf->GetRegisterSet()->pCurrentContextPointers->REGNAME) \ + { \ + if ((void*)&pCf->GetRegisterSet()->pCallerContextPointers->REGNAME == (void*)&pCf->GetRegisterSet()->pCallerContextPointers->Rbp) \ + hasRBPFrame = true; \ + RegRestore restoreData; restoreData.reg = RegisterToRestore::REGNAME; \ + restoreData.offset = (uint32_t)((uint8_t*)pCf->GetRegisterSet()->pCallerContextPointers->REGNAME - (uint8_t*)pCf->GetRegisterSet()->SP); savedRegRestoreData.Push(restoreData); \ + memcpy((pStackData - taskletCaptureData->stackToIgnoreFromPreviousFrame) + restoreData.offset, &pCf->GetRegisterSet()->pCurrentContext->REGNAME, sizeof(pCf->GetRegisterSet()->pCurrentContext->REGNAME)); \ + } + -#define DISCOVER_RESTORED_REG(REGNAME) if (pCf->GetRegisterSet()->pCallerContextPointers->REGNAME != pCf->GetRegisterSet()->pCurrentContextPointers->REGNAME) { RegRestore restoreData; restoreData.reg = RegisterToRestore::REGNAME; restoreData.offset = (uint32_t)((uint8_t*)pCf->GetRegisterSet()->pCallerContextPointers->REGNAME - (uint8_t*)pCf->GetRegisterSet()->SP); savedRegRestoreData.Push(restoreData); } #include "restoreregs_for_runtimesuspension.h" #undef DISCOVER_RESTORED_REG RegRestore returnData; - returnData.reg = RegisterToRestore::ReturnRegisters; + if (hasRBPFrame) + { + returnData.reg = RegisterToRestore::ReturnRegisters; + } + else + { + returnData.reg = RegisterToRestore::ReturnRegistersNoFrame; + } returnData.offset = 0; savedRegRestoreData.Push(returnData); @@ -424,6 +447,16 @@ StackWalkAction CaptureTaskletsCore(CrawlFrame* pCf, VOID* data) &enumData, NO_OVERRIDE_OFFSET); +// HACK for frame pointer handling + for (int iRegister = 0; iRegister < savedRegRestoreData.Size(); iRegister++) + { + if (savedRegRestoreData[iRegister].reg == RegisterToRestore::Rbp) + { + // If we have Rbp as a saved register, report it as a byref, as its probably the frame pointer and thus in need of adjusting + enumGCRefs((void*)&enumData, (OBJECTREF*)(pCf->GetRegisterSet()->SP + savedRegRestoreData[iRegister].offset), GC_CALL_INTERIOR); + } + } + stackDataInfo.ByRefOffsets = (int32_t*)malloc(enumData.ByRefOffsets.Size() * sizeof(int32_t)); memcpy(stackDataInfo.ByRefOffsets, enumData.ByRefOffsets.Ptr(), enumData.ByRefOffsets.Size() * sizeof(int32_t)); stackDataInfo.ObjectRefOffsets = (uint32_t*)malloc(enumData.ObjectRefOffsets.Size() * sizeof(uint32_t)); diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs index 9c504bfa6cdd92..9ac93227268ce0 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs @@ -421,7 +421,9 @@ public unsafe void ResumptionFunc() while (_nextTasklet != null) { - int maxStackNeeded = _nextTasklet->GetMaxStackNeeded(); + Tasklet* pCurTasklet = _nextTasklet; + int maxStackNeeded = pCurTasklet->GetMaxStackNeeded(); + _nextTasklet = pCurTasklet->pTaskletNextInStack; if (maxStackNeeded > collectiveStackAllocsPerformed) { #pragma warning disable CA2014 @@ -436,13 +438,13 @@ public unsafe void ResumptionFunc() try { - switch (_nextTasklet->taskletReturnType) + switch (pCurTasklet->taskletReturnType) { case TaskletReturnType.ObjectReference: - _retValue = new RuntimeAsyncReturnValue(ResumeTaskletReferenceReturn(_nextTasklet, ref _retValue)); + _retValue = new RuntimeAsyncReturnValue(ResumeTaskletReferenceReturn(pCurTasklet, ref _retValue)); break; case TaskletReturnType.Integer: - _retValue = new RuntimeAsyncReturnValue(ResumeTaskletIntegerRegisterReturn(_nextTasklet, ref _retValue)); + _retValue = new RuntimeAsyncReturnValue(ResumeTaskletIntegerRegisterReturn(pCurTasklet, ref _retValue)); break; case TaskletReturnType.ByReference: throw new NotImplementedException(); // This will be awkward (but not impossible) to implement. Hold off for now @@ -450,9 +452,8 @@ public unsafe void ResumptionFunc() } finally { - DeleteTasklet(_nextTasklet); + DeleteTasklet(pCurTasklet); } - _nextTasklet = _nextTasklet->pTaskletNextInStack; } } finally From 56178c396b33cbd7cb09ca6eb99fe5f64445331d Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Tue, 17 Oct 2023 16:04:20 -0700 Subject: [PATCH 026/203] It runs? Doesn't handle return value correctly, but runs for the right number of iterations, I think --- .../CompilerServices/RuntimeHelpers.cs | 162 ++++-------------- 1 file changed, 32 insertions(+), 130 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs index 9ac93227268ce0..b9d65c9f4f585e 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs @@ -134,36 +134,13 @@ public static TResult UnsafeAwaitAwaiterFromRuntimeAsync(TAwa Action? resumption = RuntimeHelpers.GetOrCreateResumptionDelegate(ref stackMark); if (resumption != null) { - // We are trying to suspend - bool threwException = true; - try - { - // Call the UnsafeOnCompleted api under a try block, as registering the suspension may cause - // an exception to occur. - awaiter.UnsafeOnCompleted(resumption); - threwException = false; - } - finally - { - // If UnsafeOnCompleted itself threw, we should bubble the error up, but we need - // to destroy any allocated tasklets that were created as part of the GetOrCreateResumptionDelegate api - // as that state will never be useable. - if (threwException) - RuntimeHelpers.AbortSuspend(); - } // If we reach here, the only way that we actually run follow on code is for the continuation to actually run, // and return from GetOrCreateResumptionDelegate with a null return value. ref AsyncDataFrame asyncFrame = ref GetCurrentAsyncDataFrame(); RuntimeAsyncMaintainedData maintainedData = asyncFrame._maintainedData!; - if (maintainedData._abortSuspend) - { - AbortSuspend(); - } - else - { - // This function must be called from the same function that has the stackmark in it. - unsafe { UnwindToFunctionWithAsyncFrame(maintainedData._nextTasklet, maintainedData._suspendActive); } - } + maintainedData._awaiter = awaiter; + // This function must be called from the same function that has the stackmark in it. + unsafe { UnwindToFunctionWithAsyncFrame(maintainedData._nextTasklet, maintainedData._suspendActive); } } } @@ -185,42 +162,18 @@ public static TResult AwaitAwaiterFromRuntimeAsync(TAwaiter a Action? resumption = RuntimeHelpers.GetOrCreateResumptionDelegate(ref stackMark); if (resumption != null) { - // We are trying to suspend - bool threwException = true; - try - { - // Call the OnCompleted api under a try block, as registering the suspension may cause - // an exception to occur. - awaiter.OnCompleted(resumption); - threwException = false; - } - finally - { - // If OnCompleted itself threw, we should bubble the error up, but we need - // to destroy any allocated tasklets that were created as part of the GetOrCreateResumptionDelegate api - // as that state will never be useable. - if (threwException) - RuntimeHelpers.AbortSuspend(); - } // If we reach here, the only way that we actually run follow on code is for the continuation to actually run, // and return from GetOrCreateResumptionDelegate with a null return value. ref AsyncDataFrame asyncFrame = ref GetCurrentAsyncDataFrame(); RuntimeAsyncMaintainedData maintainedData = asyncFrame._maintainedData!; - if (maintainedData._abortSuspend) - { - AbortSuspend(); - } - else - { - // This function must be called from the same function that has the stackmark in it. - unsafe { UnwindToFunctionWithAsyncFrame(maintainedData._nextTasklet, maintainedData._suspendActive); } - } + maintainedData._awaiter = awaiter; + // This function must be called from the same function that has the stackmark in it. + unsafe { UnwindToFunctionWithAsyncFrame(maintainedData._nextTasklet, maintainedData._suspendActive); } } } // Get the result from the awaiter, or throw the exception stored in the Task return awaiter.GetResult(); - } // TODO, this method should be marked so that it is only callable from a runtime async method @@ -237,36 +190,13 @@ public static void UnsafeAwaitAwaiterFromRuntimeAsync(TAwaiter awaiter Action? resumption = RuntimeHelpers.GetOrCreateResumptionDelegate(ref stackMark); if (resumption != null) { - // We are trying to suspend - bool threwException = true; - try - { - // Call the UnsafeOnCompleted api under a try block, as registering the suspension may cause - // an exception to occur. - awaiter.UnsafeOnCompleted(resumption); - threwException = false; - } - finally - { - // If UnsafeOnCompleted itself threw, we should bubble the error up, but we need - // to destroy any allocated tasklets that were created as part of the GetOrCreateResumptionDelegate api - // as that state will never be useable. - if (threwException) - RuntimeHelpers.AbortSuspend(); - } // If we reach here, the only way that we actually run follow on code is for the continuation to actually run, // and return from GetOrCreateResumptionDelegate with a null return value. ref AsyncDataFrame asyncFrame = ref GetCurrentAsyncDataFrame(); RuntimeAsyncMaintainedData maintainedData = asyncFrame._maintainedData!; - if (maintainedData._abortSuspend) - { - AbortSuspend(); - } - else - { - // This function must be called from the same function that has the stackmark in it. - unsafe { UnwindToFunctionWithAsyncFrame(maintainedData._nextTasklet, maintainedData._suspendActive); } - } + maintainedData._awaiter = awaiter; + // This function must be called from the same function that has the stackmark in it. + unsafe { UnwindToFunctionWithAsyncFrame(maintainedData._nextTasklet, maintainedData._suspendActive); } } } @@ -288,36 +218,13 @@ public static void AwaitAwaiterFromRuntimeAsync(TAwaiter awaiter) wher Action? resumption = RuntimeHelpers.GetOrCreateResumptionDelegate(ref stackMark); if (resumption != null) { - // We are trying to suspend - bool threwException = true; - try - { - // Call the OnCompleted api under a try block, as registering the suspension may cause - // an exception to occur. - awaiter.OnCompleted(resumption); - threwException = false; - } - finally - { - // If OnCompleted itself threw, we should bubble the error up, but we need - // to destroy any allocated tasklets that were created as part of the GetOrCreateResumptionDelegate api - // as that state will never be useable. - if (threwException) - RuntimeHelpers.AbortSuspend(); - } // If we reach here, the only way that we actually run follow on code is for the continuation to actually run, // and return from GetOrCreateResumptionDelegate with a null return value. ref AsyncDataFrame asyncFrame = ref GetCurrentAsyncDataFrame(); RuntimeAsyncMaintainedData maintainedData = asyncFrame._maintainedData!; - if (maintainedData._abortSuspend) - { - AbortSuspend(); - } - else - { - // This function must be called from the same function that has the stackmark in it. - unsafe { UnwindToFunctionWithAsyncFrame(maintainedData._nextTasklet, maintainedData._suspendActive); } - } + maintainedData._awaiter = awaiter; + // This function must be called from the same function that has the stackmark in it. + unsafe { UnwindToFunctionWithAsyncFrame(maintainedData._nextTasklet, maintainedData._suspendActive); } } } @@ -351,11 +258,11 @@ internal abstract unsafe class RuntimeAsyncMaintainedData { public Action? _resumption; public Exception? _exception; + public INotifyCompletion? _awaiter; public int _suspendActive; public bool _initialTaskEntry = true; public bool _completed; public byte _dummy; - public bool _abortSuspend; public ExecutionContext? _executionCtx; public SynchronizationContext? _syncCtx; @@ -389,12 +296,6 @@ public RuntimeAsyncMaintainedData() public unsafe void ResumptionFunc() { - if (HasCurrentAsyncDataFrame() && GetCurrentAsyncDataFrame()._maintainedData == this) - { - _abortSuspend = true; - return; - } - // Suspension has finished and we are resuming _suspendActive = 0; @@ -412,6 +313,7 @@ public unsafe void ResumptionFunc() // PushAsyncData will fill in the value of _previousExecutionCtx and _previousSyncCtx if (_syncCtx != dataFrame._previousSyncCtx) { + // TODO: Possibly we should marshal to the right sync context? I dunno. We'll figure this out dataFrame._currentThread._synchronizationContext = _syncCtx; } if (_executionCtx != dataFrame._previousExecutionCtx) @@ -454,6 +356,12 @@ public unsafe void ResumptionFunc() { DeleteTasklet(pCurTasklet); } + + if (_suspendActive != 0) + { + // TODO: we should capture the sync and execution contexts at this point + break; + } } } finally @@ -465,6 +373,15 @@ public unsafe void ResumptionFunc() { SetException(e); } + + if (_suspendActive != 0) + { + // We suspended again. Just register to return to this function, and return + _awaiter!.OnCompleted(_resumption!); + return; + } + // We are actually done. Set the final result + SetResultDone(); } private Action? _taskResumer; @@ -483,6 +400,7 @@ public RuntimeAsyncMaintainedData GetAwaiter() public override Task GetTask() { + _awaiter!.OnCompleted(_resumption!); return _task!; } @@ -593,11 +511,13 @@ internal static unsafe void PopAsyncData(ref AsyncDataFrame asyncData) } } + [MethodImpl(MethodImplOptions.NoInlining)] internal static unsafe bool HasCurrentAsyncDataFrame() { return t_asyncData != null; } + [MethodImpl(MethodImplOptions.NoInlining)] private static unsafe ref AsyncDataFrame GetCurrentAsyncDataFrame() { return ref Unsafe.AsRef(t_asyncData); @@ -637,7 +557,6 @@ private static unsafe ref AsyncDataFrame GetCurrentAsyncDataFrame() lastTasklet->pTaskletNextInStack = maintainedData._nextTasklet; maintainedData._nextTasklet = nextTaskletInStack; - maintainedData._abortSuspend = false; maintainedData._suspendActive = framesCaptured; maintainedData._executionCtx = asyncFrame._currentThread._executionContext; @@ -646,23 +565,6 @@ private static unsafe ref AsyncDataFrame GetCurrentAsyncDataFrame() return maintainedData._resumption; } - - private static unsafe void AbortSuspend() - { - ref AsyncDataFrame asyncFrame = ref GetCurrentAsyncDataFrame(); - RuntimeAsyncMaintainedData maintainedData = asyncFrame._maintainedData!; - - Tasklet* pTaskletCur = maintainedData._nextTasklet; - while (pTaskletCur != maintainedData._oldTaskletNext) - { - Tasklet* pTaskletPrev = pTaskletCur; - pTaskletCur = pTaskletPrev; - DeleteTasklet(pTaskletPrev); - } - maintainedData._nextTasklet = maintainedData._oldTaskletNext; - maintainedData._oldTaskletNext = null; - maintainedData._suspendActive = 0; - } #endif } } From 52f272b1b8ede7651e6c1d7de5cbfc55c1929875 Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Tue, 17 Oct 2023 16:31:18 -0700 Subject: [PATCH 027/203] Free memory properly --- src/coreclr/vm/runtimesuspension.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/coreclr/vm/runtimesuspension.cpp b/src/coreclr/vm/runtimesuspension.cpp index af35884f42bbdf..4b61c75bc408cb 100644 --- a/src/coreclr/vm/runtimesuspension.cpp +++ b/src/coreclr/vm/runtimesuspension.cpp @@ -50,9 +50,9 @@ struct StackDataInfo if (ByRefOffsets != NULL) free(ByRefOffsets); if (ObjectRefOffsets != NULL) - free(ByRefOffsets); + free(ObjectRefOffsets); if (RegistersToRestore != NULL) - free(ByRefOffsets); + free(RegistersToRestore); } uint32_t StackRequirement; uint32_t UnrecordedDataSize; // From the restored RSP to the data chunk, how many bytes are skipped @@ -557,5 +557,8 @@ extern "C" Tasklet* QCALLTYPE RuntimeSuspension_CaptureTasklets(QCall::StackCraw extern "C" void QCALLTYPE RuntimeSuspension_DeleteTasklet(Tasklet* tasklet) { - // Well, leaking isn't a good plan in the long term, but for now it'll be fine + tasklet->pStackDataInfo->CleanupStackDataInfo(); + free(tasklet->pStackData); + free(tasklet->pStackDataInfo); + free(tasklet); } From 204633eea51948f087092648389985e65cd6af90 Mon Sep 17 00:00:00 2001 From: Vladimir Sadov Date: Wed, 18 Oct 2023 18:33:41 +0000 Subject: [PATCH 028/203] Merged PR 34523: remove DEFINE_METHOD for no longer existing methods remove DEFINE_METHOD for no longer existing methods --- src/coreclr/vm/corelib.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/coreclr/vm/corelib.h b/src/coreclr/vm/corelib.h index f06a9aa6847a9d..42be7407625842 100644 --- a/src/coreclr/vm/corelib.h +++ b/src/coreclr/vm/corelib.h @@ -679,8 +679,6 @@ DEFINE_METHOD(RUNTIME_HELPERS, GET_TAILCALL_INFO, GetTailCallInfo, NoS DEFINE_METHOD(RUNTIME_HELPERS, DISPATCH_TAILCALLS, DispatchTailCalls, NoSig) DEFINE_METHOD(RUNTIME_HELPERS, GET_OR_CREATE_RESUMPTION_DELEGATE, GetOrCreateResumptionDelegate, NoSig) -DEFINE_METHOD(RUNTIME_HELPERS, ABORT_SUSPEND, AbortSuspend, NoSig) -DEFINE_METHOD(RUNTIME_HELPERS, SUSPEND_IF_SUSPENSION_NOT_ABORTED, SuspendIfSuspensionNotAborted, NoSig) DEFINE_METHOD(RUNTIME_HELPERS, UNSAFE_AWAIT_AWAITER_FROM_RUNTIME_ASYNC_2, UnsafeAwaitAwaiterFromRuntimeAsync, GM_U_RetT) DEFINE_CLASS(UNSAFE, CompilerServices, Unsafe) From f30fa3f8af9e3befd2f752914c7753f1e35cedaf Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Wed, 18 Oct 2023 13:57:16 -0700 Subject: [PATCH 029/203] Fix returns within async2 tasks... returning value to outer task is still not working --- src/coreclr/vm/amd64/AsmHelpers.asm | 5 +++++ src/coreclr/vm/runtimesuspension.cpp | 2 +- .../src/System/Runtime/CompilerServices/RuntimeHelpers.cs | 3 ++- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/coreclr/vm/amd64/AsmHelpers.asm b/src/coreclr/vm/amd64/AsmHelpers.asm index 7b1120ece20830..286b871f21f26e 100644 --- a/src/coreclr/vm/amd64/AsmHelpers.asm +++ b/src/coreclr/vm/amd64/AsmHelpers.asm @@ -708,6 +708,10 @@ Check&RegisterName&: endm NESTED_ENTRY RuntimeSuspension_ResumeTaskletReferenceReturn, _TEXT +; On entry rcx is Tasklet* +; rdx is a pointer to the return value structure +; +; r10 and r8 are used as scratch registers push_nonvol_reg rbp ; We capture RBP to create a stack frame so that we can safely adjust the RSP register, This means that since we always capture this, async2 functions must ALWAYS be compiled with a frame pointer. alloc_stack 50h set_frame rbp, 50h @@ -738,6 +742,7 @@ NESTED_ENTRY RuntimeSuspension_ResumeTaskletReferenceReturn, _TEXT mov rax, [rbp] mov [rsp+40h], rax + ; Note that neither rcx or rdx has been modified from entry to this point, and are passed straight through to the PlatformIndependentRestore function call PlatformIndependentRestore ; RAX now points at the RegRestore array mov rcx, [rsp+20h + 10h] diff --git a/src/coreclr/vm/runtimesuspension.cpp b/src/coreclr/vm/runtimesuspension.cpp index 4b61c75bc408cb..c41d979f96cbfb 100644 --- a/src/coreclr/vm/runtimesuspension.cpp +++ b/src/coreclr/vm/runtimesuspension.cpp @@ -79,8 +79,8 @@ struct Tasklet struct RuntimeAsyncReturnValue { - uintptr_t _ptr; uintptr_t _obj; + uintptr_t _ptr; TaskletReturnType _returnType; }; diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs index b9d65c9f4f585e..90e03ea13c4c02 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs @@ -249,8 +249,8 @@ public RuntimeAsyncReturnValue(IntPtr ptr) _ptr = ptr; _returnType = TaskletReturnType.Integer; } - public IntPtr _ptr; public object? _obj; + public IntPtr _ptr; public TaskletReturnType _returnType; } @@ -556,6 +556,7 @@ private static unsafe ref AsyncDataFrame GetCurrentAsyncDataFrame() maintainedData._oldTaskletNext = maintainedData._nextTasklet; lastTasklet->pTaskletNextInStack = maintainedData._nextTasklet; maintainedData._nextTasklet = nextTaskletInStack; + maintainedData._retValue = default(RuntimeAsyncReturnValue); maintainedData._suspendActive = framesCaptured; From 19b5c9935dbfaac1562f7dfbf0aa544682e2ee4b Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Wed, 18 Oct 2023 14:14:37 -0700 Subject: [PATCH 030/203] Set the return value on output from this whole thing! The example works! --- .../Runtime/CompilerServices/RuntimeHelpers.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs index 90e03ea13c4c02..f4eda7aff82683 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs @@ -413,6 +413,19 @@ public void SetException(Exception exception) public void SetResultDone() { + switch (_retValue._returnType) + { + case TaskletReturnType.Integer: + _returnData = Unsafe.As(ref _retValue._ptr); + Thread.MemoryBarrier(); + break; + case TaskletReturnType.ObjectReference: + _returnData = Unsafe.As(ref _retValue!._obj!)!; + break; + default: + // Other possibiilities not yet implemented + throw new NotImplementedException(); + } _completed = true; _taskResumer!(); } From 41744475b82e33494b9032f76ceb74963570ae1f Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Thu, 19 Oct 2023 12:28:17 -0700 Subject: [PATCH 031/203] Fix for code which uses the 32 byte parameter area on X64 --- src/coreclr/vm/runtimesuspension.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/coreclr/vm/runtimesuspension.cpp b/src/coreclr/vm/runtimesuspension.cpp index c41d979f96cbfb..ec919dbf0eb6f3 100644 --- a/src/coreclr/vm/runtimesuspension.cpp +++ b/src/coreclr/vm/runtimesuspension.cpp @@ -329,6 +329,9 @@ StackWalkAction CaptureTaskletsCore(CrawlFrame* pCf, VOID* data) uint8_t* pTopOfStackInFunction = (uint8_t*)pCf->GetRegisterSet()->SP; uint32_t sizeofArgStack = (uint32_t)pCf->GetFunction()->SizeOfArgStack(); +#ifdef TARGET_AMD64 // AND Target windows + sizeofArgStack = max(sizeofArgStack, 32); // On Windows X64 there is always a parameter area of 32 bytes, which is for use by the called function +#endif uint8_t* pBottomOfStackInFunction = (uint8_t*)::GetSP(pCf->GetRegisterSet()->pCallerContext) + sizeofArgStack; uint32_t sizeofEntireMeaningfulStack = (uint32_t)(pBottomOfStackInFunction - pTopOfStackInFunction); sizeofEntireMeaningfulStack -= (uint32_t)taskletCaptureData->stackToIgnoreFromPreviousFrame; From 989c4cb5cc53f66448b02b05de9308028d47baf7 Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Thu, 19 Oct 2023 16:55:00 -0700 Subject: [PATCH 032/203] Add corjit flags and corinfo for the new async work --- src/coreclr/inc/corinfo.h | 2 ++ src/coreclr/inc/corjitflags.h | 1 + src/coreclr/vm/jitinterface.cpp | 11 +++++++++++ src/coreclr/vm/methodtablebuilder.cpp | 14 ++++++++++++-- 4 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/coreclr/inc/corinfo.h b/src/coreclr/inc/corinfo.h index 4941bf56f7d68a..97e0d385753ce3 100644 --- a/src/coreclr/inc/corinfo.h +++ b/src/coreclr/inc/corinfo.h @@ -755,6 +755,7 @@ enum CorInfoCallConv CORINFO_CALLCONV_HASTHIS = 0x20, CORINFO_CALLCONV_EXPLICITTHIS=0x40, CORINFO_CALLCONV_PARAMTYPE = 0x80, // Passed last. Same as CORINFO_GENERICS_CTXT_FROM_PARAMTYPEARG + CORINFO_CALLCONV_ASYNCCALL = 0x100, // Is this a call to an async function? }; // Represents the calling conventions supported with the extensible calling convention syntax @@ -1137,6 +1138,7 @@ struct CORINFO_SIG_INFO unsigned totalILArgs() { return (numArgs + (hasImplicitThis() ? 1 : 0)); } bool isVarArg() { return ((getCallConv() == CORINFO_CALLCONV_VARARG) || (getCallConv() == CORINFO_CALLCONV_NATIVEVARARG)); } bool hasTypeArg() { return ((callConv & CORINFO_CALLCONV_PARAMTYPE) != 0); } + bool isAsyncCall() { return ((callConv & CORINFO_CALLCONV_ASYNCCALL) != 0); } }; struct CORINFO_METHOD_INFO diff --git a/src/coreclr/inc/corjitflags.h b/src/coreclr/inc/corjitflags.h index dce9a9f00b3998..e1c569e46ad499 100644 --- a/src/coreclr/inc/corjitflags.h +++ b/src/coreclr/inc/corjitflags.h @@ -68,6 +68,7 @@ class CORJIT_FLAGS CORJIT_FLAG_VECTOR512_THROTTLING = 31, // On x86/x64, 512-bit vector usage may incur CPU frequency throttling #endif + CORJIT_FLAG_RUNTIMEASYNCFUNCTION = 32, // Generate Code for use as an async2 function }; CORJIT_FLAGS() diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index d577d15c37c36a..6b4dba089e19e6 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -460,6 +460,8 @@ enum ConvToJitSigFlags : int // localSig - Is it a local variables declaration, or a method signature (with return type, etc). // contextType - The type with any instantiaton information // +AsyncTaskMethod ClassifyAsyncMethodCore(SigPointer sig, Module* pModule, PCCOR_SIGNATURE initialSig, ULONG* offsetOfAsyncDetails); + static void ConvToJitSig( PCCOR_SIGNATURE pSig, DWORD cbSig, @@ -544,6 +546,12 @@ static void ConvToJitSig( sigRet->retType = CEEInfo::asCorInfoType(type, typeHnd, &sigRet->retTypeClass); sigRet->retTypeSigClass = CORINFO_CLASS_HANDLE(typeHnd.AsPtr()); + auto asyncMethodClassification = ClassifyAsyncMethodCore(sig, module, NULL, NULL); + if ((asyncMethodClassification == AsyncTaskMethod::Async2Method) || (asyncMethodClassification == AsyncTaskMethod::Async2Method)) + { + sigRet->callConv = (CorInfoCallConv)(sigRet->callConv | CORINFO_CALLCONV_ASYNCCALL); + } + IfFailThrow(sig.SkipExactlyOne()); // must to a skip so we skip any class tokens associated with the return type _ASSERTE(sigRet->retType < CORINFO_TYPE_COUNT); @@ -12601,6 +12609,9 @@ static CORJIT_FLAGS GetCompileFlags(PrepareCodeConfig* prepareConfig, MethodDesc // flags.Add(CEEInfo::GetBaseCompileFlags(ftn)); + if (ftn->IsAsync2Method()) + flags.Add(CORJIT_FLAGS::CORJIT_FLAG_RUNTIMEASYNCFUNCTION); + // // Get CPU specific flags // diff --git a/src/coreclr/vm/methodtablebuilder.cpp b/src/coreclr/vm/methodtablebuilder.cpp index 7419681a103dea..db8788bdcc6ae4 100644 --- a/src/coreclr/vm/methodtablebuilder.cpp +++ b/src/coreclr/vm/methodtablebuilder.cpp @@ -2748,6 +2748,8 @@ bool IsTypeDefOrRefAByRefStruct(Module* pModule, mdToken tk) return false; } +AsyncTaskMethod ClassifyAsyncMethodCore(SigPointer sig, Module* pModule, PCCOR_SIGNATURE initialSig, ULONG* offsetOfAsyncDetails); + AsyncTaskMethod ClassifyAsyncMethod(SigPointer sig, Module* pModule, ULONG* offsetOfAsyncDetails) { PCCOR_SIGNATURE initialSig = sig.GetPtr(); @@ -2761,13 +2763,20 @@ AsyncTaskMethod ClassifyAsyncMethod(SigPointer sig, Module* pModule, ULONG* offs // Return argument count IfFailThrow(sig.GetData(&data)); + return ClassifyAsyncMethodCore(sig, pModule, initialSig, offsetOfAsyncDetails); +} + +AsyncTaskMethod ClassifyAsyncMethodCore(SigPointer sig, Module* pModule, PCCOR_SIGNATURE initialSig, ULONG* offsetOfAsyncDetails) +{ + uint32_t data; // Now we should be parsing the return type // If the first custommodifier is a MOD_OPT to CallConvAsync2Call // Then this is a async2 function CorElementType elemType; - *offsetOfAsyncDetails = (ULONG)(sig.GetPtr() - initialSig); + if (offsetOfAsyncDetails != NULL) + *offsetOfAsyncDetails = (ULONG)(sig.GetPtr() - initialSig); BYTE elemTypeByte; IfFailThrow(sig.GetByte(&elemTypeByte)); // Don't use GetElemType as it skips custom modifiers elemType = (CorElementType)elemTypeByte; @@ -2835,7 +2844,8 @@ AsyncTaskMethod ClassifyAsyncMethod(SigPointer sig, Module* pModule, ULONG* offs } } - *offsetOfAsyncDetails = (ULONG)(sig.GetPtr() - initialSig) - 1; + if (offsetOfAsyncDetails != NULL) + *offsetOfAsyncDetails = (ULONG)(sig.GetPtr() - initialSig) - 1; } if (elemType == ELEMENT_TYPE_GENERICINST) From 9c7a44461fd3a7a96cab2c382c0e03dbfd564b8e Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Fri, 20 Oct 2023 09:58:42 -0700 Subject: [PATCH 033/203] Add test case compiled with the new compiler --- .../async/async2fibonacci-with-yields.cs | 61 +++++++++++++++++++ .../async/async2fibonacci-with-yields.csproj | 8 +++ 2 files changed, 69 insertions(+) create mode 100644 src/tests/Loader/async/async2fibonacci-with-yields.cs create mode 100644 src/tests/Loader/async/async2fibonacci-with-yields.csproj diff --git a/src/tests/Loader/async/async2fibonacci-with-yields.cs b/src/tests/Loader/async/async2fibonacci-with-yields.cs new file mode 100644 index 00000000000000..5e2c09a4926ce8 --- /dev/null +++ b/src/tests/Loader/async/async2fibonacci-with-yields.cs @@ -0,0 +1,61 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Threading.Tasks; +using System.Diagnostics; + +class Async2FibonacceWithYields +{ + const uint Threshold = 1_000; + static bool done; + + public static void Main() + { + AsyncEntry().Wait(); + } + + public static async Task AsyncEntry() + { + for (int i = 0; i < 10 && !done; i++) + { + var sw = new Stopwatch(); + sw.Start(); + uint result = await A(100_000_000); + Console.WriteLine($"{sw.ElapsedMilliseconds} ms result={result}"); + } + } + + static async2 uint A(uint n) + { + uint result = n; + for (uint i = 0; i < n && !done; i++) + result = await B(result); + return result; + } + + static async2 uint B(uint n) + { + uint result = n; + + result = result * 1_999_999_981; + if (result < Threshold) + { + await Task.Yield(); + } + + result = result * 1_999_999_981; + if (result < Threshold) + { + await Task.Yield(); + } + + result = result * 1_999_999_981; + if (result < Threshold) + { + await Task.Yield(); + } + + return result; + } +} diff --git a/src/tests/Loader/async/async2fibonacci-with-yields.csproj b/src/tests/Loader/async/async2fibonacci-with-yields.csproj new file mode 100644 index 00000000000000..f67acfa29a9565 --- /dev/null +++ b/src/tests/Loader/async/async2fibonacci-with-yields.csproj @@ -0,0 +1,8 @@ + + + 1 + + + + + From bd38a81e04c1ba12aba336b0bf589140123e262e Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Fri, 20 Oct 2023 10:19:55 -0700 Subject: [PATCH 034/203] Fix issue exposed by merging to latest main bits --- src/coreclr/vm/runtimesuspension.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/coreclr/vm/runtimesuspension.cpp b/src/coreclr/vm/runtimesuspension.cpp index ec919dbf0eb6f3..ed5c5629177a35 100644 --- a/src/coreclr/vm/runtimesuspension.cpp +++ b/src/coreclr/vm/runtimesuspension.cpp @@ -305,6 +305,14 @@ StackWalkAction CaptureTaskletsCore(CrawlFrame* pCf, VOID* data) _ASSERTE(pFunc); TaskletCaptureData* taskletCaptureData = (TaskletCaptureData*) data; + + // Ignore any frames before we get to the interesting methods + if (!pCf->IsFrameless()) + { + _ASSERTE(taskletCaptureData->firstTasklet == NULL); // This should only happen for cases before we get to interesting components of the stack + return SWA_CONTINUE; + } + if (taskletCaptureData->firstTasklet == NULL) { if (!IsInCurrentFrame(pCf->GetRegisterSet(), taskletCaptureData->stackMark)) From 41aaae82df198fd7b2b9b1183f7ecdc5a9a47e99 Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Fri, 20 Oct 2023 16:18:48 -0700 Subject: [PATCH 035/203] Add GC Support to unwind based async implementation --- src/coreclr/vm/amd64/AsmHelpers.asm | 15 ++++-- src/coreclr/vm/ceemain.cpp | 3 +- src/coreclr/vm/gcenv.ee.cpp | 7 +++ src/coreclr/vm/runtimesuspension.cpp | 78 ++++++++++++++++++++++++++++ src/coreclr/vm/runtimesuspension.h | 5 ++ 5 files changed, 104 insertions(+), 4 deletions(-) diff --git a/src/coreclr/vm/amd64/AsmHelpers.asm b/src/coreclr/vm/amd64/AsmHelpers.asm index 286b871f21f26e..2473822036c140 100644 --- a/src/coreclr/vm/amd64/AsmHelpers.asm +++ b/src/coreclr/vm/amd64/AsmHelpers.asm @@ -683,6 +683,7 @@ NESTED_END OnCallCountThresholdReachedStub, _TEXT endif ; FEATURE_TIERED_COMPILATION extern PlatformIndependentRestore:proc +extern ForceThisThreadHasNoHijackForUnwind:proc ; regRestore swaps whats in the register for the value in the restoration location. The idea is that the ; value in the restoration location is the value that has been stored away for use in this function @@ -811,7 +812,7 @@ Unwind&RegisterName&: jmp RegUnwindLoop endm -LEAF_ENTRY RuntimeSuspension_UnwindToFunctionWithAsyncFrame, _TEXT +NESTED_ENTRY RuntimeSuspension_UnwindToFunctionWithAsyncFrame, _TEXT ; // Psuedocode... ; Tasklet* pCurTasklet = topTasklet; @@ -829,6 +830,14 @@ LEAF_ENTRY RuntimeSuspension_UnwindToFunctionWithAsyncFrame, _TEXT ; } ; Jmp ip + alloc_stack 38h + END_PROLOGUE + mov [rsp+20h], rcx + mov [rsp+28h], rdx + call ForceThisThreadHasNoHijackForUnwind + mov rcx, [rsp+20h] + mov rdx, [rsp+28h] + add rsp, 40h ; rcx is pCurTasklet ; rdx is count of tasklets to unwind @@ -838,7 +847,6 @@ LEAF_ENTRY RuntimeSuspension_UnwindToFunctionWithAsyncFrame, _TEXT ; r10 is second scratch register ; r8 is pRegRestore ; r9 is pStackDataInfo - pop rax; Pop off saved IP, so that we start with the right SP set_frame NewTaskletUnwind: mov r9, [rcx+28h] ; Load r9 with the pStackDataInfo value on the current tasklet mov r8, [r9+30h] ; Load r8 with the pRegRestore value @@ -888,5 +896,6 @@ TaskletUnwindDone: mov rcx, rax ; Move return address into rcx xor rax, rax ; Clear return register in case it is supposed to be an object reference or something -- TODO, note that for structure returns this will mean we need to slightly adjust the calling convention so that we don't rely on this being a pointer to the return buffer jmp rcx -LEAF_END RuntimeSuspension_UnwindToFunctionWithAsyncFrame, _TEXT +NESTED_END RuntimeSuspension_UnwindToFunctionWithAsyncFrame, _TEXT + end \ No newline at end of file diff --git a/src/coreclr/vm/ceemain.cpp b/src/coreclr/vm/ceemain.cpp index c5eaacd0c168ee..bfdd1f6dd085e5 100644 --- a/src/coreclr/vm/ceemain.cpp +++ b/src/coreclr/vm/ceemain.cpp @@ -206,6 +206,7 @@ #endif // FEATURE_GDBJIT #include "genanalysis.h" +#include "runtimesuspension.h" static int GetThreadUICultureId(_Out_ LocaleIDValue* pLocale); // TODO: This shouldn't use the LCID. We should rely on name instead @@ -693,7 +694,7 @@ void EEStartupHelper() Frame::Init(); - + InitializeTasklets(); #ifdef LOGGING diff --git a/src/coreclr/vm/gcenv.ee.cpp b/src/coreclr/vm/gcenv.ee.cpp index 31bfab32e755f2..108ac06eaad801 100644 --- a/src/coreclr/vm/gcenv.ee.cpp +++ b/src/coreclr/vm/gcenv.ee.cpp @@ -26,6 +26,8 @@ #include "genanalysis.h" #include "eventpipeadapter.h" +#include "runtimesuspension.h" + // Finalizes a weak reference directly. extern void FinalizeWeakReference(Object* obj); @@ -323,6 +325,11 @@ void GCToEEInterface::GcScanRoots(promote_func* fn, int condemned, int max_gen, SystemDomain::EnumAllStaticGCRefs(fn, sc); } } + +#ifndef DACCESS_COMPILE +// TODO Make tasklet reporting DAC friendly + IterateTaskletsForGC(fn, sc); +#endif } void GCToEEInterface::GcStartWork (int condemned, int max_gen) diff --git a/src/coreclr/vm/runtimesuspension.cpp b/src/coreclr/vm/runtimesuspension.cpp index ed5c5629177a35..6b2c51a44d2aa5 100644 --- a/src/coreclr/vm/runtimesuspension.cpp +++ b/src/coreclr/vm/runtimesuspension.cpp @@ -525,6 +525,7 @@ PORTABILIT_ASSERT() pTasklet->restoreIPAddress = (uintptr_t)GetControlPC(pCf->GetRegisterSet()); pTasklet->pStackDataInfo = pStackDataInfo; pTasklet->taskletReturnType = taskletReturnType; + RegisterTasklet(pTasklet); if (taskletCaptureData->firstTasklet == NULL) { @@ -568,8 +569,85 @@ extern "C" Tasklet* QCALLTYPE RuntimeSuspension_CaptureTasklets(QCall::StackCraw extern "C" void QCALLTYPE RuntimeSuspension_DeleteTasklet(Tasklet* tasklet) { + UnregisterTasklet(tasklet); tasklet->pStackDataInfo->CleanupStackDataInfo(); free(tasklet->pStackData); free(tasklet->pStackDataInfo); free(tasklet); } + +void RegisterTasklet(Tasklet* pTasklet); +void InitializeTasklets(); +void UnregisterTasklet(Tasklet* pTasklet); + +Tasklet *g_pTaskletSentinel = NULL; + +CrstStatic g_taskletCrst; + +void InitializeTasklets() +{ + g_taskletCrst.Init(CrstLeafLock, CRST_UNSAFE_ANYMODE); + +} + +void RegisterTasklet(Tasklet* pTasklet) +{ + CrstHolder crstHolder(&g_taskletCrst); + if (g_pTaskletSentinel == NULL) + { + g_pTaskletSentinel = (Tasklet*)malloc(sizeof(Tasklet)); + memset(g_pTaskletSentinel, 0, sizeof(Tasklet)); + g_pTaskletSentinel->pTaskletNextInLiveList = g_pTaskletSentinel; + g_pTaskletSentinel->pTaskletPrevInLiveList = g_pTaskletSentinel; + } + + pTasklet->pTaskletNextInLiveList = g_pTaskletSentinel->pTaskletNextInLiveList; + pTasklet->pTaskletPrevInLiveList = g_pTaskletSentinel; + g_pTaskletSentinel->pTaskletNextInLiveList = pTasklet; + pTasklet->pTaskletNextInLiveList->pTaskletPrevInLiveList = pTasklet; +} + +void UnregisterTasklet(Tasklet* pTasklet) +{ + CrstHolder crstHolder(&g_taskletCrst); + pTasklet->pTaskletPrevInLiveList->pTaskletNextInLiveList = pTasklet->pTaskletNextInLiveList; + pTasklet->pTaskletNextInLiveList->pTaskletPrevInLiveList = pTasklet->pTaskletPrevInLiveList; +} + +void IterateTaskletsForGC(promote_func* pCallback, ScanContext* sc) +{ + CrstHolder crstHolder(&g_taskletCrst); + Tasklet *pCurTasklet = g_pTaskletSentinel->pTaskletNextInLiveList; + while (pCurTasklet != g_pTaskletSentinel) + { + // Report GC pointers + auto pStackDataInfo = pCurTasklet->pStackDataInfo; + uint8_t *pLogicalRSP = pCurTasklet->pStackData - pStackDataInfo->UnrecordedDataSize; + uint32_t iRef; + for (iRef = 0; iRef < pStackDataInfo->cObjectRefs; iRef++) + { + pCallback((PTR_PTR_Object)(pLogicalRSP + pStackDataInfo->ObjectRefOffsets[iRef]), sc, 0); + } + + for (iRef = 0; iRef < pStackDataInfo->cObjectRefs; iRef++) + { + int32_t offset = pStackDataInfo->ByRefOffsets[iRef]; + uint32_t flags = GC_CALL_INTERIOR; + if (offset < 0) + { + offset = -offset; + flags |= GC_CALL_PINNED; + } + + pCallback((PTR_PTR_Object)(pLogicalRSP + offset), sc, flags); + } + + pCurTasklet = pCurTasklet->pTaskletNextInLiveList; + } +} + +extern "C" void ForceThisThreadHasNoHijackForUnwind() +{ + // If there is a hijack of a frame in place and we unwind, who knows what's going to happen. This code just forces the hijack to disappear. The runtime should put it back later if it matters. + GetThread()->UnhijackThread(); +} diff --git a/src/coreclr/vm/runtimesuspension.h b/src/coreclr/vm/runtimesuspension.h index d06e7a149d7720..4f9b23f01d65a7 100644 --- a/src/coreclr/vm/runtimesuspension.h +++ b/src/coreclr/vm/runtimesuspension.h @@ -12,3 +12,8 @@ EXTERN_C FCDECL1(void, RuntimeSuspension_UnwindToFunctionWithAsyncFrame, AsyncDa EXTERN_C FCDECL2(Object*, RuntimeSuspension_ResumeTaskletReferenceReturn, Tasklet* tasklet, RuntimeAsyncReturnValue *resumptionReturnValue); EXTERN_C FCDECL2(Object*, RuntimeSuspension_ResumeTaskletIntegerRegisterReturn, Tasklet* tasklet, RuntimeAsyncReturnValue *resumptionReturnValue); + +void RegisterTasklet(Tasklet* pTasklet); +void InitializeTasklets(); +void UnregisterTasklet(Tasklet* pTasklet); +void IterateTaskletsForGC(promote_func* pCallback, ScanContext* sc); From 18d7cddd729af7ffd46bb46fb72f34fafba9e011 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Fri, 20 Oct 2023 21:15:38 +0000 Subject: [PATCH 036/203] Merged PR 34691: Prototype for generating state machines in JIT for async --- ...async-with-jit-generated-state-machines.md | 481 +++++++++ .../RuntimeHelpers.CoreCLR.cs | 16 + .../src/System/StubHelpers.cs | 3 + src/coreclr/inc/clrconfigvalues.h | 5 + src/coreclr/inc/corinfo.h | 25 + src/coreclr/inc/icorjitinfoimpl_generated.h | 5 + src/coreclr/inc/jiteeversionguid.h | 10 +- src/coreclr/inc/jithelpers.h | 2 + src/coreclr/jit/CMakeLists.txt | 2 + src/coreclr/jit/ICorJitInfo_names_generated.h | 2 + .../jit/ICorJitInfo_wrapper_generated.hpp | 16 + src/coreclr/jit/async.cpp | 999 ++++++++++++++++++ src/coreclr/jit/async.h | 59 ++ src/coreclr/jit/block.cpp | 4 + src/coreclr/jit/block.h | 5 +- src/coreclr/jit/codegen.h | 2 + src/coreclr/jit/codegencommon.cpp | 25 + src/coreclr/jit/codegenxarch.cpp | 20 + src/coreclr/jit/compiler.cpp | 5 + src/coreclr/jit/compiler.h | 26 + src/coreclr/jit/compiler.hpp | 8 + src/coreclr/jit/compmemkind.h | 1 + src/coreclr/jit/compphases.h | 1 + src/coreclr/jit/fgdiagnostic.cpp | 10 + src/coreclr/jit/forwardsub.cpp | 2 +- src/coreclr/jit/gentree.cpp | 24 + src/coreclr/jit/gentree.h | 6 + src/coreclr/jit/gtlist.h | 3 + src/coreclr/jit/importer.cpp | 1 + src/coreclr/jit/importercalls.cpp | 74 +- src/coreclr/jit/jitconfigvalues.h | 3 + src/coreclr/jit/jitee.h | 1 + src/coreclr/jit/lclvars.cpp | 94 +- src/coreclr/jit/lir.h | 14 + src/coreclr/jit/liveness.cpp | 1 + src/coreclr/jit/lsraxarch.cpp | 6 + src/coreclr/jit/morph.cpp | 2 + src/coreclr/jit/namedintrinsiclist.h | 1 + src/coreclr/jit/targetamd64.h | 3 + src/coreclr/jit/targetarm.h | 3 + src/coreclr/jit/targetarm64.h | 3 + src/coreclr/jit/targetx86.h | 3 + src/coreclr/jit/valuenum.cpp | 6 +- .../Common/JitInterface/CorInfoHelpFunc.cs | 2 + .../tools/Common/JitInterface/CorInfoImpl.cs | 15 + .../JitInterface/CorInfoImpl_generated.cs | 149 +-- .../tools/Common/JitInterface/CorInfoTypes.cs | 13 + .../ThunkGenerator/ThunkInput.txt | 3 + .../aot/jitinterface/jitinterface_generated.h | 18 + .../superpmi-shim-collector/icorjitinfo.cpp | 11 + .../icorjitinfo_generated.cpp | 13 + .../icorjitinfo_generated.cpp | 11 + .../tools/superpmi/superpmi/icorjitinfo.cpp | 12 + src/coreclr/vm/appdomain.cpp | 1 + src/coreclr/vm/corelib.h | 11 +- src/coreclr/vm/dllimport.h | 7 + src/coreclr/vm/ecall.cpp | 9 + src/coreclr/vm/ecall.h | 2 + src/coreclr/vm/ilstubcache.cpp | 6 + src/coreclr/vm/jitinterface.cpp | 251 ++++- src/coreclr/vm/method.hpp | 2 + src/coreclr/vm/prestub.cpp | 2 + src/coreclr/vm/stubgen.cpp | 10 + src/coreclr/vm/stubgen.h | 2 + 64 files changed, 2433 insertions(+), 99 deletions(-) create mode 100644 docs/design/features/async-with-jit-generated-state-machines.md create mode 100644 src/coreclr/jit/async.cpp create mode 100644 src/coreclr/jit/async.h diff --git a/docs/design/features/async-with-jit-generated-state-machines.md b/docs/design/features/async-with-jit-generated-state-machines.md new file mode 100644 index 00000000000000..56b65068ce30b1 --- /dev/null +++ b/docs/design/features/async-with-jit-generated-state-machines.md @@ -0,0 +1,481 @@ +# Async with JIT generated state machines + +## Introduction + +This document describes a potential way to move the async transformation from Roslyn to the JIT. +The approach described here has the JIT transform async methods into a state machine in a way that such async methods can both suspend and be resumed. +As a goal, the design strives to make modifications such that the impact on the JIT's codegen of the synchronous path is minimized. + +To achieve that, we design around a more or less standard call/return stack pattern in the synchronous case, and then build on top of it the necessary infrastructure to handle resumption. + +## Dispatching continuations + +The main complication happens when continuations are to be dispatched. +When that occurs the stack logically inverts compared to the normal synchronous call: the deepest frame's continuation has to be called first, and once it returns, it has to ensure that it calls the next continuation. +There are multiple issues that the machinery today has to handle around this: +1. It has to move continuations to other stack frames to ensure the stack is not exhausted +2. It has to propagate exceptions thrown inside later frames to the next continuations; it cannot rely on normal EH to propagate exceptions, since the logical callers are no longer on the stack. + +I expect that 2. has some impact on the quality of the JIT generated code since the JIT can be more conservative inside EH constructs; however, the impact may not be that high given that much of the state is not expected to be live in the handler. + +To solve both issues we propose using an an async dispatcher. +The idea is to move the responsibility of invoking follow-up continuations from the async method to the async dispatcher. +This is very similar to the current tailcall via helpers approach. + +The async dispatcher will look something like (details omitted, full version of this is given below): + +```csharp +enum Suspended +{ + Yes, + No, +} + +struct Continuation +{ + public object Next; + public delegate* Resume; + public int State; +} + +public static void DispatchContinuations(object boxedContinuation, Exception exToPropagate) +{ + do + { + ref Continuation continuation = ref Unsafe.Unbox(boxedContinuation); + try + { + if (continuation.Resume(boxedContinuation, exToPropagate) == Suspended.Yes) + { + break; + } + } + catch (Exception ex) + { + exToPropagate = ex; + } + + boxedContinuation = continuation.Next; + } while (boxedContinuation != null); +} +``` + +The first time an asynchronous continuation is resumed it is expected that such a dispatcher is set up on the stack frame by the code doing the actual asynchronous resumption (for example, a socket receive callback). + +The JIT must set up all the stuff to make the above dispatching work. +1. It has to generate code to create a continuation when suspension happens. + Logically, the continuation captures the live state of the function that is suspending, including some way to figure out where in the function suspension happened. + Furthermore, the continuation must somehow be registered with the callee to know what to call when it is resuming. + +2. It has to generate code to allow resuming from a continuation. + This involves: + 1. Determining where in the function control needs to be transferred back to. + It may not always be possible to directly transfer control back, in particular when the function has EH, in which case we can employ a "switchboard" approach at the beginning of each `try` (OSR already must do this). + 2. Based on the above, determining what we expect to have been saved in the continuation and restoring registers/stack slots from it. + 3. If any resumption exception is present, invoking necessary EH constructs based on it. + The simple way to do this is just to rethrow the exception after transferring control back to the suspension point. + Optimizations here may be possible, for example by having the dispatcher skip continuations without EH. + +The continuation also saves space for the return value of the callee. + +## Resumption stubs +We expect to split the second step above into two components. +1. A part that is handled by the JIT as part of generating the async method and that becomes part of that function's codegen. + We'd like to ensure this part has minimal negative impact on the CQ of the async method to not harm the common synchronous path. + This code will involve reloading registers and stack locations, and transferring control back to the right place in the function. + It will mean the async methods have a special calling convention to identify whether they are being resumed or whether they are being invoked normally. +2. The second part is an IL stub created by the runtime on request of the JIT. + The continuations created by the JIT will point at such a resumption stub. + The resumption stubs are necessary because of the following: + 1. When the function is resumed, something must allocate stack space for the arguments and set registers containing arguments to sensible values, for stack scanning/GC purposes + 2. If the resumed function returns normally, then something must propagate the return value back to the next continuation, if required + 3. Since the async method has a special calling convention for resumption, something needs to know how to call it specially and where to pass the continuation. + +## Continuation layout +The `Continuation` struct shown above will be a prefix of the continuation struct created by the JIT. +For example, for the following case: + +```csharp +static async2 void Foo(int a, int b) +{ + BarReturn ret = await Bar(); + b += ret.val + a; + int ret2 = await Baz(); + Console.WriteLine(b); +} + +// uninlined +static async2 BarReturn Bar() +{ + return new BarReturn {val = 0}; +} + +// uninlined +static async2 int Baz() +{ + await Yield(); + return 0; +} +``` + +The runtime/JIT would create these continuation structures: + +```csharp +struct ContinuationFoo1 +{ + public object Next; + public delegate* Resume; + public int State; + public BarReturn Returned; + public int a; + public int b; +} + +struct ContinuationFoo2 +{ + public object Next; + public delegate* Resume; + public int State; + public int Returned; + // int a; not necessary since a is not live here + public int b; +} + +struct ContinuationBaz1 +{ + public object Next; + public delegate* Resume; + public int State; +} +``` + +and the resumption stubs may look like: +```csharp +object ResumeFoo1(object continuation, Exception exToPropagate) +{ + object newContinuation = null; + // No return value to propagate back since 'Foo' returns void + RuntimeHelpers.Async2Call(ref newContinuation); + Foo(); + return newContinuation; +} + +object ResumeBaz1(object continuation, Exception exToPropagate) +{ + object newContinuation = null; + RuntimeHelpers.Async2Call(ref newContinuation); + int bazResult = Baz(); + if (newContinuation == null) + { + // Points to ContinuationFoo2 always in the example above + Unsafe.Unbox>(continuation.Next).Result = bazResult; + } + + return newContinuation; +} + +struct ContinuationWithResult +{ + public object Next; + public delegate*, Exception, object> Resume; + public int State; + public T Result; +} +``` + +The continuations contain GC pointers, so we will need some way to create these types dynamically, with their corresponding GC info. +They will be lifted to the heap by boxing. + +## Custom calling convention + +To call the new async methods, a custom calling convention is necessary for two reasons: +1. The async method needs the continuation in case it is being resumed, or null in case it isn't +2. The async method returns its boxed continuation to the caller in case it suspended. + +For 1 we can model it after generic contexts by adding another argument, or we can use a special register (this might be problematic on arm32). +For 2 we expect to return the box in a separate register. + +`RuntimeHelpers.CallAsync2` used above is a JIT intrinsic that allows IL code to interface with the new calling convention. +It has signature: +```csharp +[Intrinsic] +internal static T CallAsync2(T callResult, ref Continuation continuation, out object newContinuation) +``` + +IL that calls this intrinsic is only considered valid if the first argument was pushed as a direct result of a `call`, `calli` or `callvirt` instruction. + +To interface with the new calling convention on the return side we also provide `RuntimeHelpers.Async2Return`: +```csharp +[Intrinsic] +internal static T Async2Return(T result, object continuation); + +[Intrinsic] +internal static void Async2Return(object continuation); +``` + +`RuntimeHelpers.Async2Return` must appear right before a `ret` IL instruction. + +Note that normal returns in an async2 method are equivalent to `return RuntimeHelpers.Async2Return(result, null)`.` + +## Synchronous calls +For synchronous calls we pay the following costs: +* On entry to the function, we must check if there is a continuation, meaning that we are resuming. + This cost can probably be avoided in common cases if we build some support for generating multiple entry points to a function in the JIT/VM. +* To call an async method we must pass a zero continuation. +* On return of an async method we must check whether it suspended or not. +* For functions with suspension points inside a a `try` block, we may pay additional cost at the beginning of every `try` and `finally` block. + These costs may be avoidable with more JIT work. + +## Interoperating with async2 calls +To integrate async2 with the existing ecosystem we need to be able to make calls between async2 and async1 methods. +Async1 in this case refers to anything the C# compiler would consider awaitable; however, here we reduce the scope and consider only `Task`, `Task`, `ValueTask` and `ValueTask`. + +Calling async1 methods is done through the runtime functions described in [David's document](https://dev.azure.com/dnceng/internal/_git/dotnet-runtime?path=/docs/design/features/runtime-handled-tasks.md&version=GBdev/davidwr/async2-experiment&_a=preview): +```csharp +public interface ICriticalNotifyCompletion2 : ICriticalNotifyCompletion +{ + bool IsCompleted { get; } + void GetResult(); +} +public interface ICriticalNotifyCompletion2 : ICriticalNotifyCompletion +{ + bool IsCompleted { get; } + TResult GetResult(); +} +public interface INotifyCompletion2 : INotifyCompletion +{ + bool IsCompleted { get; } + void GetResult(); +} +public interface INotifyCompletion2 : INotifyCompletion +{ + bool IsCompleted { get; } + TResult GetResult(); +} + +public static async2 void AwaitAwaiterFromRuntimeAsync(TAwaiter awaiter) where TAwaiter : INotifyCompletion2 +public static async2 TResult AwaitAwaiterFromRuntimeAsync(TAwaiter awaiter) where TAwaiter : INotifyCompletion2 +public static async2 void UnsafeAwaitAwaiterFromRuntimeAsync(TAwaiter awaiter) where TAwaiter : ICriticalNotifyCompletion2 +public static async2 TResult UnsafeAwaitAwaiterFromRuntimeAsync(TAwaiter awaiter) where TAwaiter : ICriticalNotifyCompletion2 +``` + +Calling an async2 method follows the same principles as in David's document, i.e. a thunk is created to wrap it in a Task. + +When a function suspends the JIT will link all continuations into a chain as the call stack unwinds. +To make this thread safe with `INotifyCompletion` we must delay calling `OnCompleted` until the continuations have all been linked. +Thus, the caller will be responsible for doing this; it is either done by the dispatcher, or done as part of the async1 -> async2 thunk. + +The following shows an example of what the async2 -> async1 adapting layer may look like. +It conceptually just stores the awaiter and prepares for the caller to call `OnCompleted`. + +```csharp +internal struct RuntimeAsyncAwaitState +{ + public Async2DispatcherBase Dispatcher; + public object SentinelContinuation; +} + +[ThreadStatic] +public static RuntimeAsyncAwaitState t_runtimeAsyncAwait; + +public static async2 TResult UnsafeAwaitAwaiterFromRuntimeAsync(TAwaiter awaiter) where TAwaiter : ICriticalNotifyCompletion2 +{ + if (awaiter.IsCompleted) + return awaiter.GetResult(); + + ref RuntimeAsyncAwaitState runtimeAsyncAwait = ref t_runtimeAsyncAwait; + + if (runtimeAsyncAwait.SentinelContinuation == null) + runtimeAsyncAwait.SentinelContinuation = new Continuation(); + + runtimeAsyncAwait.Dispatcher = new Async2Dispatcher(awaiter); + return RuntimeHelpers.Async2Return(default, runtimeAsyncAwait.SentinelContinuation); +} + +internal abstract class Async2DispatcherBase +{ + public abstract void Setup(object continuation); +} + +internal sealed class Async2Dispatcher : Async2DispatcherBase where TAwaiter : ICriticalNotifyCompletion2 +{ + private readonly object _continuation; + private readonly TAwaiter _awaiter; + + public Async2Dispatcher(TAwaiter awaiter) + { + _awaiter = awaiter; + } + + public override void Setup(object continuation) + { + _continuation = continuation; + _awaiter.UnsafeOnCompleted(Run); + } + + private void Run() + { + // Omitted + } +} +``` + +The function returns a sentinel continuation which the JIT will write the first continuation into such that the caller knows where the chain starts. + +The remaining part is to describe what the async1 -> async2 adapter layer looks like. +It consists of the following: +* The thunk that calls an async2 method and produces a Task +* The `Async2Dispatcher.Run` method that is the callback of the asynchronous operation +* The final implementation of the dispatcher + +Let us start with the thunk: +```csharp +public static Task ThunkAsync(ParameterType1 param1, ParameterType2 param2, ...) +{ + object boxedContinuation; + T result; + try + { + result = RuntimeHelpers.CallAsync2(TargetMethod(param1, param2, ...), ref Unsafe.NullRef(), out boxedContinuation); + } + catch (Exception ex) + { + return Task.FromException(ex); + } + + if (boxedContinuation == null) + return Task.FromResult(result); + + // Link in a final continuation that fires the TCS. + TaskCompletionSource tcs = new(); + ref Continuation continuation = ref Unsafe.Unbox(boxedContinuation); + continuation.Next = new TaskCompletionSourceContinuation + { + Resume = &ResumeTaskCompletionSource, + TCS = tcs, + }; + + // Now call OnCompleted on the awaiter we hit in the leaf. + ref RuntimeAsyncAwaitState runtimeAsyncAwait = ref t_runtimeAsyncAwait; + object headContinuation = Unsafe.Unbox(runtimeAsyncAwait.SentinelContinuation).Next; + runtimeAsyncAwait.Dispatcher.Setup(headContinuation); + return tcs.Task; +} + +private static object ResumeTaskCompletionSource(ref TaskCompletionSourceContinuation continuation, Exception ex) +{ + if (ex == null) + { + continuation.TCS.SetResult(continuation.Result); + } + else + { + continuation.TCS.SetException(ex); + } + + return null; +} + +public struct TaskCompletionSourceContinuation +{ + public object Next; + public delegate*, Exception, object> Resume; + public int State; + public T Result; + public TaskCompletionSource TCS; +} +``` +The thunk has a fast path in case the async2 method finished synchronously. +If not then the function creates a TCS and links in a synthetic continuation that will invoke this TCS. +It retrieves the head continuation (which is the successor of the sentinel continuation that was saved in the leaf), and finally it calls `Async2DispatcherBase.Setup` which is responsible for actually calling `OnCompleted` on the saved awaiter. + +The implementation of `Async2Dispatcher.Run` was omitted above. +This is the first function called by the asynchronous callback, and it needs to propagate the result into the continuation and start running the dispatcher. +```csharp +internal sealed class Async2Dispatcher : Async2DispatcherBase where TAwaiter : ICriticalNotifyCompletion2 +{ + private readonly object _continuation; + private readonly TAwaiter _awaiter; + + private void Run() + { + Exception exToPropagate = null; + try + { + TResult result = _awaiter.GetResult(); + Unsafe.Unbox>(_continuation).Result = result; + } + catch (Exception ex) + { + exToPropagate = ex; + } + + RuntimeHelpers.DispatchContinuations(_continuation, exToPropagate); + } +} +``` + +Finally, the dispatcher must be expanded to also handle registering the continuation chain for the asynchronous callback, in case a continuation suspends one more time: +```csharp +public static void DispatchContinuations(object boxedContinuation, Exception exToPropagate) +{ + do + { + ref Continuation continuation = ref Unsafe.Unbox(boxedContinuation); + try + { + object newContinuation = continuation.Resume(ref continuation, exToPropagate); + + if (newContinuation != null) + { + Unsafe.Unbox(newContinuation).Next = continuation.Next; + + ref RuntimeAsyncAwaitState runtimeAsyncAwait = ref t_runtimeAsyncAwait; + object headContinuation = Unsafe.Unbox(runtimeAsyncAwait.SentinelContinuation).Next; + runtimeAsyncAwait.Dispatcher.Setup(headContinuation); + return; + } + } + catch (Exception ex) + { + exToPropagate = ex; + } + + boxedContinuation = continuation.Next; + } while (boxedContinuation != null); +} +``` +It also needs to transfer the next continuation stored in the previous (now invoked continuation) to the newly returned one. + +## Thoughts/questions + +1. Is it better to allocate a "mega"-continuation and reuse it? It will mean less type loader traffic, but more stuff kept on the heap overall. +It will also allow us to skip transferring the "Next" from the previous continuation when a resumed continuation suspends again +2. We know address-exposed locals become unexposed at suspension points. +How do we make use of this? Likely we need a custom liveness pass. + +## Required JIT work + +1. A pass that walks the IR and finds all awaited async calls. + At these points we must identify live state (so run some kind of liveness) and generate code to create continuations in case the callee suspends. + We must also create the IR that restores the live state from the continuation, and throw the exception. +3. Prolog work to handle "resume" vs "start". +4. Work to handle suspension and resumption within `try` + +Some things to consider: +* Should the pass happen early or late? Early means less live state, but likely impact on CQ. + In particular the resumption code is going to create a lot of synthetic control flow that I expect would have significant CQ impact. + Doing it late will probably make it harder to introduce the boxing and generate the registration IR. + Also, doing it later means more chance for optimizations to create illegal constructs (e.g. local addresses that span suspension points). +* Doing it late may allow us to simply jump directly into nested `try` blocks and exit `try` blocks without invoking finallies, though I am not sure about that. +* We need to be able to introduce control flow around awaited async calls. + If done early we can use `gtSplitTree`. + If done late we need to track LIR edges and introduce temps. +* How do we represent the new calls internally? They effectively define two things (return value and the "suspended"). + ~~We can probably cheat and consider these as always defining an additional pseudo-local.~~ No we cannot, because encoding that in LSRA and codegen is a nightmare when there is no tree to attach the RPs to. +* How about if the JIT creates local addresses that span across awaits? Do we need to represent the calls as opaque defs of all live state? That seems conservative and might not even fully avoid the problem. + +## Optional JIT work + +1. Support for generating multiple different entry points. +2. Deal with the fact that liveness of address exposed locals is going to be very conservative. \ No newline at end of file diff --git a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs index a931499ac7e2dc..5ee4958fa2a91d 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs @@ -380,6 +380,13 @@ private static unsafe void DispatchTailCalls( } } } + + private static object AllocContinuation(object prevContinuation, nuint numGCRefs, nuint dataSize) + { + object newContinuation = new Continuation { Data = new byte[dataSize], GCData = new object[numGCRefs] }; + Unsafe.Unbox(prevContinuation).Next = newContinuation; + return newContinuation; + } } // Helper class to assist with unsafe pinning of arbitrary objects. // It's used by VM code. @@ -660,4 +667,13 @@ internal unsafe struct TailCallTls public PortableTailCallFrame* Frame; public IntPtr ArgBuffer; } + + internal unsafe struct Continuation + { + public object Next; + public delegate* Resume; + public uint State; + public byte[] Data; + public object GCData; + } } diff --git a/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs b/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs index 431c2f724a02ba..18f9073c982326 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs @@ -1296,5 +1296,8 @@ internal static void CheckStringLength(uint length) [Intrinsic] [MethodImpl(MethodImplOptions.InternalCall)] internal static extern IntPtr NextCallReturnAddress(); + + [Intrinsic] + internal static object? Async2CallContinuation() => null; } // class StubHelpers } diff --git a/src/coreclr/inc/clrconfigvalues.h b/src/coreclr/inc/clrconfigvalues.h index c556d45c495894..1ee66824c6a233 100644 --- a/src/coreclr/inc/clrconfigvalues.h +++ b/src/coreclr/inc/clrconfigvalues.h @@ -800,6 +800,11 @@ RETAIL_CONFIG_DWORD_INFO(EXTERNAL_EnableArm64Rcpc, W("EnableArm64Rc RETAIL_CONFIG_DWORD_INFO(EXTERNAL_EnableArm64Rcpc2, W("EnableArm64Rcpc2"), 1, "Allows Arm64 Rcpc2+ hardware intrinsics to be disabled") #endif +/// +/// Runtime async +/// +RETAIL_CONFIG_DWORD_INFO(EXTERNAL_RuntimeAsyncViaJitGeneratedStateMachines, W("RuntimeAsyncViaJitGeneratedStateMachines"), 0, "Use JIT generated state machines instead of unwinding-based runtime async") + /// /// Uncategorized /// diff --git a/src/coreclr/inc/corinfo.h b/src/coreclr/inc/corinfo.h index 97e0d385753ce3..610fd5112a4f1c 100644 --- a/src/coreclr/inc/corinfo.h +++ b/src/coreclr/inc/corinfo.h @@ -662,6 +662,8 @@ enum CorInfoHelpFunc CORINFO_HELP_VALIDATE_INDIRECT_CALL, // CFG: Validate function pointer CORINFO_HELP_DISPATCH_INDIRECT_CALL, // CFG: Validate and dispatch to pointer + CORINFO_HELP_ALLOC_CONTINUATION, + CORINFO_HELP_COUNT, }; @@ -957,6 +959,7 @@ enum CorInfoGCType enum CorInfoClassId { CLASSID_SYSTEM_OBJECT, + CLASSID_SYSTEM_BYTE, CLASSID_TYPED_BYREF, CLASSID_TYPE_HANDLE, CLASSID_FIELD_HANDLE, @@ -1820,6 +1823,22 @@ struct CORINFO_EE_INFO CORINFO_OS osType; }; +struct CORINFO_ASYNC2_INFO +{ + // Class handle for System.Runtime.CompilerServices.Continuation + CORINFO_CLASS_HANDLE continuationClsHnd; + // 'Next' field + CORINFO_FIELD_HANDLE continuationNextFldHnd; + // 'Resume' field + CORINFO_FIELD_HANDLE continuationResumeFldHnd; + // 'State' field + CORINFO_FIELD_HANDLE continuationStateFldHnd; + // 'Data' field + CORINFO_FIELD_HANDLE continuationDataFldHnd; + // 'GCData' field + CORINFO_FIELD_HANDLE continuationGCDataFldHnd; +}; + // Flags passed from JIT to runtime. enum CORINFO_GET_TAILCALL_HELPERS_FLAGS { @@ -2997,6 +3016,10 @@ class ICorStaticInfo // Returns name of the JIT timer log virtual const char16_t *getJitTimeLogFilename() = 0; + virtual void getAsync2Info( + CORINFO_ASYNC2_INFO* pAsync2InfoOut + ) = 0; + /*********************************************************************************/ // // Diagnostic methods @@ -3329,6 +3352,8 @@ class ICorDynamicInfo : public ICorStaticInfo CORINFO_TAILCALL_HELPERS* pResult ) = 0; + virtual CORINFO_METHOD_HANDLE getAsyncResumptionStub() = 0; + // Optionally, convert calli to regular method call. This is for PInvoke argument marshalling. virtual bool convertPInvokeCalliToCall( CORINFO_RESOLVED_TOKEN * pResolvedToken, diff --git a/src/coreclr/inc/icorjitinfoimpl_generated.h b/src/coreclr/inc/icorjitinfoimpl_generated.h index 4fd79ef9303c71..a00826e252434e 100644 --- a/src/coreclr/inc/icorjitinfoimpl_generated.h +++ b/src/coreclr/inc/icorjitinfoimpl_generated.h @@ -470,6 +470,9 @@ bool runWithSPMIErrorTrap( void getEEInfo( CORINFO_EE_INFO* pEEInfoOut) override; +void getAsync2Info( + CORINFO_ASYNC2_INFO* pAsync2InfoOut) override; + const char16_t* getJitTimeLogFilename() override; mdMethodDef getMethodDefFromMethod( @@ -634,6 +637,8 @@ bool getTailCallHelpers( CORINFO_GET_TAILCALL_HELPERS_FLAGS flags, CORINFO_TAILCALL_HELPERS* pResult) override; +CORINFO_METHOD_HANDLE getAsyncResumptionStub() override; + bool convertPInvokeCalliToCall( CORINFO_RESOLVED_TOKEN* pResolvedToken, bool mustConvert) override; diff --git a/src/coreclr/inc/jiteeversionguid.h b/src/coreclr/inc/jiteeversionguid.h index 70960ff577ee8a..b394ac0f49109a 100644 --- a/src/coreclr/inc/jiteeversionguid.h +++ b/src/coreclr/inc/jiteeversionguid.h @@ -43,11 +43,11 @@ typedef const GUID *LPCGUID; #define GUID_DEFINED #endif // !GUID_DEFINED -constexpr GUID JITEEVersionIdentifier = { /* a2974440-e8ee-4d95-9e6e-799a330be1a0 */ - 0xa2974440, - 0xe8ee, - 0x4d95, - {0x9e, 0x6e, 0x79, 0x9a, 0x33, 0x0b, 0xe1, 0xa0} +constexpr GUID JITEEVersionIdentifier = { /* 6834d668-2cd6-491e-9e13-28675a28da97 */ + 0x6834d668, + 0x2cd6, + 0x491e, + {0x9e, 0x13, 0x28, 0x67, 0x5a, 0x28, 0xda, 0x97} }; ////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/src/coreclr/inc/jithelpers.h b/src/coreclr/inc/jithelpers.h index 06d09a1b5e15ab..698938dafbc10a 100644 --- a/src/coreclr/inc/jithelpers.h +++ b/src/coreclr/inc/jithelpers.h @@ -359,6 +359,8 @@ JITHELPER(CORINFO_HELP_DISPATCH_INDIRECT_CALL, NULL, CORINFO_HELP_SIG_REG_ONLY) #endif + DYNAMICJITHELPER(CORINFO_HELP_ALLOC_CONTINUATION, NULL, CORINFO_HELP_SIG_REG_ONLY) + #undef JITHELPER #undef DYNAMICJITHELPER #undef JITHELPER diff --git a/src/coreclr/jit/CMakeLists.txt b/src/coreclr/jit/CMakeLists.txt index c2d873aca7bd1b..9c6a864a26e805 100644 --- a/src/coreclr/jit/CMakeLists.txt +++ b/src/coreclr/jit/CMakeLists.txt @@ -90,6 +90,7 @@ endif(CLR_CMAKE_TARGET_WIN32) set( JIT_SOURCES alloc.cpp + async.cpp assertionprop.cpp bitset.cpp block.cpp @@ -281,6 +282,7 @@ set( JIT_HEADERS ../inc/corjithost.h _typeinfo.h alloc.h + async.h arraystack.h bitset.h layout.h diff --git a/src/coreclr/jit/ICorJitInfo_names_generated.h b/src/coreclr/jit/ICorJitInfo_names_generated.h index fcc982f7b0be6a..3d9aaefe1c1f9a 100644 --- a/src/coreclr/jit/ICorJitInfo_names_generated.h +++ b/src/coreclr/jit/ICorJitInfo_names_generated.h @@ -116,6 +116,7 @@ DEF_CLR_API(getHFAType) DEF_CLR_API(runWithErrorTrap) DEF_CLR_API(runWithSPMIErrorTrap) DEF_CLR_API(getEEInfo) +DEF_CLR_API(getAsync2Info) DEF_CLR_API(getJitTimeLogFilename) DEF_CLR_API(getMethodDefFromMethod) DEF_CLR_API(printMethodName) @@ -155,6 +156,7 @@ DEF_CLR_API(getFieldThreadLocalStoreID) DEF_CLR_API(GetDelegateCtor) DEF_CLR_API(MethodCompileComplete) DEF_CLR_API(getTailCallHelpers) +DEF_CLR_API(getAsyncResumptionStub) DEF_CLR_API(convertPInvokeCalliToCall) DEF_CLR_API(notifyInstructionSetUsage) DEF_CLR_API(updateEntryPointForTailCall) diff --git a/src/coreclr/jit/ICorJitInfo_wrapper_generated.hpp b/src/coreclr/jit/ICorJitInfo_wrapper_generated.hpp index 88cca4f6f8cff3..f846ef774416e4 100644 --- a/src/coreclr/jit/ICorJitInfo_wrapper_generated.hpp +++ b/src/coreclr/jit/ICorJitInfo_wrapper_generated.hpp @@ -1107,6 +1107,14 @@ void WrapICorJitInfo::getEEInfo( API_LEAVE(getEEInfo); } +void WrapICorJitInfo::getAsync2Info( + CORINFO_ASYNC2_INFO* pAsync2InfoOut) +{ + API_ENTER(getAsync2Info); + wrapHnd->getAsync2Info(pAsync2InfoOut); + API_LEAVE(getAsync2Info); +} + const char16_t* WrapICorJitInfo::getJitTimeLogFilename() { API_ENTER(getJitTimeLogFilename); @@ -1497,6 +1505,14 @@ bool WrapICorJitInfo::getTailCallHelpers( return temp; } +CORINFO_METHOD_HANDLE WrapICorJitInfo::getAsyncResumptionStub() +{ + API_ENTER(getAsyncResumptionStub); + CORINFO_METHOD_HANDLE temp = wrapHnd->getAsyncResumptionStub(); + API_LEAVE(getAsyncResumptionStub); + return temp; +} + bool WrapICorJitInfo::convertPInvokeCalliToCall( CORINFO_RESOLVED_TOKEN* pResolvedToken, bool mustConvert) diff --git a/src/coreclr/jit/async.cpp b/src/coreclr/jit/async.cpp new file mode 100644 index 00000000000000..3d94d6fb5ee36f --- /dev/null +++ b/src/coreclr/jit/async.cpp @@ -0,0 +1,999 @@ +#include "jitpch.h" +#include "jitstd/algorithm.h" +#include "async.h" + +PhaseStatus Compiler::TransformAsync2() +{ + assert(compIsAsync2()); + + Async2Transformation transformation(this); + return transformation.Run(); +} + +PhaseStatus Async2Transformation::Run() +{ + ArrayStack worklist(m_comp->getAllocator(CMK_Async2)); + + for (BasicBlock* block : m_comp->Blocks()) + { + for (GenTree* tree : LIR::AsRange(block)) + { + if (tree->IsCall() && tree->AsCall()->IsAsync2()) + { + JITDUMP(FMT_BB " contains await(s)\n", block->bbNum); + worklist.Push(block); + break; + } + } + } + + JITDUMP("Found %d blocks with awaits\n", worklist.Height()); + + if (worklist.Height() <= 0) + { + return PhaseStatus::MODIFIED_NOTHING; + } + + m_resumeStub = m_comp->info.compCompHnd->getAsyncResumptionStub(); + m_comp->info.compCompHnd->getFunctionFixedEntryPoint(m_resumeStub, false, &m_resumeStubLookup); + + m_returnedContinuationVar = m_comp->lvaGrabTemp(false DEBUGARG("returned continuation")); + m_comp->lvaGetDesc(m_returnedContinuationVar)->lvType = TYP_REF; + m_newContinuationVar = m_comp->lvaGrabTemp(false DEBUGARG("new continuation")); + m_comp->lvaGetDesc(m_newContinuationVar)->lvType = TYP_REF; + + m_comp->info.compCompHnd->getAsync2Info(&m_async2Info); + m_objectClsHnd = m_comp->info.compCompHnd->getBuiltinClass(CLASSID_SYSTEM_OBJECT); + m_byteClsHnd = m_comp->info.compCompHnd->getBuiltinClass(CLASSID_SYSTEM_BYTE); + + m_comp->lvaComputeRefCounts(true, false); + + m_comp->fgLocalVarLiveness(); + m_livenessLvaCount = m_comp->lvaCount; + + VarSetOps::AssignNoCopy(m_comp, m_comp->compCurLife, VarSetOps::MakeEmpty(m_comp)); + + TreeLifeUpdater life(m_comp); + jitstd::vector defs(m_comp->getAllocator(CMK_Async2)); + + for (int i = 0; i < worklist.Height(); i++) + { + assert(defs.size() == 0); + + BasicBlock* block = worklist.Bottom(i); + VarSetOps::Assign(m_comp, m_comp->compCurLife, block->bbLiveIn); + + bool any; + do + { + any = false; + for (GenTree* tree : LIR::AsRange(block)) + { + tree->VisitOperands([&defs](GenTree* op) { + if (op->IsValue()) + { + for (size_t i = defs.size(); i > 0; i--) + { + if (op == defs[i - 1]) + { + defs[i - 1] = defs[defs.size() - 1]; + defs.erase(defs.begin() + (defs.size() - 1), defs.end()); + break; + } + } + } + + return GenTree::VisitResult::Continue; + }); + + life.UpdateLife(tree); + + if (tree->IsCall() && tree->AsCall()->IsAsync2()) + { + Transform(block, tree->AsCall(), defs, &block); + defs.clear(); + any = true; + break; + } + + if (tree->IsValue() && !tree->IsUnusedValue()) + { + defs.push_back(tree); + } + } + } while (any); + } + + CreateResumptionSwitch(); + + return PhaseStatus::MODIFIED_EVERYTHING; +} + +void Async2Transformation::Transform(BasicBlock* block, + GenTreeCall* call, + jitstd::vector& defs, + BasicBlock** pRemainder) +{ +#ifdef DEBUG + if (m_comp->verbose) + { + printf("Processing call [%06u]\n", Compiler::dspTreeID(call)); + printf(" %zu live LIR edges\n", defs.size()); + + if (defs.size() > 0) + { + const char* sep = " "; + for (GenTree* tree : defs) + { + printf("%s[%06u] (%s)", sep, Compiler::dspTreeID(tree), varTypeName(tree->TypeGet())); + sep = ", "; + } + + printf("\n"); + } + } +#endif + + m_liveLocals.clear(); + + for (unsigned lclNum = 0; lclNum < m_livenessLvaCount; lclNum++) + { + if (!IsLive(lclNum)) + { + continue; + } + + m_liveLocals.push_back(LiveLocalInfo(lclNum)); + } + + LiftLIREdges(block, call, defs, m_liveLocals); + +#ifdef DEBUG + if (m_comp->verbose) + { + printf(" %zu live locals\n", m_liveLocals.size()); + + if (m_liveLocals.size() > 0) + { + const char* sep = " "; + for (LiveLocalInfo& inf : m_liveLocals) + { + printf("%sV%02u (%s)", sep, inf.LclNum, varTypeName(m_comp->lvaGetDesc(inf.LclNum)->TypeGet())); + sep = ", "; + } + + printf("\n"); + } + } +#endif + + for (LiveLocalInfo& inf : m_liveLocals) + { + LclVarDsc* dsc = m_comp->lvaGetDesc(inf.LclNum); + + if ((dsc->TypeGet() == TYP_STRUCT) || dsc->IsImplicitByRef()) + { + ClassLayout* layout = dsc->GetLayout(); + assert(!layout->HasGCByRef()); + + if (layout->IsBlockLayout()) + { + inf.Alignment = 1; + inf.DataSize = layout->GetSize(); + inf.GCDataCount = 0; + } + else + { + inf.Alignment = m_comp->info.compCompHnd->getClassAlignmentRequirement(layout->GetClassHandle()); + if ((layout->GetGCPtrCount() * TARGET_POINTER_SIZE) == layout->GetSize()) + { + inf.DataSize = 0; + } + else + { + inf.DataSize = layout->GetSize(); + } + + inf.GCDataCount = layout->GetGCPtrCount(); + } + } + else if (dsc->TypeGet() == TYP_REF) + { + inf.Alignment = TARGET_POINTER_SIZE; + inf.DataSize = 0; + inf.GCDataCount = 1; + } + else + { + assert(dsc->TypeGet() != TYP_BYREF); + + inf.Alignment = genTypeAlignments[dsc->TypeGet()]; + inf.DataSize = genTypeSize(dsc); + inf.GCDataCount = 0; + } + } + + jitstd::sort(m_liveLocals.begin(), m_liveLocals.end(), [](const LiveLocalInfo& lhs, const LiveLocalInfo& rhs) { + if (lhs.Alignment == rhs.Alignment) + { + // Prefer lowest local num first for same alignment. + return lhs.LclNum < rhs.LclNum; + } + + // Otherwise prefer highest alignment first. + return lhs.Alignment > rhs.Alignment; + }); + + unsigned dataSize = 0; + // Result value is currently always stored boxed in the continuation as it + // needs to be written by the resumption stub and by the asynchronous + // callbacks. If we specialized the continuation type we could avoid this. + unsigned gcRefsCount = call->gtReturnType != TYP_VOID ? 1 : 0; + + for (LiveLocalInfo& inf : m_liveLocals) + { + dataSize = roundUp(dataSize, inf.Alignment); + + inf.DataOffset = dataSize; + inf.GCDataIndex = gcRefsCount; + + dataSize += inf.DataSize; + gcRefsCount += inf.GCDataCount; + } + +#ifdef DEBUG + if (m_comp->verbose) + { + printf(" Continuation layout (%u bytes, %u GC pointers):\n", dataSize, gcRefsCount); + for (LiveLocalInfo& inf : m_liveLocals) + { + printf(" +%03u (GC@+%02u) V%02u: %u bytes, %u GC pointers\n", inf.DataOffset, inf.GCDataIndex, + inf.LclNum, inf.DataSize, inf.GCDataCount); + } + } +#endif + + unsigned stateNum = (unsigned)m_resumptionBBs.size(); + JITDUMP(" Assigned state %u\n", stateNum); + + GenTreeLclVarCommon* storeResultNode = nullptr; + GenTree* insertAfter = call; + + if (!call->TypeIs(TYP_VOID) && !call->IsUnusedValue()) + { + assert(call->gtNext != nullptr); + if (!call->gtNext->OperIsLocalStore() || (call->gtNext->Data() != call)) + { + LIR::Use use; + bool gotUse = LIR::AsRange(block).TryGetUse(call, &use); + assert(gotUse); + + use.ReplaceWithLclVar(m_comp); + } + + assert(call->gtNext->OperIsLocalStore() && (call->gtNext->Data() == call)); + storeResultNode = call->gtNext->AsLclVarCommon(); + insertAfter = call->gtNext; + } + + GenTree* continuationPhysReg = m_comp->gtNewPhysRegNode(REG_ASYNC_CONTINUATION_RET, TYP_REF); + GenTree* storeContinuation = m_comp->gtNewStoreLclVarNode(m_returnedContinuationVar, continuationPhysReg); + LIR::AsRange(block).InsertAfter(insertAfter, continuationPhysReg, storeContinuation); + + GenTree* null = m_comp->gtNewNull(); + GenTree* returnedContinuation = m_comp->gtNewLclvNode(m_returnedContinuationVar, TYP_REF); + GenTree* neNull = m_comp->gtNewOperNode(GT_NE, TYP_INT, returnedContinuation, null); + GenTree* jtrue = m_comp->gtNewOperNode(GT_JTRUE, TYP_VOID, neNull); + + LIR::AsRange(block).InsertAfter(storeContinuation, null, returnedContinuation, neNull, jtrue); + BasicBlock* remainder = m_comp->fgSplitBlockAfterNode(block, jtrue); + *pRemainder = remainder; + + assert(block->KindIs(BBJ_NONE) && block->NextIs(remainder)); + + if (m_lastSuspensionBB == nullptr) + { + m_lastSuspensionBB = m_comp->fgLastBBInMainFunction(); + } + + BasicBlock* retBB = m_comp->fgNewBBafter(BBJ_RETURN, m_lastSuspensionBB, true); + retBB->bbSetRunRarely(); + m_lastSuspensionBB = retBB; + + block->SetJumpKindAndTarget(BBJ_COND, retBB DEBUGARG(m_comp)); + + m_comp->fgAddRefPred(retBB, block); + + // Allocate continuation + returnedContinuation = m_comp->gtNewLclvNode(m_returnedContinuationVar, TYP_REF); + GenTree* gcRefsCountNode = m_comp->gtNewIconNode((ssize_t)gcRefsCount, TYP_I_IMPL); + GenTree* dataSizeNode = m_comp->gtNewIconNode((ssize_t)dataSize, TYP_I_IMPL); + GenTreeCall* allocContinuation = m_comp->gtNewHelperCallNode(CORINFO_HELP_ALLOC_CONTINUATION, TYP_REF, + returnedContinuation, gcRefsCountNode, dataSizeNode); + + m_comp->compCurBB = retBB; + m_comp->fgMorphTree(allocContinuation); + + LIR::AsRange(retBB).InsertAtEnd(LIR::SeqTree(m_comp, allocContinuation)); + + GenTree* storeNewContinuation = m_comp->gtNewStoreLclVarNode(m_newContinuationVar, allocContinuation); + LIR::AsRange(retBB).InsertAtEnd(storeNewContinuation); + + // Fill in 'Resume' + GenTree* newContinuation = m_comp->gtNewLclvNode(m_newContinuationVar, TYP_REF); + unsigned resumeOffset = + TARGET_POINTER_SIZE + m_comp->info.compCompHnd->getFieldOffset(m_async2Info.continuationResumeFldHnd); + GenTree* resumeStubAddr = CreateResumptionStubAddrTree(); + GenTree* storeResume = StoreAtOffset(newContinuation, resumeOffset, resumeStubAddr); + LIR::AsRange(retBB).InsertAtEnd(LIR::SeqTree(m_comp, storeResume)); + + // Fill in 'state' + newContinuation = m_comp->gtNewLclvNode(m_newContinuationVar, TYP_REF); + unsigned stateOffset = + TARGET_POINTER_SIZE + m_comp->info.compCompHnd->getFieldOffset(m_async2Info.continuationStateFldHnd); + GenTree* stateNumNode = m_comp->gtNewIconNode((ssize_t)stateNum, TYP_I_IMPL); + GenTree* store = StoreAtOffset(newContinuation, stateOffset, stateNumNode); + LIR::AsRange(retBB).InsertAtEnd(LIR::SeqTree(m_comp, store)); + + // Fill in GC pointers + if (gcRefsCount > 0) + { + unsigned objectArrLclNum = GetGCDataArrayVar(); + + newContinuation = m_comp->gtNewLclvNode(m_newContinuationVar, TYP_REF); + unsigned gcDataOffset = + TARGET_POINTER_SIZE + m_comp->info.compCompHnd->getFieldOffset(m_async2Info.continuationGCDataFldHnd); + GenTree* gcDataInd = LoadFromOffset(newContinuation, gcDataOffset, TYP_REF); + GenTree* storeAllocedObjectArr = m_comp->gtNewStoreLclVarNode(objectArrLclNum, gcDataInd); + LIR::AsRange(retBB).InsertAtEnd(LIR::SeqTree(m_comp, storeAllocedObjectArr)); + + for (LiveLocalInfo& inf : m_liveLocals) + { + if (inf.GCDataCount <= 0) + { + continue; + } + + LclVarDsc* dsc = m_comp->lvaGetDesc(inf.LclNum); + if (dsc->TypeGet() == TYP_REF) + { + GenTree* value = m_comp->gtNewLclvNode(inf.LclNum, TYP_REF); + GenTree* objectArr = m_comp->gtNewLclvNode(objectArrLclNum, TYP_REF); + GenTree* store = + StoreAtOffset(objectArr, OFFSETOF__CORINFO_Array__data + (inf.GCDataIndex * TARGET_POINTER_SIZE), + value); + LIR::AsRange(retBB).InsertAtEnd(LIR::SeqTree(m_comp, store)); + } + else + { + assert((dsc->TypeGet() == TYP_STRUCT) || dsc->IsImplicitByRef()); + ClassLayout* layout = dsc->GetLayout(); + unsigned numSlots = layout->GetSlotCount(); + for (unsigned i = 0; i < numSlots; i++) + { + var_types gcPtrType = layout->GetGCPtrType(i); + assert((gcPtrType == TYP_I_IMPL) || (gcPtrType == TYP_REF)); + if (gcPtrType != TYP_REF) + { + continue; + } + + GenTree* value; + if (dsc->IsImplicitByRef()) + { + GenTree* baseAddr = m_comp->gtNewLclvNode(inf.LclNum, dsc->TypeGet()); + value = LoadFromOffset(baseAddr, i * TARGET_POINTER_SIZE, TYP_REF); + } + else + { + value = m_comp->gtNewLclFldNode(inf.LclNum, TYP_REF, i * TARGET_POINTER_SIZE); + } + + GenTree* objectArr = m_comp->gtNewLclvNode(objectArrLclNum, TYP_REF); + unsigned offset = OFFSETOF__CORINFO_Array__data + ((inf.GCDataIndex + i) * TARGET_POINTER_SIZE); + GenTree* store = StoreAtOffset(objectArr, offset, value); + LIR::AsRange(retBB).InsertAtEnd(LIR::SeqTree(m_comp, store)); + + if (inf.DataSize > 0) + { + // Null out the GC field in preparation of storing the rest. + GenTree* null = m_comp->gtNewNull(); + + if (dsc->IsImplicitByRef()) + { + GenTree* baseAddr = m_comp->gtNewLclvNode(inf.LclNum, dsc->TypeGet()); + store = StoreAtOffset(baseAddr, i * TARGET_POINTER_SIZE, null); + } + else + { + store = m_comp->gtNewStoreLclFldNode(inf.LclNum, TYP_REF, i * TARGET_POINTER_SIZE, null); + } + + LIR::AsRange(retBB).InsertAtEnd(LIR::SeqTree(m_comp, store)); + } + } + + m_comp->lvaSetVarDoNotEnregister(inf.LclNum DEBUGARG(DoNotEnregisterReason::LocalField)); + } + } + } + + // Store data in byte[] + if (dataSize > 0) + { + unsigned byteArrLclNum = GetDataArrayVar(); + + GenTree* newContinuation = m_comp->gtNewLclvNode(m_newContinuationVar, TYP_REF); + unsigned dataOffset = + TARGET_POINTER_SIZE + m_comp->info.compCompHnd->getFieldOffset(m_async2Info.continuationDataFldHnd); + GenTree* dataOffsetNode = m_comp->gtNewIconNode((ssize_t)dataOffset, TYP_I_IMPL); + GenTree* dataAddr = m_comp->gtNewOperNode(GT_ADD, TYP_BYREF, newContinuation, dataOffsetNode); + GenTree* dataInd = m_comp->gtNewIndir(TYP_REF, dataAddr, GTF_IND_NONFAULTING); + GenTree* storeAllocedByteArr = m_comp->gtNewStoreLclVarNode(byteArrLclNum, dataInd); + LIR::AsRange(retBB).InsertAtEnd(newContinuation, dataOffsetNode, dataAddr, dataInd, storeAllocedByteArr); + + // Fill in data + for (LiveLocalInfo& inf : m_liveLocals) + { + if (inf.DataSize <= 0) + { + continue; + } + + LclVarDsc* dsc = m_comp->lvaGetDesc(inf.LclNum); + + GenTree* byteArr = m_comp->gtNewLclvNode(byteArrLclNum, TYP_REF); + unsigned offset = OFFSETOF__CORINFO_Array__data + inf.DataOffset; + + GenTree* value; + if (dsc->IsImplicitByRef()) + { + GenTree* baseAddr = m_comp->gtNewLclvNode(inf.LclNum, dsc->TypeGet()); + value = m_comp->gtNewBlkIndir(dsc->GetLayout(), baseAddr, GTF_IND_NONFAULTING); + } + else + { + value = m_comp->gtNewLclvNode(inf.LclNum, genActualType(dsc->TypeGet())); + } + + GenTree* store; + if ((dsc->TypeGet() == TYP_STRUCT) || dsc->IsImplicitByRef()) + { + GenTree* cns = m_comp->gtNewIconNode((ssize_t)offset, TYP_I_IMPL); + GenTree* addr = m_comp->gtNewOperNode(GT_ADD, TYP_BYREF, byteArr, cns); + // This is to heap, but all GC refs are nulled out already, so we can skip the write barrier. + // TODO-CQ: Backend does not care about GTF_IND_TGT_NOT_HEAP for STORE_BLK. + store = m_comp->gtNewStoreBlkNode(dsc->GetLayout(), addr, value, + GTF_IND_NONFAULTING | GTF_IND_TGT_NOT_HEAP); + } + else + { + store = StoreAtOffset(byteArr, offset, value); + } + + LIR::AsRange(retBB).InsertAtEnd(LIR::SeqTree(m_comp, store)); + } + } + + newContinuation = m_comp->gtNewLclvNode(m_newContinuationVar, TYP_REF); + GenTree* ret = m_comp->gtNewOperNode(GT_RETURN_SUSPEND, TYP_VOID, newContinuation); + LIR::AsRange(retBB).InsertAtEnd(newContinuation, ret); + + JITDUMP(" Created suspension BB " FMT_BB "\n", retBB->bbNum); + + BasicBlock* prevResumptionBB; + if (m_resumptionBBs.size() == 0) + { + prevResumptionBB = m_comp->fgLastBBInMainFunction(); + } + else + { + prevResumptionBB = m_resumptionBBs[m_resumptionBBs.size() - 1]; + } + + BasicBlock* resumeBB = m_comp->fgNewBBafter(BBJ_ALWAYS, prevResumptionBB, true, remainder); + resumeBB->bbSetRunRarely(); + resumeBB->bbFlags |= BBF_ASYNC_RESUMPTION; + + m_comp->fgAddRefPred(remainder, resumeBB); + + if (dataSize > 0) + { + unsigned byteArrLclNum = GetDataArrayVar(); + + GenTree* newContinuation = m_comp->gtNewLclvNode(m_newContinuationVar, TYP_REF); + unsigned dataOffset = + TARGET_POINTER_SIZE + m_comp->info.compCompHnd->getFieldOffset(m_async2Info.continuationDataFldHnd); + GenTree* dataOffsetNode = m_comp->gtNewIconNode((ssize_t)dataOffset, TYP_I_IMPL); + GenTree* dataAddr = m_comp->gtNewOperNode(GT_ADD, TYP_BYREF, newContinuation, dataOffsetNode); + GenTree* dataInd = m_comp->gtNewIndir(TYP_REF, dataAddr, GTF_IND_NONFAULTING); + GenTree* storeAllocedByteArr = m_comp->gtNewStoreLclVarNode(byteArrLclNum, dataInd); + LIR::AsRange(resumeBB).InsertAtEnd(newContinuation, dataOffsetNode, dataAddr, dataInd, storeAllocedByteArr); + + // Copy data + for (LiveLocalInfo& inf : m_liveLocals) + { + if (inf.DataSize <= 0) + { + continue; + } + + LclVarDsc* dsc = m_comp->lvaGetDesc(inf.LclNum); + + GenTree* byteArr = m_comp->gtNewLclvNode(byteArrLclNum, TYP_REF); + unsigned offset = OFFSETOF__CORINFO_Array__data + inf.DataOffset; + GenTree* cns = m_comp->gtNewIconNode((ssize_t)offset, TYP_I_IMPL); + GenTree* addr = m_comp->gtNewOperNode(GT_ADD, TYP_BYREF, byteArr, cns); + + GenTree* value; + if ((dsc->TypeGet() == TYP_STRUCT) || dsc->IsImplicitByRef()) + { + value = m_comp->gtNewBlkIndir(dsc->GetLayout(), addr, GTF_IND_NONFAULTING); + } + else + { + value = m_comp->gtNewIndir(dsc->TypeGet(), addr, GTF_IND_NONFAULTING); + } + + GenTree* store; + if (dsc->IsImplicitByRef()) + { + GenTree* baseAddr = m_comp->gtNewLclvNode(inf.LclNum, dsc->TypeGet()); + // TODO-CQ: Incoming data has no non-null GC refs, so this does not need write barriers. + // Backend does not handle GTF_IND_TGT_NOT_HEAP for STORE_BLK. + store = m_comp->gtNewStoreBlkNode(dsc->GetLayout(), baseAddr, value, + GTF_IND_NONFAULTING | GTF_IND_TGT_NOT_HEAP); + } + else + { + store = m_comp->gtNewStoreLclVarNode(inf.LclNum, value); + } + + LIR::AsRange(resumeBB).InsertAtEnd(LIR::SeqTree(m_comp, store)); + } + } + + if (gcRefsCount > 0) + { + unsigned objectArrLclNum = GetGCDataArrayVar(); + + newContinuation = m_comp->gtNewLclvNode(m_newContinuationVar, TYP_REF); + unsigned gcDataOffset = + TARGET_POINTER_SIZE + m_comp->info.compCompHnd->getFieldOffset(m_async2Info.continuationGCDataFldHnd); + GenTree* gcDataOffsetNode = m_comp->gtNewIconNode((ssize_t)gcDataOffset, TYP_I_IMPL); + GenTree* gcDataAddr = m_comp->gtNewOperNode(GT_ADD, TYP_BYREF, newContinuation, gcDataOffsetNode); + GenTree* gcDataInd = m_comp->gtNewIndir(TYP_REF, gcDataAddr, GTF_IND_NONFAULTING); + GenTree* storeAllocedObjectArr = m_comp->gtNewStoreLclVarNode(objectArrLclNum, gcDataInd); + LIR::AsRange(resumeBB).InsertAtEnd(newContinuation, gcDataOffsetNode, gcDataAddr, gcDataInd, + storeAllocedObjectArr); + + // Copy GC pointers + for (LiveLocalInfo& inf : m_liveLocals) + { + if (inf.GCDataCount <= 0) + { + continue; + } + + LclVarDsc* dsc = m_comp->lvaGetDesc(inf.LclNum); + if (dsc->TypeGet() == TYP_REF) + { + GenTree* objectArr = m_comp->gtNewLclvNode(objectArrLclNum, TYP_REF); + unsigned offset = OFFSETOF__CORINFO_Array__data + (inf.GCDataIndex * TARGET_POINTER_SIZE); + GenTree* value = LoadFromOffset(objectArr, offset, TYP_REF); + GenTree* store = m_comp->gtNewStoreLclVarNode(inf.LclNum, value); + + LIR::AsRange(resumeBB).InsertAtEnd(LIR::SeqTree(m_comp, store)); + } + else + { + assert((dsc->TypeGet() == TYP_STRUCT) || dsc->IsImplicitByRef()); + ClassLayout* layout = dsc->GetLayout(); + unsigned numSlots = layout->GetSlotCount(); + for (unsigned i = 0; i < numSlots; i++) + { + var_types gcPtrType = layout->GetGCPtrType(i); + assert((gcPtrType == TYP_I_IMPL) || (gcPtrType == TYP_REF)); + if (gcPtrType != TYP_REF) + { + continue; + } + + GenTree* objectArr = m_comp->gtNewLclvNode(objectArrLclNum, TYP_REF); + unsigned offset = OFFSETOF__CORINFO_Array__data + ((inf.GCDataIndex + i) * TARGET_POINTER_SIZE); + GenTree* value = LoadFromOffset(objectArr, offset, TYP_REF); + GenTree* store; + if (dsc->IsImplicitByRef()) + { + GenTree* baseAddr = m_comp->gtNewLclvNode(inf.LclNum, dsc->TypeGet()); + store = StoreAtOffset(baseAddr, i * TARGET_POINTER_SIZE, value); + // Implicit byref args are never on heap today, skip write barriers. + // TODO-CQ: Remove this once all implicit byrefs are TYP_I_IMPL typed. + store->gtFlags |= GTF_IND_TGT_NOT_HEAP; + } + else + { + store = m_comp->gtNewStoreLclFldNode(inf.LclNum, TYP_REF, i * TARGET_POINTER_SIZE, value); + } + + LIR::AsRange(resumeBB).InsertAtEnd(LIR::SeqTree(m_comp, store)); + } + } + } + + // Copy call return value. + if (storeResultNode != nullptr) + { + GenTree* objectArr = m_comp->gtNewLclvNode(objectArrLclNum, TYP_REF); + GenTree* resultBox = LoadFromOffset(objectArr, OFFSETOF__CORINFO_Array__data, TYP_REF); + + LclVarDsc* resultLcl = m_comp->lvaGetDesc(storeResultNode); + assert((resultLcl->TypeGet() == TYP_STRUCT) == storeResultNode->TypeIs(TYP_STRUCT)); + + // TODO-TP: We can use liveness to avoid generating a lot of this IR. + if (storeResultNode->TypeIs(TYP_STRUCT)) + { + if (m_comp->lvaGetPromotionType(resultLcl) != Compiler::PROMOTION_TYPE_INDEPENDENT) + { + GenTree* boxDataOffset = m_comp->gtNewIconNode(TARGET_POINTER_SIZE, TYP_I_IMPL); + GenTree* boxDataAddr = m_comp->gtNewOperNode(GT_ADD, TYP_BYREF, resultBox, boxDataOffset); + GenTree* boxData = + m_comp->gtNewBlkIndir(storeResultNode->GetLayout(m_comp), boxDataAddr, GTF_IND_NONFAULTING); + if (storeResultNode->OperIs(GT_STORE_LCL_VAR)) + { + store = m_comp->gtNewStoreLclVarNode(storeResultNode->GetLclNum(), boxData); + } + else + { + store = m_comp->gtNewStoreLclFldNode(storeResultNode->GetLclNum(), TYP_STRUCT, + storeResultNode->GetLayout(m_comp), + storeResultNode->GetLclOffs(), boxData); + } + + LIR::AsRange(resumeBB).InsertAtEnd(LIR::SeqTree(m_comp, store)); + } + else + { + if (resultLcl->lvFieldCnt > 1) + { + unsigned resultBoxVar = GetResultBoxVar(); + GenTree* storeResultBox = m_comp->gtNewStoreLclVarNode(resultBoxVar, resultBox); + LIR::AsRange(resumeBB).InsertAtEnd(LIR::SeqTree(m_comp, storeResultBox)); + + resultBox = m_comp->gtNewLclVarNode(resultBoxVar, TYP_REF); + } + + assert(storeResultNode->OperIs(GT_STORE_LCL_VAR)); + for (unsigned i = 0; i < resultLcl->lvFieldCnt; i++) + { + unsigned fieldLclNum = resultLcl->lvFieldLclStart + i; + LclVarDsc* fieldDsc = m_comp->lvaGetDesc(fieldLclNum); + + unsigned fldOffset = TARGET_POINTER_SIZE + fieldDsc->lvFldOffset; + GenTree* value = LoadFromOffset(resultBox, fldOffset, fieldDsc->TypeGet()); + GenTree* store = m_comp->gtNewStoreLclVarNode(fieldLclNum, value); + LIR::AsRange(resumeBB).InsertAtEnd(LIR::SeqTree(m_comp, store)); + + if (i + 1 != resultLcl->lvFieldCnt) + { + resultBox = m_comp->gtCloneExpr(resultBox); + } + } + } + } + else + { + GenTree* value; + if (call->gtReturnType == TYP_REF) + { + value = resultBox; + } + else + { + value = LoadFromOffset(resultBox, TARGET_POINTER_SIZE, call->gtReturnType); + } + + if (storeResultNode->OperIs(GT_STORE_LCL_VAR)) + { + store = m_comp->gtNewStoreLclVarNode(storeResultNode->GetLclNum(), value); + } + else + { + store = m_comp->gtNewStoreLclFldNode(storeResultNode->GetLclNum(), storeResultNode->TypeGet(), + storeResultNode->GetLclOffs(), value); + } + + LIR::AsRange(resumeBB).InsertAtEnd(LIR::SeqTree(m_comp, store)); + } + } + } + + m_resumptionBBs.push_back(resumeBB); + JITDUMP(" Created resumption BB " FMT_BB "\n", resumeBB->bbNum); +} + +GenTreeIndir* Async2Transformation::LoadFromOffset(GenTree* base, unsigned offset, var_types type) +{ + assert(base->TypeIs(TYP_REF, TYP_BYREF, TYP_I_IMPL)); + GenTree* cns = m_comp->gtNewIconNode((ssize_t)offset, TYP_I_IMPL); + var_types addrType = base->TypeIs(TYP_I_IMPL) ? TYP_I_IMPL : TYP_BYREF; + GenTree* addr = m_comp->gtNewOperNode(GT_ADD, addrType, base, cns); + GenTreeIndir* load = m_comp->gtNewIndir(type, addr, GTF_IND_NONFAULTING); + return load; +} + +GenTreeStoreInd* Async2Transformation::StoreAtOffset(GenTree* base, unsigned offset, GenTree* value) +{ + assert(base->TypeIs(TYP_REF, TYP_BYREF, TYP_I_IMPL)); + GenTree* cns = m_comp->gtNewIconNode((ssize_t)offset, TYP_I_IMPL); + var_types addrType = base->TypeIs(TYP_I_IMPL) ? TYP_I_IMPL : TYP_BYREF; + GenTree* addr = m_comp->gtNewOperNode(GT_ADD, addrType, base, cns); + GenTreeStoreInd* store = m_comp->gtNewStoreIndNode(value->TypeGet(), addr, value, GTF_IND_NONFAULTING); + return store; +} + +bool Async2Transformation::IsLive(unsigned lclNum) +{ +#if FEATURE_FIXED_OUT_ARGS + if (lclNum == m_comp->lvaOutgoingArgSpaceVar) + { + return false; + } +#endif + + LclVarDsc* dsc = m_comp->lvaGetDesc(lclNum); + if (dsc->lvRefCnt(RCS_NORMAL) == 0) + { + return false; + } + + Compiler::lvaPromotionType promoType = m_comp->lvaGetPromotionType(dsc); + if (promoType == Compiler::PROMOTION_TYPE_INDEPENDENT) + { + // Independently promoted structs are handled only through their + // fields. + return false; + } + + if (promoType == Compiler::PROMOTION_TYPE_DEPENDENT) + { + // Dependently promoted structs are handled only through the base + // struct local. + // + // A dependently promoted struct is live either if it has significant + // padding (since we do not track liveness for the significant + // padding), or if all its fields are dead. + + if (dsc->lvAnySignificantPadding) + { + // We could technically only save the padding and the live fields + // in this case, but that's a lot of complexity for not a lot of + // gain. + return true; + } + + for (unsigned i = 0; i < dsc->lvFieldCnt; i++) + { + LclVarDsc* fieldDsc = m_comp->lvaGetDesc(dsc->lvFieldLclStart + i); + if (!fieldDsc->lvTracked || VarSetOps::IsMember(m_comp, m_comp->compCurLife, fieldDsc->lvVarIndex)) + { + return true; + } + } + } + + if (dsc->lvIsStructField && (m_comp->lvaGetParentPromotionType(dsc) == Compiler::PROMOTION_TYPE_DEPENDENT)) + { + return false; + } + + return !dsc->lvTracked || VarSetOps::IsMember(m_comp, m_comp->compCurLife, dsc->lvVarIndex); +} + +unsigned Async2Transformation::GetDataArrayVar() +{ + if (m_dataArrayVar == BAD_VAR_NUM) + { + m_dataArrayVar = m_comp->lvaGrabTemp(false DEBUGARG("byte[] for continuation")); + m_comp->lvaGetDesc(m_dataArrayVar)->lvType = TYP_REF; + } + + return m_dataArrayVar; +} + +unsigned Async2Transformation::GetGCDataArrayVar() +{ + if (m_gcDataArrayVar == BAD_VAR_NUM) + { + m_gcDataArrayVar = m_comp->lvaGrabTemp(false DEBUGARG("object[] for continuation")); + m_comp->lvaGetDesc(m_gcDataArrayVar)->lvType = TYP_REF; + } + + return m_gcDataArrayVar; +} + +unsigned Async2Transformation::GetResultBoxVar() +{ + if (m_resultBoxVar == BAD_VAR_NUM) + { + m_resultBoxVar = m_comp->lvaGrabTemp(false DEBUGARG("object for result box")); + m_comp->lvaGetDesc(m_resultBoxVar)->lvType = TYP_REF; + } + + return m_resultBoxVar; +} + +GenTree* Async2Transformation::CreateResumptionStubAddrTree() +{ + switch (m_resumeStubLookup.accessType) + { + case IAT_VALUE: + { + return CreateFunctionTargetAddr(m_resumeStub, m_resumeStubLookup); + } + case IAT_PVALUE: + { + GenTree* tree = CreateFunctionTargetAddr(m_resumeStub, m_resumeStubLookup); + tree = m_comp->gtNewIndir(TYP_I_IMPL, tree, GTF_IND_NONFAULTING | GTF_IND_INVARIANT); + return tree; + } + case IAT_PPVALUE: + { + noway_assert(!"Unexpected IAT_PPVALUE"); + return nullptr; + } + case IAT_RELPVALUE: + { + GenTree* addr = CreateFunctionTargetAddr(m_resumeStub, m_resumeStubLookup); + GenTree* tree = CreateFunctionTargetAddr(m_resumeStub, m_resumeStubLookup); + tree = m_comp->gtNewIndir(TYP_I_IMPL, tree, GTF_IND_NONFAULTING | GTF_IND_INVARIANT); + tree = m_comp->gtNewOperNode(GT_ADD, TYP_I_IMPL, tree, addr); + return tree; + } + default: + { + noway_assert(!"Bad accessType"); + return nullptr; + } + } +} + +GenTree* Async2Transformation::CreateFunctionTargetAddr(CORINFO_METHOD_HANDLE methHnd, + const CORINFO_CONST_LOOKUP& lookup) +{ + GenTree* con = m_comp->gtNewIconHandleNode((size_t)lookup.addr, GTF_ICON_FTN_ADDR); + INDEBUG(con->AsIntCon()->gtTargetHandle = (size_t)methHnd); + return con; +} + +void Async2Transformation::LiftLIREdges(BasicBlock* block, + GenTree* beyond, + jitstd::vector& defs, + jitstd::vector& liveLocals) +{ + if (defs.size() <= 0) + { + return; + } + + for (GenTree* tree : defs) + { + // TODO-CQ: Breaks our recognition of how the call is stored. + // if (tree->OperIs(GT_LCL_VAR)) + //{ + // LclVarDsc* dsc = m_comp->lvaGetDesc(tree->AsLclVarCommon()); + // if (!dsc->IsAddressExposed()) + // { + // // No interference by IR invariants. + // LIR::AsRange(block).Remove(tree); + // LIR::AsRange(block).InsertAfter(beyond, tree); + // continue; + // } + //} + + LIR::Use use; + bool gotUse = LIR::AsRange(block).TryGetUse(tree, &use); + assert(gotUse); // Defs list should not contain unused values. + + unsigned newLclNum = use.ReplaceWithLclVar(m_comp); + liveLocals.push_back(LiveLocalInfo(newLclNum)); + GenTree* newUse = use.Def(); + LIR::AsRange(block).Remove(newUse); + LIR::AsRange(block).InsertBefore(use.User(), newUse); + } +} + +void Async2Transformation::CreateResumptionSwitch() +{ + BasicBlock* newEntryBB = m_comp->bbNewBasicBlock(BBJ_NONE); + JITDUMP("New entry BB: " FMT_BB "\n", newEntryBB->bbNum); + + if (m_comp->fgFirstBB->hasProfileWeight()) + { + newEntryBB->inheritWeight(m_comp->fgFirstBB); + } + m_comp->fgFirstBB->bbRefs--; + + FlowEdge* edge = m_comp->fgAddRefPred(m_comp->fgFirstBB, newEntryBB); + edge->setLikelihood(1); + + m_comp->fgInsertBBbefore(m_comp->fgFirstBB, newEntryBB); + + // If previous first BB was a scratch BB, then we must add a new scratch BB + // to create IR before the switch. + m_comp->fgFirstBBScratch = nullptr; + + GenTree* continuationArg = m_comp->gtNewLclvNode(m_comp->lvaAsyncContinuationArg, TYP_REF); + GenTree* null = m_comp->gtNewNull(); + GenTree* neNull = m_comp->gtNewOperNode(GT_NE, TYP_INT, continuationArg, null); + GenTree* jtrue = m_comp->gtNewOperNode(GT_JTRUE, TYP_VOID, neNull); + LIR::AsRange(newEntryBB).InsertAtEnd(continuationArg, null, neNull, jtrue); + + if (m_resumptionBBs.size() == 1) + { + newEntryBB->SetJumpKindAndTarget(BBJ_COND, m_resumptionBBs[0] DEBUGARG(m_comp)); + m_comp->fgAddRefPred(m_resumptionBBs[0], newEntryBB); + } + else if (m_resumptionBBs.size() == 2) + { + BasicBlock* condBB = m_comp->fgNewBBbefore(BBJ_COND, m_resumptionBBs[0], true); + condBB->bbSetRunRarely(); + newEntryBB->SetJumpKindAndTarget(BBJ_COND, condBB DEBUGARG(m_comp)); + + JITDUMP("New cond BB: " FMT_BB "\n", condBB->bbNum); + + m_comp->fgAddRefPred(condBB, newEntryBB); + continuationArg = m_comp->gtNewLclvNode(m_comp->lvaAsyncContinuationArg, TYP_REF); + unsigned stateOffset = + TARGET_POINTER_SIZE + m_comp->info.compCompHnd->getFieldOffset(m_async2Info.continuationStateFldHnd); + GenTree* stateOffsetNode = m_comp->gtNewIconNode((ssize_t)stateOffset, TYP_I_IMPL); + GenTree* stateAddr = m_comp->gtNewOperNode(GT_ADD, TYP_BYREF, continuationArg, stateOffsetNode); + GenTree* stateInd = m_comp->gtNewIndir(TYP_INT, stateAddr, GTF_IND_NONFAULTING); + GenTree* zero = m_comp->gtNewZeroConNode(TYP_INT); + GenTree* stateNeZero = m_comp->gtNewOperNode(GT_NE, TYP_INT, stateInd, zero); + GenTree* jtrue = m_comp->gtNewOperNode(GT_JTRUE, TYP_VOID, stateNeZero); + + LIR::AsRange(condBB).InsertAtEnd(continuationArg, stateOffsetNode, stateAddr, stateInd, zero, stateNeZero, + jtrue); + condBB->SetJumpDest(m_resumptionBBs[1]); + + m_comp->fgAddRefPred(m_resumptionBBs[0], condBB); + m_comp->fgAddRefPred(m_resumptionBBs[1], condBB); + } + else + { + BasicBlock* switchBB = m_comp->fgNewBBbefore(BBJ_SWITCH, m_resumptionBBs[0], true); + switchBB->bbSetRunRarely(); + newEntryBB->SetJumpKindAndTarget(BBJ_COND, switchBB DEBUGARG(m_comp)); + + JITDUMP("New switch BB: " FMT_BB "\n", switchBB->bbNum); + + m_comp->fgAddRefPred(switchBB, newEntryBB); + + continuationArg = m_comp->gtNewLclvNode(m_comp->lvaAsyncContinuationArg, TYP_REF); + unsigned stateOffset = + TARGET_POINTER_SIZE + m_comp->info.compCompHnd->getFieldOffset(m_async2Info.continuationStateFldHnd); + GenTree* stateOffsetNode = m_comp->gtNewIconNode((ssize_t)stateOffset, TYP_I_IMPL); + GenTree* stateAddr = m_comp->gtNewOperNode(GT_ADD, TYP_BYREF, continuationArg, stateOffsetNode); + GenTree* stateInd = m_comp->gtNewIndir(TYP_INT, stateAddr, GTF_IND_NONFAULTING); + GenTree* switchNode = m_comp->gtNewOperNode(GT_SWITCH, TYP_VOID, stateInd); + + LIR::AsRange(switchBB).InsertAtEnd(continuationArg, stateOffsetNode, stateAddr, stateInd, switchNode); + + m_comp->fgHasSwitch = true; + + //// Default case. TODO-CQ: Support bbsHasDefault = false before lowering. + m_resumptionBBs.push_back(m_resumptionBBs[0]); + BBswtDesc* swtDesc = new (m_comp, CMK_BasicBlock) BBswtDesc; + swtDesc->bbsCount = (unsigned)m_resumptionBBs.size(); + swtDesc->bbsHasDefault = true; + swtDesc->bbsDstTab = m_resumptionBBs.data(); + + switchBB->SetSwitchKindAndTarget(swtDesc); + + for (BasicBlock* bb : m_resumptionBBs) + { + m_comp->fgAddRefPred(bb, switchBB); + } + } +} diff --git a/src/coreclr/jit/async.h b/src/coreclr/jit/async.h new file mode 100644 index 00000000000000..2bc6838eb6ce6b --- /dev/null +++ b/src/coreclr/jit/async.h @@ -0,0 +1,59 @@ +class Async2Transformation +{ + struct LiveLocalInfo + { + unsigned LclNum; + unsigned Alignment; + unsigned DataOffset; + unsigned DataSize; + unsigned GCDataIndex; + unsigned GCDataCount; + + explicit LiveLocalInfo(unsigned lclNum) : LclNum(lclNum) + { + } + }; + + Compiler* m_comp; + unsigned m_livenessLvaCount = 0; + jitstd::vector m_liveLocals; + CORINFO_ASYNC2_INFO m_async2Info; + CORINFO_CLASS_HANDLE m_objectClsHnd; + CORINFO_CLASS_HANDLE m_byteClsHnd; + jitstd::vector m_resumptionBBs; + CORINFO_METHOD_HANDLE m_resumeStub = NO_METHOD_HANDLE; + CORINFO_CONST_LOOKUP m_resumeStubLookup; + unsigned m_returnedContinuationVar = BAD_VAR_NUM; + unsigned m_newContinuationVar = BAD_VAR_NUM; + unsigned m_dataArrayVar = BAD_VAR_NUM; + unsigned m_gcDataArrayVar = BAD_VAR_NUM; + unsigned m_resultBoxVar = BAD_VAR_NUM; + BasicBlock* m_lastSuspensionBB = nullptr; + + void LiftLIREdges(BasicBlock* block, + GenTree* beyond, + jitstd::vector& defs, + jitstd::vector& liveLocals); + bool IsLive(unsigned lclNum); + void Transform(BasicBlock* block, GenTreeCall* call, jitstd::vector& defs, BasicBlock** remainder); + + GenTreeIndir* LoadFromOffset(GenTree* base, unsigned offset, var_types type); + GenTreeStoreInd* StoreAtOffset(GenTree* base, unsigned offset, GenTree* value); + + unsigned GetDataArrayVar(); + unsigned GetGCDataArrayVar(); + unsigned GetResultBoxVar(); + + GenTree* CreateResumptionStubAddrTree(); + GenTree* CreateFunctionTargetAddr(CORINFO_METHOD_HANDLE methHnd, const CORINFO_CONST_LOOKUP& lookup); + + void CreateResumptionSwitch(); + +public: + Async2Transformation(Compiler* comp) + : m_comp(comp), m_liveLocals(comp->getAllocator(CMK_Async2)), m_resumptionBBs(comp->getAllocator(CMK_Async2)) + { + } + + PhaseStatus Run(); +}; diff --git a/src/coreclr/jit/block.cpp b/src/coreclr/jit/block.cpp index 2407afea595717..caf7b6c5ce7166 100644 --- a/src/coreclr/jit/block.cpp +++ b/src/coreclr/jit/block.cpp @@ -521,6 +521,10 @@ void BasicBlock::dspFlags() { printf("mdarr "); } + if (bbFlags & BBF_ASYNC_RESUMPTION) + { + printf("resume "); + } } /***************************************************************************** diff --git a/src/coreclr/jit/block.h b/src/coreclr/jit/block.h index ecdce490528f61..6926bef3b6fa1e 100644 --- a/src/coreclr/jit/block.h +++ b/src/coreclr/jit/block.h @@ -440,6 +440,7 @@ enum BasicBlockFlags : unsigned __int64 BBF_TAILCALL_SUCCESSOR = MAKE_BBFLAG(40), // BB has pred that has potential tail call BBF_RECURSIVE_TAILCALL = MAKE_BBFLAG(41), // Block has recursive tailcall that may turn into a loop BBF_NO_CSE_IN = MAKE_BBFLAG(42), // Block should kill off any incoming CSE + BBF_ASYNC_RESUMPTION = MAKE_BBFLAG(43), // Block is a resumption block in an async method // The following are sets of flags. @@ -461,7 +462,7 @@ enum BasicBlockFlags : unsigned __int64 // For example, the top block might or might not have BBF_GC_SAFE_POINT, // but we assume it does not have BBF_GC_SAFE_POINT any more. - BBF_SPLIT_LOST = BBF_GC_SAFE_POINT | BBF_HAS_JMP | BBF_KEEP_BBJ_ALWAYS | BBF_CLONED_FINALLY_END | BBF_RECURSIVE_TAILCALL, + BBF_SPLIT_LOST = BBF_GC_SAFE_POINT | BBF_HAS_JMP | BBF_KEEP_BBJ_ALWAYS | BBF_CLONED_FINALLY_END | BBF_RECURSIVE_TAILCALL | BBF_ASYNC_RESUMPTION, // Flags gained by the bottom block when a block is split. // Note, this is a conservative guess. @@ -469,7 +470,7 @@ enum BasicBlockFlags : unsigned __int64 // TODO: Should BBF_RUN_RARELY be added to BBF_SPLIT_GAINED ? BBF_SPLIT_GAINED = BBF_DONT_REMOVE | BBF_HAS_JMP | BBF_BACKWARD_JUMP | BBF_HAS_IDX_LEN | BBF_HAS_MD_IDX_LEN | BBF_PROF_WEIGHT | \ - BBF_HAS_NEWOBJ | BBF_KEEP_BBJ_ALWAYS | BBF_CLONED_FINALLY_END | BBF_HAS_NULLCHECK | BBF_HAS_HISTOGRAM_PROFILE | BBF_HAS_MDARRAYREF, + BBF_HAS_NEWOBJ | BBF_KEEP_BBJ_ALWAYS | BBF_CLONED_FINALLY_END | BBF_HAS_NULLCHECK | BBF_HAS_HISTOGRAM_PROFILE | BBF_HAS_MDARRAYREF | BBF_ASYNC_RESUMPTION, // Flags that must be propagated to a new block if code is copied from a block to a new block. These are flags that // limit processing of a block if the code in question doesn't exist. This is conservative; we might not diff --git a/src/coreclr/jit/codegen.h b/src/coreclr/jit/codegen.h index 7e15e7f6526150..437db845d163ec 100644 --- a/src/coreclr/jit/codegen.h +++ b/src/coreclr/jit/codegen.h @@ -1152,6 +1152,7 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX void genCodeForCpBlkHelper(GenTreeBlk* cpBlkNode); #endif void genCodeForPhysReg(GenTreePhysReg* tree); + void genCodeForAsyncContinuation(GenTree* tree); void genCodeForNullCheck(GenTreeIndir* tree); void genCodeForCmpXchg(GenTreeCmpXchg* tree); void genCodeForReuseVal(GenTree* treeNode); @@ -1285,6 +1286,7 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX #endif // TARGET_ARM64 || TARGET_LOONGARCH64 || TARGET_RISCV64 void genReturn(GenTree* treeNode); + void genReturnSuspend(GenTreeUnOp* treeNode); #ifdef TARGET_XARCH void genStackPointerConstantAdjustment(ssize_t spDelta, bool trackSpAdjustments); diff --git a/src/coreclr/jit/codegencommon.cpp b/src/coreclr/jit/codegencommon.cpp index 83270d95b2564c..254808c18ff1c9 100644 --- a/src/coreclr/jit/codegencommon.cpp +++ b/src/coreclr/jit/codegencommon.cpp @@ -7608,6 +7608,11 @@ void CodeGen::genReturn(GenTree* treeNode) } } + if (treeNode->OperIs(GT_RETURN) && compiler->compIsAsync2StateMachine()) + { + instGen_Set_Reg_To_Zero(EA_PTRSIZE, REG_ASYNC_CONTINUATION_RET); + } + #ifdef PROFILING_SUPPORTED // !! Note !! // TODO-AMD64-Unix: If the profiler hook is implemented on *nix, make sure for 2 register returned structs @@ -7690,6 +7695,26 @@ void CodeGen::genReturn(GenTree* treeNode) #endif // defined(DEBUG) && defined(TARGET_XARCH) } +void CodeGen::genReturnSuspend(GenTreeUnOp* treeNode) +{ + GenTree* op = treeNode->gtGetOp1(); + assert(op->TypeIs(TYP_REF)); + + regNumber reg = genConsumeReg(op); + inst_Mov_Extend(TYP_REF, /* srcInReg */ true, REG_ASYNC_CONTINUATION_RET, reg, /* canSkip */ true); + + ReturnTypeDesc retTypeDesc = compiler->compRetTypeDesc; + unsigned numRetRegs = retTypeDesc.GetReturnRegCount(); + for (unsigned i = 0; i < numRetRegs; i++) + { + if (varTypeIsGC(retTypeDesc.GetReturnRegType(i))) + { + regNumber returnReg = retTypeDesc.GetABIReturnReg(i); + instGen_Set_Reg_To_Zero(EA_PTRSIZE, returnReg); + } + } +} + //------------------------------------------------------------------------ // isStructReturn: Returns whether the 'treeNode' is returning a struct. // diff --git a/src/coreclr/jit/codegenxarch.cpp b/src/coreclr/jit/codegenxarch.cpp index e9d52419bb52bf..91c90ce8ff97c7 100644 --- a/src/coreclr/jit/codegenxarch.cpp +++ b/src/coreclr/jit/codegenxarch.cpp @@ -1924,6 +1924,10 @@ void CodeGen::genCodeForTreeNode(GenTree* treeNode) genReturn(treeNode); break; + case GT_RETURN_SUSPEND: + genReturnSuspend(treeNode->AsUnOp()); + break; + case GT_LEA: // If we are here, it is the case where there is an LEA that cannot be folded into a parent instruction. genLeaInstruction(treeNode->AsAddrMode()); @@ -2103,6 +2107,9 @@ void CodeGen::genCodeForTreeNode(GenTree* treeNode) genConsumeReg(treeNode); break; + case GT_ASYNC_CONTINUATION: + break; + #if !defined(FEATURE_EH_FUNCLETS) case GT_END_LFIN: @@ -4520,6 +4527,19 @@ void CodeGen::genCodeForPhysReg(GenTreePhysReg* tree) genProduceReg(tree); } +void CodeGen::genCodeForAsyncContinuation(GenTree* tree) +{ + assert(tree->OperIs(GT_ASYNC_CONTINUATION)); + + var_types targetType = tree->TypeGet(); + regNumber targetReg = tree->GetRegNum(); + + inst_Mov(targetType, targetReg, REG_ASYNC_CONTINUATION_RET, /* canSkip */ true); + genTransferRegGCState(targetReg, REG_ASYNC_CONTINUATION_RET); + + genProduceReg(tree); +} + //--------------------------------------------------------------------- // genCodeForNullCheck - generate code for a GT_NULLCHECK node // diff --git a/src/coreclr/jit/compiler.cpp b/src/coreclr/jit/compiler.cpp index 233667b24e632d..67466abfe6b037 100644 --- a/src/coreclr/jit/compiler.cpp +++ b/src/coreclr/jit/compiler.cpp @@ -5099,6 +5099,11 @@ void Compiler::compCompile(void** methodCodePtr, uint32_t* methodCodeSize, JitFl } #endif // TARGET_ARM + if (compIsAsync2StateMachine()) + { + DoPhase(this, PHASE_ASYNC2, &Compiler::TransformAsync2); + } + // Assign registers to variables, etc. // Create LinearScan before Lowering, so that Lowering can call LinearScan methods diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 44437870ae655b..5d1fef53083443 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -703,6 +703,15 @@ class LclVarDsc } #endif // FEATURE_MULTIREG_ARGS + bool IsImplicitByRef() + { +#if FEATURE_IMPLICIT_BYREFS + return lvIsImplicitByRef; +#else + return false; +#endif + } + CorInfoHFAElemType GetLvHfaElemKind() const { #ifdef FEATURE_HFA_FIELDS_PRESENT @@ -3361,6 +3370,9 @@ class Compiler // where it is used to detect tail-call chains. unsigned lvaRetAddrVar; + // Variable representing async continuation argument passed. + unsigned lvaAsyncContinuationArg; + #if defined(DEBUG) && defined(TARGET_XARCH) unsigned lvaReturnSpCheck; // Stores SP to confirm it is not corrupted on return. @@ -3475,6 +3487,7 @@ class Compiler void lvaInitUserArgs(InitVarDscInfo* varDscInfo, unsigned skipArgs, unsigned takeArgs); void lvaInitGenericsCtxt(InitVarDscInfo* varDscInfo); void lvaInitVarArgsHandle(InitVarDscInfo* varDscInfo); + void lvaInitAsyncContinuation(InitVarDscInfo* varDscInfo); void lvaInitVarDsc(LclVarDsc* varDsc, unsigned varNum, @@ -4901,6 +4914,8 @@ class Compiler PhaseStatus placeLoopAlignInstructions(); #endif + PhaseStatus TransformAsync2(); + // This field keep the R2R helper call that would be inserted to trigger the constructor // of the static class. It is set as nongc or gc static base if they are imported, so // CSE can eliminate the repeated call, or the chepeast helper function that triggers it. @@ -10207,6 +10222,16 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX #endif // TARGET_AMD64 } + bool compIsAsync2() const + { + return opts.jitFlags->IsSet(JitFlags::JIT_FLAG_RUNTIMEASYNCFUNCTION); + } + + bool compIsAsync2StateMachine() const + { + return compIsAsync2() && (JitConfig.RuntimeAsyncViaJitGeneratedStateMachines() != 0); + } + //------------------------------------------------------------------------ // compMethodReturnsMultiRegRetType: Does this method return a multi-reg value? // @@ -11182,6 +11207,7 @@ class GenTreeVisitor // Leaf nodes case GT_CATCH_ARG: + case GT_ASYNC_CONTINUATION: case GT_LABEL: case GT_FTN_ADDR: case GT_RET_EXPR: diff --git a/src/coreclr/jit/compiler.hpp b/src/coreclr/jit/compiler.hpp index 47c9007ddee4fc..d21ba8abd40462 100644 --- a/src/coreclr/jit/compiler.hpp +++ b/src/coreclr/jit/compiler.hpp @@ -2779,6 +2779,12 @@ inline unsigned Compiler::compMapILargNum(unsigned ILargNum) assert(ILargNum < info.compLocalsCount); // compLocals count already adjusted. } + if (ILargNum >= (unsigned)lvaAsyncContinuationArg) + { + ILargNum++; + assert(ILargNum < info.compLocalsCount); // compLocals count already adjusted. + } + if (ILargNum >= (unsigned)lvaVarargsHandleArg) { ILargNum++; @@ -4440,6 +4446,7 @@ void GenTree::VisitOperands(TVisitor visitor) case GT_LCL_FLD: case GT_LCL_ADDR: case GT_CATCH_ARG: + case GT_ASYNC_CONTINUATION: case GT_LABEL: case GT_FTN_ADDR: case GT_RET_EXPR: @@ -4514,6 +4521,7 @@ void GenTree::VisitOperands(TVisitor visitor) case GT_RETURNTRAP: case GT_KEEPALIVE: case GT_INC_SATURATE: + case GT_RETURN_SUSPEND: visitor(this->AsUnOp()->gtOp1); return; diff --git a/src/coreclr/jit/compmemkind.h b/src/coreclr/jit/compmemkind.h index 645a6b44f80ee2..cf49ab2e885773 100644 --- a/src/coreclr/jit/compmemkind.h +++ b/src/coreclr/jit/compmemkind.h @@ -59,6 +59,7 @@ CompMemKindMacro(TailMergeThrows) CompMemKindMacro(EarlyProp) CompMemKindMacro(ZeroInit) CompMemKindMacro(Pgo) +CompMemKindMacro(Async2) //clang-format on #undef CompMemKindMacro diff --git a/src/coreclr/jit/compphases.h b/src/coreclr/jit/compphases.h index 12cc374546e438..f97f831002721a 100644 --- a/src/coreclr/jit/compphases.h +++ b/src/coreclr/jit/compphases.h @@ -103,6 +103,7 @@ CompPhaseNameMacro(PHASE_CREATE_THROW_HELPERS, "Create throw helper blocks CompPhaseNameMacro(PHASE_DETERMINE_FIRST_COLD_BLOCK, "Determine first cold block", false, -1, true) CompPhaseNameMacro(PHASE_RATIONALIZE, "Rationalize IR", false, -1, false) +CompPhaseNameMacro(PHASE_ASYNC2, "Transform async2", false, -1, true) CompPhaseNameMacro(PHASE_LCLVARLIVENESS, "Local var liveness", true, -1, false) CompPhaseNameMacro(PHASE_LCLVARLIVENESS_INIT, "Local var liveness init", false, PHASE_LCLVARLIVENESS, false) CompPhaseNameMacro(PHASE_LCLVARLIVENESS_PERBLOCK, "Per block local var liveness", false, PHASE_LCLVARLIVENESS, false) diff --git a/src/coreclr/jit/fgdiagnostic.cpp b/src/coreclr/jit/fgdiagnostic.cpp index 835c6f401fe9b6..c0c911d99d2dff 100644 --- a/src/coreclr/jit/fgdiagnostic.cpp +++ b/src/coreclr/jit/fgdiagnostic.cpp @@ -2654,6 +2654,11 @@ bool BBPredsChecker::CheckEhTryDsc(BasicBlock* block, BasicBlock* blockPred, EHb return true; } + if ((blockPred->bbFlags & BBF_ASYNC_RESUMPTION) != 0) + { + return true; + } + printf("Jump into the middle of try region: " FMT_BB " branches to " FMT_BB "\n", blockPred->bbNum, block->bbNum); assert(!"Jump into middle of try region"); return false; @@ -3194,6 +3199,7 @@ void Compiler::fgDebugCheckFlags(GenTree* tree) break; case GT_CATCH_ARG: + case GT_ASYNC_CONTINUATION: expectedFlags |= GTF_ORDER_SIDEEFF; break; @@ -3485,6 +3491,10 @@ void Compiler::fgDebugCheckNodeLinks(BasicBlock* block, Statement* stmt) // The root of the tree should have GTF_ORDER_SIDEEFF set noway_assert(stmt->GetRootNode()->gtFlags & GTF_ORDER_SIDEEFF); } + else if (tree->OperIs(GT_ASYNC_CONTINUATION)) + { + assert(tree->gtFlags & GTF_ORDER_SIDEEFF); + } } if (tree->OperIsUnary() && tree->AsOp()->gtOp1) diff --git a/src/coreclr/jit/forwardsub.cpp b/src/coreclr/jit/forwardsub.cpp index 7c1b0316153dfc..a16c57de858fd7 100644 --- a/src/coreclr/jit/forwardsub.cpp +++ b/src/coreclr/jit/forwardsub.cpp @@ -498,7 +498,7 @@ bool Compiler::fgForwardSubStatement(Statement* stmt) // Can't substitute GT_LCLHEAP. // // Don't substitute a no return call (trips up morph in some cases). - if (fwdSubNode->OperIs(GT_CATCH_ARG, GT_LCLHEAP)) + if (fwdSubNode->OperIs(GT_CATCH_ARG, GT_LCLHEAP, GT_ASYNC_CONTINUATION)) { JITDUMP(" tree to sub is catch arg, or lcl heap\n"); return false; diff --git a/src/coreclr/jit/gentree.cpp b/src/coreclr/jit/gentree.cpp index 585ffa86802104..9e8ffb5b06c1c1 100644 --- a/src/coreclr/jit/gentree.cpp +++ b/src/coreclr/jit/gentree.cpp @@ -1733,6 +1733,9 @@ void CallArgs::AddedWellKnownArg(WellKnownArg arg) case WellKnownArg::RetBuffer: m_hasRetBuffer = true; break; + case WellKnownArg::AsyncContinuation: + m_hasAsyncContinuation = true; + break; default: break; } @@ -1756,6 +1759,10 @@ void CallArgs::RemovedWellKnownArg(WellKnownArg arg) assert(FindWellKnownArg(arg) == nullptr); m_hasRetBuffer = false; break; + case WellKnownArg::AsyncContinuation: + assert(FindWellKnownArg(arg) == nullptr); + m_hasAsyncContinuation = false; + break; default: break; } @@ -2354,6 +2361,11 @@ bool GenTreeCall::HasSideEffects(Compiler* compiler, bool ignoreExceptions, bool (!helperProperties.IsAllocator(helper) || ((gtCallMoreFlags & GTF_CALL_M_ALLOC_SIDE_EFFECTS) != 0)); } +bool GenTreeCall::IsAsync2() const +{ + return gtArgs.HasAsyncContinuation(); +} + //------------------------------------------------------------------------- // HasNonStandardAddedArgs: Return true if the method has non-standard args added to the call // argument list during argument morphing (fgMorphArgs), e.g., passed in R10 or R11 on AMD64. @@ -6419,6 +6431,7 @@ bool GenTree::TryGetUse(GenTree* operand, GenTree*** pUse) case GT_LCL_FLD: case GT_LCL_ADDR: case GT_CATCH_ARG: + case GT_ASYNC_CONTINUATION: case GT_LABEL: case GT_FTN_ADDR: case GT_RET_EXPR: @@ -7152,6 +7165,7 @@ bool GenTree::OperSupportsOrderingSideEffect() const case GT_CMPXCHG: case GT_MEMORYBARRIER: case GT_CATCH_ARG: + case GT_ASYNC_CONTINUATION: return true; default: return false; @@ -9209,6 +9223,7 @@ GenTree* Compiler::gtCloneExpr( goto DONE; case GT_CATCH_ARG: + case GT_ASYNC_CONTINUATION: case GT_NO_OP: case GT_LABEL: copy = new (this, oper) GenTree(oper, tree->gtType); @@ -10071,6 +10086,7 @@ GenTreeUseEdgeIterator::GenTreeUseEdgeIterator(GenTree* node) case GT_LCL_FLD: case GT_LCL_ADDR: case GT_CATCH_ARG: + case GT_ASYNC_CONTINUATION: case GT_LABEL: case GT_FTN_ADDR: case GT_RET_EXPR: @@ -10135,6 +10151,7 @@ GenTreeUseEdgeIterator::GenTreeUseEdgeIterator(GenTree* node) case GT_PUTARG_SPLIT: #endif // FEATURE_ARG_SPLIT case GT_RETURNTRAP: + case GT_RETURN_SUSPEND: m_edge = &m_node->AsUnOp()->gtOp1; assert(*m_edge != nullptr); m_advance = &GenTreeUseEdgeIterator::Terminate; @@ -11618,6 +11635,10 @@ void Compiler::gtGetLclVarNameInfo(unsigned lclNum, const char** ilKindOut, cons { ilName = "this"; } + else if (lclNum == lvaAsyncContinuationArg) + { + ilName = "AsyncCont"; + } else { ilKind = "arg"; @@ -12145,6 +12166,7 @@ void Compiler::gtDispLeaf(GenTree* tree, IndentStack* indentStack) case GT_START_PREEMPTGC: case GT_PROF_HOOK: case GT_CATCH_ARG: + case GT_ASYNC_CONTINUATION: case GT_MEMORYBARRIER: case GT_PINVOKE_PROLOG: case GT_JMPTABLE: @@ -12910,6 +12932,8 @@ const char* Compiler::gtGetWellKnownArgNameForArgMsg(WellKnownArg arg) return "va cookie"; case WellKnownArg::InstParam: return "gctx"; + case WellKnownArg::AsyncContinuation: + return "async"; case WellKnownArg::RetBuffer: return "retbuf"; case WellKnownArg::PInvokeFrame: diff --git a/src/coreclr/jit/gentree.h b/src/coreclr/jit/gentree.h index 0430fe885c1144..34441aabe02820 100644 --- a/src/coreclr/jit/gentree.h +++ b/src/coreclr/jit/gentree.h @@ -4354,6 +4354,7 @@ enum class WellKnownArg ThisPointer, VarArgsCookie, InstParam, + AsyncContinuation, RetBuffer, PInvokeFrame, SecretStubParam, @@ -4711,6 +4712,7 @@ class CallArgs #endif bool m_hasThisPointer : 1; bool m_hasRetBuffer : 1; + bool m_hasAsyncContinuation : 1; bool m_isVarArgs : 1; bool m_abiInformationDetermined : 1; // True if we have one or more register arguments. @@ -4757,6 +4759,7 @@ class CallArgs CallArg* InsertAfter(Compiler* comp, CallArg* after, const NewCallArg& arg); CallArg* InsertAfterUnchecked(Compiler* comp, CallArg* after, const NewCallArg& arg); CallArg* InsertInstParam(Compiler* comp, GenTree* node); + CallArg* InsertAsyncContinuationParam(Compiler* comp, GenTree* node); CallArg* InsertAfterThisOrFirst(Compiler* comp, const NewCallArg& arg); void PushLateBack(CallArg* arg); void Remove(CallArg* arg); @@ -4785,6 +4788,7 @@ class CallArgs // clang-format off bool HasThisPointer() const { return m_hasThisPointer; } bool HasRetBuffer() const { return m_hasRetBuffer; } + bool HasAsyncContinuation() const { return m_hasAsyncContinuation; } bool IsVarArgs() const { return m_isVarArgs; } void SetIsVarArgs() { m_isVarArgs = true; } void ClearIsVarArgs() { m_isVarArgs = false; } @@ -4979,6 +4983,8 @@ struct GenTreeCall final : public GenTree #endif } + bool IsAsync2() const; + //--------------------------------------------------------------------------- // GetRegNumByIdx: get i'th return register allocated to this call node. // diff --git a/src/coreclr/jit/gtlist.h b/src/coreclr/jit/gtlist.h index e88c447d3774db..7d3e07485fa4ea 100644 --- a/src/coreclr/jit/gtlist.h +++ b/src/coreclr/jit/gtlist.h @@ -33,6 +33,7 @@ GTNODE(LCL_ADDR , GenTreeLclFld ,0,0,GTK_LEAF) // local //----------------------------------------------------------------------------- GTNODE(CATCH_ARG , GenTree ,0,0,GTK_LEAF) // Exception object in a catch block +GTNODE(ASYNC_CONTINUATION, GenTree ,0,0,GTK_LEAF) // Returned continuation by an async2 call GTNODE(LABEL , GenTree ,0,0,GTK_LEAF) // Jump-target GTNODE(JMP , GenTreeVal ,0,0,GTK_LEAF|GTK_NOVALUE) // Jump to another function GTNODE(FTN_ADDR , GenTreeFptrVal ,0,0,GTK_LEAF) // Address of a function @@ -279,6 +280,8 @@ GTNODE(RETURN , GenTreeOp ,0,1,GTK_UNOP|GTK_NOVALUE) GTNODE(SWITCH , GenTreeOp ,0,1,GTK_UNOP|GTK_NOVALUE) GTNODE(NO_OP , GenTree ,0,0,GTK_LEAF|GTK_NOVALUE) // A NOP that cannot be deleted. +GTNODE(RETURN_SUSPEND , GenTreeOp ,0,1,GTK_UNOP|GTK_NOVALUE) // Return a continuation in an async2 method + GTNODE(START_NONGC , GenTree ,0,0,GTK_LEAF|GTK_NOVALUE|DBK_NOTHIR) // Starts a new instruction group that will be non-gc interruptible. GTNODE(START_PREEMPTGC , GenTree ,0,0,GTK_LEAF|GTK_NOVALUE|DBK_NOTHIR) // Starts a new instruction group where preemptive GC is enabled. GTNODE(PROF_HOOK , GenTree ,0,0,GTK_LEAF|GTK_NOVALUE|DBK_NOTHIR) // Profiler Enter/Leave/TailCall hook. diff --git a/src/coreclr/jit/importer.cpp b/src/coreclr/jit/importer.cpp index 0b24cbda69bbae..28a826da7761a2 100644 --- a/src/coreclr/jit/importer.cpp +++ b/src/coreclr/jit/importer.cpp @@ -12870,6 +12870,7 @@ void Compiler::impInlineInitVars(InlineInfo* pInlineInfo) break; case WellKnownArg::RetBuffer: case WellKnownArg::InstParam: + case WellKnownArg::AsyncContinuation: // These do not appear in the table of inline arg info; do not include them continue; default: diff --git a/src/coreclr/jit/importercalls.cpp b/src/coreclr/jit/importercalls.cpp index 6006a48a86f2af..15f2c58159f594 100644 --- a/src/coreclr/jit/importercalls.cpp +++ b/src/coreclr/jit/importercalls.cpp @@ -96,7 +96,8 @@ var_types Compiler::impImportCall(OPCODE opcode, bool bIntrinsicImported = false; CORINFO_SIG_INFO calliSig; - NewCallArg extraArg; + GenTree* varArgsCookie = nullptr; + GenTree* instParam = nullptr; /*------------------------------------------------------------------------- * First create the call node @@ -715,12 +716,10 @@ var_types Compiler::impImportCall(OPCODE opcode, } } - /*------------------------------------------------------------------------- - * Create the argument list - */ + // Now create the argument list. //------------------------------------------------------------------------- - // Special case - for varargs we have an implicit last argument + // Special case - for varargs we have an extra argument if ((sig->callConv & CORINFO_CALLCONV_MASK) == CORINFO_CALLCONV_VARARG) { @@ -735,9 +734,7 @@ var_types Compiler::impImportCall(OPCODE opcode, varCookie = info.compCompHnd->getVarArgsHandle(sig, &pVarCookie); assert((!varCookie) != (!pVarCookie)); - GenTree* cookieNode = gtNewIconEmbHndNode(varCookie, pVarCookie, GTF_ICON_VARG_HDL, sig); - assert(extraArg.Node == nullptr); - extraArg = NewCallArg::Primitive(cookieNode).WellKnown(WellKnownArg::VarArgsCookie); + varArgsCookie = gtNewIconEmbHndNode(varCookie, pVarCookie, GTF_ICON_VARG_HDL, sig); } //------------------------------------------------------------------------- @@ -757,7 +754,7 @@ var_types Compiler::impImportCall(OPCODE opcode, // We also set the exact type context associated with the call so we can // inline the call correctly later on. - if (sig->callConv & CORINFO_CALLCONV_PARAMTYPE) + if (sig->hasTypeArg()) { assert(call->AsCall()->gtCallType == CT_USER_FUNC); if (clsHnd == nullptr) @@ -767,8 +764,7 @@ var_types Compiler::impImportCall(OPCODE opcode, assert(opcode != CEE_CALLI); - GenTree* instParam; - bool runtimeLookup; + bool runtimeLookup; // Instantiated generic method if (((SIZE_T)exactContextHnd & CORINFO_CONTEXTFLAGS_MASK) == CORINFO_CONTEXTFLAGS_METHOD) @@ -858,9 +854,6 @@ var_types Compiler::impImportCall(OPCODE opcode, } } } - - assert(extraArg.Node == nullptr); - extraArg = NewCallArg::Primitive(instParam).WellKnown(WellKnownArg::InstParam); } if ((opcode == CEE_NEWOBJ) && ((clsFlags & CORINFO_FLG_DELEGATE) != 0)) @@ -882,18 +875,50 @@ var_types Compiler::impImportCall(OPCODE opcode, // The main group of arguments impPopCallArgs(sig, call->AsCall()); - if (extraArg.Node != nullptr) + + // Extra args + if ((instParam != nullptr) || sig->isAsyncCall() || (varArgsCookie != nullptr)) { if (Target::g_tgtArgOrder == Target::ARG_ORDER_R2L) { - call->AsCall()->gtArgs.PushFront(this, extraArg); + if (varArgsCookie != nullptr) + { + call->AsCall()->gtArgs.PushFront(this, NewCallArg::Primitive(varArgsCookie, TYP_REF) + .WellKnown(WellKnownArg::VarArgsCookie)); + } + + if (compIsAsync2StateMachine() && sig->isAsyncCall()) + { + call->AsCall()->gtArgs.PushFront(this, NewCallArg::Primitive(gtNewNull(), TYP_REF) + .WellKnown(WellKnownArg::AsyncContinuation)); + } + + if (instParam != nullptr) + { + call->AsCall()->gtArgs.PushFront(this, + NewCallArg::Primitive(instParam).WellKnown(WellKnownArg::InstParam)); + } } else { - call->AsCall()->gtArgs.PushBack(this, extraArg); - } + if (instParam != nullptr) + { + call->AsCall()->gtArgs.PushBack(this, + NewCallArg::Primitive(instParam).WellKnown(WellKnownArg::InstParam)); + } - call->gtFlags |= extraArg.Node->gtFlags & GTF_GLOB_EFFECT; + if (compIsAsync2StateMachine() && sig->isAsyncCall()) + { + call->AsCall()->gtArgs.PushBack(this, NewCallArg::Primitive(gtNewNull(), TYP_REF) + .WellKnown(WellKnownArg::AsyncContinuation)); + } + + if (varArgsCookie != nullptr) + { + call->AsCall()->gtArgs.PushBack(this, NewCallArg::Primitive(varArgsCookie, TYP_REF) + .WellKnown(WellKnownArg::VarArgsCookie)); + } + } } //------------------------------------------------------------------------- @@ -2536,6 +2561,13 @@ GenTree* Compiler::impIntrinsic(GenTree* newobjThis, return new (this, GT_LABEL) GenTree(GT_LABEL, TYP_I_IMPL); } + if (ni == NI_System_StubHelpers_Async2CallContinuation) + { + GenTree* node = new (this, GT_ASYNC_CONTINUATION) GenTree(GT_ASYNC_CONTINUATION, TYP_REF); + node->SetHasOrderingSideEffect(); + return node; + } + bool betterToExpand = false; // Allow some lighweight intrinsics in Tier0 which can improve throughput @@ -9173,6 +9205,10 @@ NamedIntrinsic Compiler::lookupNamedIntrinsic(CORINFO_METHOD_HANDLE method) { result = NI_System_StubHelpers_NextCallReturnAddress; } + else if (strcmp(methodName, "Async2CallContinuation") == 0) + { + result = NI_System_StubHelpers_Async2CallContinuation; + } } } else if (strcmp(namespaceName, "Text") == 0) diff --git a/src/coreclr/jit/jitconfigvalues.h b/src/coreclr/jit/jitconfigvalues.h index 8483bf25bc4465..1cdd96279a9deb 100644 --- a/src/coreclr/jit/jitconfigvalues.h +++ b/src/coreclr/jit/jitconfigvalues.h @@ -653,6 +653,9 @@ CONFIG_INTEGER(JitEnableHeadTailMerge, W("JitEnableHeadTailMerge"), 1) // Enable physical promotion CONFIG_INTEGER(JitEnablePhysicalPromotion, W("JitEnablePhysicalPromotion"), 1) +// Should JIT generate state machines for async2 methods? +CONFIG_INTEGER(RuntimeAsyncViaJitGeneratedStateMachines, W("RuntimeAsyncViaJitGeneratedStateMachines"), 0) + #if defined(DEBUG) // JitFunctionFile: Name of a file that contains a list of functions. If the currently compiled function is in the // file, certain other JIT config variables will be active. If the currently compiled function is not in the file, diff --git a/src/coreclr/jit/jitee.h b/src/coreclr/jit/jitee.h index 27963ac356efb5..525d7960ade0da 100644 --- a/src/coreclr/jit/jitee.h +++ b/src/coreclr/jit/jitee.h @@ -47,6 +47,7 @@ class JitFlags #if defined(TARGET_XARCH) JIT_FLAG_VECTOR512_THROTTLING = 31, // On Xarch, 512-bit vector usage may incur CPU frequency throttling #endif + JIT_FLAG_RUNTIMEASYNCFUNCTION = 32, // Generate code for use as an async2 function // Note: the mcs tool uses the currently unused upper flags bits when outputting SuperPMI MC file flags. // See EXTRA_JIT_FLAGS and spmidumphelper.cpp. Currently, these are bits 56 through 63. If they overlap, diff --git a/src/coreclr/jit/lclvars.cpp b/src/coreclr/jit/lclvars.cpp index adf993d37b70c5..95289c66beff6b 100644 --- a/src/coreclr/jit/lclvars.cpp +++ b/src/coreclr/jit/lclvars.cpp @@ -63,11 +63,12 @@ void Compiler::lvaInit() #ifdef TARGET_X86 lvaVarargsBaseOfStkArgs = BAD_VAR_NUM; #endif // TARGET_X86 - lvaVarargsHandleArg = BAD_VAR_NUM; - lvaStubArgumentVar = BAD_VAR_NUM; - lvaArg0Var = BAD_VAR_NUM; - lvaMonAcquired = BAD_VAR_NUM; - lvaRetAddrVar = BAD_VAR_NUM; + lvaVarargsHandleArg = BAD_VAR_NUM; + lvaStubArgumentVar = BAD_VAR_NUM; + lvaArg0Var = BAD_VAR_NUM; + lvaMonAcquired = BAD_VAR_NUM; + lvaRetAddrVar = BAD_VAR_NUM; + lvaAsyncContinuationArg = BAD_VAR_NUM; lvaInlineeReturnSpillTemp = BAD_VAR_NUM; @@ -97,19 +98,19 @@ void Compiler::lvaInitTypeRef() { /* x86 args look something like this: - [this ptr] [hidden return buffer] [declared arguments]* [generic context] [var arg cookie] + [this ptr] [hidden return buffer] [declared arguments]* [generic context] [async continuation] [var arg cookie] x64 is closer to the native ABI: - [this ptr] [hidden return buffer] [generic context] [var arg cookie] [declared arguments]* + [this ptr] [hidden return buffer] [generic context] [async continuation] [var arg cookie] [declared arguments]* (Note: prior to .NET Framework 4.5.1 for Windows 8.1 (but not .NET Framework 4.5.1 "downlevel"), the "hidden return buffer" came before the "this ptr". Now, the "this ptr" comes first. This is different from the C++ order, where the "hidden return buffer" always comes first.) ARM and ARM64 are the same as the current x64 convention: - [this ptr] [hidden return buffer] [generic context] [var arg cookie] [declared arguments]* + [this ptr] [hidden return buffer] [generic context] [async continuation] [var arg cookie] [declared arguments]* Key difference: - The var arg cookie and generic context are swapped with respect to the user arguments + The var arg cookie, generic context and async continuations are swapped with respect to the user arguments */ /* Set compArgsCount and compLocalsCount */ @@ -184,6 +185,11 @@ void Compiler::lvaInitTypeRef() info.compTypeCtxtArg = BAD_VAR_NUM; } + if (compIsAsync2StateMachine()) + { + info.compArgsCount++; + } + lvaCount = info.compLocalsCount = info.compArgsCount + info.compMethodInfo->locals.numArgs; info.compILlocalsCount = info.compILargsCount + info.compMethodInfo->locals.numArgs; @@ -419,6 +425,8 @@ void Compiler::lvaInitArgs(InitVarDscInfo* varDscInfo) // and shared generic struct instance methods lvaInitGenericsCtxt(varDscInfo); + lvaInitAsyncContinuation(varDscInfo); + /* If the method is varargs, process the varargs cookie */ lvaInitVarArgsHandle(varDscInfo); #endif @@ -432,6 +440,8 @@ void Compiler::lvaInitArgs(InitVarDscInfo* varDscInfo) // and shared generic struct instance methods lvaInitGenericsCtxt(varDscInfo); + lvaInitAsyncContinuation(varDscInfo); + /* If the method is varargs, process the varargs cookie */ lvaInitVarArgsHandle(varDscInfo); #endif @@ -1354,6 +1364,58 @@ void Compiler::lvaInitGenericsCtxt(InitVarDscInfo* varDscInfo) } } +void Compiler::lvaInitAsyncContinuation(InitVarDscInfo* varDscInfo) +{ + if (!compIsAsync2StateMachine()) + { + return; + } + + lvaAsyncContinuationArg = varDscInfo->varNum; + LclVarDsc* varDsc = varDscInfo->varDsc; + varDsc->lvType = TYP_REF; + varDsc->lvIsParam = 1; + +#ifdef DEBUG + varDsc->lvReason = "Async continuation arg"; +#endif + + if (varDscInfo->canEnreg(TYP_REF)) + { + // Passed in register + + varDsc->lvIsRegArg = 1; + varDsc->SetArgReg(genMapRegArgNumToRegNum(varDscInfo->regArgNum(TYP_INT), varDsc->TypeGet())); +#if FEATURE_MULTIREG_ARGS + varDsc->SetOtherArgReg(REG_NA); +#endif + varDsc->lvOnFrame = true; // The final home for this incoming register might be our local stack frame + + varDscInfo->intRegArgNum++; + + JITDUMP("'AsyncContinuation' passed in register %s\n", getRegName(varDsc->GetArgReg())); + } + else + { +// We need to mark these as being on the stack, as this is not done elsewhere in the case that canEnreg +// returns false. +#if FEATURE_FASTTAILCALL + varDsc->SetStackOffset(varDscInfo->stackArgSize); + varDscInfo->stackArgSize += TARGET_POINTER_SIZE; +#endif // FEATURE_FASTTAILCALL + } + + compArgSize += TARGET_POINTER_SIZE; + +#if defined(TARGET_X86) + if (info.compIsVarArgs) + varDsc->SetStackOffset(compArgSize); +#endif // TARGET_X86 + + varDscInfo->varNum++; + varDscInfo->varDsc++; +} + /*****************************************************************************/ void Compiler::lvaInitVarArgsHandle(InitVarDscInfo* varDscInfo) { @@ -5503,6 +5565,13 @@ void Compiler::lvaAssignVirtualFrameOffsetsToArgs() argOffs UNIX_AMD64_ABI_ONLY_ARG(&callerArgOffset)); } + if (compIsAsync2StateMachine()) + { + noway_assert(lclNum == lvaAsyncContinuationArg); + argOffs = lvaAssignVirtualFrameOffsetToArg(lclNum++, REGSIZE_BYTES, + argOffs UNIX_AMD64_ABI_ONLY_ARG(&callerArgOffset)); + } + if (info.compIsVarArgs) { argOffs = lvaAssignVirtualFrameOffsetToArg(lclNum++, REGSIZE_BYTES, @@ -5612,6 +5681,13 @@ void Compiler::lvaAssignVirtualFrameOffsetsToArgs() argOffs UNIX_AMD64_ABI_ONLY_ARG(&callerArgOffset)); } + if (compIsAsync2StateMachine()) + { + noway_assert(lclNum == lvaAsyncContinuationArg); + argOffs = lvaAssignVirtualFrameOffsetToArg(lclNum++, REGSIZE_BYTES, + argOffs UNIX_AMD64_ABI_ONLY_ARG(&callerArgOffset)); + } + if (info.compIsVarArgs) { argOffs = lvaAssignVirtualFrameOffsetToArg(lclNum++, REGSIZE_BYTES, diff --git a/src/coreclr/jit/lir.h b/src/coreclr/jit/lir.h index 9b4f940bc0ae38..d2c23bc0bca891 100644 --- a/src/coreclr/jit/lir.h +++ b/src/coreclr/jit/lir.h @@ -280,6 +280,20 @@ class LIR final void InsertAtBeginning(Range&& range); void InsertAtEnd(Range&& range); + template + void InsertAtBeginning(GenTree* tree, Trees&&... rest) + { + InsertAtBeginning(std::forward(rest)...); + InsertAtBeginning(tree); + } + + template + void InsertAtEnd(GenTree* tree, Trees&&... rest) + { + InsertAtEnd(tree); + InsertAtEnd(std::forward(rest)...); + } + void Remove(GenTree* node, bool markOperandsUnused = false); Range Remove(GenTree* firstNode, GenTree* lastNode); Range Remove(ReadOnlyRange&& range); diff --git a/src/coreclr/jit/liveness.cpp b/src/coreclr/jit/liveness.cpp index 3c914b2875f689..aefdcbdc57e6c7 100644 --- a/src/coreclr/jit/liveness.cpp +++ b/src/coreclr/jit/liveness.cpp @@ -2025,6 +2025,7 @@ void Compiler::fgComputeLifeLIR(VARSET_TP& life, BasicBlock* block, VARSET_VALAR case GT_JCC: case GT_JTRUE: case GT_RETURN: + case GT_RETURN_SUSPEND: case GT_SWITCH: case GT_RETFILT: case GT_START_NONGC: diff --git a/src/coreclr/jit/lsraxarch.cpp b/src/coreclr/jit/lsraxarch.cpp index 3257b3c04b216b..92fe1d29157829 100644 --- a/src/coreclr/jit/lsraxarch.cpp +++ b/src/coreclr/jit/lsraxarch.cpp @@ -578,6 +578,12 @@ int LinearScan::BuildNode(GenTree* tree) BuildDef(tree, RBM_EXCEPTION_OBJECT); break; + case GT_ASYNC_CONTINUATION: + srcCount = 0; + assert(dstCount == 1); + BuildDef(tree, RBM_ASYNC_CONTINUATION_RET); + break; + #if !defined(FEATURE_EH_FUNCLETS) case GT_END_LFIN: srcCount = 0; diff --git a/src/coreclr/jit/morph.cpp b/src/coreclr/jit/morph.cpp index 85539583800a6a..1c4c78b3f8bf62 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -741,6 +741,8 @@ const char* getWellKnownArgName(WellKnownArg arg) return "VarArgsCookie"; case WellKnownArg::InstParam: return "InstParam"; + case WellKnownArg::AsyncContinuation: + return "AsyncContinuation"; case WellKnownArg::RetBuffer: return "RetBuffer"; case WellKnownArg::PInvokeFrame: diff --git a/src/coreclr/jit/namedintrinsiclist.h b/src/coreclr/jit/namedintrinsiclist.h index 0973a45571f498..cd91d7413e09e6 100644 --- a/src/coreclr/jit/namedintrinsiclist.h +++ b/src/coreclr/jit/namedintrinsiclist.h @@ -93,6 +93,7 @@ enum NamedIntrinsic : unsigned short NI_System_RuntimeType_get_TypeHandle, NI_System_StubHelpers_GetStubContext, NI_System_StubHelpers_NextCallReturnAddress, + NI_System_StubHelpers_Async2CallContinuation, NI_Array_Address, NI_Array_Get, diff --git a/src/coreclr/jit/targetamd64.h b/src/coreclr/jit/targetamd64.h index 4abe71984b57cf..dc4b957b4062ec 100644 --- a/src/coreclr/jit/targetamd64.h +++ b/src/coreclr/jit/targetamd64.h @@ -539,6 +539,9 @@ #define REG_VALIDATE_INDIRECT_CALL_ADDR REG_RCX #define REG_DISPATCH_INDIRECT_CALL_ADDR REG_RAX + #define REG_ASYNC_CONTINUATION_RET REG_RCX + #define RBM_ASYNC_CONTINUATION_RET RBM_RCX + // What sort of reloc do we use for [disp32] address mode #define IMAGE_REL_BASED_DISP32 IMAGE_REL_BASED_REL32 diff --git a/src/coreclr/jit/targetarm.h b/src/coreclr/jit/targetarm.h index 3fa00cacbf4700..f6421f989c47d4 100644 --- a/src/coreclr/jit/targetarm.h +++ b/src/coreclr/jit/targetarm.h @@ -247,6 +247,9 @@ #define RBM_VALIDATE_INDIRECT_CALL_TRASH (RBM_INT_CALLEE_TRASH) #define REG_VALIDATE_INDIRECT_CALL_ADDR REG_R0 + #define REG_ASYNC_CONTINUATION_RET REG_R3 + #define RBM_ASYNC_CONTINUATION_RET RBM_R3 + #define REG_FPBASE REG_R11 #define RBM_FPBASE RBM_R11 #define STR_FPBASE "r11" diff --git a/src/coreclr/jit/targetarm64.h b/src/coreclr/jit/targetarm64.h index a454b47608bb37..410019b8cca682 100644 --- a/src/coreclr/jit/targetarm64.h +++ b/src/coreclr/jit/targetarm64.h @@ -256,6 +256,9 @@ #define REG_VALIDATE_INDIRECT_CALL_ADDR REG_R15 #define REG_DISPATCH_INDIRECT_CALL_ADDR REG_R9 + #define REG_ASYNC_CONTINUATION_RET REG_R8 + #define RBM_ASYNC_CONTINUATION_RET RBM_R8 + #define REG_FPBASE REG_FP #define RBM_FPBASE RBM_FP #define STR_FPBASE "fp" diff --git a/src/coreclr/jit/targetx86.h b/src/coreclr/jit/targetx86.h index 60b2f7793f435b..e8c412bd6d5d03 100644 --- a/src/coreclr/jit/targetx86.h +++ b/src/coreclr/jit/targetx86.h @@ -300,6 +300,9 @@ #define RBM_VALIDATE_INDIRECT_CALL_TRASH (RBM_INT_CALLEE_TRASH & ~RBM_ECX) #define REG_VALIDATE_INDIRECT_CALL_ADDR REG_ECX + #define REG_ASYNC_CONTINUATION_RET REG_ECX + #define RBM_ASYNC_CONTINUATION_RET RBM_ECX + #define REG_FPBASE REG_EBP #define RBM_FPBASE RBM_EBP #define STR_FPBASE "ebp" diff --git a/src/coreclr/jit/valuenum.cpp b/src/coreclr/jit/valuenum.cpp index aea80a8ca48edd..cfe3793c895adf 100644 --- a/src/coreclr/jit/valuenum.cpp +++ b/src/coreclr/jit/valuenum.cpp @@ -9375,7 +9375,8 @@ static genTreeOps genTreeOpsIllegalAsVNFunc[] = {GT_IND, // When we do heap memo GT_BITCAST, // Needs to encode the target type. // These control-flow operations need no values. - GT_JTRUE, GT_RETURN, GT_SWITCH, GT_RETFILT, GT_CKFINITE}; + GT_JTRUE, GT_RETURN, GT_RETURN_SUSPEND, GT_SWITCH, GT_RETFILT, + GT_CKFINITE}; void ValueNumStore::ValidateValueNumStoreStatics() { @@ -11226,7 +11227,8 @@ void Compiler::fgValueNumberTree(GenTree* tree) break; case GT_CATCH_ARG: - // We know nothing about the value of a caught expression. + case GT_ASYNC_CONTINUATION: + // We know nothing about the value of these. tree->gtVNPair.SetBoth(vnStore->VNForExpr(compCurBB, tree->TypeGet())); break; diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoHelpFunc.cs b/src/coreclr/tools/Common/JitInterface/CorInfoHelpFunc.cs index 7837e8c24f832f..74b0f72798d6de 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoHelpFunc.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoHelpFunc.cs @@ -304,6 +304,8 @@ which is the right helper to use to allocate an object of a given type. */ CORINFO_HELP_VALIDATE_INDIRECT_CALL, // CFG: Validate function pointer CORINFO_HELP_DISPATCH_INDIRECT_CALL, // CFG: Validate and dispatch to pointer + CORINFO_HELP_ALLOC_CONTINUATION, + CORINFO_HELP_COUNT, } } diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs index 9d98650b91bbb9..4a26f5ccf2fcc5 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs @@ -2722,6 +2722,9 @@ private CorInfoInitClassResult initClass(CORINFO_FIELD_STRUCT_* field, CORINFO_M case CorInfoClassId.CLASSID_SYSTEM_OBJECT: return ObjectToHandle(_compilation.TypeSystemContext.GetWellKnownType(WellKnownType.Object)); + case CorInfoClassId.CLASSID_SYSTEM_BYTE: + return ObjectToHandle(_compilation.TypeSystemContext.GetWellKnownType(WellKnownType.Byte)); + case CorInfoClassId.CLASSID_TYPED_BYREF: return ObjectToHandle(_compilation.TypeSystemContext.GetWellKnownType(WellKnownType.TypedReference)); @@ -3243,6 +3246,11 @@ private void getEEInfo(ref CORINFO_EE_INFO pEEInfoOut) pEEInfoOut.osType = TargetToOs(_compilation.NodeFactory.Target); } + private void getAsync2Info(ref CORINFO_ASYNC2_INFO pAsync2InfoOut) + { + throw new NotImplementedException(); + } + #pragma warning disable CA1822 // Mark members as static private char* getJitTimeLogFilename() #pragma warning restore CA1822 // Mark members as static @@ -3542,6 +3550,13 @@ private bool getTailCallHelpers(ref CORINFO_RESOLVED_TOKEN callToken, CORINFO_SI #endif } +#pragma warning disable CA1822 // Mark members as static + private CORINFO_METHOD_STRUCT_* getAsyncResumptionStub() +#pragma warning restore CA1822 // Mark members as static + { + return null; + } + private byte[] _code; private byte[] _coldCode; private int _codeAlignment; diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoImpl_generated.cs b/src/coreclr/tools/Common/JitInterface/CorInfoImpl_generated.cs index 8d467b5880d9bc..04499066324aa0 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoImpl_generated.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoImpl_generated.cs @@ -1666,6 +1666,20 @@ private static void _getEEInfo(IntPtr thisHandle, IntPtr* ppException, CORINFO_E } } + [UnmanagedCallersOnly] + private static void _getAsync2Info(IntPtr thisHandle, IntPtr* ppException, CORINFO_ASYNC2_INFO* pAsync2InfoOut) + { + var _this = GetThis(thisHandle); + try + { + _this.getAsync2Info(ref *pAsync2InfoOut); + } + catch (Exception ex) + { + *ppException = _this.AllocException(ex); + } + } + [UnmanagedCallersOnly] private static char* _getJitTimeLogFilename(IntPtr thisHandle, IntPtr* ppException) { @@ -2243,6 +2257,21 @@ private static byte _getTailCallHelpers(IntPtr thisHandle, IntPtr* ppException, } } + [UnmanagedCallersOnly] + private static CORINFO_METHOD_STRUCT_* _getAsyncResumptionStub(IntPtr thisHandle, IntPtr* ppException) + { + var _this = GetThis(thisHandle); + try + { + return _this.getAsyncResumptionStub(); + } + catch (Exception ex) + { + *ppException = _this.AllocException(ex); + return default; + } + } + [UnmanagedCallersOnly] private static byte _convertPInvokeCalliToCall(IntPtr thisHandle, IntPtr* ppException, CORINFO_RESOLVED_TOKEN* pResolvedToken, byte mustConvert) { @@ -2522,7 +2551,7 @@ private static uint _getJitFlags(IntPtr thisHandle, IntPtr* ppException, CORJIT_ private static IntPtr GetUnmanagedCallbacks() { - void** callbacks = (void**)Marshal.AllocCoTaskMem(sizeof(IntPtr) * 170); + void** callbacks = (void**)Marshal.AllocCoTaskMem(sizeof(IntPtr) * 172); callbacks[0] = (delegate* unmanaged)&_isIntrinsic; callbacks[1] = (delegate* unmanaged)&_getMethodAttribs; @@ -2636,64 +2665,66 @@ private static IntPtr GetUnmanagedCallbacks() callbacks[109] = (delegate* unmanaged)&_runWithErrorTrap; callbacks[110] = (delegate* unmanaged)&_runWithSPMIErrorTrap; callbacks[111] = (delegate* unmanaged)&_getEEInfo; - callbacks[112] = (delegate* unmanaged)&_getJitTimeLogFilename; - callbacks[113] = (delegate* unmanaged)&_getMethodDefFromMethod; - callbacks[114] = (delegate* unmanaged)&_printMethodName; - callbacks[115] = (delegate* unmanaged)&_getMethodNameFromMetadata; - callbacks[116] = (delegate* unmanaged)&_getMethodHash; - callbacks[117] = (delegate* unmanaged)&_getSystemVAmd64PassStructInRegisterDescriptor; - callbacks[118] = (delegate* unmanaged)&_getLoongArch64PassStructInRegisterFlags; - callbacks[119] = (delegate* unmanaged)&_getRISCV64PassStructInRegisterFlags; - callbacks[120] = (delegate* unmanaged)&_getThreadTLSIndex; - callbacks[121] = (delegate* unmanaged)&_getAddrOfCaptureThreadGlobal; - callbacks[122] = (delegate* unmanaged)&_getHelperFtn; - callbacks[123] = (delegate* unmanaged)&_getFunctionEntryPoint; - callbacks[124] = (delegate* unmanaged)&_getFunctionFixedEntryPoint; - callbacks[125] = (delegate* unmanaged)&_getMethodSync; - callbacks[126] = (delegate* unmanaged)&_getLazyStringLiteralHelper; - callbacks[127] = (delegate* unmanaged)&_embedModuleHandle; - callbacks[128] = (delegate* unmanaged)&_embedClassHandle; - callbacks[129] = (delegate* unmanaged)&_embedMethodHandle; - callbacks[130] = (delegate* unmanaged)&_embedFieldHandle; - callbacks[131] = (delegate* unmanaged)&_embedGenericHandle; - callbacks[132] = (delegate* unmanaged)&_getLocationOfThisType; - callbacks[133] = (delegate* unmanaged)&_getAddressOfPInvokeTarget; - callbacks[134] = (delegate* unmanaged)&_GetCookieForPInvokeCalliSig; - callbacks[135] = (delegate* unmanaged)&_canGetCookieForPInvokeCalliSig; - callbacks[136] = (delegate* unmanaged)&_getJustMyCodeHandle; - callbacks[137] = (delegate* unmanaged)&_GetProfilingHandle; - callbacks[138] = (delegate* unmanaged)&_getCallInfo; - callbacks[139] = (delegate* unmanaged)&_getClassDomainID; - callbacks[140] = (delegate* unmanaged)&_getStaticFieldContent; - callbacks[141] = (delegate* unmanaged)&_getObjectContent; - callbacks[142] = (delegate* unmanaged)&_getStaticFieldCurrentClass; - callbacks[143] = (delegate* unmanaged)&_getVarArgsHandle; - callbacks[144] = (delegate* unmanaged)&_canGetVarArgsHandle; - callbacks[145] = (delegate* unmanaged)&_constructStringLiteral; - callbacks[146] = (delegate* unmanaged)&_emptyStringLiteral; - callbacks[147] = (delegate* unmanaged)&_getFieldThreadLocalStoreID; - callbacks[148] = (delegate* unmanaged)&_GetDelegateCtor; - callbacks[149] = (delegate* unmanaged)&_MethodCompileComplete; - callbacks[150] = (delegate* unmanaged)&_getTailCallHelpers; - callbacks[151] = (delegate* unmanaged)&_convertPInvokeCalliToCall; - callbacks[152] = (delegate* unmanaged)&_notifyInstructionSetUsage; - callbacks[153] = (delegate* unmanaged)&_updateEntryPointForTailCall; - callbacks[154] = (delegate* unmanaged)&_allocMem; - callbacks[155] = (delegate* unmanaged)&_reserveUnwindInfo; - callbacks[156] = (delegate* unmanaged)&_allocUnwindInfo; - callbacks[157] = (delegate* unmanaged)&_allocGCInfo; - callbacks[158] = (delegate* unmanaged)&_setEHcount; - callbacks[159] = (delegate* unmanaged)&_setEHinfo; - callbacks[160] = (delegate* unmanaged)&_logMsg; - callbacks[161] = (delegate* unmanaged)&_doAssert; - callbacks[162] = (delegate* unmanaged)&_reportFatalError; - callbacks[163] = (delegate* unmanaged)&_getPgoInstrumentationResults; - callbacks[164] = (delegate* unmanaged)&_allocPgoInstrumentationBySchema; - callbacks[165] = (delegate* unmanaged)&_recordCallSite; - callbacks[166] = (delegate* unmanaged)&_recordRelocation; - callbacks[167] = (delegate* unmanaged)&_getRelocTypeHint; - callbacks[168] = (delegate* unmanaged)&_getExpectedTargetArchitecture; - callbacks[169] = (delegate* unmanaged)&_getJitFlags; + callbacks[112] = (delegate* unmanaged)&_getAsync2Info; + callbacks[113] = (delegate* unmanaged)&_getJitTimeLogFilename; + callbacks[114] = (delegate* unmanaged)&_getMethodDefFromMethod; + callbacks[115] = (delegate* unmanaged)&_printMethodName; + callbacks[116] = (delegate* unmanaged)&_getMethodNameFromMetadata; + callbacks[117] = (delegate* unmanaged)&_getMethodHash; + callbacks[118] = (delegate* unmanaged)&_getSystemVAmd64PassStructInRegisterDescriptor; + callbacks[119] = (delegate* unmanaged)&_getLoongArch64PassStructInRegisterFlags; + callbacks[120] = (delegate* unmanaged)&_getRISCV64PassStructInRegisterFlags; + callbacks[121] = (delegate* unmanaged)&_getThreadTLSIndex; + callbacks[122] = (delegate* unmanaged)&_getAddrOfCaptureThreadGlobal; + callbacks[123] = (delegate* unmanaged)&_getHelperFtn; + callbacks[124] = (delegate* unmanaged)&_getFunctionEntryPoint; + callbacks[125] = (delegate* unmanaged)&_getFunctionFixedEntryPoint; + callbacks[126] = (delegate* unmanaged)&_getMethodSync; + callbacks[127] = (delegate* unmanaged)&_getLazyStringLiteralHelper; + callbacks[128] = (delegate* unmanaged)&_embedModuleHandle; + callbacks[129] = (delegate* unmanaged)&_embedClassHandle; + callbacks[130] = (delegate* unmanaged)&_embedMethodHandle; + callbacks[131] = (delegate* unmanaged)&_embedFieldHandle; + callbacks[132] = (delegate* unmanaged)&_embedGenericHandle; + callbacks[133] = (delegate* unmanaged)&_getLocationOfThisType; + callbacks[134] = (delegate* unmanaged)&_getAddressOfPInvokeTarget; + callbacks[135] = (delegate* unmanaged)&_GetCookieForPInvokeCalliSig; + callbacks[136] = (delegate* unmanaged)&_canGetCookieForPInvokeCalliSig; + callbacks[137] = (delegate* unmanaged)&_getJustMyCodeHandle; + callbacks[138] = (delegate* unmanaged)&_GetProfilingHandle; + callbacks[139] = (delegate* unmanaged)&_getCallInfo; + callbacks[140] = (delegate* unmanaged)&_getClassDomainID; + callbacks[141] = (delegate* unmanaged)&_getStaticFieldContent; + callbacks[142] = (delegate* unmanaged)&_getObjectContent; + callbacks[143] = (delegate* unmanaged)&_getStaticFieldCurrentClass; + callbacks[144] = (delegate* unmanaged)&_getVarArgsHandle; + callbacks[145] = (delegate* unmanaged)&_canGetVarArgsHandle; + callbacks[146] = (delegate* unmanaged)&_constructStringLiteral; + callbacks[147] = (delegate* unmanaged)&_emptyStringLiteral; + callbacks[148] = (delegate* unmanaged)&_getFieldThreadLocalStoreID; + callbacks[149] = (delegate* unmanaged)&_GetDelegateCtor; + callbacks[150] = (delegate* unmanaged)&_MethodCompileComplete; + callbacks[151] = (delegate* unmanaged)&_getTailCallHelpers; + callbacks[152] = (delegate* unmanaged)&_getAsyncResumptionStub; + callbacks[153] = (delegate* unmanaged)&_convertPInvokeCalliToCall; + callbacks[154] = (delegate* unmanaged)&_notifyInstructionSetUsage; + callbacks[155] = (delegate* unmanaged)&_updateEntryPointForTailCall; + callbacks[156] = (delegate* unmanaged)&_allocMem; + callbacks[157] = (delegate* unmanaged)&_reserveUnwindInfo; + callbacks[158] = (delegate* unmanaged)&_allocUnwindInfo; + callbacks[159] = (delegate* unmanaged)&_allocGCInfo; + callbacks[160] = (delegate* unmanaged)&_setEHcount; + callbacks[161] = (delegate* unmanaged)&_setEHinfo; + callbacks[162] = (delegate* unmanaged)&_logMsg; + callbacks[163] = (delegate* unmanaged)&_doAssert; + callbacks[164] = (delegate* unmanaged)&_reportFatalError; + callbacks[165] = (delegate* unmanaged)&_getPgoInstrumentationResults; + callbacks[166] = (delegate* unmanaged)&_allocPgoInstrumentationBySchema; + callbacks[167] = (delegate* unmanaged)&_recordCallSite; + callbacks[168] = (delegate* unmanaged)&_recordRelocation; + callbacks[169] = (delegate* unmanaged)&_getRelocTypeHint; + callbacks[170] = (delegate* unmanaged)&_getExpectedTargetArchitecture; + callbacks[171] = (delegate* unmanaged)&_getJitFlags; return (IntPtr)callbacks; } diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs b/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs index 47e7e6bb7dd710..f259748f8fea70 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs @@ -469,6 +469,7 @@ public enum CorInfoGCType public enum CorInfoClassId { CLASSID_SYSTEM_OBJECT, + CLASSID_SYSTEM_BYTE, CLASSID_TYPED_BYREF, CLASSID_TYPE_HANDLE, CLASSID_FIELD_HANDLE, @@ -870,6 +871,18 @@ public struct InlinedCallFrameInfo public CORINFO_OS osType; } + public unsafe struct CORINFO_ASYNC2_INFO + { + // Class handle for System.Runtime.CompilerServices.Continuation + public CORINFO_CLASS_STRUCT_* continuationClsHnd; + // 'Next' field + public CORINFO_FIELD_STRUCT_* continuationNextFldHnd; + // 'Data' field + public CORINFO_FIELD_STRUCT_* continuationDataFldHnd; + // 'GCData' field + public CORINFO_FIELD_STRUCT_* continuationGCDataFldHnd; + } + // Flags passed from JIT to runtime. public enum CORINFO_GET_TAILCALL_HELPERS_FLAGS { diff --git a/src/coreclr/tools/Common/JitInterface/ThunkGenerator/ThunkInput.txt b/src/coreclr/tools/Common/JitInterface/ThunkGenerator/ThunkInput.txt index bc620591465f48..0f99ca869de5c7 100644 --- a/src/coreclr/tools/Common/JitInterface/ThunkGenerator/ThunkInput.txt +++ b/src/coreclr/tools/Common/JitInterface/ThunkGenerator/ThunkInput.txt @@ -82,6 +82,7 @@ CORINFO_SIG_INFO* CORINFO_RESOLVED_TOKEN*,ref CORINFO_RESOLVED_TOKEN CORINFO_RESOLVED_TOKEN_PTR,CORINFO_RESOLVED_TOKEN*,CORINFO_RESOLVED_TOKEN*,CORINFO_RESOLVED_TOKEN* CORINFO_EE_INFO*,ref CORINFO_EE_INFO +CORINFO_ASYNC2_INFO*,ref CORINFO_ASYNC2_INFO CORINFO_TAILCALL_HELPERS*,ref CORINFO_TAILCALL_HELPERS CORINFO_GENERICHANDLE_RESULT*,ref CORINFO_GENERICHANDLE_RESULT CORINFO_METHOD_INFO*,CORINFO_METHOD_INFO* @@ -273,6 +274,7 @@ FUNCTIONS [ManualNativeWrapper] bool runWithErrorTrap(ICorJitInfo::errorTrapFunction function, void* parameter); [ManualNativeWrapper] bool runWithSPMIErrorTrap(ICorJitInfo::errorTrapFunction function, void* parameter); void getEEInfo(CORINFO_EE_INFO* pEEInfoOut); + void getAsync2Info(CORINFO_ASYNC2_INFO* pAsync2InfoOut); const char16_t * getJitTimeLogFilename(); mdMethodDef getMethodDefFromMethod(CORINFO_METHOD_HANDLE hMethod); size_t printMethodName(CORINFO_METHOD_HANDLE ftn, char* buffer, size_t bufferSize, size_t* pRequiredBufferSize) @@ -312,6 +314,7 @@ FUNCTIONS CORINFO_METHOD_HANDLE GetDelegateCtor(CORINFO_METHOD_HANDLE methHnd, CORINFO_CLASS_HANDLE clsHnd, CORINFO_METHOD_HANDLE targetMethodHnd, DelegateCtorArgs * pCtorData); void MethodCompileComplete(CORINFO_METHOD_HANDLE methHnd); bool getTailCallHelpers(CORINFO_RESOLVED_TOKEN* callToken, CORINFO_SIG_INFO* sig, CORINFO_GET_TAILCALL_HELPERS_FLAGS flags, CORINFO_TAILCALL_HELPERS* pResult); + CORINFO_METHOD_HANDLE getAsyncResumptionStub(); bool convertPInvokeCalliToCall(CORINFO_RESOLVED_TOKEN * pResolvedToken, bool mustConvert); bool notifyInstructionSetUsage(CORINFO_InstructionSet instructionSet,bool supportEnabled); void updateEntryPointForTailCall(CORINFO_CONST_LOOKUP* entryPoint); diff --git a/src/coreclr/tools/aot/jitinterface/jitinterface_generated.h b/src/coreclr/tools/aot/jitinterface/jitinterface_generated.h index 2ec781cb7f7b50..737037285279fb 100644 --- a/src/coreclr/tools/aot/jitinterface/jitinterface_generated.h +++ b/src/coreclr/tools/aot/jitinterface/jitinterface_generated.h @@ -123,6 +123,7 @@ struct JitInterfaceCallbacks bool (* runWithErrorTrap)(void * thisHandle, CorInfoExceptionClass** ppException, ICorJitInfo::errorTrapFunction function, void* parameter); bool (* runWithSPMIErrorTrap)(void * thisHandle, CorInfoExceptionClass** ppException, ICorJitInfo::errorTrapFunction function, void* parameter); void (* getEEInfo)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_EE_INFO* pEEInfoOut); + void (* getAsync2Info)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_ASYNC2_INFO* pAsync2InfoOut); const char16_t* (* getJitTimeLogFilename)(void * thisHandle, CorInfoExceptionClass** ppException); mdMethodDef (* getMethodDefFromMethod)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_METHOD_HANDLE hMethod); size_t (* printMethodName)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_METHOD_HANDLE ftn, char* buffer, size_t bufferSize, size_t* pRequiredBufferSize); @@ -162,6 +163,7 @@ struct JitInterfaceCallbacks CORINFO_METHOD_HANDLE (* GetDelegateCtor)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_METHOD_HANDLE methHnd, CORINFO_CLASS_HANDLE clsHnd, CORINFO_METHOD_HANDLE targetMethodHnd, DelegateCtorArgs* pCtorData); void (* MethodCompileComplete)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_METHOD_HANDLE methHnd); bool (* getTailCallHelpers)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_RESOLVED_TOKEN* callToken, CORINFO_SIG_INFO* sig, CORINFO_GET_TAILCALL_HELPERS_FLAGS flags, CORINFO_TAILCALL_HELPERS* pResult); + CORINFO_METHOD_HANDLE (* getAsyncResumptionStub)(void * thisHandle, CorInfoExceptionClass** ppException); bool (* convertPInvokeCalliToCall)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_RESOLVED_TOKEN* pResolvedToken, bool mustConvert); bool (* notifyInstructionSetUsage)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_InstructionSet instructionSet, bool supportEnabled); void (* updateEntryPointForTailCall)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_CONST_LOOKUP* entryPoint); @@ -1279,6 +1281,14 @@ class JitInterfaceWrapper : public ICorJitInfo if (pException != nullptr) throw pException; } + virtual void getAsync2Info( + CORINFO_ASYNC2_INFO* pAsync2InfoOut) +{ + CorInfoExceptionClass* pException = nullptr; + _callbacks->getAsync2Info(_thisHandle, &pException, pAsync2InfoOut); + if (pException != nullptr) throw pException; +} + virtual const char16_t* getJitTimeLogFilename() { CorInfoExceptionClass* pException = nullptr; @@ -1669,6 +1679,14 @@ class JitInterfaceWrapper : public ICorJitInfo return temp; } + virtual CORINFO_METHOD_HANDLE getAsyncResumptionStub() +{ + CorInfoExceptionClass* pException = nullptr; + CORINFO_METHOD_HANDLE temp = _callbacks->getAsyncResumptionStub(_thisHandle, &pException); + if (pException != nullptr) throw pException; + return temp; +} + virtual bool convertPInvokeCalliToCall( CORINFO_RESOLVED_TOKEN* pResolvedToken, bool mustConvert) diff --git a/src/coreclr/tools/superpmi/superpmi-shim-collector/icorjitinfo.cpp b/src/coreclr/tools/superpmi/superpmi-shim-collector/icorjitinfo.cpp index 90ad83f09b0a4b..b8f58508824110 100644 --- a/src/coreclr/tools/superpmi/superpmi-shim-collector/icorjitinfo.cpp +++ b/src/coreclr/tools/superpmi/superpmi-shim-collector/icorjitinfo.cpp @@ -1306,6 +1306,11 @@ void interceptor_ICJI::getEEInfo(CORINFO_EE_INFO* pEEInfoOut) mc->recGetEEInfo(pEEInfoOut); } +void interceptor_ICJI::getAsync2Info(CORINFO_ASYNC2_INFO* pAsync2Info) +{ + // TODO +} + // Returns name of the JIT timer log const char16_t* interceptor_ICJI::getJitTimeLogFilename() { @@ -1723,6 +1728,12 @@ bool interceptor_ICJI::getTailCallHelpers( return result; } +CORINFO_METHOD_HANDLE interceptor_ICJI::getAsyncResumptionStub() +{ + // TODO + return NULL; +} + void interceptor_ICJI::updateEntryPointForTailCall(CORINFO_CONST_LOOKUP* entryPoint) { mc->cr->AddCall("updateEntryPointForTailCall"); 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 ef01fa5977d75d..16c50ee49523c6 100644 --- a/src/coreclr/tools/superpmi/superpmi-shim-counter/icorjitinfo_generated.cpp +++ b/src/coreclr/tools/superpmi/superpmi-shim-counter/icorjitinfo_generated.cpp @@ -909,6 +909,13 @@ void interceptor_ICJI::getEEInfo( original_ICorJitInfo->getEEInfo(pEEInfoOut); } +void interceptor_ICJI::getAsync2Info( + CORINFO_ASYNC2_INFO* pAsync2InfoOut) +{ + mcs->AddCall("getAsync2Info"); + original_ICorJitInfo->getAsync2Info(pAsync2InfoOut); +} + const char16_t* interceptor_ICJI::getJitTimeLogFilename() { mcs->AddCall("getJitTimeLogFilename"); @@ -1229,6 +1236,12 @@ bool interceptor_ICJI::getTailCallHelpers( return original_ICorJitInfo->getTailCallHelpers(callToken, sig, flags, pResult); } +CORINFO_METHOD_HANDLE interceptor_ICJI::getAsyncResumptionStub() +{ + mcs->AddCall("getAsyncResumptionStub"); + return original_ICorJitInfo->getAsyncResumptionStub(); +} + bool interceptor_ICJI::convertPInvokeCalliToCall( CORINFO_RESOLVED_TOKEN* pResolvedToken, bool mustConvert) 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 8dd3ebb08d92ef..68268b4d419ad8 100644 --- a/src/coreclr/tools/superpmi/superpmi-shim-simple/icorjitinfo_generated.cpp +++ b/src/coreclr/tools/superpmi/superpmi-shim-simple/icorjitinfo_generated.cpp @@ -797,6 +797,12 @@ void interceptor_ICJI::getEEInfo( original_ICorJitInfo->getEEInfo(pEEInfoOut); } +void interceptor_ICJI::getAsync2Info( + CORINFO_ASYNC2_INFO* pAsync2InfoOut) +{ + original_ICorJitInfo->getAsync2Info(pAsync2InfoOut); +} + const char16_t* interceptor_ICJI::getJitTimeLogFilename() { return original_ICorJitInfo->getJitTimeLogFilename(); @@ -1078,6 +1084,11 @@ bool interceptor_ICJI::getTailCallHelpers( return original_ICorJitInfo->getTailCallHelpers(callToken, sig, flags, pResult); } +CORINFO_METHOD_HANDLE interceptor_ICJI::getAsyncResumptionStub() +{ + return original_ICorJitInfo->getAsyncResumptionStub(); +} + bool interceptor_ICJI::convertPInvokeCalliToCall( CORINFO_RESOLVED_TOKEN* pResolvedToken, bool mustConvert) diff --git a/src/coreclr/tools/superpmi/superpmi/icorjitinfo.cpp b/src/coreclr/tools/superpmi/superpmi/icorjitinfo.cpp index 7e7f3e67416c85..f476ee8186bd16 100644 --- a/src/coreclr/tools/superpmi/superpmi/icorjitinfo.cpp +++ b/src/coreclr/tools/superpmi/superpmi/icorjitinfo.cpp @@ -1117,6 +1117,11 @@ void MyICJI::getEEInfo(CORINFO_EE_INFO* pEEInfoOut) jitInstance->mc->repGetEEInfo(pEEInfoOut); } +void MyICJI::getAsync2Info(CORINFO_ASYNC2_INFO* pAsync2Info) +{ + // TODO +} + // Returns name of the JIT timer log const char16_t* MyICJI::getJitTimeLogFilename() { @@ -1465,6 +1470,13 @@ bool MyICJI::getTailCallHelpers( return jitInstance->mc->repGetTailCallHelpers(callToken, sig, flags, pResult); } +CORINFO_METHOD_HANDLE MyICJI::getAsyncResumptionStub() +{ + jitInstance->mc->cr->AddCall("getAsyncResumptionStub"); + // TODO + return NULL; +} + bool MyICJI::convertPInvokeCalliToCall(CORINFO_RESOLVED_TOKEN* pResolvedToken, bool fMustConvert) { jitInstance->mc->cr->AddCall("convertPInvokeCalliToCall"); diff --git a/src/coreclr/vm/appdomain.cpp b/src/coreclr/vm/appdomain.cpp index 51aec1b9e1c69d..6301eb126e96e2 100644 --- a/src/coreclr/vm/appdomain.cpp +++ b/src/coreclr/vm/appdomain.cpp @@ -1356,6 +1356,7 @@ void SystemDomain::LoadBaseSystemClasses() // initialize cast cache here. CastCache::Initialize(); ECall::PopulateManagedCastHelpers(); + ECall::PopulateAsyncHelpers(); // used by IsImplicitInterfaceOfSZArray CoreLibBinder::GetClass(CLASS__IENUMERABLEGENERIC); diff --git a/src/coreclr/vm/corelib.h b/src/coreclr/vm/corelib.h index 42be7407625842..14e4ed9800424c 100644 --- a/src/coreclr/vm/corelib.h +++ b/src/coreclr/vm/corelib.h @@ -676,7 +676,8 @@ DEFINE_METHOD(RUNTIME_HELPERS, ENUM_EQUALS, EnumEquals, NoSig) DEFINE_METHOD(RUNTIME_HELPERS, ENUM_COMPARE_TO, EnumCompareTo, NoSig) DEFINE_METHOD(RUNTIME_HELPERS, ALLOC_TAILCALL_ARG_BUFFER, AllocTailCallArgBuffer, SM_Int_IntPtr_RetIntPtr) DEFINE_METHOD(RUNTIME_HELPERS, GET_TAILCALL_INFO, GetTailCallInfo, NoSig) -DEFINE_METHOD(RUNTIME_HELPERS, DISPATCH_TAILCALLS, DispatchTailCalls, NoSig) +DEFINE_METHOD(RUNTIME_HELPERS, DISPATCH_TAILCALLS, DispatchTailCalls, NoSig) +DEFINE_METHOD(RUNTIME_HELPERS, ALLOC_CONTINUATION, AllocContinuation, NoSig) DEFINE_METHOD(RUNTIME_HELPERS, GET_OR_CREATE_RESUMPTION_DELEGATE, GetOrCreateResumptionDelegate, NoSig) DEFINE_METHOD(RUNTIME_HELPERS, UNSAFE_AWAIT_AWAITER_FROM_RUNTIME_ASYNC_2, UnsafeAwaitAwaiterFromRuntimeAsync, GM_U_RetT) @@ -756,6 +757,13 @@ DEFINE_CLASS_U(CompilerServices, TailCallTls, TailCallTls) DEFINE_FIELD_U(Frame, TailCallTls, m_frame) DEFINE_FIELD_U(ArgBuffer, TailCallTls, m_argBuffer) +DEFINE_CLASS(CONTINUATION, CompilerServices, Continuation) +DEFINE_FIELD(CONTINUATION, NEXT, Next) +DEFINE_FIELD(CONTINUATION, RESUME, Resume) +DEFINE_FIELD(CONTINUATION, STATE, State) +DEFINE_FIELD(CONTINUATION, DATA, Data) +DEFINE_FIELD(CONTINUATION, GCDATA, GCData) + DEFINE_CLASS(RUNTIME_WRAPPED_EXCEPTION, CompilerServices, RuntimeWrappedException) DEFINE_METHOD(RUNTIME_WRAPPED_EXCEPTION, OBJ_CTOR, .ctor, IM_Obj_RetVoid) DEFINE_FIELD(RUNTIME_WRAPPED_EXCEPTION, WRAPPED_EXCEPTION, _wrappedException) @@ -972,6 +980,7 @@ DEFINE_METHOD(STUBHELPERS, VALIDATE_BYREF, Validate DEFINE_METHOD(STUBHELPERS, GET_STUB_CONTEXT, GetStubContext, SM_RetIntPtr) DEFINE_METHOD(STUBHELPERS, LOG_PINNED_ARGUMENT, LogPinnedArgument, SM_IntPtr_IntPtr_RetVoid) DEFINE_METHOD(STUBHELPERS, NEXT_CALL_RETURN_ADDRESS, NextCallReturnAddress, SM_RetIntPtr) +DEFINE_METHOD(STUBHELPERS, ASYNC2_CALL_CONTINUATION, Async2CallContinuation, SM_RetObj) DEFINE_METHOD(STUBHELPERS, SAFE_HANDLE_ADD_REF, SafeHandleAddRef, SM_SafeHandle_RefBool_RetIntPtr) DEFINE_METHOD(STUBHELPERS, SAFE_HANDLE_RELEASE, SafeHandleRelease, SM_SafeHandle_RetVoid) diff --git a/src/coreclr/vm/dllimport.h b/src/coreclr/vm/dllimport.h index 256b950799336e..11853bd5e7d0c2 100644 --- a/src/coreclr/vm/dllimport.h +++ b/src/coreclr/vm/dllimport.h @@ -195,6 +195,7 @@ enum ILStubTypes ILSTUB_TAILCALL_STOREARGS = 0x80000008, ILSTUB_TAILCALL_CALLTARGET = 0x80000009, ILSTUB_STATIC_VIRTUAL_DISPATCH_STUB = 0x8000000A, + ILSTUB_ASYNC_RESUME = 0x8000000B, }; #ifdef FEATURE_COMINTEROP @@ -234,6 +235,7 @@ inline bool SF_IsInstantiatingStub (DWORD dwStubFlags) { LIMITED_METHOD_CON #endif inline bool SF_IsTailCallStoreArgsStub (DWORD dwStubFlags) { LIMITED_METHOD_CONTRACT; return (dwStubFlags == ILSTUB_TAILCALL_STOREARGS); } inline bool SF_IsTailCallCallTargetStub (DWORD dwStubFlags) { LIMITED_METHOD_CONTRACT; return (dwStubFlags == ILSTUB_TAILCALL_CALLTARGET); } +inline bool SF_IsAsyncResumeStub (DWORD dwStubFlags) { LIMITED_METHOD_CONTRACT; return (dwStubFlags == ILSTUB_ASYNC_RESUME); } inline bool SF_IsCOMStub (DWORD dwStubFlags) { LIMITED_METHOD_CONTRACT; return COM_ONLY(dwStubFlags < NDIRECTSTUB_FL_INVALID && 0 != (dwStubFlags & NDIRECTSTUB_FL_COM)); } inline bool SF_IsCOMLateBoundStub (DWORD dwStubFlags) { LIMITED_METHOD_CONTRACT; return COM_ONLY(dwStubFlags < NDIRECTSTUB_FL_INVALID && 0 != (dwStubFlags & NDIRECTSTUB_FL_COMLATEBOUND)); } @@ -255,6 +257,11 @@ inline bool SF_IsSharedStub(DWORD dwStubFlags) return false; } + if (SF_IsAsyncResumeStub(dwStubFlags)) + { + return false; + } + return true; } diff --git a/src/coreclr/vm/ecall.cpp b/src/coreclr/vm/ecall.cpp index 37ac50d124f6f6..6f33f0b2af4f91 100644 --- a/src/coreclr/vm/ecall.cpp +++ b/src/coreclr/vm/ecall.cpp @@ -146,6 +146,15 @@ void ECall::PopulateManagedCastHelpers() SetJitHelperFunction(CORINFO_HELP_LDELEMA_REF, pDest); } +void ECall::PopulateAsyncHelpers() +{ + STANDARD_VM_CONTRACT; + + MethodDesc* pMD = CoreLibBinder::GetMethod((BinderMethodID)(METHOD__RUNTIME_HELPERS__ALLOC_CONTINUATION)); + PCODE pDest = pMD->GetMultiCallableAddrOfCode(); + SetJitHelperFunction(CORINFO_HELP_ALLOC_CONTINUATION, pDest); +} + static CrstStatic gFCallLock; // This variable is used to force the compiler not to tailcall a function. diff --git a/src/coreclr/vm/ecall.h b/src/coreclr/vm/ecall.h index bc9d63ae467137..fb888f8fcee456 100644 --- a/src/coreclr/vm/ecall.h +++ b/src/coreclr/vm/ecall.h @@ -96,6 +96,8 @@ class ECall static void PopulateManagedCastHelpers(); + static void PopulateAsyncHelpers(); + #ifdef DACCESS_COMPILE // Enumerates all gFCallMethods for minidumps. static void EnumFCallMethods(); diff --git a/src/coreclr/vm/ilstubcache.cpp b/src/coreclr/vm/ilstubcache.cpp index cb029595955016..e078895b0338d2 100644 --- a/src/coreclr/vm/ilstubcache.cpp +++ b/src/coreclr/vm/ilstubcache.cpp @@ -160,6 +160,7 @@ namespace case DynamicMethodDesc::StubTailCallStoreArgs: return "IL_STUB_StoreTailCallArgs"; case DynamicMethodDesc::StubTailCallCallTarget: return "IL_STUB_CallTailCallTarget"; case DynamicMethodDesc::StubVirtualStaticMethodDispatch: return "IL_STUB_bVirtualStaticMethodDispatch"; + case DynamicMethodDesc::StubAsyncResume: return "IL_STUB_AsyncResume"; default: UNREACHABLE_MSG("Unknown stub type"); } @@ -278,6 +279,11 @@ MethodDesc* ILStubCache::CreateNewMethodDesc(LoaderHeap* pCreationHeap, MethodTa pMD->SetILStubType(DynamicMethodDesc::StubTailCallCallTarget); } else + if (SF_IsAsyncResumeStub(dwStubFlags)) + { + pMD->SetILStubType(DynamicMethodDesc::StubAsyncResume); + } + else #ifdef FEATURE_COMINTEROP if (SF_IsCOMStub(dwStubFlags)) { diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index 6b4dba089e19e6..d3489493352c0f 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -4202,6 +4202,9 @@ CORINFO_CLASS_HANDLE CEEInfo::getBuiltinClass(CorInfoClassId classId) case CLASSID_SYSTEM_OBJECT: result = CORINFO_CLASS_HANDLE(g_pObjectClass); break; + case CLASSID_SYSTEM_BYTE: + result = CORINFO_CLASS_HANDLE(CoreLibBinder::GetClass(CLASS__BYTE)); + break; case CLASSID_TYPED_BYREF: result = CORINFO_CLASS_HANDLE(g_TypedReferenceMT); break; @@ -10037,6 +10040,22 @@ void CEEInfo::getEEInfo(CORINFO_EE_INFO *pEEInfoOut) EE_TO_JIT_TRANSITION(); } +void CEEInfo::getAsync2Info(CORINFO_ASYNC2_INFO* pAsync2InfoOut) +{ + CONTRACTL { + THROWS; + GC_TRIGGERS; + MODE_PREEMPTIVE; + } CONTRACTL_END; + + pAsync2InfoOut->continuationClsHnd = CORINFO_CLASS_HANDLE(CoreLibBinder::GetClass(CLASS__CONTINUATION)); + pAsync2InfoOut->continuationNextFldHnd = CORINFO_FIELD_HANDLE(CoreLibBinder::GetField(FIELD__CONTINUATION__NEXT)); + pAsync2InfoOut->continuationResumeFldHnd = CORINFO_FIELD_HANDLE(CoreLibBinder::GetField(FIELD__CONTINUATION__RESUME)); + pAsync2InfoOut->continuationStateFldHnd = CORINFO_FIELD_HANDLE(CoreLibBinder::GetField(FIELD__CONTINUATION__STATE)); + pAsync2InfoOut->continuationDataFldHnd = CORINFO_FIELD_HANDLE(CoreLibBinder::GetField(FIELD__CONTINUATION__DATA)); + pAsync2InfoOut->continuationGCDataFldHnd = CORINFO_FIELD_HANDLE(CoreLibBinder::GetField(FIELD__CONTINUATION__GCDATA)); +} + const char16_t * CEEInfo::getJitTimeLogFilename() { CONTRACTL { @@ -10604,7 +10623,8 @@ void* CEEJitInfo::getHelperFtn(CorInfoHelpFunc ftnNum, /* IN */ dynamicFtnNum == DYNAMIC_CORINFO_HELP_CHKCASTCLASS_SPECIAL || dynamicFtnNum == DYNAMIC_CORINFO_HELP_UNBOX || dynamicFtnNum == DYNAMIC_CORINFO_HELP_ARRADDR_ST || - dynamicFtnNum == DYNAMIC_CORINFO_HELP_LDELEMA_REF) + dynamicFtnNum == DYNAMIC_CORINFO_HELP_LDELEMA_REF || + dynamicFtnNum == DYNAMIC_CORINFO_HELP_ALLOC_CONTINUATION) { Precode* pPrecode = Precode::GetPrecodeFromEntryPoint((PCODE)hlpDynamicFuncTable[dynamicFtnNum].pfnHelper); _ASSERTE(pPrecode->GetType() == PRECODE_FIXUP); @@ -14172,6 +14192,235 @@ bool CEEInfo::getTailCallHelpers(CORINFO_RESOLVED_TOKEN* callToken, return success; } +static Signature AllocateSignature(LoaderAllocator* alloc, SigBuilder& sigBuilder) +{ + DWORD sigLen; + PCCOR_SIGNATURE builderSig = (PCCOR_SIGNATURE)sigBuilder.GetSignature(&sigLen); + AllocMemTracker pamTracker; + PVOID newBlob = pamTracker.Track(alloc->GetHighFrequencyHeap()->AllocMem(S_SIZE_T(sigLen))); + memcpy(newBlob, builderSig, sigLen); + + pamTracker.SuppressRelease(); + return Signature((PCCOR_SIGNATURE)newBlob, sigLen); +} + +static Signature BuildResumptionStubSignature(LoaderAllocator* alloc) +{ + SigBuilder sigBuilder; + sigBuilder.AppendByte(IMAGE_CEE_CS_CALLCONV_DEFAULT); + sigBuilder.AppendData(2); // 2 arguments + sigBuilder.AppendElementType(ELEMENT_TYPE_OBJECT); // return type + sigBuilder.AppendElementType(ELEMENT_TYPE_OBJECT); // continuation + sigBuilder.AppendElementType(ELEMENT_TYPE_OBJECT); // exception + + return AllocateSignature(alloc, sigBuilder); +} + +static Signature BuildResumptionStubCalliSignature(MetaSig& msig, LoaderAllocator* alloc) +{ + unsigned numArgs = 0; + if (msig.HasThis()) + { + numArgs++; + } + + if ((msig.GetCallingConvention() & CORINFO_CALLCONV_PARAMTYPE) != 0) + { + numArgs++; + } + + numArgs++; // Continuation + + numArgs += msig.NumFixedArgs(); + + SigBuilder sigBuilder; + sigBuilder.AppendByte(IMAGE_CEE_CS_CALLCONV_DEFAULT); + sigBuilder.AppendData(numArgs); + + auto appendTypeHandle = [&](TypeHandle th) { + _ASSERTE(!th.IsByRef()); + CorElementType ty = th.GetSignatureCorElementType(); + if (CorTypeInfo::IsObjRef(ty)) + { + // Especially to normalize System.__Canon. + sigBuilder.AppendElementType(ELEMENT_TYPE_OBJECT); + } + else if (CorTypeInfo::IsPrimitiveType(ty)) + { + sigBuilder.AppendElementType(ty); + } + else + { + sigBuilder.AppendElementType(ELEMENT_TYPE_INTERNAL); + sigBuilder.AppendPointer(th.AsPtr()); + } + }; + + appendTypeHandle(msig.GetRetTypeHandleThrowing()); // return type + if (msig.HasThis()) + { + sigBuilder.AppendElementType(ELEMENT_TYPE_OBJECT); + } +#ifndef TARGET_X86 + if ((msig.GetCallingConvention() & CORINFO_CALLCONV_PARAMTYPE) != 0) + { + sigBuilder.AppendElementType(ELEMENT_TYPE_I); + } + + sigBuilder.AppendElementType(ELEMENT_TYPE_OBJECT); // continuation +#endif + + msig.Reset(); + CorElementType ty; + while ((ty = msig.NextArg()) != ELEMENT_TYPE_END) + { + TypeHandle tyHnd = msig.GetLastTypeHandleThrowing(); + appendTypeHandle(tyHnd); + } + +#ifdef TARGET_X86 + if ((msig.GetCallingConvention() & CORINFO_CALLCONV_PARAMTYPE) != 0) + { + sigBuilder.AppendElementType(ELEMENT_TYPE_I); + } + + sigBuilder.AppendElementType(ELEMENT_TYPE_OBJECT); // continuation +#endif + + return AllocateSignature(alloc, sigBuilder); +} + +CORINFO_METHOD_HANDLE CEEInfo::getAsyncResumptionStub() +{ + CONTRACTL{ + THROWS; + GC_TRIGGERS; + MODE_PREEMPTIVE; + } CONTRACTL_END; + + MethodDesc* md = m_pMethodBeingCompiled; + // Not hard to support, just requires calls through a pointer and passing the + _ASSERTE(!md->IsSharedByGenericInstantiations()); + + LoaderAllocator* loaderAlloc = md->GetLoaderAllocator(); + + Signature stubSig = BuildResumptionStubSignature(md->GetLoaderAllocator()); + + MetaSig msig(md); + Signature calliSig = BuildResumptionStubCalliSignature(msig, md->GetLoaderAllocator()); + + SigTypeContext emptyCtx; + ILStubLinker sl(md->GetModule(), stubSig, &emptyCtx, NULL, ILSTUB_LINKER_FLAG_NONE); + + ILCodeStream* pCode = sl.NewCodeStream(ILStubLinker::kDispatch); + + int numArgs = 0; + + if (msig.HasThis()) + { + _ASSERTE(!md->GetMethodTable()->IsValueType()); + pCode->EmitLDNULL(); + numArgs++; + } + +#ifndef TARGET_X86 + if ((msig.GetCallingConvention() & CORINFO_CALLCONV_PARAMTYPE) != 0) + { + _ASSERTE(!"Generic context currently unhandled"); + // Will need JIT to store this in an agreed upon place. + numArgs++; + } + + // Continuation + pCode->EmitLDARG(0); + numArgs++; +#endif + + msig.Reset(); + CorElementType ty; + while ((ty = msig.NextArg()) != ELEMENT_TYPE_END) + { + TypeHandle tyHnd = msig.GetLastTypeHandleThrowing(); + DWORD loc = pCode->NewLocal(LocalDesc(tyHnd)); + pCode->EmitLDLOCA(loc); + pCode->EmitINITOBJ(pCode->GetToken(tyHnd)); + pCode->EmitLDLOC(loc); + numArgs++; + } + +#ifdef TARGET_X86 + // Continuation + pCode->EmitLDARG(0); + nmArgs++; +#endif + + // TODO: This can use indirection of precode slot directly. + pCode->EmitLDC(md->GetMultiCallableAddrOfCode(CORINFO_ACCESS_ANY)); + + pCode->EmitCALLI(pCode->GetSigToken(calliSig.GetRawSig(), calliSig.GetRawSigLen()), numArgs, msig.IsReturnTypeVoid() ? 0 : 1); + + DWORD resultLoc = UINT_MAX; + if (!msig.IsReturnTypeVoid()) + { + resultLoc = pCode->NewLocal(LocalDesc(msig.GetRetTypeHandleThrowing())); + pCode->EmitSTLOC(resultLoc); + } + + DWORD newContinuationLoc = pCode->NewLocal(ELEMENT_TYPE_OBJECT); + pCode->EmitCALL(METHOD__STUBHELPERS__ASYNC2_CALL_CONTINUATION, 0, 1); + pCode->EmitSTLOC(newContinuationLoc); + + if (!msig.IsReturnTypeVoid()) + { + ILCodeLabel* noResult = pCode->NewCodeLabel(); + pCode->EmitLDLOC(newContinuationLoc); + pCode->EmitBRTRUE(noResult); + + // Load 'next' of current continuation + pCode->EmitLDARG(0); + pCode->EmitCALL(METHOD__RUNTIME_HELPERS__GET_RAW_DATA, 1, 1); + pCode->EmitLDFLD(FIELD__CONTINUATION__NEXT); + + // Load 'gcdata' of next continuation + pCode->EmitCALL(METHOD__RUNTIME_HELPERS__GET_RAW_DATA, 1, 1); + pCode->EmitLDFLD(FIELD__CONTINUATION__GCDATA); + + // Now we have the GC array. At the first index is the result. + pCode->EmitLDC(0); + + // Box the result. + pCode->EmitLDLOC(resultLoc); + pCode->EmitBOX(pCode->GetToken(msig.GetRetTypeHandleThrowing())); + + // Finally store it. + pCode->EmitSTELEM_REF(); + + pCode->EmitLabel(noResult); + } + + pCode->EmitLDLOC(newContinuationLoc); + pCode->EmitRET(); + + MethodDesc* result = + ILStubCache::CreateAndLinkNewILStubMethodDesc( + md->GetLoaderAllocator(), + md->GetLoaderModule()->GetILStubCache()->GetOrCreateStubMethodTable(md->GetLoaderModule()), + ILSTUB_ASYNC_RESUME, + md->GetModule(), + stubSig.GetRawSig(), stubSig.GetRawSigLen(), + &emptyCtx, + &sl); + +#ifdef _DEBUG + LOG((LF_STUBS, LL_INFO1000, "ASYNC: Resumption stub created\n")); + sl.LogILStub(CORJIT_FLAGS()); +#endif + + result->DoPrestub(NULL); + + return CORINFO_METHOD_HANDLE(result); +} + bool CEEInfo::convertPInvokeCalliToCall(CORINFO_RESOLVED_TOKEN * pResolvedToken, bool fMustConvert) { return false; diff --git a/src/coreclr/vm/method.hpp b/src/coreclr/vm/method.hpp index 25c16852c12986..18f1fb92c87689 100644 --- a/src/coreclr/vm/method.hpp +++ b/src/coreclr/vm/method.hpp @@ -2527,6 +2527,8 @@ class DynamicMethodDesc : public StoredSigMethodDesc StubVirtualStaticMethodDispatch, + StubAsyncResume, + StubLast }; diff --git a/src/coreclr/vm/prestub.cpp b/src/coreclr/vm/prestub.cpp index 759117e95203b8..4948dddee95087 100644 --- a/src/coreclr/vm/prestub.cpp +++ b/src/coreclr/vm/prestub.cpp @@ -1470,6 +1470,8 @@ bool MethodDesc::TryGenerateAsyncThunk(DynamicResolver** resolver, COR_ILMETHOD_ return false; } + _ASSERTE(CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_RuntimeAsyncViaJitGeneratedStateMachines) == 0); + ULONG offsetOfAsyncDetailsUnused; AsyncTaskMethod asyncType = ClassifyAsyncMethod(GetSigPointer(), GetModule(), &offsetOfAsyncDetailsUnused); MethodDesc *pAsyncOtherVariant = this->GetAsyncOtherVariant(); diff --git a/src/coreclr/vm/stubgen.cpp b/src/coreclr/vm/stubgen.cpp index 7c8be127608431..4ed482b410a8f6 100644 --- a/src/coreclr/vm/stubgen.cpp +++ b/src/coreclr/vm/stubgen.cpp @@ -1192,6 +1192,11 @@ void ILCodeStream::EmitBNE_UN(ILCodeLabel* pCodeLabel) WRAPPER_NO_CONTRACT; Emit(CEE_BNE_UN, -2, (UINT_PTR)pCodeLabel); } +void ILCodeStream::EmitBOX(int token) +{ + WRAPPER_NO_CONTRACT; + Emit(CEE_BOX, 0, token); +} void ILCodeStream::EmitBR(ILCodeLabel* pCodeLabel) { WRAPPER_NO_CONTRACT; @@ -1827,6 +1832,11 @@ void ILCodeStream::EmitUNALIGNED(BYTE alignment) Emit(CEE_UNALIGNED, 0, alignment); } +void ILCodeStream::EmitUNBOX(int token) +{ + WRAPPER_NO_CONTRACT; + Emit(CEE_UNBOX, 0, token); +} void ILCodeStream::EmitNEWOBJ(BinderMethodID id, int numInArgs) { diff --git a/src/coreclr/vm/stubgen.h b/src/coreclr/vm/stubgen.h index 595de649220cc5..81d8d1130d5e45 100644 --- a/src/coreclr/vm/stubgen.h +++ b/src/coreclr/vm/stubgen.h @@ -708,6 +708,7 @@ class ILCodeStream void EmitBLE_UN (ILCodeLabel* pCodeLabel); void EmitBLT (ILCodeLabel* pCodeLabel); void EmitBNE_UN (ILCodeLabel* pCodeLabel); + void EmitBOX (int token); void EmitBR (ILCodeLabel* pCodeLabel); void EmitBREAK (); void EmitBRFALSE (ILCodeLabel* pCodeLabel); @@ -799,6 +800,7 @@ class ILCodeStream void EmitSUB (); void EmitTHROW (); void EmitUNALIGNED (BYTE alignment); + void EmitUNBOX (int token); // Overloads to simplify common usage patterns void EmitNEWOBJ (BinderMethodID id, int numInArgs); From 4cc1f1882b7d29a9bfc441f5cbd4ad9df09d29fc Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Mon, 23 Oct 2023 17:47:21 +0000 Subject: [PATCH 037/203] Merged PR 34735: Simplify the async2 api surface --- docs/design/features/runtime-handled-tasks.md | 31 +--- .../src/CompatibilitySuppressions.xml | 12 -- .../Strategies/FileStreamHelpers.Windows.cs | 2 +- .../ConfiguredValueTaskAwaitable.cs | 4 +- .../CompilerServices/INotifyCompletion.cs | 28 ---- .../CompilerServices/RuntimeHelpers.cs | 135 +++++------------- .../Runtime/CompilerServices/TaskAwaiter.cs | 8 +- .../CompilerServices/ValueTaskAwaiter.cs | 4 +- .../CompilerServices/YieldAwaitable.cs | 2 +- .../System.Runtime/ref/System.Runtime.cs | 44 ++---- .../runtimetask-asyncfibonacci-with-yields.il | 30 +++- 11 files changed, 83 insertions(+), 217 deletions(-) diff --git a/docs/design/features/runtime-handled-tasks.md b/docs/design/features/runtime-handled-tasks.md index 64accff939822f..5402dde90ddb47 100644 --- a/docs/design/features/runtime-handled-tasks.md +++ b/docs/design/features/runtime-handled-tasks.md @@ -131,34 +131,11 @@ The StackTrace of a exception thrown within an async2 method shall include any a The runtime shall provide the following apis ``` - public interface ICriticalNotifyCompletion2 : ICriticalNotifyCompletion - { - bool IsCompleted { get; } - void GetResult(); - } - public interface ICriticalNotifyCompletion2 : ICriticalNotifyCompletion - { - bool IsCompleted { get; } - TResult GetResult(); - } - public interface INotifyCompletion2 : INotifyCompletion - { - bool IsCompleted { get; } - void GetResult(); - } - public interface INotifyCompletion2 : INotifyCompletion - { - bool IsCompleted { get; } - TResult GetResult(); - } - - public static async2 void AwaitAwaiterFromRuntimeAsync(TAwaiter awaiter) where TAwaiter : INotifyCompletion2 - public static async2 TResult AwaitAwaiterFromRuntimeAsync(TAwaiter awaiter) where TAwaiter : INotifyCompletion2 - public static async2 void UnsafeAwaitAwaiterFromRuntimeAsync(TAwaiter awaiter) where TAwaiter : ICriticalNotifyCompletion2 - public static async2 TResult UnsafeAwaitAwaiterFromRuntimeAsync(TAwaiter awaiter) where TAwaiter : ICriticalNotifyCompletion2 - + public static async2 void AwaitAwaiterFromRuntimeAsync(TAwaiter awaiter) where TAwaiter : INotifyCompletion + public static async2 void UnsafeAwaitAwaiterFromRuntimeAsync(TAwaiter awaiter) where TAwaiter : ICriticalNotifyCompletion ``` -As well as define that `Task`/`ValueTask` and other awaiters provide awaiters provided implement the `INotifyCompletion2`/`ICriticalNotification2` interfaces. The AwaitAwaiterFromRuntimeAsync provides a means for code generators to await on anything that is awaitable today with a simple set of IL. + +Any IL in an async2 method which needs to await will follow the existing C# model of calling `IsCompleted` and `GetResult()` and in the middle instead of using the existing patterns for suspension shall call one of the above helper methods. ### Utilizing async2 based code from non-async2 based code diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml b/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml index 376c3ecb1ee8d7..9c7bffdba74162 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml @@ -967,24 +967,12 @@ ref/net9.0/System.Private.CoreLib.dll lib/net9.0/System.Private.CoreLib.dll - - CP0002 - M:System.Runtime.CompilerServices.RuntimeHelpers.AwaitAwaiterFromRuntimeAsync``2(``1) - ref/net9.0/System.Private.CoreLib.dll - lib/net9.0/System.Private.CoreLib.dll - CP0002 M:System.Runtime.CompilerServices.RuntimeHelpers.UnsafeAwaitAwaiterFromRuntimeAsync``1(``0) ref/net9.0/System.Private.CoreLib.dll lib/net9.0/System.Private.CoreLib.dll - - CP0002 - M:System.Runtime.CompilerServices.RuntimeHelpers.UnsafeAwaitAwaiterFromRuntimeAsync``2(``1) - ref/net9.0/System.Private.CoreLib.dll - lib/net9.0/System.Private.CoreLib.dll - CP0014 M:System.Runtime.CompilerServices.RuntimeHelpers.GetObjectValue(System.Object)->object?:[T:System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute] diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.Windows.cs index 9abfdf66571bf3..ce1dedaa8889d8 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.Windows.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.Windows.cs @@ -310,7 +310,7 @@ internal static async Task AsyncModeCopyToAsync(SafeFileHandle handle, bool canS } /// Used by AsyncWindowsFileStreamStrategy.CopyToAsync to enable awaiting the result of an overlapped I/O operation with minimal overhead. - private sealed unsafe class AsyncCopyToAwaitable : ICriticalNotifyCompletion2 + private sealed unsafe class AsyncCopyToAwaitable : ICriticalNotifyCompletion { /// Sentinel object used to indicate that the I/O operation has completed before being awaited. private static readonly Action s_sentinel = () => { }; diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/ConfiguredValueTaskAwaitable.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/ConfiguredValueTaskAwaitable.cs index 9a1e250a7b5a87..1700fb0cca8aa6 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/ConfiguredValueTaskAwaitable.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/ConfiguredValueTaskAwaitable.cs @@ -27,7 +27,7 @@ public readonly struct ConfiguredValueTaskAwaitable /// Provides an awaiter for a . [StructLayout(LayoutKind.Auto)] - public readonly struct ConfiguredValueTaskAwaiter : ICriticalNotifyCompletion2, IStateMachineBoxAwareAwaiter + public readonly struct ConfiguredValueTaskAwaiter : ICriticalNotifyCompletion, IStateMachineBoxAwareAwaiter { /// The value being awaited. private readonly ValueTask _value; @@ -132,7 +132,7 @@ public readonly struct ConfiguredValueTaskAwaitable /// Provides an awaiter for a . [StructLayout(LayoutKind.Auto)] - public readonly struct ConfiguredValueTaskAwaiter : ICriticalNotifyCompletion2, IStateMachineBoxAwareAwaiter + public readonly struct ConfiguredValueTaskAwaiter : ICriticalNotifyCompletion, IStateMachineBoxAwareAwaiter { /// The value being awaited. private readonly ValueTask _value; diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/INotifyCompletion.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/INotifyCompletion.cs index d97d93ed869db3..ff00892da828f8 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/INotifyCompletion.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/INotifyCompletion.cs @@ -32,32 +32,4 @@ public interface ICriticalNotifyCompletion : INotifyCompletion /// Unlike OnCompleted, UnsafeOnCompleted need not propagate ExecutionContext information. void UnsafeOnCompleted(Action continuation); } - - /// - /// Represents an awaiter used to schedule continuations when an await operation completes. Provides access to the complete api surface for awaiting - /// - public interface ICriticalNotifyCompletion2 : ICriticalNotifyCompletion - { - bool IsCompleted { get; } - void GetResult(); - } - public interface ICriticalNotifyCompletion2 : ICriticalNotifyCompletion - { - bool IsCompleted { get; } - TResult GetResult(); - } - - /// - /// Represents an awaiter used to schedule continuations when an await operation completes. Provides access to the complete api surface for awaiting - /// - public interface INotifyCompletion2 : INotifyCompletion - { - bool IsCompleted { get; } - void GetResult(); - } - public interface INotifyCompletion2 : INotifyCompletion - { - bool IsCompleted { get; } - TResult GetResult(); - } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs index f4eda7aff82683..3bb06376095e03 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs @@ -119,117 +119,46 @@ internal static bool IsPrimitiveType(this CorElementType et) #pragma warning restore IDE0060 #if !NATIVEAOT - - // TODO, this method should be marked so that it is only callable from a runtime async method - public static TResult UnsafeAwaitAwaiterFromRuntimeAsync(TAwaiter awaiter) where TAwaiter : ICriticalNotifyCompletion2 + public static void AwaitAwaiterFromRuntimeAsync(TAwaiter awaiter) where TAwaiter : INotifyCompletion { - if (!awaiter.IsCompleted) + StackCrawlMark stackMark = StackCrawlMark.LookForMe; + + // Create resumption delegate, wrapping task, and create tasklets to represent each stack frame on the stack. + // RuntimeTaskSuspender.GetOrCreateResumptionDelegate() works like a POSIX fork call in that calls to it will return a + // delegate if they are the initial call to GetOrCreateResumptionDelegate, but once the thread is resumed, + // it will resume with a return value of null. + Action? resumption = RuntimeHelpers.GetOrCreateResumptionDelegate(ref stackMark); + if (resumption != null) { - StackCrawlMark stackMark = StackCrawlMark.LookForMe; - - // Create resumption delegate, wrapping task, and create tasklets to represent each stack frame on the stack. - // RuntimeTaskSuspender.GetOrCreateResumptionDelegate() works like a POSIX fork call in that calls to it will return a - // delegate if they are the initial call to GetOrCreateResumptionDelegate, but once the thread is resumed, - // it will resume with a return value of null. - Action? resumption = RuntimeHelpers.GetOrCreateResumptionDelegate(ref stackMark); - if (resumption != null) - { - // If we reach here, the only way that we actually run follow on code is for the continuation to actually run, - // and return from GetOrCreateResumptionDelegate with a null return value. - ref AsyncDataFrame asyncFrame = ref GetCurrentAsyncDataFrame(); - RuntimeAsyncMaintainedData maintainedData = asyncFrame._maintainedData!; - maintainedData._awaiter = awaiter; - // This function must be called from the same function that has the stackmark in it. - unsafe { UnwindToFunctionWithAsyncFrame(maintainedData._nextTasklet, maintainedData._suspendActive); } - } + // If we reach here, the only way that we actually run follow on code is for the continuation to actually run, + // and return from GetOrCreateResumptionDelegate with a null return value. + ref AsyncDataFrame asyncFrame = ref GetCurrentAsyncDataFrame(); + RuntimeAsyncMaintainedData maintainedData = asyncFrame._maintainedData!; + maintainedData._awaiter = awaiter; + // This function must be called from the same function that has the stackmark in it. + unsafe { UnwindToFunctionWithAsyncFrame(maintainedData._nextTasklet, maintainedData._suspendActive); } } - - // Get the result from the awaiter, or throw the exception stored in the Task - return awaiter.GetResult(); } - // TODO, this method should be marked so that it is only callable from a runtime async method - public static TResult AwaitAwaiterFromRuntimeAsync(TAwaiter awaiter) where TAwaiter : INotifyCompletion2 + public static void UnsafeAwaitAwaiterFromRuntimeAsync(TAwaiter awaiter) where TAwaiter : ICriticalNotifyCompletion { - if (!awaiter.IsCompleted) + StackCrawlMark stackMark = StackCrawlMark.LookForMe; + + // Create resumption delegate, wrapping task, and create tasklets to represent each stack frame on the stack. + // RuntimeTaskSuspender.GetOrCreateResumptionDelegate() works like a POSIX fork call in that calls to it will return a + // delegate if they are the initial call to GetOrCreateResumptionDelegate, but once the thread is resumed, + // it will resume with a return value of null. + Action? resumption = RuntimeHelpers.GetOrCreateResumptionDelegate(ref stackMark); + if (resumption != null) { - StackCrawlMark stackMark = StackCrawlMark.LookForMe; - - // Create resumption delegate, wrapping task, and create tasklets to represent each stack frame on the stack. - // RuntimeTaskSuspender.GetOrCreateResumptionDelegate() works like a POSIX fork call in that calls to it will return a - // delegate if they are the initial call to GetOrCreateResumptionDelegate, but once the thread is resumed, - // it will resume with a return value of null. - Action? resumption = RuntimeHelpers.GetOrCreateResumptionDelegate(ref stackMark); - if (resumption != null) - { - // If we reach here, the only way that we actually run follow on code is for the continuation to actually run, - // and return from GetOrCreateResumptionDelegate with a null return value. - ref AsyncDataFrame asyncFrame = ref GetCurrentAsyncDataFrame(); - RuntimeAsyncMaintainedData maintainedData = asyncFrame._maintainedData!; - maintainedData._awaiter = awaiter; - // This function must be called from the same function that has the stackmark in it. - unsafe { UnwindToFunctionWithAsyncFrame(maintainedData._nextTasklet, maintainedData._suspendActive); } - } + // If we reach here, the only way that we actually run follow on code is for the continuation to actually run, + // and return from GetOrCreateResumptionDelegate with a null return value. + ref AsyncDataFrame asyncFrame = ref GetCurrentAsyncDataFrame(); + RuntimeAsyncMaintainedData maintainedData = asyncFrame._maintainedData!; + maintainedData._awaiter = awaiter; + // This function must be called from the same function that has the stackmark in it. + unsafe { UnwindToFunctionWithAsyncFrame(maintainedData._nextTasklet, maintainedData._suspendActive); } } - - // Get the result from the awaiter, or throw the exception stored in the Task - return awaiter.GetResult(); - } - - // TODO, this method should be marked so that it is only callable from a runtime async method - public static void UnsafeAwaitAwaiterFromRuntimeAsync(TAwaiter awaiter) where TAwaiter : ICriticalNotifyCompletion2 - { - if (!awaiter.IsCompleted) - { - StackCrawlMark stackMark = StackCrawlMark.LookForMe; - - // Create resumption delegate, wrapping task, and create tasklets to represent each stack frame on the stack. - // RuntimeTaskSuspender.GetOrCreateResumptionDelegate() works like a POSIX fork call in that calls to it will return a - // delegate if they are the initial call to GetOrCreateResumptionDelegate, but once the thread is resumed, - // it will resume with a return value of null. - Action? resumption = RuntimeHelpers.GetOrCreateResumptionDelegate(ref stackMark); - if (resumption != null) - { - // If we reach here, the only way that we actually run follow on code is for the continuation to actually run, - // and return from GetOrCreateResumptionDelegate with a null return value. - ref AsyncDataFrame asyncFrame = ref GetCurrentAsyncDataFrame(); - RuntimeAsyncMaintainedData maintainedData = asyncFrame._maintainedData!; - maintainedData._awaiter = awaiter; - // This function must be called from the same function that has the stackmark in it. - unsafe { UnwindToFunctionWithAsyncFrame(maintainedData._nextTasklet, maintainedData._suspendActive); } - } - } - - // Get the result from the awaiter, or throw the exception stored in the Task - awaiter.GetResult(); - } - - // TODO, this method should be marked so that it is only callable from a runtime async method - public static void AwaitAwaiterFromRuntimeAsync(TAwaiter awaiter) where TAwaiter : INotifyCompletion2 - { - if (!awaiter.IsCompleted) - { - StackCrawlMark stackMark = StackCrawlMark.LookForMe; - - // Create resumption delegate, wrapping task, and create tasklets to represent each stack frame on the stack. - // RuntimeTaskSuspender.GetOrCreateResumptionDelegate() works like a POSIX fork call in that calls to it will return a - // delegate if they are the initial call to GetOrCreateResumptionDelegate, but once the thread is resumed, - // it will resume with a return value of null. - Action? resumption = RuntimeHelpers.GetOrCreateResumptionDelegate(ref stackMark); - if (resumption != null) - { - // If we reach here, the only way that we actually run follow on code is for the continuation to actually run, - // and return from GetOrCreateResumptionDelegate with a null return value. - ref AsyncDataFrame asyncFrame = ref GetCurrentAsyncDataFrame(); - RuntimeAsyncMaintainedData maintainedData = asyncFrame._maintainedData!; - maintainedData._awaiter = awaiter; - // This function must be called from the same function that has the stackmark in it. - unsafe { UnwindToFunctionWithAsyncFrame(maintainedData._nextTasklet, maintainedData._suspendActive); } - } - } - - // Get the result from the awaiter, or throw the exception stored in the Task - awaiter.GetResult(); } [ThreadStatic] diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/TaskAwaiter.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/TaskAwaiter.cs index 8a5b77ee05c9bc..0e3f0ec71b6b90 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/TaskAwaiter.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/TaskAwaiter.cs @@ -17,7 +17,7 @@ namespace System.Runtime.CompilerServices { /// Provides an awaiter for awaiting a . - public readonly struct TaskAwaiter : ICriticalNotifyCompletion2, ITaskAwaiter + public readonly struct TaskAwaiter : ICriticalNotifyCompletion, ITaskAwaiter { // WARNING: Unsafe.As is used to access the generic TaskAwaiter<> as TaskAwaiter. // Its layout must remain the same. @@ -283,7 +283,7 @@ private static Action OutputWaitEtwEvents(Task task, Action continuation) } /// Provides an awaiter for awaiting a . - public readonly struct TaskAwaiter : ICriticalNotifyCompletion2, ITaskAwaiter + public readonly struct TaskAwaiter : ICriticalNotifyCompletion, ITaskAwaiter { // WARNING: Unsafe.As is used to access TaskAwaiter<> as the non-generic TaskAwaiter. // Its layout must remain the same. @@ -376,7 +376,7 @@ public ConfiguredTaskAwaiter GetAwaiter() /// Provides an awaiter for a . /// This type is intended for compiler use only. - public readonly struct ConfiguredTaskAwaiter : ICriticalNotifyCompletion2, IConfiguredTaskAwaiter + public readonly struct ConfiguredTaskAwaiter : ICriticalNotifyCompletion, IConfiguredTaskAwaiter { // WARNING: Unsafe.As is used to access the generic ConfiguredTaskAwaiter as this. // Its layout must remain the same. @@ -458,7 +458,7 @@ public ConfiguredTaskAwaiter GetAwaiter() /// Provides an awaiter for a . /// This type is intended for compiler use only. - public readonly struct ConfiguredTaskAwaiter : ICriticalNotifyCompletion2, IConfiguredTaskAwaiter + public readonly struct ConfiguredTaskAwaiter : ICriticalNotifyCompletion, IConfiguredTaskAwaiter { // WARNING: Unsafe.As is used to access this as the non-generic ConfiguredTaskAwaiter. // Its layout must remain the same. diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/ValueTaskAwaiter.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/ValueTaskAwaiter.cs index ba5aa6f10872a7..c242ad3f2ad203 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/ValueTaskAwaiter.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/ValueTaskAwaiter.cs @@ -9,7 +9,7 @@ namespace System.Runtime.CompilerServices { /// Provides an awaiter for a . - public readonly struct ValueTaskAwaiter : ICriticalNotifyCompletion2, IStateMachineBoxAwareAwaiter + public readonly struct ValueTaskAwaiter : ICriticalNotifyCompletion, IStateMachineBoxAwareAwaiter { /// Shim used to invoke an passed as the state argument to a . internal static readonly Action s_invokeActionDelegate = static state => @@ -104,7 +104,7 @@ void IStateMachineBoxAwareAwaiter.AwaitUnsafeOnCompleted(IAsyncStateMachineBox b } /// Provides an awaiter for a . - public readonly struct ValueTaskAwaiter : ICriticalNotifyCompletion2, IStateMachineBoxAwareAwaiter + public readonly struct ValueTaskAwaiter : ICriticalNotifyCompletion, IStateMachineBoxAwareAwaiter { /// The value being awaited. private readonly ValueTask _value; diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/YieldAwaitable.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/YieldAwaitable.cs index 5ddd4fd00f3628..d9bea6410babbb 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/YieldAwaitable.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/YieldAwaitable.cs @@ -43,7 +43,7 @@ public readonly struct YieldAwaitable /// Provides an awaiter that switches into a target environment. /// This type is intended for compiler use only. - public readonly struct YieldAwaiter : ICriticalNotifyCompletion2, IStateMachineBoxAwareAwaiter + public readonly struct YieldAwaiter : ICriticalNotifyCompletion, IStateMachineBoxAwareAwaiter { /// Gets whether a yield is not required. /// This property is intended for compiler user rather than use directly in code. diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index b7e9ba512a1ec1..72ef20958c50aa 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -12699,7 +12699,7 @@ public readonly partial struct ConfiguredTaskAwaitable private readonly object _dummy; private readonly int _dummyPrimitive; public System.Runtime.CompilerServices.ConfiguredTaskAwaitable.ConfiguredTaskAwaiter GetAwaiter() { throw null; } - public readonly partial struct ConfiguredTaskAwaiter : System.Runtime.CompilerServices.ICriticalNotifyCompletion2, System.Runtime.CompilerServices.INotifyCompletion + public readonly partial struct ConfiguredTaskAwaiter : System.Runtime.CompilerServices.ICriticalNotifyCompletion, System.Runtime.CompilerServices.INotifyCompletion { private readonly object _dummy; private readonly int _dummyPrimitive; @@ -12714,7 +12714,7 @@ public readonly partial struct ConfiguredTaskAwaitable private readonly object _dummy; private readonly int _dummyPrimitive; public System.Runtime.CompilerServices.ConfiguredTaskAwaitable.ConfiguredTaskAwaiter GetAwaiter() { throw null; } - public readonly partial struct ConfiguredTaskAwaiter : System.Runtime.CompilerServices.ICriticalNotifyCompletion2, System.Runtime.CompilerServices.INotifyCompletion + public readonly partial struct ConfiguredTaskAwaiter : System.Runtime.CompilerServices.ICriticalNotifyCompletion, System.Runtime.CompilerServices.INotifyCompletion { private readonly object _dummy; private readonly int _dummyPrimitive; @@ -12729,7 +12729,7 @@ public readonly partial struct ConfiguredValueTaskAwaitable private readonly object _dummy; private readonly int _dummyPrimitive; public System.Runtime.CompilerServices.ConfiguredValueTaskAwaitable.ConfiguredValueTaskAwaiter GetAwaiter() { throw null; } - public readonly partial struct ConfiguredValueTaskAwaiter : System.Runtime.CompilerServices.ICriticalNotifyCompletion2, System.Runtime.CompilerServices.INotifyCompletion + public readonly partial struct ConfiguredValueTaskAwaiter : System.Runtime.CompilerServices.ICriticalNotifyCompletion, System.Runtime.CompilerServices.INotifyCompletion { private readonly object _dummy; private readonly int _dummyPrimitive; @@ -12744,7 +12744,7 @@ public readonly partial struct ConfiguredValueTaskAwaitable private readonly object _dummy; private readonly int _dummyPrimitive; public System.Runtime.CompilerServices.ConfiguredValueTaskAwaitable.ConfiguredValueTaskAwaiter GetAwaiter() { throw null; } - public readonly partial struct ConfiguredValueTaskAwaiter : System.Runtime.CompilerServices.ICriticalNotifyCompletion2, System.Runtime.CompilerServices.INotifyCompletion + public readonly partial struct ConfiguredValueTaskAwaiter : System.Runtime.CompilerServices.ICriticalNotifyCompletion, System.Runtime.CompilerServices.INotifyCompletion { private readonly object _dummy; private readonly int _dummyPrimitive; @@ -12859,16 +12859,6 @@ public partial interface ICriticalNotifyCompletion : System.Runtime.CompilerServ { void UnsafeOnCompleted(System.Action continuation); } - public partial interface ICriticalNotifyCompletion2 : ICriticalNotifyCompletion - { - bool IsCompleted { get; } - void GetResult(); - } - public partial interface ICriticalNotifyCompletion2 : ICriticalNotifyCompletion - { - bool IsCompleted { get; } - TResult GetResult(); - } [System.AttributeUsageAttribute(System.AttributeTargets.Property, Inherited=true)] public sealed partial class IndexerNameAttribute : System.Attribute { @@ -12878,16 +12868,6 @@ public partial interface INotifyCompletion { void OnCompleted(System.Action continuation); } - public partial interface INotifyCompletion2 : INotifyCompletion - { - bool IsCompleted { get; } - void GetResult(); - } - public partial interface INotifyCompletion2 : INotifyCompletion - { - bool IsCompleted { get; } - TResult GetResult(); - } [System.AttributeUsageAttribute(System.AttributeTargets.Assembly, AllowMultiple=true, Inherited=false)] public sealed partial class InternalsVisibleToAttribute : System.Attribute { @@ -13129,10 +13109,8 @@ public static void RunModuleConstructor(System.ModuleHandle module) { } public static bool TryEnsureSufficientExecutionStack() { throw null; } public delegate void CleanupCode(object? userData, bool exceptionThrown); public delegate void TryCode(object? userData); - public static TResult UnsafeAwaitAwaiterFromRuntimeAsync(TAwaiter awaiter) where TAwaiter : ICriticalNotifyCompletion2 { throw null; } - public static void UnsafeAwaitAwaiterFromRuntimeAsync(TAwaiter awaiter) where TAwaiter : ICriticalNotifyCompletion2 { } - public static TResult AwaitAwaiterFromRuntimeAsync(TAwaiter awaiter) where TAwaiter : INotifyCompletion2 { throw null; } - public static void AwaitAwaiterFromRuntimeAsync(TAwaiter awaiter) where TAwaiter : INotifyCompletion2 { } + public static void UnsafeAwaitAwaiterFromRuntimeAsync(TAwaiter awaiter) where TAwaiter : ICriticalNotifyCompletion { } + public static void AwaitAwaiterFromRuntimeAsync(TAwaiter awaiter) where TAwaiter : INotifyCompletion { } } public sealed partial class RuntimeWrappedException : System.Exception { @@ -13195,7 +13173,7 @@ public SwitchExpressionException(string? message, System.Exception? innerExcepti [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] public override void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } } - public readonly partial struct TaskAwaiter : System.Runtime.CompilerServices.ICriticalNotifyCompletion2, System.Runtime.CompilerServices.INotifyCompletion + public readonly partial struct TaskAwaiter : System.Runtime.CompilerServices.ICriticalNotifyCompletion, System.Runtime.CompilerServices.INotifyCompletion { private readonly object _dummy; private readonly int _dummyPrimitive; @@ -13204,7 +13182,7 @@ public void GetResult() { } public void OnCompleted(System.Action continuation) { } public void UnsafeOnCompleted(System.Action continuation) { } } - public readonly partial struct TaskAwaiter : System.Runtime.CompilerServices.ICriticalNotifyCompletion2, System.Runtime.CompilerServices.INotifyCompletion + public readonly partial struct TaskAwaiter : System.Runtime.CompilerServices.ICriticalNotifyCompletion, System.Runtime.CompilerServices.INotifyCompletion { private readonly object _dummy; private readonly int _dummyPrimitive; @@ -13321,7 +13299,7 @@ public sealed partial class UnsafeValueTypeAttribute : System.Attribute { public UnsafeValueTypeAttribute() { } } - public readonly partial struct ValueTaskAwaiter : System.Runtime.CompilerServices.ICriticalNotifyCompletion2, System.Runtime.CompilerServices.INotifyCompletion + public readonly partial struct ValueTaskAwaiter : System.Runtime.CompilerServices.ICriticalNotifyCompletion, System.Runtime.CompilerServices.INotifyCompletion { private readonly object _dummy; private readonly int _dummyPrimitive; @@ -13330,7 +13308,7 @@ public void GetResult() { } public void OnCompleted(System.Action continuation) { } public void UnsafeOnCompleted(System.Action continuation) { } } - public readonly partial struct ValueTaskAwaiter : System.Runtime.CompilerServices.ICriticalNotifyCompletion2, System.Runtime.CompilerServices.INotifyCompletion + public readonly partial struct ValueTaskAwaiter : System.Runtime.CompilerServices.ICriticalNotifyCompletion, System.Runtime.CompilerServices.INotifyCompletion { private readonly object _dummy; private readonly int _dummyPrimitive; @@ -13342,7 +13320,7 @@ public void UnsafeOnCompleted(System.Action continuation) { } public readonly partial struct YieldAwaitable { public System.Runtime.CompilerServices.YieldAwaitable.YieldAwaiter GetAwaiter() { throw null; } - public readonly partial struct YieldAwaiter : System.Runtime.CompilerServices.ICriticalNotifyCompletion2, System.Runtime.CompilerServices.INotifyCompletion + public readonly partial struct YieldAwaiter : System.Runtime.CompilerServices.ICriticalNotifyCompletion, System.Runtime.CompilerServices.INotifyCompletion { public bool IsCompleted { get { throw null; } } public void GetResult() { } diff --git a/src/tests/Loader/async/runtimetask-asyncfibonacci-with-yields.il b/src/tests/Loader/async/runtimetask-asyncfibonacci-with-yields.il index b081a484996394..97e602741eb53a 100644 --- a/src/tests/Loader/async/runtimetask-asyncfibonacci-with-yields.il +++ b/src/tests/Loader/async/runtimetask-asyncfibonacci-with-yields.il @@ -349,7 +349,8 @@ bool V_2, bool V_3, uint32 V_4, - valuetype [System.Runtime]System.Runtime.CompilerServices.YieldAwaitable V_5) + valuetype [System.Runtime]System.Runtime.CompilerServices.YieldAwaitable V_5, + valuetype [System.Runtime]System.Runtime.CompilerServices.YieldAwaitable/YieldAwaiter V_6) IL_0000: nop IL_0001: ldarg.0 IL_0002: stloc.0 @@ -367,7 +368,14 @@ stloc 5 ldloca 5 call instance valuetype [System.Runtime]System.Runtime.CompilerServices.YieldAwaitable/YieldAwaiter [System.Runtime]System.Runtime.CompilerServices.YieldAwaitable::GetAwaiter() - IL_001c: call void [System.Runtime]System.Runtime.CompilerServices.RuntimeHelpers::UnsafeAwaitAwaiterFromRuntimeAsync(!!0) + stloc 6 + ldloca 6 + call instance bool [System.Runtime]System.Runtime.CompilerServices.YieldAwaitable/YieldAwaiter::get_IsCompleted() + brtrue IL_FirstAwaitFinished + ldloc 6 + call void [System.Runtime]System.Runtime.CompilerServices.RuntimeHelpers::UnsafeAwaitAwaiterFromRuntimeAsync(!!0) + IL_FirstAwaitFinished: ldloca 6 + call instance void [System.Runtime]System.Runtime.CompilerServices.YieldAwaitable/YieldAwaiter::GetResult() IL_001d: ldloc.0 IL_001e: ldc.i4 0x773593ed IL_0023: mul @@ -382,7 +390,14 @@ stloc 5 ldloca 5 call instance valuetype [System.Runtime]System.Runtime.CompilerServices.YieldAwaitable/YieldAwaiter [System.Runtime]System.Runtime.CompilerServices.YieldAwaitable::GetAwaiter() - IL_0036: call void [System.Runtime]System.Runtime.CompilerServices.RuntimeHelpers::UnsafeAwaitAwaiterFromRuntimeAsync(!!0) + stloc 6 + ldloca 6 + call instance bool [System.Runtime]System.Runtime.CompilerServices.YieldAwaitable/YieldAwaiter::get_IsCompleted() + brtrue IL_SecondAwaitFinished + ldloc 6 + call void [System.Runtime]System.Runtime.CompilerServices.RuntimeHelpers::UnsafeAwaitAwaiterFromRuntimeAsync(!!0) + IL_SecondAwaitFinished: ldloca 6 + call instance void [System.Runtime]System.Runtime.CompilerServices.YieldAwaitable/YieldAwaiter::GetResult() IL_0037: ldloc.0 IL_0038: ldc.i4 0x773593ed IL_003d: mul @@ -397,7 +412,14 @@ stloc 5 ldloca 5 call instance valuetype [System.Runtime]System.Runtime.CompilerServices.YieldAwaitable/YieldAwaiter [System.Runtime]System.Runtime.CompilerServices.YieldAwaitable::GetAwaiter() - IL_0050: call void [System.Runtime]System.Runtime.CompilerServices.RuntimeHelpers::UnsafeAwaitAwaiterFromRuntimeAsync(!!0) + stloc 6 + ldloca 6 + call instance bool [System.Runtime]System.Runtime.CompilerServices.YieldAwaitable/YieldAwaiter::get_IsCompleted() + brtrue IL_ThirdAwaitFinished + ldloc 6 + call void [System.Runtime]System.Runtime.CompilerServices.RuntimeHelpers::UnsafeAwaitAwaiterFromRuntimeAsync(!!0) + IL_ThirdAwaitFinished: ldloca 6 + call instance void [System.Runtime]System.Runtime.CompilerServices.YieldAwaitable/YieldAwaiter::GetResult() IL_0051: ldloc.0 IL_0052: stloc.s V_4 IL_0054: br.s IL_0056 From 7645bfda4588c47eba3eb5c1b74ef80875a75ed0 Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Mon, 23 Oct 2023 22:38:54 +0000 Subject: [PATCH 038/203] Merged PR 34743: Update compiler and such --- .gitignore | 3 + NuGet.config | 2 + README.md | 3 + buildroslynnugets.cmd | 31 +++++++ eng/Versions.props | 6 +- src/coreclr/vm/runtimesuspension.cpp | 11 +-- .../async/async2fibonacci-with-yields.cs | 6 +- .../async/async2fibonacci-with-yields.csproj | 1 + ...timetask-asyncfibonacci-with-yields.ilproj | 1 + ...etask-asyncfibonacci-without-yields.ilproj | 1 + .../Loader/async/syncfibonacci-with-yields.cs | 83 +++++++++++-------- .../async/syncfibonacci-with-yields.csproj | 1 + .../async/syncfibonacci-without-yields.cs | 65 ++++++++------- .../async/syncfibonacci-without-yields.csproj | 1 + .../taskbased-asyncfibonacci-with-yields.cs | 80 ++++++++++-------- ...askbased-asyncfibonacci-with-yields.csproj | 1 + ...taskbased-asyncfibonacci-without-yields.cs | 74 ++++++++++------- ...based-asyncfibonacci-without-yields.csproj | 1 + ...luetaskbased-asyncfibonacci-with-yields.cs | 81 ++++++++++-------- ...askbased-asyncfibonacci-with-yields.csproj | 1 + ...taskbased-asyncfibonacci-without-yields.cs | 74 ++++++++++------- ...based-asyncfibonacci-without-yields.csproj | 1 + 22 files changed, 317 insertions(+), 211 deletions(-) create mode 100644 buildroslynnugets.cmd diff --git a/.gitignore b/.gitignore index 71943be3a350db..c67ea693e48854 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,9 @@ syntax: glob .packages .tools +# nuget packages for custom roslyn +roslynpackages + # User-specific files *.suo *.user diff --git a/NuGet.config b/NuGet.config index 4ec29de552c2b6..79e9bf75849489 100644 --- a/NuGet.config +++ b/NuGet.config @@ -22,6 +22,8 @@ + + diff --git a/README.md b/README.md index ea640f804eb1d4..698c2da19e9534 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,8 @@ # .NET Runtime +In order to build in this repo, you must have set up a Roslyn repo parallel to this repo with the name dotnet-roslyn, and it must have a remote called AzDo which has the updated compiler in it. Then you must run buildroslynnugets.cmd to create a local copy of the compiler for use in the repo. The equivalent work has not yet been done for running on Unix-like platforms. + + [![Build Status](https://dev.azure.com/dnceng-public/public/_apis/build/status/dotnet/runtime/runtime?branchName=main)](https://dev.azure.com/dnceng-public/public/_build/latest?definitionId=129&branchName=main) [![Help Wanted](https://img.shields.io/github/issues/dotnet/runtime/help%20wanted?style=flat-square&color=%232EA043&label=help%20wanted)](https://github.com/dotnet/runtime/labels/help%20wanted) [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/dotnet/runtime) diff --git a/buildroslynnugets.cmd b/buildroslynnugets.cmd new file mode 100644 index 00000000000000..14b5c47699b78d --- /dev/null +++ b/buildroslynnugets.cmd @@ -0,0 +1,31 @@ +setlocal ENABLEEXTENSIONS +pushd %~dp0 + +set ASYNC_SUFFIX=async-2 + +cd .. +pushd dotnet-roslyn + +git fetch AzDo dev/vsadov/a2 +rem when updating this, make sure to update the ASYNC_SUFFIX above and the versions.props file +git checkout 1c35e4f42732cfd58bb2a56bbbd85d8200497fa2 + +call restore.cmd +call build.cmd -c release + +call dotnet pack src\NuGet\Microsoft.Net.Compilers.Toolset\AnyCpu\Microsoft.Net.Compilers.Toolset.Package.csproj --version-suffix %ASYNC_SUFFIX% +call dotnet pack src\Workspaces\CSharp\Portable\Microsoft.CodeAnalysis.CSharp.Workspaces.csproj --version-suffix %ASYNC_SUFFIX% +call dotnet pack src\Compilers\CSharp\Portable\Microsoft.CodeAnalysis.CSharp.csproj --version-suffix %ASYNC_SUFFIX% +call dotnet pack src\Compilers\Core\Portable\Microsoft.CodeAnalysis.csproj --version-suffix %ASYNC_SUFFIX% +call dotnet pack src\Workspaces\Core\Portable\Microsoft.CodeAnalysis.Workspaces.csproj --version-suffix %ASYNC_SUFFIX% + +pushd %~dp0 + +md roslynpackages + +copy ..\dotnet-roslyn\artifacts\packages\Release\Shipping\Microsoft.Net.Compilers.Toolset.4.9.0-%ASYNC_SUFFIX%.nupkg roslynpackages +copy ..\dotnet-roslyn\artifacts\packages\Release\Shipping\Microsoft.CodeAnalysis.Workspaces.Common.4.9.0-%ASYNC_SUFFIX%.nupkg roslynpackages +copy ..\dotnet-roslyn\artifacts\packages\Release\Shipping\Microsoft.CodeAnalysis.CSharp.Workspaces.4.9.0-%ASYNC_SUFFIX%.nupkg roslynpackages +copy ..\dotnet-roslyn\artifacts\packages\Release\Shipping\Microsoft.CodeAnalysis.CSharp.4.9.0-%ASYNC_SUFFIX%.nupkg roslynpackages +copy ..\dotnet-roslyn\artifacts\packages\Release\Shipping\Microsoft.CodeAnalysis.Common.4.9.0-%ASYNC_SUFFIX%.nupkg roslynpackages + diff --git a/eng/Versions.props b/eng/Versions.props index 4d0a53ae2308ec..6e7df86ae6af93 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -39,9 +39,9 @@ Any tools that contribute to the design-time experience should use the MicrosoftCodeAnalysisVersion_LatestVS property above to ensure they do not break the local dev experience. --> - 4.9.0-1.23517.15 - 4.9.0-1.23517.15 - 4.9.0-1.23517.15 + 4.9.0-async-2 + 4.9.0-async-2 + 4.9.0-async-2 - 4.9.0-async-2 - 4.9.0-async-2 - 4.9.0-async-2 + 4.9.0-async-3 + 4.9.0-async-3 + 4.9.0-async-3 - 4.9.0-async-3 - 4.9.0-async-3 - 4.9.0-async-3 + 4.9.0-async-4 + 4.9.0-async-4 + 4.9.0-async-4 - 4.9.0-async-4 - 4.9.0-async-4 - 4.9.0-async-4 + 4.9.0-async-5 + 4.9.0-async-5 + 4.9.0-async-5 - 4.9.0-async-5 - 4.9.0-async-5 - 4.9.0-async-5 + 4.9.0-async-6 + 4.9.0-async-6 + 4.9.0-async-6 + + CP0009 + T:System.Runtime.CompilerServices.ValueTaskAsyncAttribute + ref/net9.0/System.Private.CoreLib.dll + lib/net9.0/System.Private.CoreLib.dll + CP0015 M:System.Diagnostics.Tracing.EventSource.Write``1(System.String,``0):[T:System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute] @@ -29,4 +35,4 @@ CP0015 M:System.Diagnostics.Tracing.EventSource.WriteEventWithRelatedActivityIdCore(System.Int32,System.Guid*,System.Int32,System.Diagnostics.Tracing.EventSource.EventData*):[T:System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute] - \ No newline at end of file + diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml b/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml index 9c7bffdba74162..88c94b8826a69b 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml @@ -973,6 +973,12 @@ ref/net9.0/System.Private.CoreLib.dll lib/net9.0/System.Private.CoreLib.dll + + CP0009 + T:System.Runtime.CompilerServices.ValueTaskAsyncAttribute + ref/net9.0/System.Private.CoreLib.dll + lib/net9.0/System.Private.CoreLib.dll + CP0014 M:System.Runtime.CompilerServices.RuntimeHelpers.GetObjectValue(System.Object)->object?:[T:System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute] @@ -1005,4 +1011,4 @@ CP0015 M:System.Diagnostics.Tracing.EventSource.WriteEventWithRelatedActivityIdCore(System.Int32,System.Guid*,System.Int32,System.Diagnostics.Tracing.EventSource.EventData*):[T:System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute] - + \ No newline at end of file diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index ac73beb0c03e3c..d8c11d553d55a2 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -890,6 +890,7 @@ + @@ -2721,4 +2722,4 @@ - \ No newline at end of file + diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/ValueTaskAsyncAttribute.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/ValueTaskAsyncAttribute.cs new file mode 100644 index 00000000000000..bfe750bf836f7a --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/ValueTaskAsyncAttribute.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Runtime.CompilerServices +{ + /// + /// Specifies that the async2 function in its promise-returning form should be returning ValueTask/ValueTask`1 + /// + /// The attribute is source-only and is not supposed to be emitted into metadata, where it has no meaning. + /// TODO: (vsadov) I have a lot of doubts that this is the best possible design to support ValueTask in async2. + /// While good enough to unblock working with ValueTask async2, we should think more about the syntax. + /// + [AttributeUsage(AttributeTargets.Method)] + public class ValueTaskAsyncAttribute : Attribute + { + public ValueTaskAsyncAttribute() { } + } +} diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index 72ef20958c50aa..54bd77d4fe502a 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -13299,6 +13299,11 @@ public sealed partial class UnsafeValueTypeAttribute : System.Attribute { public UnsafeValueTypeAttribute() { } } + [AttributeUsage(AttributeTargets.Method)] + public sealed partial class ValueTaskAsyncAttribute : Attribute + { + public ValueTaskAsyncAttribute() { } + } public readonly partial struct ValueTaskAwaiter : System.Runtime.CompilerServices.ICriticalNotifyCompletion, System.Runtime.CompilerServices.INotifyCompletion { private readonly object _dummy; diff --git a/src/tests/Loader/async/async2object.cs b/src/tests/Loader/async/async2object.cs index 81fc0119c6a775..aa7381cf074f3c 100644 --- a/src/tests/Loader/async/async2object.cs +++ b/src/tests/Loader/async/async2object.cs @@ -6,7 +6,7 @@ using System.Threading.Tasks; using Xunit; -public class Async2Void +public class Async2Object { [Fact] public static int TestEntryPoint() diff --git a/src/tests/Loader/async/async2valuetask.cs b/src/tests/Loader/async/async2valuetask.cs new file mode 100644 index 00000000000000..53d0792da245b6 --- /dev/null +++ b/src/tests/Loader/async/async2valuetask.cs @@ -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. + +using System; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; +using Xunit; + +public class Async2valuetask +{ + [Fact] + public static int TestEntryPoint() + { + return (int)AsyncTestEntryPoint(100).Result; + } + + private static ValueTask AsyncTestEntryPoint(int arg) + { + return M1(arg); + } + + [ValueTaskAsyncAttribute] + private static async2 int M1(int arg) + { + await Task.Yield(); + return arg; + } +} diff --git a/src/tests/Loader/async/async2valuetask.csproj b/src/tests/Loader/async/async2valuetask.csproj new file mode 100644 index 00000000000000..0f29ed860df43e --- /dev/null +++ b/src/tests/Loader/async/async2valuetask.csproj @@ -0,0 +1,8 @@ + + + true + + + + + From 9dce32d44b94b6236bd0696957639bfbcbb6d6bd Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Mon, 6 Nov 2023 21:46:57 +0000 Subject: [PATCH 066/203] Merged PR 35075: Fix ValueTask handling for async2 Fix ValueTask handling for async2 When enabling this a week or so ago, I missed the detail that ValueTask is a struct in 1 case. Now that @ has added support for testing this at the language level, the issue was easy to fix --- src/coreclr/vm/methodtablebuilder.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/coreclr/vm/methodtablebuilder.cpp b/src/coreclr/vm/methodtablebuilder.cpp index 5fe2e00302e69a..6dcd8042459239 100644 --- a/src/coreclr/vm/methodtablebuilder.cpp +++ b/src/coreclr/vm/methodtablebuilder.cpp @@ -2874,17 +2874,14 @@ AsyncTaskMethod ClassifyAsyncMethodCore(SigPointer sig, Module* pModule, PCCOR_S if (elemType == ELEMENT_TYPE_GENERICINST) { IfFailThrow(sig.GetElemType(&elemType)); - if (elemType == ELEMENT_TYPE_VALUETYPE) - { - return AsyncTaskMethod::NormalMethod; // Task is a class, so we can't be that if we get here - } + *pIsValueTask = (elemType == ELEMENT_TYPE_VALUETYPE); IfFailThrow(sig.GetToken(&tk)); IfFailThrow(sig.GetData(&data)); if (data == 1) { // This might be System.Threading.Tasks.Task`1 GetNameOfTypeDefOrRef(pModule, tk, &name, &_namespace); - if (((strcmp(name, "Task`1") == 0) || (strcmp(name, "ValueTask`1") == 0)) && strcmp(_namespace, "System.Threading.Tasks") == 0) + if ((strcmp(name, *pIsValueTask ? "ValueTask`1" : "Task`1") == 0) && strcmp(_namespace, "System.Threading.Tasks") == 0) { *pIsValueTask = name[0] == 'V'; if (IsTypeDefOrRefImplementedInSystemModule(pModule, tk)) @@ -2895,9 +2892,10 @@ AsyncTaskMethod ClassifyAsyncMethodCore(SigPointer sig, Module* pModule, PCCOR_S else if ((elemType == ELEMENT_TYPE_CLASS) || (elemType == ELEMENT_TYPE_VALUETYPE)) { IfFailThrow(sig.GetToken(&tk)); + *pIsValueTask = (elemType == ELEMENT_TYPE_VALUETYPE); // This might be System.Threading.Tasks.Task or ValueTask GetNameOfTypeDefOrRef(pModule, tk, &name, &_namespace); - if (((strcmp(name, "Task") == 0) || (strcmp(name, "ValueTask") == 0)) && strcmp(_namespace, "System.Threading.Tasks") == 0) + if ((strcmp(name, *pIsValueTask ? "ValueTask`1" : "Task`1") == 0) && strcmp(_namespace, "System.Threading.Tasks") == 0) { *pIsValueTask = name[0] == 'V'; if (IsTypeDefOrRefImplementedInSystemModule(pModule, tk)) From 00da10fc01920bd6ec836e297411a9b5b6017bc3 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Tue, 7 Nov 2023 13:16:45 +0100 Subject: [PATCH 067/203] Fix incorrect resume BB insertion In the EH case, we were splitting up the resumption BB and its successor by inserting new resumption BBs. Also add a bunch of logging. --- src/coreclr/jit/async.cpp | 57 ++++++++++++++++++++++++++++----------- src/coreclr/jit/async.h | 1 + 2 files changed, 42 insertions(+), 16 deletions(-) diff --git a/src/coreclr/jit/async.cpp b/src/coreclr/jit/async.cpp index 75618def1538e8..3ea354603a9f84 100644 --- a/src/coreclr/jit/async.cpp +++ b/src/coreclr/jit/async.cpp @@ -272,7 +272,7 @@ void Async2Transformation::Transform( #ifdef DEBUG if (m_comp->verbose) { - printf("Processing call [%06u]\n", Compiler::dspTreeID(call)); + printf("Processing call [%06u] in " FMT_BB "\n", Compiler::dspTreeID(call), block->bbNum); printf(" %zu live LIR edges\n", defs.size()); if (defs.size() > 0) @@ -397,6 +397,7 @@ void Async2Transformation::Transform( // (-1 in the tier0 version): if (m_comp->doesMethodHavePatchpoints() || m_comp->opts.IsOSR()) { + JITDUMP(" Method %s; keeping an IL offset at the beginning of non-GC data\n", m_comp->doesMethodHavePatchpoints() ? "has patchpoints" : "is an OSR method"); dataSize += sizeof(int); } @@ -415,6 +416,8 @@ void Async2Transformation::Transform( returnInGCData = varTypeIsGC(call->gtReturnType); } + JITDUMP(" Will store return of type %s, size %u in %sGC data\n", call->gtReturnType == TYP_STRUCT ? returnStructLayout->GetClassName() : varTypeName(call->gtReturnType), returnSize, returnInGCData ? "" : "non-"); + assert((returnSize > 0) == (call->gtReturnType != TYP_VOID)); // The return value is always stored: @@ -431,11 +434,16 @@ void Async2Transformation::Transform( { returnValDataOffset = dataSize; dataSize += returnSize; + + JITDUMP(" at offset %u\n", returnValDataOffset); } unsigned exceptionGCDataIndex = UINT_MAX; if (block->hasTryIndex()) + { exceptionGCDataIndex = gcRefsCount++; + JITDUMP(" " FMT_BB " is in try region %u; exception will be at GC@+%02u in GC data\n", block->bbNum, exceptionGCDataIndex); + } for (LiveLocalInfo& inf : m_liveLocals) { @@ -518,6 +526,8 @@ void Async2Transformation::Transform( BasicBlock* remainder = m_comp->fgSplitBlockAfterNode(block, jtrue); *pRemainder = remainder; + JITDUMP(" Remainder is " FMT_BB "\n", remainder->bbNum); + assert(block->KindIs(BBJ_NONE) && block->NextIs(remainder)); if (m_lastSuspensionBB == nullptr) @@ -531,6 +541,8 @@ void Async2Transformation::Transform( retBB->clearHndIndex(); m_lastSuspensionBB = retBB; + JITDUMP(" Created suspension " FMT_BB " for state %u\n", retBB->bbNum, stateNum); + block->SetJumpKindAndTarget(BBJ_COND, retBB DEBUGARG(m_comp)); m_comp->fgAddRefPred(retBB, block); @@ -733,23 +745,19 @@ void Async2Transformation::Transform( GenTree* ret = m_comp->gtNewOperNode(GT_RETURN_SUSPEND, TYP_VOID, newContinuation); LIR::AsRange(retBB).InsertAtEnd(newContinuation, ret); - JITDUMP(" Created suspension BB " FMT_BB "\n", retBB->bbNum); - - BasicBlock* prevResumptionBB; - if (m_resumptionBBs.size() == 0) - { - prevResumptionBB = m_comp->fgLastBBInMainFunction(); - } - else + if (m_lastResumptionBB == nullptr) { - prevResumptionBB = m_resumptionBBs[m_resumptionBBs.size() - 1]; + m_lastResumptionBB = m_comp->fgLastBBInMainFunction(); } - BasicBlock* resumeBB = m_comp->fgNewBBafter(BBJ_ALWAYS, prevResumptionBB, true, remainder); + BasicBlock* resumeBB = m_comp->fgNewBBafter(BBJ_ALWAYS, m_lastResumptionBB, true, remainder); resumeBB->bbSetRunRarely(); resumeBB->clearTryIndex(); resumeBB->clearHndIndex(); resumeBB->bbFlags |= BBF_ASYNC_RESUMPTION; + m_lastResumptionBB = resumeBB; + + JITDUMP(" Created resumption " FMT_BB " for state %u\n", resumeBB->bbNum, stateNum); m_comp->fgAddRefPred(remainder, resumeBB); @@ -876,16 +884,23 @@ void Async2Transformation::Transform( BasicBlock* storeResultBB = resumeBB; if (exceptionGCDataIndex != UINT_MAX) { + JITDUMP(" We need to rethrow an exception\n"); BasicBlock* rethrowExceptionBB = m_comp->fgNewBBinRegion(BBJ_THROW, block, /* jumpDest */ nullptr, /* runRarely */ true, /* insertAtEnd */ true); + JITDUMP(" Created " FMT_BB " to rethrow exception on resumption\n", rethrowExceptionBB->bbNum); resumeBB->SetJumpKindAndTarget(BBJ_COND, rethrowExceptionBB DEBUGARG(m_comp)); m_comp->fgAddRefPred(rethrowExceptionBB, resumeBB); m_comp->fgRemoveRefPred(remainder, resumeBB); + JITDUMP(" Resumption " FMT_BB " becomes BBJ_COND to check for non-null exception\n", resumeBB->bbNum); + storeResultBB = m_comp->fgNewBBafter(BBJ_ALWAYS, resumeBB, true, remainder); + JITDUMP(" Created " FMT_BB " to store result when resuming with no exception\n", storeResultBB->bbNum); m_comp->fgAddRefPred(remainder, storeResultBB); m_comp->fgAddRefPred(storeResultBB, resumeBB); + m_lastResumptionBB = storeResultBB; + // Check if we have an exception. unsigned exceptionLclNum = GetExceptionVar(); GenTree* objectArr = m_comp->gtNewLclvNode(resumeObjectArrLclNum, TYP_REF); @@ -909,7 +924,7 @@ void Async2Transformation::Transform( LIR::AsRange(rethrowExceptionBB).InsertAtEnd(LIR::SeqTree(m_comp, rethrowException)); storeResultBB->bbFlags |= BBF_ASYNC_RESUMPTION; - JITDUMP("Added " FMT_BB " to rethrow exception at suspension point\n", rethrowExceptionBB->bbNum); + JITDUMP(" Added " FMT_BB " to rethrow exception at suspension point\n", rethrowExceptionBB->bbNum); } } @@ -1022,7 +1037,6 @@ void Async2Transformation::Transform( } m_resumptionBBs.push_back(resumeBB); - JITDUMP(" Created resumption BB " FMT_BB "\n", resumeBB->bbNum); } GenTreeIndir* Async2Transformation::LoadFromOffset(GenTree* base, @@ -1178,7 +1192,6 @@ void Async2Transformation::LiftLIREdges(BasicBlock* block, void Async2Transformation::CreateResumptionSwitch() { BasicBlock* newEntryBB = m_comp->bbNewBasicBlock(BBJ_NONE); - JITDUMP("New entry BB: " FMT_BB "\n", newEntryBB->bbNum); if (m_comp->fgFirstBB->hasProfileWeight()) { @@ -1189,6 +1202,8 @@ void Async2Transformation::CreateResumptionSwitch() FlowEdge* edge = m_comp->fgAddRefPred(m_comp->fgFirstBB, newEntryBB); edge->setLikelihood(1); + JITDUMP(" Inserting new entry " FMT_BB " before old entry " FMT_BB "\n", newEntryBB->bbNum, m_comp->fgFirstBB->bbNum); + m_comp->fgInsertBBbefore(m_comp->fgFirstBB, newEntryBB); // If previous first BB was a scratch BB, then we must add a new scratch BB @@ -1203,6 +1218,7 @@ void Async2Transformation::CreateResumptionSwitch() if (m_resumptionBBs.size() == 1) { + JITDUMP(" Redirecting entry " FMT_BB " directly to " FMT_BB " as it is the only resumption block\n", newEntryBB->bbNum, m_resumptionBBs[0]->bbNum); newEntryBB->SetJumpKindAndTarget(BBJ_COND, m_resumptionBBs[0] DEBUGARG(m_comp)); m_comp->fgAddRefPred(m_resumptionBBs[0], newEntryBB); } @@ -1212,7 +1228,7 @@ void Async2Transformation::CreateResumptionSwitch() condBB->bbSetRunRarely(); newEntryBB->SetJumpKindAndTarget(BBJ_COND, condBB DEBUGARG(m_comp)); - JITDUMP("New cond BB: " FMT_BB "\n", condBB->bbNum); + JITDUMP(" Redirecting entry " FMT_BB " to BBJ_COND " FMT_BB " for resumption with 2 states\n", newEntryBB->bbNum, condBB->bbNum); m_comp->fgAddRefPred(condBB, newEntryBB); m_comp->fgAddRefPred(m_resumptionBBs[0], condBB); @@ -1236,7 +1252,7 @@ void Async2Transformation::CreateResumptionSwitch() switchBB->bbSetRunRarely(); newEntryBB->SetJumpKindAndTarget(BBJ_COND, switchBB DEBUGARG(m_comp)); - JITDUMP("New switch BB: " FMT_BB "\n", switchBB->bbNum); + JITDUMP(" Redirecting entry " FMT_BB " to BBJ_SWITCH " FMT_BB " for resumption with %zu states\n", newEntryBB->bbNum, switchBB->bbNum, m_resumptionBBs.size()); m_comp->fgAddRefPred(switchBB, newEntryBB); @@ -1268,13 +1284,19 @@ void Async2Transformation::CreateResumptionSwitch() if (m_comp->doesMethodHavePatchpoints()) { + JITDUMP(" Method has patch points...\n"); // If we have patchpoints then first check if we need to resume in the OSR version. BasicBlock* callHelperBB = m_comp->fgNewBBafter(BBJ_THROW, m_comp->fgLastBBInMainFunction(), false); callHelperBB->bbSetRunRarely(); callHelperBB->clearTryIndex(); callHelperBB->clearHndIndex(); + JITDUMP(" Created " FMT_BB " for transitions back into OSR method\n", callHelperBB->bbNum); + BasicBlock* checkILOffsetBB = m_comp->fgNewBBbefore(BBJ_COND, newEntryBB->GetJumpDest(), true, callHelperBB); + + JITDUMP(" Created " FMT_BB " to check whether we should transition immediately to OSR\n", checkILOffsetBB->bbNum); + m_comp->fgRemoveRefPred(newEntryBB->GetJumpDest(), newEntryBB); newEntryBB->SetJumpDest(checkILOffsetBB); @@ -1310,6 +1332,7 @@ void Async2Transformation::CreateResumptionSwitch() } else if (m_comp->opts.IsOSR()) { + JITDUMP(" Method is an OSR function\n"); // If the tier-0 version resumed and then transitioned to the OSR // version by normal means then we will see a non-zero continuation // here that belongs to the tier0 method. In that case we should just @@ -1319,6 +1342,8 @@ void Async2Transformation::CreateResumptionSwitch() m_comp->fgRemoveRefPred(newEntryBB->GetJumpDest(), newEntryBB); newEntryBB->SetJumpDest(checkILOffsetBB); + JITDUMP(" Created " FMT_BB " to check for Tier-0 continuations\n", checkILOffsetBB->bbNum); + m_comp->fgAddRefPred(checkILOffsetBB, newEntryBB); m_comp->fgAddRefPred(checkILOffsetBB->Next(), checkILOffsetBB); m_comp->fgAddRefPred(checkILOffsetBB->GetJumpDest(), checkILOffsetBB); diff --git a/src/coreclr/jit/async.h b/src/coreclr/jit/async.h index c9e19c89ebfcc1..b76330bb272456 100644 --- a/src/coreclr/jit/async.h +++ b/src/coreclr/jit/async.h @@ -31,6 +31,7 @@ class Async2Transformation unsigned m_resultBaseVar = BAD_VAR_NUM; unsigned m_exceptionVar = BAD_VAR_NUM; BasicBlock* m_lastSuspensionBB = nullptr; + BasicBlock* m_lastResumptionBB = nullptr; void LiftLIREdges(BasicBlock* block, GenTree* beyond, From 55e94103366615cb74a99e5b0aa43dc9b910e36c Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Tue, 7 Nov 2023 13:17:37 +0100 Subject: [PATCH 068/203] Skip post-async2 functions for GC stress --- src/coreclr/vm/gccover.cpp | 11 +++++++++++ src/coreclr/vm/threadsuspend.cpp | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/coreclr/vm/gccover.cpp b/src/coreclr/vm/gccover.cpp index 0ce88284163c2b..490debd0221e2f 100644 --- a/src/coreclr/vm/gccover.cpp +++ b/src/coreclr/vm/gccover.cpp @@ -261,6 +261,8 @@ void SetupGcCoverage(NativeCodeVersion nativeCodeVersion, BYTE* methodStartPtr) SetupAndSprinkleBreakpointsForJittedMethod(nativeCodeVersion, codeStart); } +bool IsSpecialCaseAsyncRet(MethodDesc* pMD); + void ReplaceInstrAfterCall(PBYTE instrToReplace, MethodDesc* callMD) { ReturnKind returnKind = callMD->GetReturnKind(true); @@ -275,6 +277,15 @@ void ReplaceInstrAfterCall(PBYTE instrToReplace, MethodDesc* callMD) } _ASSERTE(IsValidReturnKind(returnKind)); + if (g_pConfig->RuntimeAsyncViaJitGeneratedStateMachines()) + { + if (callMD->IsAsync2Method() || (callMD->IsIntrinsic() && IsSpecialCaseAsyncRet(callMD))) + { + // Cannot encode requirement to protect async ret after call. + return; + } + } + bool ispointerKind = IsPointerReturnKind(returnKind); #ifdef TARGET_ARM size_t instrLen = GetARMInstructionLength(instrToReplace); diff --git a/src/coreclr/vm/threadsuspend.cpp b/src/coreclr/vm/threadsuspend.cpp index 3a7465fad92c35..d231ca26e37bb8 100644 --- a/src/coreclr/vm/threadsuspend.cpp +++ b/src/coreclr/vm/threadsuspend.cpp @@ -5110,7 +5110,7 @@ void STDCALL OnHijackWorker(HijackArgs * pArgs) #endif // HIJACK_NONINTERRUPTIBLE_THREADS } -static bool IsSpecialCaseAsyncRet(MethodDesc* pMD) +bool IsSpecialCaseAsyncRet(MethodDesc* pMD) { // TODO: What's the right way to do this through CoreLibBinder without // causing loading to happen? Also, can we just mark them as async2 in SPC, From b9190c605948ea410f679d6667a99c826e04070b Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Tue, 7 Nov 2023 16:21:27 +0100 Subject: [PATCH 069/203] Run jit-format --- src/coreclr/jit/async.cpp | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/src/coreclr/jit/async.cpp b/src/coreclr/jit/async.cpp index 3ea354603a9f84..c20d168dea1539 100644 --- a/src/coreclr/jit/async.cpp +++ b/src/coreclr/jit/async.cpp @@ -397,7 +397,8 @@ void Async2Transformation::Transform( // (-1 in the tier0 version): if (m_comp->doesMethodHavePatchpoints() || m_comp->opts.IsOSR()) { - JITDUMP(" Method %s; keeping an IL offset at the beginning of non-GC data\n", m_comp->doesMethodHavePatchpoints() ? "has patchpoints" : "is an OSR method"); + JITDUMP(" Method %s; keeping an IL offset at the beginning of non-GC data\n", + m_comp->doesMethodHavePatchpoints() ? "has patchpoints" : "is an OSR method"); dataSize += sizeof(int); } @@ -416,7 +417,12 @@ void Async2Transformation::Transform( returnInGCData = varTypeIsGC(call->gtReturnType); } - JITDUMP(" Will store return of type %s, size %u in %sGC data\n", call->gtReturnType == TYP_STRUCT ? returnStructLayout->GetClassName() : varTypeName(call->gtReturnType), returnSize, returnInGCData ? "" : "non-"); + if (returnSize > 0) + { + JITDUMP(" Will store return of type %s, size %u in %sGC data\n", + call->gtReturnType == TYP_STRUCT ? returnStructLayout->GetClassName() : varTypeName(call->gtReturnType), + returnSize, returnInGCData ? "" : "non-"); + } assert((returnSize > 0) == (call->gtReturnType != TYP_VOID)); @@ -442,7 +448,8 @@ void Async2Transformation::Transform( if (block->hasTryIndex()) { exceptionGCDataIndex = gcRefsCount++; - JITDUMP(" " FMT_BB " is in try region %u; exception will be at GC@+%02u in GC data\n", block->bbNum, exceptionGCDataIndex); + JITDUMP(" " FMT_BB " is in try region %u; exception will be at GC@+%02u in GC data\n", block->bbNum, + exceptionGCDataIndex); } for (LiveLocalInfo& inf : m_liveLocals) @@ -1202,7 +1209,8 @@ void Async2Transformation::CreateResumptionSwitch() FlowEdge* edge = m_comp->fgAddRefPred(m_comp->fgFirstBB, newEntryBB); edge->setLikelihood(1); - JITDUMP(" Inserting new entry " FMT_BB " before old entry " FMT_BB "\n", newEntryBB->bbNum, m_comp->fgFirstBB->bbNum); + JITDUMP(" Inserting new entry " FMT_BB " before old entry " FMT_BB "\n", newEntryBB->bbNum, + m_comp->fgFirstBB->bbNum); m_comp->fgInsertBBbefore(m_comp->fgFirstBB, newEntryBB); @@ -1218,7 +1226,8 @@ void Async2Transformation::CreateResumptionSwitch() if (m_resumptionBBs.size() == 1) { - JITDUMP(" Redirecting entry " FMT_BB " directly to " FMT_BB " as it is the only resumption block\n", newEntryBB->bbNum, m_resumptionBBs[0]->bbNum); + JITDUMP(" Redirecting entry " FMT_BB " directly to " FMT_BB " as it is the only resumption block\n", + newEntryBB->bbNum, m_resumptionBBs[0]->bbNum); newEntryBB->SetJumpKindAndTarget(BBJ_COND, m_resumptionBBs[0] DEBUGARG(m_comp)); m_comp->fgAddRefPred(m_resumptionBBs[0], newEntryBB); } @@ -1228,7 +1237,8 @@ void Async2Transformation::CreateResumptionSwitch() condBB->bbSetRunRarely(); newEntryBB->SetJumpKindAndTarget(BBJ_COND, condBB DEBUGARG(m_comp)); - JITDUMP(" Redirecting entry " FMT_BB " to BBJ_COND " FMT_BB " for resumption with 2 states\n", newEntryBB->bbNum, condBB->bbNum); + JITDUMP(" Redirecting entry " FMT_BB " to BBJ_COND " FMT_BB " for resumption with 2 states\n", + newEntryBB->bbNum, condBB->bbNum); m_comp->fgAddRefPred(condBB, newEntryBB); m_comp->fgAddRefPred(m_resumptionBBs[0], condBB); @@ -1252,7 +1262,8 @@ void Async2Transformation::CreateResumptionSwitch() switchBB->bbSetRunRarely(); newEntryBB->SetJumpKindAndTarget(BBJ_COND, switchBB DEBUGARG(m_comp)); - JITDUMP(" Redirecting entry " FMT_BB " to BBJ_SWITCH " FMT_BB " for resumption with %zu states\n", newEntryBB->bbNum, switchBB->bbNum, m_resumptionBBs.size()); + JITDUMP(" Redirecting entry " FMT_BB " to BBJ_SWITCH " FMT_BB " for resumption with %zu states\n", + newEntryBB->bbNum, switchBB->bbNum, m_resumptionBBs.size()); m_comp->fgAddRefPred(switchBB, newEntryBB); @@ -1295,7 +1306,8 @@ void Async2Transformation::CreateResumptionSwitch() BasicBlock* checkILOffsetBB = m_comp->fgNewBBbefore(BBJ_COND, newEntryBB->GetJumpDest(), true, callHelperBB); - JITDUMP(" Created " FMT_BB " to check whether we should transition immediately to OSR\n", checkILOffsetBB->bbNum); + JITDUMP(" Created " FMT_BB " to check whether we should transition immediately to OSR\n", + checkILOffsetBB->bbNum); m_comp->fgRemoveRefPred(newEntryBB->GetJumpDest(), newEntryBB); newEntryBB->SetJumpDest(checkILOffsetBB); From 6ac1f06a4626fede8d626a1e39fdd9dbd565899f Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Tue, 7 Nov 2023 16:21:34 +0100 Subject: [PATCH 070/203] Fix mixing up struct slot indices with GC data array indices --- src/coreclr/jit/async.cpp | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/coreclr/jit/async.cpp b/src/coreclr/jit/async.cpp index c20d168dea1539..80fe8da21096a7 100644 --- a/src/coreclr/jit/async.cpp +++ b/src/coreclr/jit/async.cpp @@ -629,8 +629,9 @@ void Async2Transformation::Transform( else { assert((dsc->TypeGet() == TYP_STRUCT) || dsc->IsImplicitByRef()); - ClassLayout* layout = dsc->GetLayout(); - unsigned numSlots = layout->GetSlotCount(); + ClassLayout* layout = dsc->GetLayout(); + unsigned numSlots = layout->GetSlotCount(); + unsigned gcRefIndex = 0; for (unsigned i = 0; i < numSlots; i++) { var_types gcPtrType = layout->GetGCPtrType(i); @@ -652,10 +653,13 @@ void Async2Transformation::Transform( } GenTree* objectArr = m_comp->gtNewLclvNode(objectArrLclNum, TYP_REF); - unsigned offset = OFFSETOF__CORINFO_Array__data + ((inf.GCDataIndex + i) * TARGET_POINTER_SIZE); - GenTree* store = StoreAtOffset(objectArr, offset, value); + unsigned offset = + OFFSETOF__CORINFO_Array__data + ((inf.GCDataIndex + gcRefIndex) * TARGET_POINTER_SIZE); + GenTree* store = StoreAtOffset(objectArr, offset, value); LIR::AsRange(retBB).InsertAtEnd(LIR::SeqTree(m_comp, store)); + gcRefIndex++; + if (inf.DataSize > 0) { // Null out the GC field in preparation of storing the rest. @@ -855,8 +859,9 @@ void Async2Transformation::Transform( else { assert((dsc->TypeGet() == TYP_STRUCT) || dsc->IsImplicitByRef()); - ClassLayout* layout = dsc->GetLayout(); - unsigned numSlots = layout->GetSlotCount(); + ClassLayout* layout = dsc->GetLayout(); + unsigned numSlots = layout->GetSlotCount(); + unsigned gcRefIndex = 0; for (unsigned i = 0; i < numSlots; i++) { var_types gcPtrType = layout->GetGCPtrType(i); @@ -867,8 +872,9 @@ void Async2Transformation::Transform( } GenTree* objectArr = m_comp->gtNewLclvNode(resumeObjectArrLclNum, TYP_REF); - unsigned offset = OFFSETOF__CORINFO_Array__data + ((inf.GCDataIndex + i) * TARGET_POINTER_SIZE); - GenTree* value = LoadFromOffset(objectArr, offset, TYP_REF); + unsigned offset = + OFFSETOF__CORINFO_Array__data + ((inf.GCDataIndex + gcRefIndex) * TARGET_POINTER_SIZE); + GenTree* value = LoadFromOffset(objectArr, offset, TYP_REF); GenTree* store; if (dsc->IsImplicitByRef()) { @@ -884,6 +890,8 @@ void Async2Transformation::Transform( } LIR::AsRange(resumeBB).InsertAtEnd(LIR::SeqTree(m_comp, store)); + + gcRefIndex++; } } } From fba68afbb2e9dab5db1e73d6d5017a86138f4418 Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Tue, 7 Nov 2023 18:44:35 +0000 Subject: [PATCH 071/203] Merged PR 35077: Add an EH performance benchmark Its a bit of a mess, but its a configurable test which adds finally and catch performance testing Note: This doesn't work in the unwinder based prototype (and we're not going to make it work, as it is quite a lot of work, and it ALSO doesn't work in the JIT prototype, but I'd like to see if @ could fix it there. --- .../Loader/async/async2-eh-microbench.cs | 299 ++++++++++++++++++ .../Loader/async/async2-eh-microbench.csproj | 9 + 2 files changed, 308 insertions(+) create mode 100644 src/tests/Loader/async/async2-eh-microbench.cs create mode 100644 src/tests/Loader/async/async2-eh-microbench.csproj diff --git a/src/tests/Loader/async/async2-eh-microbench.cs b/src/tests/Loader/async/async2-eh-microbench.cs new file mode 100644 index 00000000000000..a750c194fd21dd --- /dev/null +++ b/src/tests/Loader/async/async2-eh-microbench.cs @@ -0,0 +1,299 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +//#define ASYNC1_TASK +//#define ASYNC1_VALUETASK + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Runtime; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +public class Async2EHMicrobench +{ + [Fact] + public static void TestEntryPoint() + { + Task.Run(AsyncEntry).Wait(); + } + + public static async Task AsyncEntry() + { + if (!GCSettings.IsServerGC) + Console.WriteLine("*** Warning: Server GC is disabled, set DOTNET_gcServer=1 ***"); + + string[] args = Environment.GetCommandLineArgs(); + int depth = args.Length > 1 ? int.Parse(args[1]) : 2; + Console.WriteLine("Using depth = {0}", depth); + + // Yield N times before throwing + int yieldFrequency = args.Length > 2 ? int.Parse(args[2]) : 1; + Console.WriteLine("Yielding {0} times before throwing", yieldFrequency); + + // Inject a finally every N frames + int finallyRate = args.Length > 3 ? int.Parse(args[3]) : 1000; + Console.WriteLine("With a try/finally block every {0} frames", finallyRate); + + Benchmark warmupBm = new Benchmark(5, 5, 2); + warmupBm.Warmup = true; + for (int i = 0; i < 4; i++) + { + for (int j = 0; j < 40; j++) + { + await warmupBm.Run("Async2"); + await warmupBm.Run("Task"); + await warmupBm.Run("ValueTask"); + } + + // Make sure we tier up... + await Task.Delay(500); + } + + Console.WriteLine("Warmup done, running benchmark"); + await RunBench(yieldFrequency, depth, finallyRate, "Async2"); + await RunBench(yieldFrequency, depth, finallyRate, "Task"); + await RunBench(yieldFrequency, depth, finallyRate, "ValueTask"); + } + + private static async Task RunBench(int yieldCount, int depth, int finallyRate, string type) + { + + Benchmark bm = new(yieldCount, depth, finallyRate); + Console.WriteLine($"Running benchmark on '{type}' methods"); + + List results = new(); + for (int i = 0; i < 16; i++) + { + long numIters = await bm.Run(type); + Console.WriteLine($"iters={numIters}"); + results.Add(numIters); + } + + results.Sort(); + double avg = results.Skip(3).Take(10).Average(); + Console.WriteLine("Result = {0}", (long)avg); + } + + private class Benchmark + { + private readonly int _yieldCount; + private readonly int _depth; + private readonly int _finallyRate; + public int Sink; + public bool Warmup; + + public Benchmark(int yieldCount, int depth, int finallyRate) + { + _yieldCount = yieldCount; + _depth = depth; + _finallyRate = finallyRate; + } + + public async2 long Run(string type) + { + if (type == "Async2") + return await RunAsync2(_depth); + if (type == "Task") + return await RunTask(_depth); + if (type == "ValueTask") + return await RunValueTask(_depth); + return 0; + } + + public async Task RunTask(int depth) + { + int liveState1 = depth * 3 + _yieldCount; + int liveState2 = depth; + double liveState3 = _yieldCount; + + if (depth == 0) + { + int currentAwaitCount = 0; + + while (currentAwaitCount < _yieldCount) + { + currentAwaitCount++; + await Task.Yield(); + } + + throw new Exception(); + } + + long result = 0; + + if (depth == _depth) + { + int time = Warmup ? 5 : 500; + + Stopwatch timer = Stopwatch.StartNew(); + + long numIters = 0; + + while (timer.ElapsedMilliseconds < time) + { + try + { + result = await RunTask(depth - 1); + } + catch (Exception e) + { + } + numIters++; + + } + return numIters; + } + else if ((depth % _finallyRate) == 0) + { + try + { + result = await RunTask(depth - 1); + } + finally + { + Sink = (int)liveState1 + (int)liveState2 + (int)(1 / liveState3) + depth; + } + } + else + { + result = await RunTask(depth - 1); + } + + Sink = (int)liveState1 + (int)liveState2 + (int)(1 / liveState3) + depth; + return result; + } + + public async ValueTask RunValueTask(int depth) + { + int liveState1 = depth * 3 + _yieldCount; + int liveState2 = depth; + double liveState3 = _yieldCount; + + if (depth == 0) + { + int currentAwaitCount = 0; + + while (currentAwaitCount < _yieldCount) + { + currentAwaitCount++; + await Task.Yield(); + } + + throw new Exception(); + } + + long result = 0; + + if (depth == _depth) + { + int time = Warmup ? 5 : 500; + + Stopwatch timer = Stopwatch.StartNew(); + + long numIters = 0; + + while (timer.ElapsedMilliseconds < time) + { + try + { + result = await RunValueTask(depth - 1); + } + catch (Exception e) + { + + } + numIters++; + + } + return numIters; + } + else if ((depth % _finallyRate) == 0) + { + try + { + result = await RunValueTask(depth - 1); + } + finally + { + Sink = (int)liveState1 + (int)liveState2 + (int)(1 / liveState3) + depth; + } + } + else + { + result = await RunValueTask(depth - 1); + } + + Sink = (int)liveState1 + (int)liveState2 + (int)(1 / liveState3) + depth; + return result; + } + + public async2 long RunAsync2(int depth) + { + int liveState1 = depth * 3 + _yieldCount; + int liveState2 = depth; + double liveState3 = _yieldCount; + + if (depth == 0) + { + int currentAwaitCount = 0; + + while (currentAwaitCount < _yieldCount) + { + currentAwaitCount++; + await Task.Yield(); + } + + throw new Exception(); + } + + long result = 0; + + if (depth == _depth) + { + int time = Warmup ? 5 : 500; + + Stopwatch timer = Stopwatch.StartNew(); + + long numIters = 0; + + while (timer.ElapsedMilliseconds < time) + { + try + { + result = await RunAsync2(depth - 1); + } + catch (Exception e) + { + + } + numIters++; + + } + return numIters; + } + else if ((depth % _finallyRate) == 0) + { + try + { + result = await RunAsync2(depth - 1); + } + finally + { + Sink = (int)liveState1 + (int)liveState2 + (int)(1 / liveState3) + depth; + } + } + else + { + result = await RunAsync2(depth - 1); + } + + Sink = (int)liveState1 + (int)liveState2 + (int)(1 / liveState3) + depth; + return result; + } + } +} diff --git a/src/tests/Loader/async/async2-eh-microbench.csproj b/src/tests/Loader/async/async2-eh-microbench.csproj new file mode 100644 index 00000000000000..d159a32e762f81 --- /dev/null +++ b/src/tests/Loader/async/async2-eh-microbench.csproj @@ -0,0 +1,9 @@ + + + True + true + + + + + From 350cf5a917bf8eb57a1a68692976dd85630abbd7 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Wed, 8 Nov 2023 13:28:44 +0100 Subject: [PATCH 072/203] Update outdated comment --- .../System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs | 3 --- src/coreclr/inc/corinfo.h | 2 -- 2 files changed, 5 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs index 0e10e2a6295280..32d0b0c3f60f28 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs @@ -952,8 +952,6 @@ internal enum CorInfoContinuationFlags { // Whether or not the continuation expects the result to be boxed and // placed in the GCData array at index 0. Not set if the callee is void. - // TODO: In the future, for value types without any GC refs in them, we can - // place them at the beginning of the Data array instead to avoid a box. CORINFO_CONTINUATION_RESULT_IN_GCDATA = 1, // If this bit is set the continuation resumes inside a try block and thus // if an exception is being propagated, needs to be resumed. The exception @@ -965,7 +963,6 @@ internal enum CorInfoContinuationFlags CORINFO_CONTINUATION_OSR_IL_OFFSET_IN_DATA = 4, } - internal sealed unsafe class Continuation { public Continuation? Next; diff --git a/src/coreclr/inc/corinfo.h b/src/coreclr/inc/corinfo.h index fb3e95e40f7e90..666f196895093f 100644 --- a/src/coreclr/inc/corinfo.h +++ b/src/coreclr/inc/corinfo.h @@ -1829,8 +1829,6 @@ enum CorInfoContinuationFlags { // Whether or not the continuation expects the result to be boxed and // placed in the GCData array at index 0. Not set if the callee is void. - // TODO: In the future, for value types without any GC refs in them, we can - // place them at the beginning of the Data array instead to avoid a box. CORINFO_CONTINUATION_RESULT_IN_GCDATA = 1, // If this bit is set the continuation resumes inside a try block and thus // if an exception is being propagated, needs to be resumed. The exception From 6ac06db1823f36dd433c83e6dab7aef62acce82e Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Wed, 8 Nov 2023 13:29:04 +0100 Subject: [PATCH 073/203] Fix a JITDUMP --- 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 80fe8da21096a7..bb2bc7b2e981e4 100644 --- a/src/coreclr/jit/async.cpp +++ b/src/coreclr/jit/async.cpp @@ -449,7 +449,7 @@ void Async2Transformation::Transform( { exceptionGCDataIndex = gcRefsCount++; JITDUMP(" " FMT_BB " is in try region %u; exception will be at GC@+%02u in GC data\n", block->bbNum, - exceptionGCDataIndex); + block->getTryIndex(), exceptionGCDataIndex); } for (LiveLocalInfo& inf : m_liveLocals) From e762f70ecd4d0f9a107c98cfef1275c554d272ca Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Wed, 8 Nov 2023 15:12:04 +0100 Subject: [PATCH 074/203] Update design document for latest JIT SM changes --- docs/design/features/runtime-handled-tasks.md | 150 ++++++------------ 1 file changed, 45 insertions(+), 105 deletions(-) diff --git a/docs/design/features/runtime-handled-tasks.md b/docs/design/features/runtime-handled-tasks.md index f137f6085696d2..ff85df913ec31b 100644 --- a/docs/design/features/runtime-handled-tasks.md +++ b/docs/design/features/runtime-handled-tasks.md @@ -232,7 +232,7 @@ In addition, the JIT generates code for each of these suspension points to be ab Communicating suspension is done by returning a non-zero continuation, using a special calling convention. The continuation is a normal GC ref so special care must be taken within the JIT and within the VM to handle proper GC reporting of this return value. -On x64, the continuation is always returned in `rcx`. +On x64 the continuation is returned in `rcx`. When a callee suspends by returning a non-zero continuation the caller itself suspends by creating its own continuation and returning it. Additionally, the generated suspension code links the continuations into a linked list. @@ -263,15 +263,13 @@ The continuation must have been created by the same exact native code version th Passing a zero continuation is the same as starting the state machine. The JIT will automatically generate code to pass a zero continuation when calling an async2 method. -When the JIT generates code for an async2 function it requests the runtime to create a resumption stub. +When the JIT generates code for an async2 function it asks the runtime to create a resumption stub. The suspension code stores the pointer to the resumption stub in the `Continuation.Resume` field. The resumption stub implements the following: 1. It calls into the original function passing the non-zero continuation -2. It passes default values for all arguments, to make sure that the callee has the expected stack frame set up -3. If the function returns a value, it ensures that the return value is propagated into the next continuation. -Continuations expect that the next continuation has allocated space for the return value in its `GCData[0]` field. -Currently return values are always boxed when propagating from one continuation to the next continuation. +2. It passes default values for all arguments, to make sure that the callee has the expected stack frame set up. If parameters are live, then it is expected that the resumed function restores them from the continuation state. +3. If the function returns a value it ensures that the return value is propagated into the next continuation at the right location (either its `Data` or `GCData` arrays). 4. If the function returned a new continuation (i.e. it suspended again), the resumption stub returns it back to the caller. In pseudo-C#: @@ -289,7 +287,10 @@ static Continuation? IL_STUB_AsyncResume_Foo(Continuation continuation) if (newContinuation == null) { - continuation.Next.GCData[0] = (object)result; + // Foo returns an int and is saved in the Data array. If Foo returned a + // GC ref or struct with GC refs, it would be boxed and stored in GCData instead. + // Exact index depends on some factors; see the code for the details. + Unsafe.Write(ref continuation.Next.Data[index], result); } return newContinuation; @@ -300,7 +301,7 @@ static Continuation? IL_STUB_AsyncResume_Foo(Continuation continuation) To interact with the async2 calling convention the JIT/VM provides the following intrinsics: ```csharp -// Retrieve the continuation returned by an async2 function +// Retrieve the continuation returned by a preceding async2 function call [Intrinsic] internal static Continuation? Async2CallContinuation() => null; @@ -320,12 +321,13 @@ Instead, the resumption stub calls the target by `calli` with a signature that i There is additional complexity necessary to handle OSR correctly: 1. OSR expects that the frame was set up by a tier-0 method so resumption directly in an OSR method is not possible. -Instead, the JIT generates code in the tier0 version to check for an OSR continuation in which case the tier-0 method transitions immediately -to the OSR method on resumption. +Instead, the JIT generates code in the tier0 version to check for an OSR continuation in which case the tier-0 method transitions immediately to the OSR method on resumption. The resumption stub created for an OSR method thus points to its corresponding tier-0 code version. This is currently implemented by storing a link between OSR methods and corresponding tier-0 methods in `PatchpointInfo`. 2. When resuming in a tier-0 method, if we transition to the OSR method by normal means, the OSR method will see a non-zero continuation that belongs to the tier-0 method. The OSR method must ignore the continuation in this case. +3. OSR continuations store the IL offset in the `Data` array which complicates propagation of return values into the next continuation, as the exact index to use depends on whether or not this IL offset is present in that continuation. +This introduces a small amount of overhead in resumption stubs (in terms of a flag check). ### Interop: the async1<->async2 adapter layer @@ -377,16 +379,23 @@ static Task FooAdapter(int a, int b) { int result; Continuation? continuation = null; + + ExecutionAndSyncBlockStore state = new(); + state.Push(); + try { - delegate* foo = &Foo; - result = foo(null, a, b); + result = Foo(a, b); continuation = StubHelpers.Async2CallContinuation(); } catch (Exception ex) { return Task.FromException(ex); } + finally + { + state.Pop(); + } if (continuation == null) return Task.FromResult(result); @@ -394,100 +403,31 @@ static Task FooAdapter(int a, int b) return FinalizeTaskReturningThunk(continuation); } ``` -(TODO: ExecutionContext, SynchronizationContext handling...) - -The interesting bit is what happens in the asynchronous case, where we ended up in the async2 -> async1 adapter described above, which stored the head of the continuation into TLS. -This is handled inside `Task FinalizeTaskReturningThunk(Continuation continuation)`: - -```csharp -private static Task FinalizeTaskReturningThunk(Continuation continuation) -{ - TaskCompletionSource tcs = new(); - ref RuntimeAsyncAwaitState state = ref t_runtimeAsyncAwaitState; - object[] gcData = new object[3]; - gcData[2] = tcs; - continuation.Next = new Continuation - { - Resume = &ResumeTaskCompletionSource, - GCData = gcData, - Flags = CorInfoContinuationFlags.CORINFO_CONTINUATION_NEEDS_EXCEPTION | CorInfoContinuationFlags.CORINFO_CONTINUATION_RESULT_IN_GCDATA, - }; - - Continuation headContinuation = state.SentinelContinuation!.Next!; - state.Notifier!.OnCompleted(() => DispatchContinuations(headContinuation)); - return tcs.Task; -} - -private static Continuation? ResumeTaskCompletionSource(Continuation continuation, Exception? exception) -{ - var tcs = (TaskCompletionSource)continuation.GCData![2]; - Exception ex = (Exception)continuation.GCData![1]; - if (ex == null) - { - tcs.SetResult((T)continuation.GCData![0]); - } - else - { - tcs.SetException(ex); - } - - return null; -} -``` -A synthetic continuation is linked to the end of the chain; when resumed, this continuation will trigger the returned `Task` to be completed via the `TaskCompletionSource`. -With the entire continuation set up properly the `OnCompleted` method of the awaiter stored in TLS can be invoked; it queues a callback to the `DispatchContinuations` function with the head continuation retrieved through TLS. - -The final piece of the puzzle is what happens when the awaiter actually invokes the callback asynchronously. -It delegates to `DispatchContinuations`, another function part of the BCL. -This function repeatedly dispatches the next continuation by calling its resumption stub. -It also accounts for potential exceptions thrown, and propagates it into the next relevant continuation. -Finally, it handles the case where a resumed async2 function suspends again. -```csharp -private static unsafe void DispatchContinuations(Continuation? continuation) -{ - Debug.Assert(continuation != null); - - while (true) - { - Continuation? newContinuation = null; - try - { - newContinuation = continuation.Resume(continuation); - } - catch (Exception ex) - { - continuation = UnwindToPossibleHandler(continuation); - continuation.GCData![(continuation.Flags & CorInfoContinuationFlags.CORINFO_CONTINUATION_RESULT_IN_GCDATA) != 0 ? 1 : 0] = ex; - continue; - } - - if (newContinuation != null) - { - newContinuation.Next = continuation.Next; - ref RuntimeAsyncAwaitState state = ref t_runtimeAsyncAwaitState; - Continuation headContinuation = state.SentinelContinuation!.Next!; - state.Notifier!.OnCompleted(() => DispatchContinuations(headContinuation)); - return; - } - - continuation = continuation.Next; - if (continuation == null) - break; - } -} - -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; - } -} -``` +In the suspended case where `Foo` returns a continuation, we know that the leaf called into the async2 -> async1 adapter described previously, which has made the head of the continuation chain and the leaf awaiter available in TLS. + +The remaining work gets handled inside `Task FinalizeTaskReturningThunk(Continuation continuation)`. +See the code for the details, but logically two things happen: +1. We link a synthetic continuation to the end of the continuation chain. +When the last async2 method is resumed and finishes running, it will store its result into the synthetic continuation in the same way as other continuations, making the final result available from there. +2. We call `OnCompleted` on the awaiter that was saved in TLS. +The callback passed calls a dispatcher that starts resuming the continuation chain in a loop. + +There are complications around proper `ExecutionContext` and `SynchronizationContext` handling. +We currently implement `FinalizeTaskReturningThunk` as a normal async1 method to get (some of) this handling automatically; see the code for the details. + +### Potential future improvements +* We can avoid allocating a synthetic continuation in the async1 -> async2 case if resumption stubs instead take a pointer for where the return value should be stored. +The dispatcher should pass the return location of the next continuation if there is one, and otherwise forward a passed-in pointer for the final result. +* EH is currently handled by rethrowing exceptions at the suspension point. +It should be possible for the JIT to bypass the repeated VM EH dispatching by directly running the appropriate handlers on resumption. +* If the JIT always allocates continuations that are large enough for all suspension points within the same method then it should be possible to reuse previously allocated continuations when a resumed function suspends again. +* With support for multiple entry points to functions we can avoid the overhead on async2 to async2 calls of passing a null continuation, and the overhead of testing this continuation at the beginning of most async2 calls. +Instead, resumption stubs would resume at a special "resume" entry point that would handle the resumption continuation. +* Suspending deep async call stacks result in a lot of separate allocations as the call stack unwinds. +If we passed along the state size on async2 to async2 calls, then it would be possible to allocate the state arrays once in the leaf. +This would trade off suspension performance for synchronous performance. +Alternatively we could keep some large `byte[]` and `object[]` arrays in TLS that we stored state in during unwinding, after which we copied the resulting arrays. ## Shared implementation between Unwinder and JIT focused implementation of runtime tasks From 0828e72d7ab8cc9b1497b7f3e7aaa2f435c1f8f4 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Wed, 8 Nov 2023 15:12:35 +0100 Subject: [PATCH 075/203] Remove dead/unnecessary code --- src/coreclr/vm/prestub.cpp | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/coreclr/vm/prestub.cpp b/src/coreclr/vm/prestub.cpp index 1e61d78c318649..4269fb6f2df3a1 100644 --- a/src/coreclr/vm/prestub.cpp +++ b/src/coreclr/vm/prestub.cpp @@ -1714,13 +1714,6 @@ void MethodDesc::EmitJitStateMachineBasedRuntimeAsyncThunk(MethodDesc* pAsyncOth { ILCodeStream* pCode = pSL->NewCodeStream(ILStubLinker::kDispatch); - MetaSig asyncMsig(pAsyncOtherVariant); - SigBuilder targetSig; - CreateDerivedTargetSigWithExtraParams(asyncMsig, &targetSig); - - DWORD cbTargetSig; - PCCOR_SIGNATURE pTargetSig = (PCCOR_SIGNATURE)targetSig.GetSignature(&cbTargetSig); - unsigned continuationLocal = pCode->NewLocal(LocalDesc(CoreLibBinder::GetClass(CLASS__CONTINUATION))); TypeHandle thTaskRet = thunkMsig.GetRetTypeHandleThrowing(); @@ -1758,13 +1751,13 @@ void MethodDesc::EmitJitStateMachineBasedRuntimeAsyncThunk(MethodDesc* pAsyncOth pCode->BeginTryBlock(); DWORD localArg = 0; - if (asyncMsig.HasThis()) + if (thunkMsig.HasThis()) { // Struct async thunks not yet implemented _ASSERTE(!this->GetMethodTable()->IsValueType()); pCode->EmitLDARG(localArg++); } - for (UINT iArg = 0; iArg < asyncMsig.NumFixedArgs(); iArg++) + for (UINT iArg = 0; iArg < thunkMsig.NumFixedArgs(); iArg++) { pCode->EmitLDARG(localArg++); } From 177c3b2f9fcd5013e9f2c95e20c19ea99558ed35 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Wed, 8 Nov 2023 15:12:55 +0100 Subject: [PATCH 076/203] Fix non-generic async1 -> async2 thunks --- src/coreclr/vm/methodtablebuilder.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/coreclr/vm/methodtablebuilder.cpp b/src/coreclr/vm/methodtablebuilder.cpp index 6dcd8042459239..bf238862ca13de 100644 --- a/src/coreclr/vm/methodtablebuilder.cpp +++ b/src/coreclr/vm/methodtablebuilder.cpp @@ -2883,7 +2883,6 @@ AsyncTaskMethod ClassifyAsyncMethodCore(SigPointer sig, Module* pModule, PCCOR_S GetNameOfTypeDefOrRef(pModule, tk, &name, &_namespace); if ((strcmp(name, *pIsValueTask ? "ValueTask`1" : "Task`1") == 0) && strcmp(_namespace, "System.Threading.Tasks") == 0) { - *pIsValueTask = name[0] == 'V'; if (IsTypeDefOrRefImplementedInSystemModule(pModule, tk)) return AsyncTaskMethod::TaskReturningMethod; } @@ -2895,9 +2894,8 @@ AsyncTaskMethod ClassifyAsyncMethodCore(SigPointer sig, Module* pModule, PCCOR_S *pIsValueTask = (elemType == ELEMENT_TYPE_VALUETYPE); // This might be System.Threading.Tasks.Task or ValueTask GetNameOfTypeDefOrRef(pModule, tk, &name, &_namespace); - if ((strcmp(name, *pIsValueTask ? "ValueTask`1" : "Task`1") == 0) && strcmp(_namespace, "System.Threading.Tasks") == 0) + if ((strcmp(name, *pIsValueTask ? "ValueTask" : "Task") == 0) && strcmp(_namespace, "System.Threading.Tasks") == 0) { - *pIsValueTask = name[0] == 'V'; if (IsTypeDefOrRefImplementedInSystemModule(pModule, tk)) return AsyncTaskMethod::TaskNonGenericReturningMethod; } From 2c4257ab2920adf5cae5af54b227113366601921 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Wed, 8 Nov 2023 15:13:05 +0100 Subject: [PATCH 077/203] Add a list of expected test failures --- src/tests/Loader/async/expected_failures.txt | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 src/tests/Loader/async/expected_failures.txt diff --git a/src/tests/Loader/async/expected_failures.txt b/src/tests/Loader/async/expected_failures.txt new file mode 100644 index 00000000000000..5fe6eb747541c8 --- /dev/null +++ b/src/tests/Loader/async/expected_failures.txt @@ -0,0 +1,9 @@ +DOTNET_RuntimeAsyncViaJitGeneratedStateMachines=0: +* Loader\async\async2-returns\async2-returns.dll (retbuffers) +* Loader\async\simple-eh\simple-eh.dll (EH unsupported) +* Loader\async\async2-eh-microbench\async2-eh-microbench.cmd (EH unsupported) +* Loader\async\async2object\async2object.cmd (this one probably should be passing?) +* Loader\async\async2sharedgeneric\async2sharedgeneric.dll (generics unsupported) + +DOTNET_RuntimeAsyncViaJitGeneratedStateMachines=1: +* Loader\async\async2sharedgeneric\async2sharedgeneric.dll (generics unsupported) \ No newline at end of file From 6c3e6a40be7e1122ba6cb84a90da9da699699ea4 Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Thu, 9 Nov 2023 18:43:59 +0000 Subject: [PATCH 078/203] Merged PR 35118: Add a writeup based on the EH microbenchmark I wrote up This has insanely positive data in it. Also, I found a bug in Excel which prevented me from making graphs with raw data in them. *shrug* --- .../async-vs-async2-relative-performance.svg | 1 + docs/design/features/perf-of-throwing.svg | 1 + docs/design/features/runtime-handled-tasks.md | 100 ++++++++++++++++-- ...h-different-number-of-finally-on-stack.svg | 1 + .../throwing-relative-performance.svg | 1 + .../Loader/async/async2-eh-microbench.cs | 40 ++++--- 6 files changed, 123 insertions(+), 21 deletions(-) create mode 100644 docs/design/features/async-vs-async2-relative-performance.svg create mode 100644 docs/design/features/perf-of-throwing.svg create mode 100644 docs/design/features/throw-perf-with-different-number-of-finally-on-stack.svg create mode 100644 docs/design/features/throwing-relative-performance.svg diff --git a/docs/design/features/async-vs-async2-relative-performance.svg b/docs/design/features/async-vs-async2-relative-performance.svg new file mode 100644 index 00000000000000..8eac56d28f95ec --- /dev/null +++ b/docs/design/features/async-vs-async2-relative-performance.svg @@ -0,0 +1 @@ +0.00%200.00%400.00%600.00%800.00%1000.00%1200.00%124816iters/s async2 divided by iters/s Task base asyncStack DepthRelative performance of existing async vs runtime asyncReturn NoSuspendThrow NoSuspendReturn SuspendThrow Suspend \ No newline at end of file diff --git a/docs/design/features/perf-of-throwing.svg b/docs/design/features/perf-of-throwing.svg new file mode 100644 index 00000000000000..0658a971b015d7 --- /dev/null +++ b/docs/design/features/perf-of-throwing.svg @@ -0,0 +1 @@ +0.005,000.0010,000.0015,000.0020,000.0025,000.0030,000.0035,000.0040,000.0045,000.0050,000.001248163264iters/250msStack DepthThrow perf at varying stack depthsTask Throw SuspendValueTask Throw SuspendAsync2 Throw SuspendTask Throw NoSuspendValueTask Throw NoSuspendAsync2 Throw NoSuspend \ No newline at end of file diff --git a/docs/design/features/runtime-handled-tasks.md b/docs/design/features/runtime-handled-tasks.md index ff85df913ec31b..5a9f3183383207 100644 --- a/docs/design/features/runtime-handled-tasks.md +++ b/docs/design/features/runtime-handled-tasks.md @@ -115,11 +115,10 @@ Async2 shall integrate with `TaskScheduler.Current`. Async2 IL is broadly similar to the existing IL semantics with the following restrictions. 1. Usage of the `localloc` instruction is forbidden -3. The `ldloca` and `ldarga` instructions are redefined to return managed pointers instead of pointers. -4. A pinning local cannot be validly used with a local variable. -5. As an initial restriction that is not fundamental, the `tail.` prefix is not permitted - -Notably, the design permits byrefs and ref structs to be used within async2 methods. +2. The `ldloca` and `ldarga` instructions are redefined to return managed pointers instead of pointers. +3. A pinning local cannot be validly used with a local variable. +. As an initial restriction that is not fundamental, the `tail.` prefix is not permitted +4. Use of byrefs and ref structs within async2 methods may or may not be acceptable. The currently JIT focussed prototype is designed with the same limitations as the existing async model, and thus does not work with byref data. However, the unwinder approach is able to tolerate byrefs. #### EH Semantics No call to a method with an async2 modreq will be permitted in a finally, fault, filter, or catch clause. If such a thing exists, the program is invalid. @@ -461,10 +460,97 @@ Major identified concerns are 1. Async computation of fibonacci via recursive algorithm, with and without various amounts of yielding 2. Suspension with stacks of various depths -3. Suspension with stacks where the operation of the task operates in a sawtooth pattern. For instance, suspend first at depth 20, then reach depth 30, return to depth 10 and suspend, reach depth 30, return to depth 10 and suspend, etc. Measure the impact of suspending with greater and lesser depths. +3. Suspension with stacks where the operation of the task operates in a sawtooth pattern. +For instance, suspend first at depth 20, then reach depth 30, return to depth 10 and suspend, reach depth 30, return to depth 10 and suspend, etc. +Measure the impact of suspending with greater and lesser depths. 4. Measure the performance of thunks to async2 functions which do effectively nothing. 5. Benchmark the effect of long lived suspended state vs short lived suspended state -6. Benchmark the performance of EH at various depths. +# Benchmark the performance of EH at various depths. + +The performance of EH within the existing async programming model is highly problematic, and has caused customers issues. +In particular, the impact of async EH performance tends to interfere with responsive performance of applications when failures start ocurring suddenly. +Since EH is so much slower than standard execution performance, it is known to cause additional downtime as a server which may be capable of handling the load of succesful requests, may run out of available CPU time handling failed requests. +In addition, EH is commonly used to implement cancellation in async code. +Thus improving the performance of EH can dramatically improve codebases which make significant use of cancellation. +The runtime generated async state machine model is showing some substantial improvements to performance. + +All of these figures are gathered by using the async-eh-microbench test located at `src/tests/Loader/async/async2-eh-microbench.csproj`. +This benchmark works by looping until 250 ms has passed and measuring the number of iterations in that time. +Each iteration works by recursively calling a function until the specified stack depth is achieved (optionally calling through a try/finally block), and then suspending the async method by awaiting `Task.Yield()` or not, and then finally finishing each iteration either by returning or throwing a newly created exception. +In addition, these experiments use the JIT focused implementation of `async2`. The unwinder based implementation is both significantly slower, and does not support EH. + +## Notes on the following graphs +These benchmarks are gathered by running the async2-eh-microbench in varying configurations. +- NoSuspend vs Suspend - Suspend indicates that the async function awaits `Task.Yield()` before completing. +- `Return` vs `Throw` - `Return` indicates that the iteration finishes by returning up the call stack via return instructions, and `Throw` indicates that each iteration terminated via a `throw new Exception()`. +- `Task` vs `ValueTask` vs `Async2` - The benchmark has 3 functions which are written with the same C# code, but with different syntax using different async implementation strategies. +With `Task`, the function signature is `async Task RunTask(int depth)` +With `ValueTask`, the function signature is `async ValueTask RunTask(int depth)` +With `Task`, the function signature is `async2 long RunTask(int depth)` + +When a graph does not specify one of the config options, the graph is a relative performance gathered by dividing the iters/250ms that the benchmark produces between the two options, and then multiplying by 100 to produce a percentage. + +## Relative performance of throwing vs returning cleanly from a function at varying stack depths + +![RelativePerf](throwing-relative-performance.svg) +This graph shows the performance of throwing relative to the performance of returning cleanly from a function with varying stack depths. +There are 2 remarkable bits of data presented here. +1. The cost of throwing is extraordinary if the application would otherwise be able to complete synchronously. +2. The overhead of suspending an async function and transitioning to the thread pool is rather high. +3. The performance of runtime handled async with regards to throwing is much faster than normal EH. In fact as the stack becomes deeper, the EH model presented by runtime generated async functions does not suffer as much of a performance penalty for walking the stack as our normal EH stack walker does. + +## Performance of normal async `async Task` code vs runtime generated async functions +![PerfOfThrowing](perf-of-throwing.svg) +In this graph the raw performance of throwing at various stack depths is shown. +We can see that the performance of `ValueTask` and `Task` is nearly identical, and differs only between whether or not the function suspended or not before throwing. +In addition, we can see the different performance of throwing with `Async2`. +Since the design of EH in `Async2` is able to avoid doing expensive operations as it walks a suspended stack, the performance of throwing with various stack depths while suspended is extremely fast, and dominated by the cost of the suspend operation. +In constrast the non-suspended performance follows the same curve as the `Task` and `ValueTask` model, but is a constant factor faster, as `Async2` produces a smaller call stack, so the runtime's stack walker has substantially less work to do. + +![RelativePerf2](async-vs-async2-relative-performance.svg) + +In this graph, the performance of runtime async vs classic async is presented for the particular code in use. +What we can see is that the performance of the new runtime generated async code is always faster for this code than the traditional code generation. +Numbers above 100% indicate speedups. +This higher performance is likely due to the relatively large size of the async function used in this benchmark. +In addition, in this graph, only data out to a depth of 16 is presented, as the performance of throwing with new async at higher stack depths would make looking at the data for other depths difficult. +(It reaches a level of 3851% of the performance of the traditional async function at a stack depth of 64). Not shown is the performance of `ValueTask` based async code. It is extremely close to that of the `Task` based variant. + + +## Impact of try/finally regions on throw performance +![ThrowPerfDifferingFinallys](throw-perf-with-different-number-of-finally-on-stack.svg) + +Much of the extraordinary performance of EH the runtime generated async prototype depends on the very fast stack walk that occurs when dispatching exceptions. +Notably, in the runtime generated model we are able to easily skip frames which do not have any handlers in them with a simple flag check. +This is contrary to IL compiler generated async, where the implementation generates a catch, even if the frame has no try block written in C#. + +We can see some very interesting details. +1. In IL based async the cost of suspending once is dominated by the cost of throwing an exception, and so whether or not the function suspends, does not change the performance. +2. In the NoSuspend case, for async2 we can see that the presence or abscence of a finally has a fairly small impact on throw performance. +Most of the cost is taken up by other tasks such as stackwalking, creating the exception object, and the like. +3. In the case where we DO suspend, the performance is quite different with async2. +Without many finally blocks, we can see that the performance is really extraordinary, but as each frame gets a finally block, the cost of handling EH approaches that of the traditional async model. +4. The performance of Task Throw NoSuspend and Task Throw Suspend is effectively the same. + +## Raw data from EH microbenchmark +| Stack Depth | Task Return NoSuspend | ValueTask Return NoSuspend | Async2 Return NoSuspend | Task Throw NoSuspend | ValueTask Throw NoSuspend | Async2 Throw NoSuspend | Task Return Suspend | ValueTask Return Suspend | Async2 Return Suspend | Task Throw Suspend | ValueTask Throw Suspend | Async2 Throw Suspend | +| ----- | --------------------- | -------------------------- | ----------------------- | -------------------- | ------------------------- | ---------------------- | ------------------- | ------------------------ | --------------------- | ------------------ | ----------------------- | -------------------- | +| 1 | 7,976,995.00 | 7,994,790.00 | 13,966,110.00 | 21,630.00 | 21,789.00 | 46,434.00 | 589,057.00 | 574,660.00 | 629,310.00 | 15,563.00 | 15,584.00 | 21,208.00 | +| 2 | 4,738,359.00 | 5,036,613.00 | 11,573,294.00 | 13,781.00 | 13,359.00 | 38,975.00 | 390,167.00 | 349,900.00 | 617,464.00 | 9,806.00 | 9,740.00 | 21,514.00 | +| 4 | 2,884,153.00 | 2,924,123.00 | 8,340,047.00 | 7,717.00 | 7,477.00 | 29,887.00 | 276,584.00 | 265,590.00 | 577,707.00 | 5,778.00 | 5,495.00 | 21,288.00 | +| 8 | 1,274,335.00 | 1,324,960.00 | 5,308,395.00 | 4,068.00 | 4,001.00 | 20,358.00 | 217,067.00 | 198,172.00 | 474,723.00 | 3,476.00 | 3,376.00 | 20,877.00 | +| 16 | 536,064.00 | 533,731.00 | 2,888,828.00 | 2,104.00 | 2,063.00 | 12,941.00 | 132,636.00 | 106,605.00 | 315,191.00 | 1,912.00 | 1,909.00 | 20,496.00 | +| 32 | 263,823.00 | 249,821.00 | 1,157,832.00 | 1,032.00 | 989.00 | 7,319.00 | 71,989.00 | 55,860.00 | 184,699.00 | 973.00 | 949.00 | 19,379.00 | +| 64 | 113,187.00 | 114,845.00 | 488,245.00 | 484.00 | 473.00 | 3,936.00 | 37,086.00 | 28,365.00 | 98,769.00 | 458.00 | 456.00 | 17,565.00 | + +| Finally blocks on stack | Task Throw NoSuspend | Async2 Throw NoSuspend | Task Throw Suspend | Async2 Throw Suspend | +| ----------------------- | -------------------- | ---------------------- | ------------------ | -------------------- | +| 1 | 1067 | 7504 | 965 | 20107 | +| 2 | 1032 | 7441 | 965 | 12140 | +| 4 | 1034 | 7392 | 959 | 7306 | +| 8 | 1006 | 7287 | 928 | 4268 | +| 16 | 972 | 7045 | 912 | 2307 | +| 32 | 872 | 6961 | 857 | 1194 | ## Scenario benchmarks diff --git a/docs/design/features/throw-perf-with-different-number-of-finally-on-stack.svg b/docs/design/features/throw-perf-with-different-number-of-finally-on-stack.svg new file mode 100644 index 00000000000000..59d27773c75f47 --- /dev/null +++ b/docs/design/features/throw-perf-with-different-number-of-finally-on-stack.svg @@ -0,0 +1 @@ +050001000015000200002500012481632iters/250 ms Number of try/finally blocks thrown throughEffect of try/finally handlers on throw performanceTask Throw NoSuspendAsync2 Throw NoSuspendTask Throw SuspendAsync2 Throw Suspend \ No newline at end of file diff --git a/docs/design/features/throwing-relative-performance.svg b/docs/design/features/throwing-relative-performance.svg new file mode 100644 index 00000000000000..521e9086fd6929 --- /dev/null +++ b/docs/design/features/throwing-relative-performance.svg @@ -0,0 +1 @@ +0.00%2.00%4.00%6.00%8.00%10.00%12.00%14.00%16.00%18.00%20.00%1248163264iters/s wirth throw divided by iter/s with returnStack DepthRelative performance of throwing vs returning cleanlyTask NoSuspendValueTask NoSuspendAsync2 NoSuspendTask SuspendValueTask SuspendAsync2 Suspend \ No newline at end of file diff --git a/src/tests/Loader/async/async2-eh-microbench.cs b/src/tests/Loader/async/async2-eh-microbench.cs index a750c194fd21dd..e66bb56ceb5ec2 100644 --- a/src/tests/Loader/async/async2-eh-microbench.cs +++ b/src/tests/Loader/async/async2-eh-microbench.cs @@ -39,7 +39,11 @@ public static async Task AsyncEntry() int finallyRate = args.Length > 3 ? int.Parse(args[3]) : 1000; Console.WriteLine("With a try/finally block every {0} frames", finallyRate); - Benchmark warmupBm = new Benchmark(5, 5, 2); + // Throw or return + bool throwOrReturn = args.Length > 4 ? String.Equals(args[4], "throw", StringComparison.OrdinalIgnoreCase) : true; + Console.WriteLine($"Which will {(throwOrReturn ? "throw" : "return")} when finished yielding"); + + Benchmark warmupBm = new Benchmark(5, 5, 2, throwOrReturn); warmupBm.Warmup = true; for (int i = 0; i < 4; i++) { @@ -55,22 +59,22 @@ public static async Task AsyncEntry() } Console.WriteLine("Warmup done, running benchmark"); - await RunBench(yieldFrequency, depth, finallyRate, "Async2"); - await RunBench(yieldFrequency, depth, finallyRate, "Task"); - await RunBench(yieldFrequency, depth, finallyRate, "ValueTask"); + await RunBench(yieldFrequency, depth, finallyRate, throwOrReturn, "Async2"); + await RunBench(yieldFrequency, depth, finallyRate, throwOrReturn, "Task"); + await RunBench(yieldFrequency, depth, finallyRate, throwOrReturn, "ValueTask"); } - private static async Task RunBench(int yieldCount, int depth, int finallyRate, string type) + private static async Task RunBench(int yieldCount, int depth, int finallyRate, bool throwOrReturn, string type) { - Benchmark bm = new(yieldCount, depth, finallyRate); + Benchmark bm = new(yieldCount, depth, finallyRate, throwOrReturn); Console.WriteLine($"Running benchmark on '{type}' methods"); List results = new(); for (int i = 0; i < 16; i++) { long numIters = await bm.Run(type); - Console.WriteLine($"iters={numIters}"); +// Console.WriteLine($"iters={numIters}"); results.Add(numIters); } @@ -84,14 +88,16 @@ private class Benchmark private readonly int _yieldCount; private readonly int _depth; private readonly int _finallyRate; + private readonly bool _throwOrReturn; public int Sink; public bool Warmup; - public Benchmark(int yieldCount, int depth, int finallyRate) + public Benchmark(int yieldCount, int depth, int finallyRate, bool throwOrReturn) { _yieldCount = yieldCount; _depth = depth; _finallyRate = finallyRate; + _throwOrReturn = throwOrReturn; } public async2 long Run(string type) @@ -121,14 +127,16 @@ public async Task RunTask(int depth) await Task.Yield(); } - throw new Exception(); + if (_throwOrReturn) + throw new Exception(); + return 8375983; } long result = 0; if (depth == _depth) { - int time = Warmup ? 5 : 500; + int time = Warmup ? 5 : 250; Stopwatch timer = Stopwatch.StartNew(); @@ -184,14 +192,16 @@ public async ValueTask RunValueTask(int depth) await Task.Yield(); } - throw new Exception(); + if (_throwOrReturn) + throw new Exception(); + return 8375983; } long result = 0; if (depth == _depth) { - int time = Warmup ? 5 : 500; + int time = Warmup ? 5 : 250; Stopwatch timer = Stopwatch.StartNew(); @@ -248,14 +258,16 @@ public async2 long RunAsync2(int depth) await Task.Yield(); } - throw new Exception(); + if (_throwOrReturn) + throw new Exception(); + return 8375983; } long result = 0; if (depth == _depth) { - int time = Warmup ? 5 : 500; + int time = Warmup ? 5 : 250; Stopwatch timer = Stopwatch.StartNew(); From 5ba6888b6bc5f3f3ca52e6f7a626d9217afb693c Mon Sep 17 00:00:00 2001 From: Vladimir Sadov Date: Mon, 13 Nov 2023 19:29:29 +0000 Subject: [PATCH 079/203] Merged PR 35196: Switching to the new syntax As discussed, we will now use: `public static async2 Task Foo()` syntax. That is basically the same as async1, just with a different modifier. As a part of this also couple bugs got fixed, mostly because dealing with generics now uses the same code as async1 uses. --- buildroslynnugets.cmd | 4 ++-- eng/Versions.props | 6 +++--- .../CompatibilitySuppressions.xml | 6 ------ .../src/CompatibilitySuppressions.xml | 8 +------- .../System.Private.CoreLib.Shared.projitems | 1 - .../ValueTaskAsyncAttribute.cs | 18 ------------------ .../System.Runtime/ref/System.Runtime.cs | 5 ----- src/tests/Loader/async/async2-eh-microbench.cs | 4 ++-- src/tests/Loader/async/async2-returns.cs | 10 +++++----- .../Loader/async/async2-varying-yields.cs | 6 +++--- src/tests/Loader/async/async2-with-yields.cs | 4 ++-- .../Loader/async/async2-without-yields.cs | 7 +++++-- .../async/async2fibonacci-with-yields.cs | 4 ++-- .../async/async2fibonacci-without-yields.cs | 4 ++-- src/tests/Loader/async/async2implement.cs | 6 +++--- src/tests/Loader/async/async2object.cs | 2 +- .../Loader/async/async2objectsCaptured.cs | 2 +- src/tests/Loader/async/async2override.cs | 6 +++--- src/tests/Loader/async/async2sharedgeneric.cs | 6 +++++- src/tests/Loader/async/async2valuetask.cs | 3 +-- src/tests/Loader/async/async2void.cs | 2 +- .../Loader/async/cse-array-index-byref.cs | 4 ++-- src/tests/Loader/async/simple-eh.cs | 4 ++-- 23 files changed, 46 insertions(+), 76 deletions(-) delete mode 100644 src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/ValueTaskAsyncAttribute.cs diff --git a/buildroslynnugets.cmd b/buildroslynnugets.cmd index 9a6e68c96776b3..e097dccf488e87 100644 --- a/buildroslynnugets.cmd +++ b/buildroslynnugets.cmd @@ -1,14 +1,14 @@ setlocal ENABLEEXTENSIONS pushd %~dp0 -set ASYNC_SUFFIX=async-6 +set ASYNC_SUFFIX=async-7 cd .. pushd dotnet-roslyn git fetch AzDo dev/vsadov/a2 rem when updating this, make sure to update the ASYNC_SUFFIX above and the versions.props file -git checkout facd968bc0e0c2889c78d4f121c5d1d39c3d6566 +git checkout 046d22fad72ac726d36d93b0e9d186574e77207b call restore.cmd call build.cmd -c release diff --git a/eng/Versions.props b/eng/Versions.props index 21897be218e3ca..9b918bbd541c5c 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -39,9 +39,9 @@ Any tools that contribute to the design-time experience should use the MicrosoftCodeAnalysisVersion_LatestVS property above to ensure they do not break the local dev experience. --> - 4.9.0-async-6 - 4.9.0-async-6 - 4.9.0-async-6 + 4.9.0-async-7 + 4.9.0-async-7 + 4.9.0-async-7 - - CP0009 - T:System.Runtime.CompilerServices.ValueTaskAsyncAttribute - ref/net9.0/System.Private.CoreLib.dll - lib/net9.0/System.Private.CoreLib.dll - CP0015 M:System.Diagnostics.Tracing.EventSource.Write``1(System.String,``0):[T:System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute] diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml b/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml index 88c94b8826a69b..9c7bffdba74162 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml @@ -973,12 +973,6 @@ ref/net9.0/System.Private.CoreLib.dll lib/net9.0/System.Private.CoreLib.dll - - CP0009 - T:System.Runtime.CompilerServices.ValueTaskAsyncAttribute - ref/net9.0/System.Private.CoreLib.dll - lib/net9.0/System.Private.CoreLib.dll - CP0014 M:System.Runtime.CompilerServices.RuntimeHelpers.GetObjectValue(System.Object)->object?:[T:System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute] @@ -1011,4 +1005,4 @@ CP0015 M:System.Diagnostics.Tracing.EventSource.WriteEventWithRelatedActivityIdCore(System.Int32,System.Guid*,System.Int32,System.Diagnostics.Tracing.EventSource.EventData*):[T:System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute] - \ No newline at end of file + diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index d8c11d553d55a2..1415513eb12bfe 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -890,7 +890,6 @@ - diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/ValueTaskAsyncAttribute.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/ValueTaskAsyncAttribute.cs deleted file mode 100644 index bfe750bf836f7a..00000000000000 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/ValueTaskAsyncAttribute.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace System.Runtime.CompilerServices -{ - /// - /// Specifies that the async2 function in its promise-returning form should be returning ValueTask/ValueTask`1 - /// - /// The attribute is source-only and is not supposed to be emitted into metadata, where it has no meaning. - /// TODO: (vsadov) I have a lot of doubts that this is the best possible design to support ValueTask in async2. - /// While good enough to unblock working with ValueTask async2, we should think more about the syntax. - /// - [AttributeUsage(AttributeTargets.Method)] - public class ValueTaskAsyncAttribute : Attribute - { - public ValueTaskAsyncAttribute() { } - } -} diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index 54bd77d4fe502a..72ef20958c50aa 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -13299,11 +13299,6 @@ public sealed partial class UnsafeValueTypeAttribute : System.Attribute { public UnsafeValueTypeAttribute() { } } - [AttributeUsage(AttributeTargets.Method)] - public sealed partial class ValueTaskAsyncAttribute : Attribute - { - public ValueTaskAsyncAttribute() { } - } public readonly partial struct ValueTaskAwaiter : System.Runtime.CompilerServices.ICriticalNotifyCompletion, System.Runtime.CompilerServices.INotifyCompletion { private readonly object _dummy; diff --git a/src/tests/Loader/async/async2-eh-microbench.cs b/src/tests/Loader/async/async2-eh-microbench.cs index e66bb56ceb5ec2..ab4beb0e182b0d 100644 --- a/src/tests/Loader/async/async2-eh-microbench.cs +++ b/src/tests/Loader/async/async2-eh-microbench.cs @@ -100,7 +100,7 @@ public Benchmark(int yieldCount, int depth, int finallyRate, bool throwOrReturn) _throwOrReturn = throwOrReturn; } - public async2 long Run(string type) + public async2 Task Run(string type) { if (type == "Async2") return await RunAsync2(_depth); @@ -242,7 +242,7 @@ public async ValueTask RunValueTask(int depth) return result; } - public async2 long RunAsync2(int depth) + public async2 Task RunAsync2(int depth) { int liveState1 = depth * 3 + _yieldCount; int liveState2 = depth; diff --git a/src/tests/Loader/async/async2-returns.cs b/src/tests/Loader/async/async2-returns.cs index ae006fb022131c..174a66c479a14c 100644 --- a/src/tests/Loader/async/async2-returns.cs +++ b/src/tests/Loader/async/async2-returns.cs @@ -15,7 +15,7 @@ public static void TestEntryPoint() } [MethodImpl(MethodImplOptions.NoInlining)] - private static async2 void Returns(C c) + private static async2 Task Returns(C c) { for (int i = 0; i < 20000; i++) { @@ -58,28 +58,28 @@ private static void AssertEqual(T expected, T actual) } [MethodImpl(MethodImplOptions.NoInlining)] - private static async2 S ReturnsStruct() + private static async2 Task> ReturnsStruct() { await Task.Yield(); return new S { A = 42, B = 4242, C = 424242, D = 42424242 }; } [MethodImpl(MethodImplOptions.NoInlining)] - private static async2 S ReturnsStructGC() + private static async2 Task> ReturnsStructGC() { await Task.Yield(); return new S { A = "A", B = "B", C = "C", D = "D" }; } [MethodImpl(MethodImplOptions.NoInlining)] - private static async2 S ReturnsBytes() + private static async2 Task> ReturnsBytes() { await Task.Yield(); return new S { A = 4, B = 40, C = 42, D = 45 }; } [MethodImpl(MethodImplOptions.NoInlining)] - private static async2 string ReturnsString() + private static async2 Task ReturnsString() { await Task.Yield(); return "a string!"; diff --git a/src/tests/Loader/async/async2-varying-yields.cs b/src/tests/Loader/async/async2-varying-yields.cs index 16744385a5a553..6edc30e8118dd7 100644 --- a/src/tests/Loader/async/async2-varying-yields.cs +++ b/src/tests/Loader/async/async2-varying-yields.cs @@ -82,7 +82,7 @@ async Task #elif ASYNC1_VALUETASK async ValueTask #else - async2 long + async2 Task #endif Run(int depth) { @@ -104,7 +104,7 @@ async Task #elif ASYNC1_VALUETASK async ValueTask #else - async2 long + async2 Task #endif Loop() { @@ -130,7 +130,7 @@ async Task #elif ASYNC1_VALUETASK async ValueTask #else - async2 int + async2 Task #endif DoYields() { diff --git a/src/tests/Loader/async/async2-with-yields.cs b/src/tests/Loader/async/async2-with-yields.cs index acaaf5ce987be6..2f53a60f7c4fc5 100644 --- a/src/tests/Loader/async/async2-with-yields.cs +++ b/src/tests/Loader/async/async2-with-yields.cs @@ -8,7 +8,7 @@ public class Async2FibonacceWithYields { - internal static async2 int B(int n) + internal static async2 Task B(int n) { int num = 1; await Task.Yield(); @@ -22,7 +22,7 @@ internal static async2 int B(int n) return num; } - internal static async2 int A(int n) + internal static async2 Task A(int n) { int num = n; for (int num2 = 0; num2 < n; num2++) diff --git a/src/tests/Loader/async/async2-without-yields.cs b/src/tests/Loader/async/async2-without-yields.cs index ac3f0fc3f731e5..ba1f2aeff0df1f 100644 --- a/src/tests/Loader/async/async2-without-yields.cs +++ b/src/tests/Loader/async/async2-without-yields.cs @@ -8,12 +8,15 @@ public class Async2FibonacceWithoutYields { - internal static async2 int B(int n) + //This async method lacks 'await' +#pragma warning disable 1998 + + internal static async2 Task B(int n) { return 100; } - internal static async2 int A(int n) + internal static async2 Task A(int n) { int num = n; for (int num2 = 0; num2 < n; num2++) diff --git a/src/tests/Loader/async/async2fibonacci-with-yields.cs b/src/tests/Loader/async/async2fibonacci-with-yields.cs index 2ee89ea4f2d1ab..6df9ddc44445a9 100644 --- a/src/tests/Loader/async/async2fibonacci-with-yields.cs +++ b/src/tests/Loader/async/async2fibonacci-with-yields.cs @@ -28,7 +28,7 @@ public static async Task AsyncEntry() } } - static async2 uint A(uint n) + static async2 Task A(uint n) { uint result = n; for (uint i = 0; i < n && !done; i++) @@ -36,7 +36,7 @@ static async2 uint A(uint n) return result; } - static async2 uint B(uint n) + static async2 Task B(uint n) { uint result = n; diff --git a/src/tests/Loader/async/async2fibonacci-without-yields.cs b/src/tests/Loader/async/async2fibonacci-without-yields.cs index fe665e702d5fc6..bd47c8e869ab02 100644 --- a/src/tests/Loader/async/async2fibonacci-without-yields.cs +++ b/src/tests/Loader/async/async2fibonacci-without-yields.cs @@ -28,7 +28,7 @@ public static async Task AsyncEntry() } } - static async2 uint A(uint n) + static async2 Task A(uint n) { uint result = n; for (uint i = 0; i < n; i++) @@ -36,7 +36,7 @@ static async2 uint A(uint n) return result; } - static async2 uint B(uint n) + static async2 Task B(uint n) { uint result = n; diff --git a/src/tests/Loader/async/async2implement.cs b/src/tests/Loader/async/async2implement.cs index 6871f815227949..bb8607541d1d49 100644 --- a/src/tests/Loader/async/async2implement.cs +++ b/src/tests/Loader/async/async2implement.cs @@ -10,7 +10,7 @@ public class Async2Implement { interface IBase1 { - public async2 int M1(); + public async2 Task M1(); } class Derived1 : IBase1 @@ -24,7 +24,7 @@ public async Task M1() class Derived1a : IBase1 { - public async2 int M1() + public async2 Task M1() { await Task.Yield(); return 3; @@ -38,7 +38,7 @@ interface IBase2 class Derived2 : IBase2 { - public async2 int M1() + public async2 Task M1() { await Task.Yield(); return 12; diff --git a/src/tests/Loader/async/async2object.cs b/src/tests/Loader/async/async2object.cs index aa7381cf074f3c..fb62588652ae3f 100644 --- a/src/tests/Loader/async/async2object.cs +++ b/src/tests/Loader/async/async2object.cs @@ -20,7 +20,7 @@ private static async Task AsyncTestEntryPoint(int arg) } [MethodImpl(MethodImplOptions.NoInlining)] - private static async2 object ObjMethod(int arg) + private static async2 Task ObjMethod(int arg) { await Task.Yield(); return arg; diff --git a/src/tests/Loader/async/async2objectsCaptured.cs b/src/tests/Loader/async/async2objectsCaptured.cs index f2a6a795fcbc98..197d29f0383d98 100644 --- a/src/tests/Loader/async/async2objectsCaptured.cs +++ b/src/tests/Loader/async/async2objectsCaptured.cs @@ -8,7 +8,7 @@ public class Async2ObjectsWithYields { - internal static async2 int A(object n) + internal static async2 Task A(object n) { // use string equality so that JIT would not think of hoisting "(int)n" // also to produce some amout of garbage diff --git a/src/tests/Loader/async/async2override.cs b/src/tests/Loader/async/async2override.cs index bdb74bb2ed4aba..4ee5d1446b831a 100644 --- a/src/tests/Loader/async/async2override.cs +++ b/src/tests/Loader/async/async2override.cs @@ -10,7 +10,7 @@ public class Async2Override { class Base { - public virtual async2 int M1() + public virtual async2 Task M1() { await Task.Yield(); return 1; @@ -28,7 +28,7 @@ public override async Task M1() class Derived2 : Derived1 { - public override async2 int M1() + public override async2 Task M1() { await Task.Yield(); return 3; @@ -47,7 +47,7 @@ public virtual async Task M1() class Derived11 : Base1 { - public override async2 int M1() + public override async2 Task M1() { await Task.Yield(); return 12; diff --git a/src/tests/Loader/async/async2sharedgeneric.cs b/src/tests/Loader/async/async2sharedgeneric.cs index 42aa7ea0f0e24f..992235b1c6dcc0 100644 --- a/src/tests/Loader/async/async2sharedgeneric.cs +++ b/src/tests/Loader/async/async2sharedgeneric.cs @@ -22,8 +22,12 @@ private static async Task AsyncTestEntryPoint() { await Async2TestEntryPoint(); } + + //This async method lacks 'await' +#pragma warning disable 1998 + [MethodImpl(MethodImplOptions.NoInlining)] - private static async2 void Async2TestEntryPoint() + private static async2 Task Async2TestEntryPoint() { s_type = typeof(T); } diff --git a/src/tests/Loader/async/async2valuetask.cs b/src/tests/Loader/async/async2valuetask.cs index 53d0792da245b6..deff530e9f5d4d 100644 --- a/src/tests/Loader/async/async2valuetask.cs +++ b/src/tests/Loader/async/async2valuetask.cs @@ -19,8 +19,7 @@ private static ValueTask AsyncTestEntryPoint(int arg) return M1(arg); } - [ValueTaskAsyncAttribute] - private static async2 int M1(int arg) + private static async2 ValueTask M1(int arg) { await Task.Yield(); return arg; diff --git a/src/tests/Loader/async/async2void.cs b/src/tests/Loader/async/async2void.cs index b645fa6789d2f2..8271887731a110 100644 --- a/src/tests/Loader/async/async2void.cs +++ b/src/tests/Loader/async/async2void.cs @@ -22,7 +22,7 @@ private static async Task AsyncTestEntryPoint(int[] arr, int index) } [MethodImpl(MethodImplOptions.NoInlining)] - private static async2 void HoistedByref(int[] arr, int index) + private static async2 Task HoistedByref(int[] arr, int index) { for (int i = 0; i < 20000; i++) { diff --git a/src/tests/Loader/async/cse-array-index-byref.cs b/src/tests/Loader/async/cse-array-index-byref.cs index 321be16f18fd1f..be99722a054bbb 100644 --- a/src/tests/Loader/async/cse-array-index-byref.cs +++ b/src/tests/Loader/async/cse-array-index-byref.cs @@ -22,7 +22,7 @@ private static async Task AsyncTestEntryPoint(int[] arr, int index) } [MethodImpl(MethodImplOptions.NoInlining)] - private static async2 int HoistedByref(int[] arr, int index) + private static async2 Task HoistedByref(int[] arr, int index) { for (int i = 0; i < 20000; i++) { @@ -31,4 +31,4 @@ private static async2 int HoistedByref(int[] arr, int index) } return 0; } -} \ No newline at end of file +} diff --git a/src/tests/Loader/async/simple-eh.cs b/src/tests/Loader/async/simple-eh.cs index 06a72222bae2f9..8d70af66efc5b4 100644 --- a/src/tests/Loader/async/simple-eh.cs +++ b/src/tests/Loader/async/simple-eh.cs @@ -24,7 +24,7 @@ public static async Task AsyncEntry() Assert.Equal(42, result); } - public static async2 int Handler() + public static async2 Task Handler() { try { @@ -37,7 +37,7 @@ public static async2 int Handler() } [MethodImpl(MethodImplOptions.NoInlining)] - public static async2 int Throw(int value) + public static async2 Task Throw(int value) { await Task.Yield(); throw new IntegerException(value); From d9fbc669687a8518acc200dd59af177923e7eab4 Mon Sep 17 00:00:00 2001 From: Vladimir Sadov Date: Wed, 15 Nov 2023 01:36:28 +0000 Subject: [PATCH 080/203] Merged PR 35215: Writeup about span/byref and runtime-async --- docs/design/features/runtime-handled-tasks.md | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/docs/design/features/runtime-handled-tasks.md b/docs/design/features/runtime-handled-tasks.md index 5a9f3183383207..2b1163ba0e0397 100644 --- a/docs/design/features/runtime-handled-tasks.md +++ b/docs/design/features/runtime-handled-tasks.md @@ -557,3 +557,40 @@ Without many finally blocks, we can see that the performance is really extraordi 1. Serialization/Deserialization of JSON data to an asynchronous stream 2. ASP.NET servicing of requests where must of the pipeline is converted to the async2 model +## On supporting span/byref variables in runtime-assisted async code. + +It is not a secret that inability to use span and byref with async code causes some amount of grief. The async/span impedance in particular appears to be unnecessarily constraining. That is likely because span caters to a wider audience and thus use of span often intersects with async. A simple search brings up a lot of user concerns, workarounds, and asks to relax the constraint at least partially, if possible - + +https://stackoverflow.com/questions/57229123/how-to-use-spanbyte-in-async-method + +https://stackoverflow.com/questions/63284335/spant-and-async-methods + +[Span in async methods not supported · Issue #27147 · dotnet/roslyn (github.com)] (https://github.com/dotnet/roslyn/issues/27147) + +https://stackoverflow.com/questions/20868103/ref-and-out-arguments-in-async-method + +[Consider relaxing restrictions of async methods in blocks that do not contain `await`. · Issue 1331 · dotnet/csharplang (github.com)](https://github.com/dotnet/csharplang/issues/1331) Lots of discussions here, including insightful comments by Vance. + +The current situation is that C# does not allow byrefs and byref-likes in async methods in any form – be it parameters, locals or spans. The reason for this restriction is inability to capture byrefs as fields of display types. For consistency all use is forbidden, even if it does not require capture. +There are few cases were C# supports transient byrefs by the means of decomposing a byref-producing expression into constituent parts and capturing the parts and re-playing at use sites. Example `staticArray[i].Field += await Somehting();` That allows to handle a subset of cases, but codegen is far from great. + +In runtime-assisted async supporting span and byrefs is not a strict “no”, but there are implications. There are roughly two kinds of byrefs that async implementation would need to deal with. They come with distinct challenges. + +**Heap-referencing byrefs** are challenging because GC reporting of byrefs is only naturally supported on stack. Any alternative storage will have to implement a scheme for reporting stored variables for marking/updating purposes. The main challenge here is performance. As we expect the number of tasks to be easily in thousands at a time, O(n) algorithms inevitably result in GC pauses. There are ways to mitigate this, some of which were explored and measured as a part of this experiment. + +**Stack-referencing byrefs** do not have to be reported to GC, but must be adjusted when containing frame is reactivated as the referent is either in the current frame and thus now lives in a different stack location, or in one of still suspended caller frames and thus not on the stack at all. In the presence of “ref Span param” and similar, updating upon suspending is also necessary to keep chains of byrefs safely walkable. + +In general it would not be a tractable programming model from the user perspective, if we allow only one kind of byrefs and not another as distinction may often only be known at run time. +Note that the stack/heap-referencing nature of a byref cannot change while the frame is not active. However that is not completely true if “ref Span param” is allowed. In such case a calee may repoint a byref in a caller frame and switch from stack-pointing to heap-pointing. + +**Stack-referencing byrefs, that lead into sync callers** these are unsafe as the sync caller may exit any or all its stack frames before the Task is resumed. + +In the current experiment we have looked into two implementing strategies: + +**Stack unwinding with 1:1 stack capture for suspended frames.** In this model byrefs/spans that point to heap or other async callers can be supported as there is a mechanism to report byrefs to the GC. There are some performance challenges, but not without solutions. + +**State machine with managed storage for captured variables.** The current implementation does not support capturing byrefs and byref-likes. On the other hand it can utilize regular GC reporting mechanisms, which has many advantages, notably not having O(n) components that need to run in GC pauses. There are some ideas on how byrefs may be captured and converted into {object, offset} at the next GC, so that not to incur continuous O(n) costs. + +## On unmanaged pointers. +We cannot possibly track unmanaged pointers that refer to the stack across suspensions. Disallowing such scenarios would not be a take-back though since currently C# does not allow `await` in unsafe context, therefore await and unmanaged pointers cannot mix. + From 11f8bd26a08dd8be8f1375119ef98a2faab91c22 Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Thu, 16 Nov 2023 13:01:10 -0800 Subject: [PATCH 081/203] Update buildroslynnugets.cmd script to use public github details --- buildroslynnugets.cmd | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/buildroslynnugets.cmd b/buildroslynnugets.cmd index e097dccf488e87..7ae6eb9dca4101 100644 --- a/buildroslynnugets.cmd +++ b/buildroslynnugets.cmd @@ -1,14 +1,17 @@ setlocal ENABLEEXTENSIONS pushd %~dp0 - +set ASYNC_ROSLYN_COMMIT=046d22fad72ac726d36d93b0e9d186574e77207b set ASYNC_SUFFIX=async-7 +set ASYNC_ROSLYN_BRANCH=demos/async2-experiment cd .. -pushd dotnet-roslyn +if not exist async-roslyn-repo git clone -b %ASYNC_ROSLYN_BRANCH% -o async_roslyn_remote https://github.com/dotnet/runtimelab.git async-roslyn-repo + +pushd async-roslyn-repo -git fetch AzDo dev/vsadov/a2 +git fetch async_roslyn_remote %ASYNC_ROSLYN_BRANCH% rem when updating this, make sure to update the ASYNC_SUFFIX above and the versions.props file -git checkout 046d22fad72ac726d36d93b0e9d186574e77207b +git checkout %ASYNC_ROSLYN_COMMIT% call restore.cmd call build.cmd -c release From a6a45ed697a379118dd683d4c4e226126ecfc4d0 Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Thu, 16 Nov 2023 14:19:55 -0800 Subject: [PATCH 082/203] Add readme for async2 experiment --- README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/README.md b/README.md index 698c2da19e9534..8fb13d18b7e160 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,19 @@ +# .NET Runtime - Async2 Experiment + +This branch contains experimental fork of CoreCLR [.NET runtime](http://github.com/dotnet/runtime) where we explore implementing async directly in the runtime instead of generating state machines in the IL compiler (Roslyn). + +## Samples + +See the tests in src/tests/loader/async. + +## Documentation + +- Before building, run the buildroslynugets.cmd script, it will build a variant of the Roslyn compiler that can be used to test this codebase. Otherwise follow the standard developer workflow. +- [Async Experiment Issue](https://github.com/dotnet/runtimelab/issues/2398) +- [Design and details](docs\design\features\runtime-handled-tasks.md) + +--- + # .NET Runtime In order to build in this repo, you must have set up a Roslyn repo parallel to this repo with the name dotnet-roslyn, and it must have a remote called AzDo which has the updated compiler in it. Then you must run buildroslynnugets.cmd to create a local copy of the compiler for use in the repo. The equivalent work has not yet been done for running on Unix-like platforms. From 6fec72162e983861b335bc0d92497ba4839509f5 Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Thu, 16 Nov 2023 14:36:13 -0800 Subject: [PATCH 083/203] Follow instructions in the Create an experiment document --- .github/CODEOWNERS | 104 ------------------------------------------ Directory.Build.props | 2 +- eng/Versions.props | 2 +- 3 files changed, 2 insertions(+), 106 deletions(-) delete mode 100644 .github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS deleted file mode 100644 index ab341bf12b5d90..00000000000000 --- a/.github/CODEOWNERS +++ /dev/null @@ -1,104 +0,0 @@ -# Users referenced in this file will automatically be requested as reviewers for PRs that modify the given paths. -# See https://help.github.com/articles/about-code-owners/ - -/src/libraries/Common/src/Interop/ @dotnet/platform-deps-team -/src/libraries/Common/src/System/Net/Http/aspnetcore/ @dotnet/http -/src/libraries/Common/tests/Tests/System/Net/aspnetcore/ @dotnet/http - -# CoreCLR Code Owners - -/src/coreclr/inc/corinfo.h @dotnet/jit-contrib -/src/coreclr/inc/corjit.h @dotnet/jit-contrib -/src/coreclr/jit/ @dotnet/jit-contrib -/src/coreclr/nativeaot @MichalStrehovsky -/src/coreclr/tools/Common @dotnet/crossgen-contrib @MichalStrehovsky -/src/coreclr/tools/aot @dotnet/crossgen-contrib -/src/coreclr/tools/aot/ILCompiler.Compiler @MichalStrehovsky -/src/coreclr/tools/aot/ILCompiler.RyuJit @MichalStrehovsky -/src/coreclr/tools/aot/ILCompiler.MetadataTransform @MichalStrehovsky - -# Mono Code Owners - -/src/mono @marek-safar - -/src/mono/llvm @vargaz @SamMonoRT - -/src/mono/mono/arch @vargaz -/src/mono/mono/eglib @vargaz @lambdageek - -/src/mono/mono/metadata @vargaz @lambdageek @thaystg -/src/mono/mono/metadata/*-win* @lateralusX @lambdageek -/src/mono/mono/metadata/handle* @lambdageek @vargaz -/src/mono/mono/metadata/monitor* @brzvlad @vargaz -/src/mono/mono/metadata/sgen* @brzvlad @vargaz @lambdageek -/src/mono/mono/metadata/thread* @lateralusX @lambdageek -/src/mono/mono/metadata/w32* @lateralusX @lambdageek - -/src/mono/mono/eventpipe @lateralusX @lambdageek - -/src/mono/mono/mini @vargaz @lambdageek @SamMonoRT -/src/mono/mono/mini/*cfgdump* @vargaz -/src/mono/mono/mini/*exceptions* @vargaz @BrzVlad -/src/mono/mono/mini/*llvm* @vargaz @fanyang-mono -/src/mono/mono/mini/*ppc* @vargaz -/src/mono/mono/mini/*profiler* @BrzVlad @lambdageek -/src/mono/mono/mini/*riscv* @vargaz @lambdageek -/src/mono/mono/mini/*type-check* @lambdageek -/src/mono/mono/mini/debugger-agent.c @vargaz @thaystg @lambdageek -/src/mono/mono/mini/interp/* @BrzVlad @vargaz @kotlarmilos -/src/mono/mono/mini/interp/*jiterp* @kg -/src/mono/mono/mini/*simd* @fanyang-mono - -/src/mono/mono/profiler @BrzVlad @lambdageek -/src/mono/mono/sgen @BrzVlad @lambdageek @SamMonoRT - -/src/mono/mono/utils @vargaz @lambdageek -/src/mono/mono/utils/*-win* @lateralusX @lambdageek -/src/mono/mono/utils/atomic* @vargaz -/src/mono/mono/utils/mono-hwcap* @vargaz -/src/mono/mono/utils/mono-mem* @vargaz -/src/mono/mono/utils/mono-threads* @lambdageek @vargaz - -/src/mono/dlls @thaystg @lambdageek - -/src/native/public/mono @marek-safar @lambdageek - -/src/libraries/sendtohelix-wasm.targets @radical -/src/mono/wasm @lewing @pavelsavara @kg -/src/mono/wasm/debugger @thaystg @radical -/src/mono/wasm/build @radical -/src/mono/sample/wasm @lewing @pavelsavara -/src/libraries/System.Runtime.InteropServices.JavaScript @lewing @pavelsavara - -/src/mono/nuget/*WebAssembly*/ @lewing @radical -/src/mono/nuget/*MonoTargets*/ @lewing @radical -/src/mono/nuget/*BrowserDebugHost*/ @lewing @radical -/src/mono/nuget/*Workload.Mono.Toolchain*/ @lewing @radical -/src/mono/nuget/*MonoAOTCompiler*/ @lewing @radical - -/src/mono/wasm/Wasm* @radical -/src/mono/wasm/testassets @radical -/src/tasks/WasmAppBuilder/ @radical -/src/tasks/WorkloadBuildTasks/ @radical -/src/tasks/AotCompilerTask/ @radical -/src/tasks/WasmBuildTasks/ @radical - -/eng/pipelines/**/*wasm* @radical - -# ILLink codeowners -/src/tools/illink/ @marek-safar -/src/tools/illink/src/analyzer/ @radekdoulik -/src/tools/illink/src/ILLink.Tasks/ @sbomer -/src/tools/illink/src/ILLink.RoslynAnalyzer/ @sbomer -/src/tools/illink/src/linker/ @marek-safar @mrvoorhe -/src/tools/illink/test/ @marek-safar @mrvoorhe - -# Obsoletions / Custom Diagnostics - -/docs/project/list-of-diagnostics.md @jeffhandley -/src/libraries/Common/src/System/Obsoletions.cs @jeffhandley - -# Area ownership and repo automation -/docs/area-owners.* @jeffhandley -/docs/issue*.md @jeffhandley -/.github/fabricbot.json @jeffhandley diff --git a/Directory.Build.props b/Directory.Build.props index 3331de8a7f8d50..22e2a505531c03 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -336,7 +336,7 @@ - runtime + runtimelab https://github.com/dotnet/$(GitHubRepositoryName) https://dot.net microsoft,dotnetframework diff --git a/eng/Versions.props b/eng/Versions.props index 9b918bbd541c5c..cc9b47af989700 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -10,7 +10,7 @@ 8.0.0-rc.2.23469.22 7.0.8 6.0.$([MSBuild]::Add($([System.Version]::Parse('$(PackageVersionNet7)').Build),11)) - alpha + async 1 false From 9495c90d95b6c5f55008d918c0894c7c0c0b9190 Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Thu, 16 Nov 2023 14:39:44 -0800 Subject: [PATCH 084/203] Correct issue number for async2 work --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8fb13d18b7e160..68f5f910d0f693 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ See the tests in src/tests/loader/async. ## Documentation - Before building, run the buildroslynugets.cmd script, it will build a variant of the Roslyn compiler that can be used to test this codebase. Otherwise follow the standard developer workflow. -- [Async Experiment Issue](https://github.com/dotnet/runtimelab/issues/2398) +- [Async Experiment Issue](https://github.com/dotnet/runtimelab/issues/94620) - [Design and details](docs\design\features\runtime-handled-tasks.md) --- From 75319abcd7ab71f5d4a44ab8a8be68e8f56a0e75 Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Thu, 16 Nov 2023 14:58:30 -0800 Subject: [PATCH 085/203] Fix readme even more --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 68f5f910d0f693..1113fe689cf645 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,8 @@ See the tests in src/tests/loader/async. ## Documentation - Before building, run the buildroslynugets.cmd script, it will build a variant of the Roslyn compiler that can be used to test this codebase. Otherwise follow the standard developer workflow. -- [Async Experiment Issue](https://github.com/dotnet/runtimelab/issues/94620) -- [Design and details](docs\design\features\runtime-handled-tasks.md) +- [Async Experiment Issue](https://github.com/dotnet/runtime/issues/94620) +- [Design and details](docs/design/features/runtime-handled-tasks.md) --- From c4a2eb3cc10e11804e65d02e3b7ff1c276a2d7c1 Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Thu, 16 Nov 2023 15:47:43 -0800 Subject: [PATCH 086/203] Merged PR 35233: Add measurement of async2 logic with Execution and SynchronizationContext capture/restore (#2421) Add measurement of async2 logic with Execution and SynchronizationContext capture/restore - Attempt to determine if we could fully simulate the current async semantics while still achieving significant performance wins - TLDR: Yes, we can get a pretty substantial performance gain, but we lose somewhere between 20-30% of what we could gain --- ...-vs-async2capture-relative-performance.svg | 1 + ...-vs-async2capture-relative-performance.svg | 1 + docs/design/features/runtime-handled-tasks.md | 42 +++++-- .../Loader/async/async2-eh-microbench.cs | 117 ++++++++++++++++++ 4 files changed, 150 insertions(+), 11 deletions(-) create mode 100644 docs/design/features/async-vs-async2capture-relative-performance.svg create mode 100644 docs/design/features/async2-vs-async2capture-relative-performance.svg diff --git a/docs/design/features/async-vs-async2capture-relative-performance.svg b/docs/design/features/async-vs-async2capture-relative-performance.svg new file mode 100644 index 00000000000000..d9dee0be0a31da --- /dev/null +++ b/docs/design/features/async-vs-async2capture-relative-performance.svg @@ -0,0 +1 @@ +0.00%100.00%200.00%300.00%400.00%500.00%600.00%124816iters/s async2 with capture divided by iters/s Task base asyncStack DepthRelative performance of existing async vs runtime async with context captureReturn NoSuspendThrow NoSuspendReturn SuspendThrow Suspend \ No newline at end of file diff --git a/docs/design/features/async2-vs-async2capture-relative-performance.svg b/docs/design/features/async2-vs-async2capture-relative-performance.svg new file mode 100644 index 00000000000000..3143faacafe81a --- /dev/null +++ b/docs/design/features/async2-vs-async2capture-relative-performance.svg @@ -0,0 +1 @@ +0.00%20.00%40.00%60.00%80.00%100.00%120.00%124816iters/s async2capture divided by iters/s async2Stack DepthRelative performance of runtime async vs runtime async with context captureReturn NoSuspendThrow NoSuspendReturn SuspendThrow Suspend \ No newline at end of file diff --git a/docs/design/features/runtime-handled-tasks.md b/docs/design/features/runtime-handled-tasks.md index 2b1163ba0e0397..1f744e356d8bde 100644 --- a/docs/design/features/runtime-handled-tasks.md +++ b/docs/design/features/runtime-handled-tasks.md @@ -483,10 +483,11 @@ In addition, these experiments use the JIT focused implementation of `async2`. T These benchmarks are gathered by running the async2-eh-microbench in varying configurations. - NoSuspend vs Suspend - Suspend indicates that the async function awaits `Task.Yield()` before completing. - `Return` vs `Throw` - `Return` indicates that the iteration finishes by returning up the call stack via return instructions, and `Throw` indicates that each iteration terminated via a `throw new Exception()`. -- `Task` vs `ValueTask` vs `Async2` - The benchmark has 3 functions which are written with the same C# code, but with different syntax using different async implementation strategies. +- `Task` vs `ValueTask` vs `Async2` vs `Async2Capture` - The benchmark has 4 functions which are written with the same C# code, but with different syntax using different async implementation strategies. With `Task`, the function signature is `async Task RunTask(int depth)` With `ValueTask`, the function signature is `async ValueTask RunTask(int depth)` -With `Task`, the function signature is `async2 long RunTask(int depth)` +With `Async2`, the function signature is `async2 long RunTask(int depth)` +With `Async2Capture`, the function signature is `async2 long RunTask(int depth)`, and the body of the function contains a try/finally which saves/restores the Sync and ExecutionContext. This is intended to (imperfectly) measure the cost of adjusting the runtime async model to behave exactly like the existing async model. When a graph does not specify one of the config options, the graph is a relative performance gathered by dividing the iters/250ms that the benchmark produces between the two options, and then multiplying by 100 to produce a percentage. @@ -516,6 +517,25 @@ This higher performance is likely due to the relatively large size of the async In addition, in this graph, only data out to a depth of 16 is presented, as the performance of throwing with new async at higher stack depths would make looking at the data for other depths difficult. (It reaches a level of 3851% of the performance of the traditional async function at a stack depth of 64). Not shown is the performance of `ValueTask` based async code. It is extremely close to that of the `Task` based variant. +## Impact on performance of capturing/restoring ExecutionContext and SynchronizationContext + +![RelativePerfWithCapture](async2-vs-async2capture-relative-performance.svg) + +The Async2Capture variant of the testing is intended to show the impact that capturing/restoring `ExecutionContext` and `SynchronizationContext` would have on performance of the runtime generated async logic. +In this graph, taller bars are better, and if capture had no cost, they would all be expected to be at 100%. +The variant of code does not necessarily represent all of the costs involved in a real implementation, notably, on restore it does not attempt to ensure that the code resumes respecting the correct `SynchronizationContext` or `TaskScheduler` rules, but its probably fairly accurate for NoSuspend measurements. +In addition, in this data we can see that the cost of the try/finally results in performance unfortunately similar to that of the `async Task` based implementation in the "Throw Suspend" scenario. +This could likely be optimized to have performance similar to that of the higher performance runtime async model. (As the contents of the finally are well known, and would not require the entire EH machinery to be invoked.) + +As we can see from the graph, the performance impact on NoSuspend cases is between 20%-30% for this somewhat complex function. +This test also fails to measure the impact of losing the ability to inline small helper functions which was expected to be one of the significant wins in practice of the new async logic. + +![RelativePerfWithCapture2](async-vs-async2capture-relative-performance.svg) + +However, while this is a substantial performance loss relative to the full speed runtime async model, the overall performance of this model relative to our existing model still shows a nice performance win. +Looking at this graph, the performance win from a fully capturing model is not terrible. +We may wish to strongly consider implementing a variant of this where individual methods could choose to have capture or not. +This would allow the new model to be fully drop in, but customers could opt into higher performance/logic which can support inlining. ## Impact of try/finally regions on throw performance ![ThrowPerfDifferingFinallys](throw-perf-with-different-number-of-finally-on-stack.svg) @@ -533,15 +553,15 @@ Without many finally blocks, we can see that the performance is really extraordi 4. The performance of Task Throw NoSuspend and Task Throw Suspend is effectively the same. ## Raw data from EH microbenchmark -| Stack Depth | Task Return NoSuspend | ValueTask Return NoSuspend | Async2 Return NoSuspend | Task Throw NoSuspend | ValueTask Throw NoSuspend | Async2 Throw NoSuspend | Task Return Suspend | ValueTask Return Suspend | Async2 Return Suspend | Task Throw Suspend | ValueTask Throw Suspend | Async2 Throw Suspend | -| ----- | --------------------- | -------------------------- | ----------------------- | -------------------- | ------------------------- | ---------------------- | ------------------- | ------------------------ | --------------------- | ------------------ | ----------------------- | -------------------- | -| 1 | 7,976,995.00 | 7,994,790.00 | 13,966,110.00 | 21,630.00 | 21,789.00 | 46,434.00 | 589,057.00 | 574,660.00 | 629,310.00 | 15,563.00 | 15,584.00 | 21,208.00 | -| 2 | 4,738,359.00 | 5,036,613.00 | 11,573,294.00 | 13,781.00 | 13,359.00 | 38,975.00 | 390,167.00 | 349,900.00 | 617,464.00 | 9,806.00 | 9,740.00 | 21,514.00 | -| 4 | 2,884,153.00 | 2,924,123.00 | 8,340,047.00 | 7,717.00 | 7,477.00 | 29,887.00 | 276,584.00 | 265,590.00 | 577,707.00 | 5,778.00 | 5,495.00 | 21,288.00 | -| 8 | 1,274,335.00 | 1,324,960.00 | 5,308,395.00 | 4,068.00 | 4,001.00 | 20,358.00 | 217,067.00 | 198,172.00 | 474,723.00 | 3,476.00 | 3,376.00 | 20,877.00 | -| 16 | 536,064.00 | 533,731.00 | 2,888,828.00 | 2,104.00 | 2,063.00 | 12,941.00 | 132,636.00 | 106,605.00 | 315,191.00 | 1,912.00 | 1,909.00 | 20,496.00 | -| 32 | 263,823.00 | 249,821.00 | 1,157,832.00 | 1,032.00 | 989.00 | 7,319.00 | 71,989.00 | 55,860.00 | 184,699.00 | 973.00 | 949.00 | 19,379.00 | -| 64 | 113,187.00 | 114,845.00 | 488,245.00 | 484.00 | 473.00 | 3,936.00 | 37,086.00 | 28,365.00 | 98,769.00 | 458.00 | 456.00 | 17,565.00 | +| Stack Depth | Task Return NoSuspend | ValueTask Return NoSuspend | Async2 Return NoSuspend | Task Throw NoSuspend | ValueTask Throw NoSuspend | Async2 Throw NoSuspend | Task Return Suspend | ValueTask Return Suspend | Async2 Return Suspend | Task Throw Suspend | ValueTask Throw Suspend | Async2 Throw Suspend | Async2Capture Return NoSuspend | Async2Capture Throw NoSuspend | Async2Capture Return Suspend | Async2Capture Throw Suspend | +| ----- | --------------------- | -------------------------- | ----------------------- | -------------------- | ------------------------- | ---------------------- | ------------------- | ------------------------ | --------------------- | ------------------ | ----------------------- | -------------------- | --------------------- | ------------------ | ----------------------- | -------------------- | +| 1 | 7,976,995.00 | 7,994,790.00 | 13,966,110.00 | 21,630.00 | 21,789.00 | 46,434.00 | 589,057.00 | 574,660.00 | 629,310.00 | 15,563.00 | 15,584.00 | 21,208.00 | 12249438.00 | 45002.00 | 624336.00 | 21005.00 | +| 2 | 4,738,359.00 | 5,036,613.00 | 11,573,294.00 | 13,781.00 | 13,359.00 | 38,975.00 | 390,167.00 | 349,900.00 | 617,464.00 | 9,806.00 | 9,740.00 | 21,514.00 | 9306011.00 | 37769.00 | 594730.00 | 13163.00 | +| 4 | 2,884,153.00 | 2,924,123.00 | 8,340,047.00 | 7,717.00 | 7,477.00 | 29,887.00 | 276,584.00 | 265,590.00 | 577,707.00 | 5,778.00 | 5,495.00 | 21,288.00 | 6438143.00 | 28318.00 | 543293.00 | 7253.00 | +| 8 | 1,274,335.00 | 1,324,960.00 | 5,308,395.00 | 4,068.00 | 4,001.00 | 20,358.00 | 217,067.00 | 198,172.00 | 474,723.00 | 3,476.00 | 3,376.00 | 20,877.00 | 3836147.00 | 18871.00 | 412340.00 | 4160.00 | +| 16 | 536,064.00 | 533,731.00 | 2,888,828.00 | 2,104.00 | 2,063.00 | 12,941.00 | 132,636.00 | 106,605.00 | 315,191.00 | 1,912.00 | 1,909.00 | 20,496.00 | 2083270.00 | 11978.00 | 275944.00 | 2234.00 | +| 32 | 263,823.00 | 249,821.00 | 1,157,832.00 | 1,032.00 | 989.00 | 7,319.00 | 71,989.00 | 55,860.00 | 184,699.00 | 973.00 | 949.00 | 19,379.00 | 861826.00 | 6705.00 | 156610.00 | 1133.00 | +| 64 | 113,187.00 | 114,845.00 | 488,245.00 | 484.00 | 473.00 | 3,936.00 | 37,086.00 | 28,365.00 | 98,769.00 | 458.00 | 456.00 | 17,565.00 | 393380.00 | 3654.00 | 83977.00 | 593.00 | | Finally blocks on stack | Task Throw NoSuspend | Async2 Throw NoSuspend | Task Throw Suspend | Async2 Throw Suspend | | ----------------------- | -------------------- | ---------------------- | ------------------ | -------------------- | diff --git a/src/tests/Loader/async/async2-eh-microbench.cs b/src/tests/Loader/async/async2-eh-microbench.cs index ab4beb0e182b0d..b60d6a9b327201 100644 --- a/src/tests/Loader/async/async2-eh-microbench.cs +++ b/src/tests/Loader/async/async2-eh-microbench.cs @@ -50,6 +50,7 @@ public static async Task AsyncEntry() for (int j = 0; j < 40; j++) { await warmupBm.Run("Async2"); + await warmupBm.Run("Async2WithContextSaveRestore"); await warmupBm.Run("Task"); await warmupBm.Run("ValueTask"); } @@ -60,6 +61,7 @@ public static async Task AsyncEntry() Console.WriteLine("Warmup done, running benchmark"); await RunBench(yieldFrequency, depth, finallyRate, throwOrReturn, "Async2"); + await RunBench(yieldFrequency, depth, finallyRate, throwOrReturn, "Async2WithContextSaveRestore"); await RunBench(yieldFrequency, depth, finallyRate, throwOrReturn, "Task"); await RunBench(yieldFrequency, depth, finallyRate, throwOrReturn, "ValueTask"); } @@ -104,6 +106,8 @@ public async2 Task Run(string type) { if (type == "Async2") return await RunAsync2(_depth); + if (type == "Async2WithContextSaveRestore") + return await RunAsync2WithContextSaveRestore(_depth); if (type == "Task") return await RunTask(_depth); if (type == "ValueTask") @@ -242,6 +246,119 @@ public async ValueTask RunValueTask(int depth) return result; } + public class FakeSyncContext {} + public class FakeExecContext + { + [MethodImpl(MethodImplOptions.NoInlining)] + public static void RestoreChangedContextToThread(FakeThread thread, FakeExecContext execContext, FakeExecContext newExecContext) + { + } + } + public class FakeThread + { + public FakeSyncContext _syncContext; + public FakeExecContext _execContext; + } + [ThreadStatic] + public static FakeThread CurrentThread; + + // This case is used to test the impact of save/restore of the sync and execution context on performance + // The intent here is to measure what the performance impact of maintaining the current async semantics with + // the new implementation. + public async2 Task RunAsync2WithContextSaveRestore(int depth) + { + FakeThread thread = CurrentThread; + if (thread == null) + { + CurrentThread = new FakeThread(); + thread = CurrentThread; + } + + FakeExecContext? previousExecutionCtx = thread._execContext; + FakeSyncContext? previousSyncCtx = thread._syncContext; + + try + { + int liveState1 = depth * 3 + _yieldCount; + int liveState2 = depth; + double liveState3 = _yieldCount; + + if (depth == 0) + { + int currentAwaitCount = 0; + + while (currentAwaitCount < _yieldCount) + { + currentAwaitCount++; + await Task.Yield(); + } + + if (_throwOrReturn) + throw new Exception(); + return 8375983; + } + + long result = 0; + + if (depth == _depth) + { + int time = Warmup ? 5 : 250; + + Stopwatch timer = Stopwatch.StartNew(); + + long numIters = 0; + + while (timer.ElapsedMilliseconds < time) + { + try + { + result = await RunAsync2WithContextSaveRestore(depth - 1); + } + catch (Exception e) + { + + } + numIters++; + + } + return numIters; + } + else if ((depth % _finallyRate) == 0) + { + try + { + result = await RunAsync2WithContextSaveRestore(depth - 1); + } + finally + { + Sink = (int)liveState1 + (int)liveState2 + (int)(1 / liveState3) + depth; + } + } + else + { + result = await RunAsync2WithContextSaveRestore(depth - 1); + } + + Sink = (int)liveState1 + (int)liveState2 + (int)(1 / liveState3) + depth; + return result; + } + finally + { + // The common case is that these have not changed, so avoid the cost of a write barrier if not needed. + if (previousSyncCtx != thread._syncContext) + { + // Restore changed SynchronizationContext back to previous + thread._syncContext = previousSyncCtx; + } + + FakeExecContext? currentExecutionCtx = thread._execContext; + if (previousExecutionCtx != currentExecutionCtx) + { + FakeExecContext.RestoreChangedContextToThread(thread, previousExecutionCtx, currentExecutionCtx); + } + } + } + public async2 Task RunAsync2(int depth) { int liveState1 = depth * 3 + _yieldCount; From 6b47db0c8dbb545c969a0630bb88453724ec52d4 Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Thu, 16 Nov 2023 15:50:46 -0800 Subject: [PATCH 087/203] Fix the roslyn build script (#2422) --- buildroslynnugets.cmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildroslynnugets.cmd b/buildroslynnugets.cmd index 7ae6eb9dca4101..45e868567d6af6 100644 --- a/buildroslynnugets.cmd +++ b/buildroslynnugets.cmd @@ -5,7 +5,7 @@ set ASYNC_SUFFIX=async-7 set ASYNC_ROSLYN_BRANCH=demos/async2-experiment cd .. -if not exist async-roslyn-repo git clone -b %ASYNC_ROSLYN_BRANCH% -o async_roslyn_remote https://github.com/dotnet/runtimelab.git async-roslyn-repo +if not exist async-roslyn-repo git clone -b %ASYNC_ROSLYN_BRANCH% -o async_roslyn_remote https://github.com/dotnet/roslyn.git async-roslyn-repo pushd async-roslyn-repo From 842ad04659eb79350c818bdb75f2e09b5040fa78 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Fri, 17 Nov 2023 13:59:33 +0100 Subject: [PATCH 088/203] Null out TLS head continuation reference Avoid permanently holding on to a reference to the continuation chain from the previous suspension in TLS. --- .../System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs index 32d0b0c3f60f28..1dd6834175d8db 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs @@ -466,7 +466,9 @@ private static Continuation GetHeadContinuation(AwaitableProxy awaitableProxy) { ref RuntimeAsyncAwaitState state = ref t_runtimeAsyncAwaitState; awaitableProxy._notifier = state.Notifier; - return state.SentinelContinuation!.Next!; + Continuation head = state.SentinelContinuation!.Next!; + state.SentinelContinuation.Next = null; + return head; } private static async Task FinalizeTaskReturningThunk(Continuation continuation) From 282218b02b2ecab37c6de61880f8090c094fe9f1 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Fri, 17 Nov 2023 14:11:43 +0100 Subject: [PATCH 089/203] Manual CSE --- .../Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs index 1dd6834175d8db..b22a4b1445b07b 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs @@ -466,8 +466,9 @@ private static Continuation GetHeadContinuation(AwaitableProxy awaitableProxy) { ref RuntimeAsyncAwaitState state = ref t_runtimeAsyncAwaitState; awaitableProxy._notifier = state.Notifier; - Continuation head = state.SentinelContinuation!.Next!; - state.SentinelContinuation.Next = null; + Continuation sentinelContinuation = state.SentinelContinuation!; + Continuation head = sentinelContinuation.Next!; + sentinelContinuation.Next = null; return head; } From 5e9973bc5b639a9131de3a4b7a702c061b9556fa Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Fri, 17 Nov 2023 19:13:18 +0100 Subject: [PATCH 090/203] Fix paths in buildroslynnugets.cmd (#2426) --- buildroslynnugets.cmd | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/buildroslynnugets.cmd b/buildroslynnugets.cmd index 45e868567d6af6..1051eda3506e04 100644 --- a/buildroslynnugets.cmd +++ b/buildroslynnugets.cmd @@ -26,9 +26,9 @@ pushd %~dp0 md roslynpackages -copy ..\dotnet-roslyn\artifacts\packages\Release\Shipping\Microsoft.Net.Compilers.Toolset.4.9.0-%ASYNC_SUFFIX%.nupkg roslynpackages -copy ..\dotnet-roslyn\artifacts\packages\Release\Shipping\Microsoft.CodeAnalysis.Workspaces.Common.4.9.0-%ASYNC_SUFFIX%.nupkg roslynpackages -copy ..\dotnet-roslyn\artifacts\packages\Release\Shipping\Microsoft.CodeAnalysis.CSharp.Workspaces.4.9.0-%ASYNC_SUFFIX%.nupkg roslynpackages -copy ..\dotnet-roslyn\artifacts\packages\Release\Shipping\Microsoft.CodeAnalysis.CSharp.4.9.0-%ASYNC_SUFFIX%.nupkg roslynpackages -copy ..\dotnet-roslyn\artifacts\packages\Release\Shipping\Microsoft.CodeAnalysis.Common.4.9.0-%ASYNC_SUFFIX%.nupkg roslynpackages +copy ..\async-roslyn-repo\artifacts\packages\Release\Shipping\Microsoft.Net.Compilers.Toolset.4.9.0-%ASYNC_SUFFIX%.nupkg roslynpackages +copy ..\async-roslyn-repo\artifacts\packages\Release\Shipping\Microsoft.CodeAnalysis.Workspaces.Common.4.9.0-%ASYNC_SUFFIX%.nupkg roslynpackages +copy ..\async-roslyn-repo\artifacts\packages\Release\Shipping\Microsoft.CodeAnalysis.CSharp.Workspaces.4.9.0-%ASYNC_SUFFIX%.nupkg roslynpackages +copy ..\async-roslyn-repo\artifacts\packages\Release\Shipping\Microsoft.CodeAnalysis.CSharp.4.9.0-%ASYNC_SUFFIX%.nupkg roslynpackages +copy ..\async-roslyn-repo\artifacts\packages\Release\Shipping\Microsoft.CodeAnalysis.Common.4.9.0-%ASYNC_SUFFIX%.nupkg roslynpackages From 0b1105a8381ab11c46d7147e9ee57c48c94a4449 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Fri, 17 Nov 2023 19:15:05 +0100 Subject: [PATCH 091/203] Switch to JIT prototype by default; add support matrix (#2427) --- README.md | 18 +++++++++++++++++- docs/design/features/runtime-handled-tasks.md | 4 ++++ src/coreclr/inc/clrconfigvalues.h | 2 +- src/coreclr/jit/jitconfigvalues.h | 2 +- 4 files changed, 23 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 1113fe689cf645..ca549b195ed4ee 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,25 @@ This branch contains experimental fork of CoreCLR [.NET runtime](http://github.com/dotnet/runtime) where we explore implementing async directly in the runtime instead of generating state machines in the IL compiler (Roslyn). +## Current status + +We support two prototypes: a JIT-based codegen prototype, and an unwinder-only based prototype. +The prototypes are described in detail in the design document, see below. +The former prototype is the default; the latter can be switched to by setting `DOTNET_RuntimeAsyncViaJitGeneratedStateMachines=0`. + +Current support in the prototypes looks like the following: + +| **Feature/characteristic** | **JIT based state machines** | **Unwinder based state machines** | +|------------------------------------------|:------------------------------:|:-----------------------------------:| +| **Generics** | ❌ | ❌ | +| **Byrefs live across suspension points** | ❌ | ✅ | +| **Exception handling** | ✅ | ❌ | +| **Returns via return buffers** | ✅ | ❌ | + + ## Samples -See the tests in src/tests/loader/async. +See the tests in [src/tests/Loader/async](src/tests/Loader/async). ## Documentation diff --git a/docs/design/features/runtime-handled-tasks.md b/docs/design/features/runtime-handled-tasks.md index 1f744e356d8bde..63e529b66512b7 100644 --- a/docs/design/features/runtime-handled-tasks.md +++ b/docs/design/features/runtime-handled-tasks.md @@ -153,6 +153,8 @@ Async2 methods will not be visible in reflection via the `Type.GetMethod`, `Type The general design is to leverage the notion that a normal code generated function at a function call point is effectively a state machine. The index for resumption is the IP that returning to the function will set, and the stackframe + saved registers are the current state of the state machine. I believe we can achieve performance comparable to synchronous code with this scheme for non-suspended scenarios, and we may achieve acceptable overall performance as an async mechanism if suspension is relatively rare. +To enable this prototype, set `DOTNET_RuntimeAsyncViaJitGeneratedStateMachines=0`. + ### JIT Code changes 1. Do not allow InlinedCallFrames to be linked to the frame chain across a suspend (Or disable p/invoke inlining in these methods) 2. Report frame pointers as ByRef @@ -227,6 +229,8 @@ In the second prototype we leave it up to the JIT to generate the state machines When awaiting an async2 function from within an async2 function the JIT generates suspension code to capture the live state in a continuation. In addition, the JIT generates code for each of these suspension points to be able to resume from the continuation that was created. +This prototype is enabled by default, or when `DOTNET_RuntimeAsyncViaJitGeneratedStateMachines=1`. + ### Suspension Communicating suspension is done by returning a non-zero continuation, using a special calling convention. diff --git a/src/coreclr/inc/clrconfigvalues.h b/src/coreclr/inc/clrconfigvalues.h index 1ee66824c6a233..58fca99ce959e0 100644 --- a/src/coreclr/inc/clrconfigvalues.h +++ b/src/coreclr/inc/clrconfigvalues.h @@ -803,7 +803,7 @@ RETAIL_CONFIG_DWORD_INFO(EXTERNAL_EnableArm64Rcpc2, W("EnableArm64Rc /// /// Runtime async /// -RETAIL_CONFIG_DWORD_INFO(EXTERNAL_RuntimeAsyncViaJitGeneratedStateMachines, W("RuntimeAsyncViaJitGeneratedStateMachines"), 0, "Use JIT generated state machines instead of unwinding-based runtime async") +RETAIL_CONFIG_DWORD_INFO(EXTERNAL_RuntimeAsyncViaJitGeneratedStateMachines, W("RuntimeAsyncViaJitGeneratedStateMachines"), 1, "Use JIT generated state machines instead of unwinding-based runtime async") /// /// Uncategorized diff --git a/src/coreclr/jit/jitconfigvalues.h b/src/coreclr/jit/jitconfigvalues.h index 1cdd96279a9deb..df90f3bebcea6b 100644 --- a/src/coreclr/jit/jitconfigvalues.h +++ b/src/coreclr/jit/jitconfigvalues.h @@ -654,7 +654,7 @@ CONFIG_INTEGER(JitEnableHeadTailMerge, W("JitEnableHeadTailMerge"), 1) CONFIG_INTEGER(JitEnablePhysicalPromotion, W("JitEnablePhysicalPromotion"), 1) // Should JIT generate state machines for async2 methods? -CONFIG_INTEGER(RuntimeAsyncViaJitGeneratedStateMachines, W("RuntimeAsyncViaJitGeneratedStateMachines"), 0) +CONFIG_INTEGER(RuntimeAsyncViaJitGeneratedStateMachines, W("RuntimeAsyncViaJitGeneratedStateMachines"), 1) #if defined(DEBUG) // JitFunctionFile: Name of a file that contains a list of functions. If the currently compiled function is in the From fc15ad6fccd7772767b2c01eafed399286beb05a Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Fri, 17 Nov 2023 21:28:42 +0100 Subject: [PATCH 092/203] Rename GetHeadContinuation -> UnlinkHeadContinuation --- .../Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs index b22a4b1445b07b..dbf462c1b6e2ac 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs @@ -462,7 +462,7 @@ public void UnsafeOnCompleted(Action action) public void GetResult() {} } - private static Continuation GetHeadContinuation(AwaitableProxy awaitableProxy) + private static Continuation UnlinkHeadContinuation(AwaitableProxy awaitableProxy) { ref RuntimeAsyncAwaitState state = ref t_runtimeAsyncAwaitState; awaitableProxy._notifier = state.Notifier; @@ -496,7 +496,7 @@ private static async Task FinalizeTaskReturningThunk(Continuation continua while (true) { - Continuation headContinuation = GetHeadContinuation(awaitableProxy); + Continuation headContinuation = UnlinkHeadContinuation(awaitableProxy); await awaitableProxy; Continuation? finalResult = DispatchContinuations(headContinuation, out Exception? ex); if (finalResult != null) @@ -529,7 +529,7 @@ private static async Task FinalizeTaskReturningThunk(Continuation continuation) while (true) { - Continuation headContinuation = GetHeadContinuation(awaitableProxy); + Continuation headContinuation = UnlinkHeadContinuation(awaitableProxy); await awaitableProxy; Continuation? finalResult = DispatchContinuations(headContinuation, out Exception? ex); if (finalResult != null) @@ -566,7 +566,7 @@ private static async ValueTask FinalizeValueTaskReturningThunk(Continuatio while (true) { - Continuation headContinuation = GetHeadContinuation(awaitableProxy); + Continuation headContinuation = UnlinkHeadContinuation(awaitableProxy); await awaitableProxy; Continuation? finalResult = DispatchContinuations(headContinuation, out Exception? ex); if (finalResult != null) @@ -599,7 +599,7 @@ private static async ValueTask FinalizeValueTaskReturningThunk(Continuation cont while (true) { - Continuation headContinuation = GetHeadContinuation(awaitableProxy); + Continuation headContinuation = UnlinkHeadContinuation(awaitableProxy); await awaitableProxy; Continuation? finalResult = DispatchContinuations(headContinuation, out Exception? ex); if (finalResult != null) From ba3bbb0eda878e98bd5893029945fc52bd05ab87 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Tue, 28 Nov 2023 20:17:09 +0100 Subject: [PATCH 093/203] Fix resumption stub target in the presence of backpatching (#2428) The slot the tier 0 method gets is backpatched to point to the tier 1 method, so allocate a new slot for the target address to ensure that resumption stubs always resume in the right native code version. Also fix the size used when storing the state field; we were storing 8 bytes, but the field is only 4 bytes (this wouldn't cause any ill effects as the flags field comes right after). --- src/coreclr/jit/async.cpp | 2 +- src/coreclr/vm/jitinterface.cpp | 20 +++++++++++++++++++- src/coreclr/vm/jitinterface.h | 4 ++++ 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/coreclr/jit/async.cpp b/src/coreclr/jit/async.cpp index bb2bc7b2e981e4..2837421957aec9 100644 --- a/src/coreclr/jit/async.cpp +++ b/src/coreclr/jit/async.cpp @@ -579,7 +579,7 @@ void Async2Transformation::Transform( // Fill in 'state' newContinuation = m_comp->gtNewLclvNode(m_newContinuationVar, TYP_REF); unsigned stateOffset = m_comp->info.compCompHnd->getFieldOffset(m_async2Info.continuationStateFldHnd); - GenTree* stateNumNode = m_comp->gtNewIconNode((ssize_t)stateNum, TYP_I_IMPL); + GenTree* stateNumNode = m_comp->gtNewIconNode((ssize_t)stateNum, TYP_INT); GenTree* storeState = StoreAtOffset(newContinuation, stateOffset, stateNumNode); LIR::AsRange(retBB).InsertAtEnd(LIR::SeqTree(m_comp, storeState)); diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index fbe44befa6223c..b759fb90876813 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -10786,6 +10786,16 @@ void CEEJitInfo::WriteCodeBytes() } } +void CEEJitInfo::PublishFinalCodeAddress(PCODE addr) +{ + LIMITED_METHOD_CONTRACT; + + if (m_finalCodeAddressSlot != NULL) + { + *m_finalCodeAddressSlot = addr; + } +} + /*********************************************************************/ void CEEJitInfo::BackoutJitData(EEJitManager * jitMgr) { @@ -13082,6 +13092,8 @@ PCODE UnsafeJitFunction(PrepareCodeConfig* config, ret |= THUMB_CODE; #endif + jitInfo.PublishFinalCodeAddress(ret); + // We are done break; } @@ -14376,7 +14388,13 @@ CORINFO_METHOD_HANDLE CEEJitInfo::getAsyncResumptionStub() } else { - pCode->EmitLDC((DWORD_PTR)config->GetNativeCodeSlot()); + { + AllocMemTracker pamTracker; + m_finalCodeAddressSlot = (PCODE*)pamTracker.Track(m_pMethodBeingCompiled->GetLoaderAllocator()->GetHighFrequencyHeap()->AllocMem(S_SIZE_T(sizeof(PCODE)))); + pamTracker.SuppressRelease(); + } + + pCode->EmitLDC((DWORD_PTR)m_finalCodeAddressSlot); pCode->EmitLDIND_I(); } diff --git a/src/coreclr/vm/jitinterface.h b/src/coreclr/vm/jitinterface.h index 8cf2ab64f1f833..939712b234d255 100644 --- a/src/coreclr/vm/jitinterface.h +++ b/src/coreclr/vm/jitinterface.h @@ -818,6 +818,7 @@ class CEEJitInfo : public CEEInfo m_pPatchpointInfoFromRuntime(NULL), m_ilOffset(0), #endif + m_finalCodeAddressSlot(NULL), m_gphCache() { CONTRACTL @@ -901,6 +902,8 @@ class CEEJitInfo : public CEEInfo void WriteCode(EEJitManager * jitMgr); + void PublishFinalCodeAddress(PCODE addr); + void setPatchpointInfo(PatchpointInfo* patchpointInfo) override final; PatchpointInfo* getOSRInfo(unsigned* ilOffset) override final; @@ -979,6 +982,7 @@ protected : PatchpointInfo * m_pPatchpointInfoFromRuntime; unsigned m_ilOffset; #endif + PCODE* m_finalCodeAddressSlot; // 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 From a691996e923bf47233940eaf9f14148c29e0e56f Mon Sep 17 00:00:00 2001 From: Vladimir Sadov Date: Thu, 30 Nov 2023 07:07:36 -0800 Subject: [PATCH 094/203] [Async-2] A few small tweaks on the managed side of state-machine based implementation. (#2431) * A few tweaks - made AwaitableProxy a struct - unlink Notifier - use ExceptionDispatchInfo to rethrow * rethrow from DispatchContinuations --- .../RuntimeHelpers.CoreCLR.cs | 60 ++++++++----------- 1 file changed, 24 insertions(+), 36 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs index dbf462c1b6e2ac..c26260014f54b0 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs @@ -5,6 +5,7 @@ using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Runtime.CompilerServices; +using System.Runtime.ExceptionServices; using System.Runtime.InteropServices; using System.Runtime.Serialization; using System.Runtime.Versioning; @@ -434,9 +435,14 @@ private struct RuntimeAsyncAwaitState [Intrinsic] private static void SuspendAsync2(Continuation continuation) => throw new UnreachableException(); - private sealed class AwaitableProxy : ICriticalNotifyCompletion + private struct AwaitableProxy : ICriticalNotifyCompletion { - public INotifyCompletion? _notifier; + private readonly INotifyCompletion _notifier; + + public AwaitableProxy(INotifyCompletion notifier) + { + _notifier = notifier; + } public bool IsCompleted => false; @@ -462,10 +468,12 @@ public void UnsafeOnCompleted(Action action) public void GetResult() {} } - private static Continuation UnlinkHeadContinuation(AwaitableProxy awaitableProxy) + private static Continuation UnlinkHeadContinuation(out AwaitableProxy awaitableProxy) { ref RuntimeAsyncAwaitState state = ref t_runtimeAsyncAwaitState; - awaitableProxy._notifier = state.Notifier; + awaitableProxy = new AwaitableProxy(state.Notifier!); + state.Notifier = null; + Continuation sentinelContinuation = state.SentinelContinuation!; Continuation head = sentinelContinuation.Next!; sentinelContinuation.Next = null; @@ -492,19 +500,14 @@ private static async Task FinalizeTaskReturningThunk(Continuation continua continuation.Next = finalContinuation; - AwaitableProxy awaitableProxy = new AwaitableProxy(); - while (true) { - Continuation headContinuation = UnlinkHeadContinuation(awaitableProxy); + Continuation headContinuation = UnlinkHeadContinuation(out var awaitableProxy); await awaitableProxy; - Continuation? finalResult = DispatchContinuations(headContinuation, out Exception? ex); + Continuation? finalResult = DispatchContinuations(headContinuation); if (finalResult != null) { Debug.Assert(finalResult == finalContinuation); - if (ex != null) - throw ex; - if (IsReferenceOrContainsReferences()) { return (T)finalResult.GCData![0]; @@ -525,18 +528,14 @@ private static async Task FinalizeTaskReturningThunk(Continuation continuation) }; continuation.Next = finalContinuation; - AwaitableProxy awaitableProxy = new AwaitableProxy(); - while (true) { - Continuation headContinuation = UnlinkHeadContinuation(awaitableProxy); + Continuation headContinuation = UnlinkHeadContinuation(out var awaitableProxy); await awaitableProxy; - Continuation? finalResult = DispatchContinuations(headContinuation, out Exception? ex); + Continuation? finalResult = DispatchContinuations(headContinuation); if (finalResult != null) { Debug.Assert(finalResult == finalContinuation); - if (ex != null) - throw ex; return; } } @@ -562,19 +561,14 @@ private static async ValueTask FinalizeValueTaskReturningThunk(Continuatio continuation.Next = finalContinuation; - AwaitableProxy awaitableProxy = new AwaitableProxy(); - while (true) { - Continuation headContinuation = UnlinkHeadContinuation(awaitableProxy); + Continuation headContinuation = UnlinkHeadContinuation(out var awaitableProxy); await awaitableProxy; - Continuation? finalResult = DispatchContinuations(headContinuation, out Exception? ex); + Continuation? finalResult = DispatchContinuations(headContinuation); if (finalResult != null) { Debug.Assert(finalResult == finalContinuation); - if (ex != null) - throw ex; - if (IsReferenceOrContainsReferences()) { return (T)finalResult.GCData![0]; @@ -595,33 +589,28 @@ private static async ValueTask FinalizeValueTaskReturningThunk(Continuation cont }; continuation.Next = finalContinuation; - AwaitableProxy awaitableProxy = new AwaitableProxy(); - while (true) { - Continuation headContinuation = UnlinkHeadContinuation(awaitableProxy); + Continuation headContinuation = UnlinkHeadContinuation(out var awaitableProxy); await awaitableProxy; - Continuation? finalResult = DispatchContinuations(headContinuation, out Exception? ex); + Continuation? finalResult = DispatchContinuations(headContinuation); if (finalResult != null) { Debug.Assert(finalResult == finalContinuation); - if (ex != null) - throw ex; return; } } } // Return a continuation object if that is the one which has the final - // result of the Task, in this case exceptionResult MAY be set to an - // exception, which is the real output of the series of continuations + // result of the Task, if the real output of the series of continuations was + // an exception, it is allowed to propagate out. // OR // return NULL to indicate that this isn't yet done. - private static unsafe Continuation? DispatchContinuations(Continuation? continuation, out Exception? exceptionResult) + private static unsafe Continuation? DispatchContinuations(Continuation? continuation) { Debug.Assert(continuation != null); - exceptionResult = null; while (true) { Continuation? newContinuation; @@ -634,8 +623,7 @@ private static async ValueTask FinalizeValueTaskReturningThunk(Continuation cont continuation = UnwindToPossibleHandler(continuation); if (continuation.Resume == null) { - exceptionResult = ex; - return continuation; + throw; } continuation.GCData![(continuation.Flags & CorInfoContinuationFlags.CORINFO_CONTINUATION_RESULT_IN_GCDATA) != 0 ? 1 : 0] = ex; From 5205ef3ee4741e2c217d58804d0f1a60e863b662 Mon Sep 17 00:00:00 2001 From: Vladimir Sadov Date: Tue, 12 Dec 2023 11:32:32 -0800 Subject: [PATCH 095/203] [Async-2]: Tasklet aging (#2425) * tests for the root scan * root scan extra API * aging works * stack lists * age by whole stacks * switch test to new syntax * Updated the test * testcase update * writeup on GC pauses and allocated size --- docs/design/features/runtime-handled-tasks.md | 52 ++++ src/coreclr/gc/env/gcenv.ee.h | 4 + .../gc/env/gctoeeinterface.standalone.inl | 10 + src/coreclr/gc/gcenv.ee.standalone.inl | 12 + src/coreclr/gc/gcinterface.ee.h | 6 + src/coreclr/gc/gcscan.cpp | 15 +- src/coreclr/gc/sample/gcenv.ee.cpp | 8 + src/coreclr/inc/clrconfigvalues.h | 1 + src/coreclr/nativeaot/Runtime/gcrhenv.cpp | 8 + src/coreclr/vm/eeconfig.cpp | 1 + src/coreclr/vm/eeconfig.h | 2 + src/coreclr/vm/gcenv.ee.cpp | 26 +- src/coreclr/vm/qcallentrypoints.cpp | 1 + src/coreclr/vm/runtimesuspension.cpp | 234 +++++++++++++++--- src/coreclr/vm/runtimesuspension.h | 6 +- .../CompilerServices/RuntimeHelpers.cs | 30 ++- src/tests/Loader/async/async2gcRootsScan.cs | 211 ++++++++++++++++ .../Loader/async/async2gcRootsScan.csproj | 8 + 18 files changed, 597 insertions(+), 38 deletions(-) create mode 100644 src/tests/Loader/async/async2gcRootsScan.cs create mode 100644 src/tests/Loader/async/async2gcRootsScan.csproj diff --git a/docs/design/features/runtime-handled-tasks.md b/docs/design/features/runtime-handled-tasks.md index 63e529b66512b7..1352da07d81402 100644 --- a/docs/design/features/runtime-handled-tasks.md +++ b/docs/design/features/runtime-handled-tasks.md @@ -618,3 +618,55 @@ In the current experiment we have looked into two implementing strategies: ## On unmanaged pointers. We cannot possibly track unmanaged pointers that refer to the stack across suspensions. Disallowing such scenarios would not be a take-back though since currently C# does not allow `await` in unsafe context, therefore await and unmanaged pointers cannot mix. +## Impact on GC (heap size, pauses). + +We have examined the behavior of experimental async2 implementations on GC. For simplicity we looked into behavior of a simple benchmark that ensures 10000 suspended tasks at some point, each containing 100 stack frames with at least 1 object and 1 integer alive across await and thus requiring capture. At the time the tasks are suspended, the benchmark forces a number of Gen0 GCs and measures average time taken by GCs as well as the managed heap size. +To further reduce the number of involved variables, the benchmark is single-threaded and Workstation GC was used. + +The benchmark produces very little garbage when the masurements are performed as the main interest is not on how GC handles allocations, but on how the presence of captured tasks impacts GC. + +Our observations (on x64, AMD 5950X): + +**===== Async1**: + +There is some noise, but a typical Gen0 pause in the above scenario was: 120 microseconds. +The managed heap size (as reported by `GetGCMemoryInfo.HeapSizeBytes`) is stable at 130 Mb. +The peak working set (as reported by `Task Manager`) is 148 Mb. + +**=====Async2 (unwinding/stack-capturing based)**: + +When GC pauses were measured as-is, the results were very disappointing - **48690 microsecond**. (**~400x worse** than async1 and generally unacceptable for a Gen0 pause) + +The reasons for such timings is that the implementation would go through every one of the 1000000 tasklets and report referenced objects as roots. That is time consuming and is also very redundant when GC generational model is considered. Since suspended stacks can't see assignments of new objects, once GC performs en-masse promotion, all the roots owned by these tasks become too old to be of interest for collections that target younger generations until the containing task is resumed. +This is a unique challenge of stack-capturing implementation. In async1 suspended tasks are regular heap objects and do not require any special reporting in GC STW phases. + +As a solution to this issue, we implemented tasklet aging mechanism, similar to approach used by GC handles. We make note of GC promotions, and increase "min age" of suspended tasks, so that, for example, tasks that only refer to tenured objects do not need to report roots in ephemeral collections. + +The taklet aging was an enormous optimization and the pause time in the above benchmark went down to **480 microseconds**. That is **100x improvement** and is not too bad, considering that this is close to the worst case scenario. +It was still **4x worse** than async1. + +Once tasks know their age, root reporting is no longer the dominant factor, but we still need to check age of 10000 tasks, even if the answer is "too old". A further improvement is possible by grouping the suspended tasks in groups, so that age of entire group could be examined. + +For the memory consumption, the managed heap is reported to be very small - **28Mb**. Unsurprisingly, since this approach uses native/malloc memory to store captured data. +However, the peak working set was seen at **499Mb** that is **3.3x worse** than async1. + +The reason for higher memory consumption is that this approach captures the entire stack frame and its registers, which is less eficient than async1, which, based on data-flow analysis, may capture only variables that are live across awaits. + +**=====Async2 (JIT state-machine based)**: + +The pause times were observed at **100 microseconds**, which is **better than in async1** implementation. +Since the root set would be roughly the same as in async1, the difference could be just second-order effects of different heap graph or different distribution of object sizes. + +The peak working set was seen at 328Mb. +That is higher than async1 and most likely just additional transient allocations in the JIT. As the benchmark has just a few methods, it cannot be method bodies, but may need some follow up to confirm. + +Managed allocation impact in this model depends on JIT optimizations as the capture is based on liveness information, which is not available in tier-0 compiled methods. That is expected and could be acceptable as hot methods would not stay in tier-0. Besides it is possible to enable liveness analysis in tier-0 async2 methods, if needed, at about 5% of the estimated JIT cost. +It is also possible to improve liveness analysis/information in async2 methods in general - to further improve capturing efficiency. + +With `set DOTNET_TieredCompilation=0` +Managed heap size: **157Mb** - slightly more than async1 + +With `set DOTNET_TieredCompilation=1` +Managed heap size: **reaches 300Mb** in the first couple iterations, but then drops and **stays at 105Mb**, which is better than async1. + + \ No newline at end of file diff --git a/src/coreclr/gc/env/gcenv.ee.h b/src/coreclr/gc/env/gcenv.ee.h index 8c949492fa1baa..d9b3c470168a1e 100644 --- a/src/coreclr/gc/env/gcenv.ee.h +++ b/src/coreclr/gc/env/gcenv.ee.h @@ -45,6 +45,10 @@ class GCToEEInterface static void SyncBlockCachePromotionsGranted(int max_gen); static uint32_t GetActiveSyncBlockCount(); + // Tasklet management + static void TaskletPromotionsGranted(int condemned, int max_gen, ScanContext* sc); + static void TaskletDemote(int condemned, int max_gen, ScanContext* sc); + // Thread functions static bool IsPreemptiveGCDisabled(); static bool EnablePreemptiveGC(); diff --git a/src/coreclr/gc/env/gctoeeinterface.standalone.inl b/src/coreclr/gc/env/gctoeeinterface.standalone.inl index 0b72f3b7269873..e2e1a920109300 100644 --- a/src/coreclr/gc/env/gctoeeinterface.standalone.inl +++ b/src/coreclr/gc/env/gctoeeinterface.standalone.inl @@ -64,6 +64,16 @@ namespace standalone ::GCToEEInterface::SyncBlockCachePromotionsGranted(max_gen); } + void TaskletPromotionsGranted(int condemned, int max_gen, ScanContext* sc) + { + ::GCToEEInterface::TaskletPromotionsGranted(condemned, max_gen, sc); + } + + void TaskletDemote(int condemned, int max_gen, ScanContext* sc) + { + ::GCToEEInterface::TaskletDemote(condemned, max_gen, sc); + } + uint32_t GetActiveSyncBlockCount() { return ::GCToEEInterface::GetActiveSyncBlockCount(); diff --git a/src/coreclr/gc/gcenv.ee.standalone.inl b/src/coreclr/gc/gcenv.ee.standalone.inl index 1751e069c3e43a..e1cb52bf584a7e 100644 --- a/src/coreclr/gc/gcenv.ee.standalone.inl +++ b/src/coreclr/gc/gcenv.ee.standalone.inl @@ -90,6 +90,18 @@ inline void GCToEEInterface::SyncBlockCachePromotionsGranted(int max_gen) g_theGCToCLR->SyncBlockCachePromotionsGranted(max_gen); } +inline void GCToEEInterface::TaskletPromotionsGranted(int condemned, int max_gen, ScanContext* sc) +{ + assert(g_theGCToCLR != nullptr); + g_theGCToCLR->TaskletPromotionsGranted(condemned, max_gen, sc); +} + +inline void GCToEEInterface::TaskletDemote(int condemned, int max_gen, ScanContext* sc) +{ + assert(g_theGCToCLR != nullptr); + g_theGCToCLR->TaskletDemote(condemned, max_gen, sc); +} + inline uint32_t GCToEEInterface::GetActiveSyncBlockCount() { assert(g_theGCToCLR != nullptr); diff --git a/src/coreclr/gc/gcinterface.ee.h b/src/coreclr/gc/gcinterface.ee.h index dbc7e8ac483836..8bad4311146136 100644 --- a/src/coreclr/gc/gcinterface.ee.h +++ b/src/coreclr/gc/gcinterface.ee.h @@ -251,6 +251,12 @@ class IGCToCLR { virtual void SyncBlockCachePromotionsGranted(int max_gen) PURE_VIRTUAL + virtual + void TaskletPromotionsGranted(int condemned, int max_gen, ScanContext* sc) PURE_VIRTUAL + + virtual + void TaskletDemote(int condemned, int max_gen, ScanContext* sc) PURE_VIRTUAL + virtual uint32_t GetActiveSyncBlockCount() PURE_VIRTUAL diff --git a/src/coreclr/gc/gcscan.cpp b/src/coreclr/gc/gcscan.cpp index 92ed7f3a868706..87dcc6fd928493 100644 --- a/src/coreclr/gc/gcscan.cpp +++ b/src/coreclr/gc/gcscan.cpp @@ -222,16 +222,29 @@ void GCScan::GcDemote (int condemned, int max_gen, ScanContext* sc) { Ref_RejuvenateHandles (condemned, max_gen, sc); if (!IsServerHeap() || sc->thread_number == 0) + { GCToEEInterface::SyncBlockCacheDemote(max_gen); + + // TODO: we should partition tasklet lists by the core number on server GC + // and do this on multiple threads. + // then "sc" argument will be used. + GCToEEInterface::TaskletDemote(condemned, max_gen, sc); + } } void GCScan::GcPromotionsGranted (int condemned, int max_gen, ScanContext* sc) { Ref_AgeHandles(condemned, max_gen, sc); if (!IsServerHeap() || sc->thread_number == 0) + { GCToEEInterface::SyncBlockCachePromotionsGranted(max_gen); -} + // TODO: we should partition tasklet lists by the core number on server GC + // and do this on multiple threads. + // then "sc" argument will be used. + GCToEEInterface::TaskletPromotionsGranted(condemned, max_gen, sc); + } +} size_t GCScan::AskForMoreReservedMemory (size_t old_size, size_t need_size) { diff --git a/src/coreclr/gc/sample/gcenv.ee.cpp b/src/coreclr/gc/sample/gcenv.ee.cpp index 5a29ee63ed7066..66e0dd4f91b51d 100644 --- a/src/coreclr/gc/sample/gcenv.ee.cpp +++ b/src/coreclr/gc/sample/gcenv.ee.cpp @@ -225,6 +225,14 @@ void GCToEEInterface::SyncBlockCachePromotionsGranted(int /*max_gen*/) { } +void GCToEEInterface::TaskletPromotionsGranted(int /*condemned*/, int /*max_gen*/, ScanContext* /*sc*/) +{ +} + +void GCToEEInterface::TaskletDemote(int /*condemned*/, int /*max_gen*/, ScanContext* /*sc*/) +{ +} + void GCToEEInterface::DiagGCStart(int gen, bool isInduced) { } diff --git a/src/coreclr/inc/clrconfigvalues.h b/src/coreclr/inc/clrconfigvalues.h index 58fca99ce959e0..6792df9ede5f0f 100644 --- a/src/coreclr/inc/clrconfigvalues.h +++ b/src/coreclr/inc/clrconfigvalues.h @@ -804,6 +804,7 @@ RETAIL_CONFIG_DWORD_INFO(EXTERNAL_EnableArm64Rcpc2, W("EnableArm64Rc /// Runtime async /// RETAIL_CONFIG_DWORD_INFO(EXTERNAL_RuntimeAsyncViaJitGeneratedStateMachines, W("RuntimeAsyncViaJitGeneratedStateMachines"), 1, "Use JIT generated state machines instead of unwinding-based runtime async") +RETAIL_CONFIG_DWORD_INFO(EXTERNAL_TaskletAging, W("TaskletAging"), 1, "Enable tasklet aging when tasklet based async is used") /// /// Uncategorized diff --git a/src/coreclr/nativeaot/Runtime/gcrhenv.cpp b/src/coreclr/nativeaot/Runtime/gcrhenv.cpp index 6fa14327affef7..780d3d5e60ba5d 100644 --- a/src/coreclr/nativeaot/Runtime/gcrhenv.cpp +++ b/src/coreclr/nativeaot/Runtime/gcrhenv.cpp @@ -654,6 +654,14 @@ void GCToEEInterface::SyncBlockCachePromotionsGranted(int /*max_gen*/) { } +void GCToEEInterface::TaskletPromotionsGranted(int /*condemned*/, int /*max_gen*/, ScanContext* /*sc*/) +{ +} + +void GCToEEInterface::TaskletDemote(int /*condemned*/, int /*max_gen*/, ScanContext* /*sc*/) +{ +} + uint32_t GCToEEInterface::GetActiveSyncBlockCount() { return 0; diff --git a/src/coreclr/vm/eeconfig.cpp b/src/coreclr/vm/eeconfig.cpp index b268ead119785c..c2e2904555296d 100644 --- a/src/coreclr/vm/eeconfig.cpp +++ b/src/coreclr/vm/eeconfig.cpp @@ -817,6 +817,7 @@ HRESULT EEConfig::sync() backpatchEntryPointSlots = CLRConfig::GetConfigValue(CLRConfig::UNSUPPORTED_BackpatchEntryPointSlots) != 0; runtimeAsyncViaJitGeneratedStateMachines = CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_RuntimeAsyncViaJitGeneratedStateMachines) != 0; + taskletAging = CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_TaskletAging) != 0; #if defined(FEATURE_GDBJIT) && defined(_DEBUG) { diff --git a/src/coreclr/vm/eeconfig.h b/src/coreclr/vm/eeconfig.h index bfe7a2a28aa956..b66d7eb7609fe9 100644 --- a/src/coreclr/vm/eeconfig.h +++ b/src/coreclr/vm/eeconfig.h @@ -115,6 +115,7 @@ class EEConfig bool BackpatchEntryPointSlots() const { LIMITED_METHOD_CONTRACT; return backpatchEntryPointSlots; } bool RuntimeAsyncViaJitGeneratedStateMachines() const { LIMITED_METHOD_CONTRACT; return runtimeAsyncViaJitGeneratedStateMachines; } + bool TaskletAging() const { LIMITED_METHOD_CONTRACT; return taskletAging; } #if defined(FEATURE_GDBJIT) && defined(_DEBUG) inline bool ShouldDumpElfOnMethod(LPCUTF8 methodName) const @@ -689,6 +690,7 @@ class EEConfig #endif bool runtimeAsyncViaJitGeneratedStateMachines; + bool taskletAging; public: enum BitForMask { diff --git a/src/coreclr/vm/gcenv.ee.cpp b/src/coreclr/vm/gcenv.ee.cpp index 108ac06eaad801..5518b72f8fade9 100644 --- a/src/coreclr/vm/gcenv.ee.cpp +++ b/src/coreclr/vm/gcenv.ee.cpp @@ -328,7 +328,7 @@ void GCToEEInterface::GcScanRoots(promote_func* fn, int condemned, int max_gen, #ifndef DACCESS_COMPILE // TODO Make tasklet reporting DAC friendly - IterateTaskletsForGC(fn, sc); + IterateTaskletsForGC(fn, condemned, sc); #endif } @@ -426,6 +426,30 @@ void GCToEEInterface::SyncBlockCachePromotionsGranted(int max_gen) SyncBlockCache::GetSyncBlockCache()->GCDone(FALSE, max_gen); } +void GCToEEInterface::TaskletPromotionsGranted(int condemned, int max_gen, ScanContext* sc) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + AgeTasklets(condemned, max_gen, sc); +} + +void GCToEEInterface::TaskletDemote(int condemned, int max_gen, ScanContext* sc) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + RejuvenateTasklets(condemned, max_gen, sc); +} + uint32_t GCToEEInterface::GetActiveSyncBlockCount() { CONTRACTL diff --git a/src/coreclr/vm/qcallentrypoints.cpp b/src/coreclr/vm/qcallentrypoints.cpp index 8f5df385e1b17c..7f224344ce965e 100644 --- a/src/coreclr/vm/qcallentrypoints.cpp +++ b/src/coreclr/vm/qcallentrypoints.cpp @@ -80,6 +80,7 @@ static const Entry s_QCall[] = { + DllImportEntry(RuntimeSuspension_RegisterTasklet) DllImportEntry(RuntimeSuspension_DeleteTasklet) DllImportEntry(RuntimeSuspension_CaptureTasklets) DllImportEntry(Enum_GetValuesAndNames) diff --git a/src/coreclr/vm/runtimesuspension.cpp b/src/coreclr/vm/runtimesuspension.cpp index 577ee0b04f12df..da440b97d013f2 100644 --- a/src/coreclr/vm/runtimesuspension.cpp +++ b/src/coreclr/vm/runtimesuspension.cpp @@ -75,6 +75,10 @@ struct Tasklet uintptr_t restoreIPAddress; StackDataInfo* pStackDataInfo; TaskletReturnType taskletReturnType; + // min generation of all managed objects referred from this frame. + // -1 means the frame is a part of actiely executing stack and may have byrefs pointing to it + int32_t minGeneration; + Tasklet* pTaskletPrevInStack; }; struct RuntimeAsyncReturnValue @@ -136,20 +140,31 @@ extern "C" RegRestore* PlatformIndependentRestore(Tasklet* tasklet, RuntimeAsync uint8_t* pNewLocation = restoreLocals->GetFutureRSPLocation() + tasklet->pStackDataInfo->UnrecordedDataSize; adjustment.Adjustment = ((uintptr_t)pNewLocation) - ((uintptr_t)adjustment.pOldLocation); - // Adjust all pointers to the stack data that we are about to move, both in this frame, and in the caller frames + // Adjust all pointers to the stack data that we are about to move. + // NB: We only check refs from the current frame. + // If we have references from the caller frames, something has gone horribly bad already. Tasklet *pTaskletToAdjustByrefsOn = tasklet; - do + uint32_t cByRefs = pTaskletToAdjustByrefsOn->pStackDataInfo->cByRefs; + uint32_t* byRefOffsets = (uint32_t*)pTaskletToAdjustByrefsOn->pStackDataInfo->ByRefOffsets; + uint8_t* taskletData = pTaskletToAdjustByrefsOn->pStackData; + for (uint32_t iByRef = 0; iByRef < cByRefs; iByRef++) { - uint32_t cByRefs = pTaskletToAdjustByrefsOn->pStackDataInfo->cByRefs; - uint32_t* byRefOffsets = (uint32_t*)pTaskletToAdjustByrefsOn->pStackDataInfo->ByRefOffsets; - uint8_t* taskletData = pTaskletToAdjustByrefsOn->pStackData; - for (uint32_t iByRef = 0; iByRef < cByRefs; iByRef++) + RelocAtAddress(&adjustment, taskletData + byRefOffsets[iByRef]); + } + + // Mark stacklets as "active" so they no longer age. + // this is only needed as long as we allow cross-frame byrefs + // as such byrefs may allow a calee to chage min age of a caller. + if (tasklet->minGeneration >= 0 && tasklet->pStackDataInfo->cByRefs > 0) + { + Tasklet* pTaskletToMarkActive = tasklet; + do { - RelocAtAddress(&adjustment, taskletData + byRefOffsets[iByRef]); - } - pTaskletToAdjustByrefsOn = pTaskletToAdjustByrefsOn->pTaskletNextInStack; - } while (pTaskletToAdjustByrefsOn != NULL); - + pTaskletToMarkActive->minGeneration = -1; + pTaskletToMarkActive = pTaskletToMarkActive->pTaskletNextInStack; + } while (pTaskletToMarkActive != NULL); + } + StackDataInfo *pStackDataInfo = tasklet->pStackDataInfo; // Copy most of the memory @@ -525,7 +540,6 @@ PORTABILIT_ASSERT() pTasklet->restoreIPAddress = (uintptr_t)GetControlPC(pCf->GetRegisterSet()); pTasklet->pStackDataInfo = pStackDataInfo; pTasklet->taskletReturnType = taskletReturnType; - RegisterTasklet(pTasklet); if (taskletCaptureData->firstTasklet == NULL) { @@ -535,6 +549,7 @@ PORTABILIT_ASSERT() else { taskletCaptureData->lastTasklet->pTaskletNextInStack = pTasklet; + pTasklet->pTaskletPrevInStack = taskletCaptureData->lastTasklet; taskletCaptureData->lastTasklet = pTasklet; } taskletCaptureData->framesCaptured++; @@ -569,13 +584,29 @@ extern "C" Tasklet* QCALLTYPE RuntimeSuspension_CaptureTasklets(QCall::StackCraw extern "C" void QCALLTYPE RuntimeSuspension_DeleteTasklet(Tasklet* tasklet) { - UnregisterTasklet(tasklet); + if (tasklet->pTaskletNextInStack == NULL) + { + UnregisterTasklet(tasklet); + } + else + { + tasklet->pTaskletNextInStack->pTaskletPrevInStack = NULL; + } + tasklet->pStackDataInfo->CleanupStackDataInfo(); free(tasklet->pStackData); free(tasklet->pStackDataInfo); free(tasklet); } +extern "C" void QCALLTYPE RuntimeSuspension_RegisterTasklet(Tasklet * tasklet) +{ + // only heads of stack chains are registered. + _ASSERTE(tasklet->pTaskletNextInStack == NULL); + + RegisterTasklet(tasklet); +} + void RegisterTasklet(Tasklet* pTasklet); void InitializeTasklets(); void UnregisterTasklet(Tasklet* pTasklet); @@ -611,35 +642,174 @@ void UnregisterTasklet(Tasklet* pTasklet) pTasklet->pTaskletNextInLiveList->pTaskletPrevInLiveList = pTasklet->pTaskletPrevInLiveList; } -void IterateTaskletsForGC(promote_func* pCallback, ScanContext* sc) +void IterateTaskletsForGC(promote_func* pCallback, int condemned, ScanContext* sc) { CrstHolder crstHolder(&g_taskletCrst); - Tasklet *pCurTasklet = g_pTaskletSentinel->pTaskletNextInLiveList; - while (pCurTasklet != g_pTaskletSentinel) + for (Tasklet* pCurStack = g_pTaskletSentinel->pTaskletNextInLiveList; + pCurStack != g_pTaskletSentinel; + pCurStack = pCurStack->pTaskletNextInLiveList) { - // Report GC pointers - auto pStackDataInfo = pCurTasklet->pStackDataInfo; - uint8_t *pLogicalRSP = pCurTasklet->pStackData - pStackDataInfo->UnrecordedDataSize; - uint32_t iRef; - for (iRef = 0; iRef < pStackDataInfo->cObjectRefs; iRef++) + if (pCurStack->minGeneration > condemned) { - pCallback((PTR_PTR_Object)(pLogicalRSP + pStackDataInfo->ObjectRefOffsets[iRef]), sc, 0); - } - - for (iRef = 0; iRef < pStackDataInfo->cByRefs; iRef++) + // this stack is too old to be interesting in this GC + continue; + }; + + Tasklet* pCurTasklet = pCurStack; + do { - int32_t offset = pStackDataInfo->ByRefOffsets[iRef]; - uint32_t flags = GC_CALL_INTERIOR; - if (offset < 0) + // because of rejuvenation, some tasklets could be older than the head + // skip them if too old. + if (pCurTasklet->minGeneration > condemned) + { + // this stack is too old to be interesting in this GC + continue; + }; + + // Report GC pointers + auto pStackDataInfo = pCurTasklet->pStackDataInfo; + uint8_t *pLogicalRSP = pCurTasklet->pStackData - pStackDataInfo->UnrecordedDataSize; + uint32_t iRef; + for (iRef = 0; iRef < pStackDataInfo->cObjectRefs; iRef++) { - offset = -offset; - flags |= GC_CALL_PINNED; + pCallback((PTR_PTR_Object)(pLogicalRSP + pStackDataInfo->ObjectRefOffsets[iRef]), sc, 0); } - pCallback((PTR_PTR_Object)(pLogicalRSP + offset), sc, flags); + for (iRef = 0; iRef < pStackDataInfo->cByRefs; iRef++) + { + int32_t offset = pStackDataInfo->ByRefOffsets[iRef]; + uint32_t flags = GC_CALL_INTERIOR; + if (offset < 0) + { + offset = -offset; + flags |= GC_CALL_PINNED; + } + + pCallback((PTR_PTR_Object)(pLogicalRSP + offset), sc, flags); + } + + pCurTasklet = pCurTasklet->pTaskletPrevInStack; + } while (pCurTasklet != NULL); + } +} + +void AgeTasklets(int condemned, int max_gen, ScanContext* sc) +{ + if (!g_pConfig->TaskletAging()) + { + return; + } + + CrstHolder crstHolder(&g_taskletCrst); + for (Tasklet* pCurStack = g_pTaskletSentinel->pTaskletNextInLiveList; pCurStack != g_pTaskletSentinel; pCurStack = pCurStack->pTaskletNextInLiveList) + { + if (pCurStack->minGeneration > condemned) + { + // this stack is too old to be interesting in this GC + continue; + }; + + if (pCurStack->minGeneration < 0) + { + // this stack is active, do not age it + continue; + } + + Tasklet* pCurTasklet = pCurStack; + do + { + // actually age the tasklet + pCurTasklet->minGeneration = min(pCurTasklet->minGeneration + 1, max_gen); + + pCurTasklet = pCurTasklet->pTaskletPrevInStack; + } while (pCurTasklet != NULL); + } +} + +void RejuvenateTasklets(int condemned, int max_gen, ScanContext* sc) +{ + if (!g_pConfig->TaskletAging()) + { + return; + } + + CrstHolder crstHolder(&g_taskletCrst); + for (Tasklet* pCurStack = g_pTaskletSentinel->pTaskletNextInLiveList; + pCurStack != g_pTaskletSentinel; + pCurStack = pCurStack->pTaskletNextInLiveList) + { + if (pCurStack->minGeneration <= 0) + { + // this stack is as young as it can be + continue; + }; + + if (pCurStack->minGeneration > condemned) + { + // this tasklet is too old to be interesting in this GC + continue; } - pCurTasklet = pCurTasklet->pTaskletNextInLiveList; + Tasklet* pCurTasklet = pCurStack; + do + { + // update the minGeneration + auto pStackDataInfo = pCurTasklet->pStackDataInfo; + uint8_t* pLogicalRSP = pCurTasklet->pStackData - pStackDataInfo->UnrecordedDataSize; + uint32_t iRef; + for (iRef = 0; iRef < pStackDataInfo->cObjectRefs; iRef++) + { + auto ppObj = (PTR_PTR_Object)(pLogicalRSP + pStackDataInfo->ObjectRefOffsets[iRef]); + if (*ppObj != NULL) + { + int objGen = (INT32)GCHeapUtilities::GetGCHeap()->WhichGeneration(*ppObj); + if (objGen < pCurTasklet->minGeneration) + { + pCurTasklet->minGeneration = objGen; + if (objGen == 0) + { + // we are as young as we can be + goto nextTaskLet; + } + } + } + } + + for (iRef = 0; iRef < pStackDataInfo->cByRefs; iRef++) + { + int32_t offset = pStackDataInfo->ByRefOffsets[iRef]; + if (offset < 0) + { + offset = -offset; + } + + auto ppObj = (PTR_PTR_Object)(pLogicalRSP + offset); + int objGen = (INT32)GCHeapUtilities::GetGCHeap()->WhichGeneration(*ppObj); + if (*ppObj != NULL) + { + if (objGen < pCurTasklet->minGeneration) + { + pCurTasklet->minGeneration = objGen; + if (objGen == 0) + { + // we are as young as we can be + goto nextTaskLet; + } + } + } + } + + nextTaskLet: + + // make sure the head tasklet is no older than any tasklets in the list + // because we will use the age of the head for short-circuiting + if (pCurStack->minGeneration > pCurTasklet->minGeneration) + { + pCurStack->minGeneration = pCurTasklet->minGeneration; + } + + pCurTasklet = pCurTasklet->pTaskletPrevInStack; + } while (pCurTasklet != NULL); } } diff --git a/src/coreclr/vm/runtimesuspension.h b/src/coreclr/vm/runtimesuspension.h index 4f9b23f01d65a7..f864ca2e3bc1ef 100644 --- a/src/coreclr/vm/runtimesuspension.h +++ b/src/coreclr/vm/runtimesuspension.h @@ -6,6 +6,7 @@ struct AsyncDataFrame; struct RuntimeAsyncReturnValue; extern "C" Tasklet* QCALLTYPE RuntimeSuspension_CaptureTasklets(QCall::StackCrawlMarkHandle stackMark, uint8_t* returnValue, uint8_t useReturnValueHandle, void* taskAsyncData, Tasklet** lastTasklet, int32_t* pFramesCaptured); +extern "C" void QCALLTYPE RuntimeSuspension_RegisterTasklet(Tasklet * tasklet); extern "C" void QCALLTYPE RuntimeSuspension_DeleteTasklet(Tasklet* tasklet); EXTERN_C FCDECL1(void, RuntimeSuspension_UnwindToFunctionWithAsyncFrame, AsyncDataFrame* frame); @@ -16,4 +17,7 @@ EXTERN_C FCDECL2(Object*, RuntimeSuspension_ResumeTaskletIntegerRegisterReturn, void RegisterTasklet(Tasklet* pTasklet); void InitializeTasklets(); void UnregisterTasklet(Tasklet* pTasklet); -void IterateTaskletsForGC(promote_func* pCallback, ScanContext* sc); + +void IterateTaskletsForGC(promote_func* pCallback, int condemned, ScanContext* sc); +void AgeTasklets(int condemned, int max_gen, ScanContext* sc); +void RejuvenateTasklets(int condemned, int max_gen, ScanContext* sc); diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs index bfd71c17d0fd07..f4bfa35e89bd5a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs @@ -243,7 +243,6 @@ internal class RuntimeAsyncMaintainedData public byte _dummy; public unsafe Tasklet* _nextTasklet; - public unsafe Tasklet* _oldTaskletNext; public RuntimeAsyncReturnValue _retValue; public virtual ref byte GetReturnPointer() { return ref _dummy; } @@ -451,6 +450,8 @@ internal unsafe struct Tasklet public IntPtr restoreIPAddress; public StackDataInfo* pStackDataInfo; public TaskletReturnType taskletReturnType; + public int minGeneration; + public Tasklet* pTaskletPrevInStack; public int GetMaxStackNeeded() { return pStackDataInfo->StackRequirement; } } @@ -491,6 +492,9 @@ private static unsafe ref AsyncDataFrame GetCurrentAsyncDataFrame() [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "RuntimeSuspension_DeleteTasklet")] private static unsafe partial void DeleteTasklet(Tasklet* tasklet); + [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "RuntimeSuspension_RegisterTasklet")] + private static unsafe partial void RegisterTasklet(Tasklet* tasklet); + [MethodImpl(MethodImplOptions.InternalCall)] internal static extern unsafe void UnwindToFunctionWithAsyncFrame(Tasklet* topTasklet, nint framesToUnwind); @@ -508,8 +512,28 @@ private static unsafe bool DoSetupAndCapture(ref StackCrawlMark stackMark) if (nextTaskletInStack == null) throw new OutOfMemoryException(); - maintainedData._oldTaskletNext = maintainedData._nextTasklet; - lastTasklet->pTaskletNextInStack = maintainedData._nextTasklet; + if (maintainedData._nextTasklet == null) + { + RegisterTasklet(lastTasklet); + } + else + { + maintainedData._nextTasklet->pTaskletPrevInStack = lastTasklet; + lastTasklet->pTaskletNextInStack = maintainedData._nextTasklet; + + // we are suspending, so change the age of tasklets from -1 (active) to 0 (very young) + // NOTE: this is only needed as long as we allow cross-frame byrefs as + // that forces us to make age of active tasklets undefined (see comment in PlatformIndependentRestore) + // without such byrefs old tasklets could stay old as nothing could change locals of a captured frame. + for (Tasklet* current = maintainedData._nextTasklet; current != null; current = current->pTaskletNextInStack) + { + if (current->minGeneration == -1) + { + current->minGeneration = 0; + } + } + } + maintainedData._nextTasklet = nextTaskletInStack; maintainedData._retValue = default(RuntimeAsyncReturnValue); diff --git a/src/tests/Loader/async/async2gcRootsScan.cs b/src/tests/Loader/async/async2gcRootsScan.cs new file mode 100644 index 00000000000000..f703b88c149d58 --- /dev/null +++ b/src/tests/Loader/async/async2gcRootsScan.cs @@ -0,0 +1,211 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Threading.Tasks; +using System.Diagnostics; +using Xunit; + +public class Async2RootReporting +{ + private static TaskCompletionSource cs; + + + static async Task Recursive1(int n) + { + Task cTask = cs.Task; + + // make some garbage + object o1 = n; + //object o2 = n; + //object o3 = n; + //object o4 = n; + //object o5 = n; + //object o6 = n; + //object o7 = n; + //object o8 = n; + //object o9 = n; + //object o10 = n; + //object o11 = n; + //object o12 = n; + + if (n == 0) + return await cTask; + + var result = await Recursive1(n - 1); + + Assert.Equal(n, (int)o1); + //Assert.Equal(n, (int)o2); + //Assert.Equal(n, (int)o3); + //Assert.Equal(n, (int)o4); + //Assert.Equal(n, (int)o5); + //Assert.Equal(n, (int)o6); + //Assert.Equal(n, (int)o7); + //Assert.Equal(n, (int)o8); + //Assert.Equal(n, (int)o9); + //Assert.Equal(n, (int)o10); + //Assert.Equal(n, (int)o11); + //Assert.Equal(n, (int)o12); + + return result; + } + + static async2 Task Recursive2(int n) + { + Task cTask = cs.Task; + + // make some garbage + object o1 = n; + +// THIS CAUSES CRASHES IN UNWINDER FLAVOR + //object o2 = n; + //object o3 = n; + //object o4 = n; + //object o5 = n; + //object o6 = n; + //object o7 = n; + //object o8 = n; + //object o9 = n; + //object o10 = n; + //object o11 = n; + //object o12 = n; + + if (n == 0) + return await cTask; + + var result = await Recursive2(n - 1); + + Assert.Equal(n, (int)o1); + //Assert.Equal(n, (int)o2); + //Assert.Equal(n, (int)o3); + //Assert.Equal(n, (int)o4); + //Assert.Equal(n, (int)o5); + //Assert.Equal(n, (int)o6); + //Assert.Equal(n, (int)o7); + //Assert.Equal(n, (int)o8); + //Assert.Equal(n, (int)o9); + //Assert.Equal(n, (int)o10); + //Assert.Equal(n, (int)o11); + //Assert.Equal(n, (int)o12); + + return result; + } + + static System.Collections.Generic.List d = new System.Collections.Generic.List(); + + [Fact] + public static void Test() + { + int numStacks = 10000; + int stackDepth = 100; +#if DEBUG + int numGCs = 30; +#else + int numGCs = 500; +#endif + + void Warmup() + { + for (int k = 0; k < 10000; k++) + d.Add(new int[k % 100]); + + d = null; + + for (int k = 0; k < 10; k++) + { + cs = new TaskCompletionSource(); + Task[] tasks = new Task[numStacks]; + for (int i = 0; i < tasks.Length; i++) + { + tasks[i] = Recursive1(stackDepth); + } + + GC.Collect(); + cs.SetResult(100); + Task.WaitAll(tasks); + } + + { + cs = new TaskCompletionSource(); + Task[] tasks = new Task[numStacks]; + for (int i = 0; i < tasks.Length; i++) + { + tasks[i] = Recursive2(stackDepth); + } + + GC.Collect(); + cs.SetResult(100); + Task.WaitAll(tasks); + } + } + + Warmup(); + + Console.WriteLine("async-1 ===================== "); + var ticks = Environment.TickCount; + + // run a few iterations for 5 seconds total + while (Environment.TickCount - ticks < 5000) + { + cs = new TaskCompletionSource(); + Task[] tasks = new Task[numStacks]; + for (int i = 0; i < tasks.Length; i++) + { + tasks[i] = Recursive1(stackDepth); + } + + GC.Collect(0); + + var ticksStart = Stopwatch.GetTimestamp(); + + for (int i = 0; i < numGCs; i++) + { + if (i % 10 == 0) + Console.Write('.'); + + GC.Collect(0); + } + + var info = GC.GetGCMemoryInfo(); + Console.Write("memory Mbytes:" + info.HeapSizeBytes / 1000000 + " "); + System.Console.WriteLine("Time per Gen0 GC (microseconds): " + (Stopwatch.GetTimestamp() - ticksStart) * 1000000 / numGCs / Stopwatch.Frequency); + + cs.SetResult(100); + Task.WaitAll(tasks); + } + + Console.WriteLine("async-2 ===================== "); + ticks = Environment.TickCount; + + // run a few iterations for 5 seconds total + while (Environment.TickCount - ticks < 5000) + { + cs = new TaskCompletionSource(); + Task[] tasks = new Task[numStacks]; + for (int i = 0; i < tasks.Length; i++) + { + tasks[i] = Recursive2(stackDepth); + } + + GC.Collect(0); + + var ticksStart = Stopwatch.GetTimestamp(); + + for (int i = 0; i < numGCs; i++) + { + if (i % 10 == 0) + Console.Write('.'); + + GC.Collect(0); + } + + var info = GC.GetGCMemoryInfo(); + Console.Write("memory Mbytes:" + info.HeapSizeBytes / 1000000 + " "); + + System.Console.WriteLine("Time per Gen0 GC (microseconds): " + (Stopwatch.GetTimestamp() - ticksStart) * 1000000 / numGCs / Stopwatch.Frequency); + + cs.SetResult(100); + Task.WaitAll(tasks); + } + } +} diff --git a/src/tests/Loader/async/async2gcRootsScan.csproj b/src/tests/Loader/async/async2gcRootsScan.csproj new file mode 100644 index 00000000000000..0f29ed860df43e --- /dev/null +++ b/src/tests/Loader/async/async2gcRootsScan.csproj @@ -0,0 +1,8 @@ + + + true + + + + + From ff668bd2284ea541b1426d09f891aa53ba35bf96 Mon Sep 17 00:00:00 2001 From: Vladimir Sadov Date: Sun, 24 Dec 2023 20:55:04 -0800 Subject: [PATCH 096/203] Bump the roslyn commit to a version supporting top-level async2 functions. Add a test. (#2470) --- buildroslynnugets.cmd | 4 +- eng/Versions.props | 6 +- .../async2fibonacci-with-yields-topLevel.cs | 62 +++++++++++++++++++ ...sync2fibonacci-with-yields-topLevel.csproj | 9 +++ 4 files changed, 76 insertions(+), 5 deletions(-) create mode 100644 src/tests/Loader/async/async2fibonacci-with-yields-topLevel.cs create mode 100644 src/tests/Loader/async/async2fibonacci-with-yields-topLevel.csproj diff --git a/buildroslynnugets.cmd b/buildroslynnugets.cmd index 1051eda3506e04..b91c2c3160a70c 100644 --- a/buildroslynnugets.cmd +++ b/buildroslynnugets.cmd @@ -1,7 +1,7 @@ setlocal ENABLEEXTENSIONS pushd %~dp0 -set ASYNC_ROSLYN_COMMIT=046d22fad72ac726d36d93b0e9d186574e77207b -set ASYNC_SUFFIX=async-7 +set ASYNC_ROSLYN_COMMIT=4d45a5cfed3654d4d5a079908e3796be6665b18c +set ASYNC_SUFFIX=async-8 set ASYNC_ROSLYN_BRANCH=demos/async2-experiment cd .. diff --git a/eng/Versions.props b/eng/Versions.props index cc9b47af989700..1a62fff1dc845d 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -39,9 +39,9 @@ Any tools that contribute to the design-time experience should use the MicrosoftCodeAnalysisVersion_LatestVS property above to ensure they do not break the local dev experience. --> - 4.9.0-async-7 - 4.9.0-async-7 - 4.9.0-async-7 + 4.9.0-async-8 + 4.9.0-async-8 + 4.9.0-async-8 + + + CP0001 + T:Internal.Console + + + CP0002 + F:System.Resources.ResourceManager.BaseNameField + + + CP0002 + F:System.Resources.ResourceSet.Reader + + + CP0002 + M:System.String.Trim(System.ReadOnlySpan{System.Char}) + + + CP0002 + M:System.String.TrimEnd(System.ReadOnlySpan{System.Char}) + + + CP0002 + M:System.String.TrimStart(System.ReadOnlySpan{System.Char}) + + \ No newline at end of file diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml b/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml index eb99165343de1f..da5b1f8774ea91 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml @@ -57,14 +57,6 @@ CP0001 T:Internal.Metadata.NativeFormat.ConstantBooleanValueHandle - - CP0001 - T:Internal.Metadata.NativeFormat.ConstantEnumValue - - - CP0001 - T:Internal.Metadata.NativeFormat.ConstantEnumValueHandle - CP0001 T:Internal.Metadata.NativeFormat.ConstantByteArray @@ -121,6 +113,14 @@ CP0001 T:Internal.Metadata.NativeFormat.ConstantEnumArrayHandle + + CP0001 + T:Internal.Metadata.NativeFormat.ConstantEnumValue + + + CP0001 + T:Internal.Metadata.NativeFormat.ConstantEnumValueHandle + CP0001 T:Internal.Metadata.NativeFormat.ConstantHandleArray @@ -753,6 +753,10 @@ CP0001 T:Internal.TypeSystem.LockFreeReaderHashtable`2 + + CP0001 + T:Internal.TypeSystem.LockFreeReaderHashtableOfPointers`2 + CP0001 T:System.Diagnostics.DebugAnnotations @@ -830,8 +834,16 @@ T:System.Runtime.CompilerServices.StaticClassConstructionContext - CP0001 - T:Internal.TypeSystem.LockFreeReaderHashtableOfPointers`2 + CP0002 + F:System.Resources.ResourceManager.BaseNameField + + + CP0002 + F:System.Resources.ResourceSet.Reader + + + CP0002 + M:System.Diagnostics.DiagnosticMethodInfo.#ctor(System.String,System.String,System.String) CP0002 @@ -839,22 +851,30 @@ CP0002 - M:System.Threading.Lock.#ctor(System.Boolean) + M:System.String.Trim(System.ReadOnlySpan{System.Char}) CP0002 - M:System.Diagnostics.DiagnosticMethodInfo.#ctor(System.String,System.String,System.String) + M:System.String.TrimEnd(System.ReadOnlySpan{System.Char}) + + + CP0002 + M:System.String.TrimStart(System.ReadOnlySpan{System.Char}) + + + CP0002 + M:System.Threading.Lock.#ctor(System.Boolean) CP0002 M:System.Runtime.CompilerServices.RuntimeHelpers.AwaitAwaiterFromRuntimeAsync``1(``0) - ref/net9.0/System.Private.CoreLib.dll - lib/net9.0/System.Private.CoreLib.dll + ref/net10.0/System.Private.CoreLib.dll + lib/net10.0/System.Private.CoreLib.dll CP0002 M:System.Runtime.CompilerServices.RuntimeHelpers.UnsafeAwaitAwaiterFromRuntimeAsync``1(``0) - ref/net9.0/System.Private.CoreLib.dll - lib/net9.0/System.Private.CoreLib.dll + ref/net10.0/System.Private.CoreLib.dll + lib/net10.0/System.Private.CoreLib.dll \ No newline at end of file diff --git a/src/libraries/System.Collections.Specialized/src/CompatibilitySuppressions.xml b/src/libraries/System.Collections.Specialized/src/CompatibilitySuppressions.xml index a941952fee7773..67ada8e90c3092 100644 --- a/src/libraries/System.Collections.Specialized/src/CompatibilitySuppressions.xml +++ b/src/libraries/System.Collections.Specialized/src/CompatibilitySuppressions.xml @@ -1,6 +1,6 @@  + - CP0001 T:System.Collections.Specialized.ListDictionary.DictionaryNode From badb398b2d7056557dd7e58b628c1c78ee60ed84 Mon Sep 17 00:00:00 2001 From: vsadov <8218165+VSadov@users.noreply.github.com> Date: Wed, 16 Oct 2024 23:16:08 -0700 Subject: [PATCH 106/203] point to rebased Roslyn branch --- buildroslynnugets.cmd | 16 ++++++++-------- eng/Versions.props | 11 +++-------- 2 files changed, 11 insertions(+), 16 deletions(-) diff --git a/buildroslynnugets.cmd b/buildroslynnugets.cmd index b91c2c3160a70c..5e9564528653cc 100644 --- a/buildroslynnugets.cmd +++ b/buildroslynnugets.cmd @@ -1,8 +1,8 @@ setlocal ENABLEEXTENSIONS pushd %~dp0 -set ASYNC_ROSLYN_COMMIT=4d45a5cfed3654d4d5a079908e3796be6665b18c -set ASYNC_SUFFIX=async-8 -set ASYNC_ROSLYN_BRANCH=demos/async2-experiment +set ASYNC_ROSLYN_COMMIT=8bb20e76b6d7e041a2cd0a3152d95fdff6578050 +set ASYNC_SUFFIX=async-9 +set ASYNC_ROSLYN_BRANCH=demos/async2-experiment1 cd .. if not exist async-roslyn-repo git clone -b %ASYNC_ROSLYN_BRANCH% -o async_roslyn_remote https://github.com/dotnet/roslyn.git async-roslyn-repo @@ -26,9 +26,9 @@ pushd %~dp0 md roslynpackages -copy ..\async-roslyn-repo\artifacts\packages\Release\Shipping\Microsoft.Net.Compilers.Toolset.4.9.0-%ASYNC_SUFFIX%.nupkg roslynpackages -copy ..\async-roslyn-repo\artifacts\packages\Release\Shipping\Microsoft.CodeAnalysis.Workspaces.Common.4.9.0-%ASYNC_SUFFIX%.nupkg roslynpackages -copy ..\async-roslyn-repo\artifacts\packages\Release\Shipping\Microsoft.CodeAnalysis.CSharp.Workspaces.4.9.0-%ASYNC_SUFFIX%.nupkg roslynpackages -copy ..\async-roslyn-repo\artifacts\packages\Release\Shipping\Microsoft.CodeAnalysis.CSharp.4.9.0-%ASYNC_SUFFIX%.nupkg roslynpackages -copy ..\async-roslyn-repo\artifacts\packages\Release\Shipping\Microsoft.CodeAnalysis.Common.4.9.0-%ASYNC_SUFFIX%.nupkg roslynpackages +copy ..\async-roslyn-repo\artifacts\packages\Release\Shipping\Microsoft.Net.Compilers.Toolset.4.13.0-%ASYNC_SUFFIX%.nupkg roslynpackages +copy ..\async-roslyn-repo\artifacts\packages\Release\Shipping\Microsoft.CodeAnalysis.Workspaces.Common.4.13.0-%ASYNC_SUFFIX%.nupkg roslynpackages +copy ..\async-roslyn-repo\artifacts\packages\Release\Shipping\Microsoft.CodeAnalysis.CSharp.Workspaces.4.13.0-%ASYNC_SUFFIX%.nupkg roslynpackages +copy ..\async-roslyn-repo\artifacts\packages\Release\Shipping\Microsoft.CodeAnalysis.CSharp.4.13.0-%ASYNC_SUFFIX%.nupkg roslynpackages +copy ..\async-roslyn-repo\artifacts\packages\Release\Shipping\Microsoft.CodeAnalysis.Common.4.13.0-%ASYNC_SUFFIX%.nupkg roslynpackages diff --git a/eng/Versions.props b/eng/Versions.props index 6e16489fec57ce..2d051be477e44d 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -43,14 +43,9 @@ Any tools that contribute to the design-time experience should use the MicrosoftCodeAnalysisVersion_LatestVS property above to ensure they do not break the local dev experience. --> - 4.12.0-3.24476.1 - 4.12.0-3.24476.1 - 4.12.0-3.24476.1 - + 4.13.0-async-9 + 4.13.0-async-9 + 4.13.0-async-9 + BuildOnly diff --git a/src/tests/Loader/async/async2-mincallcost-microbench.cs b/src/tests/Loader/async/async2-mincallcost-microbench.cs index d2a64368ca60dc..122994d8061c37 100644 --- a/src/tests/Loader/async/async2-mincallcost-microbench.cs +++ b/src/tests/Loader/async/async2-mincallcost-microbench.cs @@ -16,10 +16,12 @@ public class Async2MinCallCostMicrobench { - [Fact] - public static void TestEntryPoint() + public static int Main() { Task.Run(AsyncEntry).Wait(); + + Console.WriteLine("Test Passed"); + return 100; } public static async Task AsyncEntry() diff --git a/src/tests/Loader/async/async2-mincallcost-microbench.csproj b/src/tests/Loader/async/async2-mincallcost-microbench.csproj index d159a32e762f81..f7a2511415dff2 100644 --- a/src/tests/Loader/async/async2-mincallcost-microbench.csproj +++ b/src/tests/Loader/async/async2-mincallcost-microbench.csproj @@ -2,6 +2,8 @@ True true + + BuildOnly diff --git a/src/tests/Loader/async/async2fibonacci-with-yields-topLevel.cs b/src/tests/Loader/async/async2fibonacci-with-yields-topLevel.cs index 977064a83740e3..ad1762fbdee49b 100644 --- a/src/tests/Loader/async/async2fibonacci-with-yields-topLevel.cs +++ b/src/tests/Loader/async/async2fibonacci-with-yields-topLevel.cs @@ -8,10 +8,11 @@ const uint Threshold = 1_000; bool done = false; +const int iterations = 1; async Task AsyncEntry() { - for (int i = 0; i < 10 && !done; i++) + for (int i = 0; i < iterations && !done; i++) { var sw = new Stopwatch(); sw.Start(); @@ -59,4 +60,4 @@ int Test() return 100; } -return Test(); \ No newline at end of file +return Test(); diff --git a/src/tests/Loader/async/async2fibonacci-with-yields.cs b/src/tests/Loader/async/async2fibonacci-with-yields.cs index 6df9ddc44445a9..9a8e9b505a55c1 100644 --- a/src/tests/Loader/async/async2fibonacci-with-yields.cs +++ b/src/tests/Loader/async/async2fibonacci-with-yields.cs @@ -10,6 +10,7 @@ public class Async2FibonacceWithYields { const uint Threshold = 1_000; static bool done; + const int iterations = 1; [Fact] public static void Test() @@ -19,7 +20,7 @@ public static void Test() public static async Task AsyncEntry() { - for (int i = 0; i < 10 && !done; i++) + for (int i = 0; i < iterations && !done; i++) { var sw = new Stopwatch(); sw.Start(); diff --git a/src/tests/Loader/async/async2fibonacci-without-yields.cs b/src/tests/Loader/async/async2fibonacci-without-yields.cs index bd47c8e869ab02..fc1d1fd7f1f087 100644 --- a/src/tests/Loader/async/async2fibonacci-without-yields.cs +++ b/src/tests/Loader/async/async2fibonacci-without-yields.cs @@ -10,6 +10,7 @@ public class Async2FibonacceWithoutYields { const uint Threshold = 1_000; static bool doYields = false; + const int iterations = 1; [Fact] public static void Test() @@ -19,7 +20,7 @@ public static void Test() public static async Task AsyncEntry() { - for (int i = 0; i < 10; i++) + for (int i = 0; i < iterations; i++) { var sw = new Stopwatch(); sw.Start(); diff --git a/src/tests/Loader/async/async2gcRootsScan.cs b/src/tests/Loader/async/async2gcRootsScan.cs index f703b88c149d58..fffe874af38e50 100644 --- a/src/tests/Loader/async/async2gcRootsScan.cs +++ b/src/tests/Loader/async/async2gcRootsScan.cs @@ -17,17 +17,17 @@ static async Task Recursive1(int n) // make some garbage object o1 = n; - //object o2 = n; - //object o3 = n; - //object o4 = n; - //object o5 = n; - //object o6 = n; - //object o7 = n; - //object o8 = n; - //object o9 = n; - //object o10 = n; - //object o11 = n; - //object o12 = n; + object o2 = n; + object o3 = n; + object o4 = n; + object o5 = n; + object o6 = n; + object o7 = n; + object o8 = n; + object o9 = n; + object o10 = n; + object o11 = n; + object o12 = n; if (n == 0) return await cTask; @@ -35,17 +35,17 @@ static async Task Recursive1(int n) var result = await Recursive1(n - 1); Assert.Equal(n, (int)o1); - //Assert.Equal(n, (int)o2); - //Assert.Equal(n, (int)o3); - //Assert.Equal(n, (int)o4); - //Assert.Equal(n, (int)o5); - //Assert.Equal(n, (int)o6); - //Assert.Equal(n, (int)o7); - //Assert.Equal(n, (int)o8); - //Assert.Equal(n, (int)o9); - //Assert.Equal(n, (int)o10); - //Assert.Equal(n, (int)o11); - //Assert.Equal(n, (int)o12); + Assert.Equal(n, (int)o2); + Assert.Equal(n, (int)o3); + Assert.Equal(n, (int)o4); + Assert.Equal(n, (int)o5); + Assert.Equal(n, (int)o6); + Assert.Equal(n, (int)o7); + Assert.Equal(n, (int)o8); + Assert.Equal(n, (int)o9); + Assert.Equal(n, (int)o10); + Assert.Equal(n, (int)o11); + Assert.Equal(n, (int)o12); return result; } @@ -57,18 +57,17 @@ static async2 Task Recursive2(int n) // make some garbage object o1 = n; -// THIS CAUSES CRASHES IN UNWINDER FLAVOR - //object o2 = n; - //object o3 = n; - //object o4 = n; - //object o5 = n; - //object o6 = n; - //object o7 = n; - //object o8 = n; - //object o9 = n; - //object o10 = n; - //object o11 = n; - //object o12 = n; + object o2 = n; + object o3 = n; + object o4 = n; + object o5 = n; + object o6 = n; + object o7 = n; + object o8 = n; + object o9 = n; + object o10 = n; + object o11 = n; + object o12 = n; if (n == 0) return await cTask; @@ -76,25 +75,24 @@ static async2 Task Recursive2(int n) var result = await Recursive2(n - 1); Assert.Equal(n, (int)o1); - //Assert.Equal(n, (int)o2); - //Assert.Equal(n, (int)o3); - //Assert.Equal(n, (int)o4); - //Assert.Equal(n, (int)o5); - //Assert.Equal(n, (int)o6); - //Assert.Equal(n, (int)o7); - //Assert.Equal(n, (int)o8); - //Assert.Equal(n, (int)o9); - //Assert.Equal(n, (int)o10); - //Assert.Equal(n, (int)o11); - //Assert.Equal(n, (int)o12); + Assert.Equal(n, (int)o2); + Assert.Equal(n, (int)o3); + Assert.Equal(n, (int)o4); + Assert.Equal(n, (int)o5); + Assert.Equal(n, (int)o6); + Assert.Equal(n, (int)o7); + Assert.Equal(n, (int)o8); + Assert.Equal(n, (int)o9); + Assert.Equal(n, (int)o10); + Assert.Equal(n, (int)o11); + Assert.Equal(n, (int)o12); return result; } static System.Collections.Generic.List d = new System.Collections.Generic.List(); - [Fact] - public static void Test() + public static int Main() { int numStacks = 10000; int stackDepth = 100; @@ -207,5 +205,7 @@ void Warmup() cs.SetResult(100); Task.WaitAll(tasks); } + + return 100; } } diff --git a/src/tests/Loader/async/async2gcRootsScan.csproj b/src/tests/Loader/async/async2gcRootsScan.csproj index 0f29ed860df43e..b6e7e75bd6ebc6 100644 --- a/src/tests/Loader/async/async2gcRootsScan.csproj +++ b/src/tests/Loader/async/async2gcRootsScan.csproj @@ -1,6 +1,9 @@ + True true + + BuildOnly diff --git a/src/tests/Loader/async/syncfibonacci-with-yields.cs b/src/tests/Loader/async/syncfibonacci-with-yields.cs index e8a76958b2bd89..fa05d09572ef0e 100644 --- a/src/tests/Loader/async/syncfibonacci-with-yields.cs +++ b/src/tests/Loader/async/syncfibonacci-with-yields.cs @@ -12,8 +12,7 @@ public class SyncFibonacciWithYields { const uint Threshold = 1_000; - [Fact] - public static void Test() + public static int Main() { for (int i = 0; i < 10; i++) { @@ -22,6 +21,8 @@ public static void Test() uint result = A(100_000_000); Console.WriteLine($"{sw.ElapsedMilliseconds} ms result={result}"); } + + return 100; } static uint A(uint n) diff --git a/src/tests/Loader/async/syncfibonacci-with-yields.csproj b/src/tests/Loader/async/syncfibonacci-with-yields.csproj index 4a1973da13c789..c089ac9ebc8bab 100644 --- a/src/tests/Loader/async/syncfibonacci-with-yields.csproj +++ b/src/tests/Loader/async/syncfibonacci-with-yields.csproj @@ -1,7 +1,9 @@ - true True + true + + BuildOnly diff --git a/src/tests/Loader/async/syncfibonacci-without-yields.cs b/src/tests/Loader/async/syncfibonacci-without-yields.cs index 27c428ab57313a..1b40117c02419b 100644 --- a/src/tests/Loader/async/syncfibonacci-without-yields.cs +++ b/src/tests/Loader/async/syncfibonacci-without-yields.cs @@ -12,8 +12,7 @@ public class SyncFibonacciWithoutYields { const uint Threshold = 1_000; - [Fact] - public static void Test() + public static int Main() { for (int i = 0; i < 10; i++) { @@ -22,6 +21,8 @@ public static void Test() uint result = A(100_000_000); Console.WriteLine($"{sw.ElapsedMilliseconds} ms result={result}"); } + + return 100; } static uint A(uint n) diff --git a/src/tests/Loader/async/syncfibonacci-without-yields.csproj b/src/tests/Loader/async/syncfibonacci-without-yields.csproj index 4a1973da13c789..c089ac9ebc8bab 100644 --- a/src/tests/Loader/async/syncfibonacci-without-yields.csproj +++ b/src/tests/Loader/async/syncfibonacci-without-yields.csproj @@ -1,7 +1,9 @@ - true True + true + + BuildOnly diff --git a/src/tests/Loader/async/taskbased-asyncfibonacci-with-yields.cs b/src/tests/Loader/async/taskbased-asyncfibonacci-with-yields.cs index c000811184fb17..11b131f959c92d 100644 --- a/src/tests/Loader/async/taskbased-asyncfibonacci-with-yields.cs +++ b/src/tests/Loader/async/taskbased-asyncfibonacci-with-yields.cs @@ -11,8 +11,7 @@ public class TaskBasedAsyncFibonacciWithYields { const uint Threshold = 1_000; - [Fact] - public static int Test() { return AsyncMain().Result; } + public static int Main() { return AsyncMain().Result; } static async Task AsyncMain() { @@ -52,4 +51,4 @@ static async Task B(uint n){ return result; } -} \ No newline at end of file +} diff --git a/src/tests/Loader/async/taskbased-asyncfibonacci-with-yields.csproj b/src/tests/Loader/async/taskbased-asyncfibonacci-with-yields.csproj index 4a1973da13c789..c089ac9ebc8bab 100644 --- a/src/tests/Loader/async/taskbased-asyncfibonacci-with-yields.csproj +++ b/src/tests/Loader/async/taskbased-asyncfibonacci-with-yields.csproj @@ -1,7 +1,9 @@ - true True + true + + BuildOnly diff --git a/src/tests/Loader/async/taskbased-asyncfibonacci-without-yields.cs b/src/tests/Loader/async/taskbased-asyncfibonacci-without-yields.cs index 44312d4f7319b3..6be313d1986dec 100644 --- a/src/tests/Loader/async/taskbased-asyncfibonacci-without-yields.cs +++ b/src/tests/Loader/async/taskbased-asyncfibonacci-without-yields.cs @@ -11,8 +11,7 @@ public class TaskBasedAsyncFibonacciWithoutYields { const uint Threshold = 1_000; - [Fact] - public static int Test() { return AsyncMain().Result; } + public static int Main() { return AsyncMain().Result; } static async Task AsyncMain() { @@ -48,4 +47,4 @@ static async Task B(uint n) return result; } -} \ No newline at end of file +} diff --git a/src/tests/Loader/async/taskbased-asyncfibonacci-without-yields.csproj b/src/tests/Loader/async/taskbased-asyncfibonacci-without-yields.csproj index 4a1973da13c789..c089ac9ebc8bab 100644 --- a/src/tests/Loader/async/taskbased-asyncfibonacci-without-yields.csproj +++ b/src/tests/Loader/async/taskbased-asyncfibonacci-without-yields.csproj @@ -1,7 +1,9 @@ - true True + true + + BuildOnly diff --git a/src/tests/Loader/async/valuetaskbased-asyncfibonacci-with-yields.cs b/src/tests/Loader/async/valuetaskbased-asyncfibonacci-with-yields.cs index 12828c937e4036..a288db583b4fbe 100644 --- a/src/tests/Loader/async/valuetaskbased-asyncfibonacci-with-yields.cs +++ b/src/tests/Loader/async/valuetaskbased-asyncfibonacci-with-yields.cs @@ -11,8 +11,7 @@ public class ValueTaskBasedAsyncFibonacciWithYields { const uint Threshold = 1_000; - [Fact] - public static int Test() { return AsyncMain().Result; } + public static int Main() { return AsyncMain().Result; } static async Task AsyncMain() { diff --git a/src/tests/Loader/async/valuetaskbased-asyncfibonacci-with-yields.csproj b/src/tests/Loader/async/valuetaskbased-asyncfibonacci-with-yields.csproj index 4a1973da13c789..c089ac9ebc8bab 100644 --- a/src/tests/Loader/async/valuetaskbased-asyncfibonacci-with-yields.csproj +++ b/src/tests/Loader/async/valuetaskbased-asyncfibonacci-with-yields.csproj @@ -1,7 +1,9 @@ - true True + true + + BuildOnly diff --git a/src/tests/Loader/async/valuetaskbased-asyncfibonacci-without-yields.cs b/src/tests/Loader/async/valuetaskbased-asyncfibonacci-without-yields.cs index b27d19a8b15691..bdbb7b0b3647a2 100644 --- a/src/tests/Loader/async/valuetaskbased-asyncfibonacci-without-yields.cs +++ b/src/tests/Loader/async/valuetaskbased-asyncfibonacci-without-yields.cs @@ -11,8 +11,7 @@ public class ValueTaskBasedAsyncFibonacciWithoutYields { const uint Threshold = 1_000; - [Fact] - public static int Test() { return AsyncMain().Result; } + public static int Main() { return AsyncMain().Result; } static async ValueTask AsyncMain() { @@ -48,4 +47,4 @@ static async ValueTask B(uint n) return result; } -} \ No newline at end of file +} diff --git a/src/tests/Loader/async/valuetaskbased-asyncfibonacci-without-yields.csproj b/src/tests/Loader/async/valuetaskbased-asyncfibonacci-without-yields.csproj index 4a1973da13c789..c089ac9ebc8bab 100644 --- a/src/tests/Loader/async/valuetaskbased-asyncfibonacci-without-yields.csproj +++ b/src/tests/Loader/async/valuetaskbased-asyncfibonacci-without-yields.csproj @@ -1,7 +1,9 @@ - true True + true + + BuildOnly From 0a10db8cae9ca24fc034c06db250b5c5df30886c Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Tue, 12 Nov 2024 10:49:06 +0100 Subject: [PATCH 121/203] [Runtime Async] Add support for shared generics (#2755) --- README.md | 2 +- src/coreclr/vm/callhelpers.cpp | 2 +- src/coreclr/vm/gcenv.ee.common.cpp | 4 +- src/coreclr/vm/interpreter.cpp | 2 +- src/coreclr/vm/jitinterface.cpp | 20 +- src/coreclr/vm/method.hpp | 7 +- src/coreclr/vm/prestub.cpp | 181 ++++++++++++++++-- src/coreclr/vm/siginfo.hpp | 9 + src/coreclr/vm/stubgen.h | 1 - src/tests/Loader/async/async2sharedgeneric.cs | 116 +++++++++-- src/tests/Loader/async/expected_failures.txt | 2 +- 11 files changed, 297 insertions(+), 49 deletions(-) diff --git a/README.md b/README.md index dd3e832c394d85..a7d5533b6df305 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Current support in the prototypes looks like the following: | **Feature/characteristic** | **JIT based state machines** | **Unwinder based state machines** | |------------------------------------------|:------------------------------:|:-----------------------------------:| -| **Generics** | ❌ | ❌ | +| **Generics** | ✅ | ❌ | | **Byrefs live across suspension points** | ❌ | ✅ | | **Exception handling** | ✅ | ❌ | | **Returns via return buffers** | ✅ | ❌ | diff --git a/src/coreclr/vm/callhelpers.cpp b/src/coreclr/vm/callhelpers.cpp index 3906d396c87f54..c3fdfffe3396ae 100644 --- a/src/coreclr/vm/callhelpers.cpp +++ b/src/coreclr/vm/callhelpers.cpp @@ -341,7 +341,7 @@ void MethodDescCallSite::CallTargetWorker(const ARG_SLOT *pArguments, ARG_SLOT * #ifndef FEATURE_INTERPRETER _ASSERTE(isCallConv(m_methodSig.GetCallingConvention(), IMAGE_CEE_CS_CALLCONV_DEFAULT)); - _ASSERTE(!(m_methodSig.GetCallingConventionInfo() & CORINFO_CALLCONV_PARAMTYPE)); + _ASSERTE(!m_methodSig.HasGenericContextArg()); #endif #ifdef DEBUGGING_SUPPORTED diff --git a/src/coreclr/vm/gcenv.ee.common.cpp b/src/coreclr/vm/gcenv.ee.common.cpp index 6cbd83dad75bab..8e8ebc981bf2fa 100644 --- a/src/coreclr/vm/gcenv.ee.common.cpp +++ b/src/coreclr/vm/gcenv.ee.common.cpp @@ -448,7 +448,7 @@ StackWalkAction GcStackCrawlCallBack(CrawlFrame* pCF, VOID* pData) if (paramContextType == GENERIC_PARAM_CONTEXT_METHODDESC) { MethodDesc *pMDReal = dac_cast(pCF->GetParamTypeArg()); - _ASSERTE((pMDReal != NULL) || !pCF->IsFrameless()); + _ASSERTE((pMDReal != NULL) || !pCF->IsFrameless() || pMD->IsAsync2Method()); if (pMDReal != NULL) { GcReportLoaderAllocator(gcctx->f, gcctx->sc, pMDReal->GetLoaderAllocator()); @@ -457,7 +457,7 @@ StackWalkAction GcStackCrawlCallBack(CrawlFrame* pCF, VOID* pData) else if (paramContextType == GENERIC_PARAM_CONTEXT_METHODTABLE) { MethodTable *pMTReal = dac_cast(pCF->GetParamTypeArg()); - _ASSERTE((pMTReal != NULL) || !pCF->IsFrameless()); + _ASSERTE((pMTReal != NULL) || !pCF->IsFrameless() || pMD->IsAsync2Method()); if (pMTReal != NULL) { GcReportLoaderAllocator(gcctx->f, gcctx->sc, pMTReal->GetLoaderAllocator()); diff --git a/src/coreclr/vm/interpreter.cpp b/src/coreclr/vm/interpreter.cpp index 80a058b3494d96..a64efc88bd9473 100644 --- a/src/coreclr/vm/interpreter.cpp +++ b/src/coreclr/vm/interpreter.cpp @@ -970,7 +970,7 @@ CorJitResult Interpreter::GenerateInterpreterStub(CEEInfo* comp, totalArgs++; } - if (sig.GetCallingConventionInfo() & CORINFO_CALLCONV_PARAMTYPE) + if (sig.HasGenericContextArg()) { _ASSERTE(info->args.callConv & CORINFO_CALLCONV_PARAMTYPE); hasGenericsContextArg = true; diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index 1203da0fdf1cc3..d3b7cd5d437621 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -14374,7 +14374,7 @@ static Signature BuildResumptionStubCalliSignature(MetaSig& msig, LoaderAllocato numArgs++; } - if ((msig.GetCallingConvention() & CORINFO_CALLCONV_PARAMTYPE) != 0) + if (msig.HasGenericContextArg()) { numArgs++; } @@ -14412,7 +14412,7 @@ static Signature BuildResumptionStubCalliSignature(MetaSig& msig, LoaderAllocato sigBuilder.AppendElementType(ELEMENT_TYPE_OBJECT); } #ifndef TARGET_X86 - if ((msig.GetCallingConvention() & CORINFO_CALLCONV_PARAMTYPE) != 0) + if (msig.HasGenericContextArg()) { sigBuilder.AppendElementType(ELEMENT_TYPE_I); } @@ -14429,7 +14429,7 @@ static Signature BuildResumptionStubCalliSignature(MetaSig& msig, LoaderAllocato } #ifdef TARGET_X86 - if ((msig.GetCallingConvention() & CORINFO_CALLCONV_PARAMTYPE) != 0) + if (msig.HasGenericContextArg()) { sigBuilder.AppendElementType(ELEMENT_TYPE_I); } @@ -14449,7 +14449,6 @@ CORINFO_METHOD_HANDLE CEEJitInfo::getAsyncResumptionStub() } CONTRACTL_END; MethodDesc* md = m_pMethodBeingCompiled; - _ASSERTE(!md->IsSharedByGenericInstantiations()); LoaderAllocator* loaderAlloc = md->GetLoaderAllocator(); @@ -14473,10 +14472,9 @@ CORINFO_METHOD_HANDLE CEEJitInfo::getAsyncResumptionStub() } #ifndef TARGET_X86 - if ((msig.GetCallingConvention() & CORINFO_CALLCONV_PARAMTYPE) != 0) + if (msig.HasGenericContextArg()) { - _ASSERTE(!"Generic context currently unhandled"); - // Will need JIT to store this in an agreed upon place. + pCode->EmitLDC(0); numArgs++; } @@ -14498,9 +14496,15 @@ CORINFO_METHOD_HANDLE CEEJitInfo::getAsyncResumptionStub() } #ifdef TARGET_X86 + if (msig.HasGenericContextArg()) + { + pCode->EmitLDC(0); + numArgs++; + } + // Continuation pCode->EmitLDARG(0); - nmArgs++; + numArgs++; #endif // Resumption stubs are uniquely coupled to the code version (since the diff --git a/src/coreclr/vm/method.hpp b/src/coreclr/vm/method.hpp index ea745041e381a7..505edf0ed383de 100644 --- a/src/coreclr/vm/method.hpp +++ b/src/coreclr/vm/method.hpp @@ -1457,7 +1457,7 @@ class MethodDesc PTR_PCODE GetAddrOfNativeCodeSlot(); PTR_AsyncMethodData GetAddrOfAsyncMethodData(); #ifndef DACCESS_COMPILE - const AsyncMethodData& GetAsyncMethodData() { _ASSERTE(IsAsyncThunkMethod()); return *GetAddrOfAsyncMethodData(); } + const AsyncMethodData& GetAsyncMethodData() { _ASSERTE(HasAsyncMethodData()); return *GetAddrOfAsyncMethodData(); } #endif BOOL MayHaveNativeCode(); @@ -1615,8 +1615,7 @@ class MethodDesc MethodDesc* GetAsyncOtherVariant() { - // TODO This should be FindOrCreateAssociatedMethodDesc with some set of params - return GetMethodTable()->GetParallelMethodDesc(this, AsyncVariantLookup::AsyncOtherVariant); + return FindOrCreateAssociatedMethodDesc(this, GetMethodTable(), FALSE, GetMethodInstantiation(), TRUE, FALSE, TRUE, AsyncVariantLookup::AsyncOtherVariant); } // True if a MD is an funny BoxedEntryPointStub (not from the method table) or @@ -1995,6 +1994,8 @@ class MethodDesc bool TryGenerateUnsafeAccessor(DynamicResolver** resolver, COR_ILMETHOD_DECODER** methodILDecoder); void EmitJitStateMachineBasedRuntimeAsyncThunk(MethodDesc* pAsyncOtherVariant, MetaSig& thunkMsig, ILStubLinker* pSL); void EmitAsync2MethodThunk(MethodDesc* pAsyncOtherVariant, MetaSig& msig, ILStubLinker* pSL); + SigPointer GetAsync2ThunkResultTypeSig(); + int GetTokenForGenericMethodCallWithAsyncReturnType(ILCodeStream* pCode, MethodDesc* md); public: static void CreateDerivedTargetSigWithExtraParams(MetaSig& msig, SigBuilder* stubSigBuilder); bool TryGenerateTransientILImplementation(DynamicResolver** resolver, COR_ILMETHOD_DECODER** methodILDecoder); diff --git a/src/coreclr/vm/prestub.cpp b/src/coreclr/vm/prestub.cpp index ebf46e71a376e3..459dc754db3768 100644 --- a/src/coreclr/vm/prestub.cpp +++ b/src/coreclr/vm/prestub.cpp @@ -1744,15 +1744,17 @@ bool MethodDesc::TryGenerateAsyncThunk(DynamicResolver** resolver, COR_ILMETHOD_ ULONG offsetOfAsyncDetailsUnused; bool isValueTypeUnused; AsyncTaskMethod asyncType = ClassifyAsyncMethod(GetSigPointer(), GetModule(), &offsetOfAsyncDetailsUnused, &isValueTypeUnused); + MethodDesc *pAsyncOtherVariant = this->GetAsyncOtherVariant(); + _ASSERTE(!IsWrapperStub() && !pAsyncOtherVariant->IsWrapperStub()); + MetaSig msig(this); - // [TODO] Handle generics - SigTypeContext emptyContext; + SigTypeContext sigContext(pAsyncOtherVariant); ILStubLinker sl( GetModule(), GetSignature(), - &emptyContext, + &sigContext, pAsyncOtherVariant, (ILStubLinkerFlags)ILSTUB_LINKER_FLAG_NONE); @@ -1850,12 +1852,68 @@ void MethodDesc::EmitJitStateMachineBasedRuntimeAsyncThunk(MethodDesc* pAsyncOth _ASSERTE(!this->GetMethodTable()->IsValueType()); pCode->EmitLDARG(localArg++); } + for (UINT iArg = 0; iArg < thunkMsig.NumFixedArgs(); iArg++) { pCode->EmitLDARG(localArg++); } - pCode->EmitCALL(pCode->GetToken(pAsyncOtherVariant), localArg, logicalResultLocal != UINT_MAX ? 1 : 0); + int token; + _ASSERTE(!pAsyncOtherVariant->IsWrapperStub()); + if (pAsyncOtherVariant->HasClassOrMethodInstantiation()) + { + // For generic code emit generic signatures. + int typeSigToken = mdTokenNil; + if (pAsyncOtherVariant->HasClassInstantiation()) + { + SigBuilder typeSigBuilder; + typeSigBuilder.AppendElementType(ELEMENT_TYPE_GENERICINST); + typeSigBuilder.AppendElementType(ELEMENT_TYPE_INTERNAL); + // TODO: Encoding potentially shared method tables in + // signatures of tokens seems odd, but this hits assert + // with the typical method table. + typeSigBuilder.AppendPointer(pAsyncOtherVariant->GetMethodTable()); + DWORD numClassTypeArgs = pAsyncOtherVariant->GetNumGenericClassArgs(); + typeSigBuilder.AppendData(numClassTypeArgs); + for (DWORD i = 0; i < numClassTypeArgs; ++i) + { + typeSigBuilder.AppendElementType(ELEMENT_TYPE_VAR); + typeSigBuilder.AppendData(i); + } + + DWORD typeSigLen; + PCCOR_SIGNATURE typeSig = (PCCOR_SIGNATURE)typeSigBuilder.GetSignature(&typeSigLen); + typeSigToken = pCode->GetSigToken(typeSig, typeSigLen); + } + + if (pAsyncOtherVariant->HasMethodInstantiation()) + { + SigBuilder methodSigBuilder; + DWORD numMethodTypeArgs = pAsyncOtherVariant->GetNumGenericMethodArgs(); + methodSigBuilder.AppendByte(IMAGE_CEE_CS_CALLCONV_GENERICINST); + methodSigBuilder.AppendData(numMethodTypeArgs); + for (DWORD i = 0; i < numMethodTypeArgs; ++i) + { + methodSigBuilder.AppendElementType(ELEMENT_TYPE_MVAR); + methodSigBuilder.AppendData(i); + } + + DWORD sigLen; + PCCOR_SIGNATURE sig = (PCCOR_SIGNATURE)methodSigBuilder.GetSignature(&sigLen); + int methodSigToken = pCode->GetSigToken(sig, sigLen); + token = pCode->GetToken(pAsyncOtherVariant, typeSigToken, methodSigToken); + } + else + { + token = pCode->GetToken(pAsyncOtherVariant, typeSigToken); + } + } + else + { + token = pCode->GetToken(pAsyncOtherVariant); + } + + pCode->EmitCALL(token, localArg, logicalResultLocal != UINT_MAX ? 1 : 0); if (logicalResultLocal != UINT_MAX) pCode->EmitSTLOC(logicalResultLocal); @@ -1867,24 +1925,31 @@ void MethodDesc::EmitJitStateMachineBasedRuntimeAsyncThunk(MethodDesc* pAsyncOth // Catch { pCode->BeginCatchBlock(pCode->GetToken(CoreLibBinder::GetClass(CLASS__EXCEPTION))); - MethodDesc* fromExceptionMD; + + int fromExceptionToken; if (logicalResultLocal != UINT_MAX) { - MethodDesc *md; + MethodDesc* fromExceptionMD; if (isValueTask) - md = CoreLibBinder::GetMethod(METHOD__VALUETASK__FROM_EXCEPTION_1); + fromExceptionMD = CoreLibBinder::GetMethod(METHOD__VALUETASK__FROM_EXCEPTION_1); else - md = CoreLibBinder::GetMethod(METHOD__TASK__FROM_EXCEPTION_1); - fromExceptionMD = FindOrCreateAssociatedMethodDesc(md, md->GetMethodTable(), FALSE, Instantiation(&thLogicalRetType, 1), FALSE); + fromExceptionMD = CoreLibBinder::GetMethod(METHOD__TASK__FROM_EXCEPTION_1); + + fromExceptionMD = FindOrCreateAssociatedMethodDesc(fromExceptionMD, fromExceptionMD->GetMethodTable(), FALSE, Instantiation(&thLogicalRetType, 1), FALSE); + + fromExceptionToken = GetTokenForGenericMethodCallWithAsyncReturnType(pCode, fromExceptionMD); } else { + MethodDesc* fromExceptionMD; if (isValueTask) fromExceptionMD = CoreLibBinder::GetMethod(METHOD__VALUETASK__FROM_EXCEPTION); else fromExceptionMD = CoreLibBinder::GetMethod(METHOD__TASK__FROM_EXCEPTION); + + fromExceptionToken = pCode->GetToken(fromExceptionMD); } - pCode->EmitCALL(pCode->GetToken(fromExceptionMD), 1, 1); + pCode->EmitCALL(fromExceptionToken, 1, 1); pCode->EmitSTLOC(returnLocal); pCode->EmitLEAVE(pReturnResultLabel); pCode->EndCatchBlock(); @@ -1912,7 +1977,9 @@ void MethodDesc::EmitJitStateMachineBasedRuntimeAsyncThunk(MethodDesc* pAsyncOth else md = CoreLibBinder::GetMethod(METHOD__TASK__FROM_RESULT_T); md = FindOrCreateAssociatedMethodDesc(md, md->GetMethodTable(), FALSE, Instantiation(&thLogicalRetType, 1), FALSE); - pCode->EmitCALL(pCode->GetToken(md), 1, 1); + + int fromResultToken = GetTokenForGenericMethodCallWithAsyncReturnType(pCode, md); + pCode->EmitCALL(fromResultToken, 1, 1); } else { @@ -1929,7 +1996,7 @@ void MethodDesc::EmitJitStateMachineBasedRuntimeAsyncThunk(MethodDesc* pAsyncOth pCode->EmitLabel(pSuspendedLabel); - MethodDesc* finalizeTaskReturningThunkMD; + int finalizeTaskReturningThunkToken; if (logicalResultLocal != UINT_MAX) { MethodDesc* md; @@ -1937,20 +2004,100 @@ void MethodDesc::EmitJitStateMachineBasedRuntimeAsyncThunk(MethodDesc* pAsyncOth md = CoreLibBinder::GetMethod(METHOD__RUNTIME_HELPERS__FINALIZE_VALUETASK_RETURNING_THUNK_1); else md = CoreLibBinder::GetMethod(METHOD__RUNTIME_HELPERS__FINALIZE_TASK_RETURNING_THUNK_1); - finalizeTaskReturningThunkMD = FindOrCreateAssociatedMethodDesc(md, md->GetMethodTable(), FALSE, Instantiation(&thLogicalRetType, 1), FALSE); + + md = FindOrCreateAssociatedMethodDesc(md, md->GetMethodTable(), FALSE, Instantiation(&thLogicalRetType, 1), FALSE); + finalizeTaskReturningThunkToken = GetTokenForGenericMethodCallWithAsyncReturnType(pCode, md); } else { + MethodDesc* md; if (isValueTask) - finalizeTaskReturningThunkMD = CoreLibBinder::GetMethod(METHOD__RUNTIME_HELPERS__FINALIZE_VALUETASK_RETURNING_THUNK); + md = CoreLibBinder::GetMethod(METHOD__RUNTIME_HELPERS__FINALIZE_VALUETASK_RETURNING_THUNK); else - finalizeTaskReturningThunkMD = CoreLibBinder::GetMethod(METHOD__RUNTIME_HELPERS__FINALIZE_TASK_RETURNING_THUNK); + md = CoreLibBinder::GetMethod(METHOD__RUNTIME_HELPERS__FINALIZE_TASK_RETURNING_THUNK); + finalizeTaskReturningThunkToken = pCode->GetToken(md); } pCode->EmitLDLOC(continuationLocal); - pCode->EmitCALL(pCode->GetToken(finalizeTaskReturningThunkMD), 1, 1); + pCode->EmitCALL(finalizeTaskReturningThunkToken, 1, 1); pCode->EmitRET(); } +// Given an async 2 method, return a SigPointer to the unwrapped result type. For +// example, for async2 Task Foo() this returns the signature representing +// (MVAR 0). For Task, it returns the signature representing (int). +SigPointer MethodDesc::GetAsync2ThunkResultTypeSig() +{ + _ASSERTE(GetAsyncMethodData().type == AsyncMethodType::TaskToAsync); + PCCOR_SIGNATURE pSigRaw; + DWORD cSig; + if (FAILED(GetMDImport()->GetSigOfMethodDef(GetMemberDef(), &cSig, &pSigRaw))) + { + _ASSERTE(!"Loaded MethodDesc should not fail to get signature"); + pSigRaw = NULL; + cSig = 0; + } + + SigPointer pSig(pSigRaw, cSig); + uint32_t callConvInfo; + IfFailThrow(pSig.GetCallingConvInfo(&callConvInfo)); + + if ((callConvInfo & IMAGE_CEE_CS_CALLCONV_GENERIC) != 0) + { + // GenParamCount + IfFailThrow(pSig.GetData(NULL)); + } + + // ParamCount + IfFailThrow(pSig.GetData(NULL)); + + // ReturnType comes now. Skip the modifiers (like async2 modifier). + IfFailThrow(pSig.SkipCustomModifiers()); + + // Get the start of the return type + PCCOR_SIGNATURE returnTypeSig; + uint32_t tailLength; + pSig.GetSignature(&returnTypeSig, &tailLength); + + // Skip to the end of the return type so we can get the length. + IfFailThrow(pSig.SkipExactlyOne()); + + PCCOR_SIGNATURE returnTypeSigEnd; + pSig.GetSignature(&returnTypeSigEnd, &tailLength); + + return SigPointer(returnTypeSig, (DWORD)(returnTypeSigEnd - returnTypeSig)); +} + +// Given a method Foo, return a MethodSpec token for Foo instantiated +// with the result type from the current async method's return type. For +// example, if "this" represents async2 Task> Foo(), and "md" is +// Task.FromResult, this returns a MethodSpec representing +// Task.FromResult>. +int MethodDesc::GetTokenForGenericMethodCallWithAsyncReturnType(ILCodeStream* pCode, MethodDesc* md) +{ + if (!md->HasClassOrMethodInstantiation()) + { + return pCode->GetToken(md); + } + + // We never get here with a class instantiation currently. + _ASSERTE(!md->HasClassInstantiation()); + + SigBuilder methodSigBuilder; + methodSigBuilder.AppendByte(IMAGE_CEE_CS_CALLCONV_GENERICINST); + methodSigBuilder.AppendData(1); + SigPointer retTypeSig = GetAsync2ThunkResultTypeSig(); + PCCOR_SIGNATURE retTypeSigRaw; + uint32_t retTypeSigLen; + retTypeSig.GetSignature(&retTypeSigRaw, &retTypeSigLen); + methodSigBuilder.AppendBlob((const PVOID)retTypeSigRaw, retTypeSigLen); + + DWORD methodSigLen; + PCCOR_SIGNATURE methodSig = (PCCOR_SIGNATURE)methodSigBuilder.GetSignature(&methodSigLen); + int methodSigToken = pCode->GetSigToken(methodSig, methodSigLen); + + return pCode->GetToken(md, mdTokenNil, methodSigToken); +} + void MethodDesc::EmitAsync2MethodThunk(MethodDesc* pAsyncOtherVariant, MetaSig& msig, ILStubLinker* pSL) { // Implement IL that is effectively the following @@ -2492,7 +2639,7 @@ void MethodDesc::CreateDerivedTargetSigWithExtraParams(MetaSig& msig, SigBuilder stubSigBuilder->AppendByte(callingConvention); unsigned numArgs = msig.NumFixedArgs(); - bool hasInstParam = (msig.GetCallingConventionInfo() & CORINFO_CALLCONV_PARAMTYPE) != 0; + bool hasInstParam = msig.HasGenericContextArg() != FALSE; if (hasInstParam) numArgs++; if (msig.HasAsyncContinuation()) diff --git a/src/coreclr/vm/siginfo.hpp b/src/coreclr/vm/siginfo.hpp index fbfe1f73094069..79c22a1c3d51b5 100644 --- a/src/coreclr/vm/siginfo.hpp +++ b/src/coreclr/vm/siginfo.hpp @@ -729,6 +729,15 @@ class MetaSig return GetCallingConvention() == IMAGE_CEE_CS_CALLCONV_VARARG; } + //---------------------------------------------------------- + // Does it have a generic context argument? + //---------------------------------------------------------- + BOOL HasGenericContextArg() + { + LIMITED_METHOD_CONTRACT; + return m_CallConv & CORINFO_CALLCONV_PARAMTYPE; + } + //---------------------------------------------------------- // Is it an async call? //---------------------------------------------------------- diff --git a/src/coreclr/vm/stubgen.h b/src/coreclr/vm/stubgen.h index f5f57024d48daf..c848b0665008fa 100644 --- a/src/coreclr/vm/stubgen.h +++ b/src/coreclr/vm/stubgen.h @@ -434,7 +434,6 @@ class TokenLookupMap MODE_ANY; GC_NOTRIGGER; PRECONDITION(pMD != NULL); - PRECONDITION(typeSignature != mdTokenNil); PRECONDITION(methodSignature != mdTokenNil); } CONTRACTL_END; diff --git a/src/tests/Loader/async/async2sharedgeneric.cs b/src/tests/Loader/async/async2sharedgeneric.cs index 992235b1c6dcc0..cafb18d1eab974 100644 --- a/src/tests/Loader/async/async2sharedgeneric.cs +++ b/src/tests/Loader/async/async2sharedgeneric.cs @@ -6,29 +6,117 @@ using Xunit; public class Async2SharedGeneric { - static Type s_type; [Fact] public static void TestEntryPoint() { - int[] arr = new int[1]; - AsyncTestEntryPoint().Wait(); - Assert.Equal(typeof(int), s_type); - AsyncTestEntryPoint().Wait(); - Assert.Equal(typeof(string), s_type); - AsyncTestEntryPoint().Wait(); - Assert.Equal(typeof(object), s_type); + Async1EntryPoint(typeof(int), 42).Wait(); + Async1EntryPoint(typeof(string), "abc").Wait(); + Async1EntryPoint(typeof(object), "def").Wait(); + + Async2EntryPoint(typeof(int), 142).Wait(); + Async2EntryPoint(typeof(string), "ghi").Wait(); + Async2EntryPoint(typeof(object), "jkl").Wait(); + } + + private static async Task Async1EntryPoint(Type t, T value) + { + await new GenericClass().InstanceMethod(t); + await GenericClass.StaticMethod(t); + await GenericClass.StaticMethod(t, t); + await GenericClass.StaticMethodAsync1(t); + await GenericClass.StaticMethodAsync1(t, t); + Assert.Equal(value, await GenericClass.StaticReturnClassType(value)); + Assert.Equal(value, await GenericClass.StaticReturnMethodType(value)); + Assert.Equal(value, await GenericClass.StaticReturnClassTypeAsync1(value)); + Assert.Equal(value, await GenericClass.StaticReturnMethodTypeAsync1(value)); + } + + private static async2 Task Async2EntryPoint(Type t, T value) + { + await new GenericClass().InstanceMethod(t); + await GenericClass.StaticMethod(t); + await GenericClass.StaticMethod(t, t); + await GenericClass.StaticMethodAsync1(t); + await GenericClass.StaticMethodAsync1(t, t); + Assert.Equal(value, await GenericClass.StaticReturnClassType(value)); + Assert.Equal(value, await GenericClass.StaticReturnMethodType(value)); + Assert.Equal(value, await GenericClass.StaticReturnClassTypeAsync1(value)); + Assert.Equal(value, await GenericClass.StaticReturnMethodTypeAsync1(value)); + } +} + +public class GenericClass +{ + // 'this' is context + [MethodImpl(MethodImplOptions.NoInlining)] + public async2 Task InstanceMethod(Type t) + { + Assert.Equal(typeof(T), t); + await Task.Yield(); + Assert.Equal(typeof(T), t); + } + + // Class context + [MethodImpl(MethodImplOptions.NoInlining)] + public static async2 Task StaticMethod(Type t) + { + Assert.Equal(typeof(T), t); + await Task.Yield(); + Assert.Equal(typeof(T), t); } - private static async Task AsyncTestEntryPoint() + + // Method context + [MethodImpl(MethodImplOptions.NoInlining)] + public static async2 Task StaticMethod(Type t, Type tm) { - await Async2TestEntryPoint(); + Assert.Equal(typeof(T), t); + Assert.Equal(typeof(TM), tm); + await Task.Yield(); + Assert.Equal(typeof(T), t); + Assert.Equal(typeof(TM), tm); } - //This async method lacks 'await' -#pragma warning disable 1998 + // Class context + [MethodImpl(MethodImplOptions.NoInlining)] + public static async Task StaticMethodAsync1(Type t) + { + Assert.Equal(typeof(T), t); + await Task.Yield(); + Assert.Equal(typeof(T), t); + } + // Method context [MethodImpl(MethodImplOptions.NoInlining)] - private static async2 Task Async2TestEntryPoint() + public static async Task StaticMethodAsync1(Type t, Type tm) + { + Assert.Equal(typeof(T), t); + Assert.Equal(typeof(TM), tm); + await Task.Yield(); + Assert.Equal(typeof(T), t); + Assert.Equal(typeof(TM), tm); + } + + public static async2 Task StaticReturnClassType(T value) + { + await Task.Yield(); + return value; + } + + public static async2 Task StaticReturnMethodType(TM value) + { + await Task.Yield(); + return value; + } + + public static async Task StaticReturnClassTypeAsync1(T value) + { + await Task.Yield(); + return value; + } + + public static async Task StaticReturnMethodTypeAsync1(TM value) { - s_type = typeof(T); + await Task.Yield(); + return value; } } diff --git a/src/tests/Loader/async/expected_failures.txt b/src/tests/Loader/async/expected_failures.txt index 5fe6eb747541c8..045d3cfe1eb8ec 100644 --- a/src/tests/Loader/async/expected_failures.txt +++ b/src/tests/Loader/async/expected_failures.txt @@ -6,4 +6,4 @@ DOTNET_RuntimeAsyncViaJitGeneratedStateMachines=0: * Loader\async\async2sharedgeneric\async2sharedgeneric.dll (generics unsupported) DOTNET_RuntimeAsyncViaJitGeneratedStateMachines=1: -* Loader\async\async2sharedgeneric\async2sharedgeneric.dll (generics unsupported) \ No newline at end of file +* None \ No newline at end of file From 19ce23f0803bbbeabf088126a6ea423d1774435c Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Wed, 13 Nov 2024 10:20:59 +0100 Subject: [PATCH 122/203] [RuntimeAsync] Add x86 support (#2767) The main thing needed is that the JIT32 GC encoder has a hard limit on 4 epilogs, so the JIT needs to share suspension epilogs, and also needs to ensure we have at most 3 other normal epilogs when we get to the async2 transformation. --- src/coreclr/jit/async.cpp | 95 +++++++++++++++++++++----------- src/coreclr/jit/async.h | 1 + src/coreclr/jit/block.cpp | 5 +- src/coreclr/jit/fgdiagnostic.cpp | 8 ++- src/coreclr/jit/flowgraph.cpp | 13 ++++- src/coreclr/jit/lclvars.cpp | 6 +- src/coreclr/vm/i386/cgencpu.h | 6 +- src/coreclr/vm/jithelpers.cpp | 11 +++- src/coreclr/vm/jitinterface.cpp | 4 ++ src/coreclr/vm/prestub.cpp | 9 ++- 10 files changed, 111 insertions(+), 47 deletions(-) diff --git a/src/coreclr/jit/async.cpp b/src/coreclr/jit/async.cpp index 46cd7e4c1cabf6..a636af937252f5 100644 --- a/src/coreclr/jit/async.cpp +++ b/src/coreclr/jit/async.cpp @@ -192,6 +192,22 @@ PhaseStatus Async2Transformation::Run() m_objectClsHnd = m_comp->info.compCompHnd->getBuiltinClass(CLASSID_SYSTEM_OBJECT); m_byteClsHnd = m_comp->info.compCompHnd->getBuiltinClass(CLASSID_SYSTEM_BYTE); +#ifdef JIT32_GCENCODER + // Due to a hard cap on epilogs we need a shared return here. + m_sharedReturnBB = m_comp->fgNewBBafter(BBJ_RETURN, m_comp->fgLastBBInMainFunction(), false); + m_sharedReturnBB->bbSetRunRarely(); + m_sharedReturnBB->clearTryIndex(); + m_sharedReturnBB->clearHndIndex(); + + GenTree* continuation = m_comp->gtNewLclvNode(m_newContinuationVar, TYP_REF); + GenTree* ret = m_comp->gtNewOperNode(GT_RETURN_SUSPEND, TYP_VOID, continuation); + LIR::AsRange(m_sharedReturnBB).InsertAtEnd(continuation, ret); + + JITDUMP("Created shared return BB " FMT_BB "\n", m_sharedReturnBB->bbNum); + + DISPRANGE(LIR::AsRange(m_sharedReturnBB)); +#endif + if (m_comp->opts.OptimizationEnabled()) { if (m_comp->m_dfsTree == nullptr) @@ -418,13 +434,6 @@ void Async2Transformation::Transform( returnInGCData = varTypeIsGC(call->gtReturnType); } - if (returnSize > 0) - { - JITDUMP(" Will store return of type %s, size %u in %sGC data\n", - call->gtReturnType == TYP_STRUCT ? returnStructLayout->GetClassName() : varTypeName(call->gtReturnType), - returnSize, returnInGCData ? "" : "non-"); - } - assert((returnSize > 0) == (call->gtReturnType != TYP_VOID)); // The return value is always stored: @@ -441,9 +450,25 @@ void Async2Transformation::Transform( { returnValDataOffset = dataSize; dataSize += returnSize; + } + +#ifdef DEBUG + if (returnSize > 0) + { + JITDUMP(" Will store return of type %s, size %u in", + call->gtReturnType == TYP_STRUCT ? returnStructLayout->GetClassName() : varTypeName(call->gtReturnType), + returnSize); - JITDUMP(" at offset %u\n", returnValDataOffset); + if (returnInGCData) + { + JITDUMP(" GC data\n"); + } + else + { + JITDUMP(" non-GC data at offset %u\n", returnValDataOffset); + } } +#endif unsigned exceptionGCDataIndex = UINT_MAX; if (block->hasTryIndex()) @@ -543,15 +568,20 @@ void Async2Transformation::Transform( m_lastSuspensionBB = m_comp->fgLastBBInMainFunction(); } - BasicBlock* retBB = m_comp->fgNewBBafter(BBJ_RETURN, m_lastSuspensionBB, false); - retBB->bbSetRunRarely(); - retBB->clearTryIndex(); - retBB->clearHndIndex(); - m_lastSuspensionBB = retBB; + BasicBlock* suspendBB = m_comp->fgNewBBafter(BBJ_RETURN, m_lastSuspensionBB, false); + suspendBB->bbSetRunRarely(); + suspendBB->clearTryIndex(); + suspendBB->clearHndIndex(); + m_lastSuspensionBB = suspendBB; + + if (m_sharedReturnBB != nullptr) + { + suspendBB->SetKindAndTargetEdge(BBJ_ALWAYS, m_comp->fgAddRefPred(m_sharedReturnBB, suspendBB)); + } - JITDUMP(" Created suspension " FMT_BB " for state %u\n", retBB->bbNum, stateNum); + JITDUMP(" Created suspension " FMT_BB " for state %u\n", suspendBB->bbNum, stateNum); - FlowEdge* retBBEdge = m_comp->fgAddRefPred(retBB, block); + FlowEdge* retBBEdge = m_comp->fgAddRefPred(suspendBB, block); block->SetCond(retBBEdge, block->GetTargetEdge()); block->GetTrueEdge()->setLikelihood(RESUME_SUSPEND_LIKELIHOOD); @@ -564,27 +594,27 @@ void Async2Transformation::Transform( GenTreeCall* allocContinuation = m_comp->gtNewHelperCallNode(CORINFO_HELP_ALLOC_CONTINUATION, TYP_REF, returnedContinuation, gcRefsCountNode, dataSizeNode); - m_comp->compCurBB = retBB; + m_comp->compCurBB = suspendBB; m_comp->fgMorphTree(allocContinuation); - LIR::AsRange(retBB).InsertAtEnd(LIR::SeqTree(m_comp, allocContinuation)); + LIR::AsRange(suspendBB).InsertAtEnd(LIR::SeqTree(m_comp, allocContinuation)); GenTree* storeNewContinuation = m_comp->gtNewStoreLclVarNode(m_newContinuationVar, allocContinuation); - LIR::AsRange(retBB).InsertAtEnd(storeNewContinuation); + LIR::AsRange(suspendBB).InsertAtEnd(storeNewContinuation); // Fill in 'Resume' GenTree* newContinuation = m_comp->gtNewLclvNode(m_newContinuationVar, TYP_REF); unsigned resumeOffset = m_comp->info.compCompHnd->getFieldOffset(m_async2Info.continuationResumeFldHnd); GenTree* resumeStubAddr = CreateResumptionStubAddrTree(); GenTree* storeResume = StoreAtOffset(newContinuation, resumeOffset, resumeStubAddr); - LIR::AsRange(retBB).InsertAtEnd(LIR::SeqTree(m_comp, storeResume)); + LIR::AsRange(suspendBB).InsertAtEnd(LIR::SeqTree(m_comp, storeResume)); // Fill in 'state' newContinuation = m_comp->gtNewLclvNode(m_newContinuationVar, TYP_REF); unsigned stateOffset = m_comp->info.compCompHnd->getFieldOffset(m_async2Info.continuationStateFldHnd); GenTree* stateNumNode = m_comp->gtNewIconNode((ssize_t)stateNum, TYP_INT); GenTree* storeState = StoreAtOffset(newContinuation, stateOffset, stateNumNode); - LIR::AsRange(retBB).InsertAtEnd(LIR::SeqTree(m_comp, storeState)); + LIR::AsRange(suspendBB).InsertAtEnd(LIR::SeqTree(m_comp, storeState)); // Fill in 'flags' unsigned continuationFlags = 0; @@ -599,7 +629,7 @@ void Async2Transformation::Transform( unsigned flagsOffset = m_comp->info.compCompHnd->getFieldOffset(m_async2Info.continuationFlagsFldHnd); GenTree* flagsNode = m_comp->gtNewIconNode((ssize_t)continuationFlags, TYP_INT); GenTree* storeFlags = StoreAtOffset(newContinuation, flagsOffset, flagsNode); - LIR::AsRange(retBB).InsertAtEnd(LIR::SeqTree(m_comp, storeFlags)); + LIR::AsRange(suspendBB).InsertAtEnd(LIR::SeqTree(m_comp, storeFlags)); // Fill in GC pointers if (gcRefsCount > 0) @@ -610,7 +640,7 @@ void Async2Transformation::Transform( unsigned gcDataOffset = m_comp->info.compCompHnd->getFieldOffset(m_async2Info.continuationGCDataFldHnd); GenTree* gcDataInd = LoadFromOffset(newContinuation, gcDataOffset, TYP_REF); GenTree* storeAllocedObjectArr = m_comp->gtNewStoreLclVarNode(objectArrLclNum, gcDataInd); - LIR::AsRange(retBB).InsertAtEnd(LIR::SeqTree(m_comp, storeAllocedObjectArr)); + LIR::AsRange(suspendBB).InsertAtEnd(LIR::SeqTree(m_comp, storeAllocedObjectArr)); for (LiveLocalInfo& inf : m_liveLocals) { @@ -627,7 +657,7 @@ void Async2Transformation::Transform( GenTree* store = StoreAtOffset(objectArr, OFFSETOF__CORINFO_Array__data + (inf.GCDataIndex * TARGET_POINTER_SIZE), value); - LIR::AsRange(retBB).InsertAtEnd(LIR::SeqTree(m_comp, store)); + LIR::AsRange(suspendBB).InsertAtEnd(LIR::SeqTree(m_comp, store)); } else { @@ -659,7 +689,7 @@ void Async2Transformation::Transform( unsigned offset = OFFSETOF__CORINFO_Array__data + ((inf.GCDataIndex + gcRefIndex) * TARGET_POINTER_SIZE); GenTree* store = StoreAtOffset(objectArr, offset, value); - LIR::AsRange(retBB).InsertAtEnd(LIR::SeqTree(m_comp, store)); + LIR::AsRange(suspendBB).InsertAtEnd(LIR::SeqTree(m_comp, store)); gcRefIndex++; @@ -678,7 +708,7 @@ void Async2Transformation::Transform( store = m_comp->gtNewStoreLclFldNode(inf.LclNum, TYP_REF, i * TARGET_POINTER_SIZE, null); } - LIR::AsRange(retBB).InsertAtEnd(LIR::SeqTree(m_comp, store)); + LIR::AsRange(suspendBB).InsertAtEnd(LIR::SeqTree(m_comp, store)); } } @@ -696,7 +726,7 @@ void Async2Transformation::Transform( unsigned dataOffset = m_comp->info.compCompHnd->getFieldOffset(m_async2Info.continuationDataFldHnd); GenTree* dataInd = LoadFromOffset(newContinuation, dataOffset, TYP_REF); GenTree* storeAllocedByteArr = m_comp->gtNewStoreLclVarNode(byteArrLclNum, dataInd); - LIR::AsRange(retBB).InsertAtEnd(LIR::SeqTree(m_comp, storeAllocedByteArr)); + LIR::AsRange(suspendBB).InsertAtEnd(LIR::SeqTree(m_comp, storeAllocedByteArr)); if (m_comp->doesMethodHavePatchpoints() || m_comp->opts.IsOSR()) { @@ -709,7 +739,7 @@ void Async2Transformation::Transform( GenTree* byteArr = m_comp->gtNewLclvNode(byteArrLclNum, TYP_REF); unsigned offset = OFFSETOF__CORINFO_Array__data; GenTree* storePatchpointOffset = StoreAtOffset(byteArr, offset, ilOffsetToStore); - LIR::AsRange(retBB).InsertAtEnd(LIR::SeqTree(m_comp, storePatchpointOffset)); + LIR::AsRange(suspendBB).InsertAtEnd(LIR::SeqTree(m_comp, storePatchpointOffset)); } // Fill in data @@ -751,13 +781,16 @@ void Async2Transformation::Transform( store = StoreAtOffset(byteArr, offset, value); } - LIR::AsRange(retBB).InsertAtEnd(LIR::SeqTree(m_comp, store)); + LIR::AsRange(suspendBB).InsertAtEnd(LIR::SeqTree(m_comp, store)); } } - newContinuation = m_comp->gtNewLclvNode(m_newContinuationVar, TYP_REF); - GenTree* ret = m_comp->gtNewOperNode(GT_RETURN_SUSPEND, TYP_VOID, newContinuation); - LIR::AsRange(retBB).InsertAtEnd(newContinuation, ret); + if (suspendBB->KindIs(BBJ_RETURN)) + { + newContinuation = m_comp->gtNewLclvNode(m_newContinuationVar, TYP_REF); + GenTree* ret = m_comp->gtNewOperNode(GT_RETURN_SUSPEND, TYP_VOID, newContinuation); + LIR::AsRange(suspendBB).InsertAtEnd(newContinuation, ret); + } if (m_lastResumptionBB == nullptr) { diff --git a/src/coreclr/jit/async.h b/src/coreclr/jit/async.h index b76330bb272456..377ab1a0b56692 100644 --- a/src/coreclr/jit/async.h +++ b/src/coreclr/jit/async.h @@ -32,6 +32,7 @@ class Async2Transformation unsigned m_exceptionVar = BAD_VAR_NUM; BasicBlock* m_lastSuspensionBB = nullptr; BasicBlock* m_lastResumptionBB = nullptr; + BasicBlock* m_sharedReturnBB = nullptr; void LiftLIREdges(BasicBlock* block, GenTree* beyond, diff --git a/src/coreclr/jit/block.cpp b/src/coreclr/jit/block.cpp index fec2250d73fa46..fd9c54545b6ef8 100644 --- a/src/coreclr/jit/block.cpp +++ b/src/coreclr/jit/block.cpp @@ -494,6 +494,7 @@ void BasicBlock::dspFlags() const {BBF_HAS_ALIGN, "has-align"}, {BBF_HAS_MDARRAYREF, "mdarr"}, {BBF_NEEDS_GCPOLL, "gcpoll"}, + {BBF_ASYNC_RESUMPTION, "resume"}, }; bool first = true; @@ -509,10 +510,6 @@ void BasicBlock::dspFlags() const first = false; } } - if (bbFlags & BBF_ASYNC_RESUMPTION) - { - printf("resume "); - } } /***************************************************************************** diff --git a/src/coreclr/jit/fgdiagnostic.cpp b/src/coreclr/jit/fgdiagnostic.cpp index 4ecaa9f183f275..ab14613248bd20 100644 --- a/src/coreclr/jit/fgdiagnostic.cpp +++ b/src/coreclr/jit/fgdiagnostic.cpp @@ -2726,6 +2726,9 @@ bool BBPredsChecker::CheckEhTryDsc(BasicBlock* block, BasicBlock* blockPred, EHb return true; } + // Async resumptions are allowed to jump into try blocks at any point. They + // are introduced late enough that the invariant of single entry is no + // longer necessary. if (blockPred->HasFlag(BBF_ASYNC_RESUMPTION)) { return true; @@ -3122,7 +3125,8 @@ void Compiler::fgDebugCheckBBlist(bool checkBBNum /* = false */, bool checkBBRef // A branch or fall-through to a BBJ_CALLFINALLY block must come from the `try` region associated // with the finally block the BBJ_CALLFINALLY is targeting. There is one special case: if the // BBJ_CALLFINALLY is the first block of a `try`, then its predecessor can be outside the `try`: - // either a branch or fall-through to the first block. + // either a branch or fall-through to the first block. Similarly internal resumption blocks for + // async2 are allowed to do this as they are introduced late enough that we no longer need the invariant. // // Note that this IR condition is a choice. It naturally occurs when importing EH constructs. // This condition prevents flow optimizations from skipping blocks in a `try` and branching @@ -3160,7 +3164,7 @@ void Compiler::fgDebugCheckBBlist(bool checkBBNum /* = false */, bool checkBBRef } else { - assert(bbInTryRegions(finallyIndex, block)); + assert(bbInTryRegions(finallyIndex, block) || block->HasFlag(BBF_ASYNC_RESUMPTION)); } } } diff --git a/src/coreclr/jit/flowgraph.cpp b/src/coreclr/jit/flowgraph.cpp index ee07f03599e878..9cfd9e3291a1be 100644 --- a/src/coreclr/jit/flowgraph.cpp +++ b/src/coreclr/jit/flowgraph.cpp @@ -2420,7 +2420,18 @@ PhaseStatus Compiler::fgAddInternal() } else { - merger.SetMaxReturns(MergedReturns::ReturnCountHardLimit); + unsigned limit = MergedReturns::ReturnCountHardLimit; +#ifdef JIT32_GCENCODER + // For the jit32 GC encoder the limit is an actual hard limit. In + // async2 functions we will be introducing another return during + // the async2 transformation, so make sure there's a free epilog + // for it. + if (compIsAsync2()) + { + limit--; + } +#endif + merger.SetMaxReturns(limit); } } diff --git a/src/coreclr/jit/lclvars.cpp b/src/coreclr/jit/lclvars.cpp index c53ad722a50593..d58445d6c95db8 100644 --- a/src/coreclr/jit/lclvars.cpp +++ b/src/coreclr/jit/lclvars.cpp @@ -1499,7 +1499,10 @@ void Compiler::lvaInitAsyncContinuation(InitVarDscInfo* varDscInfo) lvaAsyncContinuationArg = varDscInfo->varNum; LclVarDsc* varDsc = varDscInfo->varDsc; varDsc->lvType = TYP_REF; - varDsc->lvIsParam = 1; + varDsc->lvIsParam = true; + + // The final home for this incoming register might be our local stack frame + varDsc->lvOnFrame = true; #ifdef DEBUG varDsc->lvReason = "Async continuation arg"; @@ -1514,7 +1517,6 @@ void Compiler::lvaInitAsyncContinuation(InitVarDscInfo* varDscInfo) #if FEATURE_MULTIREG_ARGS varDsc->SetOtherArgReg(REG_NA); #endif - varDsc->lvOnFrame = true; // The final home for this incoming register might be our local stack frame varDscInfo->intRegArgNum++; diff --git a/src/coreclr/vm/i386/cgencpu.h b/src/coreclr/vm/i386/cgencpu.h index b87527be323331..a6ec982c021e61 100644 --- a/src/coreclr/vm/i386/cgencpu.h +++ b/src/coreclr/vm/i386/cgencpu.h @@ -436,7 +436,11 @@ struct HijackArgs DWORD Esi; DWORD Ebx; DWORD Edx; - DWORD Ecx; + union + { + DWORD Ecx; + size_t AsyncRet; + }; union { DWORD Eax; diff --git a/src/coreclr/vm/jithelpers.cpp b/src/coreclr/vm/jithelpers.cpp index b09e04d2795a61..30612106ef6e63 100644 --- a/src/coreclr/vm/jithelpers.cpp +++ b/src/coreclr/vm/jithelpers.cpp @@ -2644,7 +2644,7 @@ HCIMPL1(void, IL_ThrowExact, Object* obj) OBJECTREF oref = ObjectToOBJECTREF(obj); #if defined(_DEBUG) && defined(TARGET_X86) - __helperframe.InsureInit(false, NULL); + __helperframe.InsureInit(NULL); g_ExceptionEIP = (LPVOID)__helperframe.GetReturnAddress(); #endif // defined(_DEBUG) && defined(TARGET_X86) @@ -3928,6 +3928,15 @@ HCIMPL1(VOID, JIT_PartialCompilationPatchpoint, int ilOffset) } HCIMPLEND +void JIT_ResumeOSR(unsigned ilOffset) +{ + // Stub version if OSR feature is disabled + // + // Should not be called. + + UNREACHABLE(); +} + #endif // FEATURE_ON_STACK_REPLACEMENT static unsigned HandleHistogramProfileRand() diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index d3b7cd5d437621..8bc995514988da 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -14514,12 +14514,16 @@ CORINFO_METHOD_HANDLE CEEJitInfo::getAsyncResumptionStub() 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 + _ASSERTE(!"Unexpected optimization tier with OSR disabled"); +#endif } else { diff --git a/src/coreclr/vm/prestub.cpp b/src/coreclr/vm/prestub.cpp index 459dc754db3768..ea4ab4f6d6efa8 100644 --- a/src/coreclr/vm/prestub.cpp +++ b/src/coreclr/vm/prestub.cpp @@ -2639,8 +2639,7 @@ void MethodDesc::CreateDerivedTargetSigWithExtraParams(MetaSig& msig, SigBuilder stubSigBuilder->AppendByte(callingConvention); unsigned numArgs = msig.NumFixedArgs(); - bool hasInstParam = msig.HasGenericContextArg() != FALSE; - if (hasInstParam) + if (msig.HasGenericContextArg()) numArgs++; if (msig.HasAsyncContinuation()) numArgs++; @@ -2652,7 +2651,7 @@ void MethodDesc::CreateDerivedTargetSigWithExtraParams(MetaSig& msig, SigBuilder pReturn.ConvertToInternalExactlyOne(msig.GetModule(), msig.GetSigTypeContext(), stubSigBuilder); #ifndef TARGET_X86 - if (hasInstParam) + if (msig.HasGenericContextArg()) { // The hidden context parameter stubSigBuilder->AppendElementType(ELEMENT_TYPE_I); @@ -2673,13 +2672,13 @@ void MethodDesc::CreateDerivedTargetSigWithExtraParams(MetaSig& msig, SigBuilder } #ifdef TARGET_X86 - if (hasInstParam) + if (msig.HasGenericContextArg()) { // The hidden context parameter stubSigBuilder->AppendElementType(ELEMENT_TYPE_I); } - if (hasAsyncCont) + if (msig.HasAsyncContinuation()) { stubSigBuilder->AppendElementType(ELEMENT_TYPE_OBJECT); } From 0c6694130a7e228d85b67b473b8e7f0fed4fccaf Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Thu, 14 Nov 2024 11:12:59 +0100 Subject: [PATCH 123/203] [RuntimeAsync] Clean up and fix async2 tests (#2768) - Remove the BuildOnly benchmark tests from the test wrapper - Remove all `RequiresProcessIsolation`, since none of these tests should require process isolation (they can be built standalone if desired) - Mark all the tests as optimized, since we get more coverage that way (tier0/tier1 testing will cover with and without JIT optimizations) - Rename all tests to remove the async2 prefix and use a consistent style - Move tests out of Loader subdirectory --- src/tests/Loader/async/async2implement.csproj | 8 ------- src/tests/Loader/async/async2override.csproj | 8 ------- .../Loader/async/async2sharedgeneric.csproj | 8 ------- src/tests/Loader/async/async2void.csproj | 8 ------- src/tests/Loader/async/asynctests.csproj | 9 -------- src/tests/async/asynctests.csproj | 21 +++++++++++++++++++ .../async/cse-array-index-byref.cs | 0 .../async/cse-array-index-byref.csproj | 1 - .../eh-microbench.cs} | 0 .../eh-microbench.csproj} | 1 - .../{Loader => }/async/expected_failures.txt | 0 .../fibonacci-with-yields-toplevel.cs} | 0 .../fibonacci-with-yields-toplevel.csproj} | 3 ++- .../fibonacci-with-yields.cs} | 0 .../fibonacci-with-yields.csproj} | 1 - .../fibonacci-without-yields.cs} | 0 .../fibonacci-without-yields.csproj} | 1 - .../gc-roots-scan.cs} | 0 .../gc-roots-scan.csproj} | 1 - .../async2implement.cs => async/implement.cs} | 0 .../implement.csproj} | 2 +- .../mincallcost-microbench.cs} | 0 .../mincallcost-microbench.csproj} | 1 - .../async/async2object.cs => async/object.cs} | 0 .../object.csproj} | 0 .../objects-captured.cs} | 0 .../objects-captured.csproj} | 0 .../async2override.cs => async/override.cs} | 0 .../override.csproj} | 2 +- .../async2-returns.cs => async/returns.cs} | 0 .../returns.csproj} | 2 +- .../shared-generic.cs} | 0 .../shared-generic.csproj} | 2 +- src/tests/{Loader => }/async/simple-eh.cs | 0 src/tests/{Loader => }/async/simple-eh.csproj | 1 - .../async/syncfibonacci-with-yields.cs | 0 .../async/syncfibonacci-with-yields.csproj | 1 - .../async/syncfibonacci-without-yields.cs | 0 .../async/syncfibonacci-without-yields.csproj | 1 - .../taskbased-asyncfibonacci-with-yields.cs | 0 ...askbased-asyncfibonacci-with-yields.csproj | 1 - ...taskbased-asyncfibonacci-without-yields.cs | 0 ...based-asyncfibonacci-without-yields.csproj | 1 - .../async2valuetask.cs => async/valuetask.cs} | 0 .../valuetask.csproj} | 2 +- ...luetaskbased-asyncfibonacci-with-yields.cs | 0 ...askbased-asyncfibonacci-with-yields.csproj | 1 - ...taskbased-asyncfibonacci-without-yields.cs | 0 ...based-asyncfibonacci-without-yields.csproj | 1 - .../varying-yields-task.csproj} | 2 +- .../varying-yields-valuetask.csproj} | 2 +- .../varying-yields.cs} | 0 src/tests/async/varying-yields.csproj | 8 +++++++ .../async/async2void.cs => async/void.cs} | 0 src/tests/async/void.csproj | 8 +++++++ .../with-yields.cs} | 0 src/tests/async/with-yields.csproj | 8 +++++++ .../without-yields.cs} | 0 src/tests/async/without-yields.csproj | 8 +++++++ 59 files changed, 62 insertions(+), 62 deletions(-) delete mode 100644 src/tests/Loader/async/async2implement.csproj delete mode 100644 src/tests/Loader/async/async2override.csproj delete mode 100644 src/tests/Loader/async/async2sharedgeneric.csproj delete mode 100644 src/tests/Loader/async/async2void.csproj delete mode 100644 src/tests/Loader/async/asynctests.csproj create mode 100644 src/tests/async/asynctests.csproj rename src/tests/{Loader => }/async/cse-array-index-byref.cs (100%) rename src/tests/{Loader => }/async/cse-array-index-byref.csproj (85%) rename src/tests/{Loader/async/async2-eh-microbench.cs => async/eh-microbench.cs} (100%) rename src/tests/{Loader/async/async2-eh-microbench.csproj => async/eh-microbench.csproj} (81%) rename src/tests/{Loader => }/async/expected_failures.txt (100%) rename src/tests/{Loader/async/async2fibonacci-with-yields-topLevel.cs => async/fibonacci-with-yields-toplevel.cs} (100%) rename src/tests/{Loader/async/async2fibonacci-without-yields.csproj => async/fibonacci-with-yields-toplevel.csproj} (64%) rename src/tests/{Loader/async/async2fibonacci-with-yields.cs => async/fibonacci-with-yields.cs} (100%) rename src/tests/{Loader/async/async2fibonacci-with-yields-topLevel.csproj => async/fibonacci-with-yields.csproj} (75%) rename src/tests/{Loader/async/async2fibonacci-without-yields.cs => async/fibonacci-without-yields.cs} (100%) rename src/tests/{Loader/async/async2fibonacci-with-yields.csproj => async/fibonacci-without-yields.csproj} (75%) rename src/tests/{Loader/async/async2gcRootsScan.cs => async/gc-roots-scan.cs} (100%) rename src/tests/{Loader/async/async2gcRootsScan.csproj => async/gc-roots-scan.csproj} (81%) rename src/tests/{Loader/async/async2implement.cs => async/implement.cs} (100%) rename src/tests/{Loader/async/async2valuetask.csproj => async/implement.csproj} (72%) rename src/tests/{Loader/async/async2-mincallcost-microbench.cs => async/mincallcost-microbench.cs} (100%) rename src/tests/{Loader/async/async2-mincallcost-microbench.csproj => async/mincallcost-microbench.csproj} (81%) rename src/tests/{Loader/async/async2object.cs => async/object.cs} (100%) rename src/tests/{Loader/async/async2-returns.csproj => async/object.csproj} (100%) rename src/tests/{Loader/async/async2objectsCaptured.cs => async/objects-captured.cs} (100%) rename src/tests/{Loader/async/async2-varying-yields.csproj => async/objects-captured.csproj} (100%) rename src/tests/{Loader/async/async2override.cs => async/override.cs} (100%) rename src/tests/{Loader/async/async2object.csproj => async/override.csproj} (72%) rename src/tests/{Loader/async/async2-returns.cs => async/returns.cs} (100%) rename src/tests/{Loader/async/async2objectsCaptured.csproj => async/returns.csproj} (72%) rename src/tests/{Loader/async/async2sharedgeneric.cs => async/shared-generic.cs} (100%) rename src/tests/{Loader/async/async2-without-yields.csproj => async/shared-generic.csproj} (72%) rename src/tests/{Loader => }/async/simple-eh.cs (100%) rename src/tests/{Loader => }/async/simple-eh.csproj (85%) rename src/tests/{Loader => }/async/syncfibonacci-with-yields.cs (100%) rename src/tests/{Loader => }/async/syncfibonacci-with-yields.csproj (81%) rename src/tests/{Loader => }/async/syncfibonacci-without-yields.cs (100%) rename src/tests/{Loader => }/async/syncfibonacci-without-yields.csproj (81%) rename src/tests/{Loader => }/async/taskbased-asyncfibonacci-with-yields.cs (100%) rename src/tests/{Loader => }/async/taskbased-asyncfibonacci-with-yields.csproj (81%) rename src/tests/{Loader => }/async/taskbased-asyncfibonacci-without-yields.cs (100%) rename src/tests/{Loader => }/async/taskbased-asyncfibonacci-without-yields.csproj (81%) rename src/tests/{Loader/async/async2valuetask.cs => async/valuetask.cs} (100%) rename src/tests/{Loader/async/async2-with-yields.csproj => async/valuetask.csproj} (72%) rename src/tests/{Loader => }/async/valuetaskbased-asyncfibonacci-with-yields.cs (100%) rename src/tests/{Loader => }/async/valuetaskbased-asyncfibonacci-with-yields.csproj (81%) rename src/tests/{Loader => }/async/valuetaskbased-asyncfibonacci-without-yields.cs (100%) rename src/tests/{Loader => }/async/valuetaskbased-asyncfibonacci-without-yields.csproj (81%) rename src/tests/{Loader/async/async2-varying-yields-task.csproj => async/varying-yields-task.csproj} (80%) rename src/tests/{Loader/async/async2-varying-yields-valuetask.csproj => async/varying-yields-valuetask.csproj} (81%) rename src/tests/{Loader/async/async2-varying-yields.cs => async/varying-yields.cs} (100%) create mode 100644 src/tests/async/varying-yields.csproj rename src/tests/{Loader/async/async2void.cs => async/void.cs} (100%) create mode 100644 src/tests/async/void.csproj rename src/tests/{Loader/async/async2-with-yields.cs => async/with-yields.cs} (100%) create mode 100644 src/tests/async/with-yields.csproj rename src/tests/{Loader/async/async2-without-yields.cs => async/without-yields.cs} (100%) create mode 100644 src/tests/async/without-yields.csproj diff --git a/src/tests/Loader/async/async2implement.csproj b/src/tests/Loader/async/async2implement.csproj deleted file mode 100644 index 0f29ed860df43e..00000000000000 --- a/src/tests/Loader/async/async2implement.csproj +++ /dev/null @@ -1,8 +0,0 @@ - - - true - - - - - diff --git a/src/tests/Loader/async/async2override.csproj b/src/tests/Loader/async/async2override.csproj deleted file mode 100644 index 0f29ed860df43e..00000000000000 --- a/src/tests/Loader/async/async2override.csproj +++ /dev/null @@ -1,8 +0,0 @@ - - - true - - - - - diff --git a/src/tests/Loader/async/async2sharedgeneric.csproj b/src/tests/Loader/async/async2sharedgeneric.csproj deleted file mode 100644 index 0f29ed860df43e..00000000000000 --- a/src/tests/Loader/async/async2sharedgeneric.csproj +++ /dev/null @@ -1,8 +0,0 @@ - - - true - - - - - diff --git a/src/tests/Loader/async/async2void.csproj b/src/tests/Loader/async/async2void.csproj deleted file mode 100644 index 6607f198fc54fb..00000000000000 --- a/src/tests/Loader/async/async2void.csproj +++ /dev/null @@ -1,8 +0,0 @@ - - - true - - - - - diff --git a/src/tests/Loader/async/asynctests.csproj b/src/tests/Loader/async/asynctests.csproj deleted file mode 100644 index be189042ee44c3..00000000000000 --- a/src/tests/Loader/async/asynctests.csproj +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/src/tests/async/asynctests.csproj b/src/tests/async/asynctests.csproj new file mode 100644 index 00000000000000..265fe338158a83 --- /dev/null +++ b/src/tests/async/asynctests.csproj @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/src/tests/Loader/async/cse-array-index-byref.cs b/src/tests/async/cse-array-index-byref.cs similarity index 100% rename from src/tests/Loader/async/cse-array-index-byref.cs rename to src/tests/async/cse-array-index-byref.cs diff --git a/src/tests/Loader/async/cse-array-index-byref.csproj b/src/tests/async/cse-array-index-byref.csproj similarity index 85% rename from src/tests/Loader/async/cse-array-index-byref.csproj rename to src/tests/async/cse-array-index-byref.csproj index 501217e4d86892..de6d5e08882e86 100644 --- a/src/tests/Loader/async/cse-array-index-byref.csproj +++ b/src/tests/async/cse-array-index-byref.csproj @@ -1,6 +1,5 @@ - None True diff --git a/src/tests/Loader/async/async2-eh-microbench.cs b/src/tests/async/eh-microbench.cs similarity index 100% rename from src/tests/Loader/async/async2-eh-microbench.cs rename to src/tests/async/eh-microbench.cs diff --git a/src/tests/Loader/async/async2-eh-microbench.csproj b/src/tests/async/eh-microbench.csproj similarity index 81% rename from src/tests/Loader/async/async2-eh-microbench.csproj rename to src/tests/async/eh-microbench.csproj index f7a2511415dff2..1f1906fd50a23e 100644 --- a/src/tests/Loader/async/async2-eh-microbench.csproj +++ b/src/tests/async/eh-microbench.csproj @@ -1,7 +1,6 @@ True - true BuildOnly diff --git a/src/tests/Loader/async/expected_failures.txt b/src/tests/async/expected_failures.txt similarity index 100% rename from src/tests/Loader/async/expected_failures.txt rename to src/tests/async/expected_failures.txt diff --git a/src/tests/Loader/async/async2fibonacci-with-yields-topLevel.cs b/src/tests/async/fibonacci-with-yields-toplevel.cs similarity index 100% rename from src/tests/Loader/async/async2fibonacci-with-yields-topLevel.cs rename to src/tests/async/fibonacci-with-yields-toplevel.cs diff --git a/src/tests/Loader/async/async2fibonacci-without-yields.csproj b/src/tests/async/fibonacci-with-yields-toplevel.csproj similarity index 64% rename from src/tests/Loader/async/async2fibonacci-without-yields.csproj rename to src/tests/async/fibonacci-with-yields-toplevel.csproj index 4a1973da13c789..14c1fed92f82b6 100644 --- a/src/tests/Loader/async/async2fibonacci-without-yields.csproj +++ b/src/tests/async/fibonacci-with-yields-toplevel.csproj @@ -1,6 +1,7 @@ - true + + True True diff --git a/src/tests/Loader/async/async2fibonacci-with-yields.cs b/src/tests/async/fibonacci-with-yields.cs similarity index 100% rename from src/tests/Loader/async/async2fibonacci-with-yields.cs rename to src/tests/async/fibonacci-with-yields.cs diff --git a/src/tests/Loader/async/async2fibonacci-with-yields-topLevel.csproj b/src/tests/async/fibonacci-with-yields.csproj similarity index 75% rename from src/tests/Loader/async/async2fibonacci-with-yields-topLevel.csproj rename to src/tests/async/fibonacci-with-yields.csproj index 4a1973da13c789..de6d5e08882e86 100644 --- a/src/tests/Loader/async/async2fibonacci-with-yields-topLevel.csproj +++ b/src/tests/async/fibonacci-with-yields.csproj @@ -1,6 +1,5 @@ - true True diff --git a/src/tests/Loader/async/async2fibonacci-without-yields.cs b/src/tests/async/fibonacci-without-yields.cs similarity index 100% rename from src/tests/Loader/async/async2fibonacci-without-yields.cs rename to src/tests/async/fibonacci-without-yields.cs diff --git a/src/tests/Loader/async/async2fibonacci-with-yields.csproj b/src/tests/async/fibonacci-without-yields.csproj similarity index 75% rename from src/tests/Loader/async/async2fibonacci-with-yields.csproj rename to src/tests/async/fibonacci-without-yields.csproj index 4a1973da13c789..de6d5e08882e86 100644 --- a/src/tests/Loader/async/async2fibonacci-with-yields.csproj +++ b/src/tests/async/fibonacci-without-yields.csproj @@ -1,6 +1,5 @@ - true True diff --git a/src/tests/Loader/async/async2gcRootsScan.cs b/src/tests/async/gc-roots-scan.cs similarity index 100% rename from src/tests/Loader/async/async2gcRootsScan.cs rename to src/tests/async/gc-roots-scan.cs diff --git a/src/tests/Loader/async/async2gcRootsScan.csproj b/src/tests/async/gc-roots-scan.csproj similarity index 81% rename from src/tests/Loader/async/async2gcRootsScan.csproj rename to src/tests/async/gc-roots-scan.csproj index b6e7e75bd6ebc6..f7972beb1c4a45 100644 --- a/src/tests/Loader/async/async2gcRootsScan.csproj +++ b/src/tests/async/gc-roots-scan.csproj @@ -1,7 +1,6 @@ True - true BuildOnly diff --git a/src/tests/Loader/async/async2implement.cs b/src/tests/async/implement.cs similarity index 100% rename from src/tests/Loader/async/async2implement.cs rename to src/tests/async/implement.cs diff --git a/src/tests/Loader/async/async2valuetask.csproj b/src/tests/async/implement.csproj similarity index 72% rename from src/tests/Loader/async/async2valuetask.csproj rename to src/tests/async/implement.csproj index 0f29ed860df43e..1ae294349c376f 100644 --- a/src/tests/Loader/async/async2valuetask.csproj +++ b/src/tests/async/implement.csproj @@ -1,6 +1,6 @@ - true + True diff --git a/src/tests/Loader/async/async2-mincallcost-microbench.cs b/src/tests/async/mincallcost-microbench.cs similarity index 100% rename from src/tests/Loader/async/async2-mincallcost-microbench.cs rename to src/tests/async/mincallcost-microbench.cs diff --git a/src/tests/Loader/async/async2-mincallcost-microbench.csproj b/src/tests/async/mincallcost-microbench.csproj similarity index 81% rename from src/tests/Loader/async/async2-mincallcost-microbench.csproj rename to src/tests/async/mincallcost-microbench.csproj index f7a2511415dff2..1f1906fd50a23e 100644 --- a/src/tests/Loader/async/async2-mincallcost-microbench.csproj +++ b/src/tests/async/mincallcost-microbench.csproj @@ -1,7 +1,6 @@ True - true BuildOnly diff --git a/src/tests/Loader/async/async2object.cs b/src/tests/async/object.cs similarity index 100% rename from src/tests/Loader/async/async2object.cs rename to src/tests/async/object.cs diff --git a/src/tests/Loader/async/async2-returns.csproj b/src/tests/async/object.csproj similarity index 100% rename from src/tests/Loader/async/async2-returns.csproj rename to src/tests/async/object.csproj diff --git a/src/tests/Loader/async/async2objectsCaptured.cs b/src/tests/async/objects-captured.cs similarity index 100% rename from src/tests/Loader/async/async2objectsCaptured.cs rename to src/tests/async/objects-captured.cs diff --git a/src/tests/Loader/async/async2-varying-yields.csproj b/src/tests/async/objects-captured.csproj similarity index 100% rename from src/tests/Loader/async/async2-varying-yields.csproj rename to src/tests/async/objects-captured.csproj diff --git a/src/tests/Loader/async/async2override.cs b/src/tests/async/override.cs similarity index 100% rename from src/tests/Loader/async/async2override.cs rename to src/tests/async/override.cs diff --git a/src/tests/Loader/async/async2object.csproj b/src/tests/async/override.csproj similarity index 72% rename from src/tests/Loader/async/async2object.csproj rename to src/tests/async/override.csproj index 6607f198fc54fb..9367a79b2edbb1 100644 --- a/src/tests/Loader/async/async2object.csproj +++ b/src/tests/async/override.csproj @@ -1,6 +1,6 @@ - true + True diff --git a/src/tests/Loader/async/async2-returns.cs b/src/tests/async/returns.cs similarity index 100% rename from src/tests/Loader/async/async2-returns.cs rename to src/tests/async/returns.cs diff --git a/src/tests/Loader/async/async2objectsCaptured.csproj b/src/tests/async/returns.csproj similarity index 72% rename from src/tests/Loader/async/async2objectsCaptured.csproj rename to src/tests/async/returns.csproj index 6607f198fc54fb..9367a79b2edbb1 100644 --- a/src/tests/Loader/async/async2objectsCaptured.csproj +++ b/src/tests/async/returns.csproj @@ -1,6 +1,6 @@ - true + True diff --git a/src/tests/Loader/async/async2sharedgeneric.cs b/src/tests/async/shared-generic.cs similarity index 100% rename from src/tests/Loader/async/async2sharedgeneric.cs rename to src/tests/async/shared-generic.cs diff --git a/src/tests/Loader/async/async2-without-yields.csproj b/src/tests/async/shared-generic.csproj similarity index 72% rename from src/tests/Loader/async/async2-without-yields.csproj rename to src/tests/async/shared-generic.csproj index 6607f198fc54fb..9367a79b2edbb1 100644 --- a/src/tests/Loader/async/async2-without-yields.csproj +++ b/src/tests/async/shared-generic.csproj @@ -1,6 +1,6 @@ - true + True diff --git a/src/tests/Loader/async/simple-eh.cs b/src/tests/async/simple-eh.cs similarity index 100% rename from src/tests/Loader/async/simple-eh.cs rename to src/tests/async/simple-eh.cs diff --git a/src/tests/Loader/async/simple-eh.csproj b/src/tests/async/simple-eh.csproj similarity index 85% rename from src/tests/Loader/async/simple-eh.csproj rename to src/tests/async/simple-eh.csproj index 501217e4d86892..de6d5e08882e86 100644 --- a/src/tests/Loader/async/simple-eh.csproj +++ b/src/tests/async/simple-eh.csproj @@ -1,6 +1,5 @@ - None True diff --git a/src/tests/Loader/async/syncfibonacci-with-yields.cs b/src/tests/async/syncfibonacci-with-yields.cs similarity index 100% rename from src/tests/Loader/async/syncfibonacci-with-yields.cs rename to src/tests/async/syncfibonacci-with-yields.cs diff --git a/src/tests/Loader/async/syncfibonacci-with-yields.csproj b/src/tests/async/syncfibonacci-with-yields.csproj similarity index 81% rename from src/tests/Loader/async/syncfibonacci-with-yields.csproj rename to src/tests/async/syncfibonacci-with-yields.csproj index c089ac9ebc8bab..a7c0f53acc0cb4 100644 --- a/src/tests/Loader/async/syncfibonacci-with-yields.csproj +++ b/src/tests/async/syncfibonacci-with-yields.csproj @@ -1,7 +1,6 @@ True - true BuildOnly diff --git a/src/tests/Loader/async/syncfibonacci-without-yields.cs b/src/tests/async/syncfibonacci-without-yields.cs similarity index 100% rename from src/tests/Loader/async/syncfibonacci-without-yields.cs rename to src/tests/async/syncfibonacci-without-yields.cs diff --git a/src/tests/Loader/async/syncfibonacci-without-yields.csproj b/src/tests/async/syncfibonacci-without-yields.csproj similarity index 81% rename from src/tests/Loader/async/syncfibonacci-without-yields.csproj rename to src/tests/async/syncfibonacci-without-yields.csproj index c089ac9ebc8bab..a7c0f53acc0cb4 100644 --- a/src/tests/Loader/async/syncfibonacci-without-yields.csproj +++ b/src/tests/async/syncfibonacci-without-yields.csproj @@ -1,7 +1,6 @@ True - true BuildOnly diff --git a/src/tests/Loader/async/taskbased-asyncfibonacci-with-yields.cs b/src/tests/async/taskbased-asyncfibonacci-with-yields.cs similarity index 100% rename from src/tests/Loader/async/taskbased-asyncfibonacci-with-yields.cs rename to src/tests/async/taskbased-asyncfibonacci-with-yields.cs diff --git a/src/tests/Loader/async/taskbased-asyncfibonacci-with-yields.csproj b/src/tests/async/taskbased-asyncfibonacci-with-yields.csproj similarity index 81% rename from src/tests/Loader/async/taskbased-asyncfibonacci-with-yields.csproj rename to src/tests/async/taskbased-asyncfibonacci-with-yields.csproj index c089ac9ebc8bab..a7c0f53acc0cb4 100644 --- a/src/tests/Loader/async/taskbased-asyncfibonacci-with-yields.csproj +++ b/src/tests/async/taskbased-asyncfibonacci-with-yields.csproj @@ -1,7 +1,6 @@ True - true BuildOnly diff --git a/src/tests/Loader/async/taskbased-asyncfibonacci-without-yields.cs b/src/tests/async/taskbased-asyncfibonacci-without-yields.cs similarity index 100% rename from src/tests/Loader/async/taskbased-asyncfibonacci-without-yields.cs rename to src/tests/async/taskbased-asyncfibonacci-without-yields.cs diff --git a/src/tests/Loader/async/taskbased-asyncfibonacci-without-yields.csproj b/src/tests/async/taskbased-asyncfibonacci-without-yields.csproj similarity index 81% rename from src/tests/Loader/async/taskbased-asyncfibonacci-without-yields.csproj rename to src/tests/async/taskbased-asyncfibonacci-without-yields.csproj index c089ac9ebc8bab..a7c0f53acc0cb4 100644 --- a/src/tests/Loader/async/taskbased-asyncfibonacci-without-yields.csproj +++ b/src/tests/async/taskbased-asyncfibonacci-without-yields.csproj @@ -1,7 +1,6 @@ True - true BuildOnly diff --git a/src/tests/Loader/async/async2valuetask.cs b/src/tests/async/valuetask.cs similarity index 100% rename from src/tests/Loader/async/async2valuetask.cs rename to src/tests/async/valuetask.cs diff --git a/src/tests/Loader/async/async2-with-yields.csproj b/src/tests/async/valuetask.csproj similarity index 72% rename from src/tests/Loader/async/async2-with-yields.csproj rename to src/tests/async/valuetask.csproj index 6607f198fc54fb..9367a79b2edbb1 100644 --- a/src/tests/Loader/async/async2-with-yields.csproj +++ b/src/tests/async/valuetask.csproj @@ -1,6 +1,6 @@ - true + True diff --git a/src/tests/Loader/async/valuetaskbased-asyncfibonacci-with-yields.cs b/src/tests/async/valuetaskbased-asyncfibonacci-with-yields.cs similarity index 100% rename from src/tests/Loader/async/valuetaskbased-asyncfibonacci-with-yields.cs rename to src/tests/async/valuetaskbased-asyncfibonacci-with-yields.cs diff --git a/src/tests/Loader/async/valuetaskbased-asyncfibonacci-with-yields.csproj b/src/tests/async/valuetaskbased-asyncfibonacci-with-yields.csproj similarity index 81% rename from src/tests/Loader/async/valuetaskbased-asyncfibonacci-with-yields.csproj rename to src/tests/async/valuetaskbased-asyncfibonacci-with-yields.csproj index c089ac9ebc8bab..a7c0f53acc0cb4 100644 --- a/src/tests/Loader/async/valuetaskbased-asyncfibonacci-with-yields.csproj +++ b/src/tests/async/valuetaskbased-asyncfibonacci-with-yields.csproj @@ -1,7 +1,6 @@ True - true BuildOnly diff --git a/src/tests/Loader/async/valuetaskbased-asyncfibonacci-without-yields.cs b/src/tests/async/valuetaskbased-asyncfibonacci-without-yields.cs similarity index 100% rename from src/tests/Loader/async/valuetaskbased-asyncfibonacci-without-yields.cs rename to src/tests/async/valuetaskbased-asyncfibonacci-without-yields.cs diff --git a/src/tests/Loader/async/valuetaskbased-asyncfibonacci-without-yields.csproj b/src/tests/async/valuetaskbased-asyncfibonacci-without-yields.csproj similarity index 81% rename from src/tests/Loader/async/valuetaskbased-asyncfibonacci-without-yields.csproj rename to src/tests/async/valuetaskbased-asyncfibonacci-without-yields.csproj index c089ac9ebc8bab..a7c0f53acc0cb4 100644 --- a/src/tests/Loader/async/valuetaskbased-asyncfibonacci-without-yields.csproj +++ b/src/tests/async/valuetaskbased-asyncfibonacci-without-yields.csproj @@ -1,7 +1,6 @@ True - true BuildOnly diff --git a/src/tests/Loader/async/async2-varying-yields-task.csproj b/src/tests/async/varying-yields-task.csproj similarity index 80% rename from src/tests/Loader/async/async2-varying-yields-task.csproj rename to src/tests/async/varying-yields-task.csproj index 6eb7985558de71..f4098eb1998351 100644 --- a/src/tests/Loader/async/async2-varying-yields-task.csproj +++ b/src/tests/async/varying-yields-task.csproj @@ -4,6 +4,6 @@ ASYNC1_TASK;$(DefineConstants) - + diff --git a/src/tests/Loader/async/async2-varying-yields-valuetask.csproj b/src/tests/async/varying-yields-valuetask.csproj similarity index 81% rename from src/tests/Loader/async/async2-varying-yields-valuetask.csproj rename to src/tests/async/varying-yields-valuetask.csproj index d10972a7e74300..841fdd1bcf8110 100644 --- a/src/tests/Loader/async/async2-varying-yields-valuetask.csproj +++ b/src/tests/async/varying-yields-valuetask.csproj @@ -4,6 +4,6 @@ ASYNC1_VALUETASK;$(DefineConstants) - + diff --git a/src/tests/Loader/async/async2-varying-yields.cs b/src/tests/async/varying-yields.cs similarity index 100% rename from src/tests/Loader/async/async2-varying-yields.cs rename to src/tests/async/varying-yields.cs diff --git a/src/tests/async/varying-yields.csproj b/src/tests/async/varying-yields.csproj new file mode 100644 index 00000000000000..9367a79b2edbb1 --- /dev/null +++ b/src/tests/async/varying-yields.csproj @@ -0,0 +1,8 @@ + + + True + + + + + diff --git a/src/tests/Loader/async/async2void.cs b/src/tests/async/void.cs similarity index 100% rename from src/tests/Loader/async/async2void.cs rename to src/tests/async/void.cs diff --git a/src/tests/async/void.csproj b/src/tests/async/void.csproj new file mode 100644 index 00000000000000..9367a79b2edbb1 --- /dev/null +++ b/src/tests/async/void.csproj @@ -0,0 +1,8 @@ + + + True + + + + + diff --git a/src/tests/Loader/async/async2-with-yields.cs b/src/tests/async/with-yields.cs similarity index 100% rename from src/tests/Loader/async/async2-with-yields.cs rename to src/tests/async/with-yields.cs diff --git a/src/tests/async/with-yields.csproj b/src/tests/async/with-yields.csproj new file mode 100644 index 00000000000000..9367a79b2edbb1 --- /dev/null +++ b/src/tests/async/with-yields.csproj @@ -0,0 +1,8 @@ + + + True + + + + + diff --git a/src/tests/Loader/async/async2-without-yields.cs b/src/tests/async/without-yields.cs similarity index 100% rename from src/tests/Loader/async/async2-without-yields.cs rename to src/tests/async/without-yields.cs diff --git a/src/tests/async/without-yields.csproj b/src/tests/async/without-yields.csproj new file mode 100644 index 00000000000000..9367a79b2edbb1 --- /dev/null +++ b/src/tests/async/without-yields.csproj @@ -0,0 +1,8 @@ + + + True + + + + + From efb2892e2023aa8434fd6fc6ced4d4c6e71b6da6 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Thu, 14 Nov 2024 11:14:29 +0100 Subject: [PATCH 124/203] [RuntimeAsync] Add support for arm64 (#2769) Mainly just fixing some missing pieces on the JIT side, and adding x2 as the register used to return the async continuation. --- src/coreclr/jit/codegenarmarch.cpp | 8 ++++++++ src/coreclr/jit/codegencommon.cpp | 14 ++++++++++++++ src/coreclr/jit/codegenxarch.cpp | 13 ------------- src/coreclr/jit/lsraarm.cpp | 11 +++++++++++ src/coreclr/jit/lsraarm64.cpp | 11 +++++++++++ src/coreclr/jit/lsraloongarch64.cpp | 16 ++++++++++++++++ src/coreclr/jit/lsrariscv64.cpp | 16 ++++++++++++++++ src/coreclr/jit/targetarm64.h | 4 ++-- src/coreclr/vm/arm64/asmhelpers.S | 18 ++++++++++++------ src/coreclr/vm/arm64/asmhelpers.asm | 18 ++++++++++++------ src/coreclr/vm/arm64/cgencpu.h | 6 ++++++ src/coreclr/vm/arm64/stubs.cpp | 2 ++ src/coreclr/vm/jitinterface.cpp | 2 +- 13 files changed, 111 insertions(+), 28 deletions(-) diff --git a/src/coreclr/jit/codegenarmarch.cpp b/src/coreclr/jit/codegenarmarch.cpp index 6edd0599f3020b..a3013804950cc9 100644 --- a/src/coreclr/jit/codegenarmarch.cpp +++ b/src/coreclr/jit/codegenarmarch.cpp @@ -289,6 +289,10 @@ void CodeGen::genCodeForTreeNode(GenTree* treeNode) break; #endif // SWIFT_SUPPORT + case GT_RETURN_SUSPEND: + genReturnSuspend(treeNode->AsUnOp()); + break; + case GT_LEA: // If we are here, it is the case where there is an LEA that cannot be folded into a parent instruction. genLeaInstruction(treeNode->AsAddrMode()); @@ -504,6 +508,10 @@ void CodeGen::genCodeForTreeNode(GenTree* treeNode) genConsumeReg(treeNode); break; + case GT_ASYNC_CONTINUATION: + genCodeForAsyncContinuation(treeNode); + break; + case GT_PINVOKE_PROLOG: noway_assert(((gcInfo.gcRegGCrefSetCur | gcInfo.gcRegByrefSetCur) & ~fullIntArgRegMask(compiler->info.compCallConv)) == 0); diff --git a/src/coreclr/jit/codegencommon.cpp b/src/coreclr/jit/codegencommon.cpp index 0fa1d3a3c36088..7a494a625b0115 100644 --- a/src/coreclr/jit/codegencommon.cpp +++ b/src/coreclr/jit/codegencommon.cpp @@ -7333,6 +7333,20 @@ void CodeGen::genReturnSuspend(GenTreeUnOp* treeNode) } } } + +void CodeGen::genCodeForAsyncContinuation(GenTree* tree) +{ + assert(tree->OperIs(GT_ASYNC_CONTINUATION)); + + var_types targetType = tree->TypeGet(); + regNumber targetReg = tree->GetRegNum(); + + inst_Mov(targetType, targetReg, REG_ASYNC_CONTINUATION_RET, /* canSkip */ true); + genTransferRegGCState(targetReg, REG_ASYNC_CONTINUATION_RET); + + genProduceReg(tree); +} + //------------------------------------------------------------------------ // isStructReturn: Returns whether the 'treeNode' is returning a struct. // diff --git a/src/coreclr/jit/codegenxarch.cpp b/src/coreclr/jit/codegenxarch.cpp index 0a561d86aeb14b..814bab53a9db06 100644 --- a/src/coreclr/jit/codegenxarch.cpp +++ b/src/coreclr/jit/codegenxarch.cpp @@ -4707,19 +4707,6 @@ void CodeGen::genCodeForPhysReg(GenTreePhysReg* tree) genProduceReg(tree); } -void CodeGen::genCodeForAsyncContinuation(GenTree* tree) -{ - assert(tree->OperIs(GT_ASYNC_CONTINUATION)); - - var_types targetType = tree->TypeGet(); - regNumber targetReg = tree->GetRegNum(); - - inst_Mov(targetType, targetReg, REG_ASYNC_CONTINUATION_RET, /* canSkip */ true); - genTransferRegGCState(targetReg, REG_ASYNC_CONTINUATION_RET); - - genProduceReg(tree); -} - //--------------------------------------------------------------------- // genCodeForNullCheck - generate code for a GT_NULLCHECK node // diff --git a/src/coreclr/jit/lsraarm.cpp b/src/coreclr/jit/lsraarm.cpp index 815f0149aede11..ff5aa8c8f96978 100644 --- a/src/coreclr/jit/lsraarm.cpp +++ b/src/coreclr/jit/lsraarm.cpp @@ -631,6 +631,17 @@ int LinearScan::BuildNode(GenTree* tree) BuildDef(tree, RBM_EXCEPTION_OBJECT.GetIntRegSet()); break; + case GT_ASYNC_CONTINUATION: + srcCount = 0; + assert(dstCount == 1); + // We kill the continuation arg here to communicate to the + // selection phase that the argument is no longer busy. This is a + // hack to make sure we do not overwrite the continuation between + // the call and this node. + addKillForRegs(RBM_ASYNC_CONTINUATION_RET, currentLoc); + BuildDef(tree, RBM_ASYNC_CONTINUATION_RET.GetIntRegSet()); + break; + case GT_COPY: srcCount = 1; #ifdef TARGET_ARM diff --git a/src/coreclr/jit/lsraarm64.cpp b/src/coreclr/jit/lsraarm64.cpp index ab86d18d706def..4af401da71b98c 100644 --- a/src/coreclr/jit/lsraarm64.cpp +++ b/src/coreclr/jit/lsraarm64.cpp @@ -1314,6 +1314,17 @@ int LinearScan::BuildNode(GenTree* tree) BuildDef(tree, RBM_EXCEPTION_OBJECT.GetIntRegSet()); break; + case GT_ASYNC_CONTINUATION: + srcCount = 0; + assert(dstCount == 1); + // We kill the continuation arg here to communicate to the + // selection phase that the argument is no longer busy. This is a + // hack to make sure we do not overwrite the continuation between + // the call and this node. + addKillForRegs(RBM_ASYNC_CONTINUATION_RET, currentLoc); + BuildDef(tree, RBM_ASYNC_CONTINUATION_RET.GetIntRegSet()); + break; + case GT_INDEX_ADDR: assert(dstCount == 1); srcCount = BuildBinaryUses(tree->AsOp()); diff --git a/src/coreclr/jit/lsraloongarch64.cpp b/src/coreclr/jit/lsraloongarch64.cpp index 529e6d8127b670..18d978e6db2ac7 100644 --- a/src/coreclr/jit/lsraloongarch64.cpp +++ b/src/coreclr/jit/lsraloongarch64.cpp @@ -563,6 +563,17 @@ int LinearScan::BuildNode(GenTree* tree) BuildDef(tree, RBM_EXCEPTION_OBJECT.GetIntRegSet()); break; + case GT_ASYNC_CONTINUATION: + srcCount = 0; + assert(dstCount == 1); + // We kill the continuation arg here to communicate to the + // selection phase that the argument is no longer busy. This is a + // hack to make sure we do not overwrite the continuation between + // the call and this node. + addKillForRegs(RBM_ASYNC_CONTINUATION_RET, currentLoc); + BuildDef(tree, RBM_ASYNC_CONTINUATION_RET.GetIntRegSet()); + break; + case GT_INDEX_ADDR: assert(dstCount == 1); srcCount = BuildBinaryUses(tree->AsOp()); @@ -803,6 +814,11 @@ int LinearScan::BuildCall(GenTreeCall* call) BuildKills(call, killMask); } + if (call->IsAsync2() && compiler->compIsAsync2()) + { + MarkAsyncContinuationBusyForCall(call); + } + // No args are placed in registers anymore. placedArgRegs = RBM_NONE; numPlacedArgLocals = 0; diff --git a/src/coreclr/jit/lsrariscv64.cpp b/src/coreclr/jit/lsrariscv64.cpp index 1185eac4cea938..564c27c81043c2 100644 --- a/src/coreclr/jit/lsrariscv64.cpp +++ b/src/coreclr/jit/lsrariscv64.cpp @@ -705,6 +705,17 @@ int LinearScan::BuildNode(GenTree* tree) BuildDef(tree, RBM_EXCEPTION_OBJECT.GetIntRegSet()); break; + case GT_ASYNC_CONTINUATION: + srcCount = 0; + assert(dstCount == 1); + // We kill the continuation arg here to communicate to the + // selection phase that the argument is no longer busy. This is a + // hack to make sure we do not overwrite the continuation between + // the call and this node. + addKillForRegs(RBM_ASYNC_CONTINUATION_RET, currentLoc); + BuildDef(tree, RBM_ASYNC_CONTINUATION_RET.GetIntRegSet()); + break; + case GT_INDEX_ADDR: assert(dstCount == 1); srcCount = BuildBinaryUses(tree->AsOp()); @@ -957,6 +968,11 @@ int LinearScan::BuildCall(GenTreeCall* call) BuildKills(call, killMask); } + if (call->IsAsync2() && compiler->compIsAsync2()) + { + MarkAsyncContinuationBusyForCall(call); + } + // No args are placed in registers anymore. placedArgRegs = RBM_NONE; numPlacedArgLocals = 0; diff --git a/src/coreclr/jit/targetarm64.h b/src/coreclr/jit/targetarm64.h index 753e93c027b92f..b38e3f5043f906 100644 --- a/src/coreclr/jit/targetarm64.h +++ b/src/coreclr/jit/targetarm64.h @@ -267,8 +267,8 @@ #define REG_VALIDATE_INDIRECT_CALL_ADDR REG_R15 #define REG_DISPATCH_INDIRECT_CALL_ADDR REG_R9 - #define REG_ASYNC_CONTINUATION_RET REG_R8 - #define RBM_ASYNC_CONTINUATION_RET RBM_R8 + #define REG_ASYNC_CONTINUATION_RET REG_R2 + #define RBM_ASYNC_CONTINUATION_RET RBM_R2 #define REG_FPBASE REG_FP #define RBM_FPBASE RBM_FP diff --git a/src/coreclr/vm/arm64/asmhelpers.S b/src/coreclr/vm/arm64/asmhelpers.S index ac51662c82db4f..d438a03bc18147 100644 --- a/src/coreclr/vm/arm64/asmhelpers.S +++ b/src/coreclr/vm/arm64/asmhelpers.S @@ -301,7 +301,7 @@ NESTED_END TheUMEntryPrestub, _TEXT // ------------------------------------------------------------------ // Hijack function for functions which return a scalar type or a struct (value type) NESTED_ENTRY OnHijackTripThread, _TEXT, NoHandler - PROLOG_SAVE_REG_PAIR_INDEXED fp, lr, -176 + PROLOG_SAVE_REG_PAIR_INDEXED fp, lr, -192 // Spill callee saved registers PROLOG_SAVE_REG_PAIR x19, x20, 16 PROLOG_SAVE_REG_PAIR x21, x22, 32 @@ -312,9 +312,12 @@ NESTED_ENTRY OnHijackTripThread, _TEXT, NoHandler // save any integral return value(s) stp x0, x1, [sp, #96] + // save async continuation return value + str x2, [sp, #112] + // save any FP/HFA return value(s) - stp q0, q1, [sp, #112] - stp q2, q3, [sp, #144] + stp q0, q1, [sp, #128] + stp q2, q3, [sp, #160] mov x0, sp bl C_FUNC(OnHijackWorker) @@ -322,16 +325,19 @@ NESTED_ENTRY OnHijackTripThread, _TEXT, NoHandler // restore any integral return value(s) ldp x0, x1, [sp, #96] + // restore async continuation return value + ldr x2, [sp, #112] + // restore any FP/HFA return value(s) - ldp q0, q1, [sp, #112] - ldp q2, q3, [sp, #144] + ldp q0, q1, [sp, #128] + ldp q2, q3, [sp, #160] EPILOG_RESTORE_REG_PAIR x19, x20, 16 EPILOG_RESTORE_REG_PAIR x21, x22, 32 EPILOG_RESTORE_REG_PAIR x23, x24, 48 EPILOG_RESTORE_REG_PAIR x25, x26, 64 EPILOG_RESTORE_REG_PAIR x27, x28, 80 - EPILOG_RESTORE_REG_PAIR_INDEXED fp, lr, 176 + EPILOG_RESTORE_REG_PAIR_INDEXED fp, lr, 192 EPILOG_RETURN NESTED_END OnHijackTripThread, _TEXT diff --git a/src/coreclr/vm/arm64/asmhelpers.asm b/src/coreclr/vm/arm64/asmhelpers.asm index 153c7a3dbe9b5d..bbf17c476b78e5 100644 --- a/src/coreclr/vm/arm64/asmhelpers.asm +++ b/src/coreclr/vm/arm64/asmhelpers.asm @@ -630,7 +630,7 @@ COMToCLRDispatchHelper_RegSetup ; ------------------------------------------------------------------ ; Hijack function for functions which return a scalar type or a struct (value type) NESTED_ENTRY OnHijackTripThread - PROLOG_SAVE_REG_PAIR fp, lr, #-176! + PROLOG_SAVE_REG_PAIR fp, lr, #-192! ; Spill callee saved registers PROLOG_SAVE_REG_PAIR x19, x20, #16 PROLOG_SAVE_REG_PAIR x21, x22, #32 @@ -641,9 +641,12 @@ COMToCLRDispatchHelper_RegSetup ; save any integral return value(s) stp x0, x1, [sp, #96] + ; save async continuation return value + str x2, [sp, #112] + ; save any FP/HFA/HVA return value(s) - stp q0, q1, [sp, #112] - stp q2, q3, [sp, #144] + stp q0, q1, [sp, #128] + stp q2, q3, [sp, #160] mov x0, sp bl OnHijackWorker @@ -651,16 +654,19 @@ COMToCLRDispatchHelper_RegSetup ; restore any integral return value(s) ldp x0, x1, [sp, #96] + ; restore async continuation return value + ldr x2, [sp, #112] + ; restore any FP/HFA/HVA return value(s) - ldp q0, q1, [sp, #112] - ldp q2, q3, [sp, #144] + ldp q0, q1, [sp, #128] + ldp q2, q3, [sp, #160] EPILOG_RESTORE_REG_PAIR x19, x20, #16 EPILOG_RESTORE_REG_PAIR x21, x22, #32 EPILOG_RESTORE_REG_PAIR x23, x24, #48 EPILOG_RESTORE_REG_PAIR x25, x26, #64 EPILOG_RESTORE_REG_PAIR x27, x28, #80 - EPILOG_RESTORE_REG_PAIR fp, lr, #176! + EPILOG_RESTORE_REG_PAIR fp, lr, #192! EPILOG_RETURN NESTED_END diff --git a/src/coreclr/vm/arm64/cgencpu.h b/src/coreclr/vm/arm64/cgencpu.h index f83de42fecc368..20e5e5412fcfb2 100644 --- a/src/coreclr/vm/arm64/cgencpu.h +++ b/src/coreclr/vm/arm64/cgencpu.h @@ -558,6 +558,12 @@ struct HijackArgs size_t ReturnValue[2]; }; union + { + DWORD64 X2; + size_t AsyncRet; + }; + DWORD64 Pad; + union { struct { NEON128 Q0; diff --git a/src/coreclr/vm/arm64/stubs.cpp b/src/coreclr/vm/arm64/stubs.cpp index 6cf5a7ec845896..48208d3d6106f2 100644 --- a/src/coreclr/vm/arm64/stubs.cpp +++ b/src/coreclr/vm/arm64/stubs.cpp @@ -789,6 +789,8 @@ void HijackFrame::UpdateRegDisplay(const PREGDISPLAY pRD, bool updateFloats) pRD->pCurrentContext->Sp = PTR_TO_TADDR(m_Args) + s ; pRD->pCurrentContext->X0 = m_Args->X0; + pRD->pCurrentContext->X1 = m_Args->X1; + pRD->pCurrentContext->X2 = m_Args->X2; pRD->pCurrentContext->X19 = m_Args->X19; pRD->pCurrentContext->X20 = m_Args->X20; diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index 8bc995514988da..cb0b7e5340c4fe 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -14653,7 +14653,7 @@ CORINFO_METHOD_HANDLE CEEJitInfo::getAsyncResumptionStub() } char name[256]; - int numWritten = sprintf_s(name, "IL_STUB_AsyncResume_%s_%s", m_pMethodBeingCompiled->GetName(), optimizationTierName); + int numWritten = sprintf_s(name, ARRAY_SIZE(name), "IL_STUB_AsyncResume_%s_%s", m_pMethodBeingCompiled->GetName(), optimizationTierName); if (numWritten != -1) { AllocMemTracker pamTracker; From 36b43057af0f7a217a8c17ba423cb321213aaeb8 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Thu, 14 Nov 2024 11:17:36 +0100 Subject: [PATCH 125/203] Fix linux-x64 support (#2770) - Fix Linux version of `OnHijackTripThread` - Fix some code lost in a bad merge --- src/coreclr/jit/codegenxarch.cpp | 1 + src/coreclr/vm/amd64/unixasmhelpers.S | 9 +++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/coreclr/jit/codegenxarch.cpp b/src/coreclr/jit/codegenxarch.cpp index 814bab53a9db06..eaffa835e1190f 100644 --- a/src/coreclr/jit/codegenxarch.cpp +++ b/src/coreclr/jit/codegenxarch.cpp @@ -2208,6 +2208,7 @@ void CodeGen::genCodeForTreeNode(GenTree* treeNode) break; case GT_ASYNC_CONTINUATION: + genCodeForAsyncContinuation(treeNode); break; #if defined(FEATURE_EH_WINDOWS_X86) diff --git a/src/coreclr/vm/amd64/unixasmhelpers.S b/src/coreclr/vm/amd64/unixasmhelpers.S index 7e404a2a09cf6c..1f7f3a3856e5b1 100644 --- a/src/coreclr/vm/amd64/unixasmhelpers.S +++ b/src/coreclr/vm/amd64/unixasmhelpers.S @@ -152,13 +152,16 @@ NESTED_ENTRY OnHijackTripThread, _TEXT, NoHandler PUSH_CALLEE_SAVED_REGISTERS + // Push rcx for the async continuation + push_register rcx + // Push rdx for the second half of the return value push_register rdx // Push rax again - this is where integer/pointer return values are returned push_register rax mov rdi, rsp - alloc_stack 0x28 + alloc_stack 0x20 // First float return register movdqa [rsp], xmm0 @@ -171,9 +174,11 @@ NESTED_ENTRY OnHijackTripThread, _TEXT, NoHandler movdqa xmm0, [rsp] movdqa xmm1, [rsp+0x10] - free_stack 0x28 + free_stack 0x20 + pop_register rax pop_register rdx + pop_register rcx POP_CALLEE_SAVED_REGISTERS ret From 0933305379c3bcc68a021e1efe75529389e81020 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Fri, 15 Nov 2024 10:30:11 +0100 Subject: [PATCH 126/203] Add arm32 support (#2773) - Use `r2` for the async continuation return register - Skip a prolog/epilog optimization that could cause us to overwrite this register in the epilog Contributes to #2764 --- src/coreclr/jit/codegenarm.cpp | 9 +++++++++ src/coreclr/jit/lsraarm.cpp | 1 + src/coreclr/jit/targetarm.h | 4 ++-- src/coreclr/vm/arm/asmhelpers.S | 6 ++---- src/coreclr/vm/arm/cgencpu.h | 6 ++++++ 5 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/coreclr/jit/codegenarm.cpp b/src/coreclr/jit/codegenarm.cpp index 9b8fb642160a3f..7f1b7d5706bc3d 100644 --- a/src/coreclr/jit/codegenarm.cpp +++ b/src/coreclr/jit/codegenarm.cpp @@ -2087,7 +2087,16 @@ regMaskTP CodeGen::genStackAllocRegisterMask(unsigned frameSize, regMaskTP maskC // We can't do this optimization with callee saved floating point registers because // the stack would be allocated in a wrong spot. if (maskCalleeSavedFloat != RBM_NONE) + { + return RBM_NONE; + } + + // We similarly skip it for async2 due to the extra async continuation + // return that may be overridden by the pop. + if (compiler->compIsAsync2()) + { return RBM_NONE; + } // Allocate space for small frames by pushing extra registers. It generates smaller and faster code // that extra sub sp,XXX/add sp,XXX. diff --git a/src/coreclr/jit/lsraarm.cpp b/src/coreclr/jit/lsraarm.cpp index ff5aa8c8f96978..286dbb00d0cb1f 100644 --- a/src/coreclr/jit/lsraarm.cpp +++ b/src/coreclr/jit/lsraarm.cpp @@ -704,6 +704,7 @@ int LinearScan::BuildNode(GenTree* tree) case GT_JCC: case GT_SETCC: case GT_MEMORYBARRIER: + case GT_RETURN_SUSPEND: srcCount = BuildSimple(tree); break; diff --git a/src/coreclr/jit/targetarm.h b/src/coreclr/jit/targetarm.h index da9bf1a06da171..507ca4422cf7cb 100644 --- a/src/coreclr/jit/targetarm.h +++ b/src/coreclr/jit/targetarm.h @@ -251,8 +251,8 @@ #define RBM_VALIDATE_INDIRECT_CALL_TRASH (RBM_INT_CALLEE_TRASH) #define REG_VALIDATE_INDIRECT_CALL_ADDR REG_R0 - #define REG_ASYNC_CONTINUATION_RET REG_R3 - #define RBM_ASYNC_CONTINUATION_RET RBM_R3 + #define REG_ASYNC_CONTINUATION_RET REG_R2 + #define RBM_ASYNC_CONTINUATION_RET RBM_R2 #define REG_FPBASE REG_R11 #define RBM_FPBASE RBM_R11 diff --git a/src/coreclr/vm/arm/asmhelpers.S b/src/coreclr/vm/arm/asmhelpers.S index 515c6112d2628d..4631168d0df38d 100644 --- a/src/coreclr/vm/arm/asmhelpers.S +++ b/src/coreclr/vm/arm/asmhelpers.S @@ -845,22 +845,20 @@ DelayLoad_Helper\suffix: // ------------------------------------------------------------------ // Hijack function for functions which return a value type NESTED_ENTRY OnHijackTripThread, _TEXT, NoHandler - PROLOG_PUSH "{r0,r4-r11,lr}" + PROLOG_PUSH "{r0,r2,r4-r11,lr}" PROLOG_VPUSH "{d0-d3}" // saving as d0-d3 can have the floating point return value PROLOG_PUSH "{r1}" // saving as r1 can have partial return value when return is > 32 bits - alloc_stack 4 // 8 byte align CHECK_STACK_ALIGNMENT add r0, sp, #40 bl C_FUNC(OnHijackWorker) - free_stack 4 EPILOG_POP "{r1}" EPILOG_VPOP "{d0-d3}" - EPILOG_POP "{r0,r4-r11,pc}" + EPILOG_POP "{r0,r2,r4-r11,pc}" NESTED_END OnHijackTripThread, _TEXT #endif diff --git a/src/coreclr/vm/arm/cgencpu.h b/src/coreclr/vm/arm/cgencpu.h index 712d3454132d38..8cd60e5ed2e551 100644 --- a/src/coreclr/vm/arm/cgencpu.h +++ b/src/coreclr/vm/arm/cgencpu.h @@ -980,6 +980,12 @@ struct HijackArgs // this is only used by functions OnHijackWorker() }; + union + { + DWORD R2; + size_t AsyncRet; + }; + // // Non-volatile Integer registers // From 81e5c1e1b0fc3b7228493fb89dce1e1ce44fd75f Mon Sep 17 00:00:00 2001 From: Vladimir Sadov Date: Mon, 18 Nov 2024 09:03:51 -0800 Subject: [PATCH 127/203] awaits of Task-returning methods from async2 are done as thunk calls. (#2781) * Bump Roslyn version to one that uses thunks when async2 awaits [Task|ValueTask] returning methods * attempt to make a2->a1 stub to work * Fix MethodDesc::IsAsync2Method * Fix signatures for more methods in AsyncToTask thunks --------- Co-authored-by: Jakob Botsch Nielsen --- buildroslynnugets.cmd | 4 +- eng/Versions.props | 6 +- src/coreclr/vm/corelib.h | 5 + src/coreclr/vm/method.hpp | 4 +- src/coreclr/vm/prestub.cpp | 204 +++++++++++++++++++++++++++++++++---- 5 files changed, 198 insertions(+), 25 deletions(-) diff --git a/buildroslynnugets.cmd b/buildroslynnugets.cmd index 5e9564528653cc..2358e7f4a9f02f 100644 --- a/buildroslynnugets.cmd +++ b/buildroslynnugets.cmd @@ -1,7 +1,7 @@ setlocal ENABLEEXTENSIONS pushd %~dp0 -set ASYNC_ROSLYN_COMMIT=8bb20e76b6d7e041a2cd0a3152d95fdff6578050 -set ASYNC_SUFFIX=async-9 +set ASYNC_ROSLYN_COMMIT=fa251ef06afcc9d1c79761a112b89c6341c0ccfa +set ASYNC_SUFFIX=async-10 set ASYNC_ROSLYN_BRANCH=demos/async2-experiment1 cd .. diff --git a/eng/Versions.props b/eng/Versions.props index 4a117fd3e9ddd9..b573c01eba46d6 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -43,9 +43,9 @@ Any tools that contribute to the design-time experience should use the MicrosoftCodeAnalysisVersion_LatestVS property above to ensure they do not break the local dev experience. --> - 4.13.0-async-9 - 4.13.0-async-9 - 4.13.0-async-9 + 4.13.0-async-10 + 4.13.0-async-10 + 4.13.0-async-10 + True + True + + + + + diff --git a/src/tests/issues.targets b/src/tests/issues.targets index 1724254d8ad898..82f08f2743fb0e 100644 --- a/src/tests/issues.targets +++ b/src/tests/issues.targets @@ -983,6 +983,9 @@ https://github.com/dotnet/runtimelab/issues/155: Collectible assemblies + + https://github.com/dotnet/runtimelab/issues/155: Collectible assemblies + https://github.com/dotnet/runtimelab/issues/165 @@ -3130,6 +3133,9 @@ Loads an assembly from file + + Loads an assembly from file + Loads an assembly from file From d38e9535c63f290146b2cc75da4e776b297cc5e8 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Wed, 4 Dec 2024 10:04:54 +0100 Subject: [PATCH 130/203] [RuntimeAsync] Fix support for pinvokes (#2847) - Make sure async transformation leaves a scratch BB around for the pinvoke prolog to be inserted into - Make sure we properly insert pinvoke epilogs for `GT_RETURN_SUSPEND` nodes (needed on x86) - Ensure the pinvoke frame related locals are not captured by the async transformation --- src/coreclr/jit/async.cpp | 29 ++++++++++++++++++---- src/coreclr/jit/compiler.cpp | 2 +- src/coreclr/jit/lower.cpp | 6 ++++- src/tests/async/pinvoke.cs | 44 ++++++++++++++++++++++++++++++++++ src/tests/async/pinvoke.csproj | 8 +++++++ 5 files changed, 83 insertions(+), 6 deletions(-) create mode 100644 src/tests/async/pinvoke.cs create mode 100644 src/tests/async/pinvoke.csproj diff --git a/src/coreclr/jit/async.cpp b/src/coreclr/jit/async.cpp index c6d7665b1c327a..e0726ccc977e33 100644 --- a/src/coreclr/jit/async.cpp +++ b/src/coreclr/jit/async.cpp @@ -60,6 +60,16 @@ class AsyncLiveness return true; } + if (lclNum == m_comp->info.compLvFrameListRoot) + { + return true; + } + + if (lclNum == m_comp->lvaInlinedPInvokeFrameVar) + { + return true; + } + #ifdef FEATURE_EH_WINDOWS_X86 if (lclNum == m_comp->lvaShadowSPslotsVar) { @@ -1296,12 +1306,22 @@ void Async2Transformation::CreateResumptionSwitch() { BasicBlock* newEntryBB = BasicBlock::New(m_comp, BBJ_ALWAYS); + bool hadScratchBB = m_comp->fgFirstBBisScratch(); + if (m_comp->fgFirstBB->hasProfileWeight()) { newEntryBB->inheritWeight(m_comp->fgFirstBB); } m_comp->fgFirstBB->bbRefs--; + if (hadScratchBB) + { + // If previous first BB was a scratch BB then we have to recreate it + // after since later phases may be relying on it. + // TODO-Cleanup: The later phases should be creating it if they need it. + m_comp->fgFirstBBScratch = nullptr; + } + FlowEdge* toPrevEntryEdge = m_comp->fgAddRefPred(m_comp->fgFirstBB, newEntryBB); toPrevEntryEdge->setLikelihood(1); @@ -1310,10 +1330,6 @@ void Async2Transformation::CreateResumptionSwitch() m_comp->fgInsertBBbefore(m_comp->fgFirstBB, newEntryBB); - // If previous first BB was a scratch BB, then we must add a new scratch BB - // to create IR before the switch. - m_comp->fgFirstBBScratch = nullptr; - GenTree* continuationArg = m_comp->gtNewLclvNode(m_comp->lvaAsyncContinuationArg, TYP_REF); GenTree* null = m_comp->gtNewNull(); GenTree* neNull = m_comp->gtNewOperNode(GT_NE, TYP_INT, continuationArg, null); @@ -1493,4 +1509,9 @@ void Async2Transformation::CreateResumptionSwitch() GenTree* jtrue = m_comp->gtNewOperNode(GT_JTRUE, TYP_VOID, ltZero); LIR::AsRange(checkILOffsetBB).InsertAtEnd(LIR::SeqTree(m_comp, jtrue)); } + + if (hadScratchBB) + { + m_comp->fgEnsureFirstBBisScratch(); + } } diff --git a/src/coreclr/jit/compiler.cpp b/src/coreclr/jit/compiler.cpp index 3e8ae18bf4a343..dca14dfe0937ad 100644 --- a/src/coreclr/jit/compiler.cpp +++ b/src/coreclr/jit/compiler.cpp @@ -3368,7 +3368,7 @@ void Compiler::compInitOptions(JitFlags* jitFlags) if (compIsAsync2()) { - printf("OPTIONS: compilation is an async2\n"); + printf("OPTIONS: compilation is an async2 state machine\n"); } } #endif diff --git a/src/coreclr/jit/lower.cpp b/src/coreclr/jit/lower.cpp index e257751c5afc2d..15eea3d18ad933 100644 --- a/src/coreclr/jit/lower.cpp +++ b/src/coreclr/jit/lower.cpp @@ -4888,7 +4888,6 @@ void Lowering::LowerRet(GenTreeOp* ret) } } - // Method doing PInvokes has exactly one return block unless it has tail calls. if (comp->compMethodRequiresPInvokeFrame()) { InsertPInvokeMethodEpilog(comp->compCurBB DEBUGARG(ret)); @@ -5332,6 +5331,11 @@ void Lowering::LowerReturnSuspend(GenTree* node) { BlockRange().Remove(BlockRange().LastNode(), true); } + + if (comp->compMethodRequiresPInvokeFrame()) + { + InsertPInvokeMethodEpilog(comp->compCurBB DEBUGARG(node)); + } } //---------------------------------------------------------------------------------------------- diff --git a/src/tests/async/pinvoke.cs b/src/tests/async/pinvoke.cs new file mode 100644 index 00000000000000..df04091cbaf2ff --- /dev/null +++ b/src/tests/async/pinvoke.cs @@ -0,0 +1,44 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading.Tasks; +using Xunit; + +public class Async2PInvoke +{ + [Fact] + public static void TestEntryPoint() + { + AsyncEntryPoint().Wait(); + } + + private static async2 Task AsyncEntryPoint() + { + unsafe + { + Assert.Equal(5, GetFPtr()()); + } + + await Task.Yield(); + + unsafe + { + Assert.Equal(5, GetFPtr()()); + } + + await Task.Yield(); + + unsafe + { + Assert.Equal(5, GetFPtr()()); + } + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static unsafe delegate* unmanaged GetFPtr() => &GetValue; + + [UnmanagedCallersOnly] + private static int GetValue() => 5; +} diff --git a/src/tests/async/pinvoke.csproj b/src/tests/async/pinvoke.csproj new file mode 100644 index 00000000000000..9367a79b2edbb1 --- /dev/null +++ b/src/tests/async/pinvoke.csproj @@ -0,0 +1,8 @@ + + + True + + + + + From d95f6eadeeb696071635d9662fbe33f8a0e0d645 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Thu, 5 Dec 2024 12:45:20 +0100 Subject: [PATCH 131/203] [RuntimeAsync] Fix a couple of cases where x86 would overwrite ecx on return (#2854) shared-generics was not working on x86 because the returned continuation was being overwritten in one particular case. This fixes the issue. --- src/coreclr/jit/codegenxarch.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/coreclr/jit/codegenxarch.cpp b/src/coreclr/jit/codegenxarch.cpp index eaffa835e1190f..d1f84e7ff682fb 100644 --- a/src/coreclr/jit/codegenxarch.cpp +++ b/src/coreclr/jit/codegenxarch.cpp @@ -10000,10 +10000,10 @@ void CodeGen::genFnEpilog(BasicBlock* block) if (frameSize > 0) { #ifdef TARGET_X86 - /* Add 'compiler->compLclFrameSize' to ESP */ - /* Use pop ECX to increment ESP by 4, unless compiler->compJmpOpUsed is true */ + // Add 'compiler->compLclFrameSize' to ESP. Use "pop ECX" for that, except in cases + // where ECX may contain some state. - if ((frameSize == TARGET_POINTER_SIZE) && !compiler->compJmpOpUsed) + if ((frameSize == TARGET_POINTER_SIZE) && !compiler->compJmpOpUsed && !compiler->compIsAsync2()) { inst_RV(INS_pop, REG_ECX, TYP_I_IMPL); regSet.verifyRegUsed(REG_ECX); @@ -10011,8 +10011,8 @@ void CodeGen::genFnEpilog(BasicBlock* block) else #endif // TARGET_X86 { - /* Add 'compiler->compLclFrameSize' to ESP */ - /* Generate "add esp, " */ + // Add 'compiler->compLclFrameSize' to ESP + // Generate "add esp, " inst_RV_IV(INS_add, REG_SPBASE, frameSize, EA_PTRSIZE); } } @@ -10090,7 +10090,7 @@ void CodeGen::genFnEpilog(BasicBlock* block) // do nothing before popping the callee-saved registers } #ifdef TARGET_X86 - else if (compiler->compLclFrameSize == REGSIZE_BYTES) + else if ((compiler->compLclFrameSize == REGSIZE_BYTES) && !compiler->compJmpOpUsed && !compiler->compIsAsync2()) { // "pop ecx" will make ESP point to the callee-saved registers inst_RV(INS_pop, REG_ECX, TYP_I_IMPL); From 7624a38ae9820b8c034818c5fbef30f09c4a4ab1 Mon Sep 17 00:00:00 2001 From: Vladimir Sadov Date: Fri, 13 Dec 2024 19:21:25 -0800 Subject: [PATCH 132/203] [RuntimeAsync] Switch the implementation to the new shape of runtime async methods. (#2818) * update Roslyn ref to one that uses new IL shape * part1 * IsAsyncThunkMethod --> IsAsyncHelperMethod * reintroduce IsAsyncThunkMethod * fix shared generic test * fix after rebasing * typo * clarified a comment * PR feedback Co-authored-by: Jakob Botsch Nielsen * switch to a newer Roslyn prototype * workaround - moved `GC.Collect/WFPF` into a noinline helper. * PR feedback * Revert "workaround - moved `GC.Collect/WFPF` into a noinline helper." This reverts commit ef4580304b1b9b7ac49a5af09e4d4cb1112ec042. * reenable tiering for async methods * more isThunk-->isHelper renames * fixes tiered compilation * switch to a new ROslyn SHA * A few fixes after updating to new Roslyn compiler * add a testcase for PGO/OSR * PR feedback * file rename * 4 iterations / 100 msec delay --------- Co-authored-by: Jakob Botsch Nielsen --- buildroslynnugets.cmd | 4 +- eng/Versions.props | 6 +- src/coreclr/inc/corcompile.h | 2 +- src/coreclr/inc/corhdr.h | 5 +- src/coreclr/vm/class.cpp | 4 +- src/coreclr/vm/clrtocomcall.cpp | 4 +- src/coreclr/vm/clsload.cpp | 2 +- src/coreclr/vm/codeman.cpp | 1 + src/coreclr/vm/commodule.cpp | 2 +- src/coreclr/vm/commtmemberinfomap.cpp | 4 +- src/coreclr/vm/comtoclrcall.cpp | 2 +- src/coreclr/vm/dispatchinfo.cpp | 6 +- src/coreclr/vm/dllimport.cpp | 14 +- src/coreclr/vm/encee.cpp | 2 +- src/coreclr/vm/genericdict.cpp | 14 +- src/coreclr/vm/genmeth.cpp | 30 ++--- src/coreclr/vm/instmethhash.cpp | 6 +- src/coreclr/vm/instmethhash.h | 2 +- src/coreclr/vm/interoputil.cpp | 4 +- src/coreclr/vm/jitinterface.cpp | 4 +- src/coreclr/vm/method.cpp | 8 +- src/coreclr/vm/method.hpp | 67 +++++---- src/coreclr/vm/methoditer.cpp | 4 +- src/coreclr/vm/methodtable.cpp | 8 +- src/coreclr/vm/methodtablebuilder.cpp | 127 ++++++++---------- src/coreclr/vm/methodtablebuilder.h | 24 ++-- src/coreclr/vm/multicorejit.cpp | 4 +- src/coreclr/vm/prestub.cpp | 50 +++++-- src/coreclr/vm/readytoruninfo.cpp | 6 +- src/coreclr/vm/zapsig.cpp | 10 +- .../Nrbf/ArraySinglePrimitiveRecord.cs | 10 +- .../src/System/Numerics/BigInteger.cs | 2 +- .../Json/Document/JsonDocument.MetadataDb.cs | 2 +- src/tests/async/pgo.cs | 55 ++++++++ src/tests/async/pgo.csproj | 8 ++ 35 files changed, 302 insertions(+), 201 deletions(-) create mode 100644 src/tests/async/pgo.cs create mode 100644 src/tests/async/pgo.csproj diff --git a/buildroslynnugets.cmd b/buildroslynnugets.cmd index 2358e7f4a9f02f..281ddee98868f3 100644 --- a/buildroslynnugets.cmd +++ b/buildroslynnugets.cmd @@ -1,7 +1,7 @@ setlocal ENABLEEXTENSIONS pushd %~dp0 -set ASYNC_ROSLYN_COMMIT=fa251ef06afcc9d1c79761a112b89c6341c0ccfa -set ASYNC_SUFFIX=async-10 +set ASYNC_ROSLYN_COMMIT=10a5611cb10cd64876a2638664f0740255197e1b +set ASYNC_SUFFIX=async-11 set ASYNC_ROSLYN_BRANCH=demos/async2-experiment1 cd .. diff --git a/eng/Versions.props b/eng/Versions.props index b573c01eba46d6..6b5bef1844e2b5 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -43,9 +43,9 @@ Any tools that contribute to the design-time experience should use the MicrosoftCodeAnalysisVersion_LatestVS property above to ensure they do not break the local dev experience. --> - 4.13.0-async-10 - 4.13.0-async-10 - 4.13.0-async-10 + 4.13.0-async-11 + 4.13.0-async-11 + 4.13.0-async-11 - 4.13.0-async-11 - 4.13.0-async-11 - 4.13.0-async-11 + 4.13.0-async-12 + 4.13.0-async-12 + 4.13.0-async-12 - 4.13.0-async-12 - 4.13.0-async-12 - 4.13.0-async-12 + 4.14.0-async-13 + 4.14.0-async-13 + 4.14.0-async-13 - True - True - - - - - diff --git a/src/tests/async/fibonacci-with-yields.cs b/src/tests/async/fibonacci-with-yields.cs index 9a8e9b505a55c1..a51a834fbdd773 100644 --- a/src/tests/async/fibonacci-with-yields.cs +++ b/src/tests/async/fibonacci-with-yields.cs @@ -6,59 +6,49 @@ using System.Diagnostics; using Xunit; -public class Async2FibonacceWithYields +public class Async2FibonacciWithYields { - const uint Threshold = 1_000; - static bool done; - const int iterations = 1; + const int iterations = 3; + const bool doYields = true; [Fact] public static void Test() { - AsyncEntry().Wait(); + long allocated = GC.GetTotalAllocatedBytes(precise: true); + + AsyncEntry().GetAwaiter().GetResult(); + + allocated = GC.GetTotalAllocatedBytes(precise: true) - allocated; + System.Console.WriteLine("allocated: " + allocated); } - public static async Task AsyncEntry() + public static async2 Task AsyncEntry() { - for (int i = 0; i < iterations && !done; i++) + for (int i = 0; i < iterations; i++) { - var sw = new Stopwatch(); - sw.Start(); - uint result = await A(100_000_000); + var sw = Stopwatch.StartNew(); + int result = await Fib(25); + sw.Stop(); + Console.WriteLine($"{sw.ElapsedMilliseconds} ms result={result}"); } } - static async2 Task A(uint n) - { - uint result = n; - for (uint i = 0; i < n && !done; i++) - result = await B(result); - return result; - } - - static async2 Task B(uint n) + static async2 Task Fib(int i) { - uint result = n; - - result = result * 1_999_999_981; - if (result < Threshold) + if (i <= 1) { - await Task.Yield(); - } + if (doYields) + { + await Task.Yield(); + } - result = result * 1_999_999_981; - if (result < Threshold) - { - await Task.Yield(); + return 1; } - result = result * 1_999_999_981; - if (result < Threshold) - { - await Task.Yield(); - } + int i1 = await Fib(i - 1); + int i2 = await Fib(i - 2); - return result; + return i1 + i2; } } diff --git a/src/tests/async/fibonacci-with-yields_struct_return.cs b/src/tests/async/fibonacci-with-yields_struct_return.cs new file mode 100644 index 00000000000000..eee23a2c16245b --- /dev/null +++ b/src/tests/async/fibonacci-with-yields_struct_return.cs @@ -0,0 +1,63 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#define WITH_OBJECT + +using System; +using System.Threading.Tasks; +using System.Diagnostics; +using Xunit; + +public class Async2FibonacceWithYields +{ + const int iterations = 3; + + [Fact] + public static void Test() + { + long allocated = GC.GetTotalAllocatedBytes(precise: true); + + AsyncEntry().GetAwaiter().GetResult(); + + allocated = GC.GetTotalAllocatedBytes(precise: true) - allocated; + System.Console.WriteLine("allocated: " + allocated); + } + + public struct MyInt + { + public int i; +#if WITH_OBJECT + public object dummy; +#else + IntPtr dummy; +#endif + public MyInt(int i) => this.i = i; + } + + public static async2 Task AsyncEntry() + { + for (int i = 0; i < iterations; i++) + { + var sw = Stopwatch.StartNew(); + MyInt result = await Fib(new MyInt(25)); + sw.Stop(); + + Console.WriteLine($"{sw.ElapsedMilliseconds} ms result={result.i}"); + } + } + + static async2 Task Fib(MyInt n) + { + int i = n.i; + if (i <= 1) + { + await Task.Yield(); + return new MyInt(1); + } + + int i1 = (await Fib(new MyInt(i - 1))).i; + int i2 = (await Fib(new MyInt(i - 2))).i; + + return new MyInt(i1 + i2); + } +} diff --git a/src/tests/async/syncfibonacci-with-yields.csproj b/src/tests/async/fibonacci-with-yields_struct_return.csproj similarity index 69% rename from src/tests/async/syncfibonacci-with-yields.csproj rename to src/tests/async/fibonacci-with-yields_struct_return.csproj index a7c0f53acc0cb4..de6d5e08882e86 100644 --- a/src/tests/async/syncfibonacci-with-yields.csproj +++ b/src/tests/async/fibonacci-with-yields_struct_return.csproj @@ -1,8 +1,6 @@ True - - BuildOnly diff --git a/src/tests/async/fibonacci-without-yields.cs b/src/tests/async/fibonacci-without-yields.cs index fc1d1fd7f1f087..77ff71e8d1ee36 100644 --- a/src/tests/async/fibonacci-without-yields.cs +++ b/src/tests/async/fibonacci-without-yields.cs @@ -6,59 +6,49 @@ using System.Diagnostics; using Xunit; -public class Async2FibonacceWithoutYields +public class Async2FibonacciWithoutYields { - const uint Threshold = 1_000; - static bool doYields = false; - const int iterations = 1; + const int iterations = 3; + const bool doYields = false; [Fact] public static void Test() { - AsyncEntry().Wait(); + long allocated = GC.GetTotalAllocatedBytes(precise: true); + + AsyncEntry().GetAwaiter().GetResult(); + + allocated = GC.GetTotalAllocatedBytes(precise: true) - allocated; + System.Console.WriteLine("allocated: " + allocated); } - public static async Task AsyncEntry() + public static async2 Task AsyncEntry() { for (int i = 0; i < iterations; i++) { - var sw = new Stopwatch(); - sw.Start(); - uint result = await A(100_000_000); + var sw = Stopwatch.StartNew(); + int result = await Fib(25); + sw.Stop(); + Console.WriteLine($"{sw.ElapsedMilliseconds} ms result={result}"); } } - static async2 Task A(uint n) - { - uint result = n; - for (uint i = 0; i < n; i++) - result = await B(result); - return result; - } - - static async2 Task B(uint n) + static async2 Task Fib(int i) { - uint result = n; - - result = result * 1_999_999_981; - if ((result < Threshold) && doYields) + if (i <= 1) { - await Task.Yield(); - } + if (doYields) + { + await Task.Yield(); + } - result = result * 1_999_999_981; - if ((result < Threshold) && doYields) - { - await Task.Yield(); + return 1; } - result = result * 1_999_999_981; - if ((result < Threshold) && doYields) - { - await Task.Yield(); - } + int i1 = await Fib(i - 1); + int i2 = await Fib(i - 2); - return result; + return i1 + i2; } } diff --git a/src/tests/async/syncfibonacci-with-yields.cs b/src/tests/async/syncfibonacci-with-yields.cs deleted file mode 100644 index fa05d09572ef0e..00000000000000 --- a/src/tests/async/syncfibonacci-with-yields.cs +++ /dev/null @@ -1,60 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Diagnostics; -using System.Threading; -using System.Threading.Tasks; -using System.Runtime.CompilerServices; -using Xunit; - -public class SyncFibonacciWithYields -{ - const uint Threshold = 1_000; - - public static int Main() - { - for (int i = 0; i < 10; i++) - { - var sw = new Stopwatch(); - sw.Start(); - uint result = A(100_000_000); - Console.WriteLine($"{sw.ElapsedMilliseconds} ms result={result}"); - } - - return 100; - } - - static uint A(uint n) - { - uint result = n; - for (uint i = 0; i < n; i++) - result = B(result); - return result; - } - - static uint B(uint n) - { - uint result = n; - - result = result * 1_999_999_981; - if (result < Threshold) - MyYield(); - - result = result * 1_999_999_981; - if (result < Threshold) - MyYield(); - - result = result * 1_999_999_981; - if (result < Threshold) - MyYield(); - - return result; - } - - // Workaround for inlining of Thread.Yield inflating the caller's frame. - [MethodImpl(MethodImplOptions.NoInlining)] - static void MyYield() => Thread.Yield(); - -} - diff --git a/src/tests/async/syncfibonacci-without-yields.cs b/src/tests/async/syncfibonacci-without-yields.cs index 1b40117c02419b..c6eeb9725d70f7 100644 --- a/src/tests/async/syncfibonacci-without-yields.cs +++ b/src/tests/async/syncfibonacci-without-yields.cs @@ -2,47 +2,49 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Diagnostics; -using System.Threading; using System.Threading.Tasks; -using System.Runtime.CompilerServices; +using System.Diagnostics; using Xunit; -public class SyncFibonacciWithoutYields +public class SyncFibonacci { - const uint Threshold = 1_000; + const int iterations = 3; + const bool doYields = false; public static int Main() { - for (int i = 0; i < 10; i++) - { - var sw = new Stopwatch(); - sw.Start(); - uint result = A(100_000_000); - Console.WriteLine($"{sw.ElapsedMilliseconds} ms result={result}"); - } + long allocated = GC.GetTotalAllocatedBytes(precise: true); + + Entry(); + + allocated = GC.GetTotalAllocatedBytes(precise: true) - allocated; + System.Console.WriteLine("allocated: " + allocated); return 100; } - static uint A(uint n) + public static void Entry() { - uint result = n; - for (uint i = 0; i < n; i++) - result = B(result); - return result; + for (int i = 0; i < iterations; i++) + { + var sw = Stopwatch.StartNew(); + int result = Fib(25); + sw.Stop(); + + Console.WriteLine($"{sw.ElapsedMilliseconds} ms result={result}"); + } } - static uint B(uint n) + static int Fib(int i) { - uint result = n; - - result = result * 1_999_999_981; - - result = result * 1_999_999_981; + if (i <= 1) + { + return 1; + } - result = result * 1_999_999_981; + int i1 = Fib(i - 1); + int i2 = Fib(i - 2); - return result; + return i1 + i2; } } diff --git a/src/tests/async/taskbased-asyncfibonacci-with-yields.cs b/src/tests/async/taskbased-asyncfibonacci-with-yields.cs index 11b131f959c92d..4829e029636da6 100644 --- a/src/tests/async/taskbased-asyncfibonacci-with-yields.cs +++ b/src/tests/async/taskbased-asyncfibonacci-with-yields.cs @@ -3,52 +3,53 @@ using System; using System.Threading.Tasks; -using System.Diagnostics; -using System.Runtime.CompilerServices; +using System.Diagnostics; using Xunit; public class TaskBasedAsyncFibonacciWithYields { - const uint Threshold = 1_000; + const int iterations = 3; + const bool doYields = true; - public static int Main() { return AsyncMain().Result; } - - static async Task AsyncMain() + public static int Main() { - for (int i = 0; i < 10; i++) - { - var sw = new Stopwatch(); - sw.Start(); - uint result = await A(100_000_000); - Console.WriteLine($"{sw.ElapsedMilliseconds} ms result={result}"); - } + long allocated = GC.GetTotalAllocatedBytes(precise: true); + + AsyncEntry().GetAwaiter().GetResult(); + + allocated = GC.GetTotalAllocatedBytes(precise: true) - allocated; + System.Console.WriteLine("allocated: " + allocated); return 100; } - static async Task A(uint n) + public static async Task AsyncEntry() { - uint result = n; - for (uint i = 0; i < n; i++) - result = await B(result); - return result; - } + for (int i = 0; i < iterations; i++) + { + var sw = Stopwatch.StartNew(); + int result = await Fib(25); + sw.Stop(); - static async Task B(uint n){ - uint result = n; + Console.WriteLine($"{sw.ElapsedMilliseconds} ms result={result}"); + } + } - result = result * 1_999_999_981; - if (result < Threshold) - await Task.Yield(); + static async Task Fib(int i) + { + if (i <= 1) + { + if (doYields) + { + await Task.Yield(); + } - result = result * 1_999_999_981; - if (result < Threshold) - await Task.Yield(); + return 1; + } - result = result * 1_999_999_981; - if (result < Threshold) - await Task.Yield(); + int i1 = await Fib(i - 1); + int i2 = await Fib(i - 2); - return result; + return i1 + i2; } } diff --git a/src/tests/async/taskbased-asyncfibonacci-without-yields.cs b/src/tests/async/taskbased-asyncfibonacci-without-yields.cs index 6be313d1986dec..e3e9c7ff21f8f1 100644 --- a/src/tests/async/taskbased-asyncfibonacci-without-yields.cs +++ b/src/tests/async/taskbased-asyncfibonacci-without-yields.cs @@ -2,49 +2,54 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Diagnostics; -using System.Runtime.CompilerServices; using System.Threading.Tasks; +using System.Diagnostics; using Xunit; public class TaskBasedAsyncFibonacciWithoutYields { - const uint Threshold = 1_000; + const int iterations = 3; + const bool doYields = false; - public static int Main() { return AsyncMain().Result; } - - static async Task AsyncMain() + public static int Main() { - for (int i = 0; i < 10; i++) - { - var sw = new Stopwatch(); - sw.Start(); - uint result = await A(100_000_000); - Console.WriteLine($"{sw.ElapsedMilliseconds} ms result={result}"); - } + long allocated = GC.GetTotalAllocatedBytes(precise: true); + + AsyncEntry().GetAwaiter().GetResult(); + + allocated = GC.GetTotalAllocatedBytes(precise: true) - allocated; + System.Console.WriteLine("allocated: " + allocated); return 100; } - static async Task A(uint n) + public static async Task AsyncEntry() { - uint result = n; - for (uint i = 0; i < n; i++) - result = await B(result); - return result; + for (int i = 0; i < iterations; i++) + { + var sw = Stopwatch.StartNew(); + int result = await Fib(25); + sw.Stop(); + + Console.WriteLine($"{sw.ElapsedMilliseconds} ms result={result}"); + } } - #pragma warning disable CS1998 - static async Task B(uint n) + static async Task Fib(int i) { - uint result = n; - - result = result * 1_999_999_981; + if (i <= 1) + { + if (doYields) + { + await Task.Yield(); + } - result = result * 1_999_999_981; + return 1; + } - result = result * 1_999_999_981; + int i1 = await Fib(i - 1); + int i2 = await Fib(i - 2); - return result; + return i1 + i2; } } diff --git a/src/tests/async/valuetaskbased-asyncfibonacci-with-yields.cs b/src/tests/async/valuetaskbased-asyncfibonacci-with-yields.cs index a288db583b4fbe..ac48372814efb6 100644 --- a/src/tests/async/valuetaskbased-asyncfibonacci-with-yields.cs +++ b/src/tests/async/valuetaskbased-asyncfibonacci-with-yields.cs @@ -3,52 +3,53 @@ using System; using System.Threading.Tasks; -using System.Diagnostics; -using System.Runtime.CompilerServices; +using System.Diagnostics; using Xunit; -public class ValueTaskBasedAsyncFibonacciWithYields +public class TaskBasedAsyncFibonacciWithYields { - const uint Threshold = 1_000; + const int iterations = 3; + const bool doYields = true; - public static int Main() { return AsyncMain().Result; } - - static async Task AsyncMain() + public static int Main() { - for (int i = 0; i < 10; i++) - { - var sw = new Stopwatch(); - sw.Start(); - uint result = await A(100_000_000); - Console.WriteLine($"{sw.ElapsedMilliseconds} ms result={result}"); - } + long allocated = GC.GetTotalAllocatedBytes(precise: true); + + AsyncEntry().GetAwaiter().GetResult(); + + allocated = GC.GetTotalAllocatedBytes(precise: true) - allocated; + System.Console.WriteLine("allocated: " + allocated); return 100; } - static async ValueTask A(uint n) + public static async ValueTask AsyncEntry() { - uint result = n; - for (uint i = 0; i < n; i++) - result = await B(result); - return result; - } + for (int i = 0; i < iterations; i++) + { + var sw = Stopwatch.StartNew(); + int result = await Fib(25); + sw.Stop(); - static async ValueTask B(uint n){ - uint result = n; + Console.WriteLine($"{sw.ElapsedMilliseconds} ms result={result}"); + } + } - result = result * 1_999_999_981; - if (result < Threshold) - await Task.Yield(); + static async ValueTask Fib(int i) + { + if (i <= 1) + { + if (doYields) + { + await Task.Yield(); + } - result = result * 1_999_999_981; - if (result < Threshold) - await Task.Yield(); + return 1; + } - result = result * 1_999_999_981; - if (result < Threshold) - await Task.Yield(); + int i1 = await Fib(i - 1); + int i2 = await Fib(i - 2); - return result; + return i1 + i2; } } diff --git a/src/tests/async/valuetaskbased-asyncfibonacci-without-yields.cs b/src/tests/async/valuetaskbased-asyncfibonacci-without-yields.cs index bdbb7b0b3647a2..1e3aab57ec7da5 100644 --- a/src/tests/async/valuetaskbased-asyncfibonacci-without-yields.cs +++ b/src/tests/async/valuetaskbased-asyncfibonacci-without-yields.cs @@ -2,49 +2,54 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Diagnostics; -using System.Runtime.CompilerServices; using System.Threading.Tasks; +using System.Diagnostics; using Xunit; -public class ValueTaskBasedAsyncFibonacciWithoutYields +public class TaskBasedAsyncFibonacciWithoutYields { - const uint Threshold = 1_000; + const int iterations = 3; + const bool doYields = false; - public static int Main() { return AsyncMain().Result; } - - static async ValueTask AsyncMain() + public static int Main() { - for (int i = 0; i < 10; i++) - { - var sw = new Stopwatch(); - sw.Start(); - uint result = await A(100_000_000); - Console.WriteLine($"{sw.ElapsedMilliseconds} ms result={result}"); - } + long allocated = GC.GetTotalAllocatedBytes(precise: true); + + AsyncEntry().GetAwaiter().GetResult(); + + allocated = GC.GetTotalAllocatedBytes(precise: true) - allocated; + System.Console.WriteLine("allocated: " + allocated); return 100; } - static async ValueTask A(uint n) + public static async ValueTask AsyncEntry() { - uint result = n; - for (uint i = 0; i < n; i++) - result = await B(result); - return result; + for (int i = 0; i < iterations; i++) + { + var sw = Stopwatch.StartNew(); + int result = await Fib(25); + sw.Stop(); + + Console.WriteLine($"{sw.ElapsedMilliseconds} ms result={result}"); + } } - #pragma warning disable CS1998 - static async ValueTask B(uint n) + static async ValueTask Fib(int i) { - uint result = n; - - result = result * 1_999_999_981; + if (i <= 1) + { + if (doYields) + { + await Task.Yield(); + } - result = result * 1_999_999_981; + return 1; + } - result = result * 1_999_999_981; + int i1 = await Fib(i - 1); + int i2 = await Fib(i - 2); - return result; + return i1 + i2; } } From d3be3290d645e2f40ac03bd75dd5878eeb582c79 Mon Sep 17 00:00:00 2001 From: Vladimir Sadov Date: Wed, 26 Mar 2025 08:54:26 -0700 Subject: [PATCH 151/203] [RuntimeAsync] Recognizing new overloads of Await related to the ConfigureAwait pattern. (#3051) * Add new helpers * added test for ConfigureAwait(false) * detecting and optimizing the ConfigAwait(0|1) pattern * set test to 3 iterations by default * Factor out Await pattern match as a separate method. --- src/coreclr/jit/compiler.h | 2 + src/coreclr/jit/importer.cpp | 122 +++++++++++++----- src/coreclr/jit/importercalls.cpp | 11 ++ src/coreclr/jit/namedintrinsiclist.h | 2 + .../src/CompatibilitySuppressions.xml | 24 ++++ .../CompilerServices/RuntimeHelpers.cs | 63 +++++++++ .../src/System/Threading/Tasks/Future.cs | 2 + .../src/System/Threading/Tasks/Task.cs | 2 + .../src/System/Threading/Tasks/ValueTask.cs | 2 + .../System.Runtime/ref/System.Runtime.cs | 4 + .../fibonacci-without-yields-config-await.cs | 57 ++++++++ ...bonacci-without-yields-config-await.csproj | 8 ++ 12 files changed, 266 insertions(+), 33 deletions(-) create mode 100644 src/tests/async/fibonacci-without-yields-config-await.cs create mode 100644 src/tests/async/fibonacci-without-yields-config-await.csproj diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 04ab89fe38e8bb..211ecc16a05b8f 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -4847,6 +4847,8 @@ class Compiler bool impMatchIsInstBooleanConversion(const BYTE* codeAddr, const BYTE* codeEndp, int* consumed); + bool impMatchAwaitPattern(const BYTE * codeAddr, const BYTE * codeEndp, int* configVal); + GenTree* impCastClassOrIsInstToTree( GenTree* op1, GenTree* op2, CORINFO_RESOLVED_TOKEN* pResolvedToken, bool isCastClass, bool* booleanCheck, IL_OFFSET ilOffset); diff --git a/src/coreclr/jit/importer.cpp b/src/coreclr/jit/importer.cpp index ce161e13f7d47d..f8975f2815bccf 100644 --- a/src/coreclr/jit/importer.cpp +++ b/src/coreclr/jit/importer.cpp @@ -5976,6 +5976,85 @@ bool Compiler::impBlockIsInALoop(BasicBlock* block) block->HasFlag(BBF_BACKWARD_JUMP); } +//------------------------------------------------------------------------ +// impMatchAwaitPattern: check if a method call starts an Await pattern +// that can be optimized for runtime async +// +// Arguments: +// codeAddr - IL after call[virt] +// codeEndp - End of IL code stream +// configVal - [out] set to 0 or 1, accordingly, if we saw ConfigureAwait(0|1) +// +// Returns: +// true if this is an Await that we can optimize +// +bool Compiler::impMatchAwaitPattern(const BYTE* codeAddr, const BYTE* codeEndp, int* configVal) +{ + // If we see the following code pattern in runtime async methods: + // + // call[virt] + // [ OPTIONAL ] + // ldc.i4.0 / ldc.i4.1 + // call[virt] + // call + // + // We emit an eqivalent of: + // + // call[virt] + // + // where "RtMethod" is the runtime-async counterpart of a Task-returning method. + // + // NOTE: we could potentially check if Method is not a thunk and, in cases when we can tell, + // bypass this optimization. Otherwise in a non-thunk case we would be + // replacing the pattern with a call to a thunk, which contains roughly the same code. + + const BYTE* nextOpcode = codeAddr + sizeof(mdToken); + // There must be enough space after ldc for {call + tk + call + tk} + if (nextOpcode + 2 * (1 + sizeof(mdToken)) < codeEndp) + { + uint8_t nextOp = getU1LittleEndian(nextOpcode); + uint8_t nextNextOp = getU1LittleEndian(nextOpcode + 1); + if ((nextOp != CEE_LDC_I4_0 && nextOp != CEE_LDC_I4_1) || + (nextNextOp != CEE_CALL && nextNextOp != CEE_CALLVIRT)) + { + goto checkForAwait; + } + + // check if the token after {ldc, call[virt]} is ConfigAwait + CORINFO_RESOLVED_TOKEN nextCallTok; + impResolveToken(nextOpcode + 2, &nextCallTok, CORINFO_TOKENKIND_Method); + + if (!eeIsIntrinsic(nextCallTok.hMethod) || + lookupNamedIntrinsic(nextCallTok.hMethod) != NI_System_Threading_Tasks_Task_ConfigureAwait) + { + goto checkForAwait; + } + + *configVal = nextOp == CEE_LDC_I4_0 ? 0 : 1; + // skip {ldc; call; } + nextOpcode += 1 + 1 + sizeof(mdToken); + } + +checkForAwait: + + if ((nextOpcode + sizeof(mdToken) < codeEndp) && (getU1LittleEndian(nextOpcode) == CEE_CALL)) + { + // resolve the next token + CORINFO_RESOLVED_TOKEN nextCallTok; + impResolveToken(nextOpcode + 1, &nextCallTok, CORINFO_TOKENKIND_Method); + + // check if it is an Await intrinsic + if (eeIsIntrinsic(nextCallTok.hMethod) && + lookupNamedIntrinsic(nextCallTok.hMethod) == NI_System_Runtime_CompilerServices_RuntimeHelpers_Await) + { + // yes, this is an Await + return true; + } + } + + return false; +} + #ifdef _PREFAST_ #pragma warning(push) #pragma warning(disable : 21000) // Suppress PREFast warning about overly large function @@ -9013,40 +9092,13 @@ void Compiler::impImportBlockCode(BasicBlock* block) if (opcode != CEE_CALLI) { bool isAwait = false; - if (JitConfig.JitOptimizeAwait()) + // TODO: The configVal should be wired to the actual implementation + // that control the flow of sync context. + // We do not have that yet. + int configVal= -1; // -1 not congigured, 0/1 configured to false/true + if (compIsAsync2() && JitConfig.JitOptimizeAwait()) { - // If we see the following code pattern in runtime async methods: - // - // call[virt] - // call - // - // we emit an eqivalent of - // - // call[virt] - // - // where "RtMethod" is the runtime-async counterpart of a Task-returning method. - // - // NOTE: we could potentially check if Method is not a thunk and, in cases when we can tell, - // bypass this optimization. Otherwise in a non-thunk case we would be - // replacing the pattern with a call to a thunk, which contains roughly the same code. - - const BYTE* nextOpcode = codeAddr + sizeof(mdToken); - if (compIsAsync2() && (nextOpcode + sizeof(mdToken) < codeEndp) && - (getU1LittleEndian(nextOpcode) == CEE_CALL)) - { - // resolve the next token - CORINFO_RESOLVED_TOKEN nextCallTok; - impResolveToken(nextOpcode + 1, &nextCallTok, CORINFO_TOKENKIND_Method); - - // check if it is an Await intrinsic - if (eeIsIntrinsic(nextCallTok.hMethod) && - lookupNamedIntrinsic(nextCallTok.hMethod) == - NI_System_Runtime_CompilerServices_RuntimeHelpers_Await) - { - // yes, this is an Await - isAwait = true; - } - } + isAwait = impMatchAwaitPattern(codeAddr, codeEndp, &configVal); } if (isAwait) @@ -9055,6 +9107,10 @@ void Compiler::impImportBlockCode(BasicBlock* block) if (resolvedToken.hMethod != NULL) { // There is a runtime async variant that is implicitly awaitable, just call that. + // if configured, skip {ldc call ConfigureAwait} + if (configVal >= 0) + codeAddr += 2 + sizeof(mdToken); + // Skip the call to `Await` codeAddr += 1 + sizeof(mdToken); } diff --git a/src/coreclr/jit/importercalls.cpp b/src/coreclr/jit/importercalls.cpp index b3f78ff2f4f501..8165f3b775fe80 100644 --- a/src/coreclr/jit/importercalls.cpp +++ b/src/coreclr/jit/importercalls.cpp @@ -11243,6 +11243,17 @@ NamedIntrinsic Compiler::lookupNamedIntrinsic(CORINFO_METHOD_HANDLE method) } } } + else if (strcmp(namespaceName, "Threading.Tasks") == 0) + { + if (strcmp(methodName, "ConfigureAwait") == 0) + { + if (strcmp(className, "Task`1") == 0 || strcmp(className, "Task") == 0 || + strcmp(className, "ValuTask`1") == 0 || strcmp(className, "ValueTask") == 0) + { + result = NI_System_Threading_Tasks_Task_ConfigureAwait; + } + } + } } } else if (strcmp(namespaceName, "Internal.Runtime") == 0) diff --git a/src/coreclr/jit/namedintrinsiclist.h b/src/coreclr/jit/namedintrinsiclist.h index 96ce387af66a4b..20d60f4b22c530 100644 --- a/src/coreclr/jit/namedintrinsiclist.h +++ b/src/coreclr/jit/namedintrinsiclist.h @@ -155,6 +155,8 @@ enum NamedIntrinsic : unsigned short NI_System_Threading_Interlocked_ExchangeAdd, NI_System_Threading_Interlocked_MemoryBarrier, + NI_System_Threading_Tasks_Task_ConfigureAwait, + // These two are special marker IDs so that we still get the inlining profitability boost NI_System_Numerics_Intrinsic, NI_System_Runtime_Intrinsics_Intrinsic, diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml b/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml index 69db65a1850946..8c95bf386d1e6a 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml @@ -857,6 +857,18 @@ CP0002 M:System.Threading.Lock.#ctor(System.Boolean) + + CP0002 + M:System.Runtime.CompilerServices.RuntimeHelpers.Await(System.Runtime.CompilerServices.ConfiguredTaskAwaitable) + ref/net10.0/System.Private.CoreLib.dll + lib/net10.0/System.Private.CoreLib.dll + + + CP0002 + M:System.Runtime.CompilerServices.RuntimeHelpers.Await(System.Runtime.CompilerServices.ConfiguredValueTaskAwaitable) + ref/net10.0/System.Private.CoreLib.dll + lib/net10.0/System.Private.CoreLib.dll + CP0002 M:System.Runtime.CompilerServices.RuntimeHelpers.Await(System.Threading.Tasks.Task) @@ -869,6 +881,18 @@ ref/net10.0/System.Private.CoreLib.dll lib/net10.0/System.Private.CoreLib.dll + + CP0002 + M:System.Runtime.CompilerServices.RuntimeHelpers.Await``1(System.Runtime.CompilerServices.ConfiguredTaskAwaitable{``0}) + ref/net10.0/System.Private.CoreLib.dll + lib/net10.0/System.Private.CoreLib.dll + + + CP0002 + M:System.Runtime.CompilerServices.RuntimeHelpers.Await``1(System.Runtime.CompilerServices.ConfiguredValueTaskAwaitable{``0}) + ref/net10.0/System.Private.CoreLib.dll + lib/net10.0/System.Private.CoreLib.dll + CP0002 M:System.Runtime.CompilerServices.RuntimeHelpers.Await``1(System.Threading.Tasks.Task{``0}) diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs index 93f9e01de1fa45..45399a2a7d3c05 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs @@ -271,6 +271,69 @@ public static void Await(ValueTask task) awaiter.GetResult(); } + // Marked intrinsic since this needs to be + // recognized as an async2 call. + [Intrinsic] + [BypassReadyToRun] + [MethodImpl(MethodImplOptions.Async)] + public static void Await(ConfiguredTaskAwaitable configuredAwaitable) + { + ConfiguredTaskAwaitable.ConfiguredTaskAwaiter awaiter = configuredAwaitable.GetAwaiter(); + if (!awaiter.IsCompleted) + { + UnsafeAwaitAwaiterFromRuntimeAsync(awaiter); + } + + awaiter.GetResult(); + } + + // Marked intrinsic since this needs to be + // recognized as an async2 call. + [Intrinsic] + [BypassReadyToRun] + [MethodImpl(MethodImplOptions.Async)] + public static void Await(ConfiguredValueTaskAwaitable configuredAwaitable) + { + ConfiguredValueTaskAwaitable.ConfiguredValueTaskAwaiter awaiter = configuredAwaitable.GetAwaiter(); + if (!awaiter.IsCompleted) + { + UnsafeAwaitAwaiterFromRuntimeAsync(awaiter); + } + + awaiter.GetResult(); + } + + // Marked intrinsic since this needs to be + // recognized as an async2 call. + [Intrinsic] + [BypassReadyToRun] + [MethodImpl(MethodImplOptions.Async)] + public static T Await(ConfiguredTaskAwaitable configuredAwaitable) + { + ConfiguredTaskAwaitable.ConfiguredTaskAwaiter awaiter = configuredAwaitable.GetAwaiter(); + if (!awaiter.IsCompleted) + { + UnsafeAwaitAwaiterFromRuntimeAsync(awaiter); + } + + return awaiter.GetResult(); + } + + // Marked intrinsic since this needs to be + // recognized as an async2 call. + [Intrinsic] + [BypassReadyToRun] + [MethodImpl(MethodImplOptions.Async)] + public static T Await(ConfiguredValueTaskAwaitable configuredAwaitable) + { + ConfiguredValueTaskAwaitable.ConfiguredValueTaskAwaiter awaiter = configuredAwaitable.GetAwaiter(); + if (!awaiter.IsCompleted) + { + UnsafeAwaitAwaiterFromRuntimeAsync(awaiter); + } + + return awaiter.GetResult(); + } #endif } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Future.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Future.cs index ce4f18b6099baf..1e96a5905b29e2 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Future.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Future.cs @@ -513,6 +513,7 @@ internal override void InnerInvoke() /// true to attempt to marshal the continuation back to the original context captured; otherwise, false. /// /// An object used to await this task. + [Intrinsic] public new ConfiguredTaskAwaitable ConfigureAwait(bool continueOnCapturedContext) { return new ConfiguredTaskAwaitable(this, continueOnCapturedContext ? ConfigureAwaitOptions.ContinueOnCapturedContext : ConfigureAwaitOptions.None); @@ -522,6 +523,7 @@ internal override void InnerInvoke() /// Options used to configure how awaits on this task are performed. /// An object used to await this task. /// The argument specifies an invalid value. + [Intrinsic] public new ConfiguredTaskAwaitable ConfigureAwait(ConfigureAwaitOptions options) { if ((options & ~(ConfigureAwaitOptions.ContinueOnCapturedContext | diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs index d007bc756e8bdb..0f64ba8ea52f94 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs @@ -2448,6 +2448,7 @@ public TaskAwaiter GetAwaiter() /// true to attempt to marshal the continuation back to the original context captured; otherwise, false. /// /// An object used to await this task. + [Intrinsic] public ConfiguredTaskAwaitable ConfigureAwait(bool continueOnCapturedContext) { return new ConfiguredTaskAwaitable(this, continueOnCapturedContext ? ConfigureAwaitOptions.ContinueOnCapturedContext : ConfigureAwaitOptions.None); @@ -2457,6 +2458,7 @@ public ConfiguredTaskAwaitable ConfigureAwait(bool continueOnCapturedContext) /// Options used to configure how awaits on this task are performed. /// An object used to await this task. /// The argument specifies an invalid value. + [Intrinsic] public ConfiguredTaskAwaitable ConfigureAwait(ConfigureAwaitOptions options) { if ((options & ~(ConfigureAwaitOptions.ContinueOnCapturedContext | diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/ValueTask.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/ValueTask.cs index c8d0aec3960bbe..90f86c68159c28 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/ValueTask.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/ValueTask.cs @@ -425,6 +425,7 @@ internal static ValueTask DangerousCreateFromTypedValueTask(ValueTask /// true to attempt to marshal the continuation back to the captured context; otherwise, false. /// + [Intrinsic] [MethodImpl(MethodImplOptions.AggressiveInlining)] public ConfiguredValueTaskAwaitable ConfigureAwait(bool continueOnCapturedContext) => new ConfiguredValueTaskAwaitable(new ValueTask(_obj, _token, continueOnCapturedContext)); @@ -825,6 +826,7 @@ public TResult Result /// /// true to attempt to marshal the continuation back to the captured context; otherwise, false. /// + [Intrinsic] [MethodImpl(MethodImplOptions.AggressiveInlining)] public ConfiguredValueTaskAwaitable ConfigureAwait(bool continueOnCapturedContext) => new ConfiguredValueTaskAwaitable(new ValueTask(_obj, _result, _token, continueOnCapturedContext)); diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index c5879025b87244..100b88dcaecedb 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -13757,6 +13757,10 @@ public static void AwaitAwaiterFromRuntimeAsync(TAwaiter awaiter) wher public static T Await(System.Threading.Tasks.Task task) { throw null; } public static void Await(System.Threading.Tasks.ValueTask task) { throw null; } public static T Await(System.Threading.Tasks.ValueTask task) { throw null; } + public static void Await(System.Runtime.CompilerServices.ConfiguredTaskAwaitable configuredAwaitable) { throw null; } + public static void Await(System.Runtime.CompilerServices.ConfiguredValueTaskAwaitable configuredAwaitable) { throw null; } + public static T Await(System.Runtime.CompilerServices.ConfiguredTaskAwaitable configuredAwaitable) { throw null; } + public static T Await(System.Runtime.CompilerServices.ConfiguredValueTaskAwaitable configuredAwaitable) { throw null; } } public sealed partial class RuntimeWrappedException : System.Exception { diff --git a/src/tests/async/fibonacci-without-yields-config-await.cs b/src/tests/async/fibonacci-without-yields-config-await.cs new file mode 100644 index 00000000000000..047411ec312838 --- /dev/null +++ b/src/tests/async/fibonacci-without-yields-config-await.cs @@ -0,0 +1,57 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Threading.Tasks; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using Xunit; + +#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously + +public class Async2FibonacciWithYields +{ + const int iterations = 3; + const bool doYields = false; + + [Fact] + public static void Test() + { + long allocated = GC.GetTotalAllocatedBytes(precise: true); + + AsyncEntry().GetAwaiter().GetResult(); + + allocated = GC.GetTotalAllocatedBytes(precise: true) - allocated; + System.Console.WriteLine("allocated: " + allocated); + } + + public static async2 Task AsyncEntry() + { + for (int i = 0; i < iterations; i++) + { + var sw = Stopwatch.StartNew(); + int result = RuntimeHelpers.Await(Fib(30).ConfigureAwait(false)); + sw.Stop(); + + Console.WriteLine($"{sw.ElapsedMilliseconds} ms result={result}"); + } + } + + static async2 Task Fib(int i) + { + if (i <= 1) + { + if (doYields) + { + await Task.Yield(); + } + + return 1; + } + + int i1 = RuntimeHelpers.Await(Fib(i - 1).ConfigureAwait(true)); + int i2 = RuntimeHelpers.Await(Fib(i - 2).ConfigureAwait(false)); + + return i1 + i2; + } +} diff --git a/src/tests/async/fibonacci-without-yields-config-await.csproj b/src/tests/async/fibonacci-without-yields-config-await.csproj new file mode 100644 index 00000000000000..de6d5e08882e86 --- /dev/null +++ b/src/tests/async/fibonacci-without-yields-config-await.csproj @@ -0,0 +1,8 @@ + + + True + + + + + From 94f8e5ab15f97aed6a1fd1e1ff1a8010ee858b64 Mon Sep 17 00:00:00 2001 From: Vladimir Sadov Date: Wed, 26 Mar 2025 08:59:30 -0700 Subject: [PATCH 152/203] turn off the assert and enable test scenario (#3054) --- src/coreclr/vm/prestub.cpp | 2 -- src/tests/async/struct.cs | 10 +++++++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/coreclr/vm/prestub.cpp b/src/coreclr/vm/prestub.cpp index f07ba9a987d466..8de48f53ab59d7 100644 --- a/src/coreclr/vm/prestub.cpp +++ b/src/coreclr/vm/prestub.cpp @@ -1843,8 +1843,6 @@ void MethodDesc::EmitJitStateMachineBasedRuntimeAsyncThunk(MethodDesc* pAsyncOth DWORD localArg = 0; if (thunkMsig.HasThis()) { - // Struct async thunks not yet implemented - _ASSERTE(!this->GetMethodTable()->IsValueType()); pCode->EmitLDARG(localArg++); } diff --git a/src/tests/async/struct.cs b/src/tests/async/struct.cs index fd73631e9b9d66..cc2923b89ece5e 100644 --- a/src/tests/async/struct.cs +++ b/src/tests/async/struct.cs @@ -14,9 +14,17 @@ public class Async2Struct public static void TestEntryPoint() { Async().Wait(); + Async2().Wait(); } - private static async2 Task Async() + private static async Task Async() + { + S s = new S(100); + await s.Test(); + AssertEqual(100, s.Value); + } + + private static async2 Task Async2() { S s = new S(100); await s.Test(); From eae52eda2655c1d4132b68855c308300ea8b6265 Mon Sep 17 00:00:00 2001 From: vsadov <8218165+VSadov@users.noreply.github.com> Date: Thu, 27 Mar 2025 13:14:48 -0700 Subject: [PATCH 153/203] reconcile root and eng files with runtime repo --- .gitignore | 3 - Directory.Build.props | 139 +---- NuGet.config | 1 - README.md | 35 -- buildroslynnugets.cmd | 34 -- eng/Brewfile | 5 - eng/DotNetBuild.props | 17 + eng/OSArch.props | 35 ++ eng/Publishing.props | 221 +++----- eng/RuntimeIdentifier.props | 100 ++++ eng/Signing.props | 164 ++++-- eng/Subsets.props | 13 +- eng/Version.Details.xml | 284 +++++----- eng/Versions.props | 155 +++--- eng/build-analysis-configuration.json | 4 + eng/build.ps1 | 2 +- eng/build.sh | 6 +- eng/common/CIBuild.cmd | 2 +- eng/common/build.ps1 | 7 +- eng/common/build.sh | 13 +- eng/common/cibuild.sh | 2 +- eng/common/core-templates/job/job.yml | 1 - .../job/publish-build-assets.yml | 4 - .../core-templates/jobs/codeql-build.yml | 1 - eng/common/core-templates/jobs/jobs.yml | 4 - .../core-templates/steps/generate-sbom.yml | 2 +- .../steps/install-microbuild.yml | 30 +- eng/common/cross/armel/armel.jessie.patch | 43 -- eng/common/cross/build-rootfs.sh | 53 +- eng/common/generate-sbom-prep.ps1 | 20 +- eng/common/generate-sbom-prep.sh | 17 +- eng/common/native/install-dependencies.sh | 7 +- eng/common/templates-official/job/job.yml | 5 +- .../steps/publish-build-artifacts.yml | 7 +- eng/common/templates/job/job.yml | 4 +- .../steps/publish-build-artifacts.yml | 8 +- eng/common/tools.ps1 | 2 +- eng/common/tools.sh | 2 +- eng/docker/libraries-sdk.linux.Dockerfile | 2 +- eng/docker/libraries-sdk.windows.Dockerfile | 2 +- eng/extract-for-crossdac.ps1 | 2 +- eng/native/configurecompiler.cmake | 2 +- eng/native/configureplatform.cmake | 2 +- eng/native/functions.cmake | 15 +- eng/pipelines/common/global-build-job.yml | 37 +- .../common/templates/global-build-step.yml | 2 +- .../build-runtime-tests-and-send-to-helix.yml | 1 - .../runtimes/build-runtime-tests.yml | 1 - .../templates/runtimes/run-test-job.yml | 5 +- .../templates/runtimes/send-to-helix-step.yml | 2 + .../common/templates/runtimes/xplat-job.yml | 6 - .../common/templates/wasm-library-tests.yml | 2 +- eng/pipelines/coreclr/libraries-pgo.yml | 1 - .../coreclr/superpmi-collect-test.yml | 368 +------------ eng/pipelines/coreclr/superpmi-collect.yml | 364 +------------ eng/pipelines/coreclr/superpmi-replay-apx.yml | 19 + eng/pipelines/coreclr/superpmi-replay.yml | 74 +-- .../coreclr/templates/helix-queues-setup.yml | 12 +- .../coreclr/templates/jit-replay-pipeline.yml | 58 +++ .../templates/run-superpmi-replay-job.yml | 9 +- .../templates/superpmi-collect-pipeline.yml | 359 +++++++++++++ .../coreclr/templates/superpmi-replay-job.yml | 15 +- .../diagnostics/runtime-diag-job.yml | 261 ++++++++++ .../runtime-extra-platforms-ioslike.yml | 4 +- ...ntime-extra-platforms-ioslikesimulator.yml | 2 +- .../runtime-extra-platforms-maccatalyst.yml | 4 +- .../runtime-extra-platforms-other.yml | 12 +- .../libraries/helix-queues-setup.yml | 12 +- eng/pipelines/libraries/outerloop.yml | 8 +- eng/pipelines/libraries/stress/http.yml | 4 +- eng/pipelines/libraries/stress/ssl.yml | 4 +- eng/pipelines/official/pipeline.yml | 30 ++ .../{jobs => }/prepare-signed-artifacts.yml | 23 +- eng/pipelines/official/stages/publish.yml | 57 -- eng/pipelines/performance/perf-build.yml | 236 +++++++++ eng/pipelines/performance/perf-slow.yml | 4 +- eng/pipelines/performance/perf.yml | 1 - .../templates/perf-bdn-build-jobs.yml | 2 +- .../performance/templates/perf-build-jobs.yml | 2 +- .../templates/perf-coreclr-build-jobs.yml | 6 +- .../perf-ios-scenarios-build-jobs.yml | 4 +- eng/pipelines/runtime-diagnostics.yml | 68 ++- eng/pipelines/runtime-official.yml | 492 ++++++++---------- eng/pipelines/runtime.yml | 16 +- eng/testing/BrowserVersions.props | 12 +- eng/testing/tests.wasm.targets | 4 +- global.json | 10 +- 87 files changed, 2109 insertions(+), 1981 deletions(-) delete mode 100644 buildroslynnugets.cmd delete mode 100644 eng/Brewfile create mode 100644 eng/OSArch.props create mode 100644 eng/RuntimeIdentifier.props delete mode 100644 eng/common/cross/armel/armel.jessie.patch create mode 100644 eng/pipelines/coreclr/superpmi-replay-apx.yml create mode 100644 eng/pipelines/coreclr/templates/jit-replay-pipeline.yml create mode 100644 eng/pipelines/coreclr/templates/superpmi-collect-pipeline.yml create mode 100644 eng/pipelines/diagnostics/runtime-diag-job.yml create mode 100644 eng/pipelines/official/pipeline.yml rename eng/pipelines/official/{jobs => }/prepare-signed-artifacts.yml (73%) delete mode 100644 eng/pipelines/official/stages/publish.yml create mode 100644 eng/pipelines/performance/perf-build.yml diff --git a/.gitignore b/.gitignore index 0c35f3bd69184f..88e4f6ab632aee 100644 --- a/.gitignore +++ b/.gitignore @@ -11,9 +11,6 @@ syntax: glob .packages .tools -# nuget packages for custom roslyn -roslynpackages - # User-specific files *.suo *.user diff --git a/Directory.Build.props b/Directory.Build.props index b3265a67189527..d13e0749062eaf 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -17,39 +17,8 @@ false - - <_hostOS>linux - <_hostOS Condition="$([MSBuild]::IsOSPlatform('OSX'))">osx - <_hostOS Condition="$([MSBuild]::IsOSPlatform('FREEBSD'))">freebsd - <_hostOS Condition="$([MSBuild]::IsOSPlatform('NETBSD'))">netbsd - <_hostOS Condition="$([MSBuild]::IsOSPlatform('ILLUMOS'))">illumos - <_hostOS Condition="$([MSBuild]::IsOSPlatform('SOLARIS'))">solaris - <_hostOS Condition="$([MSBuild]::IsOSPlatform('HAIKU'))">haiku - <_hostOS Condition="$([MSBuild]::IsOSPlatform('WINDOWS'))">windows - $(_hostOS) - browser - $(_hostOS) - true - true - - - - - <_hostArch>$([System.Runtime.InteropServices.RuntimeInformation]::ProcessArchitecture.ToString().ToLowerInvariant) - $(_hostArch) - wasm - wasm - arm - armv6 - armel - arm64 - loongarch64 - s390x - ppc64le - x64 - x64 - $(TargetArchitecture) - + + @@ -197,104 +163,7 @@ $(MonoCrossAOTTargetOS)+tvos+ios+maccatalyst - - false - true - - - - - - - <_portableOS>$(TargetOS.ToLowerInvariant()) - <_portableOS Condition="'$(_portableOS)' == 'windows'">win - - - <_portableOS Condition="'$(_portableOS)' == 'anyos'">$(__PortableTargetOS) - - - <_portableOS Condition="'$(_portableOS)' == 'linux' and '$(__PortableTargetOS)' == 'linux-musl'">linux-musl - <_portableOS Condition="'$(_portableOS)' == 'linux' and '$(__PortableTargetOS)' == 'linux-bionic'">linux-bionic - - - <_portableOS Condition="'$(HostOS)' == 'win' and '$(TargetsMobile)' != 'true'">win - - - - - <_packageOS>$(_portableOS) - - <_packageOS Condition="'$(CrossBuild)' == 'true' and '$(_portableOS)' != 'linux-musl' and '$(_portableOS)' != 'linux-bionic' and '$(_portableOS)' != 'android'">$(_hostOS) - - - $(PackageOS)-$(TargetArchitecture) - $(_packageOS)-$(TargetArchitecture) - - - - - - <_portableHostOS>$(_hostOS) - <_portableHostOS Condition="'$(_portableHostOS)' == 'windows'">win - <_portableHostOS Condition="'$(CrossBuild)' != 'true' and '$(_portableOS)' == 'linux-musl'">linux-musl - - - $(ToolsOS)-$(_hostArch) - $(_portableHostOS)-$(_hostArch) - - - $(ToolsRID) - - - - - <_hostRid Condition="'$(MSBuildRuntimeType)' == 'core'">$([System.Runtime.InteropServices.RuntimeInformation]::RuntimeIdentifier) - <_hostRid Condition="'$(MSBuildRuntimeType)' != 'core'">win-$([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture.ToString().ToLowerInvariant) - - <_parseDistroRid>$(__DistroRid) - <_parseDistroRid Condition="'$(_parseDistroRid)' == ''">$(_hostRid) - <_distroRidIndex>$(_parseDistroRid.LastIndexOf('-')) - - <_outputOS>$(_parseDistroRid.SubString(0, $(_distroRidIndex))) - <_outputOS Condition="'$(PortableBuild)' == 'true'">$(_portableOS) - - $(_outputOS)-$(TargetArchitecture) - - - - true - true - true - true - true - true - true - true - true - true - true - true - true - true - true - true - true - true - true - true - + $([MSBuild]::NormalizeDirectory('$(ArtifactsBinDir)', 'microsoft.netcore.app.ref')) @@ -357,7 +226,7 @@ - runtimelab + runtime https://github.com/dotnet/$(GitHubRepositoryName) https://dot.net microsoft,dotnetframework diff --git a/NuGet.config b/NuGet.config index f05e91436c2ec5..b8539443065853 100644 --- a/NuGet.config +++ b/NuGet.config @@ -19,7 +19,6 @@ - diff --git a/README.md b/README.md index a7d5533b6df305..cd95f118df2ffb 100644 --- a/README.md +++ b/README.md @@ -1,40 +1,5 @@ -# .NET Runtime - Async2 Experiment - -This branch contains experimental fork of CoreCLR [.NET runtime](http://github.com/dotnet/runtime) where we explore implementing async directly in the runtime instead of generating state machines in the IL compiler (Roslyn). - -## Current status - -We support two prototypes: a JIT-based codegen prototype, and an unwinder-only based prototype. -The prototypes are described in detail in the design document, see below. -The former prototype is the default; the latter can be switched to by setting `DOTNET_RuntimeAsyncViaJitGeneratedStateMachines=0`. - -Current support in the prototypes looks like the following: - -| **Feature/characteristic** | **JIT based state machines** | **Unwinder based state machines** | -|------------------------------------------|:------------------------------:|:-----------------------------------:| -| **Generics** | ✅ | ❌ | -| **Byrefs live across suspension points** | ❌ | ✅ | -| **Exception handling** | ✅ | ❌ | -| **Returns via return buffers** | ✅ | ❌ | - - -## Samples - -See the tests in [src/tests/Loader/async](src/tests/Loader/async). - -## Documentation - -- Before building, run the buildroslynugets.cmd script, it will build a variant of the Roslyn compiler that can be used to test this codebase. Otherwise follow the standard developer workflow. -- [Async Experiment Issue](https://github.com/dotnet/runtime/issues/94620) -- [Design and details](docs/design/features/runtime-handled-tasks.md) - ---- - # .NET Runtime -In order to build in this repo, you must have set up a Roslyn repo parallel to this repo with the name dotnet-roslyn, and it must have a remote called AzDo which has the updated compiler in it. Then you must run buildroslynnugets.cmd to create a local copy of the compiler for use in the repo. The equivalent work has not yet been done for running on Unix-like platforms. - - [![Build Status](https://dev.azure.com/dnceng-public/public/_apis/build/status/dotnet/runtime/runtime?branchName=main)](https://dev.azure.com/dnceng-public/public/_build/latest?definitionId=129&branchName=main) [![Help Wanted](https://img.shields.io/github/issues/dotnet/runtime/help%20wanted?style=flat-square&color=%232EA043&label=help%20wanted)](https://github.com/dotnet/runtime/labels/help%20wanted) [![Good First Issue](https://img.shields.io/github/issues/dotnet/runtime/good%20first%20issue?style=flat-square&color=%232EA043&label=good%20first%20issue)](https://github.com/dotnet/runtime/labels/good%20first%20issue) diff --git a/buildroslynnugets.cmd b/buildroslynnugets.cmd deleted file mode 100644 index 983551b3c29755..00000000000000 --- a/buildroslynnugets.cmd +++ /dev/null @@ -1,34 +0,0 @@ -setlocal ENABLEEXTENSIONS -pushd %~dp0 -set ASYNC_ROSLYN_COMMIT=22232ae7939c97984d1158f4f371b0d89ea3db08 -set ASYNC_SUFFIX=async-13 -set ASYNC_ROSLYN_BRANCH=demos/async2-experiment1 - -cd .. -if not exist async-roslyn-repo git clone -b %ASYNC_ROSLYN_BRANCH% -o async_roslyn_remote https://github.com/dotnet/roslyn.git async-roslyn-repo - -pushd async-roslyn-repo - -git fetch async_roslyn_remote %ASYNC_ROSLYN_BRANCH% -rem when updating this, make sure to update the ASYNC_SUFFIX above and the versions.props file -git checkout %ASYNC_ROSLYN_COMMIT% - -call restore.cmd -call build.cmd -c release - -call dotnet pack src\NuGet\Microsoft.Net.Compilers.Toolset\AnyCpu\Microsoft.Net.Compilers.Toolset.Package.csproj --version-suffix %ASYNC_SUFFIX% -call dotnet pack src\Workspaces\CSharp\Portable\Microsoft.CodeAnalysis.CSharp.Workspaces.csproj --version-suffix %ASYNC_SUFFIX% -call dotnet pack src\Compilers\CSharp\Portable\Microsoft.CodeAnalysis.CSharp.csproj --version-suffix %ASYNC_SUFFIX% -call dotnet pack src\Compilers\Core\Portable\Microsoft.CodeAnalysis.csproj --version-suffix %ASYNC_SUFFIX% -call dotnet pack src\Workspaces\Core\Portable\Microsoft.CodeAnalysis.Workspaces.csproj --version-suffix %ASYNC_SUFFIX% - -pushd %~dp0 - -md roslynpackages - -copy ..\async-roslyn-repo\artifacts\packages\Release\Shipping\Microsoft.Net.Compilers.Toolset.4.14.0-%ASYNC_SUFFIX%.nupkg roslynpackages -copy ..\async-roslyn-repo\artifacts\packages\Release\Shipping\Microsoft.CodeAnalysis.Workspaces.Common.4.14.0-%ASYNC_SUFFIX%.nupkg roslynpackages -copy ..\async-roslyn-repo\artifacts\packages\Release\Shipping\Microsoft.CodeAnalysis.CSharp.Workspaces.4.14.0-%ASYNC_SUFFIX%.nupkg roslynpackages -copy ..\async-roslyn-repo\artifacts\packages\Release\Shipping\Microsoft.CodeAnalysis.CSharp.4.14.0-%ASYNC_SUFFIX%.nupkg roslynpackages -copy ..\async-roslyn-repo\artifacts\packages\Release\Shipping\Microsoft.CodeAnalysis.Common.4.14.0-%ASYNC_SUFFIX%.nupkg roslynpackages - diff --git a/eng/Brewfile b/eng/Brewfile deleted file mode 100644 index 7a145df5dc637c..00000000000000 --- a/eng/Brewfile +++ /dev/null @@ -1,5 +0,0 @@ -brew "cmake" -brew "icu4c" -brew "openssl@3" -brew "pkg-config" -brew "python3" diff --git a/eng/DotNetBuild.props b/eng/DotNetBuild.props index 87a013e43b127f..81d4d6792a1098 100644 --- a/eng/DotNetBuild.props +++ b/eng/DotNetBuild.props @@ -38,6 +38,19 @@ true + + + + false + + true + + false + + @@ -86,6 +99,8 @@ $(InnerBuildArgs) /p:DefaultArtifactVisibility=$(DefaultArtifactVisibility) $(InnerBuildArgs) /p:DotNetEsrpToolPath=$(DotNetEsrpToolPath) $(InnerBuildArgs) /p:DotNetBuildTests=$(DotNetBuildTests) + $(InnerBuildArgs) /p:PublishingVersion=$(PublishingVersion) + $(InnerBuildArgs) /p:RepositoryUrl=$(RepositoryUrl) $(InnerBuildArgs) /p:SourceBuiltAssetsDir=$(SourceBuiltAssetsDir) @@ -107,6 +122,8 @@ $(InnerBuildArgs) /p:NetCoreAppToolCurrentVersion=$(NetCoreAppToolCurrentVersion) + + $(InnerBuildArgs) /p:EnableDefaultRidSpecificArtifacts=$(EnableDefaultRidSpecificArtifacts) diff --git a/eng/OSArch.props b/eng/OSArch.props new file mode 100644 index 00000000000000..d1be0745175825 --- /dev/null +++ b/eng/OSArch.props @@ -0,0 +1,35 @@ + + + <_hostOS>linux + <_hostOS Condition="$([MSBuild]::IsOSPlatform('OSX'))">osx + <_hostOS Condition="$([MSBuild]::IsOSPlatform('FREEBSD'))">freebsd + <_hostOS Condition="$([MSBuild]::IsOSPlatform('NETBSD'))">netbsd + <_hostOS Condition="$([MSBuild]::IsOSPlatform('ILLUMOS'))">illumos + <_hostOS Condition="$([MSBuild]::IsOSPlatform('SOLARIS'))">solaris + <_hostOS Condition="$([MSBuild]::IsOSPlatform('HAIKU'))">haiku + <_hostOS Condition="$([MSBuild]::IsOSPlatform('WINDOWS'))">windows + $(_hostOS) + browser + $(_hostOS) + true + true + + + + + <_hostArch>$([System.Runtime.InteropServices.RuntimeInformation]::ProcessArchitecture.ToString().ToLowerInvariant) + $(_hostArch) + wasm + wasm + arm + armv6 + armel + arm64 + loongarch64 + s390x + ppc64le + x64 + x64 + $(TargetArchitecture) + + \ No newline at end of file diff --git a/eng/Publishing.props b/eng/Publishing.props index da3d606ed683c4..dbd80cac884d17 100644 --- a/eng/Publishing.props +++ b/eng/Publishing.props @@ -1,153 +1,62 @@ - - + true false - - $(TargetArchitecture) - - - - - - + + true + $(ArtifactsDir)staging/ + $(ArtifactsStagingDir)SymStore + + $(ArtifactsStagingDir) + $(ArtifactsStagingDir)packages/$(Configuration)/Shipping + $(ArtifactsStagingDir)packages/$(Configuration)/NonShipping + true + + $(ArtifactsTmpDir)/manifests/$(Configuration) + VerticalManifest.xml + + + + + + + %(FullPath).sha512 + - - - - - - - - - - - - - - - - + + <_HostArtifact Include="$(ArtifactsPackagesDir)**\runtime.*.Microsoft.NETCore.ILAsm.*.nupkg" + Exclude="$(ArtifactsPackagesDir)**\runtime.$(OutputRID).Microsoft.NETCore.ILAsm.*.nupkg" /> - - + <_HostArtifact Include="$(ArtifactsPackagesDir)**\runtime.*.Microsoft.NETCore.ILDAsm.*.nupkg" + Exclude="$(ArtifactsPackagesDir)**\runtime.$(OutputRID).Microsoft.NETCore.ILDAsm.*.nupkg" /> - - + <_HostArtifact Include="$(ArtifactsPackagesDir)**\runtime.*.Microsoft.DotNet.ILCompiler.*.nupkg" + Exclude="$(ArtifactsPackagesDir)**\runtime.$(OutputRID).Microsoft.DotNet.ILCompiler.*.nupkg" /> - - + <_HostArtifact Include="$(ArtifactsPackagesDir)**\Microsoft.NETCore.App.Crossgen2.*.nupkg" + Exclude="$(ArtifactsPackagesDir)**\Microsoft.NETCore.App.Crossgen2.$(OutputRID).*.nupkg" /> - - - - - - - - - - - - - - - - - - - - - - + @@ -168,15 +77,15 @@ This ensures that we don't produce these files in the "Repo source build" builds, but we do produce them in both the VMR and the runtime official build. --> - - <_ShouldGenerateProductVersionFiles Condition="'$(DotNetBuildRepo)' == 'true' and '$(DotNetBuildOrchestrator)' == 'true'">true - <_ShouldGenerateProductVersionFiles Condition="'$(DotNetBuildRepo)' != 'true' and '$(DotNetBuildOrchestrator)' != 'true'">true + + true + true + Condition="'$(ShouldGenerateProductVersionFiles)' == 'true'"> - - + + + + + + + + + + - - - - - - - - - diff --git a/eng/RuntimeIdentifier.props b/eng/RuntimeIdentifier.props new file mode 100644 index 00000000000000..9ebd5e65194874 --- /dev/null +++ b/eng/RuntimeIdentifier.props @@ -0,0 +1,100 @@ + + + false + true + + + + + + + <_portableOS>$(TargetOS.ToLowerInvariant()) + <_portableOS Condition="'$(_portableOS)' == 'windows'">win + + + <_portableOS Condition="'$(_portableOS)' == 'anyos'">$(__PortableTargetOS) + + + <_portableOS Condition="'$(_portableOS)' == 'linux' and '$(__PortableTargetOS)' == 'linux-musl'">linux-musl + <_portableOS Condition="'$(_portableOS)' == 'linux' and '$(__PortableTargetOS)' == 'linux-bionic'">linux-bionic + + + <_portableOS Condition="'$(HostOS)' == 'win' and '$(TargetsMobile)' != 'true'">win + + + + + <_packageOS>$(_portableOS) + + <_packageOS Condition="'$(CrossBuild)' == 'true' and '$(_portableOS)' != 'linux-musl' and '$(_portableOS)' != 'linux-bionic' and '$(_portableOS)' != 'android'">$(_hostOS) + + + $(PackageOS)-$(TargetArchitecture) + $(_packageOS)-$(TargetArchitecture) + + + + + + <_portableHostOS>$(_hostOS) + <_portableHostOS Condition="'$(_portableHostOS)' == 'windows'">win + <_portableHostOS Condition="'$(CrossBuild)' != 'true' and '$(_portableOS)' == 'linux-musl'">linux-musl + + + $(ToolsOS)-$(_hostArch) + $(_portableHostOS)-$(_hostArch) + + + $(ToolsRID) + + + + + <_hostRid Condition="'$(MSBuildRuntimeType)' == 'core'">$([System.Runtime.InteropServices.RuntimeInformation]::RuntimeIdentifier) + <_hostRid Condition="'$(MSBuildRuntimeType)' != 'core'">win-$([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture.ToString().ToLowerInvariant) + + <_parseDistroRid>$(__DistroRid) + <_parseDistroRid Condition="'$(_parseDistroRid)' == ''">$(_hostRid) + <_distroRidIndex>$(_parseDistroRid.LastIndexOf('-')) + + <_outputOS>$(_parseDistroRid.SubString(0, $(_distroRidIndex))) + <_outputOS Condition="'$(PortableBuild)' == 'true'">$(_portableOS) + + $(_outputOS)-$(TargetArchitecture) + + + + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + + \ No newline at end of file diff --git a/eng/Signing.props b/eng/Signing.props index 12fc319128c6a6..d06028e5d5e24b 100644 --- a/eng/Signing.props +++ b/eng/Signing.props @@ -1,6 +1,35 @@ + + + - false + + false + true + false + + + $(PackageRID) + + + false + true + + true @@ -14,6 +43,9 @@ + + + @@ -24,18 +56,6 @@ - - - - - - - - - - - - - - - - + + + - - - + + %(FullPath).sha512 - + + + + + + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/eng/Subsets.props b/eng/Subsets.props index aa4da501a51acc..fe161b00b0ac18 100644 --- a/eng/Subsets.props +++ b/eng/Subsets.props @@ -307,6 +307,10 @@ $(ClrRuntimeBuildSubsets);ClrILToolsSubset=true + + $(ClrRuntimeBuildSubsets);ClrCdacSubset=true + + $(ClrRuntimeBuildSubsets);ClrNativeAotSubset=true @@ -440,9 +444,12 @@ - - - + + + diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index af7bfc5fd40368..a935c46af19ab0 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -8,37 +8,37 @@ https://github.com/dotnet/wcf 7f504aabb1988e9a093c1e74d8040bd52feb2f01 - + https://github.com/dotnet/llvm-project - 047727dd52040402cc85b90533e6e020fc0e84bb + da5dd054a531e6fea65643b7e754285b73eab433 - + https://github.com/dotnet/llvm-project - 047727dd52040402cc85b90533e6e020fc0e84bb + da5dd054a531e6fea65643b7e754285b73eab433 - + https://github.com/dotnet/llvm-project - 047727dd52040402cc85b90533e6e020fc0e84bb + da5dd054a531e6fea65643b7e754285b73eab433 - + https://github.com/dotnet/llvm-project - 047727dd52040402cc85b90533e6e020fc0e84bb + da5dd054a531e6fea65643b7e754285b73eab433 - + https://github.com/dotnet/llvm-project - 047727dd52040402cc85b90533e6e020fc0e84bb + da5dd054a531e6fea65643b7e754285b73eab433 - + https://github.com/dotnet/llvm-project - 047727dd52040402cc85b90533e6e020fc0e84bb + da5dd054a531e6fea65643b7e754285b73eab433 - + https://github.com/dotnet/llvm-project - 047727dd52040402cc85b90533e6e020fc0e84bb + da5dd054a531e6fea65643b7e754285b73eab433 - + https://github.com/dotnet/llvm-project - 047727dd52040402cc85b90533e6e020fc0e84bb + da5dd054a531e6fea65643b7e754285b73eab433 https://github.com/dotnet/command-line-api @@ -60,20 +60,20 @@ f7a78a77069262841fe3c3c6b89ff243fc43cefc - + https://github.com/dotnet/emsdk - 7619d65bf2534a42257d1d8488552e59790475f6 + e7ebd8aa0424ba4dc6ac03419954c3287d891faa - + https://github.com/dotnet/emsdk - 7619d65bf2534a42257d1d8488552e59790475f6 + e7ebd8aa0424ba4dc6ac03419954c3287d891faa - + https://github.com/dotnet/source-build-reference-packages - 9c8e3885e783e984a48ed3585ff51d86b020d7ce + cf30e8fd726730aa4b142275fb2fc0503528b7dd @@ -84,87 +84,87 @@ - + https://github.com/dotnet/arcade - 5b1e2d133be215e729cf90016ae12b520950ce7a + 584374998d21b1d550452753c7b5f41725c07a11 - + https://github.com/dotnet/arcade - 5b1e2d133be215e729cf90016ae12b520950ce7a + 584374998d21b1d550452753c7b5f41725c07a11 - + https://github.com/dotnet/arcade - 5b1e2d133be215e729cf90016ae12b520950ce7a + 584374998d21b1d550452753c7b5f41725c07a11 - + https://github.com/dotnet/arcade - 5b1e2d133be215e729cf90016ae12b520950ce7a + 584374998d21b1d550452753c7b5f41725c07a11 - + https://github.com/dotnet/arcade - 5b1e2d133be215e729cf90016ae12b520950ce7a + 584374998d21b1d550452753c7b5f41725c07a11 - + https://github.com/dotnet/arcade - 5b1e2d133be215e729cf90016ae12b520950ce7a + 584374998d21b1d550452753c7b5f41725c07a11 - + https://github.com/dotnet/arcade - 5b1e2d133be215e729cf90016ae12b520950ce7a + 584374998d21b1d550452753c7b5f41725c07a11 - + https://github.com/dotnet/arcade - 5b1e2d133be215e729cf90016ae12b520950ce7a + 584374998d21b1d550452753c7b5f41725c07a11 - + https://github.com/dotnet/arcade - 5b1e2d133be215e729cf90016ae12b520950ce7a + 584374998d21b1d550452753c7b5f41725c07a11 - + https://github.com/dotnet/arcade - 5b1e2d133be215e729cf90016ae12b520950ce7a + 584374998d21b1d550452753c7b5f41725c07a11 - + https://github.com/dotnet/arcade - 5b1e2d133be215e729cf90016ae12b520950ce7a + 584374998d21b1d550452753c7b5f41725c07a11 - + https://github.com/dotnet/arcade - 5b1e2d133be215e729cf90016ae12b520950ce7a + 584374998d21b1d550452753c7b5f41725c07a11 - + https://github.com/dotnet/arcade - 5b1e2d133be215e729cf90016ae12b520950ce7a + 584374998d21b1d550452753c7b5f41725c07a11 - + https://github.com/dotnet/arcade - 5b1e2d133be215e729cf90016ae12b520950ce7a + 584374998d21b1d550452753c7b5f41725c07a11 - + https://github.com/dotnet/arcade - 5b1e2d133be215e729cf90016ae12b520950ce7a + 584374998d21b1d550452753c7b5f41725c07a11 - + https://github.com/dotnet/arcade - 5b1e2d133be215e729cf90016ae12b520950ce7a + 584374998d21b1d550452753c7b5f41725c07a11 - + https://github.com/dotnet/arcade - 5b1e2d133be215e729cf90016ae12b520950ce7a + 584374998d21b1d550452753c7b5f41725c07a11 - + https://github.com/dotnet/arcade - 5b1e2d133be215e729cf90016ae12b520950ce7a + 584374998d21b1d550452753c7b5f41725c07a11 - + https://github.com/dotnet/arcade - 5b1e2d133be215e729cf90016ae12b520950ce7a + 584374998d21b1d550452753c7b5f41725c07a11 - + https://github.com/dotnet/arcade - 5b1e2d133be215e729cf90016ae12b520950ce7a + 584374998d21b1d550452753c7b5f41725c07a11 https://github.com/dotnet/runtime-assets @@ -218,89 +218,89 @@ https://github.com/dotnet/runtime-assets 207b0aac7c32b425a684734a70ba78bfdddb9e48 - + https://github.com/dotnet/llvm-project - 047727dd52040402cc85b90533e6e020fc0e84bb + da5dd054a531e6fea65643b7e754285b73eab433 - + https://github.com/dotnet/llvm-project - 047727dd52040402cc85b90533e6e020fc0e84bb + da5dd054a531e6fea65643b7e754285b73eab433 - + https://github.com/dotnet/llvm-project - 047727dd52040402cc85b90533e6e020fc0e84bb + da5dd054a531e6fea65643b7e754285b73eab433 - + https://github.com/dotnet/llvm-project - 047727dd52040402cc85b90533e6e020fc0e84bb + da5dd054a531e6fea65643b7e754285b73eab433 - + https://github.com/dotnet/llvm-project - 047727dd52040402cc85b90533e6e020fc0e84bb + da5dd054a531e6fea65643b7e754285b73eab433 - + https://github.com/dotnet/llvm-project - 047727dd52040402cc85b90533e6e020fc0e84bb + da5dd054a531e6fea65643b7e754285b73eab433 - + https://github.com/dotnet/llvm-project - 047727dd52040402cc85b90533e6e020fc0e84bb + da5dd054a531e6fea65643b7e754285b73eab433 - + https://github.com/dotnet/llvm-project - 047727dd52040402cc85b90533e6e020fc0e84bb + da5dd054a531e6fea65643b7e754285b73eab433 - + https://github.com/dotnet/llvm-project - 047727dd52040402cc85b90533e6e020fc0e84bb + da5dd054a531e6fea65643b7e754285b73eab433 - + https://github.com/dotnet/llvm-project - 047727dd52040402cc85b90533e6e020fc0e84bb + da5dd054a531e6fea65643b7e754285b73eab433 - + https://github.com/dotnet/llvm-project - 047727dd52040402cc85b90533e6e020fc0e84bb + da5dd054a531e6fea65643b7e754285b73eab433 - + https://github.com/dotnet/llvm-project - 047727dd52040402cc85b90533e6e020fc0e84bb + da5dd054a531e6fea65643b7e754285b73eab433 - + https://github.com/dotnet/llvm-project - 047727dd52040402cc85b90533e6e020fc0e84bb + da5dd054a531e6fea65643b7e754285b73eab433 - + https://github.com/dotnet/llvm-project - 047727dd52040402cc85b90533e6e020fc0e84bb + da5dd054a531e6fea65643b7e754285b73eab433 - + https://github.com/dotnet/llvm-project - 047727dd52040402cc85b90533e6e020fc0e84bb + da5dd054a531e6fea65643b7e754285b73eab433 - + https://github.com/dotnet/llvm-project - 047727dd52040402cc85b90533e6e020fc0e84bb + da5dd054a531e6fea65643b7e754285b73eab433 - + https://github.com/dotnet/llvm-project - 047727dd52040402cc85b90533e6e020fc0e84bb + da5dd054a531e6fea65643b7e754285b73eab433 - + https://github.com/dotnet/llvm-project - 047727dd52040402cc85b90533e6e020fc0e84bb + da5dd054a531e6fea65643b7e754285b73eab433 - + https://github.com/dotnet/llvm-project - 047727dd52040402cc85b90533e6e020fc0e84bb + da5dd054a531e6fea65643b7e754285b73eab433 - + https://github.com/dotnet/llvm-project - 047727dd52040402cc85b90533e6e020fc0e84bb + da5dd054a531e6fea65643b7e754285b73eab433 - + https://github.com/dotnet/llvm-project - 047727dd52040402cc85b90533e6e020fc0e84bb + da5dd054a531e6fea65643b7e754285b73eab433 https://github.com/dotnet/runtime @@ -336,37 +336,37 @@ https://github.com/dotnet/runtime 088d199063dd1bf7fa00a445d23f93f915f84b31 - + https://github.com/dotnet/xharness - cf1b2925785f504d4d52773bcab470044e35ea15 + 132fdfbd9c8f09f8c51179c1e7742f048f94734c - + https://github.com/dotnet/xharness - cf1b2925785f504d4d52773bcab470044e35ea15 + 132fdfbd9c8f09f8c51179c1e7742f048f94734c - + https://github.com/dotnet/xharness - cf1b2925785f504d4d52773bcab470044e35ea15 + 132fdfbd9c8f09f8c51179c1e7742f048f94734c - + https://github.com/dotnet/arcade - 5b1e2d133be215e729cf90016ae12b520950ce7a + 584374998d21b1d550452753c7b5f41725c07a11 - + https://dev.azure.com/dnceng/internal/_git/dotnet-optimization - 79859263121f7fb30c6f8a5c463616afc5a1fc5d + f0670b0742edbc53c8a2803fa86e5e4417a0e681 - + https://dev.azure.com/dnceng/internal/_git/dotnet-optimization - 79859263121f7fb30c6f8a5c463616afc5a1fc5d + f0670b0742edbc53c8a2803fa86e5e4417a0e681 - + https://dev.azure.com/dnceng/internal/_git/dotnet-optimization - 79859263121f7fb30c6f8a5c463616afc5a1fc5d + f0670b0742edbc53c8a2803fa86e5e4417a0e681 - + https://dev.azure.com/dnceng/internal/_git/dotnet-optimization - 79859263121f7fb30c6f8a5c463616afc5a1fc5d + f0670b0742edbc53c8a2803fa86e5e4417a0e681 https://github.com/dotnet/hotreload-utils @@ -402,17 +402,17 @@ bfe9d9f9059008d919d867fe5bdfabfe8b6ed69d - + https://github.com/dotnet/sdk - c921fd0a32c3e6001b10791dd0a30f8ef80f915f + 5ebd5267376b61d01b4afc9d4cf9c832488312b2 - + https://dev.azure.com/dnceng/internal/_git/dotnet-optimization - 79859263121f7fb30c6f8a5c463616afc5a1fc5d + f0670b0742edbc53c8a2803fa86e5e4417a0e681 - + https://dev.azure.com/dnceng/internal/_git/dotnet-optimization - 79859263121f7fb30c6f8a5c463616afc5a1fc5d + f0670b0742edbc53c8a2803fa86e5e4417a0e681 @@ -432,37 +432,37 @@ https://github.com/NuGet/NuGet.Client 8fef55f5a55a3b4f2c96cd1a9b5ddc51d4b927f8 - + https://github.com/dotnet/node - de1caf9e631e1e15d7fa7f016569d8ea16a3e470 + 7f33d14aae0d91f2d5befda939160177e13b3f47 - + https://github.com/dotnet/node - de1caf9e631e1e15d7fa7f016569d8ea16a3e470 + 7f33d14aae0d91f2d5befda939160177e13b3f47 - + https://github.com/dotnet/node - de1caf9e631e1e15d7fa7f016569d8ea16a3e470 + 7f33d14aae0d91f2d5befda939160177e13b3f47 - + https://github.com/dotnet/node - de1caf9e631e1e15d7fa7f016569d8ea16a3e470 + 7f33d14aae0d91f2d5befda939160177e13b3f47 - + https://github.com/dotnet/node - de1caf9e631e1e15d7fa7f016569d8ea16a3e470 + 7f33d14aae0d91f2d5befda939160177e13b3f47 - + https://github.com/dotnet/node - de1caf9e631e1e15d7fa7f016569d8ea16a3e470 + 7f33d14aae0d91f2d5befda939160177e13b3f47 - + https://github.com/dotnet/node - de1caf9e631e1e15d7fa7f016569d8ea16a3e470 + 7f33d14aae0d91f2d5befda939160177e13b3f47 - + https://github.com/dotnet/node - de1caf9e631e1e15d7fa7f016569d8ea16a3e470 + 7f33d14aae0d91f2d5befda939160177e13b3f47 https://github.com/dotnet/runtime-assets diff --git a/eng/Versions.props b/eng/Versions.props index 4f21ee086e5c40..74d48a52fedec6 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -7,12 +7,12 @@ 0 0 $(MajorVersion).0.100 - 9.0.2 + 9.0.3 8.0.$([MSBuild]::Add($([System.Version]::Parse('$(PackageVersionNet9)').Build),11)) 7.0.20 - 6.0.$([MSBuild]::Add($([System.Version]::Parse('$(PackageVersionNet8)').Build),25)) - async - 1 + 6.0.36 + preview + 4 false release @@ -44,9 +44,9 @@ Any tools that contribute to the design-time experience should use the MicrosoftCodeAnalysisVersion_LatestVS property above to ensure they do not break the local dev experience. --> - 4.14.0-async-13 - 4.14.0-async-13 - 4.14.0-async-13 + 4.14.0-2.25121.3 + 4.14.0-2.25121.3 + 4.14.0-2.25121.3 - 10.0.100-preview.3.25126.5 + 10.0.100-preview.3.25173.9 - 10.0.0-beta.25125.2 - 10.0.0-beta.25125.2 - 10.0.0-beta.25125.2 - 10.0.0-beta.25125.2 - 2.9.2-beta.25125.2 - 10.0.0-beta.25125.2 - 2.9.2-beta.25125.2 - 10.0.0-beta.25125.2 - 10.0.0-beta.25125.2 - 10.0.0-beta.25125.2 - 10.0.0-beta.25125.2 - 10.0.0-beta.25125.2 - 10.0.0-beta.25125.2 - 10.0.0-beta.25125.2 - 10.0.0-beta.25125.2 - 10.0.0-beta.25125.2 + 10.0.0-beta.25175.2 + 10.0.0-beta.25175.2 + 10.0.0-beta.25175.2 + 10.0.0-beta.25175.2 + 2.9.2-beta.25175.2 + 10.0.0-beta.25175.2 + 2.9.2-beta.25175.2 + 10.0.0-beta.25175.2 + 10.0.0-beta.25175.2 + 10.0.0-beta.25175.2 + 10.0.0-beta.25175.2 + 10.0.0-beta.25175.2 + 10.0.0-beta.25175.2 + 10.0.0-beta.25175.2 + 10.0.0-beta.25175.2 + 10.0.0-beta.25175.2 1.4.0 @@ -110,19 +110,23 @@ 6.0.0 10.0.0-preview.3.25152.4 + + 6.0.0 + 4.6.1 + 4.9.0 + 4.6.2 + 4.6.1 + 6.1.1 + 4.6.2 + 4.6.1 6.0.0 - 6.0.0 5.0.0 1.2.0-beta.556 - 4.6.0 5.0.0 - 4.9.0 8.0.0 8.0.1 5.0.0 - 4.6.0 - 4.6.0 10.0.0-preview.3.25152.4 10.0.0-preview.3.25152.4 6.0.0 @@ -131,16 +135,19 @@ 5.0.0 7.0.0 10.0.0-preview.3.25152.4 - 6.1.0 7.0.0 - 4.6.0 - 4.5.0 10.0.0-preview.3.25152.4 + 8.0.0 + 4.5.1 8.0.0 - 8.0.4 + 4.5.5 + 8.0.5 8.0.0 8.0.0 + 8.0.0 + 8.0.0 + 4.5.4 10.0.0-beta.25126.1 10.0.0-beta.25126.1 @@ -158,18 +165,18 @@ 10.0.0-beta.25126.1 10.0.0-beta.25126.1 - 10.0.0-prerelease.25103.1 - 10.0.0-prerelease.25103.1 - 10.0.0-prerelease.25103.1 + 10.0.0-prerelease.25167.4 + 10.0.0-prerelease.25167.4 + 10.0.0-prerelease.25167.4 10.0.0-alpha.0.25126.1 - 1.0.0-prerelease.25104.10 - 1.0.0-prerelease.25104.10 - 1.0.0-prerelease.25104.10 - 1.0.0-prerelease.25104.10 - 1.0.0-prerelease.25104.10 - 1.0.0-prerelease.25104.10 + 1.0.0-prerelease.25171.2 + 1.0.0-prerelease.25171.2 + 1.0.0-prerelease.25171.2 + 1.0.0-prerelease.25171.2 + 1.0.0-prerelease.25171.2 + 1.0.0-prerelease.25171.2 2.0.0 17.10.0-beta1.24272.1 @@ -201,7 +208,7 @@ 1.0.2 2.0.4 4.18.4 - 6.7.0 + 8.0.2 2.14.3 2.9.1 @@ -226,51 +233,51 @@ 2.4.8 9.0.0-alpha.1.24167.3 - 19.1.0-alpha.1.25103.1 - 19.1.0-alpha.1.25103.1 - 19.1.0-alpha.1.25103.1 - 19.1.0-alpha.1.25103.1 - 19.1.0-alpha.1.25103.1 - 19.1.0-alpha.1.25103.1 - 19.1.0-alpha.1.25103.1 - 19.1.0-alpha.1.25103.1 - 19.1.0-alpha.1.25103.1 - 19.1.0-alpha.1.25103.1 - 19.1.0-alpha.1.25103.1 - 19.1.0-alpha.1.25103.1 - 19.1.0-alpha.1.25103.1 - 19.1.0-alpha.1.25103.1 - 19.1.0-alpha.1.25103.1 - 19.1.0-alpha.1.25103.1 - 19.1.0-alpha.1.25103.1 - 19.1.0-alpha.1.25103.1 - 19.1.0-alpha.1.25103.1 - 19.1.0-alpha.1.25103.1 - 19.1.0-alpha.1.25103.1 + 19.1.0-alpha.1.25167.1 + 19.1.0-alpha.1.25167.1 + 19.1.0-alpha.1.25167.1 + 19.1.0-alpha.1.25167.1 + 19.1.0-alpha.1.25167.1 + 19.1.0-alpha.1.25167.1 + 19.1.0-alpha.1.25167.1 + 19.1.0-alpha.1.25167.1 + 19.1.0-alpha.1.25167.1 + 19.1.0-alpha.1.25167.1 + 19.1.0-alpha.1.25167.1 + 19.1.0-alpha.1.25167.1 + 19.1.0-alpha.1.25167.1 + 19.1.0-alpha.1.25167.1 + 19.1.0-alpha.1.25167.1 + 19.1.0-alpha.1.25167.1 + 19.1.0-alpha.1.25167.1 + 19.1.0-alpha.1.25167.1 + 19.1.0-alpha.1.25167.1 + 19.1.0-alpha.1.25167.1 + 19.1.0-alpha.1.25167.1 - 10.0.0-preview.3.25155.1 + 10.0.0-preview.4.25176.1 $(MicrosoftNETWorkloadEmscriptenCurrentManifest100100TransportVersion) 1.1.87-gba258badda 1.0.0-v3.14.0.5722 - 19.1.0-alpha.1.25103.1 - 19.1.0-alpha.1.25103.1 - 19.1.0-alpha.1.25103.1 - 19.1.0-alpha.1.25103.1 - 19.1.0-alpha.1.25103.1 - 19.1.0-alpha.1.25103.1 - 19.1.0-alpha.1.25103.1 - 19.1.0-alpha.1.25103.1 + 19.1.0-alpha.1.25167.1 + 19.1.0-alpha.1.25167.1 + 19.1.0-alpha.1.25167.1 + 19.1.0-alpha.1.25167.1 + 19.1.0-alpha.1.25167.1 + 19.1.0-alpha.1.25167.1 + 19.1.0-alpha.1.25167.1 + 19.1.0-alpha.1.25167.1 3.1.7 1.0.406601 $(MicrosoftDotNetApiCompatTaskVersion) - 10.0.0-alpha.1.25125.1 + 10.0.0-alpha.1.25169.1 $(MicrosoftNETRuntimeEmscriptenVersion) $(runtimewinx64MicrosoftNETCoreRuntimeWasmNodeTransportPackageVersion) diff --git a/eng/build-analysis-configuration.json b/eng/build-analysis-configuration.json index 06bf441873e2dc..208e753c836201 100644 --- a/eng/build-analysis-configuration.json +++ b/eng/build-analysis-configuration.json @@ -15,6 +15,10 @@ { "PipelineId": 157, "PipelineName": "runtime-llvm" + }, + { + "PipelineId": 265, + "PipelineName": "runtime-nativeaot-outerloop" } ] } diff --git a/eng/build.ps1 b/eng/build.ps1 index e38be81d660ecd..dd41e2cf85fbe9 100644 --- a/eng/build.ps1 +++ b/eng/build.ps1 @@ -78,7 +78,7 @@ function Get-Help() { Write-Host "Libraries settings:" Write-Host " -coverage Collect code coverage when testing." - Write-Host " -framework (-f) Build framework: net10.0 or net48." + Write-Host " -framework (-f) Build framework: net10.0 or net481." Write-Host " [Default: net10.0]" Write-Host " -testnobuild Skip building tests when invoking -test." Write-Host " -testscope Scope tests, allowed values: innerloop, outerloop, all." diff --git a/eng/build.sh b/eng/build.sh index 1cab7739726fa3..a4e36af19ee956 100755 --- a/eng/build.sh +++ b/eng/build.sh @@ -66,7 +66,7 @@ usage() echo "Libraries settings:" echo " --coverage Collect code coverage when testing." - echo " --framework (-f) Build framework: net10.0 or net48." + echo " --framework (-f) Build framework: net10.0 or net481." echo " [Default: net10.0]" echo " --testnobuild Skip building tests when invoking -test." echo " --testscope Test scope, allowed values: innerloop, outerloop, all." @@ -542,6 +542,10 @@ fi if [[ "$os" == "browser" ]]; then # override default arch for Browser, we only support wasm arch=wasm + # because on docker instance without swap file, MSBuild nodes need to make some room for LLVM + # https://github.com/dotnet/runtime/issues/113724 + # this is hexa percentage: 46-> 70% + export DOTNET_GCHeapHardLimitPercent="46" fi if [[ "$os" == "wasi" ]]; then # override default arch for wasi, we only support wasm diff --git a/eng/common/CIBuild.cmd b/eng/common/CIBuild.cmd index 56c2f25ac22ffa..ac1f72bf94e055 100644 --- a/eng/common/CIBuild.cmd +++ b/eng/common/CIBuild.cmd @@ -1,2 +1,2 @@ @echo off -powershell -ExecutionPolicy ByPass -NoProfile -command "& """%~dp0Build.ps1""" -restore -build -test -sign -pack -publish -ci %*" \ No newline at end of file +powershell -ExecutionPolicy ByPass -NoProfile -command "& """%~dp0Build.ps1""" -restore -build -test -sign -pack -publish -ci %*" diff --git a/eng/common/build.ps1 b/eng/common/build.ps1 index 438f9920c43e4e..d419c3a2eff55c 100644 --- a/eng/common/build.ps1 +++ b/eng/common/build.ps1 @@ -7,6 +7,7 @@ Param( [string] $msbuildEngine = $null, [bool] $warnAsError = $true, [bool] $nodeReuse = $true, + [switch] $buildCheck = $false, [switch][Alias('r')]$restore, [switch] $deployDeps, [switch][Alias('b')]$build, @@ -71,6 +72,8 @@ function Print-Usage() { Write-Host " -msbuildEngine Msbuild engine to use to run build ('dotnet', 'vs', or unspecified)." Write-Host " -excludePrereleaseVS Set to exclude build engines in prerelease versions of Visual Studio" Write-Host " -nativeToolsOnMachine Sets the native tools on machine environment variable (indicating that the script should use native tools on machine)" + Write-Host " -nodeReuse Sets nodereuse msbuild parameter ('true' or 'false')" + Write-Host " -buildCheck Sets /check msbuild parameter" Write-Host "" Write-Host "Command line arguments not listed above are passed thru to msbuild." @@ -97,6 +100,7 @@ function Build { $bl = if ($binaryLog) { '/bl:' + (Join-Path $LogDir 'Build.binlog') } else { '' } $platformArg = if ($platform) { "/p:Platform=$platform" } else { '' } + $check = if ($buildCheck) { '/check' } else { '' } if ($projects) { # Re-assign properties to a new variable because PowerShell doesn't let us append properties directly for unclear reasons. @@ -113,6 +117,7 @@ function Build { MSBuild $toolsetBuildProj ` $bl ` $platformArg ` + $check ` /p:Configuration=$configuration ` /p:RepoRoot=$RepoRoot ` /p:Restore=$restore ` @@ -166,4 +171,4 @@ catch { ExitWithExitCode 1 } -ExitWithExitCode 0 +ExitWithExitCode 0 \ No newline at end of file diff --git a/eng/common/build.sh b/eng/common/build.sh index 483647daf182c6..e24bb68f484001 100755 --- a/eng/common/build.sh +++ b/eng/common/build.sh @@ -42,6 +42,7 @@ usage() echo " --prepareMachine Prepare machine for CI run, clean up processes after build" echo " --nodeReuse Sets nodereuse msbuild parameter ('true' or 'false')" echo " --warnAsError Sets warnaserror msbuild parameter ('true' or 'false')" + echo " --buildCheck Sets /check msbuild parameter" echo "" echo "Command line arguments not listed above are passed thru to msbuild." echo "Arguments can also be passed in with a single hyphen." @@ -76,6 +77,7 @@ clean=false warn_as_error=true node_reuse=true +build_check=false binary_log=false exclude_ci_binary_log=false pipelines_log=false @@ -173,6 +175,9 @@ while [[ $# > 0 ]]; do node_reuse=$2 shift ;; + -buildcheck) + build_check=true + ;; -runtimesourcefeed) runtime_source_feed=$2 shift @@ -224,8 +229,14 @@ function Build { bl="/bl:\"$log_dir/Build.binlog\"" fi + local check="" + if [[ "$build_check" == true ]]; then + check="/check" + fi + MSBuild $_InitializeToolset \ $bl \ + $check \ /p:Configuration=$configuration \ /p:RepoRoot="$repo_root" \ /p:Restore=$restore \ @@ -256,4 +267,4 @@ if [[ "$restore" == true ]]; then InitializeNativeTools fi -Build +Build \ No newline at end of file diff --git a/eng/common/cibuild.sh b/eng/common/cibuild.sh index 1a02c0dec8fd7b..66e3b0ac61c365 100755 --- a/eng/common/cibuild.sh +++ b/eng/common/cibuild.sh @@ -13,4 +13,4 @@ while [[ -h $source ]]; do done scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" -. "$scriptroot/build.sh" --restore --build --test --pack --publish --ci $@ \ No newline at end of file +. "$scriptroot/build.sh" --restore --build --test --pack --publish --ci $@ diff --git a/eng/common/core-templates/job/job.yml b/eng/common/core-templates/job/job.yml index 295c9a2317c44b..4dfb3d86836c1d 100644 --- a/eng/common/core-templates/job/job.yml +++ b/eng/common/core-templates/job/job.yml @@ -23,7 +23,6 @@ parameters: enablePublishBuildArtifacts: false enablePublishBuildAssets: false enablePublishTestResults: false - enablePublishUsingPipelines: false enableBuildRetry: false mergeTestResults: false testRunTitle: '' diff --git a/eng/common/core-templates/job/publish-build-assets.yml b/eng/common/core-templates/job/publish-build-assets.yml index c7d59dcbfe1566..5da3c2a2a20967 100644 --- a/eng/common/core-templates/job/publish-build-assets.yml +++ b/eng/common/core-templates/job/publish-build-assets.yml @@ -20,9 +20,6 @@ parameters: # if 'true', the build won't run any of the internal only steps, even if it is running in non-public projects. runAsPublic: false - # Optional: whether the build's artifacts will be published using release pipelines or direct feed publishing - publishUsingPipelines: false - # Optional: whether the build's artifacts will be published using release pipelines or direct feed publishing publishAssetsImmediately: false @@ -96,7 +93,6 @@ jobs: arguments: -task PublishBuildAssets -restore -msbuildEngine dotnet /p:ManifestsPath='$(Build.StagingDirectory)/AssetManifests' /p:MaestroApiEndpoint=https://maestro.dot.net - /p:PublishUsingPipelines=${{ parameters.publishUsingPipelines }} /p:OfficialBuildId=$(Build.BuildNumber) condition: ${{ parameters.condition }} continueOnError: ${{ parameters.continueOnError }} diff --git a/eng/common/core-templates/jobs/codeql-build.yml b/eng/common/core-templates/jobs/codeql-build.yml index f2144252cc65c8..693b00b370447b 100644 --- a/eng/common/core-templates/jobs/codeql-build.yml +++ b/eng/common/core-templates/jobs/codeql-build.yml @@ -15,7 +15,6 @@ jobs: enablePublishBuildArtifacts: false enablePublishTestResults: false enablePublishBuildAssets: false - enablePublishUsingPipelines: false enableTelemetry: true variables: diff --git a/eng/common/core-templates/jobs/jobs.yml b/eng/common/core-templates/jobs/jobs.yml index ea69be4341c62f..3d2deaa4a06d35 100644 --- a/eng/common/core-templates/jobs/jobs.yml +++ b/eng/common/core-templates/jobs/jobs.yml @@ -5,9 +5,6 @@ parameters: # Optional: Include PublishBuildArtifacts task enablePublishBuildArtifacts: false - # Optional: Enable publishing using release pipelines - enablePublishUsingPipelines: false - # Optional: Enable running the source-build jobs to build repo from source enableSourceBuild: false @@ -112,7 +109,6 @@ jobs: - Source_Build_Complete runAsPublic: ${{ parameters.runAsPublic }} - publishUsingPipelines: ${{ parameters.enablePublishUsingPipelines }} publishAssetsImmediately: ${{ parameters.publishAssetsImmediately }} enablePublishBuildArtifacts: ${{ parameters.enablePublishBuildArtifacts }} artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} diff --git a/eng/common/core-templates/steps/generate-sbom.yml b/eng/common/core-templates/steps/generate-sbom.yml index b8e6dea83c2886..44a9636cdff90a 100644 --- a/eng/common/core-templates/steps/generate-sbom.yml +++ b/eng/common/core-templates/steps/generate-sbom.yml @@ -38,7 +38,7 @@ steps: PackageName: ${{ parameters.packageName }} BuildDropPath: ${{ parameters.buildDropPath }} PackageVersion: ${{ parameters.packageVersion }} - ManifestDirPath: ${{ parameters.manifestDirPath }} + ManifestDirPath: ${{ parameters.manifestDirPath }}/$(ARTIFACT_NAME) ${{ if ne(parameters.IgnoreDirectories, '') }}: AdditionalComponentDetectorArgs: '--IgnoreDirectories ${{ parameters.IgnoreDirectories }}' diff --git a/eng/common/core-templates/steps/install-microbuild.yml b/eng/common/core-templates/steps/install-microbuild.yml index 2a6a529482b522..dba506e74c3cab 100644 --- a/eng/common/core-templates/steps/install-microbuild.yml +++ b/eng/common/core-templates/steps/install-microbuild.yml @@ -5,7 +5,7 @@ parameters: # Will be ignored if 'enableMicrobuild' is false or 'Agent.Os' is 'Windows_NT' enableMicrobuildForMacAndLinux: false # Location of the MicroBuild output folder - microBuildOutputFolder: '$(Agent.TempDirectory)' + microBuildOutputFolder: '$(Build.SourcesDirectory)' continueOnError: false steps: @@ -41,7 +41,7 @@ steps: inputs: packageType: sdk version: 8.0.x - installationPath: ${{ parameters.microBuildOutputFolder }}/dotnet + installationPath: ${{ parameters.microBuildOutputFolder }}/.dotnet workingDirectory: ${{ parameters.microBuildOutputFolder }} condition: and(succeeded(), ne(variables['Agent.Os'], 'Windows_NT')) @@ -53,6 +53,7 @@ steps: feedSource: https://dnceng.pkgs.visualstudio.com/_packaging/MicroBuildToolset/nuget/v3/index.json ${{ if and(eq(parameters.enableMicrobuildForMacAndLinux, 'true'), ne(variables['Agent.Os'], 'Windows_NT')) }}: azureSubscription: 'MicroBuild Signing Task (DevDiv)' + useEsrpCli: true env: TeamName: $(_TeamName) MicroBuildOutputFolderOverride: ${{ parameters.microBuildOutputFolder }} @@ -71,3 +72,28 @@ steps: eq(variables['_SignType'], 'real') ) )) + + # Workaround for ESRP CLI on Linux - https://github.com/dotnet/source-build/issues/4964 + - ${{ if eq(parameters.enableMicrobuildForMacAndLinux, 'true') }}: + - task: UseDotNet@2 + displayName: Install .NET 9.0 SDK for ESRP CLI Workaround + inputs: + packageType: sdk + version: 9.0.x + installationPath: ${{ parameters.microBuildOutputFolder }}/.dotnet + workingDirectory: ${{ parameters.microBuildOutputFolder }} + condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux')) + + - task: PowerShell@2 + displayName: Workaround for ESRP CLI on Linux + inputs: + targetType: 'inline' + script: | + Write-Host "Copying Linux Path" + $MBSIGN_APPFOLDER = '$(MBSIGN_APPFOLDER)' + $MBSIGN_APPFOLDER = $MBSIGN_APPFOLDER -replace '/build', '' + $MBSIGN_APPFOLDER = $MBSIGN_APPFOLDER + '/1.1.1032' + '/build' + $MBSIGN_APPFOLDER | Write-Host + $SignConfigPath = $MBSIGN_APPFOLDER + '/signconfig.xml' + Copy-Item -Path "$(MBSIGN_APPFOLDER)/signconfig.xml" -Destination $SignConfigPath -Force + condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux')) diff --git a/eng/common/cross/armel/armel.jessie.patch b/eng/common/cross/armel/armel.jessie.patch deleted file mode 100644 index 2d2615619351fc..00000000000000 --- a/eng/common/cross/armel/armel.jessie.patch +++ /dev/null @@ -1,43 +0,0 @@ -diff -u -r a/usr/include/urcu/uatomic/generic.h b/usr/include/urcu/uatomic/generic.h ---- a/usr/include/urcu/uatomic/generic.h 2014-10-22 15:00:58.000000000 -0700 -+++ b/usr/include/urcu/uatomic/generic.h 2020-10-30 21:38:28.550000000 -0700 -@@ -69,10 +69,10 @@ - #endif - #ifdef UATOMIC_HAS_ATOMIC_SHORT - case 2: -- return __sync_val_compare_and_swap_2(addr, old, _new); -+ return __sync_val_compare_and_swap_2((uint16_t*) addr, old, _new); - #endif - case 4: -- return __sync_val_compare_and_swap_4(addr, old, _new); -+ return __sync_val_compare_and_swap_4((uint32_t*) addr, old, _new); - #if (CAA_BITS_PER_LONG == 64) - case 8: - return __sync_val_compare_and_swap_8(addr, old, _new); -@@ -109,7 +109,7 @@ - return; - #endif - case 4: -- __sync_and_and_fetch_4(addr, val); -+ __sync_and_and_fetch_4((uint32_t*) addr, val); - return; - #if (CAA_BITS_PER_LONG == 64) - case 8: -@@ -148,7 +148,7 @@ - return; - #endif - case 4: -- __sync_or_and_fetch_4(addr, val); -+ __sync_or_and_fetch_4((uint32_t*) addr, val); - return; - #if (CAA_BITS_PER_LONG == 64) - case 8: -@@ -187,7 +187,7 @@ - return __sync_add_and_fetch_2(addr, val); - #endif - case 4: -- return __sync_add_and_fetch_4(addr, val); -+ return __sync_add_and_fetch_4((uint32_t*) addr, val); - #if (CAA_BITS_PER_LONG == 64) - case 8: - return __sync_add_and_fetch_8(addr, val); diff --git a/eng/common/cross/build-rootfs.sh b/eng/common/cross/build-rootfs.sh index 74f399716ba861..d6f005b5dabea6 100755 --- a/eng/common/cross/build-rootfs.sh +++ b/eng/common/cross/build-rootfs.sh @@ -164,9 +164,13 @@ while :; do armel) __BuildArch=armel __UbuntuArch=armel - __UbuntuRepo="http://ftp.debian.org/debian/" - __CodeName=jessie + __UbuntuRepo="http://archive.debian.org/debian/" + __CodeName=buster __KeyringFile="/usr/share/keyrings/debian-archive-keyring.gpg" + __LLDB_Package="liblldb-6.0-dev" + __UbuntuPackages="${__UbuntuPackages// libomp-dev/}" + __UbuntuPackages="${__UbuntuPackages// libomp5/}" + __UbuntuSuites= ;; armv6) __BuildArch=armv6 @@ -278,46 +282,23 @@ while :; do ;; xenial) # Ubuntu 16.04 - if [[ "$__CodeName" != "jessie" ]]; then - __CodeName=xenial - fi - ;; - zesty) # Ubuntu 17.04 - if [[ "$__CodeName" != "jessie" ]]; then - __CodeName=zesty - fi + __CodeName=xenial ;; bionic) # Ubuntu 18.04 - if [[ "$__CodeName" != "jessie" ]]; then - __CodeName=bionic - fi + __CodeName=bionic ;; focal) # Ubuntu 20.04 - if [[ "$__CodeName" != "jessie" ]]; then - __CodeName=focal - fi + __CodeName=focal ;; jammy) # Ubuntu 22.04 - if [[ "$__CodeName" != "jessie" ]]; then - __CodeName=jammy - fi + __CodeName=jammy ;; noble) # Ubuntu 24.04 - if [[ "$__CodeName" != "jessie" ]]; then - __CodeName=noble - fi + __CodeName=noble if [[ -n "$__LLDB_Package" ]]; then __LLDB_Package="liblldb-18-dev" fi ;; - jessie) # Debian 8 - __CodeName=jessie - __KeyringFile="/usr/share/keyrings/debian-archive-keyring.gpg" - - if [[ -z "$__UbuntuRepo" ]]; then - __UbuntuRepo="http://ftp.debian.org/debian/" - fi - ;; stretch) # Debian 9 __CodeName=stretch __LLDB_Package="liblldb-6.0-dev" @@ -333,7 +314,7 @@ while :; do __KeyringFile="/usr/share/keyrings/debian-archive-keyring.gpg" if [[ -z "$__UbuntuRepo" ]]; then - __UbuntuRepo="http://ftp.debian.org/debian/" + __UbuntuRepo="http://archive.debian.org/debian/" fi ;; bullseye) # Debian 11 @@ -473,10 +454,6 @@ if [[ "$__AlpineVersion" =~ 3\.1[345] ]]; then __AlpinePackages="${__AlpinePackages/compiler-rt/compiler-rt-static}" fi -if [[ "$__BuildArch" == "armel" ]]; then - __LLDB_Package="lldb-3.5-dev" -fi - __UbuntuPackages+=" ${__LLDB_Package:-}" if [[ -z "$__UbuntuRepo" ]]; then @@ -850,12 +827,6 @@ EOF if [[ "$__SkipUnmount" == "0" ]]; then umount "$__RootfsDir"/* || true fi - - if [[ "$__BuildArch" == "armel" && "$__CodeName" == "jessie" ]]; then - pushd "$__RootfsDir" - patch -p1 < "$__CrossDir/$__BuildArch/armel.jessie.patch" - popd - fi elif [[ "$__Tizen" == "tizen" ]]; then ROOTFS_DIR="$__RootfsDir" "$__CrossDir/tizen-build-rootfs.sh" "$__BuildArch" else diff --git a/eng/common/generate-sbom-prep.ps1 b/eng/common/generate-sbom-prep.ps1 index 3e5c1c74a1c50d..a0c7d792a76fbe 100644 --- a/eng/common/generate-sbom-prep.ps1 +++ b/eng/common/generate-sbom-prep.ps1 @@ -4,18 +4,26 @@ Param( . $PSScriptRoot\pipeline-logging-functions.ps1 +# Normally - we'd listen to the manifest path given, but 1ES templates will overwrite if this level gets uploaded directly +# with their own overwriting ours. So we create it as a sub directory of the requested manifest path. +$ArtifactName = "${env:SYSTEM_STAGENAME}_${env:AGENT_JOBNAME}_SBOM" +$SafeArtifactName = $ArtifactName -replace '["/:<>\\|?@*"() ]', '_' +$SbomGenerationDir = Join-Path $ManifestDirPath $SafeArtifactName + +Write-Host "Artifact name before : $ArtifactName" +Write-Host "Artifact name after : $SafeArtifactName" + Write-Host "Creating dir $ManifestDirPath" + # create directory for sbom manifest to be placed -if (!(Test-Path -path $ManifestDirPath)) +if (!(Test-Path -path $SbomGenerationDir)) { - New-Item -ItemType Directory -path $ManifestDirPath - Write-Host "Successfully created directory $ManifestDirPath" + New-Item -ItemType Directory -path $SbomGenerationDir + Write-Host "Successfully created directory $SbomGenerationDir" } else{ Write-PipelineTelemetryError -category 'Build' "Unable to create sbom folder." } Write-Host "Updating artifact name" -$artifact_name = "${env:SYSTEM_STAGENAME}_${env:AGENT_JOBNAME}_SBOM" -replace '["/:<>\\|?@*"() ]', '_' -Write-Host "Artifact name $artifact_name" -Write-Host "##vso[task.setvariable variable=ARTIFACT_NAME]$artifact_name" +Write-Host "##vso[task.setvariable variable=ARTIFACT_NAME]$SafeArtifactName" diff --git a/eng/common/generate-sbom-prep.sh b/eng/common/generate-sbom-prep.sh index d5c76dc827b496..b8ecca72bbf506 100644 --- a/eng/common/generate-sbom-prep.sh +++ b/eng/common/generate-sbom-prep.sh @@ -14,19 +14,24 @@ done scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" . $scriptroot/pipeline-logging-functions.sh + +# replace all special characters with _, some builds use special characters like : in Agent.Jobname, that is not a permissible name while uploading artifacts. +artifact_name=$SYSTEM_STAGENAME"_"$AGENT_JOBNAME"_SBOM" +safe_artifact_name="${artifact_name//["/:<>\\|?@*$" ]/_}" manifest_dir=$1 -if [ ! -d "$manifest_dir" ] ; then - mkdir -p "$manifest_dir" - echo "Sbom directory created." $manifest_dir +# Normally - we'd listen to the manifest path given, but 1ES templates will overwrite if this level gets uploaded directly +# with their own overwriting ours. So we create it as a sub directory of the requested manifest path. +sbom_generation_dir="$manifest_dir/$safe_artifact_name" + +if [ ! -d "$sbom_generation_dir" ] ; then + mkdir -p "$sbom_generation_dir" + echo "Sbom directory created." $sbom_generation_dir else Write-PipelineTelemetryError -category 'Build' "Unable to create sbom folder." fi -artifact_name=$SYSTEM_STAGENAME"_"$AGENT_JOBNAME"_SBOM" echo "Artifact name before : "$artifact_name -# replace all special characters with _, some builds use special characters like : in Agent.Jobname, that is not a permissible name while uploading artifacts. -safe_artifact_name="${artifact_name//["/:<>\\|?@*$" ]/_}" echo "Artifact name after : "$safe_artifact_name export ARTIFACT_NAME=$safe_artifact_name echo "##vso[task.setvariable variable=ARTIFACT_NAME]$safe_artifact_name" diff --git a/eng/common/native/install-dependencies.sh b/eng/common/native/install-dependencies.sh index ce661e9e5cd8de..477a44f335be35 100755 --- a/eng/common/native/install-dependencies.sh +++ b/eng/common/native/install-dependencies.sh @@ -27,8 +27,9 @@ case "$os" in libssl-dev libkrb5-dev pigz cpio localedef -i en_US -c -f UTF-8 -A /usr/share/locale/locale.alias en_US.UTF-8 - elif [ "$ID" = "fedora" ] || [ "$ID" = "rhel" ]; then - dnf install -y cmake llvm lld lldb clang python curl libicu-devel openssl-devel krb5-devel lttng-ust-devel pigz cpio + elif [ "$ID" = "fedora" ] || [ "$ID" = "rhel" ] || [ "$ID" = "azurelinux" ]; then + pkg_mgr="$(command -v tdnf 2>/dev/null || command -v dnf)" + $pkg_mgr install -y cmake llvm lld lldb clang python curl libicu-devel openssl-devel krb5-devel lttng-ust-devel pigz cpio elif [ "$ID" = "alpine" ]; then apk add build-base cmake bash curl clang llvm-dev lld lldb krb5-dev lttng-ust-dev icu-dev openssl-dev pigz cpio else @@ -44,7 +45,7 @@ case "$os" in export HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1 # Skip brew update for now, see https://github.com/actions/setup-python/issues/577 # brew update --preinstall - brew bundle --no-upgrade --no-lock --file=- <,$>>:/guard:ehcont>) add_link_options($<$>:/guard:ehcont>) - #set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /CETCOMPAT") + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /CETCOMPAT") endif (CLR_CMAKE_HOST_ARCH_AMD64 AND NOT CLR_CMAKE_RUNTIME_MONO) # Statically linked CRT (libcmt[d].lib, libvcruntime[d].lib and libucrt[d].lib) by default. This is done to avoid diff --git a/eng/native/configureplatform.cmake b/eng/native/configureplatform.cmake index 274ab363bf7905..b04bfc9ca1397c 100644 --- a/eng/native/configureplatform.cmake +++ b/eng/native/configureplatform.cmake @@ -522,7 +522,7 @@ if (CLR_CMAKE_TARGET_ANDROID OR CLR_CMAKE_TARGET_MACCATALYST OR CLR_CMAKE_TARGET set(CLR_CMAKE_USE_SYSTEM_ZLIB 1) endif() -if (NOT CLR_CMAKE_TARGET_ANDROID) +if (NOT CLR_CMAKE_TARGET_ANDROID AND NOT CLR_CMAKE_TARGET_BROWSER) # opt into building tools like ildasm/ilasm set(CLR_CMAKE_BUILD_TOOLS 1) endif() diff --git a/eng/native/functions.cmake b/eng/native/functions.cmake index ba5689e01727e4..581dbd28fb2bad 100644 --- a/eng/native/functions.cmake +++ b/eng/native/functions.cmake @@ -554,11 +554,11 @@ function(install_static_library targetName destination component) endif() endfunction() -# install_clr(TARGETS targetName [targetName2 ...] [DESTINATIONS destination [destination2 ...]] [COMPONENT componentName] [INSTALL_ALL_ARTIFACTS]) +# install_clr(TARGETS targetName [targetName2 ...] [DESTINATIONS destination [destination2 ...]] [COMPONENT componentName]) function(install_clr) set(multiValueArgs TARGETS DESTINATIONS) set(singleValueArgs COMPONENT) - set(options INSTALL_ALL_ARTIFACTS) + set(options "") cmake_parse_arguments(INSTALL_CLR "${options}" "${singleValueArgs}" "${multiValueArgs}" ${ARGV}) if ("${INSTALL_CLR_TARGETS}" STREQUAL "") @@ -594,14 +594,9 @@ function(install_clr) endif() foreach(destination ${destinations}) - # Install the export libraries for static libraries. - if (${INSTALL_CLR_INSTALL_ALL_ARTIFACTS}) - install(TARGETS ${targetName} DESTINATION ${destination} COMPONENT ${INSTALL_CLR_COMPONENT}) - else() - # We don't need to install the export libraries for our DLLs - # since they won't be directly linked against. - install(PROGRAMS $ DESTINATION ${destination} COMPONENT ${INSTALL_CLR_COMPONENT}) - endif() + # We don't need to install the export libraries for our DLLs + # since they won't be directly linked against. + install(PROGRAMS $ DESTINATION ${destination} COMPONENT ${INSTALL_CLR_COMPONENT}) if (NOT "${symbolFile}" STREQUAL "") install_symbol_file(${symbolFile} ${destination} COMPONENT ${INSTALL_CLR_COMPONENT}) endif() diff --git a/eng/pipelines/common/global-build-job.yml b/eng/pipelines/common/global-build-job.yml index 6bdd96038acf75..6e3c5c0cf3208d 100644 --- a/eng/pipelines/common/global-build-job.yml +++ b/eng/pipelines/common/global-build-job.yml @@ -52,9 +52,26 @@ jobs: timeoutInMinutes: ${{ parameters.timeoutInMinutes }} enablePublishTestResults: ${{ parameters.enablePublishTestResults }} testResultsFormat: ${{ parameters.testResultsFormat }} + enableMicrobuild: ${{ parameters.isOfficialBuild }} + enableMicrobuildForMacAndLinux: ${{ parameters.isOfficialBuild }} - ${{ if ne(parameters.templateContext, '') }}: - templateContext: ${{ parameters.templateContext }} + templateContext: + ${{ if ne(parameters.templateContext, '') }}: + ${{ each pair in parameters.templateContext }}: + ${{ if notIn(pair.key, 'outputs') }}: + ${{ pair.key }}: ${{ pair.value }} + outputs: + - ${{ if ne(parameters.templateContext.outputs, '') }}: + - ${{ each output in parameters.templateContext.outputs }}: + ${{ output.key }}: ${{ output.value }} + - ${{ if eq(parameters.isOfficialBuild, true) }}: + - output: pipelineArtifact + displayName: 'Publish Pipeline Artifacts' + ${{ if eq(parameters.hostedOs, '') }}: + artifactName: ${{ format('build_{0}{1}_{2}_{3}_{4}_Artifacts', parameters.osGroup, parameters.osSubgroup, parameters.archType, parameters.buildConfig, parameters.nameSuffix) }} + ${{ if ne(parameters.hostedOs, '') }}: + artifactName: ${{ format('build_{0}{1}_{2}_{3}_{4}_{5}_Artifacts', parameters.osGroup, parameters.osSubgroup, parameters.archType, parameters.hostedOs, parameters.buildConfig, parameters.nameSuffix) }} + targetPath: $(Build.SourcesDirectory)/artifacts/staging artifacts: publish: @@ -90,6 +107,12 @@ jobs: - name: _archParameter value: -arch ${{ parameters.archType }} + - name: _AssetManifestName + value: ${{ parameters.osGroup }}${{ parameters.osSubgroup }}_${{ parameters.archType }}_${{ parameters.nameSuffix }} + + - name: _SignType + value: $[ coalesce(variables.OfficialSignType, 'real') ] + - ${{ if and(eq(parameters.osGroup, 'linux'), eq(parameters.osSubGroup, '_bionic')) }}: - name: _osParameter value: -os linux-bionic @@ -117,22 +140,16 @@ jobs: - ${{ if ne(parameters.cxxAbiLibrary, '') }}: - name: CxxAbiLibraryArg value: /p:TargetCxxAbiLibrary=${{ parameters.cxxAbiLibrary }} - + - name: TargetCxxLibraryConfigurationArgs value: $(CxxStandardLibraryArg) $(CxxStandardLibraryStaticArg) $(CxxAbiLibraryArg) - name: _officialBuildParameter ${{ if eq(parameters.isOfficialBuild, true) }}: - value: /p:OfficialBuildId=$(Build.BuildNumber) + value: /p:OfficialBuildId=$(Build.BuildNumber) /p:DotNetPublishUsingPipelines=true /p:SignType=$(_SignType) /p:DotNetSignType=$(_SignType) ${{ if ne(parameters.isOfficialBuild, true) }}: value: '' - - name: _buildDarwinFrameworksParameter - ${{ if in(parameters.osGroup, 'ios', 'tvos', 'maccatalyst')}}: - value: /p:BuildDarwinFrameworks=true - ${{ if notin(parameters.osGroup, 'ios', 'tvos', 'maccatalyst')}}: - value: '' - # Set no native sanitizers by default - name: _nativeSanitizersArg value: '' diff --git a/eng/pipelines/common/templates/global-build-step.yml b/eng/pipelines/common/templates/global-build-step.yml index 2a2262d9a4ead4..983e27c7422c8e 100644 --- a/eng/pipelines/common/templates/global-build-step.yml +++ b/eng/pipelines/common/templates/global-build-step.yml @@ -10,7 +10,7 @@ parameters: condition: succeeded() steps: - - script: $(Build.SourcesDirectory)$(dir)build$(scriptExt) -ci ${{ parameters.archParameter }} $(_osParameter) ${{ parameters.crossArg }} ${{ parameters.buildArgs }} ${{ parameters.targetCxxLibraryConfigurationArgs }} $(_officialBuildParameter) $(_buildDarwinFrameworksParameter) $(_overrideTestScriptWindowsCmdParameter) + - script: $(Build.SourcesDirectory)$(dir)build$(scriptExt) -ci ${{ parameters.archParameter }} $(_osParameter) ${{ parameters.crossArg }} ${{ parameters.buildArgs }} ${{ parameters.targetCxxLibraryConfigurationArgs }} $(_officialBuildParameter) $(_overrideTestScriptWindowsCmdParameter) displayName: ${{ parameters.displayName }} ${{ if eq(parameters.useContinueOnErrorDuringBuild, true) }}: continueOnError: ${{ parameters.shouldContinueOnError }} diff --git a/eng/pipelines/common/templates/runtimes/build-runtime-tests-and-send-to-helix.yml b/eng/pipelines/common/templates/runtimes/build-runtime-tests-and-send-to-helix.yml index 26b86483cd8e28..f88692190cc221 100644 --- a/eng/pipelines/common/templates/runtimes/build-runtime-tests-and-send-to-helix.yml +++ b/eng/pipelines/common/templates/runtimes/build-runtime-tests-and-send-to-helix.yml @@ -27,7 +27,6 @@ parameters: displayName: '' timeoutInMinutes: '' enableMicrobuild: '' - gatherAssetManifests: false shouldContinueOnError: false steps: diff --git a/eng/pipelines/common/templates/runtimes/build-runtime-tests.yml b/eng/pipelines/common/templates/runtimes/build-runtime-tests.yml index a822ccf28fd288..1b1660693d6e03 100644 --- a/eng/pipelines/common/templates/runtimes/build-runtime-tests.yml +++ b/eng/pipelines/common/templates/runtimes/build-runtime-tests.yml @@ -11,7 +11,6 @@ parameters: displayName: '' timeoutInMinutes: '' enableMicrobuild: '' - gatherAssetManifests: false shouldContinueOnError: false diff --git a/eng/pipelines/common/templates/runtimes/run-test-job.yml b/eng/pipelines/common/templates/runtimes/run-test-job.yml index 551a502ae9ed60..4171b5bcc33e57 100644 --- a/eng/pipelines/common/templates/runtimes/run-test-job.yml +++ b/eng/pipelines/common/templates/runtimes/run-test-job.yml @@ -386,6 +386,9 @@ jobs: - jitstress_isas_x86_nosse41 - jitstress_isas_x86_nosse42 - jitstress_isas_x86_nossse3 + - jitstress_isas_x86_vectort128 + - jitstress_isas_x86_vectort512 + - jitstress_isas_x86_noavx512_vectort128 - jitstress_isas_1_x86_noaes - jitstress_isas_1_x86_noavx - jitstress_isas_1_x86_noavx2 @@ -540,7 +543,6 @@ jobs: - jitpartialcompilation_pgo - jitpartialcompilation_pgo_stress_random - jitoptrepeat - - jitoldlayout ${{ else }}: scenarios: - jitosr_stress @@ -554,7 +556,6 @@ jobs: - jitphysicalpromotion_full - jitrlcse - jitoptrepeat - - jitoldlayout ${{ if in(parameters.testGroup, 'jit-cfg') }}: scenarios: - jitcfg diff --git a/eng/pipelines/common/templates/runtimes/send-to-helix-step.yml b/eng/pipelines/common/templates/runtimes/send-to-helix-step.yml index 989ccdc9319a92..918ba5b814aaa3 100644 --- a/eng/pipelines/common/templates/runtimes/send-to-helix-step.yml +++ b/eng/pipelines/common/templates/runtimes/send-to-helix-step.yml @@ -30,6 +30,7 @@ parameters: runtimeVariant: '' shouldContinueOnError: false SuperPmiCollect: '' + SuperPmiReplayType: '' SuperPmiDiffType: '' SuperPmiBaseJitOptions: '' SuperPmiDiffJitOptions: '' @@ -65,6 +66,7 @@ steps: RuntimeFlavor: ${{ parameters.runtimeFlavor }} _RuntimeVariant: ${{ parameters.runtimeVariant }} _SuperPmiCollect: ${{ parameters.SuperPmiCollect }} + _SuperPmiReplayType: ${{ parameters.SuperPmiReplayType }} _SuperPmiDiffType: ${{ parameters.SuperPmiDiffType }} _SuperPmiBaseJitOptions: ${{ parameters.SuperPmiBaseJitOptions }} _SuperPmiDiffJitOptions: ${{ parameters.SuperPmiDiffJitOptions }} diff --git a/eng/pipelines/common/templates/runtimes/xplat-job.yml b/eng/pipelines/common/templates/runtimes/xplat-job.yml index e22f8f968c4790..3b7dfa334ed259 100644 --- a/eng/pipelines/common/templates/runtimes/xplat-job.yml +++ b/eng/pipelines/common/templates/runtimes/xplat-job.yml @@ -18,7 +18,6 @@ parameters: displayName: '' timeoutInMinutes: '' enableMicrobuild: '' - gatherAssetManifests: false disableComponentGovernance: '' templatePath: 'templates' @@ -69,11 +68,6 @@ jobs: ${{ else }}: disableComponentGovernance: ${{ parameters.disableComponentGovernance }} - # Setting this results in the arcade job template including a step - # that gathers asset manifests and publishes them to pipeline - # storage. Only relevant for build jobs. - enablePublishBuildAssets: ${{ parameters.gatherAssetManifests }} - artifacts: publish: ${{ if ne(parameters.logsName, '') }}: diff --git a/eng/pipelines/common/templates/wasm-library-tests.yml b/eng/pipelines/common/templates/wasm-library-tests.yml index 51cf40074ff5bd..149215ea501937 100644 --- a/eng/pipelines/common/templates/wasm-library-tests.yml +++ b/eng/pipelines/common/templates/wasm-library-tests.yml @@ -80,7 +80,7 @@ jobs: isExtraPlatforms: ${{ parameters.isExtraPlatformsBuild }} testGroup: innerloop nameSuffix: LibraryTests${{ parameters.nameSuffix }} - buildArgs: -s mono+libs+host+packs+libs.tests -c $(_BuildConfig) /p:ArchiveTests=true /p:MonoEnableAssertMessages=true /p:BrowserHost=$(_hostedOs) $(_wasmRunSmokeTestsOnlyArg) $(chromeInstallArg) $(firefoxInstallArg) $(v8InstallArg) ${{ parameters.extraBuildArgs }} + buildArgs: -s mono+libs+host+packs+libs.tests -c $(_BuildConfig) /p:ArchiveTests=true /p:MonoEnableAssertMessages=true /p:BrowserHost=$(_hostedOs) $(_wasmRunSmokeTestsOnlyArg) $(chromeInstallArg) $(firefoxInstallArg) $(v8InstallArg) /maxcpucount:1 ${{ parameters.extraBuildArgs }} timeoutInMinutes: 240 # if !alwaysRun, then: # if this is runtime-wasm (isWasmOnlyBuild): diff --git a/eng/pipelines/coreclr/libraries-pgo.yml b/eng/pipelines/coreclr/libraries-pgo.yml index 00a050da0e6210..a8f0e16b01f35c 100644 --- a/eng/pipelines/coreclr/libraries-pgo.yml +++ b/eng/pipelines/coreclr/libraries-pgo.yml @@ -71,4 +71,3 @@ extends: - syntheticpgo - syntheticpgo_blend - jitrlcse - - jitoldlayout diff --git a/eng/pipelines/coreclr/superpmi-collect-test.yml b/eng/pipelines/coreclr/superpmi-collect-test.yml index 50728c776475e4..abc3fa9cdf089a 100644 --- a/eng/pipelines/coreclr/superpmi-collect-test.yml +++ b/eng/pipelines/coreclr/superpmi-collect-test.yml @@ -1,4 +1,4 @@ -# This job definition automates the SuperPMI collection process. +# This job definition automates the SuperPMI collection process for the "test" pipeline. trigger: none @@ -6,368 +6,4 @@ variables: - template: /eng/pipelines/common/variables.yml extends: - template: /eng/pipelines/common/templates/pipeline-with-resources.yml - parameters: - stages: - - stage: Build - jobs: - - - template: /eng/pipelines/common/platform-matrix.yml - parameters: - jobTemplate: /eng/pipelines/common/global-build-job.yml - buildConfig: checked - platforms: - - windows_x64 - - linux_x64 - jobParameters: - testGroup: outerloop - buildArgs: -s clr+libs+libs.tests -rc $(_BuildConfig) -c Release /p:ArchiveTests=true - timeoutInMinutes: 120 - postBuildSteps: - - template: /eng/pipelines/coreclr/templates/build-native-test-assets-step.yml - - template: /eng/pipelines/common/upload-artifact-step.yml - parameters: - rootFolder: $(Build.SourcesDirectory)/artifacts/bin - includeRootFolder: false - archiveType: $(archiveType) - archiveExtension: $(archiveExtension) - tarCompression: $(tarCompression) - artifactName: BuildArtifacts_$(osGroup)$(osSubgroup)_$(archType)_$(_BuildConfig) - - template: /eng/pipelines/common/upload-artifact-step.yml - parameters: - rootFolder: $(Build.SourcesDirectory)/artifacts/helix - includeRootFolder: false - archiveType: $(archiveType) - archiveExtension: $(archiveExtension) - tarCompression: $(tarCompression) - artifactName: LibrariesTestArtifacts_$(osGroup)$(osSubgroup)_$(archType)_$(_BuildConfig) - extraVariablesTemplates: - - template: /eng/pipelines/common/templates/runtimes/native-test-assets-variables.yml - parameters: - testGroup: outerloop - disableComponentGovernance: true # No shipping artifacts produced by this pipeline - - - template: /eng/pipelines/common/platform-matrix.yml - parameters: - jobTemplate: /eng/pipelines/common/global-build-job.yml - buildConfig: checked - platforms: - - windows_x86 - - windows_arm64 - - osx_arm64 - jobParameters: - testGroup: outerloop - buildArgs: -s clr+libs+libs.tests -rc $(_BuildConfig) -c Release /p:ArchiveTests=true - timeoutInMinutes: 120 - postBuildSteps: - # Build CLR assets for x64 as well as the target as we need an x64 mcs - - template: /eng/pipelines/common/templates/global-build-step.yml - parameters: - buildArgs: -s clr.spmi -c $(_BuildConfig) - archParameter: -arch x64 - displayName: Build SuperPMI - - template: /eng/pipelines/coreclr/templates/build-native-test-assets-step.yml - - template: /eng/pipelines/common/upload-artifact-step.yml - parameters: - rootFolder: $(Build.SourcesDirectory)/artifacts/bin - includeRootFolder: false - archiveType: $(archiveType) - archiveExtension: $(archiveExtension) - tarCompression: $(tarCompression) - artifactName: BuildArtifacts_$(osGroup)$(osSubgroup)_$(archType)_$(_BuildConfig) - - template: /eng/pipelines/common/upload-artifact-step.yml - parameters: - rootFolder: $(Build.SourcesDirectory)/artifacts/helix - includeRootFolder: false - archiveType: $(archiveType) - archiveExtension: $(archiveExtension) - tarCompression: $(tarCompression) - artifactName: LibrariesTestArtifacts_$(osGroup)$(osSubgroup)_$(archType)_$(_BuildConfig) - extraVariablesTemplates: - - template: /eng/pipelines/common/templates/runtimes/native-test-assets-variables.yml - parameters: - testGroup: outerloop - disableComponentGovernance: true # No shipping artifacts produced by this pipeline - - - template: /eng/pipelines/common/platform-matrix.yml - parameters: - jobTemplate: /eng/pipelines/common/global-build-job.yml - buildConfig: checked - platforms: - - linux_arm - - linux_arm64 - jobParameters: - testGroup: outerloop - buildArgs: -s clr+libs+libs.tests -rc $(_BuildConfig) -c Release /p:ArchiveTests=true - timeoutInMinutes: 120 - postBuildSteps: - # Build CLR assets for x64 as well as the target as we need an x64 mcs - - template: /eng/pipelines/common/templates/global-build-step.yml - parameters: - buildArgs: -s clr.spmi -c $(_BuildConfig) - archParameter: -arch x64 - container: linux_x64 - displayName: Build SuperPMI - - template: /eng/pipelines/coreclr/templates/build-native-test-assets-step.yml - - template: /eng/pipelines/common/upload-artifact-step.yml - parameters: - rootFolder: $(Build.SourcesDirectory)/artifacts/bin - includeRootFolder: false - archiveType: $(archiveType) - archiveExtension: $(archiveExtension) - tarCompression: $(tarCompression) - artifactName: BuildArtifacts_$(osGroup)$(osSubgroup)_$(archType)_$(_BuildConfig) - - template: /eng/pipelines/common/upload-artifact-step.yml - parameters: - rootFolder: $(Build.SourcesDirectory)/artifacts/helix - includeRootFolder: false - archiveType: $(archiveType) - archiveExtension: $(archiveExtension) - tarCompression: $(tarCompression) - artifactName: LibrariesTestArtifacts_$(osGroup)$(osSubgroup)_$(archType)_$(_BuildConfig) - extraVariablesTemplates: - - template: /eng/pipelines/common/templates/runtimes/native-test-assets-variables.yml - parameters: - testGroup: outerloop - disableComponentGovernance: true # No shipping artifacts produced by this pipeline - - - template: /eng/pipelines/common/platform-matrix.yml - parameters: - jobTemplate: /eng/pipelines/common/templates/runtimes/build-test-job.yml - buildConfig: checked - platforms: - - CoreClrTestBuildHost # Either osx_x64 or linux_x64 - jobParameters: - testGroup: outerloop - - - template: /eng/pipelines/common/platform-matrix.yml - parameters: - jobTemplate: /eng/pipelines/coreclr/templates/superpmi-collect-job.yml - buildConfig: checked - platforms: - - osx_arm64 - - linux_arm - - linux_arm64 - - linux_x64 - - windows_x64 - - windows_x86 - - windows_arm64 - helixQueueGroup: ci - helixQueuesTemplate: /eng/pipelines/coreclr/templates/helix-queues-setup.yml - jobParameters: - testGroup: outerloop - liveLibrariesBuildConfig: Release - collectionType: pmi - collectionName: libraries - collectionUpload: false - - - template: /eng/pipelines/common/platform-matrix.yml - parameters: - jobTemplate: /eng/pipelines/coreclr/templates/superpmi-collect-job.yml - buildConfig: checked - platforms: - - osx_arm64 - - linux_arm - - linux_arm64 - - linux_x64 - - windows_x64 - - windows_x86 - - windows_arm64 - helixQueueGroup: ci - helixQueuesTemplate: /eng/pipelines/coreclr/templates/helix-queues-setup.yml - jobParameters: - testGroup: outerloop - liveLibrariesBuildConfig: Release - collectionType: crossgen2 - collectionName: libraries - collectionUpload: false - - - template: /eng/pipelines/common/platform-matrix.yml - parameters: - jobTemplate: /eng/pipelines/coreclr/templates/superpmi-collect-job.yml - buildConfig: checked - platforms: - - osx_arm64 - - linux_arm - - linux_arm64 - - linux_x64 - - windows_x64 - - windows_x86 - - windows_arm64 - helixQueueGroup: ci - helixQueuesTemplate: /eng/pipelines/coreclr/templates/helix-queues-setup.yml - jobParameters: - testGroup: outerloop - liveLibrariesBuildConfig: Release - collectionType: run - collectionName: realworld - collectionUpload: false - - - template: /eng/pipelines/common/platform-matrix.yml - parameters: - jobTemplate: /eng/pipelines/coreclr/templates/superpmi-collect-job.yml - buildConfig: checked - platforms: - - osx_arm64 - - linux_arm - - linux_arm64 - - linux_x64 - - windows_x64 - - windows_x86 - - windows_arm64 - helixQueueGroup: ci - helixQueuesTemplate: /eng/pipelines/coreclr/templates/helix-queues-setup.yml - jobParameters: - testGroup: outerloop - liveLibrariesBuildConfig: Release - collectionType: run - collectionName: benchmarks - collectionUpload: false - - - template: /eng/pipelines/common/platform-matrix.yml - parameters: - jobTemplate: /eng/pipelines/coreclr/templates/superpmi-collect-job.yml - buildConfig: checked - platforms: - - osx_arm64 - - linux_arm - - linux_arm64 - - linux_x64 - - windows_x64 - - windows_x86 - - windows_arm64 - helixQueueGroup: ci - helixQueuesTemplate: /eng/pipelines/coreclr/templates/helix-queues-setup.yml - jobParameters: - testGroup: outerloop - liveLibrariesBuildConfig: Release - collectionType: run_pgo - collectionName: benchmarks - collectionUpload: false - - - template: /eng/pipelines/common/platform-matrix.yml - parameters: - jobTemplate: /eng/pipelines/coreclr/templates/superpmi-collect-job.yml - buildConfig: checked - platforms: - - osx_arm64 - - linux_arm - - linux_arm64 - - linux_x64 - - windows_x64 - - windows_x86 - - windows_arm64 - helixQueueGroup: ci - helixQueuesTemplate: /eng/pipelines/coreclr/templates/helix-queues-setup.yml - jobParameters: - testGroup: outerloop - liveLibrariesBuildConfig: Release - collectionType: run_pgo_optrepeat - collectionName: benchmarks - collectionUpload: false - - # - # Collection of coreclr test run - # - - template: /eng/pipelines/common/platform-matrix.yml - parameters: - jobTemplate: /eng/pipelines/common/templates/runtimes/run-test-job.yml - buildConfig: checked - platforms: - - osx_arm64 - - linux_arm - - linux_arm64 - - linux_x64 - - windows_x64 - - windows_x86 - - windows_arm64 - helixQueueGroup: superpmi - helixQueuesTemplate: /eng/pipelines/coreclr/templates/helix-queues-setup.yml - jobParameters: - testGroup: outerloop - liveLibrariesBuildConfig: Release - SuperPmiCollect: true - SuperPmiCollectionUpload: false - unifiedArtifactsName: BuildArtifacts_$(osGroup)$(osSubgroup)_$(archType)_$(_BuildConfig) - - - template: /eng/pipelines/common/platform-matrix.yml - parameters: - jobTemplate: /eng/pipelines/coreclr/templates/superpmi-collect-job.yml - buildConfig: checked - platforms: - - linux_arm64 - - linux_x64 - - windows_x64 - - windows_arm64 - helixQueueGroup: ci - helixQueuesTemplate: /eng/pipelines/coreclr/templates/helix-queues-setup.yml - jobParameters: - testGroup: outerloop - liveLibrariesBuildConfig: Release - collectionType: nativeaot - collectionName: smoke_tests - collectionUpload: false - - # - # Collection of libraries test run: normal - # Libraries Test Run using Release libraries, and Checked CoreCLR - # - - template: /eng/pipelines/common/platform-matrix.yml - parameters: - jobTemplate: /eng/pipelines/libraries/run-test-job.yml - buildConfig: Release - platforms: - - osx_arm64 - - linux_arm - - linux_arm64 - - linux_x64 - - windows_x64 - - windows_x86 - - windows_arm64 - helixQueueGroup: superpmi - helixQueuesTemplate: /eng/pipelines/coreclr/templates/helix-queues-setup.yml - jobParameters: - testScope: innerloop - liveRuntimeBuildConfig: Checked - dependsOnTestBuildConfiguration: Release - dependsOnTestArchitecture: x64 - scenarios: - - normal - SuperPmiCollect: true - SuperPmiCollectionName: libraries_tests - SuperPmiCollectionUpload: false - unifiedArtifactsName: BuildArtifacts_$(osGroup)$(osSubgroup)_$(archType)_Checked - helixArtifactsName: LibrariesTestArtifacts_$(osGroup)$(osSubgroup)_$(archType)_Checked - unifiedBuildConfigOverride: checked - - # - # Collection of libraries test run: no_tiered_compilation - # Libraries Test Run using Release libraries, and Checked CoreCLR - # - - template: /eng/pipelines/common/platform-matrix.yml - parameters: - jobTemplate: /eng/pipelines/libraries/run-test-job.yml - buildConfig: Release - platforms: - - osx_arm64 - - linux_arm - - linux_arm64 - - linux_x64 - - windows_x64 - - windows_x86 - - windows_arm64 - helixQueueGroup: superpmi - helixQueuesTemplate: /eng/pipelines/coreclr/templates/helix-queues-setup.yml - jobParameters: - testScope: innerloop - liveRuntimeBuildConfig: Checked - dependsOnTestBuildConfiguration: Release - dependsOnTestArchitecture: x64 - scenarios: - - no_tiered_compilation - SuperPmiCollect: true - SuperPmiCollectionName: libraries_tests_no_tiered_compilation - SuperPmiCollectionUpload: false - unifiedArtifactsName: BuildArtifacts_$(osGroup)$(osSubgroup)_$(archType)_Checked - helixArtifactsName: LibrariesTestArtifacts_$(osGroup)$(osSubgroup)_$(archType)_Checked - unifiedBuildConfigOverride: checked + template: /eng/pipelines/coreclr/templates/superpmi-collect-pipeline.yml \ No newline at end of file diff --git a/eng/pipelines/coreclr/superpmi-collect.yml b/eng/pipelines/coreclr/superpmi-collect.yml index 70910782ae1250..5fa3c7692fac32 100644 --- a/eng/pipelines/coreclr/superpmi-collect.yml +++ b/eng/pipelines/coreclr/superpmi-collect.yml @@ -1,4 +1,4 @@ -# This job definition automates the SuperPMI collection process. +# This job definition automates the SuperPMI collection process for the "production" pipeline. # Trigger this job if the JIT-EE GUID changes, which invalidates previous SuperPMI # collections. @@ -15,9 +15,6 @@ trigger: # and should not be triggerable from a PR. pr: none -variables: - - template: /eng/pipelines/common/variables.yml - schedules: - cron: "0 17 * * 0" displayName: Sun at 9:00 AM (UTC-8:00) @@ -26,359 +23,8 @@ schedules: - main always: true -extends: - template: /eng/pipelines/common/templates/pipeline-with-resources.yml - parameters: - stages: - - stage: Build - jobs: - - - template: /eng/pipelines/common/platform-matrix.yml - parameters: - jobTemplate: /eng/pipelines/common/global-build-job.yml - buildConfig: checked - platforms: - - windows_x64 - - linux_x64 - jobParameters: - testGroup: outerloop - buildArgs: -s clr+libs+libs.tests -rc $(_BuildConfig) -c Release /p:ArchiveTests=true - timeoutInMinutes: 120 - postBuildSteps: - - template: /eng/pipelines/coreclr/templates/build-native-test-assets-step.yml - - template: /eng/pipelines/common/upload-artifact-step.yml - parameters: - rootFolder: $(Build.SourcesDirectory)/artifacts/bin - includeRootFolder: false - archiveType: $(archiveType) - archiveExtension: $(archiveExtension) - tarCompression: $(tarCompression) - artifactName: BuildArtifacts_$(osGroup)$(osSubgroup)_$(archType)_$(_BuildConfig) - - template: /eng/pipelines/common/upload-artifact-step.yml - parameters: - rootFolder: $(Build.SourcesDirectory)/artifacts/helix - includeRootFolder: false - archiveType: $(archiveType) - archiveExtension: $(archiveExtension) - tarCompression: $(tarCompression) - artifactName: LibrariesTestArtifacts_$(osGroup)$(osSubgroup)_$(archType)_$(_BuildConfig) - extraVariablesTemplates: - - template: /eng/pipelines/common/templates/runtimes/native-test-assets-variables.yml - parameters: - testGroup: outerloop - disableComponentGovernance: true # No shipping artifacts produced by this pipeline - - - template: /eng/pipelines/common/platform-matrix.yml - parameters: - jobTemplate: /eng/pipelines/common/global-build-job.yml - buildConfig: checked - platforms: - - windows_x86 - - windows_arm64 - - osx_arm64 - jobParameters: - testGroup: outerloop - buildArgs: -s clr+libs+libs.tests -rc $(_BuildConfig) -c Release /p:ArchiveTests=true - timeoutInMinutes: 120 - postBuildSteps: - # Build CLR assets for x64 as well as the target as we need an x64 mcs - - template: /eng/pipelines/common/templates/global-build-step.yml - parameters: - buildArgs: -s clr.spmi -c $(_BuildConfig) - archParameter: -arch x64 - displayName: Build SuperPMI - - template: /eng/pipelines/coreclr/templates/build-native-test-assets-step.yml - - template: /eng/pipelines/common/upload-artifact-step.yml - parameters: - rootFolder: $(Build.SourcesDirectory)/artifacts/bin - includeRootFolder: false - archiveType: $(archiveType) - archiveExtension: $(archiveExtension) - tarCompression: $(tarCompression) - artifactName: BuildArtifacts_$(osGroup)$(osSubgroup)_$(archType)_$(_BuildConfig) - - template: /eng/pipelines/common/upload-artifact-step.yml - parameters: - rootFolder: $(Build.SourcesDirectory)/artifacts/helix - includeRootFolder: false - archiveType: $(archiveType) - archiveExtension: $(archiveExtension) - tarCompression: $(tarCompression) - artifactName: LibrariesTestArtifacts_$(osGroup)$(osSubgroup)_$(archType)_$(_BuildConfig) - extraVariablesTemplates: - - template: /eng/pipelines/common/templates/runtimes/native-test-assets-variables.yml - parameters: - testGroup: outerloop - disableComponentGovernance: true # No shipping artifacts produced by this pipeline - - - template: /eng/pipelines/common/platform-matrix.yml - parameters: - jobTemplate: /eng/pipelines/common/global-build-job.yml - buildConfig: checked - platforms: - - linux_arm - - linux_arm64 - jobParameters: - testGroup: outerloop - buildArgs: -s clr+libs+libs.tests -rc $(_BuildConfig) -c Release /p:ArchiveTests=true - timeoutInMinutes: 120 - postBuildSteps: - # Build CLR assets for x64 as well as the target as we need an x64 mcs - - template: /eng/pipelines/common/templates/global-build-step.yml - parameters: - buildArgs: -s clr.spmi -c $(_BuildConfig) - archParameter: -arch x64 - container: linux_x64 - displayName: Build SuperPMI - - template: /eng/pipelines/coreclr/templates/build-native-test-assets-step.yml - - template: /eng/pipelines/common/upload-artifact-step.yml - parameters: - rootFolder: $(Build.SourcesDirectory)/artifacts/bin - includeRootFolder: false - archiveType: $(archiveType) - archiveExtension: $(archiveExtension) - tarCompression: $(tarCompression) - artifactName: BuildArtifacts_$(osGroup)$(osSubgroup)_$(archType)_$(_BuildConfig) - - template: /eng/pipelines/common/upload-artifact-step.yml - parameters: - rootFolder: $(Build.SourcesDirectory)/artifacts/helix - includeRootFolder: false - archiveType: $(archiveType) - archiveExtension: $(archiveExtension) - tarCompression: $(tarCompression) - artifactName: LibrariesTestArtifacts_$(osGroup)$(osSubgroup)_$(archType)_$(_BuildConfig) - extraVariablesTemplates: - - template: /eng/pipelines/common/templates/runtimes/native-test-assets-variables.yml - parameters: - testGroup: outerloop - disableComponentGovernance: true # No shipping artifacts produced by this pipeline - - - template: /eng/pipelines/common/platform-matrix.yml - parameters: - jobTemplate: /eng/pipelines/common/templates/runtimes/build-test-job.yml - buildConfig: checked - platforms: - - CoreClrTestBuildHost # Either osx_x64 or linux_x64 - jobParameters: - testGroup: outerloop - - - template: /eng/pipelines/common/platform-matrix.yml - parameters: - jobTemplate: /eng/pipelines/coreclr/templates/superpmi-collect-job.yml - buildConfig: checked - platforms: - - osx_arm64 - - linux_arm - - linux_arm64 - - linux_x64 - - windows_x64 - - windows_x86 - - windows_arm64 - helixQueueGroup: ci - helixQueuesTemplate: /eng/pipelines/coreclr/templates/helix-queues-setup.yml - jobParameters: - testGroup: outerloop - liveLibrariesBuildConfig: Release - collectionType: pmi - collectionName: libraries - - - template: /eng/pipelines/common/platform-matrix.yml - parameters: - jobTemplate: /eng/pipelines/coreclr/templates/superpmi-collect-job.yml - buildConfig: checked - platforms: - - osx_arm64 - - linux_arm - - linux_arm64 - - linux_x64 - - windows_x64 - - windows_x86 - - windows_arm64 - helixQueueGroup: ci - helixQueuesTemplate: /eng/pipelines/coreclr/templates/helix-queues-setup.yml - jobParameters: - testGroup: outerloop - liveLibrariesBuildConfig: Release - collectionType: crossgen2 - collectionName: libraries - - - template: /eng/pipelines/common/platform-matrix.yml - parameters: - jobTemplate: /eng/pipelines/coreclr/templates/superpmi-collect-job.yml - buildConfig: checked - platforms: - - osx_arm64 - - linux_arm - - linux_arm64 - - linux_x64 - - windows_x64 - - windows_x86 - - windows_arm64 - helixQueueGroup: ci - helixQueuesTemplate: /eng/pipelines/coreclr/templates/helix-queues-setup.yml - jobParameters: - testGroup: outerloop - liveLibrariesBuildConfig: Release - collectionType: run - collectionName: realworld - - - template: /eng/pipelines/common/platform-matrix.yml - parameters: - jobTemplate: /eng/pipelines/coreclr/templates/superpmi-collect-job.yml - buildConfig: checked - platforms: - - osx_arm64 - - linux_arm - - linux_arm64 - - linux_x64 - - windows_x64 - - windows_x86 - - windows_arm64 - helixQueueGroup: ci - helixQueuesTemplate: /eng/pipelines/coreclr/templates/helix-queues-setup.yml - jobParameters: - testGroup: outerloop - liveLibrariesBuildConfig: Release - collectionType: run - collectionName: benchmarks - - - template: /eng/pipelines/common/platform-matrix.yml - parameters: - jobTemplate: /eng/pipelines/coreclr/templates/superpmi-collect-job.yml - buildConfig: checked - platforms: - - osx_arm64 - - linux_arm - - linux_arm64 - - linux_x64 - - windows_x64 - - windows_x86 - - windows_arm64 - helixQueueGroup: ci - helixQueuesTemplate: /eng/pipelines/coreclr/templates/helix-queues-setup.yml - jobParameters: - testGroup: outerloop - liveLibrariesBuildConfig: Release - collectionType: run_pgo - collectionName: benchmarks - - - template: /eng/pipelines/common/platform-matrix.yml - parameters: - jobTemplate: /eng/pipelines/coreclr/templates/superpmi-collect-job.yml - buildConfig: checked - platforms: - - osx_arm64 - - linux_arm - - linux_arm64 - - linux_x64 - - windows_x64 - - windows_x86 - - windows_arm64 - helixQueueGroup: ci - helixQueuesTemplate: /eng/pipelines/coreclr/templates/helix-queues-setup.yml - jobParameters: - testGroup: outerloop - liveLibrariesBuildConfig: Release - collectionType: run_pgo_optrepeat - collectionName: benchmarks - - # - # Collection of coreclr test run - # - - template: /eng/pipelines/common/platform-matrix.yml - parameters: - jobTemplate: /eng/pipelines/common/templates/runtimes/run-test-job.yml - buildConfig: checked - platforms: - - osx_arm64 - - linux_arm - - linux_arm64 - - linux_x64 - - windows_x64 - - windows_x86 - - windows_arm64 - helixQueueGroup: superpmi - helixQueuesTemplate: /eng/pipelines/coreclr/templates/helix-queues-setup.yml - jobParameters: - testGroup: outerloop - liveLibrariesBuildConfig: Release - SuperPmiCollect: true - unifiedArtifactsName: BuildArtifacts_$(osGroup)$(osSubgroup)_$(archType)_$(_BuildConfig) - - - template: /eng/pipelines/common/platform-matrix.yml - parameters: - jobTemplate: /eng/pipelines/coreclr/templates/superpmi-collect-job.yml - buildConfig: checked - platforms: - - linux_arm64 - - linux_x64 - - windows_x64 - - windows_arm64 - helixQueueGroup: ci - helixQueuesTemplate: /eng/pipelines/coreclr/templates/helix-queues-setup.yml - jobParameters: - testGroup: outerloop - liveLibrariesBuildConfig: Release - collectionType: nativeaot - collectionName: smoke_tests - - # - # Collection of libraries test run: normal - # Libraries Test Run using Release libraries, and Checked CoreCLR - # - - template: /eng/pipelines/common/platform-matrix.yml - parameters: - jobTemplate: /eng/pipelines/libraries/run-test-job.yml - buildConfig: Release - platforms: - - osx_arm64 - - linux_arm - - linux_arm64 - - linux_x64 - - windows_x64 - - windows_x86 - - windows_arm64 - helixQueueGroup: superpmi - helixQueuesTemplate: /eng/pipelines/coreclr/templates/helix-queues-setup.yml - jobParameters: - testScope: innerloop - liveRuntimeBuildConfig: Checked - dependsOnTestBuildConfiguration: Release - dependsOnTestArchitecture: x64 - scenarios: - - normal - SuperPmiCollect: true - SuperPmiCollectionName: libraries_tests - unifiedArtifactsName: BuildArtifacts_$(osGroup)$(osSubgroup)_$(archType)_Checked - helixArtifactsName: LibrariesTestArtifacts_$(osGroup)$(osSubgroup)_$(archType)_Checked - unifiedBuildConfigOverride: checked +variables: + - template: /eng/pipelines/common/variables.yml - # - # Collection of libraries test run: no_tiered_compilation - # Libraries Test Run using Release libraries, and Checked CoreCLR - # - - template: /eng/pipelines/common/platform-matrix.yml - parameters: - jobTemplate: /eng/pipelines/libraries/run-test-job.yml - buildConfig: Release - platforms: - - osx_arm64 - - linux_arm - - linux_arm64 - - linux_x64 - - windows_x64 - - windows_x86 - - windows_arm64 - helixQueueGroup: superpmi - helixQueuesTemplate: /eng/pipelines/coreclr/templates/helix-queues-setup.yml - jobParameters: - testScope: innerloop - liveRuntimeBuildConfig: Checked - dependsOnTestBuildConfiguration: Release - dependsOnTestArchitecture: x64 - scenarios: - - no_tiered_compilation - SuperPmiCollect: true - SuperPmiCollectionName: libraries_tests_no_tiered_compilation - unifiedArtifactsName: BuildArtifacts_$(osGroup)$(osSubgroup)_$(archType)_Checked - helixArtifactsName: LibrariesTestArtifacts_$(osGroup)$(osSubgroup)_$(archType)_Checked - unifiedBuildConfigOverride: checked +extends: + template: /eng/pipelines/coreclr/templates/superpmi-collect-pipeline.yml diff --git a/eng/pipelines/coreclr/superpmi-replay-apx.yml b/eng/pipelines/coreclr/superpmi-replay-apx.yml new file mode 100644 index 00000000000000..f5e3f0d1adba4e --- /dev/null +++ b/eng/pipelines/coreclr/superpmi-replay-apx.yml @@ -0,0 +1,19 @@ +trigger: none + +schedules: +- cron: "0 7 * * *" + displayName: Daily at 11:00 PM (UTC-8:00) + branches: + include: + - main + always: true + +variables: + - template: /eng/pipelines/common/variables.yml + +extends: + template: /eng/pipelines/coreclr/templates/jit-replay-pipeline.yml + parameters: + platforms: + - windows_x64 + replayType: apx \ No newline at end of file diff --git a/eng/pipelines/coreclr/superpmi-replay.yml b/eng/pipelines/coreclr/superpmi-replay.yml index 7ec31af8732eeb..6c9c15e7f19791 100644 --- a/eng/pipelines/coreclr/superpmi-replay.yml +++ b/eng/pipelines/coreclr/superpmi-replay.yml @@ -1,74 +1,28 @@ -# This pipeline only runs on GitHub PRs, not on merges. trigger: none -# Only run on changes to the JIT directory. Don't run if the JIT-EE GUID has changed, -# since there won't be any SuperPMI collections with the new GUID until the collection -# pipeline completes after this PR is merged. pr: branches: include: - main paths: include: - - src/coreclr/jit/* - - src/coreclr/gcinfo/* - - src/coreclr/tools/superpmi/* - exclude: - - src/coreclr/inc/jiteeversionguid.h + - src/coreclr/jit/lsra*.* + +schedules: +- cron: "0 7 * * *" + displayName: Daily at 11:00 PM (UTC-8:00) + branches: + include: + - main + always: true variables: - template: /eng/pipelines/common/variables.yml extends: - template: /eng/pipelines/common/templates/pipeline-with-resources.yml + template: /eng/pipelines/coreclr/templates/jit-replay-pipeline.yml parameters: - stages: - # Don't run if the JIT-EE GUID has changed, - # since there won't be any SuperPMI collections with the new GUID until the collection - # pipeline completes after this PR is merged. - - ${{ if eq(variables['Build.Reason'], 'PullRequest') }}: - - stage: EvaluatePaths - displayName: Evaluate Paths - jobs: - - template: /eng/pipelines/common/evaluate-paths-job.yml - parameters: - paths: - - subset: jiteeversionguid - include: - - src/coreclr/inc/jiteeversionguid.h - - - stage: Build - jobs: - - - template: /eng/pipelines/common/platform-matrix.yml - parameters: - jobTemplate: /eng/pipelines/common/global-build-job.yml - buildConfig: checked - platforms: - - windows_x64 - - windows_x86 - jobParameters: - buildArgs: -s clr.alljits+clr.spmi -c $(_BuildConfig) - postBuildSteps: - - template: /eng/pipelines/common/upload-artifact-step.yml - parameters: - rootFolder: $(Build.SourcesDirectory)/artifacts/bin/coreclr - includeRootFolder: false - archiveType: $(archiveType) - tarCompression: $(tarCompression) - archiveExtension: $(archiveExtension) - artifactName: CheckedJIT_$(osGroup)$(osSubgroup)_$(archType) - displayName: JIT and SuperPMI Assets - condition: not(eq(stageDependencies.EvaluatePaths.evaluate_paths.outputs['SetPathVars_jiteeversionguid.containsChange'], true)) - - - template: /eng/pipelines/common/platform-matrix.yml - parameters: - jobTemplate: /eng/pipelines/coreclr/templates/superpmi-replay-job.yml - buildConfig: checked - platforms: - - windows_x64 - - windows_x86 - helixQueueGroup: ci - helixQueuesTemplate: /eng/pipelines/coreclr/templates/helix-queues-setup.yml - jobParameters: - condition: not(eq(stageDependencies.EvaluatePaths.evaluate_paths.outputs['SetPathVars_jiteeversionguid.containsChange'], true)) + platforms: + - windows_x64 + - windows_x86 + replayType: standard diff --git a/eng/pipelines/coreclr/templates/helix-queues-setup.yml b/eng/pipelines/coreclr/templates/helix-queues-setup.yml index 27f26132344789..3e27b328b67fd1 100644 --- a/eng/pipelines/coreclr/templates/helix-queues-setup.yml +++ b/eng/pipelines/coreclr/templates/helix-queues-setup.yml @@ -70,16 +70,16 @@ jobs: # Linux arm64 - ${{ if eq(parameters.platform, 'linux_arm64') }}: - ${{ if eq(variables['System.TeamProject'], 'public') }}: - - (Ubuntu.2204.Arm64.Open)Ubuntu.2204.Armarch.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:ubuntu-22.04-helix-arm64v8 + - (Ubuntu.2404.Arm64.Open)Ubuntu.2204.Armarch.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:ubuntu-24.04-helix-arm64v8 - ${{ if eq(variables['System.TeamProject'], 'internal') }}: - - (Ubuntu.2204.Arm64)Ubuntu.2204.ArmArch@mcr.microsoft.com/dotnet-buildtools/prereqs:ubuntu-22.04-helix-arm64v8 + - (Ubuntu.2404.Arm64)Ubuntu.2204.ArmArch@mcr.microsoft.com/dotnet-buildtools/prereqs:ubuntu-24.04-helix-arm64v8 # Linux musl x64 - ${{ if eq(parameters.platform, 'linux_musl_x64') }}: - ${{ if eq(variables['System.TeamProject'], 'public') }}: - - (Alpine.321.Amd64.Open)Ubuntu.2204.Amd64.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:alpine-3.21-helix-amd64 + - (Alpine.321.Amd64.Open)azurelinux.3.amd64.open@mcr.microsoft.com/dotnet-buildtools/prereqs:alpine-3.21-helix-amd64 - ${{ if eq(variables['System.TeamProject'], 'internal') }}: - - (Alpine.321.Amd64)Ubuntu.2204.Amd64@mcr.microsoft.com/dotnet-buildtools/prereqs:alpine-3.21-helix-amd64 + - (Alpine.321.Amd64)azurelinux.3.amd64@mcr.microsoft.com/dotnet-buildtools/prereqs:alpine-3.21-helix-amd64 # Linux musl arm32 - ${{ if eq(parameters.platform, 'linux_musl_arm') }}: @@ -98,9 +98,9 @@ jobs: # Linux x64 - ${{ if eq(parameters.platform, 'linux_x64') }}: - ${{ if eq(variables['System.TeamProject'], 'public') }}: - - Ubuntu.2204.Amd64.Open + - azurelinux.3.amd64.open - ${{ if eq(variables['System.TeamProject'], 'internal') }}: - - Ubuntu.2204.Amd64 + - azurelinux.3.amd64 # OSX arm64 - ${{ if eq(parameters.platform, 'osx_arm64') }}: diff --git a/eng/pipelines/coreclr/templates/jit-replay-pipeline.yml b/eng/pipelines/coreclr/templates/jit-replay-pipeline.yml new file mode 100644 index 00000000000000..74f2caf2ff3852 --- /dev/null +++ b/eng/pipelines/coreclr/templates/jit-replay-pipeline.yml @@ -0,0 +1,58 @@ +parameters: + - name: platforms + type: object + - name: replayType + type: string + default: standard + +extends: + template: /eng/pipelines/common/templates/pipeline-with-resources.yml + parameters: + stages: + # Don't run if the JIT-EE GUID has changed, + # since there won't be any SuperPMI collections with the new GUID until the collection + # pipeline completes after this PR is merged. + - ${{ if eq(variables['Build.Reason'], 'PullRequest') }}: + - stage: EvaluatePaths + displayName: Evaluate Paths + jobs: + - template: /eng/pipelines/common/evaluate-paths-job.yml + parameters: + paths: + - subset: jiteeversionguid + include: + - src/coreclr/inc/jiteeversionguid.h + + - stage: Build + jobs: + + - template: /eng/pipelines/common/platform-matrix.yml + parameters: + jobTemplate: /eng/pipelines/common/global-build-job.yml + buildConfig: checked + platforms: ${{ parameters.platforms }} + jobParameters: + buildArgs: -s clr.alljits+clr.spmi -c $(_BuildConfig) + postBuildSteps: + - template: /eng/pipelines/common/upload-artifact-step.yml + parameters: + rootFolder: $(Build.SourcesDirectory)/artifacts/bin/coreclr + includeRootFolder: false + archiveType: $(archiveType) + tarCompression: $(tarCompression) + archiveExtension: $(archiveExtension) + artifactName: BuildArtifacts_$(osGroup)$(osSubgroup)_$(archType)_$(_BuildConfig) + displayName: Build Assets + condition: not(eq(stageDependencies.EvaluatePaths.evaluate_paths.outputs['SetPathVars_jiteeversionguid.containsChange'], true)) + + - template: /eng/pipelines/common/platform-matrix.yml + parameters: + jobTemplate: /eng/pipelines/coreclr/templates/superpmi-replay-job.yml + buildConfig: checked + platforms: ${{ parameters.platforms }} + helixQueueGroup: ci + helixQueuesTemplate: /eng/pipelines/coreclr/templates/helix-queues-setup.yml + jobParameters: + condition: not(eq(stageDependencies.EvaluatePaths.evaluate_paths.outputs['SetPathVars_jiteeversionguid.containsChange'], true)) + replayType: ${{ parameters.replayType }} + unifiedArtifactsName: BuildArtifacts_$(osGroup)$(osSubgroup)_$(archType)_$(_BuildConfig) diff --git a/eng/pipelines/coreclr/templates/run-superpmi-replay-job.yml b/eng/pipelines/coreclr/templates/run-superpmi-replay-job.yml index 4047a8cb2f7cf6..27cc61c653de47 100644 --- a/eng/pipelines/coreclr/templates/run-superpmi-replay-job.yml +++ b/eng/pipelines/coreclr/templates/run-superpmi-replay-job.yml @@ -16,6 +16,7 @@ parameters: enableTelemetry: false # optional -- enable for telemetry liveLibrariesBuildConfig: '' # optional -- live-live libraries configuration to use for the run helixQueues: '' # required -- Helix queues + replayType: 'standard' # required -- 'standard', 'apx' jobs: - template: /eng/pipelines/common/templates/runtimes/xplat-job.yml @@ -47,6 +48,9 @@ jobs: - ${{ each variable in parameters.variables }}: - ${{insert}}: ${{ variable }} + - name: replayType + value: ${{ parameters.replayType }} + - template: /eng/pipelines/coreclr/templates/jit-python-variables.yml parameters: osGroup: ${{ parameters.osGroup }} @@ -74,8 +78,8 @@ jobs: mkdir $(SpmiLogsLocation) displayName: Create directories - - script: $(PythonScript) $(Build.SourcesDirectory)/src/coreclr/scripts/superpmi_replay_setup.py -source_directory $(Build.SourcesDirectory) -product_directory $(buildProductRootFolderPath) -arch $(archType) - displayName: ${{ format('SuperPMI replay setup ({0})', parameters.archType) }} + - script: $(PythonScript) $(Build.SourcesDirectory)/src/coreclr/scripts/superpmi_replay_setup.py -source_directory $(Build.SourcesDirectory) -product_directory $(buildProductRootFolderPath) -type $(replayType) -arch $(archType) + displayName: ${{ format('SuperPMI replay setup ({0} {1})', parameters.replayType, parameters.archType) }} # Run superpmi replay in helix - template: /eng/pipelines/common/templates/runtimes/send-to-helix-step.yml @@ -93,6 +97,7 @@ jobs: BuildConfig: ${{ parameters.buildConfig }} osGroup: ${{ parameters.osGroup }} archType: ${{ parameters.archType }} + SuperPmiReplayType: ${{ parameters.replayType }} # Always upload the available logs for diagnostics - task: CopyFiles@2 diff --git a/eng/pipelines/coreclr/templates/superpmi-collect-pipeline.yml b/eng/pipelines/coreclr/templates/superpmi-collect-pipeline.yml new file mode 100644 index 00000000000000..95d99f3357d385 --- /dev/null +++ b/eng/pipelines/coreclr/templates/superpmi-collect-pipeline.yml @@ -0,0 +1,359 @@ +# This template definition automates the SuperPMI collection process, +# and is used by the SuperPMI pipeline definitions. + +extends: + template: /eng/pipelines/common/templates/pipeline-with-resources.yml + parameters: + stages: + - stage: Build + jobs: + + - template: /eng/pipelines/common/platform-matrix.yml + parameters: + jobTemplate: /eng/pipelines/common/global-build-job.yml + buildConfig: checked + platforms: + - windows_x64 + - linux_x64 + jobParameters: + testGroup: outerloop + buildArgs: -s clr+libs+libs.tests -rc $(_BuildConfig) -c Release /p:ArchiveTests=true + timeoutInMinutes: 120 + postBuildSteps: + - template: /eng/pipelines/coreclr/templates/build-native-test-assets-step.yml + - template: /eng/pipelines/common/upload-artifact-step.yml + parameters: + rootFolder: $(Build.SourcesDirectory)/artifacts/bin + includeRootFolder: false + archiveType: $(archiveType) + archiveExtension: $(archiveExtension) + tarCompression: $(tarCompression) + artifactName: BuildArtifacts_$(osGroup)$(osSubgroup)_$(archType)_$(_BuildConfig) + - template: /eng/pipelines/common/upload-artifact-step.yml + parameters: + rootFolder: $(Build.SourcesDirectory)/artifacts/helix + includeRootFolder: false + archiveType: $(archiveType) + archiveExtension: $(archiveExtension) + tarCompression: $(tarCompression) + artifactName: LibrariesTestArtifacts_$(osGroup)$(osSubgroup)_$(archType)_$(_BuildConfig) + extraVariablesTemplates: + - template: /eng/pipelines/common/templates/runtimes/native-test-assets-variables.yml + parameters: + testGroup: outerloop + disableComponentGovernance: true # No shipping artifacts produced by this pipeline + + - template: /eng/pipelines/common/platform-matrix.yml + parameters: + jobTemplate: /eng/pipelines/common/global-build-job.yml + buildConfig: checked + platforms: + - windows_x86 + - windows_arm64 + - osx_arm64 + jobParameters: + testGroup: outerloop + buildArgs: -s clr+libs+libs.tests -rc $(_BuildConfig) -c Release /p:ArchiveTests=true + timeoutInMinutes: 120 + postBuildSteps: + # Build CLR assets for x64 as well as the target as we need an x64 mcs + - template: /eng/pipelines/common/templates/global-build-step.yml + parameters: + buildArgs: -s clr.spmi -c $(_BuildConfig) + archParameter: -arch x64 + displayName: Build SuperPMI + - template: /eng/pipelines/coreclr/templates/build-native-test-assets-step.yml + - template: /eng/pipelines/common/upload-artifact-step.yml + parameters: + rootFolder: $(Build.SourcesDirectory)/artifacts/bin + includeRootFolder: false + archiveType: $(archiveType) + archiveExtension: $(archiveExtension) + tarCompression: $(tarCompression) + artifactName: BuildArtifacts_$(osGroup)$(osSubgroup)_$(archType)_$(_BuildConfig) + - template: /eng/pipelines/common/upload-artifact-step.yml + parameters: + rootFolder: $(Build.SourcesDirectory)/artifacts/helix + includeRootFolder: false + archiveType: $(archiveType) + archiveExtension: $(archiveExtension) + tarCompression: $(tarCompression) + artifactName: LibrariesTestArtifacts_$(osGroup)$(osSubgroup)_$(archType)_$(_BuildConfig) + extraVariablesTemplates: + - template: /eng/pipelines/common/templates/runtimes/native-test-assets-variables.yml + parameters: + testGroup: outerloop + disableComponentGovernance: true # No shipping artifacts produced by this pipeline + + - template: /eng/pipelines/common/platform-matrix.yml + parameters: + jobTemplate: /eng/pipelines/common/global-build-job.yml + buildConfig: checked + platforms: + - linux_arm + - linux_arm64 + jobParameters: + testGroup: outerloop + buildArgs: -s clr+libs+libs.tests -rc $(_BuildConfig) -c Release /p:ArchiveTests=true + timeoutInMinutes: 120 + postBuildSteps: + # Build CLR assets for x64 as well as the target as we need an x64 mcs + - template: /eng/pipelines/common/templates/global-build-step.yml + parameters: + buildArgs: -s clr.spmi -c $(_BuildConfig) + archParameter: -arch x64 + container: linux_x64 + displayName: Build SuperPMI + - template: /eng/pipelines/coreclr/templates/build-native-test-assets-step.yml + - template: /eng/pipelines/common/upload-artifact-step.yml + parameters: + rootFolder: $(Build.SourcesDirectory)/artifacts/bin + includeRootFolder: false + archiveType: $(archiveType) + archiveExtension: $(archiveExtension) + tarCompression: $(tarCompression) + artifactName: BuildArtifacts_$(osGroup)$(osSubgroup)_$(archType)_$(_BuildConfig) + - template: /eng/pipelines/common/upload-artifact-step.yml + parameters: + rootFolder: $(Build.SourcesDirectory)/artifacts/helix + includeRootFolder: false + archiveType: $(archiveType) + archiveExtension: $(archiveExtension) + tarCompression: $(tarCompression) + artifactName: LibrariesTestArtifacts_$(osGroup)$(osSubgroup)_$(archType)_$(_BuildConfig) + extraVariablesTemplates: + - template: /eng/pipelines/common/templates/runtimes/native-test-assets-variables.yml + parameters: + testGroup: outerloop + disableComponentGovernance: true # No shipping artifacts produced by this pipeline + + - template: /eng/pipelines/common/platform-matrix.yml + parameters: + jobTemplate: /eng/pipelines/common/templates/runtimes/build-test-job.yml + buildConfig: checked + platforms: + - CoreClrTestBuildHost # Either osx_x64 or linux_x64 + jobParameters: + testGroup: outerloop + + - template: /eng/pipelines/common/platform-matrix.yml + parameters: + jobTemplate: /eng/pipelines/coreclr/templates/superpmi-collect-job.yml + buildConfig: checked + platforms: + - osx_arm64 + - linux_arm + - linux_arm64 + - linux_x64 + - windows_x64 + - windows_x86 + - windows_arm64 + helixQueueGroup: ci + helixQueuesTemplate: /eng/pipelines/coreclr/templates/helix-queues-setup.yml + jobParameters: + testGroup: outerloop + liveLibrariesBuildConfig: Release + collectionType: pmi + collectionName: libraries + + - template: /eng/pipelines/common/platform-matrix.yml + parameters: + jobTemplate: /eng/pipelines/coreclr/templates/superpmi-collect-job.yml + buildConfig: checked + platforms: + - osx_arm64 + - linux_arm + - linux_arm64 + - linux_x64 + - windows_x64 + - windows_x86 + - windows_arm64 + helixQueueGroup: ci + helixQueuesTemplate: /eng/pipelines/coreclr/templates/helix-queues-setup.yml + jobParameters: + testGroup: outerloop + liveLibrariesBuildConfig: Release + collectionType: crossgen2 + collectionName: libraries + + - template: /eng/pipelines/common/platform-matrix.yml + parameters: + jobTemplate: /eng/pipelines/coreclr/templates/superpmi-collect-job.yml + buildConfig: checked + platforms: + - osx_arm64 + - linux_arm + - linux_arm64 + - linux_x64 + - windows_x64 + - windows_x86 + - windows_arm64 + helixQueueGroup: ci + helixQueuesTemplate: /eng/pipelines/coreclr/templates/helix-queues-setup.yml + jobParameters: + testGroup: outerloop + liveLibrariesBuildConfig: Release + collectionType: run + collectionName: realworld + + - template: /eng/pipelines/common/platform-matrix.yml + parameters: + jobTemplate: /eng/pipelines/coreclr/templates/superpmi-collect-job.yml + buildConfig: checked + platforms: + - osx_arm64 + - linux_arm + - linux_arm64 + - linux_x64 + - windows_x64 + - windows_x86 + - windows_arm64 + helixQueueGroup: ci + helixQueuesTemplate: /eng/pipelines/coreclr/templates/helix-queues-setup.yml + jobParameters: + testGroup: outerloop + liveLibrariesBuildConfig: Release + collectionType: run + collectionName: benchmarks + + - template: /eng/pipelines/common/platform-matrix.yml + parameters: + jobTemplate: /eng/pipelines/coreclr/templates/superpmi-collect-job.yml + buildConfig: checked + platforms: + - osx_arm64 + - linux_arm + - linux_arm64 + - linux_x64 + - windows_x64 + - windows_x86 + - windows_arm64 + helixQueueGroup: ci + helixQueuesTemplate: /eng/pipelines/coreclr/templates/helix-queues-setup.yml + jobParameters: + testGroup: outerloop + liveLibrariesBuildConfig: Release + collectionType: run_pgo + collectionName: benchmarks + + - template: /eng/pipelines/common/platform-matrix.yml + parameters: + jobTemplate: /eng/pipelines/coreclr/templates/superpmi-collect-job.yml + buildConfig: checked + platforms: + - osx_arm64 + - linux_arm + - linux_arm64 + - linux_x64 + - windows_x64 + - windows_x86 + - windows_arm64 + helixQueueGroup: ci + helixQueuesTemplate: /eng/pipelines/coreclr/templates/helix-queues-setup.yml + jobParameters: + testGroup: outerloop + liveLibrariesBuildConfig: Release + collectionType: run_pgo_optrepeat + collectionName: benchmarks + + # + # Collection of coreclr test run + # + - template: /eng/pipelines/common/platform-matrix.yml + parameters: + jobTemplate: /eng/pipelines/common/templates/runtimes/run-test-job.yml + buildConfig: checked + platforms: + - osx_arm64 + - linux_arm + - linux_arm64 + - linux_x64 + - windows_x64 + - windows_x86 + - windows_arm64 + helixQueueGroup: superpmi + helixQueuesTemplate: /eng/pipelines/coreclr/templates/helix-queues-setup.yml + jobParameters: + testGroup: outerloop + liveLibrariesBuildConfig: Release + SuperPmiCollect: true + unifiedArtifactsName: BuildArtifacts_$(osGroup)$(osSubgroup)_$(archType)_$(_BuildConfig) + + - template: /eng/pipelines/common/platform-matrix.yml + parameters: + jobTemplate: /eng/pipelines/coreclr/templates/superpmi-collect-job.yml + buildConfig: checked + platforms: + - linux_arm64 + - linux_x64 + - windows_x64 + - windows_arm64 + helixQueueGroup: ci + helixQueuesTemplate: /eng/pipelines/coreclr/templates/helix-queues-setup.yml + jobParameters: + testGroup: outerloop + liveLibrariesBuildConfig: Release + collectionType: nativeaot + collectionName: smoke_tests + + # + # Collection of libraries test run: normal + # Libraries Test Run using Release libraries, and Checked CoreCLR + # + - template: /eng/pipelines/common/platform-matrix.yml + parameters: + jobTemplate: /eng/pipelines/libraries/run-test-job.yml + buildConfig: Release + platforms: + - osx_arm64 + - linux_arm + - linux_arm64 + - linux_x64 + - windows_x64 + - windows_x86 + - windows_arm64 + helixQueueGroup: superpmi + helixQueuesTemplate: /eng/pipelines/coreclr/templates/helix-queues-setup.yml + jobParameters: + testScope: innerloop + liveRuntimeBuildConfig: Checked + dependsOnTestBuildConfiguration: Release + dependsOnTestArchitecture: x64 + scenarios: + - normal + SuperPmiCollect: true + SuperPmiCollectionName: libraries_tests + unifiedArtifactsName: BuildArtifacts_$(osGroup)$(osSubgroup)_$(archType)_Checked + helixArtifactsName: LibrariesTestArtifacts_$(osGroup)$(osSubgroup)_$(archType)_Checked + unifiedBuildConfigOverride: checked + + # + # Collection of libraries test run: no_tiered_compilation + # Libraries Test Run using Release libraries, and Checked CoreCLR + # + - template: /eng/pipelines/common/platform-matrix.yml + parameters: + jobTemplate: /eng/pipelines/libraries/run-test-job.yml + buildConfig: Release + platforms: + - osx_arm64 + - linux_arm + - linux_arm64 + - linux_x64 + - windows_x64 + - windows_x86 + - windows_arm64 + helixQueueGroup: superpmi + helixQueuesTemplate: /eng/pipelines/coreclr/templates/helix-queues-setup.yml + jobParameters: + testScope: innerloop + liveRuntimeBuildConfig: Checked + dependsOnTestBuildConfiguration: Release + dependsOnTestArchitecture: x64 + scenarios: + - no_tiered_compilation + SuperPmiCollect: true + SuperPmiCollectionName: libraries_tests_no_tiered_compilation + unifiedArtifactsName: BuildArtifacts_$(osGroup)$(osSubgroup)_$(archType)_Checked + helixArtifactsName: LibrariesTestArtifacts_$(osGroup)$(osSubgroup)_$(archType)_Checked + unifiedBuildConfigOverride: checked diff --git a/eng/pipelines/coreclr/templates/superpmi-replay-job.yml b/eng/pipelines/coreclr/templates/superpmi-replay-job.yml index ea7854339a2147..ec364c8d506321 100644 --- a/eng/pipelines/coreclr/templates/superpmi-replay-job.yml +++ b/eng/pipelines/coreclr/templates/superpmi-replay-job.yml @@ -9,12 +9,14 @@ parameters: variables: {} helixQueues: '' runJobTemplate: '/eng/pipelines/coreclr/templates/run-superpmi-replay-job.yml' + replayType: 'standard' + unifiedArtifactsName: '' jobs: - template: ${{ parameters.runJobTemplate }} parameters: - jobName: ${{ format('superpmi_replay_{0}{1}_{2}_{3}', parameters.osGroup, parameters.osSubgroup, parameters.archType, parameters.buildConfig) }} - displayName: ${{ format('SuperPMI replay {0}{1} {2} {3}', parameters.osGroup, parameters.osSubgroup, parameters.archType, parameters.buildConfig) }} + jobName: ${{ format('superpmi_replay_{0}_{1}{2}_{3}_{4}', parameters.replayType, parameters.osGroup, parameters.osSubgroup, parameters.archType, parameters.buildConfig) }} + displayName: ${{ format('SuperPMI replay {0} {1}{2} {3} {4}', parameters.replayType, parameters.osGroup, parameters.osSubgroup, parameters.archType, parameters.buildConfig) }} pool: ${{ parameters.pool }} buildConfig: ${{ parameters.buildConfig }} archType: ${{ parameters.archType }} @@ -23,6 +25,7 @@ jobs: condition: ${{ parameters.condition }} timeoutInMinutes: ${{ parameters.timeoutInMinutes }} helixQueues: ${{ parameters.helixQueues }} + replayType: ${{ parameters.replayType }} dependsOn: - 'build_${{ parameters.osGroup }}${{ parameters.osSubgroup }}_${{ parameters.archType }}_${{ parameters.buildConfig }}_' @@ -30,11 +33,11 @@ jobs: steps: - # Download jit builds + # Download builds - template: /eng/pipelines/common/download-artifact-step.yml parameters: unpackFolder: $(Build.SourcesDirectory)/artifacts/bin/coreclr - artifactFileName: 'CheckedJIT_$(osGroup)$(osSubgroup)_$(archType)$(archiveExtension)' - artifactName: 'CheckedJIT_$(osGroup)$(osSubgroup)_$(archType)' - displayName: 'JIT checked build' + artifactFileName: '${{ parameters.unifiedArtifactsName }}$(archiveExtension)' + artifactName: '${{ parameters.unifiedArtifactsName }}' + displayName: 'unified artifacts' cleanUnpackFolder: false diff --git a/eng/pipelines/diagnostics/runtime-diag-job.yml b/eng/pipelines/diagnostics/runtime-diag-job.yml new file mode 100644 index 00000000000000..0c8009aaf8a8e2 --- /dev/null +++ b/eng/pipelines/diagnostics/runtime-diag-job.yml @@ -0,0 +1,261 @@ +parameters: + buildConfig: '' + name: '' + buildArgs: '' + archType: '' + hostedOs: '' + osGroup: '' + osSubgroup: '' + container: '' + crossBuild: false + variables: [] + targetRid: '' + timeoutInMinutes: '' + dependsOn: [] + # The following parameter is used to specify dependencies on other global build for the same platform. + # We provide this mechanism to allow for global builds to depend on other global builds and use the multiplexing + # that platform-matrix.yml enables. + # Each item can have the following properties: + # - name: The suffix of the job name to depend on. + # - buildConfig: The configuration of the job to depend on. + dependsOnGlobalBuilds: [] + pool: '' + platform: '' + condition: true + useContinueOnErrorDuringBuild: false + shouldContinueOnError: false + isOfficialBuild: false + runtimeFlavor: 'coreclr' + runtimeVariant: '' + helixQueues: '' + enablePublishTestResults: false + testResultsFormat: '' + postBuildSteps: [] + extraVariablesTemplates: [] + preBuildSteps: [] + templatePath: 'templates' + templateContext: '' + disableComponentGovernance: '' + liveRuntimeDir: '' + +jobs: +- template: /eng/common/${{ parameters.templatePath }}/job/job.yml + parameters: + name: ${{ coalesce(parameters.name, parameters.osGroup) }}_${{ parameters.archType }}_${{ parameters.buildConfig }} + pool: ${{ parameters.pool }} + container: ${{ parameters.container }} + condition: and(succeeded(), ${{ parameters.condition }}) + timeoutInMinutes: ${{ parameters.timeoutInMinutes }} + enablePublishTestResults: ${{ parameters.enablePublishTestResults }} + testResultsFormat: ${{ parameters.testResultsFormat }} + + ${{ if ne(parameters.templateContext, '') }}: + templateContext: ${{ parameters.templateContext }} + + artifacts: + publish: + logs: + ${{ if notin(parameters.osGroup, 'browser', 'wasi') }}: + name: Logs_Build_Attempt$(System.JobAttempt)_${{ parameters.osGroup }}_${{ parameters.osSubGroup }}_${{ parameters.archType }}_${{ parameters.buildConfig }}_${{ parameters.name }} + ${{ if in(parameters.osGroup, 'browser', 'wasi') }}: + name: Logs_Build_Attempt$(System.JobAttempt)_${{ parameters.osGroup }}_${{ parameters.archType }}_${{ parameters.hostedOs }}_${{ parameters.buildConfig }}_${{ parameters.name }} + + # Component governance does not work on musl machines + ${{ if eq(parameters.osSubGroup, '_musl') }}: + disableComponentGovernance: true + ${{ else }}: + disableComponentGovernance: ${{ parameters.disableComponentGovernance }} + + workspace: + clean: all + + ${{ if or(ne(parameters.dependsOn,''), ne(parameters.dependsOnGlobalBuilds,'')) }}: + dependsOn: + - ${{ each build in parameters.dependsOn }}: + - ${{ build }} + - ${{ each globalBuild in parameters.dependsOnGlobalBuilds }}: + - ${{ format('build_{0}{1}_{2}_{3}_{4}', parameters.osGroup, parameters.osSubgroup, parameters.archType, coalesce(globalBuild.buildConfig, parameters.buildConfig), globalBuild.name) }} + + variables: + - ${{ if eq(variables['System.TeamProject'], 'internal') }}: + - group: DotNet-HelixApi-Access + - group: AzureDevOps-Artifact-Feeds-Pats + + - _PhaseName: ${{ coalesce(parameters.name, parameters.osGroup) }}_${{ parameters.archType }}_${{ parameters.buildConfig }} + - _Pipeline_StreamDumpDir: $(Build.SourcesDirectory)/artifacts/tmp/${{ parameters.buildConfig }}/streams + + - _TestArgs: '-test' + - _Cross: '' + + - _buildScript: $(Build.SourcesDirectory)$(dir)build$(scriptExt) + + - ${{ if and(eq(parameters.testOnly, 'true'), eq(parameters.buildOnly, 'true')) }}: + 'error, testOnly and buildOnly cannot be true at the same time': error + + - ${{ if eq(parameters.testOnly, 'true') }}: + - _TestArgs: '-test -skipnative' + + - ${{ if or(eq(parameters.buildOnly, 'true'), eq(parameters.isCodeQLRun, 'true')) }}: + - _TestArgs: '' + + # For testing msrc's and service releases. The RuntimeSourceVersion is either "default" or the service release version to test + - _InternalInstallArgs: '' + - ${{ if and(ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest'), eq(parameters.isCodeQLRun, 'false')) }}: + - _InternalInstallArgs: + -dotnetruntimeversion '$(DotnetRuntimeVersion)' + -dotnetruntimedownloadversion '$(DotnetRuntimeDownloadVersion)' + -runtimesourcefeed '$(RuntimeFeedUrl)' + -runtimesourcefeedkey '$(RuntimeFeedBase64SasToken)' + + - ${{ each variableTemplate in parameters.extraVariablesTemplates }}: + - template: ${{ variableTemplate.template }} + parameters: + osGroup: ${{ parameters.osGroup }} + osSubgroup: ${{ parameters.osSubgroup }} + archType: ${{ parameters.archType }} + buildConfig: ${{ parameters.buildConfig }} + runtimeFlavor: ${{ parameters.runtimeFlavor }} + runtimeVariant: ${{ parameters.runtimeVariant }} + helixQueues: ${{ parameters.helixQueues }} + targetRid: ${{ parameters.targetRid }} + name: ${{ parameters.name }} + platform: ${{ parameters.platform }} + shouldContinueOnError: ${{ parameters.shouldContinueOnError }} + ${{ if ne(variableTemplate.forwardedParameters, '') }}: + ${{ each parameter in variableTemplate.forwardedParameters }}: + ${{ parameter }}: ${{ parameters[parameter] }} + ${{ if ne(variableTemplate.parameters, '') }}: + ${{ insert }}: ${{ variableTemplate.parameters }} + + - ${{ each variable in parameters.variables }}: + - ${{ variable }} + + steps: + - ${{ if eq(parameters.osGroup, 'windows') }}: + - template: /eng/pipelines/common/templates/disable-vsupdate-or-failfast.yml + + - checkout: diagnostics + clean: true + fetchDepth: $(checkoutFetchDepth) + + - ${{ if and(eq(parameters.isOfficialBuild, true), notin(parameters.osGroup, 'osx', 'maccatalyst', 'ios', 'iossimulator', 'tvos', 'tvossimulator')) }}: + - template: /eng/pipelines/common/restore-internal-tools.yml + + - ${{ if ne(variables['System.TeamProject'], 'public') }}: + - ${{ if and(ne(parameters.osGroup, 'windows'), ne(parameters.hostedOs, 'windows')) }}: + - task: Bash@3 + displayName: Setup Private Feeds Credentials + inputs: + filePath: $(Build.SourcesDirectory)/eng/common/SetupNugetSources.sh + arguments: $(Build.SourcesDirectory)/NuGet.config $Token + env: + Token: $(dn-bot-dnceng-artifact-feeds-rw) + - ${{ else }}: + - task: PowerShell@2 + displayName: Setup Private Feeds Credentials + inputs: + filePath: $(Build.SourcesDirectory)/eng/common/SetupNugetSources.ps1 + arguments: -ConfigFile $(Build.SourcesDirectory)/NuGet.config -Password $Env:Token + env: + Token: $(dn-bot-dnceng-artifact-feeds-rw) + # Run the NuGetAuthenticate task after the internal feeds are added to the nuget.config + # This ensures that creds are set appropriately for all feeds in the config, and that the + # credential provider is installed. + - task: NuGetAuthenticate@1 + + - ${{ if in(parameters.osGroup, 'osx', 'maccatalyst', 'ios', 'iossimulator', 'tvos', 'tvossimulator') }}: + - script: $(Build.SourcesDirectory)/eng/common/native/install-dependencies.sh ${{ parameters.osGroup }} + displayName: Install Build Dependencies + + - script: | + du -sh $(Build.SourcesDirectory)/* + df -h + displayName: Disk Usage before Build + + - ${{ if in(parameters.platform, 'browser_wasm_win', 'wasi_wasm_win') }}: + # Update machine certs + - task: PowerShell@2 + displayName: Update machine certs + inputs: + filePath: $(Build.SourcesDirectory)/eng/pipelines/mono/update-machine-certs.ps1 + + - ${{ if ne(parameters.preBuildSteps,'') }}: + - ${{ each preBuildStep in parameters.preBuildSteps }}: + - ${{ if ne(preBuildStep.template, '') }}: + - template: ${{ preBuildStep.template }} + parameters: + osGroup: ${{ parameters.osGroup }} + osSubgroup: ${{ parameters.osSubgroup }} + archType: ${{ parameters.archType }} + buildConfig: ${{ parameters.buildConfig }} + runtimeFlavor: ${{ parameters.runtimeFlavor }} + runtimeVariant: ${{ parameters.runtimeVariant }} + helixQueues: ${{ parameters.helixQueues }} + targetRid: ${{ parameters.targetRid }} + name: ${{ parameters.name }} + platform: ${{ parameters.platform }} + shouldContinueOnError: ${{ parameters.shouldContinueOnError }} + ${{ if ne(preBuildStep.forwardedParameters, '') }}: + ${{ each parameter in preBuildStep.forwardedParameters }}: + ${{ parameter }}: ${{ parameters[parameter] }} + ${{ if ne(preBuildStep.parameters, '') }}: + ${{ insert }}: ${{ preBuildStep.parameters }} + - ${{ else }}: + - ${{ preBuildStep }} + + # Build + - script: $(_buildScript) + -ci + -configuration ${{ parameters.buildConfig }} + -architecture ${{ parameters.archType }} + -privatebuild + -useCdac + -liveRuntimeDir ${{ parameters.liveRuntimeDir }} + $(_TestArgs) + $(_Cross) + $(_InternalInstallArgs) + /p:OfficialBuildId=$(BUILD.BUILDNUMBER) + ${{ if eq(parameters.testOnly, 'true') }}: + displayName: Test + ${{ elseif eq(parameters.buildOnly, 'true') }}: + displayName: Build + ${{ else }}: + displayName: Build / Test + condition: succeeded() + + - ${{ if in(parameters.osGroup, 'osx', 'ios', 'tvos', 'android') }}: + - script: | + du -sh $(Build.SourcesDirectory)/* + df -h + displayName: Disk Usage after Build + condition: always() + + # If intended to send extra steps after regular build add them here. + - ${{ if ne(parameters.postBuildSteps,'') }}: + - ${{ each postBuildStep in parameters.postBuildSteps }}: + - ${{ if ne(postBuildStep.template, '') }}: + - template: ${{ postBuildStep.template }} + parameters: + osGroup: ${{ parameters.osGroup }} + osSubgroup: ${{ parameters.osSubgroup }} + archType: ${{ parameters.archType }} + buildConfig: ${{ parameters.buildConfig }} + runtimeFlavor: ${{ parameters.runtimeFlavor }} + runtimeVariant: ${{ parameters.runtimeVariant }} + helixQueues: ${{ parameters.helixQueues }} + targetRid: ${{ parameters.targetRid }} + name: ${{ parameters.name }} + platform: ${{ parameters.platform }} + shouldContinueOnError: ${{ parameters.shouldContinueOnError }} + ${{ if ne(postBuildStep.forwardedParameters, '') }}: + ${{ each parameter in postBuildStep.forwardedParameters }}: + ${{ parameter }}: ${{ parameters[parameter] }} + ${{ if ne(postBuildStep.parameters, '') }}: + ${{ insert }}: ${{ postBuildStep.parameters }} + - ${{ else }}: + - ${{ postBuildStep }} + + - ${{ if and(eq(parameters.isOfficialBuild, true), eq(parameters.osGroup, 'windows')) }}: + - powershell: ./eng/collect_vsinfo.ps1 -ArchiveRunName postbuild_log + displayName: Collect vslogs on exit + condition: always() diff --git a/eng/pipelines/extra-platforms/runtime-extra-platforms-ioslike.yml b/eng/pipelines/extra-platforms/runtime-extra-platforms-ioslike.yml index 47a38df343f80d..7f10d317464cf8 100644 --- a/eng/pipelines/extra-platforms/runtime-extra-platforms-ioslike.yml +++ b/eng/pipelines/extra-platforms/runtime-extra-platforms-ioslike.yml @@ -39,9 +39,9 @@ jobs: isExtraPlatforms: ${{ parameters.isExtraPlatformsBuild }} # Don't trim tests on rolling builds ${{ if eq(variables['isRollingBuild'], true) }}: - buildArgs: -s mono+libs+libs.tests -c $(_BuildConfig) /p:ArchiveTests=true /p:DevTeamProvisioning=- /p:RunAOTCompilation=true $(_runSmokeTestsOnlyArg) /p:BuildTestsOnHelix=true /p:EnableAdditionalTimezoneChecks=true /p:UsePortableRuntimePack=false /p:BuildDarwinFrameworks=true /p:IsManualOrRollingBuild=true /p:EnableAggressiveTrimming=false + buildArgs: -s mono+libs+libs.tests -c $(_BuildConfig) /p:ArchiveTests=true /p:DevTeamProvisioning=- /p:RunAOTCompilation=true $(_runSmokeTestsOnlyArg) /p:BuildTestsOnHelix=true /p:EnableAdditionalTimezoneChecks=true /p:UsePortableRuntimePack=false /p:IsManualOrRollingBuild=true /p:EnableAggressiveTrimming=false ${{ else }}: - buildArgs: -s mono+libs+libs.tests -c $(_BuildConfig) /p:ArchiveTests=true /p:DevTeamProvisioning=- /p:RunAOTCompilation=true $(_runSmokeTestsOnlyArg) /p:BuildTestsOnHelix=true /p:EnableAdditionalTimezoneChecks=true /p:UsePortableRuntimePack=false /p:BuildDarwinFrameworks=true /p:IsManualOrRollingBuild=true /p:EnableAggressiveTrimming=true + buildArgs: -s mono+libs+libs.tests -c $(_BuildConfig) /p:ArchiveTests=true /p:DevTeamProvisioning=- /p:RunAOTCompilation=true $(_runSmokeTestsOnlyArg) /p:BuildTestsOnHelix=true /p:EnableAdditionalTimezoneChecks=true /p:UsePortableRuntimePack=false /p:IsManualOrRollingBuild=true /p:EnableAggressiveTrimming=true timeoutInMinutes: 480 # extra steps, run tests postBuildSteps: diff --git a/eng/pipelines/extra-platforms/runtime-extra-platforms-ioslikesimulator.yml b/eng/pipelines/extra-platforms/runtime-extra-platforms-ioslikesimulator.yml index 2cfb553d5dbd6e..3527856913c7ce 100644 --- a/eng/pipelines/extra-platforms/runtime-extra-platforms-ioslikesimulator.yml +++ b/eng/pipelines/extra-platforms/runtime-extra-platforms-ioslikesimulator.yml @@ -37,7 +37,7 @@ jobs: jobParameters: testGroup: innerloop nameSuffix: AllSubsets_Mono - buildArgs: -s mono+libs+host+packs+libs.tests -c $(_BuildConfig) /p:ArchiveTests=true $(_runSmokeTestsOnlyArg) /p:RunAOTCompilation=true /p:MonoForceInterpreter=true /p:BuildDarwinFrameworks=true + buildArgs: -s mono+libs+host+packs+libs.tests -c $(_BuildConfig) /p:ArchiveTests=true $(_runSmokeTestsOnlyArg) /p:RunAOTCompilation=true /p:MonoForceInterpreter=true timeoutInMinutes: 180 # extra steps, run tests postBuildSteps: diff --git a/eng/pipelines/extra-platforms/runtime-extra-platforms-maccatalyst.yml b/eng/pipelines/extra-platforms/runtime-extra-platforms-maccatalyst.yml index 3eac90bcf95661..4ffece996d9cdc 100644 --- a/eng/pipelines/extra-platforms/runtime-extra-platforms-maccatalyst.yml +++ b/eng/pipelines/extra-platforms/runtime-extra-platforms-maccatalyst.yml @@ -34,7 +34,7 @@ jobs: jobParameters: testGroup: innerloop nameSuffix: AllSubsets_Mono - buildArgs: -s mono+libs+host+packs+libs.tests -c $(_BuildConfig) /p:ArchiveTests=true /p:DevTeamProvisioning=adhoc /p:RunAOTCompilation=true /p:MonoForceInterpreter=true /p:BuildDarwinFrameworks=true + buildArgs: -s mono+libs+host+packs+libs.tests -c $(_BuildConfig) /p:ArchiveTests=true /p:DevTeamProvisioning=adhoc /p:RunAOTCompilation=true /p:MonoForceInterpreter=true timeoutInMinutes: 180 # extra steps, run tests postBuildSteps: @@ -68,7 +68,7 @@ jobs: jobParameters: testGroup: innerloop nameSuffix: AllSubsets_Mono_AppSandbox - buildArgs: -s mono+libs+host+packs+libs.tests -c $(_BuildConfig) /p:ArchiveTests=true $(_runSmokeTestsOnlyArg) /p:DevTeamProvisioning=adhoc /p:RunAOTCompilation=true /p:MonoForceInterpreter=true /p:BuildDarwinFrameworks=true /p:EnableAppSandbox=true + buildArgs: -s mono+libs+host+packs+libs.tests -c $(_BuildConfig) /p:ArchiveTests=true $(_runSmokeTestsOnlyArg) /p:DevTeamProvisioning=adhoc /p:RunAOTCompilation=true /p:MonoForceInterpreter=true /p:EnableAppSandbox=true timeoutInMinutes: 180 # extra steps, run tests postBuildSteps: diff --git a/eng/pipelines/extra-platforms/runtime-extra-platforms-other.yml b/eng/pipelines/extra-platforms/runtime-extra-platforms-other.yml index e47cb4996cc704..d4c755945c0372 100644 --- a/eng/pipelines/extra-platforms/runtime-extra-platforms-other.yml +++ b/eng/pipelines/extra-platforms/runtime-extra-platforms-other.yml @@ -40,7 +40,7 @@ jobs: eq(stageDependencies.EvaluatePaths.evaluate_paths.outputs['SetPathVars_libraries.containsChange'], true), eq(variables['isRollingBuild'], true)) -# Run net48 tests on win-x64 +# Run net481 tests on win-x64 - template: /eng/pipelines/common/platform-matrix.yml parameters: jobTemplate: /eng/pipelines/common/global-build-job.yml @@ -49,16 +49,16 @@ jobs: - windows_x64 helixQueuesTemplate: /eng/pipelines/libraries/helix-queues-setup.yml jobParameters: - framework: net48 - buildArgs: -s tools+libs+libs.tests -framework net48 -c $(_BuildConfig) -testscope innerloop /p:ArchiveTests=true - nameSuffix: Libraries_NET48 + framework: net481 + buildArgs: -s tools+libs+libs.tests -framework net481 -c $(_BuildConfig) -testscope innerloop /p:ArchiveTests=true + nameSuffix: Libraries_NET481 timeoutInMinutes: 150 postBuildSteps: - template: /eng/pipelines/libraries/helix.yml parameters: creator: dotnet-bot - testRunNamePrefixSuffix: NET48_$(_BuildConfig) - extraHelixArguments: /p:BuildTargetFramework=net48 + testRunNamePrefixSuffix: NET481_$(_BuildConfig) + extraHelixArguments: /p:BuildTargetFramework=net481 isExtraPlatformsBuild: ${{ parameters.isExtraPlatformsBuild }} condition: >- or( diff --git a/eng/pipelines/libraries/helix-queues-setup.yml b/eng/pipelines/libraries/helix-queues-setup.yml index 67afc0dd7b3c65..817c66b36286c5 100644 --- a/eng/pipelines/libraries/helix-queues-setup.yml +++ b/eng/pipelines/libraries/helix-queues-setup.yml @@ -34,7 +34,7 @@ jobs: # Linux arm64 - ${{ if eq(parameters.platform, 'linux_arm64') }}: - - (Ubuntu.2204.Arm64.Open)Ubuntu.2204.ArmArch.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:ubuntu-22.04-helix-arm64v8 + - (Ubuntu.2504.Arm64.Open)Ubuntu.2204.ArmArch.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:ubuntu-25.04-helix-arm64v8 - ${{ if or(ne(parameters.jobParameters.isExtraPlatformsBuild, true), eq(parameters.jobParameters.includeAllPlatforms, true)) }}: - (Debian.13.Arm64.Open)Ubuntu.2204.Armarch.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-13-helix-arm64v8 @@ -56,7 +56,7 @@ jobs: - SLES.15.Amd64.Open - (Centos.10.Amd64.Open)Ubuntu.2204.Amd64.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:centos-stream-10-helix-amd64 - (Fedora.41.Amd64.Open)Ubuntu.2204.Amd64.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:fedora-41-helix - - (Ubuntu.2204.Amd64.Open)Ubuntu.2204.Amd64.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:ubuntu-22.04-helix-amd64 + - (Ubuntu.2504.Amd64.Open)Ubuntu.2204.Amd64.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:ubuntu-25.04-helix-amd64 - (Debian.13.Amd64.Open)Ubuntu.2204.Amd64.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-13-helix-amd64 - ${{ if or(ne(parameters.jobParameters.testScope, 'outerloop'), ne(parameters.jobParameters.runtimeFlavor, 'mono')) }}: - ${{ if or(eq(parameters.jobParameters.isExtraPlatformsBuild, true), eq(parameters.jobParameters.includeAllPlatforms, true)) }}: @@ -115,7 +115,7 @@ jobs: # windows x64 - ${{ if eq(parameters.platform, 'windows_x64') }}: # netcoreapp - - ${{ if notIn(parameters.jobParameters.framework, 'net48') }}: + - ${{ if notIn(parameters.jobParameters.framework, 'net481') }}: # libraries on mono outerloop - ${{ if and(eq(parameters.jobParameters.testScope, 'outerloop'), eq(parameters.jobParameters.runtimeFlavor, 'mono')) }}: - Windows.Amd64.Server2022.Open @@ -137,14 +137,14 @@ jobs: - (Windows.Nano.1809.Amd64.Open)windows.10.amd64.serverrs5.open@mcr.microsoft.com/dotnet-buildtools/prereqs:nanoserver-1809-helix-amd64 # .NETFramework - - ${{ if eq(parameters.jobParameters.framework, 'net48') }}: + - ${{ if eq(parameters.jobParameters.framework, 'net481') }}: - Windows.11.Amd64.Client.Open # windows x86 - ${{ if eq(parameters.platform, 'windows_x86') }}: # netcoreapp - - ${{ if notIn(parameters.jobParameters.framework, 'net48') }}: + - ${{ if notIn(parameters.jobParameters.framework, 'net481') }}: # mono outerloop - ${{ if and(eq(parameters.jobParameters.testScope, 'outerloop'), eq(parameters.jobParameters.runtimeFlavor, 'mono')) }}: - Windows.11.Amd64.Client.Open @@ -155,7 +155,7 @@ jobs: - Windows.11.Amd64.Client.Open # .NETFramework - - ${{ if eq(parameters.jobParameters.framework, 'net48') }}: + - ${{ if eq(parameters.jobParameters.framework, 'net481') }}: - Windows.10.Amd64.Client.Open # windows arm64 diff --git a/eng/pipelines/libraries/outerloop.yml b/eng/pipelines/libraries/outerloop.yml index 597f298c37a3e0..bbb1adc12cde55 100644 --- a/eng/pipelines/libraries/outerloop.yml +++ b/eng/pipelines/libraries/outerloop.yml @@ -92,10 +92,10 @@ extends: - ${{ if eq(variables['isRollingBuild'], true) }}: - windows_x64 jobParameters: - framework: net48 + framework: net481 testScope: outerloop - nameSuffix: NET48 - buildArgs: -s libs+libs.tests -c $(_BuildConfig) -testscope outerloop /p:ArchiveTests=true -f net48 + nameSuffix: NET481 + buildArgs: -s libs+libs.tests -c $(_BuildConfig) -testscope outerloop /p:ArchiveTests=true -f net481 timeoutInMinutes: 180 includeAllPlatforms: ${{ variables['isRollingBuild'] }} # extra steps, run tests @@ -104,4 +104,4 @@ extends: parameters: testScope: outerloop creator: dotnet-bot - extraHelixArguments: /p:BuildTargetFramework=net48 + extraHelixArguments: /p:BuildTargetFramework=net481 diff --git a/eng/pipelines/libraries/stress/http.yml b/eng/pipelines/libraries/stress/http.yml index 257334fce3e99c..e083bbac2a6e43 100644 --- a/eng/pipelines/libraries/stress/http.yml +++ b/eng/pipelines/libraries/stress/http.yml @@ -8,11 +8,11 @@ pr: schedules: - cron: "0 13 * * *" # 1PM UTC => 5 AM PST displayName: HttpStress nightly run + always: true branches: include: - main - - release/8.0 - - release/9.0 + - release/*-staging variables: - template: ../variables.yml diff --git a/eng/pipelines/libraries/stress/ssl.yml b/eng/pipelines/libraries/stress/ssl.yml index 360e67a86c98d4..eb2088242dcd2d 100644 --- a/eng/pipelines/libraries/stress/ssl.yml +++ b/eng/pipelines/libraries/stress/ssl.yml @@ -8,11 +8,11 @@ pr: schedules: - cron: "0 13 * * *" # 1PM UTC => 5 AM PST displayName: SslStress nightly run + always: true branches: include: - main - - release/8.0 - - release/9.0 + - release/*-staging variables: - template: ../variables.yml diff --git a/eng/pipelines/official/pipeline.yml b/eng/pipelines/official/pipeline.yml new file mode 100644 index 00000000000000..e343edd0e52b3f --- /dev/null +++ b/eng/pipelines/official/pipeline.yml @@ -0,0 +1,30 @@ +parameters: +- name: buildStage + type: stage +- name: otherStages + type: stageList + +extends: + template: /eng/pipelines/common/templates/pipeline-with-resources.yml + parameters: + isOfficialBuild: true + stages: + - ${{ each stage in parameters.otherStages }}: + - ${{ insert }}: ${{ stage }} + - ${{ insert }}: ${{ parameters.buildStage }} + + - stage: Publish + jobs: + - template: /eng/pipelines/official/prepare-signed-artifacts.yml + parameters: + buildStage: ${{ parameters.buildStage }} + logArtifactName: 'Logs-PrepareSignedArtifacts_Attempt$(System.JobAttempt)' + - template: /eng/common/templates-official/job/publish-build-assets.yml + parameters: + publishUsingPipelines: true + publishAssetsImmediately: true + pool: + name: $(DncEngInternalBuildPool) + demands: ImageOverride -equals 1es-windows-2022 + symbolPublishingAdditionalParameters: '/p:PublishSpecialClrFiles=true' + dependsOn: PrepareSignedArtifacts diff --git a/eng/pipelines/official/jobs/prepare-signed-artifacts.yml b/eng/pipelines/official/prepare-signed-artifacts.yml similarity index 73% rename from eng/pipelines/official/jobs/prepare-signed-artifacts.yml rename to eng/pipelines/official/prepare-signed-artifacts.yml index 1440a7a1dfdc35..ab28ff29899acb 100644 --- a/eng/pipelines/official/jobs/prepare-signed-artifacts.yml +++ b/eng/pipelines/official/prepare-signed-artifacts.yml @@ -1,7 +1,9 @@ parameters: - PublishRidAgnosticPackagesFromPlatform: '' - isOfficialBuild: false - logArtifactName: 'Logs-PrepareSignedArtifacts_Attempt$(System.JobAttempt)' + - name: logArtifactName + type: string + default: 'Logs-PrepareSignedArtifacts_Attempt$(System.JobAttempt)' + - name: buildStage + type: stage jobs: - template: /eng/common/templates-official/job/job.yml @@ -31,9 +33,15 @@ jobs: repository: self clean: true fetchDepth: 20 - - input: pipelineArtifact - artifactName: IntermediateArtifacts - targetPath: $(Build.SourcesDirectory)\artifacts\PackageDownload\IntermediateArtifacts + - ${{ each job in parameters.buildStage.jobs }}: + # Source Build publishes itself, so we don't need to publish it + - ${{ if notIn(job.job, 'Source_Build_Linux_x64')}}: + - input: pipelineArtifact + artifactName: ${{ job.job }}_Artifacts + targetPath: $(Build.SourcesDirectory)\artifacts + itemPattern: | + **/* + !_manifest/**/* outputs: - output: pipelineArtifact displayName: 'Publish BuildLogs' @@ -45,12 +53,11 @@ jobs: - script: >- build.cmd -restore -sign -publish -ci -configuration Release /p:RestoreToolsetOnly=true - /p:PublishRidAgnosticPackagesFromPlatform=${{ parameters.PublishRidAgnosticPackagesFromPlatform }} - /p:DownloadDirectory=$(Build.SourcesDirectory)\artifacts\PackageDownload\ /p:OfficialBuildId=$(Build.BuildNumber) /p:SignType=$(_SignType) /p:DotNetSignType=$(_SignType) /p:DotNetPublishUsingPipelines=true + /p:DotNetFinalPublish=true /bl:$(Build.SourcesDirectory)\prepare-artifacts.binlog displayName: Prepare artifacts and upload to build diff --git a/eng/pipelines/official/stages/publish.yml b/eng/pipelines/official/stages/publish.yml deleted file mode 100644 index 83b059ffc32a65..00000000000000 --- a/eng/pipelines/official/stages/publish.yml +++ /dev/null @@ -1,57 +0,0 @@ -parameters: - PublishRidAgnosticPackagesFromPlatform: windows_x64 - -stages: - -- stage: PrepareForPublish - displayName: Prepare for Publish - variables: - - template: /eng/common/templates-official/variables/pool-providers.yml - jobs: - # Prep artifacts: sign them and upload pipeline artifacts expected by stages-based publishing. - - template: /eng/pipelines/official/jobs/prepare-signed-artifacts.yml - parameters: - PublishRidAgnosticPackagesFromPlatform: ${{ parameters.PublishRidAgnosticPackagesFromPlatform }} - - # Publish to Build Asset Registry in order to generate the ReleaseConfigs artifact. - - template: /eng/common/templates-official/job/publish-build-assets.yml - parameters: - publishUsingPipelines: true - publishAssetsImmediately: true - dependsOn: PrepareSignedArtifacts - pool: - name: $(DncEngInternalBuildPool) - demands: ImageOverride -equals 1es-windows-2022 - symbolPublishingAdditionalParameters: '/p:PublishSpecialClrFiles=true' - -# Stages-based publishing entry point -- template: /eng/common/templates-official/post-build/post-build.yml - parameters: - validateDependsOn: - - PrepareForPublish - # The following checks are run after the build in the validation and release pipelines - # And thus are not enabled here. They can be enabled for dev builds for spot testing if desired - enableSymbolValidation: false - enableSigningValidation: false - enableNugetValidation: false - enableSourceLinkValidation: false - publishAssetsImmediately: true - SDLValidationParameters: - enable: false - artifactNames: - - PackageArtifacts - - BlobArtifacts - params: >- - -SourceToolsList @("policheck","credscan") - -TsaInstanceURL "$(TsaInstanceURL)" - -TsaProjectName "$(TsaProjectName)" - -TsaNotificationEmail "$(TsaNotificationEmail)" - -TsaCodebaseAdmin "$(TsaCodebaseAdmin)" - -TsaBugAreaPath "$(TsaBugAreaPath)" - -TsaIterationPath "$(TsaIterationPath)" - -TsaRepositoryName "$(TsaRepositoryName)" - -TsaCodebaseName "$(TsaCodebaseName)" - -TsaPublish $True - symbolPublishingAdditionalParameters: '/p:PublishSpecialClrFiles=true' - # Publish to blob storage. - publishInstallersAndChecksums: true diff --git a/eng/pipelines/performance/perf-build.yml b/eng/pipelines/performance/perf-build.yml new file mode 100644 index 00000000000000..de9a7674c15937 --- /dev/null +++ b/eng/pipelines/performance/perf-build.yml @@ -0,0 +1,236 @@ +parameters: +- name: runPrivateJobs + displayName: Upload artifacts to blob storage + type: boolean + default: false +- name: mauiFramework + type: string + default: 'net9.0' +- name: coreclr_arm64_linux + displayName: Build Coreclr Arm64 Linux + type: boolean + default: true +- name: coreclr_arm64_windows + displayName: Build Coreclr Arm64 Windows + type: boolean + default: true +- name: coreclr_muslx64_linux + displayName: Build Coreclr Musl x64 Linux + type: boolean + default: true +- name: coreclr_x64_linux + displayName: Build Coreclr x64 Linux + type: boolean + default: true +- name: coreclr_x64_windows + displayName: Build Coreclr x64 Windows + type: boolean + default: true +- name: coreclr_x86_windows + displayName: Build Coreclr x86 Windows + type: boolean + default: true +- name: coreclr_arm64_android + displayName: Build Coreclr Arm64 Android + type: boolean + default: true +- name: wasm + displayName: Build WebAssembly (wasm) + type: boolean + default: true +- name: monoAot_arm64_linux + displayName: Build Mono AOT Arm64 Linux + type: boolean + default: true +- name: monoAot_x64_linux + displayName: Build Mono AOT x64 Linux + type: boolean + default: true +- name: mono_x64_linux + displayName: Build Mono x64 Linux + type: boolean + default: true +- name: mono_arm64_linux + displayName: Build Mono Arm64 Linux + type: boolean + default: true +- name: mono_arm64_android + displayName: Build Mono Arm64 Android + type: boolean + default: true +- name: mono_arm64_ios + displayName: Build Mono Arm64 iOS + type: boolean + default: true +- name: monoBDN_arm64_android + displayName: Build Mono Arm64 Android BDN (Not working) + type: boolean + default: false # currently not working +- name: nativeAot_arm64_ios + displayName: Build native AOT Arm64 iOS + type: boolean + default: true + +trigger: + batch: false # we want to build every single commit + branches: + include: + - main + - release/9.0 + - release/8.0 + paths: + include: + - '*' + exclude: + - '**.md' + - .devcontainer/* + - .github/* + - docs/* + - LICENSE.TXT + - PATENTS.TXT + - THIRD-PARTY-NOTICES.TXT + +resources: + repositories: + - repository: performance + type: git + name: internal/dotnet-performance + +variables: + - template: /eng/pipelines/common/variables.yml + +extends: + template: /eng/pipelines/common/templates/pipeline-with-resources.yml + parameters: + stages: + - ${{ if and(ne(variables['System.TeamProject'], 'public'), or(eq(variables['Build.Reason'], 'IndividualCI'), parameters.runPrivateJobs)) }}: + - stage: RegisterBuild + displayName: 'Register Build' + jobs: + - template: /eng/pipelines/register-build-jobs.yml@performance + parameters: + runtimeRepoAlias: self + performanceRepoAlias: performance + buildType: + - ${{ if eq(parameters.coreclr_arm64_linux, true) }}: + - coreclr_arm64_linux + - ${{ if eq(parameters.coreclr_arm64_windows, true) }}: + - coreclr_arm64_windows + - ${{ if eq(parameters.coreclr_muslx64_linux, true) }}: + - coreclr_muslx64_linux + - ${{ if eq(parameters.coreclr_x64_linux, true) }}: + - coreclr_x64_linux + - ${{ if eq(parameters.coreclr_x64_windows, true) }}: + - coreclr_x64_windows + - ${{ if eq(parameters.coreclr_x86_windows, true) }}: + - coreclr_x86_windows + - ${{ if eq(parameters.coreclr_arm64_android, true) }}: + - coreclr_arm64_android + - ${{ if eq(parameters.wasm, true) }}: + - wasm + - ${{ if eq(parameters.monoAot_arm64_linux, true) }}: + - monoAot_arm64_linux + - ${{ if eq(parameters.monoAot_x64_linux, true) }}: + - monoAot_x64_linux + - ${{ if eq(parameters.mono_x64_linux, true) }}: + - mono_x64_linux + - ${{ if eq(parameters.mono_arm64_linux, true) }}: + - mono_arm64_linux + - ${{ if eq(parameters.mono_arm64_android, true) }}: + - mono_arm64_android + - ${{ if eq(parameters.mono_arm64_ios, true) }}: + - mono_arm64_ios + - ${{ if eq(parameters.monoBDN_arm64_android, true) }}: + - monoBDN_arm64_android + - ${{ if eq(parameters.nativeAot_arm64_ios, true) }}: + - nativeAot_arm64_ios + + - stage: Build + displayName: 'Build' + dependsOn: [] # so it runs in parallel with the RegisterBuild stage + jobs: + - template: /eng/pipelines/runtime-perf-build-jobs.yml@performance + parameters: + runtimeRepoAlias: self + performanceRepoAlias: performance + buildType: + - ${{ if eq(parameters.coreclr_arm64_linux, true) }}: + - coreclr_arm64_linux + - ${{ if eq(parameters.coreclr_arm64_windows, true) }}: + - coreclr_arm64_windows + - ${{ if eq(parameters.coreclr_muslx64_linux, true) }}: + - coreclr_muslx64_linux + - ${{ if eq(parameters.coreclr_x64_linux, true) }}: + - coreclr_x64_linux + - ${{ if eq(parameters.coreclr_x64_windows, true) }}: + - coreclr_x64_windows + - ${{ if eq(parameters.coreclr_x86_windows, true) }}: + - coreclr_x86_windows + - ${{ if eq(parameters.coreclr_arm64_android, true) }}: + - coreclr_arm64_android + - ${{ if eq(parameters.wasm, true) }}: + - wasm + - ${{ if eq(parameters.monoAot_arm64_linux, true) }}: + - monoAot_arm64_linux + - ${{ if eq(parameters.monoAot_x64_linux, true) }}: + - monoAot_x64_linux + - ${{ if eq(parameters.mono_x64_linux, true) }}: + - mono_x64_linux + - ${{ if eq(parameters.mono_arm64_linux, true) }}: + - mono_arm64_linux + - ${{ if eq(parameters.mono_arm64_android, true) }}: + - mono_arm64_android + - ${{ if eq(parameters.mono_arm64_ios, true) }}: + - mono_arm64_ios + - ${{ if eq(parameters.monoBDN_arm64_android, true) }}: + - monoBDN_arm64_android + - ${{ if eq(parameters.nativeAot_arm64_ios, true) }}: + - nativeAot_arm64_ios + ${{ if parameters.mauiFramework }}: + mauiFramework: ${{ parameters.mauiFramework }} + + - ${{ if and(ne(variables['System.TeamProject'], 'public'), or(eq(variables['Build.Reason'], 'IndividualCI'), parameters.runPrivateJobs)) }}: + - stage: UploadArtifacts + displayName: 'Upload Artifacts' + condition: always() + dependsOn: + - Build + - RegisterBuild + jobs: + - template: /eng/pipelines/upload-build-artifacts-jobs.yml@performance + parameters: + runtimeRepoAlias: self + performanceRepoAlias: performance + buildType: + - ${{ if eq(parameters.coreclr_arm64_linux, true) }}: + - coreclr_arm64_linux + - ${{ if eq(parameters.coreclr_arm64_windows, true) }}: + - coreclr_arm64_windows + - ${{ if eq(parameters.coreclr_muslx64_linux, true) }}: + - coreclr_muslx64_linux + - ${{ if eq(parameters.coreclr_x64_linux, true) }}: + - coreclr_x64_linux + - ${{ if eq(parameters.coreclr_x64_windows, true) }}: + - coreclr_x64_windows + - ${{ if eq(parameters.coreclr_x86_windows, true) }}: + - coreclr_x86_windows + - ${{ if eq(parameters.coreclr_arm64_android, true) }}: + - coreclr_arm64_android + - ${{ if eq(parameters.wasm, true) }}: + - wasm + - ${{ if eq(parameters.monoAot_arm64_linux, true) }}: + - monoAot_arm64_linux + - ${{ if eq(parameters.monoAot_x64_linux, true) }}: + - monoAot_x64_linux + - ${{ if eq(parameters.mono_x64_linux, true) }}: + - mono_x64_linux + - ${{ if eq(parameters.mono_arm64_linux, true) }}: + - mono_arm64_linux + - ${{ if eq(parameters.mono_arm64_android, true) }}: + - mono_arm64_android + - ${{ if eq(parameters.mono_arm64_ios, true) }}: + - mono_arm64_ios + - ${{ if eq(parameters.monoBDN_arm64_android, true) }}: + - monoBDN_arm64_android + - ${{ if eq(parameters.nativeAot_arm64_ios, true) }}: + - nativeAot_arm64_ios diff --git a/eng/pipelines/performance/perf-slow.yml b/eng/pipelines/performance/perf-slow.yml index d30ecea46d79a5..84ca1836ff2321 100644 --- a/eng/pipelines/performance/perf-slow.yml +++ b/eng/pipelines/performance/perf-slow.yml @@ -15,10 +15,10 @@ trigger: include: - main - release/9.0 + - release/8.0 paths: include: - '*' - - src/libraries/System.Private.CoreLib/* exclude: - '**.md' - .devcontainer/* @@ -62,4 +62,4 @@ extends: performanceRepoAlias: performance jobParameters: ${{ if parameters.onlySanityCheck }}: - onlySanityCheck: true \ No newline at end of file + onlySanityCheck: true diff --git a/eng/pipelines/performance/perf.yml b/eng/pipelines/performance/perf.yml index e717fbe4915927..01b80b580db2bd 100644 --- a/eng/pipelines/performance/perf.yml +++ b/eng/pipelines/performance/perf.yml @@ -13,7 +13,6 @@ trigger: paths: include: - '*' - - src/libraries/System.Private.CoreLib/* exclude: - '**.md' - .devcontainer/* diff --git a/eng/pipelines/performance/templates/perf-bdn-build-jobs.yml b/eng/pipelines/performance/templates/perf-bdn-build-jobs.yml index 3355ceeedb9d2b..c6ffc983f8c175 100644 --- a/eng/pipelines/performance/templates/perf-bdn-build-jobs.yml +++ b/eng/pipelines/performance/templates/perf-bdn-build-jobs.yml @@ -18,7 +18,7 @@ jobs: - ios_arm64 jobParameters: dependsOn: - - Build_android_arm64_release_Mono_Packs + - build_android_arm64_release_Mono_Packs buildArgs: -s mono -c $(_BuildConfig) nameSuffix: PerfBDNApp isOfficialBuild: false diff --git a/eng/pipelines/performance/templates/perf-build-jobs.yml b/eng/pipelines/performance/templates/perf-build-jobs.yml index 4a77a9796eaa75..4defede2528db2 100644 --- a/eng/pipelines/performance/templates/perf-build-jobs.yml +++ b/eng/pipelines/performance/templates/perf-build-jobs.yml @@ -9,7 +9,7 @@ jobs: windows_x64: true windows_x86: true linux_musl_x64: true - coreclrAndroid: true + android_arm64: true # build mono for AOT - template: /eng/pipelines/performance/templates/perf-mono-build-jobs.yml diff --git a/eng/pipelines/performance/templates/perf-coreclr-build-jobs.yml b/eng/pipelines/performance/templates/perf-coreclr-build-jobs.yml index 96792aab986a07..ddf103aaf4c0d9 100644 --- a/eng/pipelines/performance/templates/perf-coreclr-build-jobs.yml +++ b/eng/pipelines/performance/templates/perf-coreclr-build-jobs.yml @@ -1,11 +1,11 @@ parameters: linux_x64: false linux_musl_x64: false + linux_arm64: false windows_x64: false windows_x86: false - linux_arm64: false windows_arm64: false - coreclrAndroid: false + android_arm64: false jobs: - ${{ if or(eq(parameters.linux_x64, true), eq(parameters.windows_x64, true), eq(parameters.windows_x86, true), eq(parameters.linux_musl_x64, true), eq(parameters.linux_arm64, true), eq(parameters.windows_arm64, true)) }}: @@ -42,7 +42,7 @@ jobs: artifactName: BuildArtifacts_$(osGroup)$(osSubgroup)_$(archType)_$(_BuildConfig)_coreclr displayName: Build Assets - - ${{ if eq(parameters.coreclrAndroid, true) }}: + - ${{ if eq(parameters.android_arm64, true) }}: # build CoreCLR Android scenarios - template: /eng/pipelines/common/platform-matrix.yml parameters: diff --git a/eng/pipelines/performance/templates/perf-ios-scenarios-build-jobs.yml b/eng/pipelines/performance/templates/perf-ios-scenarios-build-jobs.yml index 10b2d79a8d03bc..85cff56fdc4c62 100644 --- a/eng/pipelines/performance/templates/perf-ios-scenarios-build-jobs.yml +++ b/eng/pipelines/performance/templates/perf-ios-scenarios-build-jobs.yml @@ -1,7 +1,7 @@ parameters: hybridGlobalization: true - mono: true - nativeAot: true + mono: false + nativeAot: false jobs: - ${{ if eq(parameters.mono, true) }}: diff --git a/eng/pipelines/runtime-diagnostics.yml b/eng/pipelines/runtime-diagnostics.yml index 3f37ef73ef9e7d..2a4982442f198d 100644 --- a/eng/pipelines/runtime-diagnostics.yml +++ b/eng/pipelines/runtime-diagnostics.yml @@ -1,13 +1,5 @@ trigger: none -schedules: - - cron: "0 10 * * *" # run at 10:00 (UTC) which is 2:00 (PST). - displayName: Runtime Diagnostics scheduled run - branches: - include: - - main - always: true - resources: repositories: - repository: diagnostics @@ -31,4 +23,62 @@ extends: parameters: stages: - stage: Build - jobs: \ No newline at end of file + jobs: + - template: /eng/pipelines/common/platform-matrix.yml + parameters: + jobTemplate: /eng/pipelines/common/global-build-job.yml + buildConfig: release + platforms: + - windows_x64 + jobParameters: + buildArgs: -s clr+libs+tools.cdacreader+host+packs -c $(_BuildConfig) + nameSuffix: AllSubsets_CoreCLR + isOfficialBuild: ${{ variables.isOfficialBuild }} + timeoutInMinutes: 360 + postBuildSteps: + - powershell: | + $versionDir = Get-ChildItem -Directory -Path "$(Build.SourcesDirectory)\artifacts\bin\testhost\net*\shared\Microsoft.NETCore.App" | Select-Object -ExpandProperty FullName + Write-Host "##vso[task.setvariable variable=versionDir]$versionDir" + displayName: 'Set Path to Shared Framework Artifacts' + - template: /eng/pipelines/common/upload-artifact-step.yml + parameters: + rootFolder: $(versionDir) + includeRootFolder: false + archiveType: $(archiveType) + archiveExtension: $(archiveExtension) + tarCompression: $(tarCompression) + artifactName: BuildArtifacts_$(osGroup)$(osSubgroup)_$(archType)_$(_BuildConfig)_coreclr + displayName: Build Assets + - template: /eng/pipelines/common/platform-matrix.yml + parameters: + jobTemplate: /eng/pipelines/diagnostics/runtime-diag-job.yml + buildConfig: release + platforms: + - windows_x64 + jobParameters: + name: Windows + isOfficialBuild: ${{ variables.isOfficialBuild }} + liveRuntimeDir: $(Build.SourcesDirectory)/artifacts/runtime + timeoutInMinutes: 360 + dependsOn: + - build_windows_x64_release_AllSubsets_CoreCLR + preBuildSteps: + - template: /eng/pipelines/common/download-artifact-step.yml + parameters: + artifactName: BuildArtifacts_$(osGroup)$(osSubgroup)_$(archType)_$(_BuildConfig)_coreclr + artifactFileName: BuildArtifacts_$(osGroup)$(osSubgroup)_$(archType)_$(_BuildConfig)_coreclr$(archiveExtension) + unpackFolder: $(Build.SourcesDirectory)/artifacts/runtime + displayName: 'Runtime Build Artifacts' + postBuildSteps: + - task: PublishTestResults@2 + inputs: + testResultsFormat: xUnit + testResultsFiles: '**/*.xml' + searchFolder: '$(Build.SourcesDirectory)/artifacts/TestResults' + testRunTitle: 'Tests $(_PhaseName)' + failTaskOnFailedTests: true + publishRunAttachments: true + mergeTestResults: true + buildConfiguration: $(_BuildConfig) + continueOnError: true + condition: always() \ No newline at end of file diff --git a/eng/pipelines/runtime-official.yml b/eng/pipelines/runtime-official.yml index 7b3afe0f222315..488464ae2d38e5 100644 --- a/eng/pipelines/runtime-official.yml +++ b/eng/pipelines/runtime-official.yml @@ -30,32 +30,36 @@ variables: teamName: dotnet-core-acquisition extends: - template: /eng/pipelines/common/templates/pipeline-with-resources.yml + template: /eng/pipelines/official/pipeline.yml parameters: - isOfficialBuild: true - stages: - - stage: Build - jobs: - - # - # Localization build - # - - ${{ if eq(variables['Build.SourceBranch'], 'refs/heads/main') }}: + otherStages: + - ${{ if eq(variables['Build.SourceBranch'], 'refs/heads/main') }}: + - stage: Localization + dependsOn: [] + jobs: + # + # Localization build + # - template: /eng/common/templates-official/job/onelocbuild.yml parameters: MirrorRepo: runtime MirrorBranch: main LclSource: lclFilesfromPackage LclPackageId: 'LCL-JUNO-PROD-RUNTIME' - - # - # Source Index Build - # - - ${{ if eq(variables['Build.SourceBranch'], 'refs/heads/main') }}: - - template: /eng/common/templates-official/job/source-index-stage1.yml - parameters: - sourceIndexBuildCommand: build.cmd -subset libs.sfx+libs.oob -binarylog -os linux -ci /p:SkipLibrariesNativeRuntimePackages=true - + - stage: Source_Index + dependsOn: [] + displayName: Source Index + jobs: + # + # Source Index Build + # + - template: /eng/common/templates-official/job/source-index-stage1.yml + parameters: + sourceIndexBuildCommand: build.cmd -subset libs.sfx+libs.oob -binarylog -os linux -ci /p:SkipLibrariesNativeRuntimePackages=true + buildStage: + stage: Build + dependsOn: [] + jobs: # # Build CoreCLR runtime packs # Windows x64/arm64 @@ -72,31 +76,28 @@ extends: variables: - name: _SignDiagnosticFilesArgs value: '' + - name: _EnableDefaultArtifactsArg + value: $[iif(and(eq(variables.osGroup, 'windows'), eq(variables.archType, 'x64')),'/p:EnableDefaultRidSpecificArtifacts=false','')] jobParameters: templatePath: 'templates-official' preBuildSteps: - template: /eng/pipelines/coreclr/templates/install-diagnostic-certs.yml parameters: - isOfficialBuild: ${{ variables.isOfficialBuild }} + isOfficialBuild: true certNames: - 'dotnetesrp-diagnostics-aad-ssl-cert' - 'dotnet-diagnostics-esrp-pki-onecert' vaultName: 'clrdiag-esrp-id' azureSubscription: 'diagnostics-esrp-kvcertuser' - buildArgs: -c $(_BuildConfig) /p:DotNetBuildAllRuntimePacks=true $(_SignDiagnosticFilesArgs) + buildArgs: -c $(_BuildConfig) -restore -build -publish /p:DotNetBuildAllRuntimePacks=true $(_SignDiagnosticFilesArgs) $(_EnableDefaultArtifactsArg) nameSuffix: AllRuntimes - isOfficialBuild: ${{ variables.isOfficialBuild }} + isOfficialBuild: true timeoutInMinutes: 120 postBuildSteps: - template: /eng/pipelines/coreclr/templates/remove-diagnostic-certs.yml parameters: - isOfficialBuild: ${{ variables.isOfficialBuild }} - - # Upload the results. - - template: /eng/pipelines/common/upload-intermediate-artifacts-step.yml - parameters: - name: $(osGroup)$(osSubgroup)_$(archType) + isOfficialBuild: true # # Build all runtime packs @@ -112,11 +113,16 @@ extends: - osx_x64 jobParameters: templatePath: 'templates-official' - buildArgs: -s clr.runtime+clr.alljits+clr.nativeaotruntime+host.native -c $(_BuildConfig) /bl:$(Build.SourcesDirectory)/artifacts/logs/$(_BuildConfig)/CoreClrNativeBuild.binlog + buildArgs: -s clr.corelib+clr.nativecorelib+clr.nativeaotlibs+clr.tools+clr.packages+mono+libs+host.tools+host.pkg+packs -restore -build -publish -c $(_BuildConfig) /p:DotNetBuildAllRuntimePacks=true nameSuffix: AllRuntimes isOfficialBuild: ${{ variables.isOfficialBuild }} timeoutInMinutes: 120 - postBuildSteps: + preBuildSteps: + # Build our native assets first so we can sign them. + - template: /eng/pipelines/common/templates/global-build-step.yml + parameters: + buildArgs: -s clr.runtime+clr.alljits+clr.nativeaotruntime+host.native -c $(_BuildConfig) /bl:$(Build.SourcesDirectory)/artifacts/logs/$(_BuildConfig)/CoreClrNativeBuild.binlog + displayName: Build native CoreCLR and host components - ${{ if and(ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: - template: /eng/pipelines/common/macos-sign-with-entitlements.yml parameters: @@ -130,27 +136,16 @@ extends: - name: apphost path: $(Build.SourcesDirectory)/artifacts/bin/$(osGroup)-$(archType).$(_BuildConfig)/corehost - - task: CopyFiles@2 - displayName: 'Copy signed createdump to sharedFramework' - inputs: - contents: createdump - sourceFolder: $(Build.SourcesDirectory)/artifacts/bin/coreclr/$(osGroup).$(archType).$(_BuildConfig) - targetFolder: $(Build.SourcesDirectory)/artifacts/bin/coreclr/$(osGroup).$(archType).$(_BuildConfig)/sharedFramework - overWrite: true - - # Now that we've entitled and signed createdump, we can build the rest. - - template: /eng/pipelines/common/templates/global-build-step.yml - parameters: - buildArgs: -s clr.corelib+clr.nativecorelib+clr.nativeaotlibs+clr.tools+clr.packages+mono+libs+host.tools+host.pkg+packs -c $(_BuildConfig) /p:DotNetBuildAllRuntimePacks=true - displayName: Build managed CoreCLR and host components, Mono, all libraries, and packs - - # Upload the results. - - template: /eng/pipelines/common/upload-intermediate-artifacts-step.yml - parameters: - name: $(osGroup)$(osSubgroup)_$(archType) + - task: CopyFiles@2 + displayName: 'Copy signed createdump to sharedFramework' + inputs: + contents: createdump + sourceFolder: $(Build.SourcesDirectory)/artifacts/bin/coreclr/$(osGroup).$(archType).$(_BuildConfig) + targetFolder: $(Build.SourcesDirectory)/artifacts/bin/coreclr/$(osGroup).$(archType).$(_BuildConfig)/sharedFramework + overWrite: true # - # Build all runtime packs for Linux and Linux musl + # Build all runtime packs for Linux, Linux musl, and mobile # - template: /eng/pipelines/common/platform-matrix.yml parameters: @@ -163,17 +158,29 @@ extends: - linux_musl_x64 - linux_musl_arm - linux_musl_arm64 + - android_x64 + - android_x86 + - android_arm + - android_arm64 + - maccatalyst_x64 + - maccatalyst_arm64 + - tvossimulator_x64 + - tvossimulator_arm64 + - tvos_arm64 + - iossimulator_x64 + - iossimulator_arm64 + - ios_arm64 + - linux_bionic_x64 + - linux_bionic_arm + - linux_bionic_arm64 + - browser_wasm + - wasi_wasm jobParameters: templatePath: 'templates-official' - buildArgs: -c $(_BuildConfig) /p:DotNetBuildAllRuntimePacks=true + buildArgs: -c $(_BuildConfig) -restore -build -publish /p:DotNetBuildAllRuntimePacks=true nameSuffix: AllRuntimes - isOfficialBuild: ${{ variables.isOfficialBuild }} + isOfficialBuild: true timeoutInMinutes: 120 - postBuildSteps: - # Upload the results. - - template: /eng/pipelines/common/upload-intermediate-artifacts-step.yml - parameters: - name: $(osGroup)$(osSubgroup)_$(archType) # # Build and Pack CrossDac @@ -186,24 +193,54 @@ extends: - windows_x64 jobParameters: templatePath: 'templates-official' - buildArgs: -s crossdacpack -c $(_BuildConfig) /p:CrossRuntimeExtractionRoot=$(CrossRuntimeExtractionRoot) $(_SignDiagnosticFilesArgs) + buildArgs: -s crossdacpack -restore -build -publish -c $(_BuildConfig) /p:EnableDefaultRidSpecificArtifacts=false /p:CrossRuntimeExtractionRoot=$(CrossRuntimeExtractionRoot) $(_SignDiagnosticFilesArgs) nameSuffix: CrossDac - isOfficialBuild: ${{ variables.isOfficialBuild }} + isOfficialBuild: true timeoutInMinutes: 120 - preBuildSteps: - - task: DownloadPipelineArtifact@2 - displayName: Download runtime packs for CrossDac + templateContext: inputs: - artifact: 'IntermediateArtifacts' - path: $(Build.SourcesDirectory)/artifacts/RuntimeDownload - patterns: | - IntermediateArtifacts/linux_*/Shipping/Microsoft.NETCore.App.Runtime.linux-*.nupkg - !IntermediateArtifacts/linux_*/Shipping/Microsoft.NETCore.App.Runtime.linux-*.symbols.nupkg - - powershell: $(Build.SourcesDirectory)/eng/extract-for-crossdac.ps1 -DownloadDirectory $(Build.SourcesDirectory)/artifacts/RuntimeDownload -ExtractDirectory $(CrossRuntimeExtractionRoot) + - input: pipelineArtifact + artifactName: Build_linux_x64_release_AllRuntimes_Artifacts + targetPath: $(Build.ArtifactStagingDirectory)/artifacts/RuntimeDownload + itemPattern: | + packages/Release/Shipping/Microsoft.NETCore.App.Runtime.linux-x64.*.nupkg + !packages/Release/Shipping/Microsoft.NETCore.App.Runtime.linux-x64.*.symbols.nupkg + - input: pipelineArtifact + artifactName: Build_linux_arm64_release_AllRuntimes_Artifacts + targetPath: $(Build.ArtifactStagingDirectory)/artifacts/RuntimeDownload + itemPattern: | + packages/Release/Shipping/Microsoft.NETCore.App.Runtime.linux-arm64.*.nupkg + !packages/Release/Shipping/Microsoft.NETCore.App.Runtime.linux-arm64.*.symbols.nupkg + - input: pipelineArtifact + artifactName: Build_linux_musl_x64_release_AllRuntimes_Artifacts + targetPath: $(Build.ArtifactStagingDirectory)/artifacts/RuntimeDownload + itemPattern: | + packages/Release/Shipping/Microsoft.NETCore.App.Runtime.linux-musl-x64.*.nupkg + !packages/Release/Shipping/Microsoft.NETCore.App.Runtime.linux-musl-x64.*.symbols.nupkg + - input: pipelineArtifact + artifactName: Build_linux_musl_arm64_release_AllRuntimes_Artifacts + targetPath: $(Build.ArtifactStagingDirectory)/artifacts/RuntimeDownload + itemPattern: | + packages/Release/Shipping/Microsoft.NETCore.App.Runtime.linux-musl-arm64.*.nupkg + !packages/Release/Shipping/Microsoft.NETCore.App.Runtime.linux-musl-arm64.*.symbols.nupkg + - input: pipelineArtifact + artifactName: Build_linux_arm_release_AllRuntimes_Artifacts + targetPath: $(Build.ArtifactStagingDirectory)/artifacts/RuntimeDownload + itemPattern: | + packages/Release/Shipping/Microsoft.NETCore.App.Runtime.linux-arm.*.nupkg + !packages/Release/Shipping/Microsoft.NETCore.App.Runtime.linux-arm.*.symbols.nupkg + - input: pipelineArtifact + artifactName: Build_linux_musl_arm_release_AllRuntimes_Artifacts + targetPath: $(Build.ArtifactStagingDirectory)/artifacts/RuntimeDownload + itemPattern: | + packages/Release/Shipping/Microsoft.NETCore.App.Runtime.linux-musl-arm.*.nupkg + !packages/Release/Shipping/Microsoft.NETCore.App.Runtime.linux-musl-arm.*.symbols.nupkg + preBuildSteps: + - powershell: $(Build.SourcesDirectory)/eng/extract-for-crossdac.ps1 -DownloadDirectory $(Build.ArtifactStagingDirectory)/artifacts/RuntimeDownload -ExtractDirectory $(CrossRuntimeExtractionRoot) displayName: Extract runtime packs - template: /eng/pipelines/coreclr/templates/install-diagnostic-certs.yml parameters: - isOfficialBuild: ${{ variables.isOfficialBuild }} + isOfficialBuild: true certNames: - 'dotnetesrp-diagnostics-aad-ssl-cert' - 'dotnet-diagnostics-esrp-pki-onecert' @@ -212,12 +249,7 @@ extends: postBuildSteps: - template: /eng/pipelines/coreclr/templates/remove-diagnostic-certs.yml parameters: - isOfficialBuild: ${{ variables.isOfficialBuild }} - # Save packages using the prepare-signed-artifacts format. - # CrossDac packages are expected to be in the windows_x64 folder. - - template: /eng/pipelines/common/upload-intermediate-artifacts-step.yml - parameters: - name: windows_x64 + isOfficialBuild: true dependsOn: - build_linux_x64_release_AllRuntimes - build_linux_arm_release_AllRuntimes @@ -231,73 +263,6 @@ extends: - name: _SignDiagnosticFilesArgs value: '' - # - # Build All runtime packs for mobile platforms - # - - template: /eng/pipelines/common/platform-matrix.yml - parameters: - jobTemplate: /eng/pipelines/common/global-build-job.yml - buildConfig: release - platforms: - - android_x64 - - android_x86 - - android_arm - - android_arm64 - - maccatalyst_x64 - - maccatalyst_arm64 - - tvossimulator_x64 - - tvossimulator_arm64 - - tvos_arm64 - - iossimulator_x64 - - iossimulator_arm64 - - ios_arm64 - - linux_bionic_x64 - - linux_bionic_arm - - linux_bionic_arm64 - jobParameters: - templatePath: 'templates-official' - buildArgs: -c $(_BuildConfig) /p:BuildMonoAOTCrossCompiler=false /p:DotNetBuildAllRuntimePacks=true - nameSuffix: AllRuntimes - isOfficialBuild: ${{ variables.isOfficialBuild }} - postBuildSteps: - # delete duplicate RIDless packages to prevent upload conflict - - task: DeleteFiles@1 - displayName: 'Delete Microsoft.NETCore.App.Ref and Microsoft.NETCore.App.HostModel package' - inputs: - SourceFolder: $(Build.SourcesDirectory)/artifacts/packages/$(_BuildConfig)/Shipping - Contents: | - 'Microsoft.NETCore.App.Ref.*.nupkg' - 'Microsoft.NET.HostModel.*.nupkg' - - template: /eng/pipelines/common/upload-intermediate-artifacts-step.yml - parameters: - name: MobileRuntimePacks - - - template: /eng/pipelines/common/platform-matrix.yml - parameters: - jobTemplate: /eng/pipelines/common/global-build-job.yml - buildConfig: release - runtimeFlavor: mono - platforms: - - browser_wasm - - wasi_wasm - jobParameters: - templatePath: 'templates-official' - buildArgs: -s mono+libs+host+packs -c $(_BuildConfig) /p:AotHostArchitecture=x64 /p:AotHostOS=$(_hostedOS) - nameSuffix: Mono - isOfficialBuild: ${{ variables.isOfficialBuild }} - postBuildSteps: - # delete duplicate RIDless packages to prevent upload conflict - - task: DeleteFiles@1 - displayName: 'Delete Microsoft.NETCore.App.Ref and Microsoft.NETCore.App.HostModel package' - inputs: - SourceFolder: $(Build.SourcesDirectory)/artifacts/packages/$(_BuildConfig)/Shipping - Contents: | - 'Microsoft.NETCore.App.Ref.*.nupkg' - 'Microsoft.NET.HostModel.*.nupkg' - - template: /eng/pipelines/common/upload-intermediate-artifacts-step.yml - parameters: - name: MobileRuntimePacks - - template: /eng/pipelines/common/platform-matrix.yml parameters: jobTemplate: /eng/pipelines/common/global-build-job.yml @@ -307,22 +272,10 @@ extends: - browser_wasm jobParameters: templatePath: 'templates-official' - buildArgs: -s mono+libs+host+packs -c $(_BuildConfig) /p:WasmEnableThreads=true /p:AotHostArchitecture=x64 /p:AotHostOS=$(_hostedOS) + buildArgs: -c $(_BuildConfig) -restore -build -publish /p:DotNetBuildAllRuntimePacks=true /p:WasmEnableThreads=true nameSuffix: Mono_multithread - isOfficialBuild: ${{ variables.isOfficialBuild }} + isOfficialBuild: true runtimeVariant: multithread - postBuildSteps: - # delete duplicate RIDless packages to prevent upload conflict - - task: DeleteFiles@1 - displayName: 'Delete Microsoft.NETCore.App.Ref and Microsoft.NETCore.App.HostModel package' - inputs: - SourceFolder: $(Build.SourcesDirectory)/artifacts/packages/$(_BuildConfig)/Shipping - Contents: | - 'Microsoft.NETCore.App.Ref.*.nupkg' - 'Microsoft.NET.HostModel.*.nupkg' - - template: /eng/pipelines/common/upload-intermediate-artifacts-step.yml - parameters: - name: MobileRuntimePacks # # Build Mono LLVM runtime packs @@ -338,23 +291,11 @@ extends: runtimeFlavor: mono jobParameters: templatePath: 'templates-official' - buildArgs: -s mono+libs+host+packs -c $(_BuildConfig) + buildArgs: -s mono+libs+host+packs -c $(_BuildConfig) -restore -build -publish /p:MonoEnableLLVM=true /p:MonoAOTEnableLLVM=true /p:MonoBundleLLVMOptimizer=true nameSuffix: Mono_LLVMAOT runtimeVariant: LLVMAOT - isOfficialBuild: ${{ variables.isOfficialBuild }} - postBuildSteps: - # delete duplicate RIDless packages to prevent upload conflict - - task: DeleteFiles@1 - displayName: 'Delete Microsoft.NETCore.App.Ref and Microsoft.NETCore.App.HostModel package' - inputs: - SourceFolder: $(Build.SourcesDirectory)/artifacts/packages/$(_BuildConfig)/Shipping - Contents: | - 'Microsoft.NETCore.App.Ref.*.nupkg' - 'Microsoft.NET.HostModel.*.nupkg' - - template: /eng/pipelines/common/upload-intermediate-artifacts-step.yml - parameters: - name: $(osGroup)$(osSubgroup)_$(archType) + isOfficialBuild: true # # Build libraries (all TFMs) and packages @@ -367,13 +308,9 @@ extends: - windows_x64 jobParameters: templatePath: 'templates-official' - buildArgs: -s tools.illink+libs -pack -c $(_BuildConfig) /p:TestAssemblies=false /p:TestPackages=true + buildArgs: -s tools.illink+libs -restore -build -pack -publish -c $(_BuildConfig) /p:TestAssemblies=false /p:TestPackages=true /p:EnableDefaultRidSpecificArtifacts=false nameSuffix: Libraries_WithPackages - isOfficialBuild: ${{ variables.isOfficialBuild }} - postBuildSteps: - - template: /eng/pipelines/common/upload-intermediate-artifacts-step.yml - parameters: - name: Libraries_WithPackages + isOfficialBuild: true timeoutInMinutes: 95 # # Build SourceBuild packages @@ -401,13 +338,9 @@ extends: - linux_arm64 jobParameters: templatePath: 'templates-official' - buildArgs: -s clr.native+clr.corelib+clr.tools+clr.nativecorelib+libs+host+packs -c $(_BuildConfig) -pgoinstrument /p:SkipLibrariesNativeRuntimePackages=true - isOfficialBuild: ${{ variables.isOfficialBuild }} + buildArgs: -s clr.native+clr.corelib+clr.tools+clr.nativecorelib+libs+host+packs -c $(_BuildConfig) -restore -build -publish -pgoinstrument /p:SkipLibrariesNativeRuntimePackages=true + isOfficialBuild: true nameSuffix: PGO - postBuildSteps: - - template: /eng/pipelines/common/upload-intermediate-artifacts-step.yml - parameters: - name: PGO timeoutInMinutes: 95 # @@ -422,96 +355,137 @@ extends: jobParameters: templatePath: 'templates-official' nameSuffix: Workloads - preBuildSteps: - - task: DownloadPipelineArtifact@2 + templateContext: inputs: - artifact: 'IntermediateArtifacts' - path: $(Build.SourcesDirectory)/artifacts/workloadPackages - patterns: | - IntermediateArtifacts/windows_x64/Shipping/Microsoft.NETCore.App.Runtime.AOT.win-x64.Cross.android-*.nupkg - IntermediateArtifacts/windows_arm64/Shipping/Microsoft.NETCore.App.Runtime.AOT.win-arm64.Cross.android-*.nupkg - IntermediateArtifacts/windows_x64/Shipping/Microsoft.NETCore.App.Runtime.AOT.win-x64.Cross.browser-wasm*.nupkg - IntermediateArtifacts/windows_arm64/Shipping/Microsoft.NETCore.App.Runtime.AOT.win-arm64.Cross.browser-wasm*.nupkg - IntermediateArtifacts/windows_x64/Shipping/Microsoft.NETCore.App.Runtime.AOT.win-x64.Cross.wasi-wasm*.nupkg - IntermediateArtifacts/windows_arm64/Shipping/Microsoft.NETCore.App.Runtime.AOT.win-arm64.Cross.wasi-wasm*.nupkg - IntermediateArtifacts/MobileRuntimePacks/Shipping/Microsoft.NETCore.App.Runtime.Mono.android-*.nupkg - IntermediateArtifacts/MobileRuntimePacks/Shipping/Microsoft.NETCore.App.Runtime.Mono.browser-wasm*.nupkg - IntermediateArtifacts/MobileRuntimePacks/Shipping/Microsoft.NETCore.App.Runtime.Mono.multithread.browser-wasm*.nupkg - IntermediateArtifacts/MobileRuntimePacks/Shipping/Microsoft.NETCore.App.Runtime.Mono.ios-*.nupkg - IntermediateArtifacts/MobileRuntimePacks/Shipping/Microsoft.NETCore.App.Runtime.Mono.iossimulator-*.nupkg - IntermediateArtifacts/MobileRuntimePacks/Shipping/Microsoft.NETCore.App.Runtime.Mono.maccatalyst-*.nupkg - IntermediateArtifacts/MobileRuntimePacks/Shipping/Microsoft.NETCore.App.Runtime.Mono.multithread.browser-wasm*.nupkg - IntermediateArtifacts/MobileRuntimePacks/Shipping/Microsoft.NETCore.App.Runtime.Mono.tvos-*.nupkg - IntermediateArtifacts/MobileRuntimePacks/Shipping/Microsoft.NETCore.App.Runtime.Mono.tvossimulator-*.nupkg - IntermediateArtifacts/MobileRuntimePacks/Shipping/Microsoft.NETCore.App.Runtime.Mono.wasi-wasm*.nupkg - IntermediateArtifacts/MobileRuntimePacks/Shipping/Microsoft.NET.Workload.Mono.ToolChain.Current.Manifest*.nupkg - IntermediateArtifacts/MobileRuntimePacks/Shipping/Microsoft.NET.Workload.Mono.ToolChain.net6.Manifest*.nupkg - IntermediateArtifacts/MobileRuntimePacks/Shipping/Microsoft.NET.Workload.Mono.ToolChain.net7.Manifest*.nupkg - IntermediateArtifacts/MobileRuntimePacks/Shipping/Microsoft.NET.Workload.Mono.ToolChain.net8.Manifest*.nupkg - IntermediateArtifacts/MobileRuntimePacks/Shipping/Microsoft.NET.Workload.Mono.ToolChain.net9.Manifest*.nupkg - IntermediateArtifacts/MobileRuntimePacks/Shipping/Microsoft.NET.Runtime.MonoTargets.Sdk*.nupkg - IntermediateArtifacts/MobileRuntimePacks/Shipping/Microsoft.NET.Runtime.MonoAOTCompiler.Task*.nupkg - IntermediateArtifacts/MobileRuntimePacks/Shipping/Microsoft.NET.Runtime.WebAssembly.Sdk*.nupkg - IntermediateArtifacts/MobileRuntimePacks/Shipping/Microsoft.NET.Runtime.WebAssembly.Wasi*.nupkg - IntermediateArtifacts/MobileRuntimePacks/Shipping/Microsoft.NET.Runtime.WebAssembly.Templates*.nupkg - IntermediateArtifacts/windows_arm64/Shipping/Microsoft.NETCore.App.Runtime.win-arm64*.nupkg - IntermediateArtifacts/windows_x64/Shipping/Microsoft.NETCore.App.Runtime.win-x64*.nupkg - IntermediateArtifacts/windows_x86/Shipping/Microsoft.NETCore.App.Runtime.win-x86*.nupkg - IntermediateArtifacts/MobileRuntimePacks/Shipping/Microsoft.NET.Sdk.WebAssembly.Pack*.nupkg + - input: pipelineArtifact + artifactName: Build_windows_x64_release_AllRuntimes_Artifacts + targetPath: $(Build.ArtifactStagingDirectory)/artifacts/workloadPackages + itemPattern: | + **/Microsoft.NETCore.App.Runtime.AOT.win-x64.Cross.android-*.nupkg + **/Microsoft.NETCore.App.Runtime.AOT.win-x64.Cross.browser-wasm*.nupkg + **/Microsoft.NETCore.App.Runtime.AOT.win-x64.Cross.wasi-wasm*.nupkg + **/Microsoft.NETCore.App.Runtime.win-x64*.nupkg + - input: pipelineArtifact + artifactName: Build_windows_arm64_release_AllRuntimes_Artifacts + targetPath: $(Build.ArtifactStagingDirectory)/artifacts/workloadPackages + itemPattern: | + **/Microsoft.NETCore.App.Runtime.AOT.win-arm64.Cross.android-*.nupkg + **/Microsoft.NETCore.App.Runtime.AOT.win-arm64.Cross.browser-wasm*.nupkg + **/Microsoft.NETCore.App.Runtime.AOT.win-arm64.Cross.wasi-wasm*.nupkg + **/Microsoft.NETCore.App.Runtime.win-arm64*.nupkg + - input: pipelineArtifact + artifactName: Build_windows_x86_release_AllRuntimes_Artifacts + targetPath: $(Build.ArtifactStagingDirectory)/artifacts/workloadPackages + itemPattern: | + **/Microsoft.NETCore.App.Runtime.win-x86*.nupkg + - input: pipelineArtifact + artifactName: Build_android_x64_release_AllRuntimes_Artifacts + targetPath: $(Build.ArtifactStagingDirectory)/artifacts/workloadPackages + itemPattern: | + **/Microsoft.NETCore.App.Runtime.Mono.android-*.nupkg + - input: pipelineArtifact + artifactName: Build_android_x86_release_AllRuntimes_Artifacts + targetPath: $(Build.ArtifactStagingDirectory)/artifacts/workloadPackages + itemPattern: | + **/Microsoft.NETCore.App.Runtime.Mono.android-*.nupkg + - input: pipelineArtifact + artifactName: Build_android_arm_release_AllRuntimes_Artifacts + targetPath: $(Build.ArtifactStagingDirectory)/artifacts/workloadPackages + itemPattern: | + **/Microsoft.NETCore.App.Runtime.Mono.android-*.nupkg + - input: pipelineArtifact + artifactName: Build_android_arm64_release_AllRuntimes_Artifacts + targetPath: $(Build.ArtifactStagingDirectory)/artifacts/workloadPackages + itemPattern: | + **/Microsoft.NETCore.App.Runtime.Mono.android-*.nupkg + - input: pipelineArtifact + artifactName: Build_browser_wasm_Linux_release_AllRuntimes_Artifacts + targetPath: $(Build.ArtifactStagingDirectory)/artifacts/workloadPackages + itemPattern: | + **/Microsoft.NETCore.App.Runtime.Mono.browser-wasm*.nupkg + **/Microsoft.NET.Workload.Mono.ToolChain.Current.Manifest*.nupkg + **/Microsoft.NET.Workload.Mono.ToolChain.net6.Manifest*.nupkg + **/Microsoft.NET.Workload.Mono.ToolChain.net7.Manifest*.nupkg + **/Microsoft.NET.Workload.Mono.ToolChain.net8.Manifest*.nupkg + **/Microsoft.NET.Workload.Mono.ToolChain.net9.Manifest*.nupkg + **/Microsoft.NET.Runtime.WebAssembly.Sdk*.nupkg + **/Microsoft.NET.Runtime.WebAssembly.Templates*.nupkg + **/Microsoft.NET.Sdk.WebAssembly.Pack*.nupkg + - input: pipelineArtifact + artifactName: build_browser_wasm_linux_release_Mono_multithread_Artifacts + targetPath: $(Build.ArtifactStagingDirectory)/artifacts/workloadPackages + itemPattern: | + **/Microsoft.NETCore.App.Runtime.Mono.multithread.browser-wasm*.nupkg + - input: pipelineArtifact + artifactName: Build_wasi_wasm_Linux_release_AllRuntimes_Artifacts + targetPath: $(Build.ArtifactStagingDirectory)/artifacts/workloadPackages + itemPattern: | + **/Microsoft.NET.Runtime.WebAssembly.Wasi*.nupkg + **/Microsoft.NETCore.App.Runtime.Mono.wasi-wasm*.nupkg + - input: pipelineArtifact + artifactName: Build_ios_arm64_release_AllRuntimes_Artifacts + targetPath: $(Build.ArtifactStagingDirectory)/artifacts/workloadPackages + itemPattern: | + **/Microsoft.NET.Runtime.MonoTargets.Sdk*.nupkg + **/Microsoft.NET.Runtime.MonoAOTCompiler.Task*.nupkg + **/Microsoft.NETCore.App.Runtime.Mono.ios-*.nupkg + - input: pipelineArtifact + artifactName: Build_iossimulator_x64_release_AllRuntimes_Artifacts + targetPath: $(Build.ArtifactStagingDirectory)/artifacts/workloadPackages + itemPattern: | + **/Microsoft.NETCore.App.Runtime.Mono.iossimulator-*.nupkg + - input: pipelineArtifact + artifactName: Build_iossimulator_arm64_release_AllRuntimes_Artifacts + targetPath: $(Build.ArtifactStagingDirectory)/artifacts/workloadPackages + itemPattern: | + **/Microsoft.NETCore.App.Runtime.Mono.iossimulator-*.nupkg + - input: pipelineArtifact + artifactName: Build_maccatalyst_x64_release_AllRuntimes_Artifacts + targetPath: $(Build.ArtifactStagingDirectory)/artifacts/workloadPackages + itemPattern: | + **/Microsoft.NETCore.App.Runtime.Mono.maccatalyst-*.nupkg + - input: pipelineArtifact + artifactName: Build_maccatalyst_arm64_release_AllRuntimes_Artifacts + targetPath: $(Build.ArtifactStagingDirectory)/artifacts/workloadPackages + itemPattern: | + **/Microsoft.NETCore.App.Runtime.Mono.maccatalyst-*.nupkg + - input: pipelineArtifact + artifactName: Build_tvos_arm64_release_AllRuntimes_Artifacts + targetPath: $(Build.ArtifactStagingDirectory)/artifacts/workloadPackages + itemPattern: | + **/Microsoft.NETCore.App.Runtime.Mono.tvos-*.nupkg + - input: pipelineArtifact + artifactName: Build_tvossimulator_x64_release_AllRuntimes_Artifacts + targetPath: $(Build.ArtifactStagingDirectory)/artifacts/workloadPackages + itemPattern: | + **/Microsoft.NETCore.App.Runtime.Mono.tvossimulator-*.nupkg + - input: pipelineArtifact + artifactName: Build_tvossimulator_arm64_release_AllRuntimes_Artifacts + targetPath: $(Build.ArtifactStagingDirectory)/artifacts/workloadPackages + itemPattern: | + **/Microsoft.NETCore.App.Runtime.Mono.tvossimulator-*.nupkg + preBuildSteps: - task: CopyFiles@2 displayName: Flatten packages inputs: - sourceFolder: $(Build.SourcesDirectory)/artifacts/workloadPackages - contents: '*/Shipping/*.nupkg' - cleanTargetFolder: false + sourceFolder: $(Build.ArtifactStagingDirectory)/artifacts/workloadPackages + contents: 'packages/Release/Shipping/*.nupkg' + cleanTargetFolder: true targetFolder: $(Build.SourcesDirectory)/artifacts/workloadPackages flattenFolders: true - buildArgs: -s mono.workloads -c $(_BuildConfig) /p:PackageSource=$(Build.SourcesDirectory)/artifacts/workloadPackages /p:WorkloadOutputPath=$(Build.SourcesDirectory)/artifacts/workloads - - postBuildSteps: - # Prepare packages wrapping msis - - task: CopyFiles@2 - displayName: Prepare package artifacts - inputs: - SourceFolder: '$(Build.SourcesDirectory)/artifacts/packages/$(_BuildConfig)' - Contents: | - Shipping/**/* - NonShipping/**/* - TargetFolder: '$(Build.ArtifactStagingDirectory)/IntermediateArtifacts1/workloads' - CleanTargetFolder: true - - # Prepare artifacts to be used for generating VS components - - task: CopyFiles@2 - displayName: Prepare VS Insertion artifacts - inputs: - SourceFolder: '$(Build.SourcesDirectory)/artifacts/VSSetup/$(_BuildConfig)' - Contents: | - Insertion/**/* - TargetFolder: '$(Build.ArtifactStagingDirectory)/IntermediateArtifacts2/workloads-vs' - CleanTargetFolder: true - - templateContext: - outputs: - - output: buildArtifacts - PathtoPublish: '$(Build.ArtifactStagingDirectory)/IntermediateArtifacts1' - ArtifactName: IntermediateArtifacts - displayName: 'Publish workload packages' - - output: buildArtifacts - PathtoPublish: '$(Build.ArtifactStagingDirectory)/IntermediateArtifacts2' - ArtifactName: IntermediateArtifacts - displayName: 'Publish workload VS Insertion artifacts' + buildArgs: -s mono.workloads -c $(_BuildConfig) -restore -build -publish /p:PackageSource=$(Build.SourcesDirectory)/artifacts/workloadPackages /p:WorkloadOutputPath=$(Build.SourcesDirectory)/artifacts/workloads /p:ShouldGenerateProductVersionFiles=true /p:EnableDefaultRidSpecificArtifacts=false - isOfficialBuild: ${{ variables.isOfficialBuild }} + isOfficialBuild: true timeoutInMinutes: 120 dependsOn: - Build_android_arm_release_AllRuntimes - Build_android_arm64_release_AllRuntimes - Build_android_x86_release_AllRuntimes - Build_android_x64_release_AllRuntimes - - Build_browser_wasm_Linux_release_Mono - - Build_wasi_wasm_linux_release_Mono + - Build_browser_wasm_Linux_release_AllRuntimes + - Build_wasi_wasm_linux_release_AllRuntimes - Build_ios_arm64_release_AllRuntimes - Build_iossimulator_x64_release_AllRuntimes - Build_iossimulator_arm64_release_AllRuntimes @@ -523,8 +497,4 @@ extends: - Build_windows_x64_release_AllRuntimes - Build_windows_x86_release_AllRuntimes - Build_windows_arm64_release_AllRuntimes - - - ${{ if eq(variables.isOfficialBuild, true) }}: - - template: /eng/pipelines/official/stages/publish.yml - parameters: - isOfficialBuild: ${{ variables.isOfficialBuild }} + - build_browser_wasm_linux_release_Mono_multithread diff --git a/eng/pipelines/runtime.yml b/eng/pipelines/runtime.yml index fe9c980fbb9354..48d65de310e92f 100644 --- a/eng/pipelines/runtime.yml +++ b/eng/pipelines/runtime.yml @@ -125,7 +125,7 @@ extends: - browser_wasm jobParameters: nameSuffix: AllSubsets_CoreCLR - buildArgs: -s mono.emsdk+clr.paltests -rc Release -c Release -lc $(_BuildConfig) + buildArgs: -s mono.emsdk+clr.runtime -rc Release -c Release -lc $(_BuildConfig) timeoutInMinutes: 120 condition: >- or( @@ -1018,7 +1018,7 @@ extends: jobParameters: testGroup: innerloop nameSuffix: AllSubsets_Mono - buildArgs: -s mono+libs+libs.tests+host+packs -c $(_BuildConfig) /p:ArchiveTests=true /p:DevTeamProvisioning=- /p:RunAOTCompilation=true /p:RunSmokeTestsOnly=true /p:BuildTestsOnHelix=true /p:EnableAdditionalTimezoneChecks=true /p:UsePortableRuntimePack=false /p:BuildDarwinFrameworks=true /p:EnableAggressiveTrimming=true + buildArgs: -s mono+libs+libs.tests+host+packs -c $(_BuildConfig) /p:ArchiveTests=true /p:DevTeamProvisioning=- /p:RunAOTCompilation=true /p:RunSmokeTestsOnly=true /p:BuildTestsOnHelix=true /p:EnableAdditionalTimezoneChecks=true /p:UsePortableRuntimePack=false /p:EnableAggressiveTrimming=true timeoutInMinutes: 480 condition: >- or( @@ -1107,7 +1107,7 @@ extends: jobParameters: testGroup: innerloop nameSuffix: AllSubsets_Mono - buildArgs: -s mono+libs+host+packs+libs.tests -c $(_BuildConfig) /p:ArchiveTests=true /p:RunSmokeTestsOnly=true /p:DevTeamProvisioning=adhoc /p:RunAOTCompilation=true /p:MonoForceInterpreter=true /p:BuildDarwinFrameworks=true + buildArgs: -s mono+libs+host+packs+libs.tests -c $(_BuildConfig) /p:ArchiveTests=true /p:RunSmokeTestsOnly=true /p:DevTeamProvisioning=adhoc /p:RunAOTCompilation=true /p:MonoForceInterpreter=true timeoutInMinutes: 180 condition: >- or( @@ -1234,16 +1234,16 @@ extends: - windows_x86 helixQueuesTemplate: /eng/pipelines/libraries/helix-queues-setup.yml jobParameters: - framework: net48 - buildArgs: -s tools+libs+libs.tests -framework net48 -c $(_BuildConfig) -testscope innerloop /p:ArchiveTests=true - nameSuffix: Libraries_NET48 + framework: net481 + buildArgs: -s tools+libs+libs.tests -framework net481 -c $(_BuildConfig) -testscope innerloop /p:ArchiveTests=true + nameSuffix: Libraries_NET481 timeoutInMinutes: 150 postBuildSteps: - template: /eng/pipelines/libraries/helix.yml parameters: creator: dotnet-bot - testRunNamePrefixSuffix: NET48_$(_BuildConfig) - extraHelixArguments: /p:BuildTargetFramework=net48 + testRunNamePrefixSuffix: NET481_$(_BuildConfig) + extraHelixArguments: /p:BuildTargetFramework=net481 condition: >- or( eq(stageDependencies.EvaluatePaths.evaluate_paths.outputs['SetPathVars_libraries.containsChange'], true), diff --git a/eng/testing/BrowserVersions.props b/eng/testing/BrowserVersions.props index 458a34198846c4..e3eb9e2da48cf9 100644 --- a/eng/testing/BrowserVersions.props +++ b/eng/testing/BrowserVersions.props @@ -1,13 +1,13 @@ - 133.0.6943.53 - 1402768 - https://storage.googleapis.com/chromium-browser-snapshots/Linux_x64/1402768 - 13.3.415 - 134.0.6998.35 + 134.0.6998.165 + 1415337 + https://storage.googleapis.com/chromium-browser-snapshots/Linux_x64/1415339 + 13.4.115 + 134.0.6998.166 1415337 https://storage.googleapis.com/chromium-browser-snapshots/Win_x64/1415350 - 13.4.114 + 13.4.115 125.0.1 0.34.0 125.0.1 diff --git a/eng/testing/tests.wasm.targets b/eng/testing/tests.wasm.targets index 65ab8ed35de7f8..0fc4926a782a41 100644 --- a/eng/testing/tests.wasm.targets +++ b/eng/testing/tests.wasm.targets @@ -87,7 +87,9 @@ - + + + Date: Thu, 27 Mar 2025 16:24:30 -0700 Subject: [PATCH 154/203] fix build --- src/coreclr/jit/lsra.cpp | 10 +++++----- src/coreclr/vm/jitinterface.h | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/coreclr/jit/lsra.cpp b/src/coreclr/jit/lsra.cpp index 9c4ca8d750fb93..ba1a9553708b22 100644 --- a/src/coreclr/jit/lsra.cpp +++ b/src/coreclr/jit/lsra.cpp @@ -3866,6 +3866,11 @@ void LinearScan::processKills(RefPosition* killRefPosition) regsBusyUntilKill &= ~killRefPosition->getKilledRegisters(); INDEBUG(dumpLsraAllocationEvent(LSRA_EVENT_KILL_REGS, nullptr, REG_NA, nullptr, NONE, killRefPosition->getKilledRegisters())); + + if (killRefPosition->busyUntilNextKill) + { + regsBusyUntilKill |= killRefPosition->getKilledRegisters(); + } } //------------------------------------------------------------------------ @@ -3901,11 +3906,6 @@ void LinearScan::freeKilledRegs(RefPosition* killRefPosition, : regRecord->recentRefPosition->nextRefPosition; updateNextFixedRef(regRecord, regNextRefPos, nextKill); } - - if (killRefPosition->busyUntilNextKill) - { - regsBusyUntilKill |= killRefPosition->getKilledRegisters(); - } } //------------------------------------------------------------------------ diff --git a/src/coreclr/vm/jitinterface.h b/src/coreclr/vm/jitinterface.h index 0bd9fe62cf0a9f..3b1a8d92553cc7 100644 --- a/src/coreclr/vm/jitinterface.h +++ b/src/coreclr/vm/jitinterface.h @@ -887,7 +887,7 @@ class CEEJitInfo final : public CEECodeGenInfo #ifdef FEATURE_ON_STACK_REPLACEMENT , m_pPatchpointInfoFromJit(NULL), m_pPatchpointInfoFromRuntime(NULL), - m_ilOffset(0) + m_ilOffset(0), #endif m_finalCodeAddressSlot(NULL) { From 1efcc6672f4737360e081325010516ef3cbc25eb Mon Sep 17 00:00:00 2001 From: vsadov <8218165+VSadov@users.noreply.github.com> Date: Thu, 27 Mar 2025 17:09:03 -0700 Subject: [PATCH 155/203] disable building runtime async tests for now --- src/tests/async/Directory.Build.targets | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 src/tests/async/Directory.Build.targets diff --git a/src/tests/async/Directory.Build.targets b/src/tests/async/Directory.Build.targets new file mode 100644 index 00000000000000..f58f1644f5a61f --- /dev/null +++ b/src/tests/async/Directory.Build.targets @@ -0,0 +1,8 @@ + + + + true + + + + From 5de877a8c212f4db26ed5799415b94ae584ddcc7 Mon Sep 17 00:00:00 2001 From: vsadov <8218165+VSadov@users.noreply.github.com> Date: Thu, 27 Mar 2025 21:05:58 -0700 Subject: [PATCH 156/203] fix for unix --- src/coreclr/jit/importer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/jit/importer.cpp b/src/coreclr/jit/importer.cpp index 83088f16e5a85a..7f42784e514766 100644 --- a/src/coreclr/jit/importer.cpp +++ b/src/coreclr/jit/importer.cpp @@ -973,7 +973,7 @@ GenTree* Compiler::impStoreStruct(GenTree* store, // Make sure we don't pass something other than a local address to the return buffer arg. // It is allowed to pass current's method return buffer as it is a local too. - if (fgAddrCouldBeHeap(destAddr) && !eeIsByrefLike(call->gtRetClsHnd) || + if ((fgAddrCouldBeHeap(destAddr) && !eeIsByrefLike(call->gtRetClsHnd)) || (compIsAsync2() && !destAddr->OperIs(GT_LCL_ADDR))) { unsigned tmp = lvaGrabTemp(false DEBUGARG("stack copy for value returned via return buffer")); From d74066dd615e54832795ac41ecd17678f1c39242 Mon Sep 17 00:00:00 2001 From: vsadov <8218165+VSadov@users.noreply.github.com> Date: Thu, 27 Mar 2025 21:43:09 -0700 Subject: [PATCH 157/203] another fix for unix --- src/coreclr/inc/patchpointinfo.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/inc/patchpointinfo.h b/src/coreclr/inc/patchpointinfo.h index 859044ecd144ef..bdff46def7ef99 100644 --- a/src/coreclr/inc/patchpointinfo.h +++ b/src/coreclr/inc/patchpointinfo.h @@ -38,7 +38,7 @@ struct PatchpointInfo void Initialize(unsigned localCount, int totalFrameSize) { m_calleeSaveRegisters = 0; - m_tier0Version = NULL; + m_tier0Version = 0; m_totalFrameSize = totalFrameSize; m_numberOfLocals = localCount; m_genericContextArgOffset = -1; From c9284cfcdd876001f7cf2dc2941fd9b4bfa3ec88 Mon Sep 17 00:00:00 2001 From: vsadov <8218165+VSadov@users.noreply.github.com> Date: Thu, 27 Mar 2025 22:05:00 -0700 Subject: [PATCH 158/203] another unix fix --- src/coreclr/vm/jithelpers.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/vm/jithelpers.cpp b/src/coreclr/vm/jithelpers.cpp index d2f91893309487..765f2f380c9cf0 100644 --- a/src/coreclr/vm/jithelpers.cpp +++ b/src/coreclr/vm/jithelpers.cpp @@ -2576,7 +2576,7 @@ extern "C" void JIT_ResumeOSRWorker(TransitionBlock * pTransitionBlock) // Find OSR method code for this IL offset. - PCODE osrMethodCode = NULL; + PCODE osrMethodCode = 0; { EECodeInfo codeInfo(ip); From 1ff64f1490e42058e9a81a1cd4c309c4eff93c3e Mon Sep 17 00:00:00 2001 From: vsadov <8218165+VSadov@users.noreply.github.com> Date: Fri, 28 Mar 2025 09:56:35 -0700 Subject: [PATCH 159/203] build fixes for x86 --- src/coreclr/vm/jithelpers.cpp | 5 +++-- src/coreclr/vm/jitinterface.h | 4 ++-- src/coreclr/vm/threadsuspend.cpp | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/coreclr/vm/jithelpers.cpp b/src/coreclr/vm/jithelpers.cpp index 765f2f380c9cf0..560708f90d4c3b 100644 --- a/src/coreclr/vm/jithelpers.cpp +++ b/src/coreclr/vm/jithelpers.cpp @@ -1677,7 +1677,7 @@ HCIMPL1(void, IL_ThrowExact, Object* obj) OBJECTREF oref = ObjectToOBJECTREF(obj); #if defined(_DEBUG) && defined(TARGET_X86) - __helperframe.InsureInit(NULL); + __helperframe.EnsureInit(NULL); g_ExceptionEIP = (LPVOID)__helperframe.GetReturnAddress(); #endif // defined(_DEBUG) && defined(TARGET_X86) @@ -2756,7 +2756,7 @@ HCIMPL1(VOID, JIT_PartialCompilationPatchpoint, int ilOffset) } HCIMPLEND -void JIT_ResumeOSR(unsigned ilOffset) +HCIMPL1(VOID, JIT_ResumeOSR, int ilOffset) { // Stub version if OSR feature is disabled // @@ -2764,6 +2764,7 @@ void JIT_ResumeOSR(unsigned ilOffset) UNREACHABLE(); } +HCIMPLEND #endif // FEATURE_ON_STACK_REPLACEMENT diff --git a/src/coreclr/vm/jitinterface.h b/src/coreclr/vm/jitinterface.h index 3b1a8d92553cc7..7eb7b24a1562b5 100644 --- a/src/coreclr/vm/jitinterface.h +++ b/src/coreclr/vm/jitinterface.h @@ -887,9 +887,9 @@ class CEEJitInfo final : public CEECodeGenInfo #ifdef FEATURE_ON_STACK_REPLACEMENT , m_pPatchpointInfoFromJit(NULL), m_pPatchpointInfoFromRuntime(NULL), - m_ilOffset(0), + m_ilOffset(0) #endif - m_finalCodeAddressSlot(NULL) + , m_finalCodeAddressSlot(NULL) { CONTRACTL { diff --git a/src/coreclr/vm/threadsuspend.cpp b/src/coreclr/vm/threadsuspend.cpp index ed495870066a89..d95340e7e8feeb 100644 --- a/src/coreclr/vm/threadsuspend.cpp +++ b/src/coreclr/vm/threadsuspend.cpp @@ -4911,7 +4911,7 @@ static bool GetReturnAddressHijackInfo(EECodeInfo *pCodeInfo X86_ARG(ReturnKind { X86_ONLY(*hasAsyncRet = false); GCInfoToken gcInfoToken = pCodeInfo->GetGCInfoToken(); - if (!pCodeInfo->GetCodeManager()->GetReturnAddressHijackInfo(gcInfoToken X86_ARG(pReturnKind))) + if (!pCodeInfo->GetCodeManager()->GetReturnAddressHijackInfo(gcInfoToken X86_ARG(returnKind))) return false; MethodDesc* pMD = pCodeInfo->GetMethodDesc(); From e93475a1a4057e49d4b8a67ea5b703cbe348d6d5 Mon Sep 17 00:00:00 2001 From: vsadov <8218165+VSadov@users.noreply.github.com> Date: Fri, 28 Mar 2025 10:38:52 -0700 Subject: [PATCH 160/203] build fix for mono --- .../src/CompatibilitySuppressions.xml | 64 +----------------- .../CompilerServices/RuntimeHelpers.cs | 65 ++++++++++++++++++- 2 files changed, 66 insertions(+), 63 deletions(-) diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml b/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml index 6e2b94377078b0..24c02cd1d575d6 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml @@ -743,11 +743,11 @@ CP0001 - T:System.MDArray + T:System.FieldHandleInfo CP0001 - T:System.FieldHandleInfo + T:System.MDArray CP0001 @@ -849,64 +849,4 @@ CP0002 M:System.Threading.Lock.#ctor(System.Boolean) - - CP0002 - M:System.Runtime.CompilerServices.RuntimeHelpers.Await(System.Runtime.CompilerServices.ConfiguredTaskAwaitable) - ref/net10.0/System.Private.CoreLib.dll - lib/net10.0/System.Private.CoreLib.dll - - - CP0002 - M:System.Runtime.CompilerServices.RuntimeHelpers.Await(System.Runtime.CompilerServices.ConfiguredValueTaskAwaitable) - ref/net10.0/System.Private.CoreLib.dll - lib/net10.0/System.Private.CoreLib.dll - - - CP0002 - M:System.Runtime.CompilerServices.RuntimeHelpers.Await(System.Threading.Tasks.Task) - ref/net10.0/System.Private.CoreLib.dll - lib/net10.0/System.Private.CoreLib.dll - - - CP0002 - M:System.Runtime.CompilerServices.RuntimeHelpers.Await(System.Threading.Tasks.ValueTask) - ref/net10.0/System.Private.CoreLib.dll - lib/net10.0/System.Private.CoreLib.dll - - - CP0002 - M:System.Runtime.CompilerServices.RuntimeHelpers.Await``1(System.Runtime.CompilerServices.ConfiguredTaskAwaitable{``0}) - ref/net10.0/System.Private.CoreLib.dll - lib/net10.0/System.Private.CoreLib.dll - - - CP0002 - M:System.Runtime.CompilerServices.RuntimeHelpers.Await``1(System.Runtime.CompilerServices.ConfiguredValueTaskAwaitable{``0}) - ref/net10.0/System.Private.CoreLib.dll - lib/net10.0/System.Private.CoreLib.dll - - - CP0002 - M:System.Runtime.CompilerServices.RuntimeHelpers.Await``1(System.Threading.Tasks.Task{``0}) - ref/net10.0/System.Private.CoreLib.dll - lib/net10.0/System.Private.CoreLib.dll - - - CP0002 - M:System.Runtime.CompilerServices.RuntimeHelpers.Await``1(System.Threading.Tasks.ValueTask{``0}) - ref/net10.0/System.Private.CoreLib.dll - lib/net10.0/System.Private.CoreLib.dll - - - CP0002 - M:System.Runtime.CompilerServices.RuntimeHelpers.AwaitAwaiterFromRuntimeAsync``1(``0) - ref/net10.0/System.Private.CoreLib.dll - lib/net10.0/System.Private.CoreLib.dll - - - CP0002 - M:System.Runtime.CompilerServices.RuntimeHelpers.UnsafeAwaitAwaiterFromRuntimeAsync``1(``0) - ref/net10.0/System.Private.CoreLib.dll - lib/net10.0/System.Private.CoreLib.dll - \ No newline at end of file diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs index 45399a2a7d3c05..8f8f36315c6ed7 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs @@ -176,7 +176,7 @@ public static ReadOnlySpan CreateSpan(RuntimeFieldHandle fldHandle) [Intrinsic] public static bool IsReferenceOrContainsReferences() where T: allows ref struct => IsReferenceOrContainsReferences(); -#if !NATIVEAOT +#if !NATIVEAOT && !MONO [Intrinsic] [BypassReadyToRun] [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.Async)] @@ -334,6 +334,69 @@ public static T Await(ConfiguredValueTaskAwaitable configuredAwaitable) return awaiter.GetResult(); } +#else + // TODO: PlatformSuppressions.xml does not seem to work on MONO. + // Thus we have these as a workaround. + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.Async)] + public static void AwaitAwaiterFromRuntimeAsync(TAwaiter awaiter) where TAwaiter : INotifyCompletion + { + throw new PlatformNotSupportedException(); + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.Async)] + public static void UnsafeAwaitAwaiterFromRuntimeAsync(TAwaiter awaiter) where TAwaiter : ICriticalNotifyCompletion + { + throw new PlatformNotSupportedException(); + } + + [MethodImpl(MethodImplOptions.Async)] + public static T Await(Task task) + { + throw new PlatformNotSupportedException(); + } + + [MethodImpl(MethodImplOptions.Async)] + public static void Await(Task task) + { + throw new PlatformNotSupportedException(); + } + + [MethodImpl(MethodImplOptions.Async)] + public static T Await(ValueTask task) + { + throw new PlatformNotSupportedException(); + } + + [MethodImpl(MethodImplOptions.Async)] + public static void Await(ValueTask task) + { + throw new PlatformNotSupportedException(); + } + + [MethodImpl(MethodImplOptions.Async)] + public static void Await(ConfiguredTaskAwaitable configuredAwaitable) + { + throw new PlatformNotSupportedException(); + } + + [MethodImpl(MethodImplOptions.Async)] + public static void Await(ConfiguredValueTaskAwaitable configuredAwaitable) + { + throw new PlatformNotSupportedException(); + } + + [MethodImpl(MethodImplOptions.Async)] + public static T Await(ConfiguredTaskAwaitable configuredAwaitable) + { + throw new PlatformNotSupportedException(); + } + + [MethodImpl(MethodImplOptions.Async)] + public static T Await(ConfiguredValueTaskAwaitable configuredAwaitable) + { + throw new PlatformNotSupportedException(); + } #endif } } From 5f48abb28566de4f6c2c1eb078a1e98c5e28d416 Mon Sep 17 00:00:00 2001 From: vsadov <8218165+VSadov@users.noreply.github.com> Date: Fri, 28 Mar 2025 10:46:33 -0700 Subject: [PATCH 161/203] jit formatting --- src/coreclr/jit/importer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/jit/importer.cpp b/src/coreclr/jit/importer.cpp index 7f42784e514766..d388b53d2324eb 100644 --- a/src/coreclr/jit/importer.cpp +++ b/src/coreclr/jit/importer.cpp @@ -9098,7 +9098,7 @@ void Compiler::impImportBlockCode(BasicBlock* block) // TODO: The configVal should be wired to the actual implementation // that control the flow of sync context. // We do not have that yet. - int configVal= -1; // -1 not congigured, 0/1 configured to false/true + int configVal = -1; // -1 not congigured, 0/1 configured to false/true if (compIsAsync2() && JitConfig.JitOptimizeAwait()) { isAwait = impMatchAwaitPattern(codeAddr, codeEndp, &configVal); From f42e2607579f704232d6c596a8ba210dfa8cdcba Mon Sep 17 00:00:00 2001 From: vsadov <8218165+VSadov@users.noreply.github.com> Date: Fri, 28 Mar 2025 10:56:14 -0700 Subject: [PATCH 162/203] trailing whitespaces --- docs/design/features/runtime-handled-tasks.md | 64 +++++++++---------- 1 file changed, 31 insertions(+), 33 deletions(-) diff --git a/docs/design/features/runtime-handled-tasks.md b/docs/design/features/runtime-handled-tasks.md index c869d7a2ce4722..eba5d22027889b 100644 --- a/docs/design/features/runtime-handled-tasks.md +++ b/docs/design/features/runtime-handled-tasks.md @@ -30,7 +30,7 @@ Unlike traditional C# compiler generated async, async2 methods shall not save/re #### Integration with `SynchronizationContext` and resumption -A new attribute `System.Runtime.CompilerServices.ConfigureAwaitAttribute` will be defined. It can be applied at the assembly/module/type/method level. It shall be defined as +A new attribute `System.Runtime.CompilerServices.ConfigureAwaitAttribute` will be defined. It can be applied at the assembly/module/type/method level. It shall be defined as ```cs namespace System.Runtime.CompilerServices { @@ -91,7 +91,7 @@ async2 Task ThunkAsync(ParameterType param1, ParameterType2 param2, } ``` -If any of the parameter types to the target method are ref parameters, or ref structures, then the Thunk generated will be +If any of the parameter types to the target method are ref parameters, or ref structures, then the Thunk generated will be ```cs async2 Task ThunkAsync(ParameterType param1, ParameterType2 param2, ...) { @@ -145,7 +145,7 @@ Non-async2 code is never permitted to directly invoke async2 code. Instead any a ### Interaction with Reflection -Async2 methods will not be visible in reflection via the `Type.GetMethod`, `TypeInfo.DeclaredMethods`, `TypeInfo.DeclaredMembers` , or `Type.GetInterfaceMap()` apis. +Async2 methods will not be visible in reflection via the `Type.GetMethod`, `TypeInfo.DeclaredMethods`, `TypeInfo.DeclaredMembers` , or `Type.GetInterfaceMap()` apis. `Type.GetMembers` and `Type.GetMethods` will be able to find the async2 methods if and only if the `BindingFlags.Async2Visible` flag is set. @@ -456,7 +456,7 @@ async2 Task Thunk(ParameterType param1, ParameterType2 param2, ...) ## C# Language changes For the purpose of the experiment we have extended the syntax and semantics of C# language to support async2 methods. It is not the goal for this syntax to become the syntax in the actual implementation, however we will record our experiences with the experimental syntax in case it is useful when designing the actual syntax. - + #### === Syntax v2 (current) The syntax that is currently in use in the experiment looks like: @@ -485,7 +485,7 @@ int32 modopt([System.Runtime]System.Threading.Tasks.ValueTask`1) M1(int arg) ``` and combinations of the above. -Internal Roslyn compiler representation of async2 methods is roughly the same as for regular `Task/ValueTask` returning methods, except that the method symbol reports that it is an async2 method thus scenarios that are different for different flavors of async/async2 (i.e. lowering of `await` operator), can work differently. +Internal Roslyn compiler representation of async2 methods is roughly the same as for regular `Task/ValueTask` returning methods, except that the method symbol reports that it is an async2 method thus scenarios that are different for different flavors of async/async2 (i.e. lowering of `await` operator), can work differently. In a particular case of lowering await operator, if we notice that both containing and awaited members are async2, we can lower the `await M1()` into a direct call to the `int modopt(..)M1()` Conversely, if we see an invocation of `M1()` without await or in a regular, non-async2 member, we lower that into a call to `Task M1()` thunk. @@ -517,18 +517,18 @@ public int32 modopt([System.Runtime]System.Threading.Tasks.Task`1) M1() Operations that are not affected by `async2`, such as generic substitution or Overriding/Hiding/Implementing end up naturally working with async2 methods. Note that these areas are some of the most complex parts of the compiler. It was very beneficial to not having to specialcase async2 in these areas. -One thing to observe is that unlike regular `async`, which is a source-only concept, the async2 survives serialization/deserialization via metadata. +One thing to observe is that unlike regular `async`, which is a source-only concept, the async2 survives serialization/deserialization via metadata. Effectively we treat the `int32 modopt([System.Runtime]System.Threading.Tasks.Task'1)` as just a special encoding of `Task` return type with additional property of making the method `async2`. #### Unresolved concerns. None of the following appears to be unresolvable or blocking, we just did not get to these due to time constraints and the scoping of the experiment. 1. There is an existing concept for `async void` methods. Does that model need to exist in the new system? -1. It looks like there might be a need for async2 flavors of delegates. +1. It looks like there might be a need for async2 flavors of delegates. To the runtime these would look like delegates with async2 `Invoke` method and may "just work". They would need some kind of syntax in C#. -1. It looks like there might be a need for async2 flavors of method pointers - to represent async2 methods. +1. It looks like there might be a need for async2 flavors of method pointers - to represent async2 methods. In the runtime this is representable via the modopt on the return type and, again, might "just work", so this is mostly a matter of picking C# syntax/semantics. -1. Is there any impact on async enumerable/iterator? +1. Is there any impact on async enumerable/iterator? While async2 is interoperable with regular async and these interfaces could be used or even implemented by the means of async2 methods, there could be opportunities to employ async2 implementations in the lowering. It is not clear if that could be beneficial without defining/deriving async2 versions of `IAsyncEnumerable`/`IAsyncEnumerator`/`IAsyncDisposable`. #### ==== Syntax v1 (abandoned) @@ -547,12 +547,12 @@ int32 modopt([System.Runtime]System.Threading.Tasks.Task`1) M1(int arg) While initially favored for being close to the IL representation, it was eventually abandoned due to major problems and inconsistencies. - When the method above is called from regular code without awaiting, user gets `Task` result. That is not what it looks in the signature. - For the purposes of overriding/hiding/inheriting the method above is equivalent to `Task M1(int arg)`, which is easily observable. -For example it is illegal to add such method in the same class as invoking `Task M1(int arg)` would become ambiguous. On the other hand overloading a method like `int M1(int arg)` is completely non-conflicting. -This was very confusing, both as a programming model and as internal implementation. -- Internally, due to duality with `Task M1(int arg)` signature it was often necessary to "lift" the signature into the `Task` form, perform operation there (such as generic substitution or overriding resolution) and then "unlift" the result back into `async2 int` form. It was frequintly a source of subtle bugs. +For example it is illegal to add such method in the same class as invoking `Task M1(int arg)` would become ambiguous. On the other hand overloading a method like `int M1(int arg)` is completely non-conflicting. +This was very confusing, both as a programming model and as internal implementation. +- Internally, due to duality with `Task M1(int arg)` signature it was often necessary to "lift" the signature into the `Task` form, perform operation there (such as generic substitution or overriding resolution) and then "unlift" the result back into `async2 int` form. It was frequintly a source of subtle bugs. - When we got to interfacing with `ValueTask` there was no good place in the syntax to express a variant of the same method, but with `ValueTask`-based thunk. -For some time we used an attribute and that was verbose and not symmetrical. -Besides, it is valid for the same class to contain two async2 methods that differ only on `Task`/`ValueTask` flavor of the thunk. It would mean that in the source one could have two methods in the same class, differing only by an attribute. +For some time we used an attribute and that was verbose and not symmetrical. +Besides, it is valid for the same class to contain two async2 methods that differ only on `Task`/`ValueTask` flavor of the thunk. It would mean that in the source one could have two methods in the same class, differing only by an attribute. ## Microbenchmarks @@ -566,7 +566,7 @@ The data was gathered using the `src/tests/Loader/async/async-mincallcost-microb This benchmark works by an async method calling into another mostly empty async method in a loop. The benchmark operates for a fixed period of time, measuring the number of iterations. -The caller method is always a `Task` returning method, and the mostly empty method, reads a global variable, and then awaits the result of a call to `Task.Yield()` if that global variable is `0`. (The variable never has the value '0'.) +The caller method is always a `Task` returning method, and the mostly empty method, reads a global variable, and then awaits the result of a call to `Task.Yield()` if that global variable is `0`. (The variable never has the value '0'.) ![MinCallCostGraph](async-mincallcost.svg) @@ -576,7 +576,7 @@ In addition, the cost to call an async2 function from another async2 function is Here we can see the cost of inlining vs not inlining, as there is a meaningful reduction in iteration count. In addition, the async2 method with Context Save is also unable to inline at the current time, and also performs work simulating the additional work that the async2 logic would need to implement to fully replicate async1 behavior around async locals, and synchronization context handling. -NOTES: +NOTES: - Cost of calls from async1 method to async2 method are shown for the JIT state machine model only. The performance of the calls from async1 to async2 for the unwinder model were substantially slower, although if we choose that as the implementation style it is expected they could be made at least as fast as the state machine model. - Measurement of calls from async1 to async 2 non-inlineable and with context save were not measured. @@ -614,7 +614,7 @@ With `ValueTask`, the function signature is `async ValueTask RunTask(int d With `Async2`, the function signature is `async2 long RunTask(int depth)` With `Async2Capture`, the function signature is `async2 long RunTask(int depth)`, and the body of the function contains a try/finally which saves/restores the Sync and ExecutionContext. This is intended to (imperfectly) measure the cost of adjusting the runtime async model to behave exactly like the existing async model. -When a graph does not specify one of the config options, the graph is a relative performance gathered by dividing the iters/250ms that the benchmark produces between the two options, and then multiplying by 100 to produce a percentage. +When a graph does not specify one of the config options, the graph is a relative performance gathered by dividing the iters/250ms that the benchmark produces between the two options, and then multiplying by 100 to produce a percentage. ## Relative performance of throwing vs returning cleanly from a function at varying stack depths @@ -708,15 +708,15 @@ It is not a secret that inability to use span and byref with async code causes s https://stackoverflow.com/questions/57229123/how-to-use-spanbyte-in-async-method -https://stackoverflow.com/questions/63284335/spant-and-async-methods +https://stackoverflow.com/questions/63284335/spant-and-async-methods -[Span in async methods not supported · Issue #27147 · dotnet/roslyn (github.com)] (https://github.com/dotnet/roslyn/issues/27147) +[Span in async methods not supported · Issue #27147 · dotnet/roslyn (github.com)] (https://github.com/dotnet/roslyn/issues/27147) -https://stackoverflow.com/questions/20868103/ref-and-out-arguments-in-async-method +https://stackoverflow.com/questions/20868103/ref-and-out-arguments-in-async-method -[Consider relaxing restrictions of async methods in blocks that do not contain `await`. · Issue 1331 · dotnet/csharplang (github.com)](https://github.com/dotnet/csharplang/issues/1331) Lots of discussions here, including insightful comments by Vance. +[Consider relaxing restrictions of async methods in blocks that do not contain `await`. · Issue 1331 · dotnet/csharplang (github.com)](https://github.com/dotnet/csharplang/issues/1331) Lots of discussions here, including insightful comments by Vance. -The current situation is that C# does not allow byrefs and byref-likes in async methods in any form – be it parameters, locals or spans. The reason for this restriction is inability to capture byrefs as fields of display types. For consistency all use is forbidden, even if it does not require capture. +The current situation is that C# does not allow byrefs and byref-likes in async methods in any form – be it parameters, locals or spans. The reason for this restriction is inability to capture byrefs as fields of display types. For consistency all use is forbidden, even if it does not require capture. There are few cases were C# supports transient byrefs by the means of decomposing a byref-producing expression into constituent parts and capturing the parts and re-playing at use sites. Example `staticArray[i].Field += await Somehting();` That allows to handle a subset of cases, but codegen is far from great. In runtime-assisted async supporting span and byrefs is not a strict “no”, but there are implications. There are roughly two kinds of byrefs that async implementation would need to deal with. They come with distinct challenges. @@ -725,14 +725,14 @@ In runtime-assisted async supporting span and byrefs is not a strict “no”, b **Stack-referencing byrefs** do not have to be reported to GC, but must be adjusted when containing frame is reactivated as the referent is either in the current frame and thus now lives in a different stack location, or in one of still suspended caller frames and thus not on the stack at all. In the presence of “ref Span param” and similar, updating upon suspending is also necessary to keep chains of byrefs safely walkable. -In general it would not be a tractable programming model from the user perspective, if we allow only one kind of byrefs and not another as distinction may often only be known at run time. -Note that the stack/heap-referencing nature of a byref cannot change while the frame is not active. However that is not completely true if “ref Span param” is allowed. In such case a calee may repoint a byref in a caller frame and switch from stack-pointing to heap-pointing. +In general it would not be a tractable programming model from the user perspective, if we allow only one kind of byrefs and not another as distinction may often only be known at run time. +Note that the stack/heap-referencing nature of a byref cannot change while the frame is not active. However that is not completely true if “ref Span param” is allowed. In such case a calee may repoint a byref in a caller frame and switch from stack-pointing to heap-pointing. **Stack-referencing byrefs, that lead into sync callers** these are unsafe as the sync caller may exit any or all its stack frames before the Task is resumed. In the current experiment we have looked into two implementing strategies: -**Stack unwinding with 1:1 stack capture for suspended frames.** In this model byrefs/spans that point to heap or other async callers can be supported as there is a mechanism to report byrefs to the GC. There are some performance challenges, but not without solutions. +**Stack unwinding with 1:1 stack capture for suspended frames.** In this model byrefs/spans that point to heap or other async callers can be supported as there is a mechanism to report byrefs to the GC. There are some performance challenges, but not without solutions. **State machine with managed storage for captured variables.** The current implementation does not support capturing byrefs and byref-likes. On the other hand it can utilize regular GC reporting mechanisms, which has many advantages, notably not having O(n) components that need to run in GC pauses. There are some ideas on how byrefs may be captured and converted into {object, offset} at the next GC, so that not to incur continuous O(n) costs. @@ -752,13 +752,13 @@ Our observations (on x64, AMD 5950X): There is some noise, but a typical Gen0 pause in the above scenario was: 120 microseconds. The managed heap size (as reported by `GetGCMemoryInfo.HeapSizeBytes`) is stable at 130 Mb. -The peak working set (as reported by `Task Manager`) is 148 Mb. +The peak working set (as reported by `Task Manager`) is 148 Mb. **=====Async2 (unwinding/stack-capturing based)**: When GC pauses were measured as-is, the results were very disappointing - **48690 microsecond**. (**~400x worse** than async1 and generally unacceptable for a Gen0 pause) -The reasons for such timings is that the implementation would go through every one of the 1000000 tasklets and report referenced objects as roots. That is time consuming and is also very redundant when GC generational model is considered. Since suspended stacks can't see assignments of new objects, once GC performs en-masse promotion, all the roots owned by these tasks become too old to be of interest for collections that target younger generations until the containing task is resumed. +The reasons for such timings is that the implementation would go through every one of the 1000000 tasklets and report referenced objects as roots. That is time consuming and is also very redundant when GC generational model is considered. Since suspended stacks can't see assignments of new objects, once GC performs en-masse promotion, all the roots owned by these tasks become too old to be of interest for collections that target younger generations until the containing task is resumed. This is a unique challenge of stack-capturing implementation. In async1 suspended tasks are regular heap objects and do not require any special reporting in GC STW phases. As a solution to this issue, we implemented tasklet aging mechanism, similar to approach used by GC handles. We make note of GC promotions, and increase "min age" of suspended tasks, so that, for example, tasks that only refer to tenured objects do not need to report roots in ephemeral collections. @@ -768,20 +768,20 @@ It was still **4x worse** than async1. Once tasks know their age, root reporting is no longer the dominant factor, but we still need to check age of 10000 tasks, even if the answer is "too old". A further improvement is possible by grouping the suspended tasks in groups, so that age of entire group could be examined. -For the memory consumption, the managed heap is reported to be very small - **28Mb**. Unsurprisingly, since this approach uses native/malloc memory to store captured data. +For the memory consumption, the managed heap is reported to be very small - **28Mb**. Unsurprisingly, since this approach uses native/malloc memory to store captured data. However, the peak working set was seen at **499Mb** that is **3.3x worse** than async1. The reason for higher memory consumption is that this approach captures the entire stack frame and its registers, which is less eficient than async1, which, based on data-flow analysis, may capture only variables that are live across awaits. **=====Async2 (JIT state-machine based)**: -The pause times were observed at **100 microseconds**, which is **better than in async1** implementation. +The pause times were observed at **100 microseconds**, which is **better than in async1** implementation. Since the root set would be roughly the same as in async1, the difference could be just second-order effects of different heap graph or different distribution of object sizes. -The peak working set was seen at 328Mb. +The peak working set was seen at 328Mb. That is higher than async1 and most likely just additional transient allocations in the JIT. As the benchmark has just a few methods, it cannot be method bodies, but may need some follow up to confirm. -Managed allocation impact in this model depends on JIT optimizations as the capture is based on liveness information, which is not available in tier-0 compiled methods. That is expected and could be acceptable as hot methods would not stay in tier-0. Besides it is possible to enable liveness analysis in tier-0 async2 methods, if needed, at about 5% of the estimated JIT cost. +Managed allocation impact in this model depends on JIT optimizations as the capture is based on liveness information, which is not available in tier-0 compiled methods. That is expected and could be acceptable as hot methods would not stay in tier-0. Besides it is possible to enable liveness analysis in tier-0 async2 methods, if needed, at about 5% of the estimated JIT cost. It is also possible to improve liveness analysis/information in async2 methods in general - to further improve capturing efficiency. With `set DOTNET_TieredCompilation=0` @@ -789,5 +789,3 @@ Managed heap size: **157Mb** - slightly more than async1 With `set DOTNET_TieredCompilation=1` Managed heap size: **reaches 300Mb** in the first couple iterations, but then drops and **stays at 105Mb**, which is better than async1. - - From 9558af64e38fc09d9f87c7b2b373430a33d90d8c Mon Sep 17 00:00:00 2001 From: vsadov <8218165+VSadov@users.noreply.github.com> Date: Fri, 28 Mar 2025 11:25:52 -0700 Subject: [PATCH 163/203] build fix for riscv --- src/coreclr/jit/targetloongarch64.h | 3 +++ src/coreclr/jit/targetriscv64.h | 3 +++ 2 files changed, 6 insertions(+) diff --git a/src/coreclr/jit/targetloongarch64.h b/src/coreclr/jit/targetloongarch64.h index 452778c31963a0..d691f4c8fd1ec2 100644 --- a/src/coreclr/jit/targetloongarch64.h +++ b/src/coreclr/jit/targetloongarch64.h @@ -246,6 +246,9 @@ #define REG_VALIDATE_INDIRECT_CALL_ADDR REG_T3 #define REG_DISPATCH_INDIRECT_CALL_ADDR REG_T0 + #define REG_ASYNC_CONTINUATION_RET REG_A2 + #define RBM_ASYNC_CONTINUATION_RET RBM_A2 + #define REG_FPBASE REG_FP #define RBM_FPBASE RBM_FP #define STR_FPBASE "fp" diff --git a/src/coreclr/jit/targetriscv64.h b/src/coreclr/jit/targetriscv64.h index e5dcded3d878f5..ee6c6d22260c7c 100644 --- a/src/coreclr/jit/targetriscv64.h +++ b/src/coreclr/jit/targetriscv64.h @@ -222,6 +222,9 @@ #define REG_VALIDATE_INDIRECT_CALL_ADDR REG_T3 #define REG_DISPATCH_INDIRECT_CALL_ADDR REG_T0 + #define REG_ASYNC_CONTINUATION_RET REG_A2 + #define RBM_ASYNC_CONTINUATION_RET RBM_A2 + #define REG_FPBASE REG_FP #define RBM_FPBASE RBM_FP #define STR_FPBASE "fp" From 01e96b5168846329fa3a5ca760737fd3a60af6be Mon Sep 17 00:00:00 2001 From: vsadov <8218165+VSadov@users.noreply.github.com> Date: Fri, 28 Mar 2025 21:19:21 -0700 Subject: [PATCH 164/203] do not report async2 variants to reflection --- src/coreclr/vm/methodtable.cpp | 46 +++++++++++++++++++++------------- 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/src/coreclr/vm/methodtable.cpp b/src/coreclr/vm/methodtable.cpp index dd1ccdc074d547..dca10e1f1418f6 100644 --- a/src/coreclr/vm/methodtable.cpp +++ b/src/coreclr/vm/methodtable.cpp @@ -7293,7 +7293,15 @@ MethodDesc * MethodTable::IntroducedMethodIterator::GetFirst(MethodTable *pMT) { LIMITED_METHOD_CONTRACT; MethodDescChunk * pChunk = pMT->GetClass()->GetChunks(); - return (pChunk != NULL) ? pChunk->GetFirstMethodDesc() : NULL; + if (pChunk == NULL) + return NULL; + + MethodDesc* md = pChunk->GetFirstMethodDesc(); + if (!md->IsAsync2VariantMethod()) + return md; + + // skip async2 variants, the other variant will be reported instead + return GetNext(md); } //========================================================================================== @@ -7302,26 +7310,30 @@ MethodDesc * MethodTable::IntroducedMethodIterator::GetNext(MethodDesc * pMD) WRAPPER_NO_CONTRACT; MethodDescChunk * pChunk = pMD->GetMethodDescChunk(); + do + { + // Check whether the next MethodDesc is still within the bounds of the current chunk + TADDR pNext = dac_cast(pMD) + pMD->SizeOf(); + TADDR pEnd = dac_cast(pChunk) + pChunk->SizeOf(); - // Check whether the next MethodDesc is still within the bounds of the current chunk - TADDR pNext = dac_cast(pMD) + pMD->SizeOf(); - TADDR pEnd = dac_cast(pChunk) + pChunk->SizeOf(); + if (pNext < pEnd) + { + // Just skip to the next method in the same chunk + pMD = PTR_MethodDesc(pNext); + } + else + { + _ASSERTE(pNext == pEnd); - if (pNext < pEnd) - { - // Just skip to the next method in the same chunk - pMD = PTR_MethodDesc(pNext); - } - else - { - _ASSERTE(pNext == pEnd); + // We have walked all the methods in the current chunk. Move on + // to the next chunk. + pChunk = pChunk->GetNextChunk(); - // We have walked all the methods in the current chunk. Move on - // to the next chunk. - pChunk = pChunk->GetNextChunk(); + pMD = (pChunk != NULL) ? pChunk->GetFirstMethodDesc() : NULL; + } - pMD = (pChunk != NULL) ? pChunk->GetFirstMethodDesc() : NULL; - } + // skip async2 variants, the other variant will be reported instead + } while (pMD != NULL && pMD->IsAsync2VariantMethod()); return pMD; } From dba51dbfed6adb7c20759461f2860430ceca5af1 Mon Sep 17 00:00:00 2001 From: vsadov <8218165+VSadov@users.noreply.github.com> Date: Sat, 29 Mar 2025 14:11:19 -0700 Subject: [PATCH 165/203] alow ELEMENT_TYPE_INTERNAL when classifying signatures. --- src/coreclr/vm/methodtablebuilder.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/coreclr/vm/methodtablebuilder.cpp b/src/coreclr/vm/methodtablebuilder.cpp index 531e5562e515fe..32abe006a016ae 100644 --- a/src/coreclr/vm/methodtablebuilder.cpp +++ b/src/coreclr/vm/methodtablebuilder.cpp @@ -2875,6 +2875,9 @@ AsyncMethodSignatureKind ClassifyAsyncMethodSignatureCore(SigPointer sig, Module if (elemType == ELEMENT_TYPE_GENERICINST) { IfFailThrow(sig.GetElemType(&elemType)); + if (elemType == ELEMENT_TYPE_INTERNAL) + return AsyncMethodSignatureKind::NormalMethod; + *pIsValueTask = (elemType == ELEMENT_TYPE_VALUETYPE); IfFailThrow(sig.GetToken(&tk)); IfFailThrow(sig.GetData(&data)); From 45a610b0b50c1d3a6aa1cad8dd27e75b2997fae1 Mon Sep 17 00:00:00 2001 From: vsadov <8218165+VSadov@users.noreply.github.com> Date: Sat, 29 Mar 2025 14:49:34 -0700 Subject: [PATCH 166/203] handle Task-returning methods implementing/overriding non-Task-returning methods. --- src/coreclr/vm/methodtablebuilder.cpp | 38 ++++++++++++++++++++------- src/coreclr/vm/methodtablebuilder.h | 7 +---- 2 files changed, 29 insertions(+), 16 deletions(-) diff --git a/src/coreclr/vm/methodtablebuilder.cpp b/src/coreclr/vm/methodtablebuilder.cpp index 32abe006a016ae..59ad52dce4987d 100644 --- a/src/coreclr/vm/methodtablebuilder.cpp +++ b/src/coreclr/vm/methodtablebuilder.cpp @@ -5603,7 +5603,7 @@ MethodTableBuilder::InitNewMethodDesc( #endif // _DEBUG Signature sig; - if (pMethod->IsAsyncThunk()) + if (pMethod->IsAsync2Variant()) { sig = pMethod->GetMethodSignature().GetSignatureClass(); } @@ -5937,10 +5937,10 @@ MethodTableBuilder::FindDeclMethodOnInterfaceEntry(bmtInterfaceEntry *pItfEntry, } } - if (variantLookup == AsyncVariantLookup::AsyncOtherVariant) + if (variantLookup == AsyncVariantLookup::AsyncOtherVariant && !declMethod.IsNull()) { bmtRTMethod* declRTMethod = declMethod.AsRTMethod(); - bool foundOtherVariant = false; + declMethod = {}; for (; !slotIt.AtEnd(); slotIt.Next()) { bmtRTMethod* slotDeclMethod = slotIt->Decl().AsRTMethod(); @@ -5951,12 +5951,9 @@ MethodTableBuilder::FindDeclMethodOnInterfaceEntry(bmtInterfaceEntry *pItfEntry, (slotDeclMethod->GetMethodDesc()->IsAsync2VariantMethod() != declRTMethod->GetMethodDesc()->IsAsync2VariantMethod())) { declMethod = slotIt->Decl(); - foundOtherVariant = true; break; } } - - _ASSERTE(foundOtherVariant); } return declMethod; @@ -6027,7 +6024,9 @@ MethodTableBuilder::ProcessInexactMethodImpls() continue; } - AsyncVariantLookup asyncVariantOfDeclToFind = !it->IsAsyncThunk() ? AsyncVariantLookup::MatchingAsyncVariant : AsyncVariantLookup::AsyncOtherVariant; + AsyncVariantLookup asyncVariantOfDeclToFind = !it->IsAsync2Variant() ? + AsyncVariantLookup::MatchingAsyncVariant : + AsyncVariantLookup::AsyncOtherVariant; // If this method serves as the BODY of a MethodImpl specification, then // we should iterate all the MethodImpl's for this class and see just how many @@ -6170,7 +6169,9 @@ MethodTableBuilder::ProcessMethodImpls() continue; } - AsyncVariantLookup asyncVariantOfDeclToFind = !it->IsAsyncThunk() ? AsyncVariantLookup::MatchingAsyncVariant : AsyncVariantLookup::AsyncOtherVariant; + AsyncVariantLookup asyncVariantOfDeclToFind = !it->IsAsync2Variant() ? + AsyncVariantLookup::MatchingAsyncVariant : + AsyncVariantLookup::AsyncOtherVariant; // If this method serves as the BODY of a MethodImpl specification, then // we should iterate all the MethodImpl's for this class and see just how many @@ -6355,6 +6356,14 @@ MethodTableBuilder::ProcessMethodImpls() declMethod = FindDeclMethodOnClassInHierarchy(it, pDeclMT, declSig, asyncVariantOfDeclToFind); } + if (declMethod.IsNull() && asyncVariantOfDeclToFind == AsyncVariantLookup::AsyncOtherVariant) + { + // when implementing/overriding, we may see a Task-returning method + // which matches a T-returning method in the interface/base, which would not have variants. + // in such case the async2 variant of the Task-returning method does not implement/override anything. + continue; + } + if (declMethod.IsNull()) { // Would prefer to let this fall out to the BuildMethodTableThrowException // below, but due to v2.0 and earlier behaviour throwing a MissingMethodException, @@ -6490,10 +6499,19 @@ MethodTableBuilder::bmtMethodHandle MethodTableBuilder::FindDeclMethodOnClassInH iPass == 0 ? &newVisited : NULL)) { if (variantLookup == AsyncVariantLookup::AsyncOtherVariant) - pCurMD = pCurMD->GetAsyncOtherVariant(); + { + if (pCurMD->IsTaskReturningMethod() || pCurMD->IsAsync2VariantMethod()) + { + pCurMD = pCurMD->GetAsyncOtherVariant(); + } + else + { + declMethod = {}; + break; + } + } declMethod = (*bmtParent->pSlotTable)[pCurMD->GetSlot()].Decl(); - break; } } diff --git a/src/coreclr/vm/methodtablebuilder.h b/src/coreclr/vm/methodtablebuilder.h index 01899ef8152d25..26e08a5d01bcf9 100644 --- a/src/coreclr/vm/methodtablebuilder.h +++ b/src/coreclr/vm/methodtablebuilder.h @@ -1060,17 +1060,12 @@ class MethodTableBuilder GetRVA() const { LIMITED_METHOD_CONTRACT; return m_dwRVA; } - bool IsAsyncThunk() const + bool IsAsync2Variant() const { return GetAsyncMethodKind() == AsyncMethodKind::Async2VariantThunk || GetAsyncMethodKind() == AsyncMethodKind::Async2VariantImpl; } - bool IsAsync2() const - { - return IsAsyncThunk(); - } - void SetAsyncMethodKind(AsyncMethodKind kind) { m_asyncMethodKind = kind; From cdf37d0db7b86de82d1ce23122d6f6a3962b26f6 Mon Sep 17 00:00:00 2001 From: vsadov <8218165+VSadov@users.noreply.github.com> Date: Sat, 29 Mar 2025 19:44:09 -0700 Subject: [PATCH 167/203] infinite recursion in TryResolveVirtualStaticMethodOnThisType --- src/coreclr/vm/methodtable.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/coreclr/vm/methodtable.cpp b/src/coreclr/vm/methodtable.cpp index dca10e1f1418f6..5d303f07db97f8 100644 --- a/src/coreclr/vm/methodtable.cpp +++ b/src/coreclr/vm/methodtable.cpp @@ -8227,6 +8227,8 @@ MethodTable::TryResolveVirtualStaticMethodOnThisType(MethodTable* pInterfaceType { differsByAsyncVariant = true; pMethodDecl = pMethodDecl->GetAsyncOtherVariant(); + if (verifyImplemented) + return pMethodDecl; } else { From aae997e4d368df250a066deb60ef5b4650cf53b6 Mon Sep 17 00:00:00 2001 From: vsadov <8218165+VSadov@users.noreply.github.com> Date: Sun, 30 Mar 2025 14:05:18 -0700 Subject: [PATCH 168/203] fix for EnC --- src/coreclr/debug/ee/functioninfo.cpp | 2 +- src/coreclr/vm/encee.cpp | 2 +- src/coreclr/vm/methoditer.cpp | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/coreclr/debug/ee/functioninfo.cpp b/src/coreclr/debug/ee/functioninfo.cpp index b28620ede25439..c7613dfd5d55c2 100644 --- a/src/coreclr/debug/ee/functioninfo.cpp +++ b/src/coreclr/debug/ee/functioninfo.cpp @@ -2022,7 +2022,7 @@ void DebuggerMethodInfo::CreateDJIsForNativeBlobs(AppDomain * pAppDomain, Method // have DJIs for every verision of a method that was EnCed. // This also handles the possibility of getting the same methoddesc back from the iterator. // It also lets EnC + generics play nice together (including if an generic method was EnC-ed) - LoadedMethodDescIterator it(pAppDomain, m_module, m_token, /* fIsAsync2Variant */false); // TODO! Debugger doesn't handle async2 variants now + LoadedMethodDescIterator it(pAppDomain, m_module, m_token, /* fIsAsync2Variant */false); // TODO: EnC doesn't handle async2 variants now CollectibleAssemblyHolder pAssembly; while (it.Next(pAssembly.This())) { diff --git a/src/coreclr/vm/encee.cpp b/src/coreclr/vm/encee.cpp index 9b2d7c6f6d9818..5b1d2ee0fa601b 100644 --- a/src/coreclr/vm/encee.cpp +++ b/src/coreclr/vm/encee.cpp @@ -342,7 +342,7 @@ HRESULT EditAndContinueModule::UpdateMethod(MethodDesc *pMethod) AppDomain::GetCurrentDomain(), module, tkMethod, - pMethod->IsAsync2VariantMethod(), + /* fIsAsync2Variant */false, // TODO: EnC doesn't handle async2 variants now AssemblyIterationFlags(kIncludeLoaded | kIncludeExecution)); CollectibleAssemblyHolder pAssembly; while (it.Next(pAssembly.This())) diff --git a/src/coreclr/vm/methoditer.cpp b/src/coreclr/vm/methoditer.cpp index 681cdb9e73af15..79af7b137359e1 100644 --- a/src/coreclr/vm/methoditer.cpp +++ b/src/coreclr/vm/methoditer.cpp @@ -57,7 +57,7 @@ BOOL LoadedMethodDescIterator::Next( return FALSE; } - if (m_fIsAsync2Variant) + if (m_fIsAsync2Variant && m_mainMD->IsTaskReturningMethod()) { m_mainMD = m_mainMD->GetMethodTable()->GetParallelMethodDesc(m_mainMD, AsyncVariantLookup::AsyncOtherVariant); @@ -250,7 +250,7 @@ LoadedMethodDescIterator::Start( mdMethodDef md, MethodDesc *pMethodDesc) { - Start(pAppDomain, pModule, md, pMethodDesc->IsAsync2VariantMethod()); + Start(pAppDomain, pModule, md, /*m_fIsAsync2Variant*/ false); // TODO: EnC doesn't handle async2 variants now m_mainMD = pMethodDesc; } From 43cdf7d6624ac22ed05ee6dff23748742703cd1c Mon Sep 17 00:00:00 2001 From: vsadov <8218165+VSadov@users.noreply.github.com> Date: Sun, 30 Mar 2025 16:22:09 -0700 Subject: [PATCH 169/203] Another workaround for reflection (need a better solution though) --- .../src/System/RuntimeType.CoreCLR.cs | 25 +++++++++++++------ src/coreclr/vm/methodtablebuilder.cpp | 2 ++ 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs index 5e872581a02f60..5aad763334d045 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs @@ -552,7 +552,12 @@ private void MergeWithGlobalList(T[] list) // Grow the list by exactly one element in this case to avoid null entries at the end. // - Debug.Assert(false); + // TODO: runtime-async we need to rationalize how async2 thunks eork in reflection. + // possibly they should not be exposed as they are runtime-provided implementation + // details, that in theory may change. + // + // For now I will disable this assert as we may get here with extra methods. + // Debug.Assert(false); newSize = cachedMembers.Length + 1; } @@ -604,9 +609,9 @@ private unsafe RuntimeMethodInfo[] PopulateMethods(Filter filter) MethodAttributes methodAttributes = RuntimeMethodHandle.GetAttributes(methodHandle); #region Continue if this is a constructor - Debug.Assert( - (RuntimeMethodHandle.GetAttributes(methodHandle) & MethodAttributes.RTSpecialName) == 0 || - RuntimeMethodHandle.GetName(methodHandle).Equals(".cctor")); + //Debug.Assert( + // (RuntimeMethodHandle.GetAttributes(methodHandle) & MethodAttributes.RTSpecialName) == 0 || + // RuntimeMethodHandle.GetName(methodHandle).Equals(".cctor")); if ((methodAttributes & MethodAttributes.RTSpecialName) != 0) continue; @@ -663,10 +668,10 @@ private unsafe RuntimeMethodInfo[] PopulateMethods(Filter filter) MethodAttributes methodAccess = methodAttributes & MethodAttributes.MemberAccessMask; #region Continue if this is a constructor - Debug.Assert( - (RuntimeMethodHandle.GetAttributes(methodHandle) & MethodAttributes.RTSpecialName) == 0 || - RuntimeMethodHandle.GetName(methodHandle).Equals(".ctor") || - RuntimeMethodHandle.GetName(methodHandle).Equals(".cctor")); + //Debug.Assert( + // (RuntimeMethodHandle.GetAttributes(methodHandle) & MethodAttributes.RTSpecialName) == 0 || + // RuntimeMethodHandle.GetName(methodHandle).Equals(".ctor") || + // RuntimeMethodHandle.GetName(methodHandle).Equals(".cctor")); if ((methodAttributes & MethodAttributes.RTSpecialName) != 0) continue; @@ -766,6 +771,10 @@ private RuntimeConstructorInfo[] PopulateConstructors(Filter filter) if ((methodAttributes & MethodAttributes.RTSpecialName) == 0) continue; + if (!RuntimeMethodHandle.GetName(methodHandle).Equals(".ctor") && + !RuntimeMethodHandle.GetName(methodHandle).Equals(".cctor")) + continue; + // Constructors should not be virtual or abstract Debug.Assert( (methodAttributes & MethodAttributes.Abstract) == 0 && diff --git a/src/coreclr/vm/methodtablebuilder.cpp b/src/coreclr/vm/methodtablebuilder.cpp index 59ad52dce4987d..9635ca0ec1ef78 100644 --- a/src/coreclr/vm/methodtablebuilder.cpp +++ b/src/coreclr/vm/methodtablebuilder.cpp @@ -3719,6 +3719,8 @@ MethodTableBuilder::EnumerateClassMethods() type, implType); + _ASSERTE(pNewMethod->IsAsync2Variant()); + pNewMethod->SetAsyncOtherVariant(pDeclaredMethod); pDeclaredMethod->SetAsyncOtherVariant(pNewMethod); } From f9facc364339a67b4ac6649c051e6d2e8207b5d5 Mon Sep 17 00:00:00 2001 From: vsadov <8218165+VSadov@users.noreply.github.com> Date: Sun, 30 Mar 2025 23:41:48 -0700 Subject: [PATCH 170/203] more build fixes for RISCV --- src/coreclr/jit/codegenloongarch64.cpp | 40 +++++++++++++++--------- src/coreclr/jit/codegenriscv64.cpp | 42 ++++++++++++++++---------- 2 files changed, 51 insertions(+), 31 deletions(-) diff --git a/src/coreclr/jit/codegenloongarch64.cpp b/src/coreclr/jit/codegenloongarch64.cpp index d660a4ae63e634..e696a3e340d03e 100644 --- a/src/coreclr/jit/codegenloongarch64.cpp +++ b/src/coreclr/jit/codegenloongarch64.cpp @@ -827,6 +827,7 @@ void CodeGen::genFnEpilog(BasicBlock* block) 0, // argSize EA_UNKNOWN // retSize MULTIREG_HAS_SECOND_GC_RET_ONLY_ARG(EA_UNKNOWN), // secondRetSize + false, // hasAsyncRet gcInfo.gcVarPtrSetCur, gcInfo.gcRegGCrefSetCur, gcInfo.gcRegByrefSetCur, @@ -2891,11 +2892,13 @@ void CodeGen::genCodeForReturnTrap(GenTreeOp* tree) // TODO-LOONGARCH64: can optimize further !!! GetEmitter()->emitIns_Call(callType, compiler->eeFindHelper(CORINFO_HELP_STOP_FOR_GC), - INDEBUG_LDISASM_COMMA(nullptr) addr, 0, EA_UNKNOWN, EA_UNKNOWN, gcInfo.gcVarPtrSetCur, - gcInfo.gcRegGCrefSetCur, gcInfo.gcRegByrefSetCur, DebugInfo(), /* IL offset */ - callTarget, /* ireg */ - REG_NA, 0, 0, /* xreg, xmul, disp */ - false /* isJump */ + INDEBUG_LDISASM_COMMA(nullptr) addr, 0, EA_UNKNOWN, EA_UNKNOWN, + false, // hasAsyncRet + gcInfo.gcVarPtrSetCur, gcInfo.gcRegGCrefSetCur, gcInfo.gcRegByrefSetCur, + DebugInfo(), /* IL offset */ + callTarget, /* ireg */ + REG_NA, 0, 0, /* xreg, xmul, disp */ + false /* isJump */ ); regMaskTP killMask = compiler->compHelperCallKillSet(CORINFO_HELP_STOP_FOR_GC); @@ -3982,11 +3985,13 @@ void CodeGen::genEmitHelperCall(unsigned helper, int argSize, emitAttr retSize, } GetEmitter()->emitIns_Call(callType, compiler->eeFindHelper(helper), INDEBUG_LDISASM_COMMA(nullptr) addr, argSize, - retSize, EA_UNKNOWN, gcInfo.gcVarPtrSetCur, gcInfo.gcRegGCrefSetCur, - gcInfo.gcRegByrefSetCur, DebugInfo(), /* IL offset */ - callTarget, /* ireg */ - REG_NA, 0, 0, /* xreg, xmul, disp */ - false /* isJump */ + retSize, EA_UNKNOWN, + false, // hasAsyncRet + gcInfo.gcVarPtrSetCur, gcInfo.gcRegGCrefSetCur, gcInfo.gcRegByrefSetCur, + DebugInfo(), /* IL offset */ + callTarget, /* ireg */ + REG_NA, 0, 0, /* xreg, xmul, disp */ + false /* isJump */ ); regMaskTP killMask = compiler->compHelperCallKillSet((CorInfoHelpFunc)helper); @@ -6117,6 +6122,7 @@ void CodeGen::genCallInstruction(GenTreeCall* call) nullptr, // addr retSize MULTIREG_HAS_SECOND_GC_RET_ONLY_ARG(secondRetSize), + false, // hasAsyncRet di, target->GetRegNum(), call->IsFastTailCall()); @@ -6165,6 +6171,7 @@ void CodeGen::genCallInstruction(GenTreeCall* call) nullptr, // addr retSize MULTIREG_HAS_SECOND_GC_RET_ONLY_ARG(secondRetSize), + false, // hasAsyncRet di, targetAddrReg, call->IsFastTailCall()); @@ -6208,6 +6215,7 @@ void CodeGen::genCallInstruction(GenTreeCall* call) addr, retSize MULTIREG_HAS_SECOND_GC_RET_ONLY_ARG(secondRetSize), + false, // hasAsyncRet di, REG_NA, call->IsFastTailCall()); @@ -6972,11 +6980,13 @@ inline void CodeGen::genJumpToThrowHlpBlk_la( BasicBlock* skipLabel = genCreateTempLabel(); emit->emitIns_Call(callType, compiler->eeFindHelper(compiler->acdHelper(codeKind)), - INDEBUG_LDISASM_COMMA(nullptr) addr, 0, EA_UNKNOWN, EA_UNKNOWN, gcInfo.gcVarPtrSetCur, - gcInfo.gcRegGCrefSetCur, gcInfo.gcRegByrefSetCur, DebugInfo(), /* IL offset */ - callTarget, /* ireg */ - REG_NA, 0, 0, /* xreg, xmul, disp */ - false /* isJump */ + INDEBUG_LDISASM_COMMA(nullptr) addr, 0, EA_UNKNOWN, EA_UNKNOWN, + false, // hasAsyncRet + gcInfo.gcVarPtrSetCur, gcInfo.gcRegGCrefSetCur, gcInfo.gcRegByrefSetCur, + DebugInfo(), /* IL offset */ + callTarget, /* ireg */ + REG_NA, 0, 0, /* xreg, xmul, disp */ + false /* isJump */ ); regMaskTP killMask = compiler->compHelperCallKillSet((CorInfoHelpFunc)(compiler->acdHelper(codeKind))); diff --git a/src/coreclr/jit/codegenriscv64.cpp b/src/coreclr/jit/codegenriscv64.cpp index db7abe9cf5e08a..9cf6746c6c0336 100644 --- a/src/coreclr/jit/codegenriscv64.cpp +++ b/src/coreclr/jit/codegenriscv64.cpp @@ -779,6 +779,7 @@ void CodeGen::genFnEpilog(BasicBlock* block) 0, // argSize EA_UNKNOWN // retSize MULTIREG_HAS_SECOND_GC_RET_ONLY_ARG(EA_UNKNOWN), // secondRetSize + false, // hasAsyncRet gcInfo.gcVarPtrSetCur, gcInfo.gcRegGCrefSetCur, gcInfo.gcRegByrefSetCur, @@ -2823,11 +2824,13 @@ void CodeGen::genCodeForReturnTrap(GenTreeOp* tree) // TODO-RISCV64: can optimize further !!! GetEmitter()->emitIns_Call(callType, compiler->eeFindHelper(CORINFO_HELP_STOP_FOR_GC), - INDEBUG_LDISASM_COMMA(nullptr) addr, 0, EA_UNKNOWN, EA_UNKNOWN, gcInfo.gcVarPtrSetCur, - gcInfo.gcRegGCrefSetCur, gcInfo.gcRegByrefSetCur, DebugInfo(), /* IL offset */ - callTarget, /* ireg */ - REG_NA, 0, 0, /* xreg, xmul, disp */ - false /* isJump */ + INDEBUG_LDISASM_COMMA(nullptr) addr, 0, EA_UNKNOWN, EA_UNKNOWN, + false, // hasAsyncRet + gcInfo.gcVarPtrSetCur, gcInfo.gcRegGCrefSetCur, gcInfo.gcRegByrefSetCur, + DebugInfo(), /* IL offset */ + callTarget, /* ireg */ + REG_NA, 0, 0, /* xreg, xmul, disp */ + false /* isJump */ ); regMaskTP killMask = compiler->compHelperCallKillSet(CORINFO_HELP_STOP_FOR_GC); @@ -3844,11 +3847,13 @@ void CodeGen::genEmitHelperCall(unsigned helper, int argSize, emitAttr retSize, } GetEmitter()->emitIns_Call(callType, compiler->eeFindHelper(helper), INDEBUG_LDISASM_COMMA(nullptr) addr, argSize, - retSize, EA_UNKNOWN, gcInfo.gcVarPtrSetCur, gcInfo.gcRegGCrefSetCur, - gcInfo.gcRegByrefSetCur, DebugInfo(), /* IL offset */ - callTarget, /* ireg */ - REG_NA, 0, 0, /* xreg, xmul, disp */ - false /* isJump */ + retSize, EA_UNKNOWN, + false, // hasAsyncRet + gcInfo.gcVarPtrSetCur, gcInfo.gcRegGCrefSetCur, gcInfo.gcRegByrefSetCur, + DebugInfo(), /* IL offset */ + callTarget, /* ireg */ + REG_NA, 0, 0, /* xreg, xmul, disp */ + false /* isJump */ ); regMaskTP killMask = compiler->compHelperCallKillSet((CorInfoHelpFunc)helper); @@ -6095,6 +6100,7 @@ void CodeGen::genCallInstruction(GenTreeCall* call) nullptr, // addr retSize MULTIREG_HAS_SECOND_GC_RET_ONLY_ARG(secondRetSize), + false, // hasAsyncRet di, target->GetRegNum(), call->IsFastTailCall()); @@ -6143,8 +6149,9 @@ void CodeGen::genCallInstruction(GenTreeCall* call) nullptr, // addr retSize MULTIREG_HAS_SECOND_GC_RET_ONLY_ARG(secondRetSize), + false, // hasAsyncRet di, - targetAddrReg, +\ targetAddrReg, call->IsFastTailCall()); // clang-format on } @@ -6186,6 +6193,7 @@ void CodeGen::genCallInstruction(GenTreeCall* call) addr, retSize MULTIREG_HAS_SECOND_GC_RET_ONLY_ARG(secondRetSize), + false, // hasAsyncRet di, REG_NA, call->IsFastTailCall()); @@ -6772,11 +6780,13 @@ void CodeGen::genJumpToThrowHlpBlk_la( BasicBlock* skipLabel = genCreateTempLabel(); emit->emitIns_Call(callType, compiler->eeFindHelper(compiler->acdHelper(codeKind)), - INDEBUG_LDISASM_COMMA(nullptr) addr, 0, EA_UNKNOWN, EA_UNKNOWN, gcInfo.gcVarPtrSetCur, - gcInfo.gcRegGCrefSetCur, gcInfo.gcRegByrefSetCur, DebugInfo(), /* IL offset */ - callTarget, /* ireg */ - REG_NA, 0, 0, /* xreg, xmul, disp */ - false /* isJump */ + INDEBUG_LDISASM_COMMA(nullptr) addr, 0, EA_UNKNOWN, EA_UNKNOWN, + false, // hasAsyncRet + gcInfo.gcVarPtrSetCur, gcInfo.gcRegGCrefSetCur, gcInfo.gcRegByrefSetCur, + DebugInfo(), /* IL offset */ + callTarget, /* ireg */ + REG_NA, 0, 0, /* xreg, xmul, disp */ + false /* isJump */ ); regMaskTP killMask = compiler->compHelperCallKillSet((CorInfoHelpFunc)(compiler->acdHelper(codeKind))); From d105671711ac28d49f3d99cc7c88ac6d519c6123 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Mon, 31 Mar 2025 15:04:57 +0200 Subject: [PATCH 171/203] Restore CODEOWNERS --- .github/CODEOWNERS | 115 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 .github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 00000000000000..34e18599e5beaf --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,115 @@ +# Users referenced in this file will automatically be requested as reviewers for PRs that modify the given paths. +# See https://help.github.com/articles/about-code-owners/ + +/src/libraries/Common/src/Interop/ @dotnet/platform-deps-team +/src/libraries/Common/src/System/Net/Http/aspnetcore/ @dotnet/http +/src/libraries/Common/tests/Tests/System/Net/aspnetcore/ @dotnet/http + +# CoreCLR Code Owners + +/src/coreclr/inc/corinfo.h @dotnet/jit-contrib +/src/coreclr/inc/corjit.h @dotnet/jit-contrib +/src/coreclr/jit/ @dotnet/jit-contrib +/src/coreclr/nativeaot @MichalStrehovsky +/src/coreclr/tools/Common @dotnet/crossgen-contrib @MichalStrehovsky +/src/coreclr/tools/aot @dotnet/crossgen-contrib +/src/coreclr/tools/aot/ILCompiler.Compiler @MichalStrehovsky +/src/coreclr/tools/aot/ILCompiler.RyuJit @MichalStrehovsky +/src/coreclr/tools/aot/ILCompiler.MetadataTransform @MichalStrehovsky + +# Mono Code Owners + +/src/mono @steveisok @vitek-karas + +/src/mono/llvm @steveisok @vitek-karas + +/src/mono/mono/arch @steveisok @vitek-karas +/src/mono/mono/eglib @steveisok @vitek-karas + +/src/mono/mono/metadata @thaystg @steveisok @vitek-karas +/src/mono/mono/metadata/*-win* @lateralusX @steveisok +/src/mono/mono/metadata/handle* @steveisok @vitek-karas +/src/mono/mono/metadata/monitor* @brzvlad @steveisok @vitek-karas +/src/mono/mono/metadata/sgen* @brzvlad @steveisok @vitek-karas +/src/mono/mono/metadata/thread* @lateralusX @steveisok @vitek-karas +/src/mono/mono/metadata/w32* @lateralusX @steveisok @vitek-karas + +/src/mono/mono/eventpipe @lateralusX @steveisok @vitek-karas + +/src/mono/mono/mini @steveisok @vitek-karas +/src/mono/mono/mini/*cfgdump* @steveisok @vitek-karas +/src/mono/mono/mini/*exceptions* @BrzVlad @steveisok @vitek-karas +/src/mono/mono/mini/*llvm* @steveisok @vitek-karas +/src/mono/mono/mini/*ppc* @steveisok @vitek-karas +/src/mono/mono/mini/*profiler* @BrzVlad @steveisok @vitek-karas +/src/mono/mono/mini/*riscv* @steveisok @vitek-karas +/src/mono/mono/mini/*type-check* @steveisok @vitek-karas +/src/mono/mono/mini/debugger-agent.c @thaystg @steveisok @vitek-karas +/src/mono/mono/mini/interp/* @BrzVlad @kotlarmilos @steveisok @vitek-karas +/src/mono/mono/mini/interp/*jiterp* @kg @steveisok @vitek-karas +/src/mono/mono/mini/*simd* @steveisok @vitek-karas + +/src/mono/mono/profiler @BrzVlad @steveisok @vitek-karas +/src/mono/mono/sgen @BrzVlad @steveisok @vitek-karas + +/src/mono/mono/utils @steveisok @vitek-karas +/src/mono/mono/utils/*-win* @lateralusX @steveisok @vitek-karas +/src/mono/mono/utils/atomic* @steveisok @vitek-karas +/src/mono/mono/utils/mono-hwcap* @steveisok @vitek-karas +/src/mono/mono/utils/mono-mem* @steveisok @vitek-karas +/src/mono/mono/utils/mono-threads* @steveisok @vitek-karas + +/src/mono/dlls @thaystg @steveisok @vitek-karas + +/src/native/public/mono @steveisok @vitek-karas +/src/native/external/libunwind @janvorli @AaronRobinsonMSFT @dotnet/dotnet-diag +/src/native/external/libunwind_extras @janvorli @AaronRobinsonMSFT @dotnet/dotnet-diag + +/src/libraries/sendtohelix-browser.targets @akoeplinger +/src/libraries/sendtohelix-wasm.targets @akoeplinger +/src/libraries/sendtohelix-wasi.targets @akoeplinger +/src/mono/browser @lewing @pavelsavara +/src/mono/wasi @lewing @pavelsavara +/src/mono/wasm @lewing @pavelsavara +/src/mono/browser/debugger @thaystg @ilonatommy +/src/mono/wasm/build @maraf @akoeplinger +/src/mono/wasi/build @maraf @akoeplinger +/src/mono/browser/build @maraf @akoeplinger +/src/mono/sample/wasm @lewing @pavelsavara +/src/mono/sample/wasi @lewing @pavelsavara +/src/libraries/System.Runtime.InteropServices.JavaScript @lewing @pavelsavara + +/src/mono/nuget/*WebAssembly*/ @lewing @akoeplinger +/src/mono/nuget/*MonoTargets*/ @lewing @akoeplinger +/src/mono/nuget/*BrowserDebugHost*/ @lewing @akoeplinger +/src/mono/nuget/*Workload.Mono.Toolchain*/ @lewing @akoeplinger +/src/mono/nuget/*MonoAOTCompiler*/ @lewing @akoeplinger + +/src/mono/wasm/Wasm* @maraf @ilonatommy +/src/mono/wasm/testassets @maraf @ilonatommy +/src/mono/wasi/testassets @maraf @ilonatommy +/src/tasks/WasmAppBuilder/ @maraf @akoeplinger +/src/tasks/WorkloadBuildTasks/ @akoeplinger +/src/tasks/AotCompilerTask/ @akoeplinger +/src/tasks/WasmBuildTasks/ @maraf @akoeplinger + +/eng/pipelines/**/*wasm* @akoeplinger + +# ILLink codeowners +/src/tools/illink/ @marek-safar +/src/tools/illink/src/analyzer/ @radekdoulik +/src/tools/illink/src/ILLink.Tasks/ @sbomer +/src/tools/illink/src/ILLink.RoslynAnalyzer/ @sbomer +/src/tools/illink/src/linker/ @marek-safar @mrvoorhe +/src/tools/illink/test/ @marek-safar @mrvoorhe + +# Obsoletions / Custom Diagnostics + +/docs/project/list-of-diagnostics.md @jeffhandley +/src/libraries/Common/src/System/Obsoletions.cs @jeffhandley + +# Area ownership and repo automation +/docs/area-owners.* @jeffhandley +/docs/issue*.md @jeffhandley +/.github/policies/ @jeffhandley @mkArtakMSFT +/.github/workflows/ @jeffhandley @dotnet/runtime-infrastructure From b8450d93cd545f8c96eeaebcda7e7a9639605097 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Mon, 31 Mar 2025 15:07:15 +0200 Subject: [PATCH 172/203] Add license headers for new files --- src/coreclr/jit/async.cpp | 3 +++ src/coreclr/jit/async.h | 3 +++ 2 files changed, 6 insertions(+) diff --git a/src/coreclr/jit/async.cpp b/src/coreclr/jit/async.cpp index 8d69a2986e505f..798f59a0ea1855 100644 --- a/src/coreclr/jit/async.cpp +++ b/src/coreclr/jit/async.cpp @@ -1,3 +1,6 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + #include "jitpch.h" #include "jitstd/algorithm.h" #include "async.h" diff --git a/src/coreclr/jit/async.h b/src/coreclr/jit/async.h index 6f572c0dbf37ab..4ea0b6b805f8bf 100644 --- a/src/coreclr/jit/async.h +++ b/src/coreclr/jit/async.h @@ -1,3 +1,6 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + class Async2Transformation { friend class AsyncLiveness; From d796485f17b80154d1936ae5d9ddfe5fd6fe5ff3 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Mon, 31 Mar 2025 15:07:34 +0200 Subject: [PATCH 173/203] Delete unused file --- .../vm/restoreregs_for_runtimesuspension.h | 22 ------------------- 1 file changed, 22 deletions(-) delete mode 100644 src/coreclr/vm/restoreregs_for_runtimesuspension.h diff --git a/src/coreclr/vm/restoreregs_for_runtimesuspension.h b/src/coreclr/vm/restoreregs_for_runtimesuspension.h deleted file mode 100644 index 689be7bf3fbf0e..00000000000000 --- a/src/coreclr/vm/restoreregs_for_runtimesuspension.h +++ /dev/null @@ -1,22 +0,0 @@ -#if defined(TARGET_AMD64) - DISCOVER_RESTORED_REG(Rbx) - DISCOVER_RESTORED_REG(Rbp) - DISCOVER_RESTORED_REG(Rdi) - DISCOVER_RESTORED_REG(Rsi) - DISCOVER_RESTORED_REG(R12) - DISCOVER_RESTORED_REG(R13) - DISCOVER_RESTORED_REG(R14) - DISCOVER_RESTORED_REG(R15) - DISCOVER_RESTORED_REG(Xmm6) - DISCOVER_RESTORED_REG(Xmm7) - DISCOVER_RESTORED_REG(Xmm8) - DISCOVER_RESTORED_REG(Xmm9) - DISCOVER_RESTORED_REG(Xmm10) - DISCOVER_RESTORED_REG(Xmm11) - DISCOVER_RESTORED_REG(Xmm12) - DISCOVER_RESTORED_REG(Xmm13) - DISCOVER_RESTORED_REG(Xmm14) - DISCOVER_RESTORED_REG(Xmm15) -#else - PORTABILITY_ASSERT(); -#endif From 66c8239a861e1afe4f12d431ecccf0dfe578e83b Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Mon, 31 Mar 2025 15:10:09 +0200 Subject: [PATCH 174/203] Undo whitespace change --- src/coreclr/vm/codeman.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/coreclr/vm/codeman.cpp b/src/coreclr/vm/codeman.cpp index 21cdf2bf1764e7..0be5ceff63d423 100644 --- a/src/coreclr/vm/codeman.cpp +++ b/src/coreclr/vm/codeman.cpp @@ -4286,7 +4286,6 @@ BOOL EECodeGenManager::JitCodeToMethodInfoWorker( if (ppMethodDesc) { - *ppMethodDesc = pCHdr->GetMethodDesc(); } return TRUE; From 3190a605c252a61d64d2bbf085a62fc006ee0de0 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Mon, 31 Mar 2025 15:14:18 +0200 Subject: [PATCH 175/203] Remove new unused code slot methods --- src/coreclr/vm/codeversion.cpp | 19 ------------------- src/coreclr/vm/codeversion.h | 2 -- src/coreclr/vm/method.hpp | 1 - src/coreclr/vm/prestub.cpp | 6 ------ 4 files changed, 28 deletions(-) diff --git a/src/coreclr/vm/codeversion.cpp b/src/coreclr/vm/codeversion.cpp index a2ff35497459cd..f67f3a4b72066d 100644 --- a/src/coreclr/vm/codeversion.cpp +++ b/src/coreclr/vm/codeversion.cpp @@ -85,12 +85,6 @@ PCODE NativeCodeVersionNode::GetNativeCode() const return m_pNativeCode; } -PTR_PCODE NativeCodeVersionNode::GetNativeCodeSlot() const -{ - LIMITED_METHOD_DAC_CONTRACT; - return dac_cast(&m_pNativeCode); -} - ReJITID NativeCodeVersionNode::GetILVersionId() const { LIMITED_METHOD_DAC_CONTRACT; @@ -227,19 +221,6 @@ PCODE NativeCodeVersion::GetNativeCode() const } } -PTR_PCODE NativeCodeVersion::GetNativeCodeSlot() const -{ - LIMITED_METHOD_DAC_CONTRACT; - if (m_storageKind == StorageKind::Explicit) - { - return AsNode()->GetNativeCodeSlot(); - } - else - { - return GetMethodDesc()->GetAddrOfSlot(); - } -} - ReJITID NativeCodeVersion::GetILCodeVersionId() const { LIMITED_METHOD_DAC_CONTRACT; diff --git a/src/coreclr/vm/codeversion.h b/src/coreclr/vm/codeversion.h index 1c95aa0d2464f7..d4e973bdfbdcac 100644 --- a/src/coreclr/vm/codeversion.h +++ b/src/coreclr/vm/codeversion.h @@ -61,7 +61,6 @@ class NativeCodeVersion NativeCodeVersionId GetVersionId() const; BOOL IsDefaultVersion() const; PCODE GetNativeCode() const; - PTR_PCODE GetNativeCodeSlot() const; #ifdef FEATURE_CODE_VERSIONING ILCodeVersion GetILCodeVersion() const; @@ -269,7 +268,6 @@ class NativeCodeVersionNode PTR_MethodDesc GetMethodDesc() const; // Can be called without any locks NativeCodeVersionId GetVersionId() const; // Can be called without any locks PCODE GetNativeCode() const; // Can be called without any locks, but result may be stale if it wasn't already set - PTR_PCODE GetNativeCodeSlot() const; ReJITID GetILVersionId() const; // Can be called without any locks ILCodeVersion GetILCodeVersion() const;// Can be called without any locks BOOL IsActiveChildVersion() const; diff --git a/src/coreclr/vm/method.hpp b/src/coreclr/vm/method.hpp index 507d2c9e194afc..9151b35df01d5d 100644 --- a/src/coreclr/vm/method.hpp +++ b/src/coreclr/vm/method.hpp @@ -2121,7 +2121,6 @@ class PrepareCodeConfig BOOL MayUsePrecompiledCode(); virtual PCODE IsJitCancellationRequested(); virtual BOOL SetNativeCode(PCODE pCode, PCODE * ppAlternateCodeToUse); - virtual PTR_PCODE GetNativeCodeSlot(); virtual COR_ILMETHOD* GetILHeader(); virtual CORJIT_FLAGS GetJitCompilationFlags(); #ifdef FEATURE_ON_STACK_REPLACEMENT diff --git a/src/coreclr/vm/prestub.cpp b/src/coreclr/vm/prestub.cpp index 19d02e1481ef57..dd61a75bf08ffb 100644 --- a/src/coreclr/vm/prestub.cpp +++ b/src/coreclr/vm/prestub.cpp @@ -1757,12 +1757,6 @@ BOOL PrepareCodeConfig::SetNativeCode(PCODE pCode, PCODE * ppAlternateCodeToUse) return FALSE; } -PTR_PCODE PrepareCodeConfig::GetNativeCodeSlot() -{ - LIMITED_METHOD_CONTRACT; - return m_nativeCodeVersion.GetNativeCodeSlot(); -} - COR_ILMETHOD* PrepareCodeConfig::GetILHeader() { STANDARD_VM_CONTRACT; From e6078fcb4b0799b880d10c0de7aa71474562971c Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Mon, 31 Mar 2025 15:21:09 +0200 Subject: [PATCH 176/203] Remove new unused EmitUNBOX method --- src/coreclr/vm/stubgen.cpp | 8 +------- src/coreclr/vm/stubgen.h | 1 - 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/src/coreclr/vm/stubgen.cpp b/src/coreclr/vm/stubgen.cpp index 6a29c1962e92ba..401b5b9baa2097 100644 --- a/src/coreclr/vm/stubgen.cpp +++ b/src/coreclr/vm/stubgen.cpp @@ -174,7 +174,7 @@ void ILCodeStream::Emit(ILInstrEnum instr, INT16 iStackDelta, UINT_PTR uArg) pInstrBuffer[idxCurInstr].iStackDelta = iStackDelta; pInstrBuffer[idxCurInstr].uArg = uArg; - if(m_buildingEHClauses.GetCount() > 0) + if (m_buildingEHClauses.GetCount() > 0) { ILStubEHClauseBuilder& clause = m_buildingEHClauses[m_buildingEHClauses.GetCount() - 1]; @@ -1832,12 +1832,6 @@ void ILCodeStream::EmitUNALIGNED(BYTE alignment) Emit(CEE_UNALIGNED, 0, alignment); } -void ILCodeStream::EmitUNBOX(int token) -{ - WRAPPER_NO_CONTRACT; - Emit(CEE_UNBOX, 0, token); -} - void ILCodeStream::EmitUNBOX_ANY(int token) { WRAPPER_NO_CONTRACT; diff --git a/src/coreclr/vm/stubgen.h b/src/coreclr/vm/stubgen.h index c848b0665008fa..b3590071386cb3 100644 --- a/src/coreclr/vm/stubgen.h +++ b/src/coreclr/vm/stubgen.h @@ -943,7 +943,6 @@ class ILCodeStream void EmitSUB (); void EmitTHROW (); void EmitUNALIGNED (BYTE alignment); - void EmitUNBOX (int token); void EmitUNBOX_ANY (int token); // Overloads to simplify common usage patterns From 479d4f404d999afffefb844056815fcfdab32689 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Mon, 31 Mar 2025 15:21:32 +0200 Subject: [PATCH 177/203] Revert a logging change --- src/coreclr/vm/stubgen.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/coreclr/vm/stubgen.cpp b/src/coreclr/vm/stubgen.cpp index 401b5b9baa2097..18eed6274dd2db 100644 --- a/src/coreclr/vm/stubgen.cpp +++ b/src/coreclr/vm/stubgen.cpp @@ -834,10 +834,10 @@ size_t ILStubLinker::Link(UINT* puMaxStack) } #ifdef _DEBUG - if (fStackUnderflow || this->GetNumEHClauses() != 0) + if (fStackUnderflow) { LogILStub(CORJIT_FLAGS()); -// CONSISTENCY_CHECK_MSG(false, "IL stack underflow! -- see logging output"); + CONSISTENCY_CHECK_MSG(false, "IL stack underflow! -- see logging output"); } #endif // _DEBUG From 33d026d175d8f8402bebaa08148913c096032a95 Mon Sep 17 00:00:00 2001 From: Vladimir Sadov Date: Mon, 31 Mar 2025 09:29:38 -0700 Subject: [PATCH 178/203] Update src/coreclr/jit/codegenriscv64.cpp Co-authored-by: Adeel Mujahid <3840695+am11@users.noreply.github.com> --- src/coreclr/jit/codegenriscv64.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/jit/codegenriscv64.cpp b/src/coreclr/jit/codegenriscv64.cpp index 9cf6746c6c0336..da6c9e06ecd1f7 100644 --- a/src/coreclr/jit/codegenriscv64.cpp +++ b/src/coreclr/jit/codegenriscv64.cpp @@ -6151,7 +6151,7 @@ void CodeGen::genCallInstruction(GenTreeCall* call) MULTIREG_HAS_SECOND_GC_RET_ONLY_ARG(secondRetSize), false, // hasAsyncRet di, -\ targetAddrReg, + targetAddrReg, call->IsFastTailCall()); // clang-format on } From 21981a989a7db37524b224d942f182b65b30f337 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Mon, 31 Mar 2025 15:36:38 +0200 Subject: [PATCH 179/203] Remove unused IsInCurrentFrame --- src/coreclr/inc/regdisp.h | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/coreclr/inc/regdisp.h b/src/coreclr/inc/regdisp.h index a8ffd5909f78a6..8f1f2a736be256 100644 --- a/src/coreclr/inc/regdisp.h +++ b/src/coreclr/inc/regdisp.h @@ -271,13 +271,6 @@ inline BOOL IsInCalleesFrames(REGDISPLAY *display, LPVOID stackPointer) return stackPointer < ((LPVOID)(display->SP)); } -inline BOOL IsInCurrentFrame(REGDISPLAY *display, LPVOID stackPointer) -{ - LIMITED_METHOD_CONTRACT; - _ASSERTE(display->IsCallerContextValid); - return stackPointer < ((LPVOID)(::GetSP(display->pCallerContext))); -} - inline TADDR GetRegdisplayStackMark(REGDISPLAY *display) { #if defined(TARGET_AMD64) From 5299ec70144e038929333cbf5fbe53155a0b98f6 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Mon, 31 Mar 2025 15:38:57 +0200 Subject: [PATCH 180/203] Remove unused new CLASSID_SYSTEM_BYTE --- src/coreclr/inc/corinfo.h | 1 - src/coreclr/jit/async.cpp | 2 -- src/coreclr/jit/async.h | 2 -- src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs | 3 --- src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs | 1 - src/coreclr/vm/jitinterface.cpp | 3 --- 6 files changed, 12 deletions(-) diff --git a/src/coreclr/inc/corinfo.h b/src/coreclr/inc/corinfo.h index 57e78ba06a8558..fa8fea85f360bd 100644 --- a/src/coreclr/inc/corinfo.h +++ b/src/coreclr/inc/corinfo.h @@ -863,7 +863,6 @@ enum CorInfoGCType enum CorInfoClassId { CLASSID_SYSTEM_OBJECT, - CLASSID_SYSTEM_BYTE, CLASSID_TYPED_BYREF, CLASSID_TYPE_HANDLE, CLASSID_FIELD_HANDLE, diff --git a/src/coreclr/jit/async.cpp b/src/coreclr/jit/async.cpp index 798f59a0ea1855..b5366bf176064b 100644 --- a/src/coreclr/jit/async.cpp +++ b/src/coreclr/jit/async.cpp @@ -210,8 +210,6 @@ PhaseStatus Async2Transformation::Run() m_comp->lvaGetDesc(m_newContinuationVar)->lvType = TYP_REF; m_comp->info.compCompHnd->getAsync2Info(&m_async2Info); - m_objectClsHnd = m_comp->info.compCompHnd->getBuiltinClass(CLASSID_SYSTEM_OBJECT); - m_byteClsHnd = m_comp->info.compCompHnd->getBuiltinClass(CLASSID_SYSTEM_BYTE); #ifdef JIT32_GCENCODER // Due to a hard cap on epilogs we need a shared return here. diff --git a/src/coreclr/jit/async.h b/src/coreclr/jit/async.h index 4ea0b6b805f8bf..046f9976556363 100644 --- a/src/coreclr/jit/async.h +++ b/src/coreclr/jit/async.h @@ -23,8 +23,6 @@ class Async2Transformation Compiler* m_comp; jitstd::vector m_liveLocals; CORINFO_ASYNC2_INFO m_async2Info; - CORINFO_CLASS_HANDLE m_objectClsHnd; - CORINFO_CLASS_HANDLE m_byteClsHnd; jitstd::vector m_resumptionBBs; CORINFO_METHOD_HANDLE m_resumeStub = NO_METHOD_HANDLE; CORINFO_CONST_LOOKUP m_resumeStubLookup; diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs index 4fc03f3e8346c2..495f9b85512261 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs @@ -2786,9 +2786,6 @@ private CorInfoInitClassResult initClass(CORINFO_FIELD_STRUCT_* field, CORINFO_M case CorInfoClassId.CLASSID_SYSTEM_OBJECT: return ObjectToHandle(_compilation.TypeSystemContext.GetWellKnownType(WellKnownType.Object)); - case CorInfoClassId.CLASSID_SYSTEM_BYTE: - return ObjectToHandle(_compilation.TypeSystemContext.GetWellKnownType(WellKnownType.Byte)); - case CorInfoClassId.CLASSID_TYPED_BYREF: return ObjectToHandle(_compilation.TypeSystemContext.GetWellKnownType(WellKnownType.TypedReference)); diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs b/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs index 785c90785b6165..05e6c372766c88 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs @@ -469,7 +469,6 @@ public enum CorInfoGCType public enum CorInfoClassId { CLASSID_SYSTEM_OBJECT, - CLASSID_SYSTEM_BYTE, CLASSID_TYPED_BYREF, CLASSID_TYPE_HANDLE, CLASSID_FIELD_HANDLE, diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index 696695d1a62e7a..b452018ad59d93 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -3973,9 +3973,6 @@ CORINFO_CLASS_HANDLE CEEInfo::getBuiltinClass(CorInfoClassId classId) case CLASSID_SYSTEM_OBJECT: result = CORINFO_CLASS_HANDLE(g_pObjectClass); break; - case CLASSID_SYSTEM_BYTE: - result = CORINFO_CLASS_HANDLE(CoreLibBinder::GetClass(CLASS__BYTE)); - break; case CLASSID_TYPED_BYREF: result = CORINFO_CLASS_HANDLE(g_TypedReferenceMT); break; From 688466b3c4abeda2a6429dee4e71799444c78c33 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Mon, 31 Mar 2025 17:33:20 +0200 Subject: [PATCH 181/203] Refactor and split up async2 transformation --- src/coreclr/jit/async.cpp | 1367 +++++++++++++++++++++---------------- src/coreclr/jit/async.h | 79 ++- 2 files changed, 861 insertions(+), 585 deletions(-) diff --git a/src/coreclr/jit/async.cpp b/src/coreclr/jit/async.cpp index b5366bf176064b..34731e1c42a672 100644 --- a/src/coreclr/jit/async.cpp +++ b/src/coreclr/jit/async.cpp @@ -5,14 +5,6 @@ #include "jitstd/algorithm.h" #include "async.h" -PhaseStatus Compiler::TransformAsync2() -{ - assert(compIsAsync2()); - - Async2Transformation transformation(this); - return transformation.Run(); -} - class AsyncLiveness { Compiler* m_comp; @@ -29,158 +21,235 @@ class AsyncLiveness { } - void StartBlock(BasicBlock* block) - { - if (!m_hasLiveness) - return; + void StartBlock(BasicBlock* block); + void Update(GenTree* node); + bool IsLive(unsigned lclNum); + void GetLiveLocals(jitstd::vector& liveLocals, unsigned fullyDefinedRetBufLcl); - VarSetOps::Assign(m_comp, m_comp->compCurLife, block->bbLiveIn); - } +private: + bool IsLocalCaptureUnnecessary(unsigned lclNum); +}; - void Update(GenTree* node) - { - if (!m_hasLiveness) - return; +//------------------------------------------------------------------------ +// AsyncLiveness::StartBlock: +// Indicate that we are now starting a new block, and do relevant liveness +// updates for it. +// +// Parameters: +// block - The block that we are starting. +// +void AsyncLiveness::StartBlock(BasicBlock* block) +{ + if (!m_hasLiveness) + return; - m_updater.UpdateLife(node); - } + VarSetOps::Assign(m_comp, m_comp->compCurLife, block->bbLiveIn); +} - bool IsLocalCaptureUnnecessary(unsigned lclNum) - { +//------------------------------------------------------------------------ +// AsyncLiveness::Update: +// Update liveness to be consistent with the specified node having been +// executed. +// +// Parameters: +// node - The node. +// +void AsyncLiveness::Update(GenTree* node) +{ + if (!m_hasLiveness) + return; + + m_updater.UpdateLife(node); +} + +//------------------------------------------------------------------------ +// AsyncLiveness::IsLocalCaptureUnnecessary: +// Check if capturing a specified local can be skipped. +// +// Parameters: +// lclNum - The local +// +// Returns: +// True if the local should not be captured. Even without liveness +// +bool AsyncLiveness::IsLocalCaptureUnnecessary(unsigned lclNum) +{ #if FEATURE_FIXED_OUT_ARGS - if (lclNum == m_comp->lvaOutgoingArgSpaceVar) - { - return true; - } + if (lclNum == m_comp->lvaOutgoingArgSpaceVar) + { + return true; + } #endif - if (lclNum == m_comp->info.compRetBuffArg) - { - return true; - } + if (lclNum == m_comp->info.compRetBuffArg) + { + return true; + } - if (lclNum == m_comp->lvaGSSecurityCookie) - { - // Initialized in prolog - return true; - } + if (lclNum == m_comp->lvaGSSecurityCookie) + { + // Initialized in prolog + return true; + } - if (lclNum == m_comp->lvaPSPSym) - { - // Initialized in prolog - return true; - } + if (lclNum == m_comp->lvaPSPSym) + { + // Initialized in prolog + return true; + } - if (lclNum == m_comp->info.compLvFrameListRoot) - { - return true; - } + if (lclNum == m_comp->info.compLvFrameListRoot) + { + return true; + } - if (lclNum == m_comp->lvaInlinedPInvokeFrameVar) - { - return true; - } + if (lclNum == m_comp->lvaInlinedPInvokeFrameVar) + { + return true; + } #ifdef FEATURE_EH_WINDOWS_X86 - if (lclNum == m_comp->lvaShadowSPslotsVar) - { - // Only expected to be live in handlers - return true; - } + if (lclNum == m_comp->lvaShadowSPslotsVar) + { + // Only expected to be live in handlers + return true; + } #endif - if (lclNum == m_comp->lvaRetAddrVar) - { - return true; - } + if (lclNum == m_comp->lvaRetAddrVar) + { + return true; + } - if (lclNum == m_comp->lvaAsyncContinuationArg) - { - return true; - } + if (lclNum == m_comp->lvaAsyncContinuationArg) + { + return true; + } + return false; +} + +//------------------------------------------------------------------------ +// AsyncLiveness::IsLive: +// Check if the specified local is live at this point and should be captured. +// +// Parameters: +// lclNum - The local +// +// Returns: +// True if the local is live and capturing it is necessary. +// +bool AsyncLiveness::IsLive(unsigned lclNum) +{ + if (IsLocalCaptureUnnecessary(lclNum)) + { return false; } - bool IsLive(unsigned lclNum) - { - if (IsLocalCaptureUnnecessary(lclNum)) - { - return false; - } + LclVarDsc* dsc = m_comp->lvaGetDesc(lclNum); - LclVarDsc* dsc = m_comp->lvaGetDesc(lclNum); + if ((dsc->TypeGet() == TYP_BYREF) || ((dsc->TypeGet() == TYP_STRUCT) && dsc->GetLayout()->HasGCByRef())) + { + // Even if these are address exposed we expect them to be dead at + // suspension points. TODO: It would be good to somehow verify these + // aren't obviously live, if the JIT creates live ranges that span a + // suspension point then this makes it quite hard to diagnose that. + return false; + } - if ((dsc->TypeGet() == TYP_BYREF) || ((dsc->TypeGet() == TYP_STRUCT) && dsc->GetLayout()->HasGCByRef())) - { - // Even if these are address exposed we expect them to be dead at - // suspension points. TODO: It would be good to somehow verify these - // aren't obviously live, if the JIT creates live ranges that span a - // suspension point then this makes it quite hard to diagnose that. - return false; - } + if (!m_hasLiveness) + { + return true; + } - if (!m_hasLiveness) - { - return true; - } + if (dsc->lvRefCnt(RCS_NORMAL) == 0) + { + return false; + } - if (dsc->lvRefCnt(RCS_NORMAL) == 0) - { - return false; - } + Compiler::lvaPromotionType promoType = m_comp->lvaGetPromotionType(dsc); + if (promoType == Compiler::PROMOTION_TYPE_INDEPENDENT) + { + // Independently promoted structs are handled only through their + // fields. + return false; + } - Compiler::lvaPromotionType promoType = m_comp->lvaGetPromotionType(dsc); - if (promoType == Compiler::PROMOTION_TYPE_INDEPENDENT) - { - // Independently promoted structs are handled only through their - // fields. - return false; - } + if (promoType == Compiler::PROMOTION_TYPE_DEPENDENT) + { + // Dependently promoted structs are handled only through the base + // struct local. + // + // A dependently promoted struct is live if any of its fields are live. - if (promoType == Compiler::PROMOTION_TYPE_DEPENDENT) + for (unsigned i = 0; i < dsc->lvFieldCnt; i++) { - // Dependently promoted structs are handled only through the base - // struct local. - // - // A dependently promoted struct is live if any of its fields are live. - - for (unsigned i = 0; i < dsc->lvFieldCnt; i++) + LclVarDsc* fieldDsc = m_comp->lvaGetDesc(dsc->lvFieldLclStart + i); + if (!fieldDsc->lvTracked || VarSetOps::IsMember(m_comp, m_comp->compCurLife, fieldDsc->lvVarIndex)) { - LclVarDsc* fieldDsc = m_comp->lvaGetDesc(dsc->lvFieldLclStart + i); - if (!fieldDsc->lvTracked || VarSetOps::IsMember(m_comp, m_comp->compCurLife, fieldDsc->lvVarIndex)) - { - return true; - } + return true; } - - return false; } - if (dsc->lvIsStructField && (m_comp->lvaGetParentPromotionType(dsc) == Compiler::PROMOTION_TYPE_DEPENDENT)) - { - return false; - } + return false; + } - return !dsc->lvTracked || VarSetOps::IsMember(m_comp, m_comp->compCurLife, dsc->lvVarIndex); + if (dsc->lvIsStructField && (m_comp->lvaGetParentPromotionType(dsc) == Compiler::PROMOTION_TYPE_DEPENDENT)) + { + return false; } - void GetLiveLocals(jitstd::vector& liveLocals, unsigned fullyDefinedRetBufLcl) + return !dsc->lvTracked || VarSetOps::IsMember(m_comp, m_comp->compCurLife, dsc->lvVarIndex); +} + +//------------------------------------------------------------------------ +// AsyncLiveness::GetLiveLocals: +// Get live locals that should be captured at this point. +// +// Parameters: +// liveLocals - Vector to add live local information into +// fullyDefinedRetBufLcl - Local to skip even if live +// +void AsyncLiveness::GetLiveLocals(jitstd::vector& liveLocals, + unsigned fullyDefinedRetBufLcl) +{ + for (unsigned lclNum = 0; lclNum < m_numVars; lclNum++) { - for (unsigned lclNum = 0; lclNum < m_numVars; lclNum++) + if ((lclNum != fullyDefinedRetBufLcl) && IsLive(lclNum)) { - if ((lclNum != fullyDefinedRetBufLcl) && IsLive(lclNum)) - { - liveLocals.push_back(Async2Transformation::LiveLocalInfo(lclNum)); - } + liveLocals.push_back(Async2Transformation::LiveLocalInfo(lclNum)); } } -}; +} + +//------------------------------------------------------------------------ +// TransformAsync2: Run async2 transformation. +// +// Returns: +// Suitable phase status. +// +PhaseStatus Compiler::TransformAsync2() +{ + assert(compIsAsync2()); + Async2Transformation transformation(this); + return transformation.Run(); +} + +//------------------------------------------------------------------------ +// Async2Transformation::Run: +// Run the transformation over all the IR. +// +// Returns: +// Suitable phase status. +// PhaseStatus Async2Transformation::Run() { ArrayStack worklist(m_comp->getAllocator(CMK_Async2)); + // First find all basic blocks with awaits in them. We'll have to track + // liveness in these basic blocks, so it does not help to record the calls + // ahead of time. for (BasicBlock* block : m_comp->Blocks()) { for (GenTree* tree : LIR::AsRange(block)) @@ -201,6 +270,9 @@ PhaseStatus Async2Transformation::Run() return PhaseStatus::MODIFIED_NOTHING; } + // 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. m_resumeStub = m_comp->info.compCompHnd->getAsyncResumptionStub(); m_comp->info.compCompHnd->getFunctionFixedEntryPoint(m_resumeStub, false, &m_resumeStubLookup); @@ -227,6 +299,8 @@ PhaseStatus Async2Transformation::Run() DISPRANGE(LIR::AsRange(m_sharedReturnBB)); #endif + // Compute liveness to be used for determining what must be captured on + // suspension. In unoptimized codegen we capture everything. if (m_comp->opts.OptimizationEnabled()) { if (m_comp->m_dfsTree == nullptr) @@ -241,6 +315,9 @@ PhaseStatus Async2Transformation::Run() AsyncLiveness liveness(m_comp, m_comp->opts.OptimizationEnabled()); + // Now walk the IR for all the blocks that contain async2 calls. Keep track + // of liveness and outstanding LIR edges as we go; the LIR edges that cross + // async2 calls are additional live variables that must be spilled. jitstd::vector defs(m_comp->getAllocator(CMK_Async2)); for (int i = 0; i < worklist.Height(); i++) @@ -256,6 +333,8 @@ PhaseStatus Async2Transformation::Run() any = false; for (GenTree* tree : LIR::AsRange(block)) { + // Remove all consumed defs; those are no longer 'live' LIR + // edges. tree->VisitOperands([&defs](GenTree* op) { if (op->IsValue()) { @@ -273,16 +352,20 @@ PhaseStatus Async2Transformation::Run() return GenTree::VisitResult::Continue; }); + // Update liveness to reflect state after this node. liveness.Update(tree); if (tree->IsCall() && tree->AsCall()->IsAsync2() && !tree->AsCall()->IsTailCall()) { + // Transform call; continue with the remainder block Transform(block, tree->AsCall(), defs, liveness, &block); defs.clear(); any = true; break; } + // Push a new definition if necessary; this defined value is + // now a live LIR edge. if (tree->IsValue() && !tree->IsUnusedValue()) { defs.push_back(tree); @@ -291,6 +374,8 @@ PhaseStatus Async2Transformation::Run() } while (any); } + // After transforming all async calls we have created resumption blocks; + // create the resumption switch. CreateResumptionSwitch(); m_comp->fgInvalidateDfsTree(); @@ -298,8 +383,19 @@ PhaseStatus Async2Transformation::Run() return PhaseStatus::MODIFIED_EVERYTHING; } +//------------------------------------------------------------------------ +// Async2Transformation::Transform: +// Transform a single async2 call in the specified block. +// +// Parameters: +// block - The block containig the async2 call +// call - The async2 call +// defs - Current live LIR edges +// life - Liveness information about live locals +// remainder - [out] Remainder block after the transformation +// void Async2Transformation::Transform( - BasicBlock* block, GenTreeCall* call, jitstd::vector& defs, AsyncLiveness& life, BasicBlock** pRemainder) + BasicBlock* block, GenTreeCall* call, jitstd::vector& defs, AsyncLiveness& life, BasicBlock** remainder) { #ifdef DEBUG if (m_comp->verbose) @@ -321,8 +417,45 @@ void Async2Transformation::Transform( } #endif - m_liveLocals.clear(); + m_liveLocalsScratch.clear(); + jitstd::vector& liveLocals = m_liveLocalsScratch; + + CreateLiveSetForSuspension(block, call, defs, life, liveLocals); + + ContinuationLayout layout = LayOutContinuation(block, call, liveLocals); + + CallDefinitionInfo callDefInfo = CanonicalizeCallDefinition(block, call, life); + + unsigned stateNum = (unsigned)m_resumptionBBs.size(); + JITDUMP(" Assigned state %u\n", stateNum); + + BasicBlock* suspendBB = CreateSuspension(block, stateNum, layout, life, liveLocals); + CreateCheckAndSuspendAfterCall(block, callDefInfo, life, suspendBB, remainder); + + BasicBlock* resumeBB = CreateResumption(block, *remainder, call, callDefInfo, stateNum, layout, liveLocals); + + m_resumptionBBs.push_back(resumeBB); +} + +//------------------------------------------------------------------------ +// Async2Transformation::CreateLiveSetForSuspension: +// Create the set of live state to be captured for suspension, for the +// specified call. +// +// Parameters: +// block - The block containig the async2 call +// call - The async2 call +// defs - Current live LIR edges +// life - Liveness information about live locals +// livenessInfo - Information about live state +// +void Async2Transformation::CreateLiveSetForSuspension(BasicBlock* block, + GenTreeCall* call, + const jitstd::vector& defs, + AsyncLiveness& life, + jitstd::vector& liveLocals) +{ unsigned fullyDefinedRetBufLcl = BAD_VAR_NUM; CallArg* retbufArg = call->gtArgs.GetRetBufferArg(); if (retbufArg != nullptr) @@ -343,18 +476,18 @@ void Async2Transformation::Transform( } } - life.GetLiveLocals(m_liveLocals, fullyDefinedRetBufLcl); - LiftLIREdges(block, call, defs, m_liveLocals); + life.GetLiveLocals(liveLocals, fullyDefinedRetBufLcl); + LiftLIREdges(block, call, defs, liveLocals); #ifdef DEBUG if (m_comp->verbose) { - printf(" %zu live locals\n", m_liveLocals.size()); + printf(" %zu live locals\n", liveLocals.size()); - if (m_liveLocals.size() > 0) + if (liveLocals.size() > 0) { const char* sep = " "; - for (LiveLocalInfo& inf : m_liveLocals) + for (LiveLocalInfo& inf : liveLocals) { printf("%sV%02u (%s)", sep, inf.LclNum, varTypeName(m_comp->lvaGetDesc(inf.LclNum)->TypeGet())); sep = ", "; @@ -364,8 +497,15 @@ void Async2Transformation::Transform( } } #endif +} - for (LiveLocalInfo& inf : m_liveLocals) +ContinuationLayout Async2Transformation::LayOutContinuation(BasicBlock* block, + GenTreeCall* call, + jitstd::vector& liveLocals) +{ + ContinuationLayout layout; + + for (LiveLocalInfo& inf : liveLocals) { LclVarDsc* dsc = m_comp->lvaGetDesc(inf.LclNum); @@ -411,7 +551,7 @@ void Async2Transformation::Transform( } } - jitstd::sort(m_liveLocals.begin(), m_liveLocals.end(), [](const LiveLocalInfo& lhs, const LiveLocalInfo& rhs) { + jitstd::sort(liveLocals.begin(), liveLocals.end(), [](const LiveLocalInfo& lhs, const LiveLocalInfo& rhs) { if (lhs.Alignment == rhs.Alignment) { // Prefer lowest local num first for same alignment. @@ -422,93 +562,86 @@ void Async2Transformation::Transform( return lhs.Alignment > rhs.Alignment; }); - unsigned dataSize = 0; - unsigned gcRefsCount = 0; - // For OSR, we store the transition IL offset at the beginning of the data // (-1 in the tier0 version): if (m_comp->doesMethodHavePatchpoints() || m_comp->opts.IsOSR()) { JITDUMP(" Method %s; keeping an IL offset at the beginning of non-GC data\n", m_comp->doesMethodHavePatchpoints() ? "has patchpoints" : "is an OSR method"); - dataSize += sizeof(int); + layout.DataSize += sizeof(int); } - ClassLayout* returnStructLayout = nullptr; - unsigned returnSize = 0; - bool returnInGCData = false; if (call->gtReturnType == TYP_STRUCT) { - returnStructLayout = m_comp->typGetObjLayout(call->gtRetClsHnd); - returnSize = returnStructLayout->GetSize(); - returnInGCData = returnStructLayout->HasGCPtr(); + layout.ReturnStructLayout = m_comp->typGetObjLayout(call->gtRetClsHnd); + layout.ReturnSize = layout.ReturnStructLayout->GetSize(); + layout.ReturnInGCData = layout.ReturnStructLayout->HasGCPtr(); } else { - returnSize = genTypeSize(call->gtReturnType); - returnInGCData = varTypeIsGC(call->gtReturnType); + layout.ReturnSize = genTypeSize(call->gtReturnType); + layout.ReturnInGCData = varTypeIsGC(call->gtReturnType); } - assert((returnSize > 0) == (call->gtReturnType != TYP_VOID)); + assert((layout.ReturnSize > 0) == (call->gtReturnType != TYP_VOID)); // The return value is always stored: // 1. At index 0 in GCData if it is a TYP_REF or a struct with GC references // 2. At index 0 in Data, for non OSR methods without GC ref returns // 3. At index 4 in Data for OSR methods without GC ref returns. The // continuation flags indicates this scenario with a flag. - unsigned returnValDataOffset = UINT_MAX; - if (returnInGCData) + if (layout.ReturnInGCData) { - gcRefsCount++; + layout.GCRefsCount++; } - else if (returnSize > 0) + else if (layout.ReturnSize > 0) { - returnValDataOffset = dataSize; - dataSize += returnSize; + layout.ReturnValDataOffset = layout.DataSize; + layout.DataSize += layout.ReturnSize; } #ifdef DEBUG - if (returnSize > 0) + if (layout.ReturnSize > 0) { JITDUMP(" Will store return of type %s, size %u in", - call->gtReturnType == TYP_STRUCT ? returnStructLayout->GetClassName() : varTypeName(call->gtReturnType), - returnSize); + call->gtReturnType == TYP_STRUCT ? layout.ReturnStructLayout->GetClassName() + : varTypeName(call->gtReturnType), + layout.ReturnSize); - if (returnInGCData) + if (layout.ReturnInGCData) { JITDUMP(" GC data\n"); } else { - JITDUMP(" non-GC data at offset %u\n", returnValDataOffset); + JITDUMP(" non-GC data at offset %u\n", layout.ReturnValDataOffset); } } #endif - unsigned exceptionGCDataIndex = UINT_MAX; if (block->hasTryIndex()) { - exceptionGCDataIndex = gcRefsCount++; + layout.ExceptionGCDataIndex = layout.GCRefsCount++; JITDUMP(" " FMT_BB " is in try region %u; exception will be at GC@+%02u in GC data\n", block->bbNum, - block->getTryIndex(), exceptionGCDataIndex); + block->getTryIndex(), layout.ExceptionGCDataIndex); } - for (LiveLocalInfo& inf : m_liveLocals) + for (LiveLocalInfo& inf : liveLocals) { - dataSize = roundUp(dataSize, inf.Alignment); + layout.DataSize = roundUp(layout.DataSize, inf.Alignment); - inf.DataOffset = dataSize; - inf.GCDataIndex = gcRefsCount; + inf.DataOffset = layout.DataSize; + inf.GCDataIndex = layout.GCRefsCount; - dataSize += inf.DataSize; - gcRefsCount += inf.GCDataCount; + layout.DataSize += inf.DataSize; + layout.GCRefsCount += inf.GCDataCount; } #ifdef DEBUG if (m_comp->verbose) { - printf(" Continuation layout (%u bytes, %u GC pointers):\n", dataSize, gcRefsCount); - for (LiveLocalInfo& inf : m_liveLocals) + printf(" Continuation layout (%u bytes, %u GC pointers):\n", layout.DataSize, layout.GCRefsCount); + for (LiveLocalInfo& inf : liveLocals) { printf(" +%03u (GC@+%02u) V%02u: %u bytes, %u GC pointers\n", inf.DataOffset, inf.GCDataIndex, inf.LclNum, inf.DataSize, inf.GCDataCount); @@ -516,11 +649,18 @@ void Async2Transformation::Transform( } #endif - unsigned stateNum = (unsigned)m_resumptionBBs.size(); - JITDUMP(" Assigned state %u\n", stateNum); + return layout; +} + +CallDefinitionInfo Async2Transformation::CanonicalizeCallDefinition(BasicBlock* block, + GenTreeCall* call, + AsyncLiveness& life) +{ + CallDefinitionInfo callDefInfo; - GenTreeLclVarCommon* storeResultNode = nullptr; - GenTree* insertAfter = call; + callDefInfo.InsertAfter = call; + + CallArg* retbufArg = call->gtArgs.GetRetBufferArg(); if (!call->TypeIs(TYP_VOID) && !call->IsUnusedValue()) { @@ -541,8 +681,8 @@ void Async2Transformation::Transform( } assert(call->gtNext->OperIsLocalStore() && (call->gtNext->Data() == call)); - storeResultNode = call->gtNext->AsLclVarCommon(); - insertAfter = call->gtNext; + callDefInfo.DefinitionNode = call->gtNext->AsLclVarCommon(); + callDefInfo.InsertAfter = call->gtNext; } if (retbufArg != nullptr) @@ -556,28 +696,18 @@ void Async2Transformation::Transform( // introducing copies in the importer on the synchronous path. noway_assert(retbufArg->GetNode()->OperIs(GT_LCL_ADDR)); - storeResultNode = retbufArg->GetNode()->AsLclVarCommon(); + callDefInfo.DefinitionNode = retbufArg->GetNode()->AsLclVarCommon(); } - GenTree* continuationArg = new (m_comp, GT_ASYNC_CONTINUATION) GenTree(GT_ASYNC_CONTINUATION, TYP_REF); - continuationArg->SetHasOrderingSideEffect(); - - GenTree* storeContinuation = m_comp->gtNewStoreLclVarNode(m_returnedContinuationVar, continuationArg); - LIR::AsRange(block).InsertAfter(insertAfter, continuationArg, storeContinuation); - - GenTree* null = m_comp->gtNewNull(); - GenTree* returnedContinuation = m_comp->gtNewLclvNode(m_returnedContinuationVar, TYP_REF); - GenTree* neNull = m_comp->gtNewOperNode(GT_NE, TYP_INT, returnedContinuation, null); - GenTree* jtrue = m_comp->gtNewOperNode(GT_JTRUE, TYP_VOID, neNull); - - LIR::AsRange(block).InsertAfter(storeContinuation, null, returnedContinuation, neNull, jtrue); - BasicBlock* remainder = m_comp->fgSplitBlockAfterNode(block, jtrue); - *pRemainder = remainder; - - JITDUMP(" Remainder is " FMT_BB "\n", remainder->bbNum); - - assert(block->KindIs(BBJ_ALWAYS) && block->TargetIs(remainder)); + return callDefInfo; +} +BasicBlock* Async2Transformation::CreateSuspension(BasicBlock* block, + unsigned stateNum, + const ContinuationLayout& layout, + AsyncLiveness& life, + jitstd::vector& liveLocals) +{ if (m_lastSuspensionBB == nullptr) { m_lastSuspensionBB = m_comp->fgLastBBInMainFunction(); @@ -594,18 +724,13 @@ void Async2Transformation::Transform( suspendBB->SetKindAndTargetEdge(BBJ_ALWAYS, m_comp->fgAddRefPred(m_sharedReturnBB, suspendBB)); } - JITDUMP(" Created suspension " FMT_BB " for state %u\n", suspendBB->bbNum, stateNum); - - FlowEdge* retBBEdge = m_comp->fgAddRefPred(suspendBB, block); - block->SetCond(retBBEdge, block->GetTargetEdge()); - - block->GetTrueEdge()->setLikelihood(0); - block->GetFalseEdge()->setLikelihood(1); + JITDUMP(" Creating suspension " FMT_BB " for state %u\n", suspendBB->bbNum, stateNum); // Allocate continuation - returnedContinuation = m_comp->gtNewLclvNode(m_returnedContinuationVar, TYP_REF); + GenTree* returnedContinuation = m_comp->gtNewLclvNode(m_returnedContinuationVar, TYP_REF); - GenTreeCall* allocContinuation = CreateAllocContinuationCall(life, returnedContinuation, gcRefsCount, dataSize); + GenTreeCall* allocContinuation = + CreateAllocContinuationCall(life, returnedContinuation, layout.GCRefsCount, layout.DataSize); m_comp->compCurBB = suspendBB; m_comp->fgMorphTree(allocContinuation); @@ -631,7 +756,7 @@ void Async2Transformation::Transform( // Fill in 'flags' unsigned continuationFlags = 0; - if (returnInGCData) + if (layout.ReturnInGCData) continuationFlags |= CORINFO_CONTINUATION_RESULT_IN_GCDATA; if (block->hasTryIndex()) continuationFlags |= CORINFO_CONTINUATION_NEEDS_EXCEPTION; @@ -644,167 +769,257 @@ void Async2Transformation::Transform( GenTree* storeFlags = StoreAtOffset(newContinuation, flagsOffset, flagsNode); LIR::AsRange(suspendBB).InsertAtEnd(LIR::SeqTree(m_comp, storeFlags)); - // Fill in GC pointers - if (gcRefsCount > 0) + if (layout.GCRefsCount > 0) { - unsigned objectArrLclNum = GetGCDataArrayVar(); + FillInGCPointersOnSuspension(liveLocals, suspendBB); + } - newContinuation = m_comp->gtNewLclvNode(m_newContinuationVar, TYP_REF); - unsigned gcDataOffset = m_comp->info.compCompHnd->getFieldOffset(m_async2Info.continuationGCDataFldHnd); - GenTree* gcDataInd = LoadFromOffset(newContinuation, gcDataOffset, TYP_REF); - GenTree* storeAllocedObjectArr = m_comp->gtNewStoreLclVarNode(objectArrLclNum, gcDataInd); - LIR::AsRange(suspendBB).InsertAtEnd(LIR::SeqTree(m_comp, storeAllocedObjectArr)); + if (layout.DataSize > 0) + { + FillInDataOnSuspension(liveLocals, suspendBB); + } + + if (suspendBB->KindIs(BBJ_RETURN)) + { + newContinuation = m_comp->gtNewLclvNode(m_newContinuationVar, TYP_REF); + GenTree* ret = m_comp->gtNewOperNode(GT_RETURN_SUSPEND, TYP_VOID, newContinuation); + LIR::AsRange(suspendBB).InsertAtEnd(newContinuation, ret); + } + + return suspendBB; +} - for (LiveLocalInfo& inf : m_liveLocals) +GenTreeCall* Async2Transformation::CreateAllocContinuationCall(AsyncLiveness& life, + GenTree* prevContinuation, + unsigned gcRefsCount, + unsigned dataSize) +{ + GenTree* gcRefsCountNode = m_comp->gtNewIconNode((ssize_t)gcRefsCount, TYP_I_IMPL); + GenTree* dataSizeNode = m_comp->gtNewIconNode((ssize_t)dataSize, TYP_I_IMPL); + // If VM requests that we report the method handle, or if we have a shared generic context method handle + // that is live here, then we need to call a different helper. + GenTree* methodHandleArg = nullptr; + GenTree* classHandleArg = nullptr; + if (((m_comp->info.compMethodInfo->options & CORINFO_GENERICS_CTXT_FROM_METHODDESC) != 0) && + life.IsLive(m_comp->info.compTypeCtxtArg)) + { + methodHandleArg = m_comp->gtNewLclvNode(m_comp->info.compTypeCtxtArg, TYP_I_IMPL); + } + else if (((m_comp->info.compMethodInfo->options & CORINFO_GENERICS_CTXT_FROM_METHODTABLE) != 0) && + life.IsLive(m_comp->info.compTypeCtxtArg)) + { + classHandleArg = m_comp->gtNewLclvNode(m_comp->info.compTypeCtxtArg, TYP_I_IMPL); + } + else if (m_async2Info.continuationsNeedMethodHandle) + { + methodHandleArg = m_comp->gtNewIconEmbMethHndNode(m_comp->info.compMethodHnd); + } + + if (methodHandleArg != nullptr) + { + return m_comp->gtNewHelperCallNode(CORINFO_HELP_ALLOC_CONTINUATION_METHOD, TYP_REF, prevContinuation, + gcRefsCountNode, dataSizeNode, methodHandleArg); + } + + if (classHandleArg != nullptr) + { + return m_comp->gtNewHelperCallNode(CORINFO_HELP_ALLOC_CONTINUATION_CLASS, TYP_REF, prevContinuation, + gcRefsCountNode, dataSizeNode, classHandleArg); + } + + return m_comp->gtNewHelperCallNode(CORINFO_HELP_ALLOC_CONTINUATION, TYP_REF, prevContinuation, gcRefsCountNode, + dataSizeNode); +} + +void Async2Transformation::FillInGCPointersOnSuspension(jitstd::vector& liveLocals, + BasicBlock* suspendBB) +{ + unsigned objectArrLclNum = GetGCDataArrayVar(); + + GenTree* newContinuation = m_comp->gtNewLclvNode(m_newContinuationVar, TYP_REF); + unsigned gcDataOffset = m_comp->info.compCompHnd->getFieldOffset(m_async2Info.continuationGCDataFldHnd); + GenTree* gcDataInd = LoadFromOffset(newContinuation, gcDataOffset, TYP_REF); + GenTree* storeAllocedObjectArr = m_comp->gtNewStoreLclVarNode(objectArrLclNum, gcDataInd); + LIR::AsRange(suspendBB).InsertAtEnd(LIR::SeqTree(m_comp, storeAllocedObjectArr)); + + for (LiveLocalInfo& inf : liveLocals) + { + if (inf.GCDataCount <= 0) { - if (inf.GCDataCount <= 0) - { - continue; - } + continue; + } - LclVarDsc* dsc = m_comp->lvaGetDesc(inf.LclNum); - if (dsc->TypeGet() == TYP_REF) + LclVarDsc* dsc = m_comp->lvaGetDesc(inf.LclNum); + if (dsc->TypeGet() == TYP_REF) + { + GenTree* value = m_comp->gtNewLclvNode(inf.LclNum, TYP_REF); + GenTree* objectArr = m_comp->gtNewLclvNode(objectArrLclNum, TYP_REF); + GenTree* store = + StoreAtOffset(objectArr, OFFSETOF__CORINFO_Array__data + (inf.GCDataIndex * TARGET_POINTER_SIZE), + value); + LIR::AsRange(suspendBB).InsertAtEnd(LIR::SeqTree(m_comp, store)); + } + else + { + assert((dsc->TypeGet() == TYP_STRUCT) || dsc->IsImplicitByRef()); + ClassLayout* layout = dsc->GetLayout(); + unsigned numSlots = layout->GetSlotCount(); + unsigned gcRefIndex = 0; + for (unsigned i = 0; i < numSlots; i++) { - GenTree* value = m_comp->gtNewLclvNode(inf.LclNum, TYP_REF); + var_types gcPtrType = layout->GetGCPtrType(i); + assert((gcPtrType == TYP_I_IMPL) || (gcPtrType == TYP_REF)); + if (gcPtrType != TYP_REF) + { + continue; + } + + GenTree* value; + if (dsc->IsImplicitByRef()) + { + GenTree* baseAddr = m_comp->gtNewLclvNode(inf.LclNum, dsc->TypeGet()); + value = LoadFromOffset(baseAddr, i * TARGET_POINTER_SIZE, TYP_REF); + } + else + { + value = m_comp->gtNewLclFldNode(inf.LclNum, TYP_REF, i * TARGET_POINTER_SIZE); + } + GenTree* objectArr = m_comp->gtNewLclvNode(objectArrLclNum, TYP_REF); - GenTree* store = - StoreAtOffset(objectArr, OFFSETOF__CORINFO_Array__data + (inf.GCDataIndex * TARGET_POINTER_SIZE), - value); + unsigned offset = + OFFSETOF__CORINFO_Array__data + ((inf.GCDataIndex + gcRefIndex) * TARGET_POINTER_SIZE); + GenTree* store = StoreAtOffset(objectArr, offset, value); LIR::AsRange(suspendBB).InsertAtEnd(LIR::SeqTree(m_comp, store)); - } - else - { - assert((dsc->TypeGet() == TYP_STRUCT) || dsc->IsImplicitByRef()); - ClassLayout* layout = dsc->GetLayout(); - unsigned numSlots = layout->GetSlotCount(); - unsigned gcRefIndex = 0; - for (unsigned i = 0; i < numSlots; i++) + + gcRefIndex++; + + if (inf.DataSize > 0) { - var_types gcPtrType = layout->GetGCPtrType(i); - assert((gcPtrType == TYP_I_IMPL) || (gcPtrType == TYP_REF)); - if (gcPtrType != TYP_REF) - { - continue; - } + // Null out the GC field in preparation of storing the rest. + GenTree* null = m_comp->gtNewNull(); - GenTree* value; if (dsc->IsImplicitByRef()) { GenTree* baseAddr = m_comp->gtNewLclvNode(inf.LclNum, dsc->TypeGet()); - value = LoadFromOffset(baseAddr, i * TARGET_POINTER_SIZE, TYP_REF); + store = StoreAtOffset(baseAddr, i * TARGET_POINTER_SIZE, null); } else { - value = m_comp->gtNewLclFldNode(inf.LclNum, TYP_REF, i * TARGET_POINTER_SIZE); + store = m_comp->gtNewStoreLclFldNode(inf.LclNum, TYP_REF, i * TARGET_POINTER_SIZE, null); } - GenTree* objectArr = m_comp->gtNewLclvNode(objectArrLclNum, TYP_REF); - unsigned offset = - OFFSETOF__CORINFO_Array__data + ((inf.GCDataIndex + gcRefIndex) * TARGET_POINTER_SIZE); - GenTree* store = StoreAtOffset(objectArr, offset, value); LIR::AsRange(suspendBB).InsertAtEnd(LIR::SeqTree(m_comp, store)); + } + } - gcRefIndex++; + m_comp->lvaSetVarDoNotEnregister(inf.LclNum DEBUGARG(DoNotEnregisterReason::LocalField)); + } + } +} - if (inf.DataSize > 0) - { - // Null out the GC field in preparation of storing the rest. - GenTree* null = m_comp->gtNewNull(); +void Async2Transformation::FillInDataOnSuspension(jitstd::vector& liveLocals, BasicBlock* suspendBB) +{ + unsigned byteArrLclNum = GetDataArrayVar(); - if (dsc->IsImplicitByRef()) - { - GenTree* baseAddr = m_comp->gtNewLclvNode(inf.LclNum, dsc->TypeGet()); - store = StoreAtOffset(baseAddr, i * TARGET_POINTER_SIZE, null); - } - else - { - store = m_comp->gtNewStoreLclFldNode(inf.LclNum, TYP_REF, i * TARGET_POINTER_SIZE, null); - } + GenTree* newContinuation = m_comp->gtNewLclvNode(m_newContinuationVar, TYP_REF); + unsigned dataOffset = m_comp->info.compCompHnd->getFieldOffset(m_async2Info.continuationDataFldHnd); + GenTree* dataInd = LoadFromOffset(newContinuation, dataOffset, TYP_REF); + GenTree* storeAllocedByteArr = m_comp->gtNewStoreLclVarNode(byteArrLclNum, dataInd); + LIR::AsRange(suspendBB).InsertAtEnd(LIR::SeqTree(m_comp, storeAllocedByteArr)); - LIR::AsRange(suspendBB).InsertAtEnd(LIR::SeqTree(m_comp, store)); - } - } + if (m_comp->doesMethodHavePatchpoints() || m_comp->opts.IsOSR()) + { + GenTree* ilOffsetToStore; + if (m_comp->doesMethodHavePatchpoints()) + ilOffsetToStore = m_comp->gtNewIconNode(-1); + else + ilOffsetToStore = m_comp->gtNewIconNode((int)m_comp->info.compILEntry); - m_comp->lvaSetVarDoNotEnregister(inf.LclNum DEBUGARG(DoNotEnregisterReason::LocalField)); - } - } + GenTree* byteArr = m_comp->gtNewLclvNode(byteArrLclNum, TYP_REF); + unsigned offset = OFFSETOF__CORINFO_Array__data; + GenTree* storePatchpointOffset = StoreAtOffset(byteArr, offset, ilOffsetToStore); + LIR::AsRange(suspendBB).InsertAtEnd(LIR::SeqTree(m_comp, storePatchpointOffset)); } - // Store data in byte[] - if (dataSize > 0) + // Fill in data + for (LiveLocalInfo& inf : liveLocals) { - unsigned byteArrLclNum = GetDataArrayVar(); + if (inf.DataSize <= 0) + { + continue; + } - GenTree* newContinuation = m_comp->gtNewLclvNode(m_newContinuationVar, TYP_REF); - unsigned dataOffset = m_comp->info.compCompHnd->getFieldOffset(m_async2Info.continuationDataFldHnd); - GenTree* dataInd = LoadFromOffset(newContinuation, dataOffset, TYP_REF); - GenTree* storeAllocedByteArr = m_comp->gtNewStoreLclVarNode(byteArrLclNum, dataInd); - LIR::AsRange(suspendBB).InsertAtEnd(LIR::SeqTree(m_comp, storeAllocedByteArr)); + LclVarDsc* dsc = m_comp->lvaGetDesc(inf.LclNum); - if (m_comp->doesMethodHavePatchpoints() || m_comp->opts.IsOSR()) - { - GenTree* ilOffsetToStore; - if (m_comp->doesMethodHavePatchpoints()) - ilOffsetToStore = m_comp->gtNewIconNode(-1); - else - ilOffsetToStore = m_comp->gtNewIconNode((int)m_comp->info.compILEntry); + GenTree* byteArr = m_comp->gtNewLclvNode(byteArrLclNum, TYP_REF); + unsigned offset = OFFSETOF__CORINFO_Array__data + inf.DataOffset; - GenTree* byteArr = m_comp->gtNewLclvNode(byteArrLclNum, TYP_REF); - unsigned offset = OFFSETOF__CORINFO_Array__data; - GenTree* storePatchpointOffset = StoreAtOffset(byteArr, offset, ilOffsetToStore); - LIR::AsRange(suspendBB).InsertAtEnd(LIR::SeqTree(m_comp, storePatchpointOffset)); + GenTree* value; + if (dsc->IsImplicitByRef()) + { + GenTree* baseAddr = m_comp->gtNewLclvNode(inf.LclNum, dsc->TypeGet()); + value = m_comp->gtNewBlkIndir(dsc->GetLayout(), baseAddr, GTF_IND_NONFAULTING); + } + else + { + value = m_comp->gtNewLclvNode(inf.LclNum, genActualType(dsc->TypeGet())); } - // Fill in data - for (LiveLocalInfo& inf : m_liveLocals) + GenTree* store; + if ((dsc->TypeGet() == TYP_STRUCT) || dsc->IsImplicitByRef()) { - if (inf.DataSize <= 0) - { - continue; - } + GenTree* cns = m_comp->gtNewIconNode((ssize_t)offset, TYP_I_IMPL); + GenTree* addr = m_comp->gtNewOperNode(GT_ADD, TYP_BYREF, byteArr, cns); + // This is to heap, but all GC refs are nulled out already, so we can skip the write barrier. + // TODO-CQ: Backend does not care about GTF_IND_TGT_NOT_HEAP for STORE_BLK. + store = + m_comp->gtNewStoreBlkNode(dsc->GetLayout(), addr, value, GTF_IND_NONFAULTING | GTF_IND_TGT_NOT_HEAP); + } + else + { + store = StoreAtOffset(byteArr, offset, value); + } - LclVarDsc* dsc = m_comp->lvaGetDesc(inf.LclNum); + LIR::AsRange(suspendBB).InsertAtEnd(LIR::SeqTree(m_comp, store)); + } +} - GenTree* byteArr = m_comp->gtNewLclvNode(byteArrLclNum, TYP_REF); - unsigned offset = OFFSETOF__CORINFO_Array__data + inf.DataOffset; +void Async2Transformation::CreateCheckAndSuspendAfterCall(BasicBlock* block, + const CallDefinitionInfo& callDefInfo, + AsyncLiveness& life, + BasicBlock* suspendBB, + BasicBlock** remainder) +{ + GenTree* continuationArg = new (m_comp, GT_ASYNC_CONTINUATION) GenTree(GT_ASYNC_CONTINUATION, TYP_REF); + continuationArg->SetHasOrderingSideEffect(); - GenTree* value; - if (dsc->IsImplicitByRef()) - { - GenTree* baseAddr = m_comp->gtNewLclvNode(inf.LclNum, dsc->TypeGet()); - value = m_comp->gtNewBlkIndir(dsc->GetLayout(), baseAddr, GTF_IND_NONFAULTING); - } - else - { - value = m_comp->gtNewLclvNode(inf.LclNum, genActualType(dsc->TypeGet())); - } + GenTree* storeContinuation = m_comp->gtNewStoreLclVarNode(m_returnedContinuationVar, continuationArg); + LIR::AsRange(block).InsertAfter(callDefInfo.InsertAfter, continuationArg, storeContinuation); - GenTree* store; - if ((dsc->TypeGet() == TYP_STRUCT) || dsc->IsImplicitByRef()) - { - GenTree* cns = m_comp->gtNewIconNode((ssize_t)offset, TYP_I_IMPL); - GenTree* addr = m_comp->gtNewOperNode(GT_ADD, TYP_BYREF, byteArr, cns); - // This is to heap, but all GC refs are nulled out already, so we can skip the write barrier. - // TODO-CQ: Backend does not care about GTF_IND_TGT_NOT_HEAP for STORE_BLK. - store = m_comp->gtNewStoreBlkNode(dsc->GetLayout(), addr, value, - GTF_IND_NONFAULTING | GTF_IND_TGT_NOT_HEAP); - } - else - { - store = StoreAtOffset(byteArr, offset, value); - } + GenTree* null = m_comp->gtNewNull(); + GenTree* returnedContinuation = m_comp->gtNewLclvNode(m_returnedContinuationVar, TYP_REF); + GenTree* neNull = m_comp->gtNewOperNode(GT_NE, TYP_INT, returnedContinuation, null); + GenTree* jtrue = m_comp->gtNewOperNode(GT_JTRUE, TYP_VOID, neNull); - LIR::AsRange(suspendBB).InsertAtEnd(LIR::SeqTree(m_comp, store)); - } - } + LIR::AsRange(block).InsertAfter(storeContinuation, null, returnedContinuation, neNull, jtrue); + *remainder = m_comp->fgSplitBlockAfterNode(block, jtrue); + JITDUMP(" Remainder is " FMT_BB "\n", (*remainder)->bbNum); - if (suspendBB->KindIs(BBJ_RETURN)) - { - newContinuation = m_comp->gtNewLclvNode(m_newContinuationVar, TYP_REF); - GenTree* ret = m_comp->gtNewOperNode(GT_RETURN_SUSPEND, TYP_VOID, newContinuation); - LIR::AsRange(suspendBB).InsertAtEnd(newContinuation, ret); - } + FlowEdge* retBBEdge = m_comp->fgAddRefPred(suspendBB, block); + block->SetCond(retBBEdge, block->GetTargetEdge()); + block->GetTrueEdge()->setLikelihood(0); + block->GetFalseEdge()->setLikelihood(1); +} + +BasicBlock* Async2Transformation::CreateResumption(BasicBlock* block, + BasicBlock* remainder, + GenTreeCall* call, + const CallDefinitionInfo& callDefInfo, + unsigned stateNum, + const ContinuationLayout& layout, + jitstd::vector& liveLocals) +{ if (m_lastResumptionBB == nullptr) { m_lastResumptionBB = m_comp->fgLastBBInMainFunction(); @@ -823,10 +1038,10 @@ void Async2Transformation::Transform( resumeBB->SetFlags(BBF_ASYNC_RESUMPTION); m_lastResumptionBB = resumeBB; - JITDUMP(" Created resumption " FMT_BB " for state %u\n", resumeBB->bbNum, stateNum); + JITDUMP(" Creating resumption " FMT_BB " for state %u\n", resumeBB->bbNum, stateNum); unsigned resumeByteArrLclNum = BAD_VAR_NUM; - if (dataSize > 0) + if (layout.DataSize > 0) { resumeByteArrLclNum = GetDataArrayVar(); @@ -837,328 +1052,322 @@ void Async2Transformation::Transform( LIR::AsRange(resumeBB).InsertAtEnd(LIR::SeqTree(m_comp, storeAllocedByteArr)); - // Copy data - for (LiveLocalInfo& inf : m_liveLocals) - { - if (inf.DataSize <= 0) - { - continue; - } + RestoreFromDataOnResumption(resumeByteArrLclNum, liveLocals, resumeBB); + } - LclVarDsc* dsc = m_comp->lvaGetDesc(inf.LclNum); + unsigned resumeObjectArrLclNum = BAD_VAR_NUM; + BasicBlock* storeResultBB = resumeBB; - GenTree* byteArr = m_comp->gtNewLclvNode(resumeByteArrLclNum, TYP_REF); - unsigned offset = OFFSETOF__CORINFO_Array__data + inf.DataOffset; - GenTree* cns = m_comp->gtNewIconNode((ssize_t)offset, TYP_I_IMPL); - GenTree* addr = m_comp->gtNewOperNode(GT_ADD, TYP_BYREF, byteArr, cns); + if (layout.GCRefsCount > 0) + { + resumeObjectArrLclNum = GetGCDataArrayVar(); - GenTree* value; - if ((dsc->TypeGet() == TYP_STRUCT) || dsc->IsImplicitByRef()) - { - value = m_comp->gtNewBlkIndir(dsc->GetLayout(), addr, GTF_IND_NONFAULTING); - } - else - { - value = m_comp->gtNewIndir(dsc->TypeGet(), addr, GTF_IND_NONFAULTING); - } + GenTree* newContinuation = m_comp->gtNewLclvNode(m_comp->lvaAsyncContinuationArg, TYP_REF); + unsigned gcDataOffset = m_comp->info.compCompHnd->getFieldOffset(m_async2Info.continuationGCDataFldHnd); + GenTree* gcDataInd = LoadFromOffset(newContinuation, gcDataOffset, TYP_REF); + GenTree* storeAllocedObjectArr = m_comp->gtNewStoreLclVarNode(resumeObjectArrLclNum, gcDataInd); + LIR::AsRange(resumeBB).InsertAtEnd(LIR::SeqTree(m_comp, storeAllocedObjectArr)); - GenTree* store; - if (dsc->IsImplicitByRef()) - { - GenTree* baseAddr = m_comp->gtNewLclvNode(inf.LclNum, dsc->TypeGet()); - // TODO-CQ: Incoming data has no non-null GC refs, so this does not need write barriers. - // Backend does not handle GTF_IND_TGT_NOT_HEAP for STORE_BLK. - store = m_comp->gtNewStoreBlkNode(dsc->GetLayout(), baseAddr, value, - GTF_IND_NONFAULTING | GTF_IND_TGT_NOT_HEAP); - } - else - { - store = m_comp->gtNewStoreLclVarNode(inf.LclNum, value); - } + RestoreFromGCPointersOnResumption(resumeObjectArrLclNum, liveLocals, resumeBB); - LIR::AsRange(resumeBB).InsertAtEnd(LIR::SeqTree(m_comp, store)); + if (layout.ExceptionGCDataIndex != UINT_MAX) + { + storeResultBB = RethrowExceptionOnResumption(block, remainder, resumeObjectArrLclNum, layout, resumeBB); } } - unsigned resumeObjectArrLclNum = BAD_VAR_NUM; - BasicBlock* storeResultBB = resumeBB; + // Copy call return value. + if (layout.ReturnSize > 0) + { + CopyReturnValueOnResumption(call, callDefInfo, resumeByteArrLclNum, resumeObjectArrLclNum, layout, + storeResultBB); + } + + return resumeBB; +} - if (gcRefsCount > 0) +void Async2Transformation::RestoreFromDataOnResumption(unsigned resumeByteArrLclNum, + jitstd::vector& liveLocals, + BasicBlock* resumeBB) +{ + // Copy data + for (LiveLocalInfo& inf : liveLocals) { - resumeObjectArrLclNum = GetGCDataArrayVar(); + if (inf.DataSize <= 0) + { + continue; + } - newContinuation = m_comp->gtNewLclvNode(m_comp->lvaAsyncContinuationArg, TYP_REF); - unsigned gcDataOffset = m_comp->info.compCompHnd->getFieldOffset(m_async2Info.continuationGCDataFldHnd); - GenTree* gcDataInd = LoadFromOffset(newContinuation, gcDataOffset, TYP_REF); - GenTree* storeAllocedObjectArr = m_comp->gtNewStoreLclVarNode(resumeObjectArrLclNum, gcDataInd); - LIR::AsRange(resumeBB).InsertAtEnd(LIR::SeqTree(m_comp, storeAllocedObjectArr)); + LclVarDsc* dsc = m_comp->lvaGetDesc(inf.LclNum); - // Copy GC pointers - for (LiveLocalInfo& inf : m_liveLocals) + GenTree* byteArr = m_comp->gtNewLclvNode(resumeByteArrLclNum, TYP_REF); + unsigned offset = OFFSETOF__CORINFO_Array__data + inf.DataOffset; + GenTree* cns = m_comp->gtNewIconNode((ssize_t)offset, TYP_I_IMPL); + GenTree* addr = m_comp->gtNewOperNode(GT_ADD, TYP_BYREF, byteArr, cns); + + GenTree* value; + if ((dsc->TypeGet() == TYP_STRUCT) || dsc->IsImplicitByRef()) { - if (inf.GCDataCount <= 0) - { - continue; - } + value = m_comp->gtNewBlkIndir(dsc->GetLayout(), addr, GTF_IND_NONFAULTING); + } + else + { + value = m_comp->gtNewIndir(dsc->TypeGet(), addr, GTF_IND_NONFAULTING); + } - LclVarDsc* dsc = m_comp->lvaGetDesc(inf.LclNum); - if (dsc->TypeGet() == TYP_REF) - { - GenTree* objectArr = m_comp->gtNewLclvNode(resumeObjectArrLclNum, TYP_REF); - unsigned offset = OFFSETOF__CORINFO_Array__data + (inf.GCDataIndex * TARGET_POINTER_SIZE); - GenTree* value = LoadFromOffset(objectArr, offset, TYP_REF); - GenTree* store = m_comp->gtNewStoreLclVarNode(inf.LclNum, value); + GenTree* store; + if (dsc->IsImplicitByRef()) + { + GenTree* baseAddr = m_comp->gtNewLclvNode(inf.LclNum, dsc->TypeGet()); + // TODO-CQ: Incoming data has no non-null GC refs, so this does not need write barriers. + // Backend does not handle GTF_IND_TGT_NOT_HEAP for STORE_BLK. + store = m_comp->gtNewStoreBlkNode(dsc->GetLayout(), baseAddr, value, + GTF_IND_NONFAULTING | GTF_IND_TGT_NOT_HEAP); + } + else + { + store = m_comp->gtNewStoreLclVarNode(inf.LclNum, value); + } - LIR::AsRange(resumeBB).InsertAtEnd(LIR::SeqTree(m_comp, store)); - } - else + LIR::AsRange(resumeBB).InsertAtEnd(LIR::SeqTree(m_comp, store)); + } +} + +void Async2Transformation::RestoreFromGCPointersOnResumption(unsigned resumeObjectArrLclNum, + jitstd::vector& liveLocals, + BasicBlock* resumeBB) +{ + for (LiveLocalInfo& inf : liveLocals) + { + if (inf.GCDataCount <= 0) + { + continue; + } + + LclVarDsc* dsc = m_comp->lvaGetDesc(inf.LclNum); + if (dsc->TypeGet() == TYP_REF) + { + GenTree* objectArr = m_comp->gtNewLclvNode(resumeObjectArrLclNum, TYP_REF); + unsigned offset = OFFSETOF__CORINFO_Array__data + (inf.GCDataIndex * TARGET_POINTER_SIZE); + GenTree* value = LoadFromOffset(objectArr, offset, TYP_REF); + GenTree* store = m_comp->gtNewStoreLclVarNode(inf.LclNum, value); + + LIR::AsRange(resumeBB).InsertAtEnd(LIR::SeqTree(m_comp, store)); + } + else + { + assert((dsc->TypeGet() == TYP_STRUCT) || dsc->IsImplicitByRef()); + ClassLayout* layout = dsc->GetLayout(); + unsigned numSlots = layout->GetSlotCount(); + unsigned gcRefIndex = 0; + for (unsigned i = 0; i < numSlots; i++) { - assert((dsc->TypeGet() == TYP_STRUCT) || dsc->IsImplicitByRef()); - ClassLayout* layout = dsc->GetLayout(); - unsigned numSlots = layout->GetSlotCount(); - unsigned gcRefIndex = 0; - for (unsigned i = 0; i < numSlots; i++) + var_types gcPtrType = layout->GetGCPtrType(i); + assert((gcPtrType == TYP_I_IMPL) || (gcPtrType == TYP_REF)); + if (gcPtrType != TYP_REF) { - var_types gcPtrType = layout->GetGCPtrType(i); - assert((gcPtrType == TYP_I_IMPL) || (gcPtrType == TYP_REF)); - if (gcPtrType != TYP_REF) - { - continue; - } + continue; + } - GenTree* objectArr = m_comp->gtNewLclvNode(resumeObjectArrLclNum, TYP_REF); - unsigned offset = - OFFSETOF__CORINFO_Array__data + ((inf.GCDataIndex + gcRefIndex) * TARGET_POINTER_SIZE); - GenTree* value = LoadFromOffset(objectArr, offset, TYP_REF); - GenTree* store; - if (dsc->IsImplicitByRef()) - { - GenTree* baseAddr = m_comp->gtNewLclvNode(inf.LclNum, dsc->TypeGet()); - store = StoreAtOffset(baseAddr, i * TARGET_POINTER_SIZE, value); - // Implicit byref args are never on heap today, skip write barriers. - // TODO-CQ: Remove this once all implicit byrefs are TYP_I_IMPL typed. - store->gtFlags |= GTF_IND_TGT_NOT_HEAP; - } - else - { - store = m_comp->gtNewStoreLclFldNode(inf.LclNum, TYP_REF, i * TARGET_POINTER_SIZE, value); - } + GenTree* objectArr = m_comp->gtNewLclvNode(resumeObjectArrLclNum, TYP_REF); + unsigned offset = + OFFSETOF__CORINFO_Array__data + ((inf.GCDataIndex + gcRefIndex) * TARGET_POINTER_SIZE); + GenTree* value = LoadFromOffset(objectArr, offset, TYP_REF); + GenTree* store; + if (dsc->IsImplicitByRef()) + { + GenTree* baseAddr = m_comp->gtNewLclvNode(inf.LclNum, dsc->TypeGet()); + store = StoreAtOffset(baseAddr, i * TARGET_POINTER_SIZE, value); + // Implicit byref args are never on heap today, skip write barriers. + // TODO-CQ: Remove this once all implicit byrefs are TYP_I_IMPL typed. + store->gtFlags |= GTF_IND_TGT_NOT_HEAP; + } + else + { + store = m_comp->gtNewStoreLclFldNode(inf.LclNum, TYP_REF, i * TARGET_POINTER_SIZE, value); + } - LIR::AsRange(resumeBB).InsertAtEnd(LIR::SeqTree(m_comp, store)); + LIR::AsRange(resumeBB).InsertAtEnd(LIR::SeqTree(m_comp, store)); - gcRefIndex++; - } + gcRefIndex++; } } + } +} - if (exceptionGCDataIndex != UINT_MAX) - { - JITDUMP(" We need to rethrow an exception\n"); +BasicBlock* Async2Transformation::RethrowExceptionOnResumption(BasicBlock* block, + BasicBlock* remainder, + unsigned resumeObjectArrLclNum, + const ContinuationLayout& layout, + BasicBlock* resumeBB) +{ + JITDUMP(" We need to rethrow an exception\n"); - BasicBlock* rethrowExceptionBB = - m_comp->fgNewBBinRegion(BBJ_THROW, block, /* runRarely */ true, /* insertAtEnd */ true); - JITDUMP(" Created " FMT_BB " to rethrow exception on resumption\n", rethrowExceptionBB->bbNum); + BasicBlock* rethrowExceptionBB = + m_comp->fgNewBBinRegion(BBJ_THROW, block, /* runRarely */ true, /* insertAtEnd */ true); + JITDUMP(" Created " FMT_BB " to rethrow exception on resumption\n", rethrowExceptionBB->bbNum); - storeResultBB = m_comp->fgNewBBafter(BBJ_ALWAYS, resumeBB, true); - JITDUMP(" Created " FMT_BB " to store result when resuming with no exception\n", storeResultBB->bbNum); + BasicBlock* storeResultBB = m_comp->fgNewBBafter(BBJ_ALWAYS, resumeBB, true); + JITDUMP(" Created " FMT_BB " to store result when resuming with no exception\n", storeResultBB->bbNum); - FlowEdge* rethrowEdge = m_comp->fgAddRefPred(rethrowExceptionBB, resumeBB); - FlowEdge* storeResultEdge = m_comp->fgAddRefPred(storeResultBB, resumeBB); + FlowEdge* rethrowEdge = m_comp->fgAddRefPred(rethrowExceptionBB, resumeBB); + FlowEdge* storeResultEdge = m_comp->fgAddRefPred(storeResultBB, resumeBB); - assert(resumeBB->KindIs(BBJ_ALWAYS)); + assert(resumeBB->KindIs(BBJ_ALWAYS)); + m_comp->fgRemoveRefPred(resumeBB->GetTargetEdge()); - resumeBB->SetCond(rethrowEdge, storeResultEdge); - rethrowEdge->setLikelihood(0); - storeResultEdge->setLikelihood(1); - rethrowExceptionBB->inheritWeightPercentage(resumeBB, 0); - storeResultBB->inheritWeightPercentage(resumeBB, 100); - JITDUMP(" Resumption " FMT_BB " becomes BBJ_COND to check for non-null exception\n", resumeBB->bbNum); + resumeBB->SetCond(rethrowEdge, storeResultEdge); + rethrowEdge->setLikelihood(0); + storeResultEdge->setLikelihood(1); + rethrowExceptionBB->inheritWeightPercentage(resumeBB, 0); + storeResultBB->inheritWeightPercentage(resumeBB, 100); + JITDUMP(" Resumption " FMT_BB " becomes BBJ_COND to check for non-null exception\n", resumeBB->bbNum); - m_comp->fgRemoveRefPred(remainderEdge); - remainderEdge = m_comp->fgAddRefPred(remainder, storeResultBB); + FlowEdge* remainderEdge = m_comp->fgAddRefPred(remainder, storeResultBB); + storeResultBB->SetTargetEdge(remainderEdge); - storeResultBB->SetTargetEdge(remainderEdge); + m_lastResumptionBB = storeResultBB; - m_lastResumptionBB = storeResultBB; + // Check if we have an exception. + unsigned exceptionLclNum = GetExceptionVar(); + GenTree* objectArr = m_comp->gtNewLclvNode(resumeObjectArrLclNum, TYP_REF); + unsigned exceptionOffset = OFFSETOF__CORINFO_Array__data + layout.ExceptionGCDataIndex * TARGET_POINTER_SIZE; + GenTree* exceptionInd = LoadFromOffset(objectArr, exceptionOffset, TYP_REF); + GenTree* storeException = m_comp->gtNewStoreLclVarNode(exceptionLclNum, exceptionInd); + LIR::AsRange(resumeBB).InsertAtEnd(LIR::SeqTree(m_comp, storeException)); - // Check if we have an exception. - unsigned exceptionLclNum = GetExceptionVar(); - GenTree* objectArr = m_comp->gtNewLclvNode(resumeObjectArrLclNum, TYP_REF); - unsigned exceptionOffset = OFFSETOF__CORINFO_Array__data + exceptionGCDataIndex * TARGET_POINTER_SIZE; - GenTree* exceptionInd = LoadFromOffset(objectArr, exceptionOffset, TYP_REF); - GenTree* storeException = m_comp->gtNewStoreLclVarNode(exceptionLclNum, exceptionInd); - LIR::AsRange(resumeBB).InsertAtEnd(LIR::SeqTree(m_comp, storeException)); + GenTree* exception = m_comp->gtNewLclVarNode(exceptionLclNum, TYP_REF); + GenTree* null = m_comp->gtNewNull(); + GenTree* neNull = m_comp->gtNewOperNode(GT_NE, TYP_INT, exception, null); + GenTree* jtrue = m_comp->gtNewOperNode(GT_JTRUE, TYP_VOID, neNull); + LIR::AsRange(resumeBB).InsertAtEnd(exception, null, neNull, jtrue); - GenTree* exception = m_comp->gtNewLclVarNode(exceptionLclNum, TYP_REF); - GenTree* null = m_comp->gtNewNull(); - GenTree* neNull = m_comp->gtNewOperNode(GT_NE, TYP_INT, exception, null); - GenTree* jtrue = m_comp->gtNewOperNode(GT_JTRUE, TYP_VOID, neNull); - LIR::AsRange(resumeBB).InsertAtEnd(exception, null, neNull, jtrue); + exception = m_comp->gtNewLclVarNode(exceptionLclNum, TYP_REF); + GenTreeCall* rethrowException = m_comp->gtNewHelperCallNode(CORINFO_HELP_THROWEXACT, TYP_VOID, exception); - exception = m_comp->gtNewLclVarNode(exceptionLclNum, TYP_REF); - GenTreeCall* rethrowException = m_comp->gtNewHelperCallNode(CORINFO_HELP_THROWEXACT, TYP_VOID, exception); + m_comp->compCurBB = rethrowExceptionBB; + m_comp->fgMorphTree(rethrowException); - m_comp->compCurBB = rethrowExceptionBB; - m_comp->fgMorphTree(rethrowException); + LIR::AsRange(rethrowExceptionBB).InsertAtEnd(LIR::SeqTree(m_comp, rethrowException)); - LIR::AsRange(rethrowExceptionBB).InsertAtEnd(LIR::SeqTree(m_comp, rethrowException)); + storeResultBB->SetFlags(BBF_ASYNC_RESUMPTION); + JITDUMP(" Added " FMT_BB " to rethrow exception at suspension point\n", rethrowExceptionBB->bbNum); - storeResultBB->SetFlags(BBF_ASYNC_RESUMPTION); - JITDUMP(" Added " FMT_BB " to rethrow exception at suspension point\n", rethrowExceptionBB->bbNum); - } - } + return storeResultBB; +} - // Copy call return value. - if (storeResultNode != nullptr) +void Async2Transformation::CopyReturnValueOnResumption(GenTreeCall* call, + const CallDefinitionInfo& callDefInfo, + unsigned resumeByteArrLclNum, + unsigned resumeObjectArrLclNum, + const ContinuationLayout& layout, + BasicBlock* storeResultBB) +{ + GenTree* resultBase; + unsigned resultOffset; + GenTreeFlags resultIndirFlags = GTF_IND_NONFAULTING; + if (layout.ReturnInGCData) { - GenTree* resultBase; - unsigned resultOffset; - GenTreeFlags resultIndirFlags = GTF_IND_NONFAULTING; - if (returnInGCData) - { - assert(resumeObjectArrLclNum != BAD_VAR_NUM); - resultBase = m_comp->gtNewLclvNode(resumeObjectArrLclNum, TYP_REF); + assert(resumeObjectArrLclNum != BAD_VAR_NUM); + resultBase = m_comp->gtNewLclvNode(resumeObjectArrLclNum, TYP_REF); - if (call->gtReturnType == TYP_STRUCT) - { - // Boxed struct. - resultBase = LoadFromOffset(resultBase, OFFSETOF__CORINFO_Array__data, TYP_REF); - resultOffset = TARGET_POINTER_SIZE; // Offset of data inside box - } - else - { - assert(call->gtReturnType == TYP_REF); - resultOffset = OFFSETOF__CORINFO_Array__data; - } + if (call->gtReturnType == TYP_STRUCT) + { + // Boxed struct. + resultBase = LoadFromOffset(resultBase, OFFSETOF__CORINFO_Array__data, TYP_REF); + resultOffset = TARGET_POINTER_SIZE; // Offset of data inside box } else { - assert(resumeByteArrLclNum != BAD_VAR_NUM); - resultBase = m_comp->gtNewLclvNode(resumeByteArrLclNum, TYP_REF); - resultOffset = OFFSETOF__CORINFO_Array__data + returnValDataOffset; - if (returnValDataOffset != 0) - resultIndirFlags = GTF_IND_UNALIGNED; + assert(call->gtReturnType == TYP_REF); + resultOffset = OFFSETOF__CORINFO_Array__data; } + } + else + { + assert(resumeByteArrLclNum != BAD_VAR_NUM); + resultBase = m_comp->gtNewLclvNode(resumeByteArrLclNum, TYP_REF); + resultOffset = OFFSETOF__CORINFO_Array__data + layout.ReturnValDataOffset; + if (layout.ReturnValDataOffset != 0) + resultIndirFlags = GTF_IND_UNALIGNED; + } - LclVarDsc* resultLcl = m_comp->lvaGetDesc(storeResultNode); - assert((resultLcl->TypeGet() == TYP_STRUCT) == (call->gtReturnType == TYP_STRUCT)); + assert(callDefInfo.DefinitionNode != nullptr); + LclVarDsc* resultLcl = m_comp->lvaGetDesc(callDefInfo.DefinitionNode); + assert((resultLcl->TypeGet() == TYP_STRUCT) == (call->gtReturnType == TYP_STRUCT)); - // TODO-TP: We can use liveness to avoid generating a lot of this IR. - if (call->gtReturnType == TYP_STRUCT) + // TODO-TP: We can use liveness to avoid generating a lot of this IR. + if (call->gtReturnType == TYP_STRUCT) + { + if (m_comp->lvaGetPromotionType(resultLcl) != Compiler::PROMOTION_TYPE_INDEPENDENT) { - if (m_comp->lvaGetPromotionType(resultLcl) != Compiler::PROMOTION_TYPE_INDEPENDENT) + GenTree* resultOffsetNode = m_comp->gtNewIconNode((ssize_t)resultOffset, TYP_I_IMPL); + GenTree* resultAddr = m_comp->gtNewOperNode(GT_ADD, TYP_BYREF, resultBase, resultOffsetNode); + GenTree* resultData = m_comp->gtNewBlkIndir(layout.ReturnStructLayout, resultAddr, resultIndirFlags); + GenTree* storeResult; + if ((callDefInfo.DefinitionNode->GetLclOffs() == 0) && + ClassLayout::AreCompatible(resultLcl->GetLayout(), layout.ReturnStructLayout)) { - GenTree* resultOffsetNode = m_comp->gtNewIconNode((ssize_t)resultOffset, TYP_I_IMPL); - GenTree* resultAddr = m_comp->gtNewOperNode(GT_ADD, TYP_BYREF, resultBase, resultOffsetNode); - GenTree* resultData = m_comp->gtNewBlkIndir(returnStructLayout, resultAddr, resultIndirFlags); - GenTree* storeResult; - if ((storeResultNode->GetLclOffs() == 0) && - ClassLayout::AreCompatible(resultLcl->GetLayout(), returnStructLayout)) - { - storeResult = m_comp->gtNewStoreLclVarNode(storeResultNode->GetLclNum(), resultData); - } - else - { - storeResult = - m_comp->gtNewStoreLclFldNode(storeResultNode->GetLclNum(), TYP_STRUCT, returnStructLayout, - storeResultNode->GetLclOffs(), resultData); - } - - LIR::AsRange(storeResultBB).InsertAtEnd(LIR::SeqTree(m_comp, storeResult)); + storeResult = m_comp->gtNewStoreLclVarNode(callDefInfo.DefinitionNode->GetLclNum(), resultData); } else { - assert(retbufArg == nullptr); // Locals defined through retbufs are never independently promoted. - - if ((resultLcl->lvFieldCnt > 1) && !resultBase->OperIsLocal()) - { - unsigned resultBaseVar = GetResultBaseVar(); - GenTree* storeResultBase = m_comp->gtNewStoreLclVarNode(resultBaseVar, resultBase); - LIR::AsRange(storeResultBB).InsertAtEnd(LIR::SeqTree(m_comp, storeResultBase)); - - resultBase = m_comp->gtNewLclVarNode(resultBaseVar, TYP_REF); - } - - assert(storeResultNode->OperIs(GT_STORE_LCL_VAR)); - for (unsigned i = 0; i < resultLcl->lvFieldCnt; i++) - { - unsigned fieldLclNum = resultLcl->lvFieldLclStart + i; - LclVarDsc* fieldDsc = m_comp->lvaGetDesc(fieldLclNum); - - unsigned fldOffset = resultOffset + fieldDsc->lvFldOffset; - GenTree* value = LoadFromOffset(resultBase, fldOffset, fieldDsc->TypeGet(), resultIndirFlags); - GenTree* store = m_comp->gtNewStoreLclVarNode(fieldLclNum, value); - LIR::AsRange(storeResultBB).InsertAtEnd(LIR::SeqTree(m_comp, store)); - - if (i + 1 != resultLcl->lvFieldCnt) - { - resultBase = m_comp->gtCloneExpr(resultBase); - } - } + storeResult = m_comp->gtNewStoreLclFldNode(callDefInfo.DefinitionNode->GetLclNum(), TYP_STRUCT, + layout.ReturnStructLayout, + callDefInfo.DefinitionNode->GetLclOffs(), resultData); } + + LIR::AsRange(storeResultBB).InsertAtEnd(LIR::SeqTree(m_comp, storeResult)); } else { - GenTree* value = LoadFromOffset(resultBase, resultOffset, call->gtReturnType, resultIndirFlags); + assert(!call->gtArgs.HasRetBuffer()); // Locals defined through retbufs are never independently promoted. - GenTree* storeResult; - if (storeResultNode->OperIs(GT_STORE_LCL_VAR)) + if ((resultLcl->lvFieldCnt > 1) && !resultBase->OperIsLocal()) { - storeResult = m_comp->gtNewStoreLclVarNode(storeResultNode->GetLclNum(), value); + unsigned resultBaseVar = GetResultBaseVar(); + GenTree* storeResultBase = m_comp->gtNewStoreLclVarNode(resultBaseVar, resultBase); + LIR::AsRange(storeResultBB).InsertAtEnd(LIR::SeqTree(m_comp, storeResultBase)); + + resultBase = m_comp->gtNewLclVarNode(resultBaseVar, TYP_REF); } - else + + assert(callDefInfo.DefinitionNode->OperIs(GT_STORE_LCL_VAR)); + for (unsigned i = 0; i < resultLcl->lvFieldCnt; i++) { - storeResult = m_comp->gtNewStoreLclFldNode(storeResultNode->GetLclNum(), storeResultNode->TypeGet(), - storeResultNode->GetLclOffs(), value); - } + unsigned fieldLclNum = resultLcl->lvFieldLclStart + i; + LclVarDsc* fieldDsc = m_comp->lvaGetDesc(fieldLclNum); - LIR::AsRange(storeResultBB).InsertAtEnd(LIR::SeqTree(m_comp, storeResult)); - } - } + unsigned fldOffset = resultOffset + fieldDsc->lvFldOffset; + GenTree* value = LoadFromOffset(resultBase, fldOffset, fieldDsc->TypeGet(), resultIndirFlags); + GenTree* store = m_comp->gtNewStoreLclVarNode(fieldLclNum, value); + LIR::AsRange(storeResultBB).InsertAtEnd(LIR::SeqTree(m_comp, store)); - m_resumptionBBs.push_back(resumeBB); -} - -GenTreeCall* Async2Transformation::CreateAllocContinuationCall(AsyncLiveness& life, - GenTree* prevContinuation, - unsigned gcRefsCount, - unsigned dataSize) -{ - GenTree* gcRefsCountNode = m_comp->gtNewIconNode((ssize_t)gcRefsCount, TYP_I_IMPL); - GenTree* dataSizeNode = m_comp->gtNewIconNode((ssize_t)dataSize, TYP_I_IMPL); - // If VM requests that we report the method handle, or if we have a shared generic context method handle - // that is live here, then we need to call a different helper. - GenTree* methodHandleArg = nullptr; - GenTree* classHandleArg = nullptr; - if (((m_comp->info.compMethodInfo->options & CORINFO_GENERICS_CTXT_FROM_METHODDESC) != 0) && - life.IsLive(m_comp->info.compTypeCtxtArg)) - { - methodHandleArg = m_comp->gtNewLclvNode(m_comp->info.compTypeCtxtArg, TYP_I_IMPL); - } - else if (((m_comp->info.compMethodInfo->options & CORINFO_GENERICS_CTXT_FROM_METHODTABLE) != 0) && - life.IsLive(m_comp->info.compTypeCtxtArg)) - { - classHandleArg = m_comp->gtNewLclvNode(m_comp->info.compTypeCtxtArg, TYP_I_IMPL); + if (i + 1 != resultLcl->lvFieldCnt) + { + resultBase = m_comp->gtCloneExpr(resultBase); + } + } + } } - else if (m_async2Info.continuationsNeedMethodHandle) + else { - methodHandleArg = m_comp->gtNewIconEmbMethHndNode(m_comp->info.compMethodHnd); - } + GenTree* value = LoadFromOffset(resultBase, resultOffset, call->gtReturnType, resultIndirFlags); - if (methodHandleArg != nullptr) - { - return m_comp->gtNewHelperCallNode(CORINFO_HELP_ALLOC_CONTINUATION_METHOD, TYP_REF, prevContinuation, - gcRefsCountNode, dataSizeNode, methodHandleArg); - } + GenTree* storeResult; + if (callDefInfo.DefinitionNode->OperIs(GT_STORE_LCL_VAR)) + { + storeResult = m_comp->gtNewStoreLclVarNode(callDefInfo.DefinitionNode->GetLclNum(), value); + } + else + { + storeResult = m_comp->gtNewStoreLclFldNode(callDefInfo.DefinitionNode->GetLclNum(), + callDefInfo.DefinitionNode->TypeGet(), + callDefInfo.DefinitionNode->GetLclOffs(), value); + } - if (classHandleArg != nullptr) - { - return m_comp->gtNewHelperCallNode(CORINFO_HELP_ALLOC_CONTINUATION_CLASS, TYP_REF, prevContinuation, - gcRefsCountNode, dataSizeNode, classHandleArg); + LIR::AsRange(storeResultBB).InsertAtEnd(LIR::SeqTree(m_comp, storeResult)); } - - return m_comp->gtNewHelperCallNode(CORINFO_HELP_ALLOC_CONTINUATION, TYP_REF, prevContinuation, gcRefsCountNode, - dataSizeNode); } GenTreeIndir* Async2Transformation::LoadFromOffset(GenTree* base, @@ -1274,10 +1483,10 @@ GenTree* Async2Transformation::CreateFunctionTargetAddr(CORINFO_METHOD_HANDLE return con; } -void Async2Transformation::LiftLIREdges(BasicBlock* block, - GenTree* beyond, - jitstd::vector& defs, - jitstd::vector& liveLocals) +void Async2Transformation::LiftLIREdges(BasicBlock* block, + GenTree* beyond, + const jitstd::vector& defs, + jitstd::vector& liveLocals) { if (defs.size() <= 0) { diff --git a/src/coreclr/jit/async.h b/src/coreclr/jit/async.h index 046f9976556363..f99d5817f3bc08 100644 --- a/src/coreclr/jit/async.h +++ b/src/coreclr/jit/async.h @@ -1,6 +1,25 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +struct ContinuationLayout +{ + unsigned DataSize = 0; + unsigned GCRefsCount = 0; + ClassLayout* ReturnStructLayout = nullptr; + unsigned ReturnSize = 0; + bool ReturnInGCData = false; + unsigned ReturnValDataOffset = UINT_MAX; + unsigned ExceptionGCDataIndex = UINT_MAX; +}; + +struct CallDefinitionInfo +{ + GenTreeLclVarCommon* DefinitionNode = nullptr; + + // Where to insert new IR for suspension checks. + GenTree* InsertAfter = nullptr; +}; + class Async2Transformation { friend class AsyncLiveness; @@ -21,7 +40,7 @@ class Async2Transformation }; Compiler* m_comp; - jitstd::vector m_liveLocals; + jitstd::vector m_liveLocalsScratch; CORINFO_ASYNC2_INFO m_async2Info; jitstd::vector m_resumptionBBs; CORINFO_METHOD_HANDLE m_resumeStub = NO_METHOD_HANDLE; @@ -36,10 +55,10 @@ class Async2Transformation BasicBlock* m_lastResumptionBB = nullptr; BasicBlock* m_sharedReturnBB = nullptr; - void LiftLIREdges(BasicBlock* block, - GenTree* beyond, - jitstd::vector& defs, - jitstd::vector& liveLocals); + void LiftLIREdges(BasicBlock* block, + GenTree* beyond, + const jitstd::vector& defs, + jitstd::vector& liveLocals); bool IsLive(unsigned lclNum); void Transform(BasicBlock* block, GenTreeCall* call, @@ -47,10 +66,58 @@ class Async2Transformation class AsyncLiveness& life, BasicBlock** remainder); + void CreateLiveSetForSuspension(BasicBlock* block, + GenTreeCall* call, + const jitstd::vector& defs, + AsyncLiveness& life, + jitstd::vector& liveLocals); + ContinuationLayout LayOutContinuation(BasicBlock* block, + GenTreeCall* call, + jitstd::vector& liveLocals); + + CallDefinitionInfo CanonicalizeCallDefinition(BasicBlock* block, GenTreeCall* call, AsyncLiveness& life); + + BasicBlock* CreateSuspension(BasicBlock* block, + unsigned stateNum, + const ContinuationLayout& layout, + AsyncLiveness& life, + jitstd::vector& liveLocals); GenTreeCall* CreateAllocContinuationCall(AsyncLiveness& life, GenTree* prevContinuation, unsigned gcRefsCount, unsigned int dataSize); + void FillInGCPointersOnSuspension(jitstd::vector& liveLocals, BasicBlock* suspendBB); + void FillInDataOnSuspension(jitstd::vector& liveLocals, BasicBlock* suspendBB); + void CreateCheckAndSuspendAfterCall(BasicBlock* block, + const CallDefinitionInfo& callDefInfo, + AsyncLiveness& life, + BasicBlock* suspendBB, + BasicBlock** remainder); + + BasicBlock* CreateResumption(BasicBlock* block, + BasicBlock* remainder, + GenTreeCall* call, + const CallDefinitionInfo& callDefInfo, + unsigned stateNum, + const ContinuationLayout& layout, + jitstd::vector& liveLocals); + void RestoreFromDataOnResumption(unsigned resumeByteArrLclNum, + jitstd::vector& liveLocals, + BasicBlock* resumeBB); + void RestoreFromGCPointersOnResumption(unsigned resumeObjectArrLclNum, + jitstd::vector& liveLocals, + BasicBlock* resumeBB); + BasicBlock* RethrowExceptionOnResumption(BasicBlock* block, + BasicBlock* remainder, + unsigned resumeObjectArrLclNum, + const ContinuationLayout& layout, + BasicBlock* resumeBB); + void CopyReturnValueOnResumption(GenTreeCall* call, + const CallDefinitionInfo& callDefInfo, + unsigned resumeByteArrLclNum, + unsigned resumeObjectArrLclNum, + const ContinuationLayout& layout, + BasicBlock* storeResultBB); GenTreeIndir* LoadFromOffset(GenTree* base, unsigned offset, @@ -71,7 +138,7 @@ class Async2Transformation public: Async2Transformation(Compiler* comp) : m_comp(comp) - , m_liveLocals(comp->getAllocator(CMK_Async2)) + , m_liveLocalsScratch(comp->getAllocator(CMK_Async2)) , m_resumptionBBs(comp->getAllocator(CMK_Async2)) { } From 3a76d37334f8a6f0673c80191b40d3408969a0f0 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Tue, 1 Apr 2025 11:47:13 +0200 Subject: [PATCH 182/203] Write function headers for new JIT functions --- src/coreclr/jit/async.cpp | 394 ++++++++++++++++++++++++++++++-------- src/coreclr/jit/async.h | 107 ++++++----- 2 files changed, 370 insertions(+), 131 deletions(-) diff --git a/src/coreclr/jit/async.cpp b/src/coreclr/jit/async.cpp index 34731e1c42a672..5361493e1ec909 100644 --- a/src/coreclr/jit/async.cpp +++ b/src/coreclr/jit/async.cpp @@ -24,7 +24,7 @@ class AsyncLiveness void StartBlock(BasicBlock* block); void Update(GenTree* node); bool IsLive(unsigned lclNum); - void GetLiveLocals(jitstd::vector& liveLocals, unsigned fullyDefinedRetBufLcl); + void GetLiveLocals(jitstd::vector& liveLocals, unsigned fullyDefinedRetBufLcl); private: bool IsLocalCaptureUnnecessary(unsigned lclNum); @@ -210,14 +210,13 @@ bool AsyncLiveness::IsLive(unsigned lclNum) // liveLocals - Vector to add live local information into // fullyDefinedRetBufLcl - Local to skip even if live // -void AsyncLiveness::GetLiveLocals(jitstd::vector& liveLocals, - unsigned fullyDefinedRetBufLcl) +void AsyncLiveness::GetLiveLocals(jitstd::vector& liveLocals, unsigned fullyDefinedRetBufLcl) { for (unsigned lclNum = 0; lclNum < m_numVars; lclNum++) { if ((lclNum != fullyDefinedRetBufLcl) && IsLive(lclNum)) { - liveLocals.push_back(Async2Transformation::LiveLocalInfo(lclNum)); + liveLocals.push_back(LiveLocalInfo(lclNum)); } } } @@ -388,7 +387,7 @@ PhaseStatus Async2Transformation::Run() // Transform a single async2 call in the specified block. // // Parameters: -// block - The block containig the async2 call +// block - The block containing the async2 call // call - The async2 call // defs - Current live LIR edges // life - Liveness information about live locals @@ -429,11 +428,11 @@ void Async2Transformation::Transform( unsigned stateNum = (unsigned)m_resumptionBBs.size(); JITDUMP(" Assigned state %u\n", stateNum); - BasicBlock* suspendBB = CreateSuspension(block, stateNum, layout, life, liveLocals); + BasicBlock* suspendBB = CreateSuspension(block, stateNum, life, liveLocals); CreateCheckAndSuspendAfterCall(block, callDefInfo, life, suspendBB, remainder); - BasicBlock* resumeBB = CreateResumption(block, *remainder, call, callDefInfo, stateNum, layout, liveLocals); + BasicBlock* resumeBB = CreateResumption(block, *remainder, call, callDefInfo, stateNum, layout); m_resumptionBBs.push_back(resumeBB); } @@ -444,11 +443,11 @@ void Async2Transformation::Transform( // specified call. // // Parameters: -// block - The block containig the async2 call +// block - The block containing the async2 call // call - The async2 call // defs - Current live LIR edges // life - Liveness information about live locals -// livenessInfo - Information about live state +// liveLocals - Information about each live local. // void Async2Transformation::CreateLiveSetForSuspension(BasicBlock* block, GenTreeCall* call, @@ -477,7 +476,7 @@ void Async2Transformation::CreateLiveSetForSuspension(BasicBlock* } life.GetLiveLocals(liveLocals, fullyDefinedRetBufLcl); - LiftLIREdges(block, call, defs, liveLocals); + LiftLIREdges(block, defs, liveLocals); #ifdef DEBUG if (m_comp->verbose) @@ -499,11 +498,72 @@ void Async2Transformation::CreateLiveSetForSuspension(BasicBlock* #endif } +//------------------------------------------------------------------------ +// Async2Transformation::LiftLIREdges: +// Create locals capturing outstanding LIR edges and add information +// indicating that these locals are live. +// +// Parameters: +// block - The block containing the definitions of the LIR edges +// defs - Current outstanding LIR edges +// liveLocals - [out] Vector to add new live local information into +// +void Async2Transformation::LiftLIREdges(BasicBlock* block, + const jitstd::vector& defs, + jitstd::vector& liveLocals) +{ + if (defs.size() <= 0) + { + return; + } + + for (GenTree* tree : defs) + { + // TODO-CQ: Enable this. It currently breaks our recognition of how the + // call is stored. + // if (tree->OperIs(GT_LCL_VAR)) + //{ + // LclVarDsc* dsc = m_comp->lvaGetDesc(tree->AsLclVarCommon()); + // if (!dsc->IsAddressExposed()) + // { + // // No interference by IR invariants. + // LIR::AsRange(block).Remove(tree); + // LIR::AsRange(block).InsertAfter(beyond, tree); + // continue; + // } + //} + + LIR::Use use; + bool gotUse = LIR::AsRange(block).TryGetUse(tree, &use); + assert(gotUse); // Defs list should not contain unused values. + + unsigned newLclNum = use.ReplaceWithLclVar(m_comp); + liveLocals.push_back(LiveLocalInfo(newLclNum)); + GenTree* newUse = use.Def(); + LIR::AsRange(block).Remove(newUse); + LIR::AsRange(block).InsertBefore(use.User(), newUse); + } +} + +//------------------------------------------------------------------------ +// Async2Transformation::LayOutContinuation: +// Create the layout of the GC pointer and data arrays in the continuation +// object. +// +// Parameters: +// block - The block containing the async2 call +// call - The async2 call +// liveLocals - [in, out] Information about each live local. Size/alignment +// information is read and offset/index information is written. +// +// Returns: +// Layout information. +// ContinuationLayout Async2Transformation::LayOutContinuation(BasicBlock* block, GenTreeCall* call, jitstd::vector& liveLocals) { - ContinuationLayout layout; + ContinuationLayout layout(liveLocals); for (LiveLocalInfo& inf : liveLocals) { @@ -652,6 +712,20 @@ ContinuationLayout Async2Transformation::LayOutContinuation(BasicBlock* return layout; } +//------------------------------------------------------------------------ +// Async2Transformation::CanonicalizeCallDefinition: +// Put the call definition in a canonical form. This ensures that either the +// value is defined by a LCL_ADDR retbuffer or by a +// STORE_LCL_VAR/STORE_LCL_FLD that follows the call node. +// +// Parameters: +// block - The block containing the async2 call +// call - The async2 call +// life - Liveness information about live locals +// +// Returns: +// Information about the definition after canonicalization. +// CallDefinitionInfo Async2Transformation::CanonicalizeCallDefinition(BasicBlock* block, GenTreeCall* call, AsyncLiveness& life) @@ -702,11 +776,24 @@ CallDefinitionInfo Async2Transformation::CanonicalizeCallDefinition(BasicBlock* return callDefInfo; } -BasicBlock* Async2Transformation::CreateSuspension(BasicBlock* block, - unsigned stateNum, - const ContinuationLayout& layout, - AsyncLiveness& life, - jitstd::vector& liveLocals) +//------------------------------------------------------------------------ +// Async2Transformation::CreateSuspension: +// Create the basic block that when branched to suspends execution after the +// specified async2 call. +// +// Parameters: +// block - The block containing the async2 call +// stateNum - State number assigned to this suspension point +// life - Liveness information about live locals +// layout - Layout information for the continuation object +// +// Returns: +// The new basic block that was created. +// +BasicBlock* Async2Transformation::CreateSuspension(BasicBlock* block, + unsigned stateNum, + AsyncLiveness& life, + const ContinuationLayout& layout) { if (m_lastSuspensionBB == nullptr) { @@ -771,12 +858,12 @@ BasicBlock* Async2Transformation::CreateSuspension(BasicBlock* if (layout.GCRefsCount > 0) { - FillInGCPointersOnSuspension(liveLocals, suspendBB); + FillInGCPointersOnSuspension(layout.Locals, suspendBB); } if (layout.DataSize > 0) { - FillInDataOnSuspension(liveLocals, suspendBB); + FillInDataOnSuspension(layout.Locals, suspendBB); } if (suspendBB->KindIs(BBJ_RETURN)) @@ -789,6 +876,19 @@ BasicBlock* Async2Transformation::CreateSuspension(BasicBlock* return suspendBB; } +//------------------------------------------------------------------------ +// Async2Transformation::CreateAllocContinuationCall: +// Create a call to the JIT helper that allocates a continuation. +// +// Parameters: +// life - Liveness information about live locals +// prevContinuation - IR node that has the value of the previous continuation object +// gcRefsCount - Number of GC refs to allocate in the continuation object +// dataSize - Number of bytes to allocate in the continuation object +// +// Returns: +// IR node representing the allocation. +// GenTreeCall* Async2Transformation::CreateAllocContinuationCall(AsyncLiveness& life, GenTree* prevContinuation, unsigned gcRefsCount, @@ -797,7 +897,7 @@ GenTreeCall* Async2Transformation::CreateAllocContinuationCall(AsyncLiveness& li GenTree* gcRefsCountNode = m_comp->gtNewIconNode((ssize_t)gcRefsCount, TYP_I_IMPL); GenTree* dataSizeNode = m_comp->gtNewIconNode((ssize_t)dataSize, TYP_I_IMPL); // If VM requests that we report the method handle, or if we have a shared generic context method handle - // that is live here, then we need to call a different helper. + // that is live here, then we need to call a different helper to keep the loader alive. GenTree* methodHandleArg = nullptr; GenTree* classHandleArg = nullptr; if (((m_comp->info.compMethodInfo->options & CORINFO_GENERICS_CTXT_FROM_METHODDESC) != 0) && @@ -831,8 +931,16 @@ GenTreeCall* Async2Transformation::CreateAllocContinuationCall(AsyncLiveness& li dataSizeNode); } -void Async2Transformation::FillInGCPointersOnSuspension(jitstd::vector& liveLocals, - BasicBlock* suspendBB) +//------------------------------------------------------------------------ +// Async2Transformation::FillInGCPointersOnSuspension: +// Create IR that fills the GC pointers of the continuation object. +// +// Parameters: +// liveLocals - Information about each live local. +// suspendBB - Basic block to add IR to. +// +void Async2Transformation::FillInGCPointersOnSuspension(const jitstd::vector& liveLocals, + BasicBlock* suspendBB) { unsigned objectArrLclNum = GetGCDataArrayVar(); @@ -842,7 +950,7 @@ void Async2Transformation::FillInGCPointersOnSuspension(jitstd::vectorgtNewStoreLclVarNode(objectArrLclNum, gcDataInd); LIR::AsRange(suspendBB).InsertAtEnd(LIR::SeqTree(m_comp, storeAllocedObjectArr)); - for (LiveLocalInfo& inf : liveLocals) + for (const LiveLocalInfo& inf : liveLocals) { if (inf.GCDataCount <= 0) { @@ -917,7 +1025,16 @@ void Async2Transformation::FillInGCPointersOnSuspension(jitstd::vector& liveLocals, BasicBlock* suspendBB) +//------------------------------------------------------------------------ +// Async2Transformation::FillInDataOnSuspension: +// Create IR that fills the data array of the continuation object. +// +// Parameters: +// liveLocals - Information about each live local. +// suspendBB - Basic block to add IR to. +// +void Async2Transformation::FillInDataOnSuspension(const jitstd::vector& liveLocals, + BasicBlock* suspendBB) { unsigned byteArrLclNum = GetDataArrayVar(); @@ -942,7 +1059,7 @@ void Async2Transformation::FillInDataOnSuspension(jitstd::vector& } // Fill in data - for (LiveLocalInfo& inf : liveLocals) + for (const LiveLocalInfo& inf : liveLocals) { if (inf.DataSize <= 0) { @@ -984,6 +1101,18 @@ void Async2Transformation::FillInDataOnSuspension(jitstd::vector& } } +//------------------------------------------------------------------------ +// Async2Transformation::CreateCheckAndSuspendAfterCall: +// Split the block containing the specified async2 call, and create the IR +// that checks whether suspension should be done after an async call. +// +// Parameters: +// block - The block containing the async2 call +// callDefInfo - Information about the async2 call's definition +// life - Liveness information about live locals +// suspendBB - Basic block to add IR to +// remainder - [out] The remainder block containing the IR that was after the async2 call. +// void Async2Transformation::CreateCheckAndSuspendAfterCall(BasicBlock* block, const CallDefinitionInfo& callDefInfo, AsyncLiveness& life, @@ -1012,13 +1141,28 @@ void Async2Transformation::CreateCheckAndSuspendAfterCall(BasicBlock* block->GetFalseEdge()->setLikelihood(1); } -BasicBlock* Async2Transformation::CreateResumption(BasicBlock* block, - BasicBlock* remainder, - GenTreeCall* call, - const CallDefinitionInfo& callDefInfo, - unsigned stateNum, - const ContinuationLayout& layout, - jitstd::vector& liveLocals) +//------------------------------------------------------------------------ +// Async2Transformation::CreateResumption: +// Create the basic block that when branched to resumes execution on entry to +// the function. +// +// Parameters: +// block - The block containing the async2 call +// remainder - The block that contains the IR after the (split) async2 call +// call - The async2 call +// callDefInfo - Information about the async2 call's definition +// stateNum - State number assigned to this suspension point +// layout - Layout information for the continuation object +// +// Returns: +// The new basic block that was created. +// +BasicBlock* Async2Transformation::CreateResumption(BasicBlock* block, + BasicBlock* remainder, + GenTreeCall* call, + const CallDefinitionInfo& callDefInfo, + unsigned stateNum, + const ContinuationLayout& layout) { if (m_lastResumptionBB == nullptr) { @@ -1052,7 +1196,7 @@ BasicBlock* Async2Transformation::CreateResumption(BasicBlock* LIR::AsRange(resumeBB).InsertAtEnd(LIR::SeqTree(m_comp, storeAllocedByteArr)); - RestoreFromDataOnResumption(resumeByteArrLclNum, liveLocals, resumeBB); + RestoreFromDataOnResumption(resumeByteArrLclNum, layout.Locals, resumeBB); } unsigned resumeObjectArrLclNum = BAD_VAR_NUM; @@ -1068,7 +1212,7 @@ BasicBlock* Async2Transformation::CreateResumption(BasicBlock* GenTree* storeAllocedObjectArr = m_comp->gtNewStoreLclVarNode(resumeObjectArrLclNum, gcDataInd); LIR::AsRange(resumeBB).InsertAtEnd(LIR::SeqTree(m_comp, storeAllocedObjectArr)); - RestoreFromGCPointersOnResumption(resumeObjectArrLclNum, liveLocals, resumeBB); + RestoreFromGCPointersOnResumption(resumeObjectArrLclNum, layout.Locals, resumeBB); if (layout.ExceptionGCDataIndex != UINT_MAX) { @@ -1086,12 +1230,22 @@ BasicBlock* Async2Transformation::CreateResumption(BasicBlock* return resumeBB; } -void Async2Transformation::RestoreFromDataOnResumption(unsigned resumeByteArrLclNum, - jitstd::vector& liveLocals, - BasicBlock* resumeBB) +//------------------------------------------------------------------------ +// Async2Transformation::RestoreFromDataOnResumption: +// Create IR that restores locals from the data array of the continuation +// object. +// +// Parameters: +// resumeByteArrLclNum - Local that has the continuation object's data array +// liveLocals - Information about each live local. +// resumeBB - Basic block to append IR to +// +void Async2Transformation::RestoreFromDataOnResumption(unsigned resumeByteArrLclNum, + const jitstd::vector& liveLocals, + BasicBlock* resumeBB) { // Copy data - for (LiveLocalInfo& inf : liveLocals) + for (const LiveLocalInfo& inf : liveLocals) { if (inf.DataSize <= 0) { @@ -1133,11 +1287,21 @@ void Async2Transformation::RestoreFromDataOnResumption(unsigned } } -void Async2Transformation::RestoreFromGCPointersOnResumption(unsigned resumeObjectArrLclNum, - jitstd::vector& liveLocals, - BasicBlock* resumeBB) +//------------------------------------------------------------------------ +// Async2Transformation::RestoreFromGCPointersOnResumption: +// Create IR that restores locals from the GC pointers array of the +// continuation object. +// +// Parameters: +// resumeObjectArrLclNum - Local that has the continuation object's GC pointers array +// liveLocals - Information about each live local. +// resumeBB - Basic block to append IR to +// +void Async2Transformation::RestoreFromGCPointersOnResumption(unsigned resumeObjectArrLclNum, + const jitstd::vector& liveLocals, + BasicBlock* resumeBB) { - for (LiveLocalInfo& inf : liveLocals) + for (const LiveLocalInfo& inf : liveLocals) { if (inf.GCDataCount <= 0) { @@ -1178,8 +1342,7 @@ void Async2Transformation::RestoreFromGCPointersOnResumption(unsigned { GenTree* baseAddr = m_comp->gtNewLclvNode(inf.LclNum, dsc->TypeGet()); store = StoreAtOffset(baseAddr, i * TARGET_POINTER_SIZE, value); - // Implicit byref args are never on heap today, skip write barriers. - // TODO-CQ: Remove this once all implicit byrefs are TYP_I_IMPL typed. + // Implicit byref args are never on heap store->gtFlags |= GTF_IND_TGT_NOT_HEAP; } else @@ -1195,6 +1358,23 @@ void Async2Transformation::RestoreFromGCPointersOnResumption(unsigned } } +//------------------------------------------------------------------------ +// Async2Transformation::RethrowExceptionOnResumption: +// Create IR that checks for an exception and rethrows it at the original +// suspension point if necessary. +// +// Parameters: +// block - The block containing the async2 call +// remainder - The block that contains the IR after the (split) async2 call +// resumeObjectArrLclNum - Local that has the continuation object's GC pointers array +// layout - Layout information for the continuation object +// resumeBB - Basic block to append IR to +// +// Returns: +// The new non-exception successor basic block for resumption. This is the +// basic block where execution will continue if there was no exception to +// rethrow. +// BasicBlock* Async2Transformation::RethrowExceptionOnResumption(BasicBlock* block, BasicBlock* remainder, unsigned resumeObjectArrLclNum, @@ -1256,6 +1436,20 @@ BasicBlock* Async2Transformation::RethrowExceptionOnResumption(BasicBlock* return storeResultBB; } +//------------------------------------------------------------------------ +// Async2Transformation::CopyReturnValueOnResumption: +// Create IR that copies the return value from the continuation object to the +// right local. +// +// Parameters: +// call - The async2 call +// callDefInfo - Information about the async2 call's definition +// block - The block containing the async2 call +// resumeByteArrLclNum - Local that has the continuation object's data array +// resumeObjectArrLclNum - Local that has the continuation object's GC pointers array +// layout - Layout information for the continuation object +// storeResultBB - Basic block to append IR to +// void Async2Transformation::CopyReturnValueOnResumption(GenTreeCall* call, const CallDefinitionInfo& callDefInfo, unsigned resumeByteArrLclNum, @@ -1370,6 +1564,19 @@ void Async2Transformation::CopyReturnValueOnResumption(GenTreeCall* } } +//------------------------------------------------------------------------ +// Async2Transformation::LoadFromOffset: +// Create a load. +// +// Parameters: +// base - Base address of the load +// offset - Offset to add on top of the base address +// type - Type of the load to create +// indirFlags - Flags to add to the load +// +// Returns: +// IR node of the load. +// GenTreeIndir* Async2Transformation::LoadFromOffset(GenTree* base, unsigned offset, var_types type, @@ -1383,6 +1590,18 @@ GenTreeIndir* Async2Transformation::LoadFromOffset(GenTree* base, return load; } +//------------------------------------------------------------------------ +// Async2Transformation::LoadFromOffset: +// Create a store. +// +// Parameters: +// base - Base address of the store +// offset - Offset to add on top of the base address +// value - Value to store +// +// Returns: +// IR node of the store. +// GenTreeStoreInd* Async2Transformation::StoreAtOffset(GenTree* base, unsigned offset, GenTree* value) { assert(base->TypeIs(TYP_REF, TYP_BYREF, TYP_I_IMPL)); @@ -1393,6 +1612,15 @@ GenTreeStoreInd* Async2Transformation::StoreAtOffset(GenTree* base, unsigned off return store; } +//------------------------------------------------------------------------ +// Async2Transformation::GetDataArrayVar: +// Create a new local to hold the data array of the continuation object. This +// local can be validly used for the entire suspension point; the returned +// local may be used by multiple suspension points. +// +// Returns: +// Local number. +// unsigned Async2Transformation::GetDataArrayVar() { // Create separate locals unless we have many locals in the method for live @@ -1407,6 +1635,15 @@ unsigned Async2Transformation::GetDataArrayVar() return m_dataArrayVar; } +//------------------------------------------------------------------------ +// Async2Transformation::GetGCDataArrayVar: +// Create a new local to hold the GC pointers array of the continuation +// object. This local can be validly used for the entire suspension point; +// the returned local may be used by multiple suspension points. +// +// Returns: +// Local number. +// unsigned Async2Transformation::GetGCDataArrayVar() { if ((m_gcDataArrayVar == BAD_VAR_NUM) || !m_comp->lvaHaveManyLocals()) @@ -1418,6 +1655,15 @@ unsigned Async2Transformation::GetGCDataArrayVar() return m_gcDataArrayVar; } +//------------------------------------------------------------------------ +// Async2Transformation::GetResultBaseVar: +// Create a new local to hold the base address of the incoming result from +// the continuation. This local can be validly used for the entire suspension +// point; the returned local may be used by multiple suspension points. +// +// Returns: +// Local number. +// unsigned Async2Transformation::GetResultBaseVar() { if ((m_resultBaseVar == BAD_VAR_NUM) || !m_comp->lvaHaveManyLocals()) @@ -1429,6 +1675,15 @@ unsigned Async2Transformation::GetResultBaseVar() return m_resultBaseVar; } +//------------------------------------------------------------------------ +// Async2Transformation::GetExceptionVar: +// Create a new local to hold the exception in the continuation. This +// local can be validly used for the entire suspension point; the returned +// local may be used by multiple suspension points. +// +// Returns: +// Local number. +// unsigned Async2Transformation::GetExceptionVar() { if ((m_exceptionVar == BAD_VAR_NUM) || !m_comp->lvaHaveManyLocals()) @@ -1440,6 +1695,14 @@ unsigned Async2Transformation::GetExceptionVar() return m_exceptionVar; } +//------------------------------------------------------------------------ +// Async2Transformation::CreateResumptionStubAddrTree: +// Create a tree that represents the address of the resumption stub entry +// point. +// +// Returns: +// IR node. +// GenTree* Async2Transformation::CreateResumptionStubAddrTree() { switch (m_resumeStubLookup.accessType) @@ -1475,6 +1738,14 @@ GenTree* Async2Transformation::CreateResumptionStubAddrTree() } } +//------------------------------------------------------------------------ +// Async2Transformation::CreateFunctionTargetAddr: +// Create a tree that represents the address of the resumption stub entry +// point. +// +// Returns: +// IR node. +// GenTree* Async2Transformation::CreateFunctionTargetAddr(CORINFO_METHOD_HANDLE methHnd, const CORINFO_CONST_LOOKUP& lookup) { @@ -1483,43 +1754,6 @@ GenTree* Async2Transformation::CreateFunctionTargetAddr(CORINFO_METHOD_HANDLE return con; } -void Async2Transformation::LiftLIREdges(BasicBlock* block, - GenTree* beyond, - const jitstd::vector& defs, - jitstd::vector& liveLocals) -{ - if (defs.size() <= 0) - { - return; - } - - for (GenTree* tree : defs) - { - // TODO-CQ: Breaks our recognition of how the call is stored. - // if (tree->OperIs(GT_LCL_VAR)) - //{ - // LclVarDsc* dsc = m_comp->lvaGetDesc(tree->AsLclVarCommon()); - // if (!dsc->IsAddressExposed()) - // { - // // No interference by IR invariants. - // LIR::AsRange(block).Remove(tree); - // LIR::AsRange(block).InsertAfter(beyond, tree); - // continue; - // } - //} - - LIR::Use use; - bool gotUse = LIR::AsRange(block).TryGetUse(tree, &use); - assert(gotUse); // Defs list should not contain unused values. - - unsigned newLclNum = use.ReplaceWithLclVar(m_comp); - liveLocals.push_back(LiveLocalInfo(newLclNum)); - GenTree* newUse = use.Def(); - LIR::AsRange(block).Remove(newUse); - LIR::AsRange(block).InsertBefore(use.User(), newUse); - } -} - void Async2Transformation::CreateResumptionSwitch() { m_comp->fgCreateNewInitBB(); diff --git a/src/coreclr/jit/async.h b/src/coreclr/jit/async.h index f99d5817f3bc08..a07283b2b5fbab 100644 --- a/src/coreclr/jit/async.h +++ b/src/coreclr/jit/async.h @@ -1,15 +1,36 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +struct LiveLocalInfo +{ + unsigned LclNum; + unsigned Alignment; + unsigned DataOffset; + unsigned DataSize; + unsigned GCDataIndex; + unsigned GCDataCount; + + explicit LiveLocalInfo(unsigned lclNum) + : LclNum(lclNum) + { + } +}; + struct ContinuationLayout { - unsigned DataSize = 0; - unsigned GCRefsCount = 0; - ClassLayout* ReturnStructLayout = nullptr; - unsigned ReturnSize = 0; - bool ReturnInGCData = false; - unsigned ReturnValDataOffset = UINT_MAX; - unsigned ExceptionGCDataIndex = UINT_MAX; + unsigned DataSize = 0; + unsigned GCRefsCount = 0; + ClassLayout* ReturnStructLayout = nullptr; + unsigned ReturnSize = 0; + bool ReturnInGCData = false; + unsigned ReturnValDataOffset = UINT_MAX; + unsigned ExceptionGCDataIndex = UINT_MAX; + const jitstd::vector& Locals; + + ContinuationLayout(jitstd::vector& locals) + : Locals(locals) + { + } }; struct CallDefinitionInfo @@ -24,21 +45,6 @@ class Async2Transformation { friend class AsyncLiveness; - struct LiveLocalInfo - { - unsigned LclNum; - unsigned Alignment; - unsigned DataOffset; - unsigned DataSize; - unsigned GCDataIndex; - unsigned GCDataCount; - - explicit LiveLocalInfo(unsigned lclNum) - : LclNum(lclNum) - { - } - }; - Compiler* m_comp; jitstd::vector m_liveLocalsScratch; CORINFO_ASYNC2_INFO m_async2Info; @@ -55,10 +61,6 @@ class Async2Transformation BasicBlock* m_lastResumptionBB = nullptr; BasicBlock* m_sharedReturnBB = nullptr; - void LiftLIREdges(BasicBlock* block, - GenTree* beyond, - const jitstd::vector& defs, - jitstd::vector& liveLocals); bool IsLive(unsigned lclNum); void Transform(BasicBlock* block, GenTreeCall* call, @@ -66,47 +68,50 @@ class Async2Transformation class AsyncLiveness& life, BasicBlock** remainder); - void CreateLiveSetForSuspension(BasicBlock* block, - GenTreeCall* call, - const jitstd::vector& defs, - AsyncLiveness& life, - jitstd::vector& liveLocals); + void CreateLiveSetForSuspension(BasicBlock* block, + GenTreeCall* call, + const jitstd::vector& defs, + AsyncLiveness& life, + jitstd::vector& liveLocals); + + void LiftLIREdges(BasicBlock* block, + const jitstd::vector& defs, + jitstd::vector& liveLocals); + ContinuationLayout LayOutContinuation(BasicBlock* block, GenTreeCall* call, jitstd::vector& liveLocals); CallDefinitionInfo CanonicalizeCallDefinition(BasicBlock* block, GenTreeCall* call, AsyncLiveness& life); - BasicBlock* CreateSuspension(BasicBlock* block, - unsigned stateNum, - const ContinuationLayout& layout, - AsyncLiveness& life, - jitstd::vector& liveLocals); + BasicBlock* CreateSuspension(BasicBlock* block, + unsigned stateNum, + AsyncLiveness& life, + const ContinuationLayout& layout); GenTreeCall* CreateAllocContinuationCall(AsyncLiveness& life, GenTree* prevContinuation, unsigned gcRefsCount, unsigned int dataSize); - void FillInGCPointersOnSuspension(jitstd::vector& liveLocals, BasicBlock* suspendBB); - void FillInDataOnSuspension(jitstd::vector& liveLocals, BasicBlock* suspendBB); + void FillInGCPointersOnSuspension(const jitstd::vector& liveLocals, BasicBlock* suspendBB); + void FillInDataOnSuspension(const jitstd::vector& liveLocals, BasicBlock* suspendBB); void CreateCheckAndSuspendAfterCall(BasicBlock* block, const CallDefinitionInfo& callDefInfo, AsyncLiveness& life, BasicBlock* suspendBB, BasicBlock** remainder); - BasicBlock* CreateResumption(BasicBlock* block, - BasicBlock* remainder, - GenTreeCall* call, - const CallDefinitionInfo& callDefInfo, - unsigned stateNum, - const ContinuationLayout& layout, - jitstd::vector& liveLocals); - void RestoreFromDataOnResumption(unsigned resumeByteArrLclNum, - jitstd::vector& liveLocals, - BasicBlock* resumeBB); - void RestoreFromGCPointersOnResumption(unsigned resumeObjectArrLclNum, - jitstd::vector& liveLocals, - BasicBlock* resumeBB); + BasicBlock* CreateResumption(BasicBlock* block, + BasicBlock* remainder, + GenTreeCall* call, + const CallDefinitionInfo& callDefInfo, + unsigned stateNum, + const ContinuationLayout& layout); + void RestoreFromDataOnResumption(unsigned resumeByteArrLclNum, + const jitstd::vector& liveLocals, + BasicBlock* resumeBB); + void RestoreFromGCPointersOnResumption(unsigned resumeObjectArrLclNum, + const jitstd::vector& liveLocals, + BasicBlock* resumeBB); BasicBlock* RethrowExceptionOnResumption(BasicBlock* block, BasicBlock* remainder, unsigned resumeObjectArrLclNum, From 828e7b2636b6313c30953935fad39ad35de5119b Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Tue, 1 Apr 2025 11:52:39 +0200 Subject: [PATCH 183/203] Remove unused functions --- src/coreclr/jit/block.h | 10 ---------- src/coreclr/jit/fgbasic.cpp | 2 -- 2 files changed, 12 deletions(-) diff --git a/src/coreclr/jit/block.h b/src/coreclr/jit/block.h index d38d7d726a7b8b..2becb39f992089 100644 --- a/src/coreclr/jit/block.h +++ b/src/coreclr/jit/block.h @@ -1412,16 +1412,6 @@ struct BasicBlock : private LIR::Range m_firstNode = tree; } - GenTree* GetLastLIRNode() const - { - return m_lastNode; - } - - void SetLastLIRNode(GenTree* tree) - { - m_lastNode = tree; - } - EntryState* bbEntryState; // verifier tracked state of all entries in stack. #define NO_BASE_TMP UINT_MAX // base# to use when we have none diff --git a/src/coreclr/jit/fgbasic.cpp b/src/coreclr/jit/fgbasic.cpp index 515128ce7243b6..b570c5a8173854 100644 --- a/src/coreclr/jit/fgbasic.cpp +++ b/src/coreclr/jit/fgbasic.cpp @@ -4862,9 +4862,7 @@ BasicBlock* Compiler::fgSplitBlockAtBeginning(BasicBlock* curr) if (curr->IsLIR()) { newBlock->SetFirstLIRNode(curr->GetFirstLIRNode()); - newBlock->SetLastLIRNode(curr->GetLastLIRNode()); curr->SetFirstLIRNode(nullptr); - curr->SetLastLIRNode(nullptr); } else { From 52ce4f79b07a52529a4b727d2344ac21ad67973c Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Tue, 1 Apr 2025 12:24:55 +0200 Subject: [PATCH 184/203] More function headers and clean up --- src/coreclr/jit/codegencommon.cpp | 18 +++ src/coreclr/jit/layout.cpp | 6 + src/coreclr/jit/lclvars.cpp | 7 ++ src/coreclr/jit/lower.cpp | 13 ++ src/coreclr/jit/lsrabuild.cpp | 8 ++ src/coreclr/jit/optcse.cpp | 202 ++++++++++++++++-------------- 6 files changed, 159 insertions(+), 95 deletions(-) diff --git a/src/coreclr/jit/codegencommon.cpp b/src/coreclr/jit/codegencommon.cpp index be56cc82991cc8..79db11e1e102e4 100644 --- a/src/coreclr/jit/codegencommon.cpp +++ b/src/coreclr/jit/codegencommon.cpp @@ -7317,6 +7317,13 @@ void CodeGen::genSwiftErrorReturn(GenTree* treeNode) } #endif // SWIFT_SUPPORT +//------------------------------------------------------------------------ +// genReturnSuspend: +// Generate code for a GT_RETURN_SUSPEND node +// +// Arguments: +// treeNode - The node +// void CodeGen::genReturnSuspend(GenTreeUnOp* treeNode) { GenTree* op = treeNode->gtGetOp1(); @@ -7339,6 +7346,10 @@ void CodeGen::genReturnSuspend(GenTreeUnOp* treeNode) genMarkReturnGCInfo(); } +//------------------------------------------------------------------------ +// genMarkReturnGCInfo: +// Mark GC and non-GC pointers of return registers going into the epilog.. +// void CodeGen::genMarkReturnGCInfo() { const ReturnTypeDesc& retTypeDesc = compiler->compRetTypeDesc; @@ -7363,6 +7374,13 @@ void CodeGen::genMarkReturnGCInfo() } } +//------------------------------------------------------------------------ +// genCodeForAsyncContinuation: +// Generate code for a GC_ASYNC_CONTINUATION node. +// +// Arguments: +// tree - The node +// void CodeGen::genCodeForAsyncContinuation(GenTree* tree) { assert(tree->OperIs(GT_ASYNC_CONTINUATION)); diff --git a/src/coreclr/jit/layout.cpp b/src/coreclr/jit/layout.cpp index 2e36fea45eb2c2..2b9a0197d07a75 100644 --- a/src/coreclr/jit/layout.cpp +++ b/src/coreclr/jit/layout.cpp @@ -536,6 +536,12 @@ ClassLayout* ClassLayout::Create(Compiler* compiler, const ClassLayoutBuilder& b return newLayout; } +//------------------------------------------------------------------------ +// HasGCByRef: // Check if this classlayout has a TYP_BYREF GC pointer in it. +// +// Return value: +// True if so. +// bool ClassLayout::HasGCByRef() const { if (!HasGCPtr()) diff --git a/src/coreclr/jit/lclvars.cpp b/src/coreclr/jit/lclvars.cpp index 46e1a5685a3168..5213828696bf53 100644 --- a/src/coreclr/jit/lclvars.cpp +++ b/src/coreclr/jit/lclvars.cpp @@ -685,6 +685,13 @@ void Compiler::lvaInitGenericsCtxt(unsigned* curVarNum) (*curVarNum)++; } +//----------------------------------------------------------------------------- +// lvaInitAsyncContinuation: +// Initialize the async continuation parameter. +// +// Type parameters: +// curVarNum - [in, out] The current local variable number for parameters +// void Compiler::lvaInitAsyncContinuation(unsigned* curVarNum) { if (!compIsAsync2()) diff --git a/src/coreclr/jit/lower.cpp b/src/coreclr/jit/lower.cpp index 9214ffd740a094..0d072a878adb74 100644 --- a/src/coreclr/jit/lower.cpp +++ b/src/coreclr/jit/lower.cpp @@ -5391,6 +5391,12 @@ void Lowering::LowerRetSingleRegStructLclVar(GenTreeUnOp* ret) } } +//---------------------------------------------------------------------------------------------- +// LowerAsyncContinuation: Lower a GT_ASYNC_CONTINUATION node +// +// Arguments: +// asyncCont - Async continuation node +// void Lowering::LowerAsyncContinuation(GenTree* asyncCont) { assert(asyncCont->OperIs(GT_ASYNC_CONTINUATION)); @@ -5419,6 +5425,13 @@ void Lowering::LowerAsyncContinuation(GenTree* asyncCont) } } +//---------------------------------------------------------------------------------------------- +// LowerReturnSuspend: +// Lower a GT_RETURN_SUSPEND by making it a terminator node. +// +// Arguments: +// node - The node +// void Lowering::LowerReturnSuspend(GenTree* node) { assert(node->OperIs(GT_RETURN_SUSPEND)); diff --git a/src/coreclr/jit/lsrabuild.cpp b/src/coreclr/jit/lsrabuild.cpp index 902eed9e230720..4b9d310a9a8434 100644 --- a/src/coreclr/jit/lsrabuild.cpp +++ b/src/coreclr/jit/lsrabuild.cpp @@ -4742,6 +4742,14 @@ void LinearScan::MarkSwiftErrorBusyForCall(GenTreeCall* call) } #endif +//------------------------------------------------------------------------ +// MarkAsyncContinuationBusyForCall: +// Add a ref position that marks the async continuation register as busy +// until it is killed. +// +// Arguments: +// call - The call node +// void LinearScan::MarkAsyncContinuationBusyForCall(GenTreeCall* call) { // Async2 calls return an async continuation argument in a separate diff --git a/src/coreclr/jit/optcse.cpp b/src/coreclr/jit/optcse.cpp index cb5bb7a9f250cf..d3df3d3bdec1e4 100644 --- a/src/coreclr/jit/optcse.cpp +++ b/src/coreclr/jit/optcse.cpp @@ -1001,101 +1001,7 @@ void Compiler::optValnumCSE_InitDataFlow() if (compIsAsync2()) { - bool anyAsyncKills = false; - cseAsyncKillsMask = BitVecOps::MakeFull(cseLivenessTraits); - for (unsigned inx = 1; inx <= optCSECandidateCount; inx++) - { - CSEdsc* dsc = optCSEtab[inx - 1]; - assert(dsc->csdIndex == inx); - bool isByRef = false; - if (dsc->csdTree->TypeIs(TYP_BYREF)) - { - isByRef = true; - } - else if (dsc->csdTree->TypeIs(TYP_STRUCT)) - { - ClassLayout* layout = dsc->csdTree->GetLayout(this); - isByRef = layout->HasGCByRef(); - } - - if (isByRef) - { - // We generate a bit pattern like: 1111111100111100 where there - // are 0s only for the byref CSEs. - BitVecOps::RemoveElemD(cseLivenessTraits, cseAsyncKillsMask, getCSEAvailBit(inx)); - BitVecOps::RemoveElemD(cseLivenessTraits, cseAsyncKillsMask, getCSEAvailCrossCallBit(inx)); - anyAsyncKills = true; - } - } - - if (anyAsyncKills) - { - for (BasicBlock* block : Blocks()) - { - Statement* asyncCallStmt = nullptr; - GenTree* asyncCall = nullptr; - // Find last async call in block - Statement* stmt = block->lastStmt(); - if (stmt == nullptr) - { - continue; - } - - while (asyncCall == nullptr) - { - if ((stmt->GetRootNode()->gtFlags & GTF_CALL) != 0) - { - for (GenTree* tree = stmt->GetRootNode(); tree != nullptr; tree = tree->gtPrev) - { - if (tree->IsCall() && tree->AsCall()->IsAsync2()) - { - asyncCallStmt = stmt; - asyncCall = tree; - break; - } - } - } - - if (stmt == block->firstStmt()) - break; - - stmt = stmt->GetPrevStmt(); - } - - if (asyncCall == nullptr) - { - continue; - } - - // This block has a suspension point. Make all BYREF CSEs unavailable. - BitVecOps::IntersectionD(cseLivenessTraits, block->bbCseGen, cseAsyncKillsMask); - BitVecOps::IntersectionD(cseLivenessTraits, block->bbCseOut, cseAsyncKillsMask); - - // Now make all byref CSEs after the suspension point available. - Statement* curStmt = asyncCallStmt; - GenTree* curTree = asyncCall; - while (true) - { - do - { - if (IS_CSE_INDEX(curTree->gtCSEnum)) - { - unsigned CSEnum = GET_CSE_INDEX(curTree->gtCSEnum); - BitVecOps::AddElemD(cseLivenessTraits, block->bbCseGen, getCSEAvailBit(CSEnum)); - BitVecOps::AddElemD(cseLivenessTraits, block->bbCseOut, getCSEAvailBit(CSEnum)); - } - - curTree = curTree->gtNext; - } while (curTree != nullptr); - - curStmt = curStmt->GetNextStmt(); - if (curStmt == nullptr) - break; - - curTree = curStmt->GetTreeList(); - } - } - } + optValnumCSE_SetUpAsync2ByrefKills(); } for (BasicBlock* const block : Blocks()) @@ -1181,6 +1087,112 @@ void Compiler::optValnumCSE_InitDataFlow() #endif // DEBUG } +//--------------------------------------------------------------------------- +// optValnumCSE_SetUpAsync2ByrefKills: +// Compute kills because of async2 calls requiring byrefs not to be live +// across them. +// +void Compiler::optValnumCSE_SetUpAsync2ByrefKills() +{ + bool anyAsyncKills = false; + cseAsyncKillsMask = BitVecOps::MakeFull(cseLivenessTraits); + for (unsigned inx = 1; inx <= optCSECandidateCount; inx++) + { + CSEdsc* dsc = optCSEtab[inx - 1]; + assert(dsc->csdIndex == inx); + bool isByRef = false; + if (dsc->csdTree->TypeIs(TYP_BYREF)) + { + isByRef = true; + } + else if (dsc->csdTree->TypeIs(TYP_STRUCT)) + { + ClassLayout* layout = dsc->csdTree->GetLayout(this); + isByRef = layout->HasGCByRef(); + } + + if (isByRef) + { + // We generate a bit pattern like: 1111111100111100 where there + // are 0s only for the byref CSEs. + BitVecOps::RemoveElemD(cseLivenessTraits, cseAsyncKillsMask, getCSEAvailBit(inx)); + BitVecOps::RemoveElemD(cseLivenessTraits, cseAsyncKillsMask, getCSEAvailCrossCallBit(inx)); + anyAsyncKills = true; + } + } + + if (!anyAsyncKills) + { + return; + } + + for (BasicBlock* block : Blocks()) + { + Statement* asyncCallStmt = nullptr; + GenTree* asyncCall = nullptr; + // Find last async call in block + Statement* stmt = block->lastStmt(); + if (stmt == nullptr) + { + continue; + } + + while (asyncCall == nullptr) + { + if ((stmt->GetRootNode()->gtFlags & GTF_CALL) != 0) + { + for (GenTree* tree = stmt->GetRootNode(); tree != nullptr; tree = tree->gtPrev) + { + if (tree->IsCall() && tree->AsCall()->IsAsync2()) + { + asyncCallStmt = stmt; + asyncCall = tree; + break; + } + } + } + + if (stmt == block->firstStmt()) + break; + + stmt = stmt->GetPrevStmt(); + } + + if (asyncCall == nullptr) + { + continue; + } + + // This block has a suspension point. Make all BYREF CSEs unavailable. + BitVecOps::IntersectionD(cseLivenessTraits, block->bbCseGen, cseAsyncKillsMask); + BitVecOps::IntersectionD(cseLivenessTraits, block->bbCseOut, cseAsyncKillsMask); + + // Now make all byref CSEs after the suspension point available. + Statement* curStmt = asyncCallStmt; + GenTree* curTree = asyncCall; + while (true) + { + do + { + if (IS_CSE_INDEX(curTree->gtCSEnum)) + { + unsigned CSEnum = GET_CSE_INDEX(curTree->gtCSEnum); + BitVecOps::AddElemD(cseLivenessTraits, block->bbCseGen, getCSEAvailBit(CSEnum)); + BitVecOps::AddElemD(cseLivenessTraits, block->bbCseOut, getCSEAvailBit(CSEnum)); + } + + curTree = curTree->gtNext; + } while (curTree != nullptr); + + curStmt = curStmt->GetNextStmt(); + if (curStmt == nullptr) + break; + + curTree = curStmt->GetTreeList(); + } + } +} + /***************************************************************************** * * CSE Dataflow, so that all helper methods for dataflow are in a single place From d9487b2c073f42a3ee3b7a8a2aa1ef93a10a2fb5 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Tue, 1 Apr 2025 15:47:05 +0200 Subject: [PATCH 185/203] Rename async2 -> async --- src/coreclr/jit/async.cpp | 203 ++++++++++++++--------------- src/coreclr/jit/async.h | 8 +- src/coreclr/jit/codegenarm.cpp | 2 +- src/coreclr/jit/codegenarmarch.cpp | 2 +- src/coreclr/jit/codegencommon.cpp | 6 +- src/coreclr/jit/codegenlinear.cpp | 2 +- src/coreclr/jit/codegenxarch.cpp | 6 +- src/coreclr/jit/compiler.cpp | 6 +- src/coreclr/jit/compiler.h | 7 +- src/coreclr/jit/compmemkind.h | 2 +- src/coreclr/jit/compphases.h | 2 +- src/coreclr/jit/fginline.cpp | 2 +- src/coreclr/jit/flowgraph.cpp | 2 +- src/coreclr/jit/gentree.cpp | 10 +- src/coreclr/jit/gentree.h | 9 +- src/coreclr/jit/importer.cpp | 6 +- src/coreclr/jit/importercalls.cpp | 12 +- src/coreclr/jit/lclvars.cpp | 4 +- src/coreclr/jit/lower.cpp | 4 +- src/coreclr/jit/lsraarmarch.cpp | 2 +- src/coreclr/jit/lsraxarch.cpp | 2 +- src/coreclr/jit/morph.cpp | 3 +- src/coreclr/jit/optcse.cpp | 14 +- 23 files changed, 159 insertions(+), 157 deletions(-) diff --git a/src/coreclr/jit/async.cpp b/src/coreclr/jit/async.cpp index 5361493e1ec909..fcbe6d4adf2403 100644 --- a/src/coreclr/jit/async.cpp +++ b/src/coreclr/jit/async.cpp @@ -222,29 +222,29 @@ void AsyncLiveness::GetLiveLocals(jitstd::vector& liveLocals, uns } //------------------------------------------------------------------------ -// TransformAsync2: Run async2 transformation. +// TransformAsync: Run async transformation. // // Returns: // Suitable phase status. // -PhaseStatus Compiler::TransformAsync2() +PhaseStatus Compiler::TransformAsync() { - assert(compIsAsync2()); + assert(compIsAsync()); - Async2Transformation transformation(this); + AsyncTransformation transformation(this); return transformation.Run(); } //------------------------------------------------------------------------ -// Async2Transformation::Run: +// AsyncTransformation::Run: // Run the transformation over all the IR. // // Returns: // Suitable phase status. // -PhaseStatus Async2Transformation::Run() +PhaseStatus AsyncTransformation::Run() { - ArrayStack worklist(m_comp->getAllocator(CMK_Async2)); + ArrayStack worklist(m_comp->getAllocator(CMK_Async)); // First find all basic blocks with awaits in them. We'll have to track // liveness in these basic blocks, so it does not help to record the calls @@ -253,7 +253,7 @@ PhaseStatus Async2Transformation::Run() { for (GenTree* tree : LIR::AsRange(block)) { - if (tree->IsCall() && tree->AsCall()->IsAsync2() && !tree->AsCall()->IsTailCall()) + if (tree->IsCall() && tree->AsCall()->IsAsync() && !tree->AsCall()->IsTailCall()) { JITDUMP(FMT_BB " contains await(s)\n", block->bbNum); worklist.Push(block); @@ -317,7 +317,7 @@ PhaseStatus Async2Transformation::Run() // Now walk the IR for all the blocks that contain async2 calls. Keep track // of liveness and outstanding LIR edges as we go; the LIR edges that cross // async2 calls are additional live variables that must be spilled. - jitstd::vector defs(m_comp->getAllocator(CMK_Async2)); + jitstd::vector defs(m_comp->getAllocator(CMK_Async)); for (int i = 0; i < worklist.Height(); i++) { @@ -354,7 +354,7 @@ PhaseStatus Async2Transformation::Run() // Update liveness to reflect state after this node. liveness.Update(tree); - if (tree->IsCall() && tree->AsCall()->IsAsync2() && !tree->AsCall()->IsTailCall()) + if (tree->IsCall() && tree->AsCall()->IsAsync() && !tree->AsCall()->IsTailCall()) { // Transform call; continue with the remainder block Transform(block, tree->AsCall(), defs, liveness, &block); @@ -383,7 +383,7 @@ PhaseStatus Async2Transformation::Run() } //------------------------------------------------------------------------ -// Async2Transformation::Transform: +// AsyncTransformation::Transform: // Transform a single async2 call in the specified block. // // Parameters: @@ -393,7 +393,7 @@ PhaseStatus Async2Transformation::Run() // life - Liveness information about live locals // remainder - [out] Remainder block after the transformation // -void Async2Transformation::Transform( +void AsyncTransformation::Transform( BasicBlock* block, GenTreeCall* call, jitstd::vector& defs, AsyncLiveness& life, BasicBlock** remainder) { #ifdef DEBUG @@ -438,7 +438,7 @@ void Async2Transformation::Transform( } //------------------------------------------------------------------------ -// Async2Transformation::CreateLiveSetForSuspension: +// AsyncTransformation::CreateLiveSetForSuspension: // Create the set of live state to be captured for suspension, for the // specified call. // @@ -449,11 +449,11 @@ void Async2Transformation::Transform( // life - Liveness information about live locals // liveLocals - Information about each live local. // -void Async2Transformation::CreateLiveSetForSuspension(BasicBlock* block, - GenTreeCall* call, - const jitstd::vector& defs, - AsyncLiveness& life, - jitstd::vector& liveLocals) +void AsyncTransformation::CreateLiveSetForSuspension(BasicBlock* block, + GenTreeCall* call, + const jitstd::vector& defs, + AsyncLiveness& life, + jitstd::vector& liveLocals) { unsigned fullyDefinedRetBufLcl = BAD_VAR_NUM; CallArg* retbufArg = call->gtArgs.GetRetBufferArg(); @@ -499,7 +499,7 @@ void Async2Transformation::CreateLiveSetForSuspension(BasicBlock* } //------------------------------------------------------------------------ -// Async2Transformation::LiftLIREdges: +// AsyncTransformation::LiftLIREdges: // Create locals capturing outstanding LIR edges and add information // indicating that these locals are live. // @@ -508,9 +508,9 @@ void Async2Transformation::CreateLiveSetForSuspension(BasicBlock* // defs - Current outstanding LIR edges // liveLocals - [out] Vector to add new live local information into // -void Async2Transformation::LiftLIREdges(BasicBlock* block, - const jitstd::vector& defs, - jitstd::vector& liveLocals) +void AsyncTransformation::LiftLIREdges(BasicBlock* block, + const jitstd::vector& defs, + jitstd::vector& liveLocals) { if (defs.size() <= 0) { @@ -546,7 +546,7 @@ void Async2Transformation::LiftLIREdges(BasicBlock* block, } //------------------------------------------------------------------------ -// Async2Transformation::LayOutContinuation: +// AsyncTransformation::LayOutContinuation: // Create the layout of the GC pointer and data arrays in the continuation // object. // @@ -559,9 +559,9 @@ void Async2Transformation::LiftLIREdges(BasicBlock* block, // Returns: // Layout information. // -ContinuationLayout Async2Transformation::LayOutContinuation(BasicBlock* block, - GenTreeCall* call, - jitstd::vector& liveLocals) +ContinuationLayout AsyncTransformation::LayOutContinuation(BasicBlock* block, + GenTreeCall* call, + jitstd::vector& liveLocals) { ContinuationLayout layout(liveLocals); @@ -713,7 +713,7 @@ ContinuationLayout Async2Transformation::LayOutContinuation(BasicBlock* } //------------------------------------------------------------------------ -// Async2Transformation::CanonicalizeCallDefinition: +// AsyncTransformation::CanonicalizeCallDefinition: // Put the call definition in a canonical form. This ensures that either the // value is defined by a LCL_ADDR retbuffer or by a // STORE_LCL_VAR/STORE_LCL_FLD that follows the call node. @@ -726,9 +726,9 @@ ContinuationLayout Async2Transformation::LayOutContinuation(BasicBlock* // Returns: // Information about the definition after canonicalization. // -CallDefinitionInfo Async2Transformation::CanonicalizeCallDefinition(BasicBlock* block, - GenTreeCall* call, - AsyncLiveness& life) +CallDefinitionInfo AsyncTransformation::CanonicalizeCallDefinition(BasicBlock* block, + GenTreeCall* call, + AsyncLiveness& life) { CallDefinitionInfo callDefInfo; @@ -777,7 +777,7 @@ CallDefinitionInfo Async2Transformation::CanonicalizeCallDefinition(BasicBlock* } //------------------------------------------------------------------------ -// Async2Transformation::CreateSuspension: +// AsyncTransformation::CreateSuspension: // Create the basic block that when branched to suspends execution after the // specified async2 call. // @@ -790,10 +790,10 @@ CallDefinitionInfo Async2Transformation::CanonicalizeCallDefinition(BasicBlock* // Returns: // The new basic block that was created. // -BasicBlock* Async2Transformation::CreateSuspension(BasicBlock* block, - unsigned stateNum, - AsyncLiveness& life, - const ContinuationLayout& layout) +BasicBlock* AsyncTransformation::CreateSuspension(BasicBlock* block, + unsigned stateNum, + AsyncLiveness& life, + const ContinuationLayout& layout) { if (m_lastSuspensionBB == nullptr) { @@ -877,7 +877,7 @@ BasicBlock* Async2Transformation::CreateSuspension(BasicBlock* blo } //------------------------------------------------------------------------ -// Async2Transformation::CreateAllocContinuationCall: +// AsyncTransformation::CreateAllocContinuationCall: // Create a call to the JIT helper that allocates a continuation. // // Parameters: @@ -889,10 +889,10 @@ BasicBlock* Async2Transformation::CreateSuspension(BasicBlock* blo // Returns: // IR node representing the allocation. // -GenTreeCall* Async2Transformation::CreateAllocContinuationCall(AsyncLiveness& life, - GenTree* prevContinuation, - unsigned gcRefsCount, - unsigned dataSize) +GenTreeCall* AsyncTransformation::CreateAllocContinuationCall(AsyncLiveness& life, + GenTree* prevContinuation, + unsigned gcRefsCount, + unsigned dataSize) { GenTree* gcRefsCountNode = m_comp->gtNewIconNode((ssize_t)gcRefsCount, TYP_I_IMPL); GenTree* dataSizeNode = m_comp->gtNewIconNode((ssize_t)dataSize, TYP_I_IMPL); @@ -932,15 +932,15 @@ GenTreeCall* Async2Transformation::CreateAllocContinuationCall(AsyncLiveness& li } //------------------------------------------------------------------------ -// Async2Transformation::FillInGCPointersOnSuspension: +// AsyncTransformation::FillInGCPointersOnSuspension: // Create IR that fills the GC pointers of the continuation object. // // Parameters: // liveLocals - Information about each live local. // suspendBB - Basic block to add IR to. // -void Async2Transformation::FillInGCPointersOnSuspension(const jitstd::vector& liveLocals, - BasicBlock* suspendBB) +void AsyncTransformation::FillInGCPointersOnSuspension(const jitstd::vector& liveLocals, + BasicBlock* suspendBB) { unsigned objectArrLclNum = GetGCDataArrayVar(); @@ -1026,15 +1026,14 @@ void Async2Transformation::FillInGCPointersOnSuspension(const jitstd::vector& liveLocals, - BasicBlock* suspendBB) +void AsyncTransformation::FillInDataOnSuspension(const jitstd::vector& liveLocals, BasicBlock* suspendBB) { unsigned byteArrLclNum = GetDataArrayVar(); @@ -1102,7 +1101,7 @@ void Async2Transformation::FillInDataOnSuspension(const jitstd::vectorSetHasOrderingSideEffect(); @@ -1142,7 +1141,7 @@ void Async2Transformation::CreateCheckAndSuspendAfterCall(BasicBlock* } //------------------------------------------------------------------------ -// Async2Transformation::CreateResumption: +// AsyncTransformation::CreateResumption: // Create the basic block that when branched to resumes execution on entry to // the function. // @@ -1157,12 +1156,12 @@ void Async2Transformation::CreateCheckAndSuspendAfterCall(BasicBlock* // Returns: // The new basic block that was created. // -BasicBlock* Async2Transformation::CreateResumption(BasicBlock* block, - BasicBlock* remainder, - GenTreeCall* call, - const CallDefinitionInfo& callDefInfo, - unsigned stateNum, - const ContinuationLayout& layout) +BasicBlock* AsyncTransformation::CreateResumption(BasicBlock* block, + BasicBlock* remainder, + GenTreeCall* call, + const CallDefinitionInfo& callDefInfo, + unsigned stateNum, + const ContinuationLayout& layout) { if (m_lastResumptionBB == nullptr) { @@ -1231,7 +1230,7 @@ BasicBlock* Async2Transformation::CreateResumption(BasicBlock* blo } //------------------------------------------------------------------------ -// Async2Transformation::RestoreFromDataOnResumption: +// AsyncTransformation::RestoreFromDataOnResumption: // Create IR that restores locals from the data array of the continuation // object. // @@ -1240,9 +1239,9 @@ BasicBlock* Async2Transformation::CreateResumption(BasicBlock* blo // liveLocals - Information about each live local. // resumeBB - Basic block to append IR to // -void Async2Transformation::RestoreFromDataOnResumption(unsigned resumeByteArrLclNum, - const jitstd::vector& liveLocals, - BasicBlock* resumeBB) +void AsyncTransformation::RestoreFromDataOnResumption(unsigned resumeByteArrLclNum, + const jitstd::vector& liveLocals, + BasicBlock* resumeBB) { // Copy data for (const LiveLocalInfo& inf : liveLocals) @@ -1288,7 +1287,7 @@ void Async2Transformation::RestoreFromDataOnResumption(unsigned } //------------------------------------------------------------------------ -// Async2Transformation::RestoreFromGCPointersOnResumption: +// AsyncTransformation::RestoreFromGCPointersOnResumption: // Create IR that restores locals from the GC pointers array of the // continuation object. // @@ -1297,9 +1296,9 @@ void Async2Transformation::RestoreFromDataOnResumption(unsigned // liveLocals - Information about each live local. // resumeBB - Basic block to append IR to // -void Async2Transformation::RestoreFromGCPointersOnResumption(unsigned resumeObjectArrLclNum, - const jitstd::vector& liveLocals, - BasicBlock* resumeBB) +void AsyncTransformation::RestoreFromGCPointersOnResumption(unsigned resumeObjectArrLclNum, + const jitstd::vector& liveLocals, + BasicBlock* resumeBB) { for (const LiveLocalInfo& inf : liveLocals) { @@ -1359,7 +1358,7 @@ void Async2Transformation::RestoreFromGCPointersOnResumption(unsigned } //------------------------------------------------------------------------ -// Async2Transformation::RethrowExceptionOnResumption: +// AsyncTransformation::RethrowExceptionOnResumption: // Create IR that checks for an exception and rethrows it at the original // suspension point if necessary. // @@ -1375,11 +1374,11 @@ void Async2Transformation::RestoreFromGCPointersOnResumption(unsigned // basic block where execution will continue if there was no exception to // rethrow. // -BasicBlock* Async2Transformation::RethrowExceptionOnResumption(BasicBlock* block, - BasicBlock* remainder, - unsigned resumeObjectArrLclNum, - const ContinuationLayout& layout, - BasicBlock* resumeBB) +BasicBlock* AsyncTransformation::RethrowExceptionOnResumption(BasicBlock* block, + BasicBlock* remainder, + unsigned resumeObjectArrLclNum, + const ContinuationLayout& layout, + BasicBlock* resumeBB) { JITDUMP(" We need to rethrow an exception\n"); @@ -1437,7 +1436,7 @@ BasicBlock* Async2Transformation::RethrowExceptionOnResumption(BasicBlock* } //------------------------------------------------------------------------ -// Async2Transformation::CopyReturnValueOnResumption: +// AsyncTransformation::CopyReturnValueOnResumption: // Create IR that copies the return value from the continuation object to the // right local. // @@ -1450,12 +1449,12 @@ BasicBlock* Async2Transformation::RethrowExceptionOnResumption(BasicBlock* // layout - Layout information for the continuation object // storeResultBB - Basic block to append IR to // -void Async2Transformation::CopyReturnValueOnResumption(GenTreeCall* call, - const CallDefinitionInfo& callDefInfo, - unsigned resumeByteArrLclNum, - unsigned resumeObjectArrLclNum, - const ContinuationLayout& layout, - BasicBlock* storeResultBB) +void AsyncTransformation::CopyReturnValueOnResumption(GenTreeCall* call, + const CallDefinitionInfo& callDefInfo, + unsigned resumeByteArrLclNum, + unsigned resumeObjectArrLclNum, + const ContinuationLayout& layout, + BasicBlock* storeResultBB) { GenTree* resultBase; unsigned resultOffset; @@ -1565,7 +1564,7 @@ void Async2Transformation::CopyReturnValueOnResumption(GenTreeCall* } //------------------------------------------------------------------------ -// Async2Transformation::LoadFromOffset: +// AsyncTransformation::LoadFromOffset: // Create a load. // // Parameters: @@ -1577,10 +1576,10 @@ void Async2Transformation::CopyReturnValueOnResumption(GenTreeCall* // Returns: // IR node of the load. // -GenTreeIndir* Async2Transformation::LoadFromOffset(GenTree* base, - unsigned offset, - var_types type, - GenTreeFlags indirFlags) +GenTreeIndir* AsyncTransformation::LoadFromOffset(GenTree* base, + unsigned offset, + var_types type, + GenTreeFlags indirFlags) { assert(base->TypeIs(TYP_REF, TYP_BYREF, TYP_I_IMPL)); GenTree* cns = m_comp->gtNewIconNode((ssize_t)offset, TYP_I_IMPL); @@ -1591,7 +1590,7 @@ GenTreeIndir* Async2Transformation::LoadFromOffset(GenTree* base, } //------------------------------------------------------------------------ -// Async2Transformation::LoadFromOffset: +// AsyncTransformation::LoadFromOffset: // Create a store. // // Parameters: @@ -1602,7 +1601,7 @@ GenTreeIndir* Async2Transformation::LoadFromOffset(GenTree* base, // Returns: // IR node of the store. // -GenTreeStoreInd* Async2Transformation::StoreAtOffset(GenTree* base, unsigned offset, GenTree* value) +GenTreeStoreInd* AsyncTransformation::StoreAtOffset(GenTree* base, unsigned offset, GenTree* value) { assert(base->TypeIs(TYP_REF, TYP_BYREF, TYP_I_IMPL)); GenTree* cns = m_comp->gtNewIconNode((ssize_t)offset, TYP_I_IMPL); @@ -1613,7 +1612,7 @@ GenTreeStoreInd* Async2Transformation::StoreAtOffset(GenTree* base, unsigned off } //------------------------------------------------------------------------ -// Async2Transformation::GetDataArrayVar: +// AsyncTransformation::GetDataArrayVar: // Create a new local to hold the data array of the continuation object. This // local can be validly used for the entire suspension point; the returned // local may be used by multiple suspension points. @@ -1621,7 +1620,7 @@ GenTreeStoreInd* Async2Transformation::StoreAtOffset(GenTree* base, unsigned off // Returns: // Local number. // -unsigned Async2Transformation::GetDataArrayVar() +unsigned AsyncTransformation::GetDataArrayVar() { // Create separate locals unless we have many locals in the method for live // range splitting purposes. This helps LSRA to avoid create additional @@ -1636,7 +1635,7 @@ unsigned Async2Transformation::GetDataArrayVar() } //------------------------------------------------------------------------ -// Async2Transformation::GetGCDataArrayVar: +// AsyncTransformation::GetGCDataArrayVar: // Create a new local to hold the GC pointers array of the continuation // object. This local can be validly used for the entire suspension point; // the returned local may be used by multiple suspension points. @@ -1644,7 +1643,7 @@ unsigned Async2Transformation::GetDataArrayVar() // Returns: // Local number. // -unsigned Async2Transformation::GetGCDataArrayVar() +unsigned AsyncTransformation::GetGCDataArrayVar() { if ((m_gcDataArrayVar == BAD_VAR_NUM) || !m_comp->lvaHaveManyLocals()) { @@ -1656,7 +1655,7 @@ unsigned Async2Transformation::GetGCDataArrayVar() } //------------------------------------------------------------------------ -// Async2Transformation::GetResultBaseVar: +// AsyncTransformation::GetResultBaseVar: // Create a new local to hold the base address of the incoming result from // the continuation. This local can be validly used for the entire suspension // point; the returned local may be used by multiple suspension points. @@ -1664,7 +1663,7 @@ unsigned Async2Transformation::GetGCDataArrayVar() // Returns: // Local number. // -unsigned Async2Transformation::GetResultBaseVar() +unsigned AsyncTransformation::GetResultBaseVar() { if ((m_resultBaseVar == BAD_VAR_NUM) || !m_comp->lvaHaveManyLocals()) { @@ -1676,7 +1675,7 @@ unsigned Async2Transformation::GetResultBaseVar() } //------------------------------------------------------------------------ -// Async2Transformation::GetExceptionVar: +// AsyncTransformation::GetExceptionVar: // Create a new local to hold the exception in the continuation. This // local can be validly used for the entire suspension point; the returned // local may be used by multiple suspension points. @@ -1684,7 +1683,7 @@ unsigned Async2Transformation::GetResultBaseVar() // Returns: // Local number. // -unsigned Async2Transformation::GetExceptionVar() +unsigned AsyncTransformation::GetExceptionVar() { if ((m_exceptionVar == BAD_VAR_NUM) || !m_comp->lvaHaveManyLocals()) { @@ -1696,14 +1695,14 @@ unsigned Async2Transformation::GetExceptionVar() } //------------------------------------------------------------------------ -// Async2Transformation::CreateResumptionStubAddrTree: +// AsyncTransformation::CreateResumptionStubAddrTree: // Create a tree that represents the address of the resumption stub entry // point. // // Returns: // IR node. // -GenTree* Async2Transformation::CreateResumptionStubAddrTree() +GenTree* AsyncTransformation::CreateResumptionStubAddrTree() { switch (m_resumeStubLookup.accessType) { @@ -1739,22 +1738,22 @@ GenTree* Async2Transformation::CreateResumptionStubAddrTree() } //------------------------------------------------------------------------ -// Async2Transformation::CreateFunctionTargetAddr: +// AsyncTransformation::CreateFunctionTargetAddr: // Create a tree that represents the address of the resumption stub entry // point. // // Returns: // IR node. // -GenTree* Async2Transformation::CreateFunctionTargetAddr(CORINFO_METHOD_HANDLE methHnd, - const CORINFO_CONST_LOOKUP& lookup) +GenTree* AsyncTransformation::CreateFunctionTargetAddr(CORINFO_METHOD_HANDLE methHnd, + const CORINFO_CONST_LOOKUP& lookup) { GenTree* con = m_comp->gtNewIconHandleNode((size_t)lookup.addr, GTF_ICON_FTN_ADDR); INDEBUG(con->AsIntCon()->gtTargetHandle = (size_t)methHnd); return con; } -void Async2Transformation::CreateResumptionSwitch() +void AsyncTransformation::CreateResumptionSwitch() { m_comp->fgCreateNewInitBB(); BasicBlock* newEntryBB = m_comp->fgFirstBB; @@ -1827,7 +1826,7 @@ void Async2Transformation::CreateResumptionSwitch() BBswtDesc* swtDesc = new (m_comp, CMK_BasicBlock) BBswtDesc; swtDesc->bbsCount = (unsigned)m_resumptionBBs.size(); swtDesc->bbsHasDefault = true; - swtDesc->bbsDstTab = new (m_comp, CMK_Async2) FlowEdge*[m_resumptionBBs.size()]; + swtDesc->bbsDstTab = new (m_comp, CMK_Async) FlowEdge*[m_resumptionBBs.size()]; weight_t stateLikelihood = 1.0 / m_resumptionBBs.size(); for (size_t i = 0; i < m_resumptionBBs.size(); i++) diff --git a/src/coreclr/jit/async.h b/src/coreclr/jit/async.h index a07283b2b5fbab..e7d3933be65a9d 100644 --- a/src/coreclr/jit/async.h +++ b/src/coreclr/jit/async.h @@ -41,7 +41,7 @@ struct CallDefinitionInfo GenTree* InsertAfter = nullptr; }; -class Async2Transformation +class AsyncTransformation { friend class AsyncLiveness; @@ -141,10 +141,10 @@ class Async2Transformation void CreateResumptionSwitch(); public: - Async2Transformation(Compiler* comp) + AsyncTransformation(Compiler* comp) : m_comp(comp) - , m_liveLocalsScratch(comp->getAllocator(CMK_Async2)) - , m_resumptionBBs(comp->getAllocator(CMK_Async2)) + , m_liveLocalsScratch(comp->getAllocator(CMK_Async)) + , m_resumptionBBs(comp->getAllocator(CMK_Async)) { } diff --git a/src/coreclr/jit/codegenarm.cpp b/src/coreclr/jit/codegenarm.cpp index 2089a7f409977f..2eec1d69d04613 100644 --- a/src/coreclr/jit/codegenarm.cpp +++ b/src/coreclr/jit/codegenarm.cpp @@ -2092,7 +2092,7 @@ regMaskTP CodeGen::genStackAllocRegisterMask(unsigned frameSize, regMaskTP maskC // We similarly skip it for async2 due to the extra async continuation // return that may be overridden by the pop. - if (compiler->compIsAsync2()) + if (compiler->compIsAsync()) { return RBM_NONE; } diff --git a/src/coreclr/jit/codegenarmarch.cpp b/src/coreclr/jit/codegenarmarch.cpp index fa634af8880998..97b84c1bb9b7c2 100644 --- a/src/coreclr/jit/codegenarmarch.cpp +++ b/src/coreclr/jit/codegenarmarch.cpp @@ -3545,7 +3545,7 @@ void CodeGen::genCallInstruction(GenTreeCall* call) } #endif // DEBUG - bool hasAsyncRet = call->IsAsync2(); + bool hasAsyncRet = call->IsAsync(); CORINFO_METHOD_HANDLE methHnd; GenTree* target = getCallTarget(call, &methHnd); diff --git a/src/coreclr/jit/codegencommon.cpp b/src/coreclr/jit/codegencommon.cpp index 79db11e1e102e4..164db290b34bb0 100644 --- a/src/coreclr/jit/codegencommon.cpp +++ b/src/coreclr/jit/codegencommon.cpp @@ -1938,7 +1938,7 @@ void CodeGen::genGenerateMachineCode() printf("; OSR variant for entry point 0x%x\n", compiler->info.compILEntry); } - if (compiler->compIsAsync2()) + if (compiler->compIsAsync()) { printf("; async2\n"); } @@ -7243,7 +7243,7 @@ void CodeGen::genReturn(GenTree* treeNode) } } - if (treeNode->OperIs(GT_RETURN) && compiler->compIsAsync2()) + if (treeNode->OperIs(GT_RETURN) && compiler->compIsAsync()) { instGen_Set_Reg_To_Zero(EA_PTRSIZE, REG_ASYNC_CONTINUATION_RET); } @@ -7368,7 +7368,7 @@ void CodeGen::genMarkReturnGCInfo() } } - if (compiler->compIsAsync2()) + if (compiler->compIsAsync()) { gcInfo.gcMarkRegPtrVal(REG_ASYNC_CONTINUATION_RET, TYP_REF); } diff --git a/src/coreclr/jit/codegenlinear.cpp b/src/coreclr/jit/codegenlinear.cpp index 42c13a966f78e8..f5ae5a5e9ba593 100644 --- a/src/coreclr/jit/codegenlinear.cpp +++ b/src/coreclr/jit/codegenlinear.cpp @@ -503,7 +503,7 @@ void CodeGen::genCodeForBBlist() } } - if (compiler->compIsAsync2()) + if (compiler->compIsAsync()) { nonVarPtrRegs &= ~RBM_ASYNC_CONTINUATION_RET; } diff --git a/src/coreclr/jit/codegenxarch.cpp b/src/coreclr/jit/codegenxarch.cpp index e74bff6589fc74..a3644fbc57c736 100644 --- a/src/coreclr/jit/codegenxarch.cpp +++ b/src/coreclr/jit/codegenxarch.cpp @@ -6321,7 +6321,7 @@ void CodeGen::genCallInstruction(GenTreeCall* call X86_ARG(target_ssize_t stackA } #endif // DEBUG - bool hasAsyncRet = call->IsAsync2(); + bool hasAsyncRet = call->IsAsync(); CORINFO_METHOD_HANDLE methHnd; GenTree* target = getCallTarget(call, &methHnd); if (target != nullptr) @@ -10540,7 +10540,7 @@ void CodeGen::genFnEpilog(BasicBlock* block) // Add 'compiler->compLclFrameSize' to ESP. Use "pop ECX" for that, except in cases // where ECX may contain some state. - if ((frameSize == TARGET_POINTER_SIZE) && !compiler->compJmpOpUsed && !compiler->compIsAsync2()) + if ((frameSize == TARGET_POINTER_SIZE) && !compiler->compJmpOpUsed && !compiler->compIsAsync()) { inst_RV(INS_pop, REG_ECX, TYP_I_IMPL); regSet.verifyRegUsed(REG_ECX); @@ -10628,7 +10628,7 @@ void CodeGen::genFnEpilog(BasicBlock* block) } #ifdef TARGET_X86 else if ((compiler->compLclFrameSize == REGSIZE_BYTES) && !compiler->compJmpOpUsed && - !compiler->compIsAsync2()) + !compiler->compIsAsync()) { // "pop ecx" will make ESP point to the callee-saved registers inst_RV(INS_pop, REG_ECX, TYP_I_IMPL); diff --git a/src/coreclr/jit/compiler.cpp b/src/coreclr/jit/compiler.cpp index 5fe5ada65b8bd5..bcbd88cb996df8 100644 --- a/src/coreclr/jit/compiler.cpp +++ b/src/coreclr/jit/compiler.cpp @@ -3161,7 +3161,7 @@ void Compiler::compInitOptions(JitFlags* jitFlags) printf("OPTIONS: Jit invoked for AOT\n"); } - if (compIsAsync2()) + if (compIsAsync()) { printf("OPTIONS: compilation is an async2 state machine\n"); } @@ -5008,9 +5008,9 @@ void Compiler::compCompile(void** methodCodePtr, uint32_t* methodCodeSize, JitFl } #endif // TARGET_ARM - if (compIsAsync2()) + if (compIsAsync()) { - DoPhase(this, PHASE_ASYNC2, &Compiler::TransformAsync2); + DoPhase(this, PHASE_ASYNC, &Compiler::TransformAsync); } // Assign registers to variables, etc. diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 9ecc0eb7d10551..37c601dc160d35 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -5533,7 +5533,7 @@ class Compiler PhaseStatus placeLoopAlignInstructions(); #endif - PhaseStatus TransformAsync2(); + PhaseStatus TransformAsync(); // This field keep the R2R helper call that would be inserted to trigger the constructor // of the static class. It is set as nongc or gc static base if they are imported, so @@ -7285,6 +7285,7 @@ class Compiler unsigned optValnumCSE_Index(GenTree* tree, Statement* stmt); bool optValnumCSE_Locate(CSE_HeuristicCommon* heuristic); void optValnumCSE_InitDataFlow(); + void optValnumCSE_SetUpAsyncByrefKills(); void optValnumCSE_DataFlow(); void optValnumCSE_Availability(); void optValnumCSE_Heuristic(CSE_HeuristicCommon* heuristic); @@ -10881,7 +10882,7 @@ class Compiler #endif // TARGET_AMD64 } - bool compIsAsync2() const + bool compIsAsync() const { return opts.jitFlags->IsSet(JitFlags::JIT_FLAG_RUNTIMEASYNCFUNCTION); } @@ -10915,7 +10916,7 @@ class Compiler bool compObjectStackAllocation() { - if (compIsAsync2()) + if (compIsAsync()) { // Object stack allocation takes the address of locals around // suspension points. Disable entirely for now. diff --git a/src/coreclr/jit/compmemkind.h b/src/coreclr/jit/compmemkind.h index 3aacd830407e44..eb2c0dffc0ee6e 100644 --- a/src/coreclr/jit/compmemkind.h +++ b/src/coreclr/jit/compmemkind.h @@ -66,7 +66,7 @@ CompMemKindMacro(ZeroInit) CompMemKindMacro(Pgo) CompMemKindMacro(MaskConversionOpt) CompMemKindMacro(TryRegionClone) -CompMemKindMacro(Async2) +CompMemKindMacro(Async) CompMemKindMacro(RangeCheckCloning) //clang-format on diff --git a/src/coreclr/jit/compphases.h b/src/coreclr/jit/compphases.h index 1a23c804c26c42..1d8c0b4e3c4a71 100644 --- a/src/coreclr/jit/compphases.h +++ b/src/coreclr/jit/compphases.h @@ -118,7 +118,7 @@ CompPhaseNameMacro(PHASE_DETERMINE_FIRST_COLD_BLOCK, "Determine first cold block CompPhaseNameMacro(PHASE_RATIONALIZE, "Rationalize IR", false, -1, false) CompPhaseNameMacro(PHASE_REPAIR_PROFILE, "Repair profile", false, -1, false) -CompPhaseNameMacro(PHASE_ASYNC2, "Transform async2", false, -1, true) +CompPhaseNameMacro(PHASE_ASYNC, "Transform async", false, -1, true) CompPhaseNameMacro(PHASE_LCLVARLIVENESS, "Local var liveness", true, -1, false) CompPhaseNameMacro(PHASE_LCLVARLIVENESS_INIT, "Local var liveness init", false, PHASE_LCLVARLIVENESS, false) CompPhaseNameMacro(PHASE_LCLVARLIVENESS_PERBLOCK, "Per block local var liveness", false, PHASE_LCLVARLIVENESS, false) diff --git a/src/coreclr/jit/fginline.cpp b/src/coreclr/jit/fginline.cpp index 125f6f2ff293e4..de1ad80ba38c06 100644 --- a/src/coreclr/jit/fginline.cpp +++ b/src/coreclr/jit/fginline.cpp @@ -1072,7 +1072,7 @@ void Compiler::fgMorphCallInlineHelper(GenTreeCall* call, InlineResult* result, return; } - if (call->gtIsAsyncCall && info.compUsesAsyncContinuation) + if (call->IsAsync() && info.compUsesAsyncContinuation) { // Currently not supported. Could provide a nice perf benefit for // async1 -> async2 thunks if we supported it. diff --git a/src/coreclr/jit/flowgraph.cpp b/src/coreclr/jit/flowgraph.cpp index ab120d1cb3939f..a8ea2f5c471ab6 100644 --- a/src/coreclr/jit/flowgraph.cpp +++ b/src/coreclr/jit/flowgraph.cpp @@ -2468,7 +2468,7 @@ PhaseStatus Compiler::fgAddInternal() // async2 functions we will be introducing another return during // the async2 transformation, so make sure there's a free epilog // for it. - if (compIsAsync2()) + if (compIsAsync()) { limit--; } diff --git a/src/coreclr/jit/gentree.cpp b/src/coreclr/jit/gentree.cpp index cd140ceb2d9d5a..5658494e9510f5 100644 --- a/src/coreclr/jit/gentree.cpp +++ b/src/coreclr/jit/gentree.cpp @@ -2281,9 +2281,9 @@ bool GenTreeCall::HasSideEffects(Compiler* compiler, bool ignoreExceptions, bool // behavior and GC reporting around the returned async continuation. This is // currently done in lowering; see LowerAsyncContinuation(). // -bool GenTreeCall::IsAsync2() const +bool GenTreeCall::IsAsync() const { - return gtIsAsyncCall; + return (gtCallMoreFlags & GTF_CALL_M_ASYNC) != 0; } //------------------------------------------------------------------------- @@ -8324,7 +8324,6 @@ GenTreeCall* Compiler::gtNewCallNode(gtCallTypes callType, node->gtRetClsHnd = nullptr; node->gtControlExpr = nullptr; node->gtCallMoreFlags = GTF_CALL_M_EMPTY; - node->gtIsAsyncCall = false; INDEBUG(node->gtCallDebugFlags = GTF_CALL_MD_EMPTY); node->gtInlineInfoCount = 0; @@ -9938,9 +9937,8 @@ GenTreeCall* Compiler::gtCloneExprCallHelper(GenTreeCall* tree) copy->gtLateDevirtualizationInfo = tree->gtLateDevirtualizationInfo; - copy->gtIsAsyncCall = tree->gtIsAsyncCall; - copy->gtCallType = tree->gtCallType; - copy->gtReturnType = tree->gtReturnType; + copy->gtCallType = tree->gtCallType; + copy->gtReturnType = tree->gtReturnType; #if FEATURE_MULTIREG_RET copy->gtReturnTypeDesc = tree->gtReturnTypeDesc; diff --git a/src/coreclr/jit/gentree.h b/src/coreclr/jit/gentree.h index 567a9ab315c431..8f8a1675c11abd 100644 --- a/src/coreclr/jit/gentree.h +++ b/src/coreclr/jit/gentree.h @@ -4238,6 +4238,7 @@ enum GenTreeCallFlags : unsigned int GTF_CALL_M_GUARDED_DEVIRT_CHAIN = 0x00080000, // this call is a candidate for chained guarded devirtualization GTF_CALL_M_ALLOC_SIDE_EFFECTS = 0x00100000, // this is a call to an allocator with side effects GTF_CALL_M_SUPPRESS_GC_TRANSITION = 0x00200000, // suppress the GC transition (i.e. during a pinvoke) but a separate GC safe point is required. + GTF_CALL_M_ASYNC = 0x00400000, // this call is a runtime async method call GTF_CALL_M_EXPANDED_EARLY = 0x00800000, // the Virtual Call target address is expanded and placed in gtControlExpr in Morph rather than in Lower GTF_CALL_M_LDVIRTFTN_INTERFACE = 0x01000000, // ldvirtftn on an interface type GTF_CALL_M_CAST_CAN_BE_EXPANDED = 0x02000000, // this cast (helper call) can be expanded if it's profitable. To be removed. @@ -5023,7 +5024,12 @@ struct GenTreeCall final : public GenTree #endif } - bool IsAsync2() const; + void SetIsAsync() + { + gtCallMoreFlags |= GTF_CALL_M_ASYNC; + } + + bool IsAsync() const; //--------------------------------------------------------------------------- // GetRegNumByIdx: get i'th return register allocated to this call node. @@ -5594,7 +5600,6 @@ struct GenTreeCall final : public GenTree var_types gtReturnType : 5; // exact return type uint8_t gtInlineInfoCount; // number of inline candidates for the given call - bool gtIsAsyncCall; CORINFO_CLASS_HANDLE gtRetClsHnd; // The return type handle of the call if it is a struct; always available union diff --git a/src/coreclr/jit/importer.cpp b/src/coreclr/jit/importer.cpp index 2f073ee7ba71e1..4623b61f85b6b7 100644 --- a/src/coreclr/jit/importer.cpp +++ b/src/coreclr/jit/importer.cpp @@ -847,7 +847,7 @@ GenTree* Compiler::impStoreStruct(GenTree* store, // Make sure we don't pass something other than a local address to the return buffer arg. // It is allowed to pass current's method return buffer as it is a local too. if ((fgAddrCouldBeHeap(destAddr) && !eeIsByrefLike(srcCall->gtRetClsHnd)) || - (compIsAsync2() && !destAddr->OperIs(GT_LCL_ADDR))) + (compIsAsync() && !destAddr->OperIs(GT_LCL_ADDR))) { unsigned tmp = lvaGrabTemp(false DEBUGARG("stack copy for value returned via return buffer")); lvaSetStruct(tmp, srcCall->gtRetClsHnd, false); @@ -974,7 +974,7 @@ GenTree* Compiler::impStoreStruct(GenTree* store, // Make sure we don't pass something other than a local address to the return buffer arg. // It is allowed to pass current's method return buffer as it is a local too. if ((fgAddrCouldBeHeap(destAddr) && !eeIsByrefLike(call->gtRetClsHnd)) || - (compIsAsync2() && !destAddr->OperIs(GT_LCL_ADDR))) + (compIsAsync() && !destAddr->OperIs(GT_LCL_ADDR))) { unsigned tmp = lvaGrabTemp(false DEBUGARG("stack copy for value returned via return buffer")); lvaSetStruct(tmp, call->gtRetClsHnd, false); @@ -9099,7 +9099,7 @@ void Compiler::impImportBlockCode(BasicBlock* block) // that control the flow of sync context. // We do not have that yet. int configVal = -1; // -1 not congigured, 0/1 configured to false/true - if (compIsAsync2() && JitConfig.JitOptimizeAwait()) + if (compIsAsync() && JitConfig.JitOptimizeAwait()) { isAwait = impMatchAwaitPattern(codeAddr, codeEndp, &configVal); } diff --git a/src/coreclr/jit/importercalls.cpp b/src/coreclr/jit/importercalls.cpp index 91ef811dfa2f15..e7e4b197f376af 100644 --- a/src/coreclr/jit/importercalls.cpp +++ b/src/coreclr/jit/importercalls.cpp @@ -524,13 +524,13 @@ var_types Compiler::impImportCall(OPCODE opcode, // Temporary hack since these functions have to be recognized as async2 // calls in JIT generated state machines only. - if (compIsAsync2() && + if (compIsAsync() && ((ni == NI_System_Runtime_CompilerServices_RuntimeHelpers_AwaitAwaiterFromRuntimeAsync) || (ni == NI_System_Runtime_CompilerServices_RuntimeHelpers_UnsafeAwaitAwaiterFromRuntimeAsync) || (ni == NI_System_Runtime_CompilerServices_RuntimeHelpers_Await))) { assert((call != nullptr) && call->OperIs(GT_CALL)); - call->AsCall()->gtIsAsyncCall = true; + call->AsCall()->SetIsAsync(); JITDUMP("Marking [%06u] as a special-case async call\n", dspTreeID(call)); } } @@ -724,7 +724,7 @@ var_types Compiler::impImportCall(OPCODE opcode, if (sig->isAsyncCall()) { - call->AsCall()->gtIsAsyncCall = true; + call->AsCall()->SetIsAsync(); } // Now create the argument list. @@ -900,7 +900,7 @@ var_types Compiler::impImportCall(OPCODE opcode, impPopCallArgs(sig, call->AsCall()); // Extra args - if ((instParam != nullptr) || call->AsCall()->IsAsync2() || (varArgsCookie != nullptr)) + if ((instParam != nullptr) || call->AsCall()->IsAsync() || (varArgsCookie != nullptr)) { if (Target::g_tgtArgOrder == Target::ARG_ORDER_R2L) { @@ -910,7 +910,7 @@ var_types Compiler::impImportCall(OPCODE opcode, .WellKnown(WellKnownArg::VarArgsCookie)); } - if (call->AsCall()->IsAsync2()) + if (call->AsCall()->IsAsync()) { call->AsCall()->gtArgs.PushFront(this, NewCallArg::Primitive(gtNewNull(), TYP_REF) .WellKnown(WellKnownArg::AsyncContinuation)); @@ -930,7 +930,7 @@ var_types Compiler::impImportCall(OPCODE opcode, NewCallArg::Primitive(instParam).WellKnown(WellKnownArg::InstParam)); } - if (call->AsCall()->IsAsync2()) + if (call->AsCall()->IsAsync()) { call->AsCall()->gtArgs.PushBack(this, NewCallArg::Primitive(gtNewNull(), TYP_REF) .WellKnown(WellKnownArg::AsyncContinuation)); diff --git a/src/coreclr/jit/lclvars.cpp b/src/coreclr/jit/lclvars.cpp index 5213828696bf53..bbd3fce509f652 100644 --- a/src/coreclr/jit/lclvars.cpp +++ b/src/coreclr/jit/lclvars.cpp @@ -161,7 +161,7 @@ void Compiler::lvaInitTypeRef() info.compTypeCtxtArg = BAD_VAR_NUM; } - if (compIsAsync2()) + if (compIsAsync()) { info.compArgsCount++; } @@ -694,7 +694,7 @@ void Compiler::lvaInitGenericsCtxt(unsigned* curVarNum) // void Compiler::lvaInitAsyncContinuation(unsigned* curVarNum) { - if (!compIsAsync2()) + if (!compIsAsync()) { return; } diff --git a/src/coreclr/jit/lower.cpp b/src/coreclr/jit/lower.cpp index 0d072a878adb74..d1db172cb5177d 100644 --- a/src/coreclr/jit/lower.cpp +++ b/src/coreclr/jit/lower.cpp @@ -5413,11 +5413,11 @@ void Lowering::LowerAsyncContinuation(GenTree* asyncCont) if (node->IsCall()) { - if (!node->AsCall()->IsAsync2()) + if (!node->AsCall()->IsAsync()) { JITDUMP("Marking the call [%06u] before async continuation [%06u] as an async call\n", Compiler::dspTreeID(node), Compiler::dspTreeID(asyncCont)); - node->AsCall()->gtIsAsyncCall = true; + node->AsCall()->SetIsAsync(); } break; diff --git a/src/coreclr/jit/lsraarmarch.cpp b/src/coreclr/jit/lsraarmarch.cpp index 289d55f155902e..2610501b4b74d4 100644 --- a/src/coreclr/jit/lsraarmarch.cpp +++ b/src/coreclr/jit/lsraarmarch.cpp @@ -302,7 +302,7 @@ int LinearScan::BuildCall(GenTreeCall* call) } #endif // SWIFT_SUPPORT - if (call->IsAsync2() && compiler->compIsAsync2()) + if (call->IsAsync() && compiler->compIsAsync()) { MarkAsyncContinuationBusyForCall(call); } diff --git a/src/coreclr/jit/lsraxarch.cpp b/src/coreclr/jit/lsraxarch.cpp index 83a3fa2f160126..76160df2857a1b 100644 --- a/src/coreclr/jit/lsraxarch.cpp +++ b/src/coreclr/jit/lsraxarch.cpp @@ -1394,7 +1394,7 @@ int LinearScan::BuildCall(GenTreeCall* call) } #endif // SWIFT_SUPPORT - if (call->IsAsync2() && compiler->compIsAsync2()) + if (call->IsAsync() && compiler->compIsAsync()) { MarkAsyncContinuationBusyForCall(call); } diff --git a/src/coreclr/jit/morph.cpp b/src/coreclr/jit/morph.cpp index 2a8873e0d8ccde..f2eed98cdc65dd 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -189,7 +189,6 @@ GenTree* Compiler::fgMorphIntoHelperCall(GenTree* tree, int helper, bool morphAr call->gtCallMoreFlags = GTF_CALL_M_EMPTY; INDEBUG(call->gtCallDebugFlags = GTF_CALL_MD_EMPTY); call->gtControlExpr = nullptr; - call->gtIsAsyncCall = false; call->ClearInlineInfo(); #ifdef UNIX_X86_ABI call->gtFlags |= GTF_CALL_POP_ARGS; @@ -4493,7 +4492,7 @@ GenTree* Compiler::fgMorphPotentialTailCall(GenTreeCall* call) } #endif - if (compIsAsync2() != call->IsAsync2()) + if (compIsAsync() != call->IsAsync()) { failTailCall("Caller and callee do not agree on async2-ness"); return nullptr; diff --git a/src/coreclr/jit/optcse.cpp b/src/coreclr/jit/optcse.cpp index d3df3d3bdec1e4..711e1d91e9a109 100644 --- a/src/coreclr/jit/optcse.cpp +++ b/src/coreclr/jit/optcse.cpp @@ -999,9 +999,9 @@ void Compiler::optValnumCSE_InitDataFlow() } } - if (compIsAsync2()) + if (compIsAsync()) { - optValnumCSE_SetUpAsync2ByrefKills(); + optValnumCSE_SetUpAsyncByrefKills(); } for (BasicBlock* const block : Blocks()) @@ -1088,11 +1088,11 @@ void Compiler::optValnumCSE_InitDataFlow() } //--------------------------------------------------------------------------- -// optValnumCSE_SetUpAsync2ByrefKills: -// Compute kills because of async2 calls requiring byrefs not to be live +// optValnumCSE_SetUpAsyncByrefKills: +// Compute kills because of async calls requiring byrefs not to be live // across them. // -void Compiler::optValnumCSE_SetUpAsync2ByrefKills() +void Compiler::optValnumCSE_SetUpAsyncByrefKills() { bool anyAsyncKills = false; cseAsyncKillsMask = BitVecOps::MakeFull(cseLivenessTraits); @@ -1143,7 +1143,7 @@ void Compiler::optValnumCSE_SetUpAsync2ByrefKills() { for (GenTree* tree = stmt->GetRootNode(); tree != nullptr; tree = tree->gtPrev) { - if (tree->IsCall() && tree->AsCall()->IsAsync2()) + if (tree->IsCall() && tree->AsCall()->IsAsync()) { asyncCallStmt = stmt; asyncCall = tree; @@ -1707,7 +1707,7 @@ void Compiler::optValnumCSE_Availability() BitVecOps::IntersectionD(cseLivenessTraits, available_cses, cseCallKillsMask); // In async state machines, make all byref CSEs unavailable after suspension points. - if (tree->AsCall()->IsAsync2() && compIsAsync2()) + if (tree->AsCall()->IsAsync() && compIsAsync()) { BitVecOps::IntersectionD(cseLivenessTraits, available_cses, cseAsyncKillsMask); } From 7364c0cb2a640a47a8d622d595f27a301904bbe5 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Tue, 1 Apr 2025 16:13:59 +0200 Subject: [PATCH 186/203] Rename in RISC-V and LA64 as well --- src/coreclr/jit/lsraloongarch64.cpp | 2 +- src/coreclr/jit/lsrariscv64.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/coreclr/jit/lsraloongarch64.cpp b/src/coreclr/jit/lsraloongarch64.cpp index 18d978e6db2ac7..3dd551bd2757bc 100644 --- a/src/coreclr/jit/lsraloongarch64.cpp +++ b/src/coreclr/jit/lsraloongarch64.cpp @@ -814,7 +814,7 @@ int LinearScan::BuildCall(GenTreeCall* call) BuildKills(call, killMask); } - if (call->IsAsync2() && compiler->compIsAsync2()) + if (call->IsAsync() && compiler->compIsAsync()) { MarkAsyncContinuationBusyForCall(call); } diff --git a/src/coreclr/jit/lsrariscv64.cpp b/src/coreclr/jit/lsrariscv64.cpp index 1621146ddb07a7..6fa1561e47696a 100644 --- a/src/coreclr/jit/lsrariscv64.cpp +++ b/src/coreclr/jit/lsrariscv64.cpp @@ -994,7 +994,7 @@ int LinearScan::BuildCall(GenTreeCall* call) BuildKills(call, killMask); } - if (call->IsAsync2() && compiler->compIsAsync2()) + if (call->IsAsync() && compiler->compIsAsync()) { MarkAsyncContinuationBusyForCall(call); } From e6599877f139ab930fa88f6a8a61a5598e144535 Mon Sep 17 00:00:00 2001 From: vsadov <8218165+VSadov@users.noreply.github.com> Date: Thu, 3 Apr 2025 15:08:13 -0700 Subject: [PATCH 187/203] remove code added to eetwain and gcinfodecoder --- src/coreclr/inc/eetwain.h | 2 -- src/coreclr/vm/eetwain.cpp | 14 ++------------ src/coreclr/vm/gcinfodecoder.cpp | 2 +- 3 files changed, 3 insertions(+), 15 deletions(-) diff --git a/src/coreclr/inc/eetwain.h b/src/coreclr/inc/eetwain.h index 3d69f4180faf89..96860e694f3da3 100644 --- a/src/coreclr/inc/eetwain.h +++ b/src/coreclr/inc/eetwain.h @@ -106,8 +106,6 @@ enum ICodeManagerFlags ReportFPBasedSlotsOnly = 0x0200, // EnumGCRefs/EnumerateLiveSlots should only include // slots that are based on the frame pointer - NoGcDecoderValidation - = 0x0400, // Turn off GCDecoder validation }; //***************************************************************************** diff --git a/src/coreclr/vm/eetwain.cpp b/src/coreclr/vm/eetwain.cpp index 0c6dc08f8b1c65..9377c42aeed003 100644 --- a/src/coreclr/vm/eetwain.cpp +++ b/src/coreclr/vm/eetwain.cpp @@ -1393,19 +1393,9 @@ bool EECodeManager::EnumGcRefs( PREGDISPLAY pRD, reportScratchSlots = (flags & ActiveStackFrame) != 0; - GcInfoDecoderFlags decoderFlags; - if (flags & NoGcDecoderValidation) - { - decoderFlags = GcInfoDecoderFlags (DECODE_GC_LIFETIMES | DECODE_SECURITY_OBJECT | DECODE_VARARG | DECODE_NO_VALIDATION); - } - else - { - decoderFlags = GcInfoDecoderFlags (DECODE_GC_LIFETIMES | DECODE_SECURITY_OBJECT | DECODE_VARARG); - } - GcInfoDecoder gcInfoDecoder( gcInfoToken, - decoderFlags, + GcInfoDecoderFlags (DECODE_GC_LIFETIMES | DECODE_SECURITY_OBJECT | DECODE_VARARG), curOffs ); @@ -2341,4 +2331,4 @@ size_t InterpreterCodeManager::GetFunctionSize(GCInfoToken gcInfoToken) return 0; } -#endif // FEATURE_INTERPRETER \ No newline at end of file +#endif // FEATURE_INTERPRETER diff --git a/src/coreclr/vm/gcinfodecoder.cpp b/src/coreclr/vm/gcinfodecoder.cpp index a94335bb991a9a..bccb5313b6a0d9 100644 --- a/src/coreclr/vm/gcinfodecoder.cpp +++ b/src/coreclr/vm/gcinfodecoder.cpp @@ -57,7 +57,7 @@ #ifndef LOG_PIPTR #define LOG_PIPTR(pObjRef, gcFlags, hCallBack) \ - if (!(m_Flags & DECODE_NO_VALIDATION)) { \ + { \ GCCONTEXT* pGCCtx = (GCCONTEXT*)(hCallBack); \ if (pGCCtx->sc->promotion) \ { \ From 0aff212500f5ff65159290c63e8e8c77633b33c4 Mon Sep 17 00:00:00 2001 From: vsadov <8218165+VSadov@users.noreply.github.com> Date: Thu, 3 Apr 2025 15:31:27 -0700 Subject: [PATCH 188/203] fix after merge --- src/coreclr/vm/jithelpers.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/coreclr/vm/jithelpers.cpp b/src/coreclr/vm/jithelpers.cpp index 676f1070f09f53..b50bd8099b3504 100644 --- a/src/coreclr/vm/jithelpers.cpp +++ b/src/coreclr/vm/jithelpers.cpp @@ -1697,11 +1697,8 @@ HCIMPL1(void, IL_ThrowExact, Object* obj) GetThread()->GetExceptionState()->SetRaisingForeignException(); #ifdef FEATURE_EH_FUNCLETS - if (g_isNewExceptionHandlingEnabled) - { - DispatchManagedException(oref); - UNREACHABLE(); - } + DispatchManagedException(oref); + UNREACHABLE(); #endif RaiseTheExceptionInternalOnly(oref, FALSE); From 211a28141a7bd4389034ec7b9674e53ff9c5f067 Mon Sep 17 00:00:00 2001 From: vsadov <8218165+VSadov@users.noreply.github.com> Date: Thu, 3 Apr 2025 17:14:50 -0700 Subject: [PATCH 189/203] undo unnecessary change in QCallHandlers.cs --- .../src/System/Runtime/CompilerServices/QCallHandles.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/QCallHandles.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/QCallHandles.cs index 2e9517bdef790f..10b955478ecd39 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/QCallHandles.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/QCallHandles.cs @@ -34,7 +34,7 @@ internal static ObjectHandleOnStack Create(ref T o) where T : class? } } - internal unsafe ref struct ByteRef + internal ref struct ByteRef { private ref byte _ref; From 6bb549287658ac5cf5cd372024f310b22776d954 Mon Sep 17 00:00:00 2001 From: vsadov <8218165+VSadov@users.noreply.github.com> Date: Thu, 3 Apr 2025 18:18:42 -0700 Subject: [PATCH 190/203] reference continuation return from volatileCurrContextPointers on ARM[64] --- src/coreclr/vm/arm/stubs.cpp | 3 +++ src/coreclr/vm/arm64/stubs.cpp | 1 + 2 files changed, 4 insertions(+) diff --git a/src/coreclr/vm/arm/stubs.cpp b/src/coreclr/vm/arm/stubs.cpp index cb665654d0cac4..56293fb430df5e 100644 --- a/src/coreclr/vm/arm/stubs.cpp +++ b/src/coreclr/vm/arm/stubs.cpp @@ -1595,7 +1595,10 @@ void HijackFrame::UpdateRegDisplay_Impl(const PREGDISPLAY pRD, bool updateFloats pRD->pCurrentContext->Sp = PTR_TO_TADDR(m_Args) + sizeof(struct HijackArgs); pRD->pCurrentContext->R0 = m_Args->R0; + pRD->pCurrentContext->R2 = m_Args->R2; + pRD->volatileCurrContextPointers.R0 = &m_Args->R0; + pRD->volatileCurrContextPointers.R2 = &m_Args->R2; pRD->pCurrentContext->R4 = m_Args->R4; pRD->pCurrentContext->R5 = m_Args->R5; diff --git a/src/coreclr/vm/arm64/stubs.cpp b/src/coreclr/vm/arm64/stubs.cpp index 5588b65b3bc2d0..751595b9f528b3 100644 --- a/src/coreclr/vm/arm64/stubs.cpp +++ b/src/coreclr/vm/arm64/stubs.cpp @@ -772,6 +772,7 @@ void HijackFrame::UpdateRegDisplay_Impl(const PREGDISPLAY pRD, bool updateFloats pRD->volatileCurrContextPointers.X0 = &m_Args->X0; pRD->volatileCurrContextPointers.X1 = &m_Args->X1; + pRD->volatileCurrContextPointers.X1 = &m_Args->X2; pRD->pCurrentContext->X19 = m_Args->X19; pRD->pCurrentContext->X20 = m_Args->X20; From df5b767871fadd92c36ed548110c65840bd36a5e Mon Sep 17 00:00:00 2001 From: Vladimir Sadov Date: Fri, 4 Apr 2025 16:52:43 -0700 Subject: [PATCH 191/203] PR feedback --- .../System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs index 5aad763334d045..066573811cd5eb 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs @@ -552,7 +552,7 @@ private void MergeWithGlobalList(T[] list) // Grow the list by exactly one element in this case to avoid null entries at the end. // - // TODO: runtime-async we need to rationalize how async2 thunks eork in reflection. + // TODO: runtime-async we need to rationalize how async2 thunks work in reflection. // possibly they should not be exposed as they are runtime-provided implementation // details, that in theory may change. // @@ -771,8 +771,7 @@ private RuntimeConstructorInfo[] PopulateConstructors(Filter filter) if ((methodAttributes & MethodAttributes.RTSpecialName) == 0) continue; - if (!RuntimeMethodHandle.GetName(methodHandle).Equals(".ctor") && - !RuntimeMethodHandle.GetName(methodHandle).Equals(".cctor")) + if (RuntimeMethodHandle.GetName(methodHandle) is not ".ctor" and not ".cctor") continue; // Constructors should not be virtual or abstract From af8488749fb8afc395a6bba3bbb7985a0b193a49 Mon Sep 17 00:00:00 2001 From: vsadov <8218165+VSadov@users.noreply.github.com> Date: Fri, 4 Apr 2025 17:19:49 -0700 Subject: [PATCH 192/203] remove unnecessary change --- src/coreclr/jit/block.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/jit/block.h b/src/coreclr/jit/block.h index 2becb39f992089..0ecf8bdf42557f 100644 --- a/src/coreclr/jit/block.h +++ b/src/coreclr/jit/block.h @@ -447,7 +447,7 @@ enum BasicBlockFlags : uint64_t BBF_COLD = MAKE_BBFLAG(23), // BB is cold BBF_PROF_WEIGHT = MAKE_BBFLAG(24), // BB weight is computed from profile data BBF_KEEP_BBJ_ALWAYS = MAKE_BBFLAG(25), // A special BBJ_ALWAYS block, used by EH code generation. Keep the jump kind - // as BBJ_ALWAYS. Used on x86 for the final step block out of a finally + // as BBJ_ALWAYS. Used on x86 for the final step block out of a finally. BBF_HAS_CALL = MAKE_BBFLAG(26), // BB contains a call BBF_DOMINATED_BY_EXCEPTIONAL_ENTRY = MAKE_BBFLAG(27), // Block is dominated by exceptional entry. BBF_BACKWARD_JUMP = MAKE_BBFLAG(28), // BB is surrounded by a backward jump/switch arc From 7ba09e8a6110c2d4cdb15b955b77ced76d05e3f9 Mon Sep 17 00:00:00 2001 From: vsadov <8218165+VSadov@users.noreply.github.com> Date: Fri, 4 Apr 2025 18:00:34 -0700 Subject: [PATCH 193/203] fix for x86 --- src/coreclr/jit/importercalls.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/jit/importercalls.cpp b/src/coreclr/jit/importercalls.cpp index c8718e346e34e4..9bec6d242e5748 100644 --- a/src/coreclr/jit/importercalls.cpp +++ b/src/coreclr/jit/importercalls.cpp @@ -940,7 +940,7 @@ var_types Compiler::impImportCall(OPCODE opcode, if (varArgsCookie != nullptr) { - call->AsCall()->gtArgs.PushBack(this, NewCallArg::Primitive(varArgsCookie, TYP_REF) + call->AsCall()->gtArgs.PushBack(this, NewCallArg::Primitive(varArgsCookie) .WellKnown(WellKnownArg::VarArgsCookie)); } } From 50e1e8500755c71946dfc2703d42f4f82d6cfef6 Mon Sep 17 00:00:00 2001 From: vsadov <8218165+VSadov@users.noreply.github.com> Date: Fri, 4 Apr 2025 19:09:20 -0700 Subject: [PATCH 194/203] fix for arm32 --- src/coreclr/vm/arm/stubs.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/coreclr/vm/arm/stubs.cpp b/src/coreclr/vm/arm/stubs.cpp index 56293fb430df5e..e28af8bcd67aaa 100644 --- a/src/coreclr/vm/arm/stubs.cpp +++ b/src/coreclr/vm/arm/stubs.cpp @@ -1592,7 +1592,11 @@ void HijackFrame::UpdateRegDisplay_Impl(const PREGDISPLAY pRD, bool updateFloats pRD->IsCallerSPValid = FALSE; pRD->pCurrentContext->Pc = m_ReturnAddress; - pRD->pCurrentContext->Sp = PTR_TO_TADDR(m_Args) + sizeof(struct HijackArgs); + size_t s = sizeof(struct HijackArgs); + _ASSERTE(s%4 == 0); // HijackArgs contains register values and hence will be a multiple of 4 + // stack must be multiple of 8. So if s is not multiple of 8 then there must be padding of 4 bytes + s = s + s%8; + pRD->pCurrentContext->Sp = PTR_TO_TADDR(m_Args) + s ; pRD->pCurrentContext->R0 = m_Args->R0; pRD->pCurrentContext->R2 = m_Args->R2; From b1fe9e272f3c1e2aea40f63fbc4c16413789fdf7 Mon Sep 17 00:00:00 2001 From: vsadov <8218165+VSadov@users.noreply.github.com> Date: Sat, 5 Apr 2025 13:21:11 -0700 Subject: [PATCH 195/203] more fixing for arm32 --- src/coreclr/vm/arm/asmhelpers.S | 9 ++++----- src/coreclr/vm/arm/cgencpu.h | 4 ++++ 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/coreclr/vm/arm/asmhelpers.S b/src/coreclr/vm/arm/asmhelpers.S index be2e22f0ccf058..d8e0e62992bd64 100644 --- a/src/coreclr/vm/arm/asmhelpers.S +++ b/src/coreclr/vm/arm/asmhelpers.S @@ -884,20 +884,19 @@ DelayLoad_Helper\suffix: // ------------------------------------------------------------------ // Hijack function for functions which return a value type NESTED_ENTRY OnHijackTripThread, _TEXT, NoHandler - PROLOG_PUSH "{r0,r2,r4-r11,lr}" + // saving r1 as well, as it can have partial return value when return is > 32 bits + PROLOG_PUSH "{r0,r1,r2,r4-r11,lr}" PROLOG_VPUSH "{d0-d3}" // saving as d0-d3 can have the floating point return value - PROLOG_PUSH "{r1}" // saving as r1 can have partial return value when return is > 32 bits CHECK_STACK_ALIGNMENT - add r0, sp, #40 + add r0, sp, #50 bl C_FUNC(OnHijackWorker) - EPILOG_POP "{r1}" EPILOG_VPOP "{d0-d3}" - EPILOG_POP "{r0,r2,r4-r11,pc}" + EPILOG_POP "{r0,r1,r2,r4-r11,pc}" NESTED_END OnHijackTripThread, _TEXT #endif diff --git a/src/coreclr/vm/arm/cgencpu.h b/src/coreclr/vm/arm/cgencpu.h index d2fba390a34ffd..6ae9d7e3b580d5 100644 --- a/src/coreclr/vm/arm/cgencpu.h +++ b/src/coreclr/vm/arm/cgencpu.h @@ -919,6 +919,10 @@ struct HijackArgs // this is only used by functions OnHijackWorker() }; + // saving r1 as well, as it can have partial return value when return is > 32 bits + // also keeps the struct size 8-byte aligned. + DWORD R1; + union { DWORD R2; From 030c9887c24d1c0da839adbc9b101de41ac641a4 Mon Sep 17 00:00:00 2001 From: vsadov <8218165+VSadov@users.noreply.github.com> Date: Sat, 5 Apr 2025 16:33:00 -0700 Subject: [PATCH 196/203] typo --- src/coreclr/vm/arm/asmhelpers.S | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/vm/arm/asmhelpers.S b/src/coreclr/vm/arm/asmhelpers.S index d8e0e62992bd64..cf1c8f76d447ab 100644 --- a/src/coreclr/vm/arm/asmhelpers.S +++ b/src/coreclr/vm/arm/asmhelpers.S @@ -891,7 +891,7 @@ DelayLoad_Helper\suffix: CHECK_STACK_ALIGNMENT - add r0, sp, #50 + add r0, sp, #40 bl C_FUNC(OnHijackWorker) EPILOG_VPOP "{d0-d3}" From 3f3fe9e6867eca04a36e76dfc450fbe7189f2958 Mon Sep 17 00:00:00 2001 From: vsadov <8218165+VSadov@users.noreply.github.com> Date: Sat, 5 Apr 2025 20:23:44 -0700 Subject: [PATCH 197/203] actual fix? --- src/coreclr/vm/arm/asmhelpers.S | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/vm/arm/asmhelpers.S b/src/coreclr/vm/arm/asmhelpers.S index cf1c8f76d447ab..973de5db340ea7 100644 --- a/src/coreclr/vm/arm/asmhelpers.S +++ b/src/coreclr/vm/arm/asmhelpers.S @@ -891,7 +891,7 @@ DelayLoad_Helper\suffix: CHECK_STACK_ALIGNMENT - add r0, sp, #40 + add r0, sp, #32 bl C_FUNC(OnHijackWorker) EPILOG_VPOP "{d0-d3}" From d5e4faf14509409e23f2c85dc58f8bc4a54b2ed9 Mon Sep 17 00:00:00 2001 From: vsadov <8218165+VSadov@users.noreply.github.com> Date: Sun, 6 Apr 2025 16:22:12 -0700 Subject: [PATCH 198/203] hide async2 variants from reflection --- src/coreclr/vm/runtimehandles.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/coreclr/vm/runtimehandles.cpp b/src/coreclr/vm/runtimehandles.cpp index 7ac7928f6e2266..434849f278d079 100644 --- a/src/coreclr/vm/runtimehandles.cpp +++ b/src/coreclr/vm/runtimehandles.cpp @@ -1826,6 +1826,10 @@ FCIMPL2(MethodDesc*, RuntimeMethodHandle::GetStubIfNeededInternal, TypeHandle instType = refType->GetType(); + // do not report async2 variants to reflection. + if (pMethod->IsAsync2VariantMethod()) + return NULL; + // Perf optimization: this logic is actually duplicated in FindOrCreateAssociatedMethodDescForReflection, but since it // is the more common case it's worth the duplicate check here to avoid the helper method frame if (pMethod->HasMethodInstantiation() @@ -1850,6 +1854,12 @@ extern "C" MethodDesc* QCALLTYPE RuntimeMethodHandle_GetStubIfNeededSlow(MethodD GCX_COOP(); + if (pMethod->IsAsync2VariantMethod()) + { + // do not report async2 variants to reflection. + pMethod = pMethod->GetAsyncOtherVariant(/*allowInstParam*/ false); + } + TypeHandle instType = declaringTypeHandle.AsTypeHandle(); TypeHandle* inst = NULL; From 6e43cadc60a7dcc49ddb25d98c84e1f89713aefd Mon Sep 17 00:00:00 2001 From: vsadov <8218165+VSadov@users.noreply.github.com> Date: Sun, 6 Apr 2025 16:35:01 -0700 Subject: [PATCH 199/203] undo reflection workaround --- .../src/System/RuntimeType.CoreCLR.cs | 70 ++++++++----------- 1 file changed, 31 insertions(+), 39 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs index 066573811cd5eb..cbee1aff762651 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs @@ -407,34 +407,34 @@ internal void Insert(ref T[] list, string? name, MemberListType listType) switch (listType) { case MemberListType.CaseSensitive: + { + // Ensure we always return a list that has + // been merged with the global list. + T[]? cachedList = m_csMemberInfos[name!]; + if (cachedList == null) { - // Ensure we always return a list that has - // been merged with the global list. - T[]? cachedList = m_csMemberInfos[name!]; - if (cachedList == null) - { - MergeWithGlobalList(list); - m_csMemberInfos[name!] = list; - } - else - list = cachedList; + MergeWithGlobalList(list); + m_csMemberInfos[name!] = list; } - break; + else + list = cachedList; + } + break; case MemberListType.CaseInsensitive: + { + // Ensure we always return a list that has + // been merged with the global list. + T[]? cachedList = m_cisMemberInfos[name!]; + if (cachedList == null) { - // Ensure we always return a list that has - // been merged with the global list. - T[]? cachedList = m_cisMemberInfos[name!]; - if (cachedList == null) - { - MergeWithGlobalList(list); - m_cisMemberInfos[name!] = list; - } - else - list = cachedList; + MergeWithGlobalList(list); + m_cisMemberInfos[name!] = list; } - break; + else + list = cachedList; + } + break; case MemberListType.All: if (!m_cacheComplete) @@ -552,12 +552,7 @@ private void MergeWithGlobalList(T[] list) // Grow the list by exactly one element in this case to avoid null entries at the end. // - // TODO: runtime-async we need to rationalize how async2 thunks work in reflection. - // possibly they should not be exposed as they are runtime-provided implementation - // details, that in theory may change. - // - // For now I will disable this assert as we may get here with extra methods. - // Debug.Assert(false); + Debug.Assert(false); newSize = cachedMembers.Length + 1; } @@ -609,9 +604,9 @@ private unsafe RuntimeMethodInfo[] PopulateMethods(Filter filter) MethodAttributes methodAttributes = RuntimeMethodHandle.GetAttributes(methodHandle); #region Continue if this is a constructor - //Debug.Assert( - // (RuntimeMethodHandle.GetAttributes(methodHandle) & MethodAttributes.RTSpecialName) == 0 || - // RuntimeMethodHandle.GetName(methodHandle).Equals(".cctor")); + Debug.Assert( + (RuntimeMethodHandle.GetAttributes(methodHandle) & MethodAttributes.RTSpecialName) == 0 || + RuntimeMethodHandle.GetName(methodHandle).Equals(".cctor")); if ((methodAttributes & MethodAttributes.RTSpecialName) != 0) continue; @@ -668,10 +663,10 @@ private unsafe RuntimeMethodInfo[] PopulateMethods(Filter filter) MethodAttributes methodAccess = methodAttributes & MethodAttributes.MemberAccessMask; #region Continue if this is a constructor - //Debug.Assert( - // (RuntimeMethodHandle.GetAttributes(methodHandle) & MethodAttributes.RTSpecialName) == 0 || - // RuntimeMethodHandle.GetName(methodHandle).Equals(".ctor") || - // RuntimeMethodHandle.GetName(methodHandle).Equals(".cctor")); + Debug.Assert( + (RuntimeMethodHandle.GetAttributes(methodHandle) & MethodAttributes.RTSpecialName) == 0 || + RuntimeMethodHandle.GetName(methodHandle).Equals(".ctor") || + RuntimeMethodHandle.GetName(methodHandle).Equals(".cctor")); if ((methodAttributes & MethodAttributes.RTSpecialName) != 0) continue; @@ -771,9 +766,6 @@ private RuntimeConstructorInfo[] PopulateConstructors(Filter filter) if ((methodAttributes & MethodAttributes.RTSpecialName) == 0) continue; - if (RuntimeMethodHandle.GetName(methodHandle) is not ".ctor" and not ".cctor") - continue; - // Constructors should not be virtual or abstract Debug.Assert( (methodAttributes & MethodAttributes.Abstract) == 0 && @@ -2379,7 +2371,7 @@ private static bool FilterApplyMethodBase( #endregion -#endregion + #endregion #region Private Data Members From f015aec2753681ce9c20b34e3e492f0e9a68bd96 Mon Sep 17 00:00:00 2001 From: vsadov <8218165+VSadov@users.noreply.github.com> Date: Sun, 6 Apr 2025 17:12:11 -0700 Subject: [PATCH 200/203] undo unnecessary EnC related changes --- src/coreclr/debug/ee/functioninfo.cpp | 2 +- src/coreclr/vm/encee.cpp | 1 - src/coreclr/vm/methoditer.cpp | 19 +------------------ src/coreclr/vm/methoditer.h | 4 ---- 4 files changed, 2 insertions(+), 24 deletions(-) diff --git a/src/coreclr/debug/ee/functioninfo.cpp b/src/coreclr/debug/ee/functioninfo.cpp index c7613dfd5d55c2..cbe39f0d4ac902 100644 --- a/src/coreclr/debug/ee/functioninfo.cpp +++ b/src/coreclr/debug/ee/functioninfo.cpp @@ -2022,7 +2022,7 @@ void DebuggerMethodInfo::CreateDJIsForNativeBlobs(AppDomain * pAppDomain, Method // have DJIs for every verision of a method that was EnCed. // This also handles the possibility of getting the same methoddesc back from the iterator. // It also lets EnC + generics play nice together (including if an generic method was EnC-ed) - LoadedMethodDescIterator it(pAppDomain, m_module, m_token, /* fIsAsync2Variant */false); // TODO: EnC doesn't handle async2 variants now + LoadedMethodDescIterator it(pAppDomain, m_module, m_token); CollectibleAssemblyHolder pAssembly; while (it.Next(pAssembly.This())) { diff --git a/src/coreclr/vm/encee.cpp b/src/coreclr/vm/encee.cpp index 5b1d2ee0fa601b..b61b8acdc8bfe2 100644 --- a/src/coreclr/vm/encee.cpp +++ b/src/coreclr/vm/encee.cpp @@ -342,7 +342,6 @@ HRESULT EditAndContinueModule::UpdateMethod(MethodDesc *pMethod) AppDomain::GetCurrentDomain(), module, tkMethod, - /* fIsAsync2Variant */false, // TODO: EnC doesn't handle async2 variants now AssemblyIterationFlags(kIncludeLoaded | kIncludeExecution)); CollectibleAssemblyHolder pAssembly; while (it.Next(pAssembly.This())) diff --git a/src/coreclr/vm/methoditer.cpp b/src/coreclr/vm/methoditer.cpp index 79af7b137359e1..7f84806123d5a5 100644 --- a/src/coreclr/vm/methoditer.cpp +++ b/src/coreclr/vm/methoditer.cpp @@ -57,17 +57,6 @@ BOOL LoadedMethodDescIterator::Next( return FALSE; } - if (m_fIsAsync2Variant && m_mainMD->IsTaskReturningMethod()) - { - m_mainMD = m_mainMD->GetMethodTable()->GetParallelMethodDesc(m_mainMD, AsyncVariantLookup::AsyncOtherVariant); - - if (m_mainMD == NULL) - { - *pAssemblyHolder = NULL; - return FALSE; - } - } - // Needs to work w/ non-generic methods too. if (!m_mainMD->HasClassOrMethodInstantiation()) { @@ -147,8 +136,6 @@ BOOL LoadedMethodDescIterator::Next( goto ADVANCE_METHOD; if (m_methodIteratorEntry->GetMethod()->GetMemberDef() != m_md) goto ADVANCE_METHOD; - if (m_methodIteratorEntry->GetMethod()->IsAsync2VariantMethod() != m_fIsAsync2Variant) - goto ADVANCE_METHOD; } else if (m_startedNonGenericMethod) { @@ -216,7 +203,6 @@ LoadedMethodDescIterator::Start( AppDomain * pAppDomain, Module *pModule, mdMethodDef md, - bool fIsAsync2Variant, AssemblyIterationFlags assemblyIterationFlags) { CONTRACTL @@ -228,8 +214,6 @@ LoadedMethodDescIterator::Start( } CONTRACTL_END; - m_fIsAsync2Variant = fIsAsync2Variant; - m_assemIterationFlags = assemblyIterationFlags; m_mainMD = NULL; m_module = pModule; @@ -250,7 +234,7 @@ LoadedMethodDescIterator::Start( mdMethodDef md, MethodDesc *pMethodDesc) { - Start(pAppDomain, pModule, md, /*m_fIsAsync2Variant*/ false); // TODO: EnC doesn't handle async2 variants now + Start(pAppDomain, pModule, md); m_mainMD = pMethodDesc; } @@ -261,5 +245,4 @@ LoadedMethodDescIterator::LoadedMethodDescIterator(void) m_module = NULL; m_md = mdTokenNil; m_pAppDomain = NULL; - m_fIsAsync2Variant = false; } diff --git a/src/coreclr/vm/methoditer.h b/src/coreclr/vm/methoditer.h index 2a0ddf8f83f13f..90ccc67e96f6c5 100644 --- a/src/coreclr/vm/methoditer.h +++ b/src/coreclr/vm/methoditer.h @@ -34,8 +34,6 @@ class LoadedMethodDescIterator mdMethodDef m_md; MethodDesc * m_mainMD; AppDomain * m_pAppDomain; - bool m_fIsAsync2Variant; - // The following hold the state of the iteration.... // Yes we iterate everything for the moment - we need @@ -70,7 +68,6 @@ class LoadedMethodDescIterator void Start(AppDomain * pAppDomain, Module *pModule, mdMethodDef md, - bool fIsAsync2Variant, AssemblyIterationFlags assemIterationFlags = (AssemblyIterationFlags)(kIncludeLoaded | kIncludeExecution)); void Start(AppDomain * pAppDomain, Module *pModule, mdMethodDef md, MethodDesc *pDesc); @@ -78,7 +75,6 @@ class LoadedMethodDescIterator AppDomain * pAppDomain, Module *pModule, mdMethodDef md, - bool fIsAsync2Variant, AssemblyIterationFlags assemblyIterationFlags = (AssemblyIterationFlags)(kIncludeLoaded | kIncludeExecution)) { LIMITED_METHOD_CONTRACT; From 32d27f81e0a969f69904f4aa1e594976bed2a162 Mon Sep 17 00:00:00 2001 From: Vladimir Sadov Date: Sun, 6 Apr 2025 17:34:48 -0700 Subject: [PATCH 201/203] Undo whitespace only changes --- src/coreclr/gc/sample/gcenv.ee.cpp | 2 +- src/coreclr/vm/eetwain.cpp | 2 +- src/coreclr/vm/gcenv.ee.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/coreclr/gc/sample/gcenv.ee.cpp b/src/coreclr/gc/sample/gcenv.ee.cpp index 19ceb56fc16f07..4aba23c9a077dc 100644 --- a/src/coreclr/gc/sample/gcenv.ee.cpp +++ b/src/coreclr/gc/sample/gcenv.ee.cpp @@ -366,4 +366,4 @@ void GCToEEInterface::LogErrorToHost(const char *message) uint64_t GCToEEInterface::GetThreadOSThreadId(Thread* thread) { return 0; -} +} \ No newline at end of file diff --git a/src/coreclr/vm/eetwain.cpp b/src/coreclr/vm/eetwain.cpp index 51cb1daf1ac271..7f6b27c14be328 100644 --- a/src/coreclr/vm/eetwain.cpp +++ b/src/coreclr/vm/eetwain.cpp @@ -2401,4 +2401,4 @@ size_t InterpreterCodeManager::GetFunctionSize(GCInfoToken gcInfoToken) return 0; } -#endif // FEATURE_INTERPRETER +#endif // FEATURE_INTERPRETER \ No newline at end of file diff --git a/src/coreclr/vm/gcenv.ee.cpp b/src/coreclr/vm/gcenv.ee.cpp index 749174e575b7b0..4738b0d7aeb8fa 100644 --- a/src/coreclr/vm/gcenv.ee.cpp +++ b/src/coreclr/vm/gcenv.ee.cpp @@ -1832,4 +1832,4 @@ void GCToEEInterface::LogErrorToHost(const char *message) uint64_t GCToEEInterface::GetThreadOSThreadId(Thread* thread) { return thread->GetOSThreadId64(); -} +} \ No newline at end of file From 86075c1614fba5b2c5ef758f19a28568fb1f0a01 Mon Sep 17 00:00:00 2001 From: vsadov <8218165+VSadov@users.noreply.github.com> Date: Sun, 6 Apr 2025 19:17:04 -0700 Subject: [PATCH 202/203] some trivial cleanups --- src/coreclr/vm/commodule.cpp | 10 ++++++++-- src/coreclr/vm/jitinterface.cpp | 4 ++-- src/coreclr/vm/method.hpp | 2 +- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/coreclr/vm/commodule.cpp b/src/coreclr/vm/commodule.cpp index 1b79649fdfb046..e89b5cf3362a25 100644 --- a/src/coreclr/vm/commodule.cpp +++ b/src/coreclr/vm/commodule.cpp @@ -302,7 +302,13 @@ extern "C" INT32 QCALLTYPE ModuleBuilder_GetMemberRefOfMethodInfo(QCall::ModuleH COMPlusThrow(kNotSupportedException); } - if ((pMeth->GetMethodTable()->GetModule() == pModule) && !pMeth->IsAsync2VariantMethod()) + if (pMeth->IsAsync2VariantMethod()) + { + _ASSERTE(!"Should not have come here!"); + COMPlusThrow(kNotSupportedException); + } + + if ((pMeth->GetMethodTable()->GetModule() == pModule)) { // If the passed in method is defined in the same module, just return the MethodDef token memberRefE = pMeth->GetMemberDef(); @@ -317,7 +323,7 @@ extern "C" INT32 QCALLTYPE ModuleBuilder_GetMemberRefOfMethodInfo(QCall::ModuleH ULONG cbComSig; PCCOR_SIGNATURE pvComSig; - pMeth->GetSig(&pvComSig, &cbComSig); + IfFailThrow(pMeth->GetMDImport()->GetSigOfMethodDef(pMeth->GetMemberDef(), &cbComSig, &pvComSig)); // Translate the method sig into this scope Assembly * pRefedAssembly = pMeth->GetModule()->GetAssembly(); diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index 4d794b4dfb0aab..5e98fbbeccb43d 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -418,6 +418,8 @@ enum ConvToJitSigFlags : int CONV_TO_JITSIG_FLAGS_LOCALSIG = 0x1, }; +AsyncMethodSignatureKind ClassifyAsyncMethodSignatureCore(SigPointer sig, Module* pModule, PCCOR_SIGNATURE initialSig, ULONG* offsetOfAsyncDetails, bool *isValueTask); + //--------------------------------------------------------------------------------------- // //@GENERICS: @@ -432,8 +434,6 @@ enum ConvToJitSigFlags : int // localSig - Is it a local variables declaration, or a method signature (with return type, etc). // contextType - The type with any instantiaton information // -AsyncMethodSignatureKind ClassifyAsyncMethodSignatureCore(SigPointer sig, Module* pModule, PCCOR_SIGNATURE initialSig, ULONG* offsetOfAsyncDetails, bool *isValueTask); - static void ConvToJitSig( SigPointer sig, CORINFO_MODULE_HANDLE scopeHnd, diff --git a/src/coreclr/vm/method.hpp b/src/coreclr/vm/method.hpp index 9151b35df01d5d..d02dda8b256ae4 100644 --- a/src/coreclr/vm/method.hpp +++ b/src/coreclr/vm/method.hpp @@ -59,7 +59,7 @@ enum class AsyncMethodKind { // Regular methods not returning tasks // These are "normal" methods that do not get other variants. - // Note: Generic T-returning methods are NotAsync, even if T could be a Task. + // NOTE: Generic T-returning methods are NotAsync, even if T could be a Task. NotAsync, // Regular methods that return Task/ValueTask From 21fcafa7a32689fe096d66ca858d10a42ad6ec16 Mon Sep 17 00:00:00 2001 From: vsadov <8218165+VSadov@users.noreply.github.com> Date: Sun, 6 Apr 2025 22:33:15 -0700 Subject: [PATCH 203/203] IntroducedMethodIterator should return all methods, filter async variants in API used in reflection, --- src/coreclr/vm/methodtable.cpp | 46 ++++++++++++------------------- src/coreclr/vm/runtimehandles.cpp | 5 ++++ 2 files changed, 22 insertions(+), 29 deletions(-) diff --git a/src/coreclr/vm/methodtable.cpp b/src/coreclr/vm/methodtable.cpp index e0d7c320497332..efb2ad647f2993 100644 --- a/src/coreclr/vm/methodtable.cpp +++ b/src/coreclr/vm/methodtable.cpp @@ -7293,15 +7293,7 @@ MethodDesc * MethodTable::IntroducedMethodIterator::GetFirst(MethodTable *pMT) { LIMITED_METHOD_CONTRACT; MethodDescChunk * pChunk = pMT->GetClass()->GetChunks(); - if (pChunk == NULL) - return NULL; - - MethodDesc* md = pChunk->GetFirstMethodDesc(); - if (!md->IsAsync2VariantMethod()) - return md; - - // skip async2 variants, the other variant will be reported instead - return GetNext(md); + return (pChunk != NULL) ? pChunk->GetFirstMethodDesc() : NULL; } //========================================================================================== @@ -7310,30 +7302,26 @@ MethodDesc * MethodTable::IntroducedMethodIterator::GetNext(MethodDesc * pMD) WRAPPER_NO_CONTRACT; MethodDescChunk * pChunk = pMD->GetMethodDescChunk(); - do - { - // Check whether the next MethodDesc is still within the bounds of the current chunk - TADDR pNext = dac_cast(pMD) + pMD->SizeOf(); - TADDR pEnd = dac_cast(pChunk) + pChunk->SizeOf(); - if (pNext < pEnd) - { - // Just skip to the next method in the same chunk - pMD = PTR_MethodDesc(pNext); - } - else - { - _ASSERTE(pNext == pEnd); + // Check whether the next MethodDesc is still within the bounds of the current chunk + TADDR pNext = dac_cast(pMD) + pMD->SizeOf(); + TADDR pEnd = dac_cast(pChunk) + pChunk->SizeOf(); - // We have walked all the methods in the current chunk. Move on - // to the next chunk. - pChunk = pChunk->GetNextChunk(); + if (pNext < pEnd) + { + // Just skip to the next method in the same chunk + pMD = PTR_MethodDesc(pNext); + } + else + { + _ASSERTE(pNext == pEnd); - pMD = (pChunk != NULL) ? pChunk->GetFirstMethodDesc() : NULL; - } + // We have walked all the methods in the current chunk. Move on + // to the next chunk. + pChunk = pChunk->GetNextChunk(); - // skip async2 variants, the other variant will be reported instead - } while (pMD != NULL && pMD->IsAsync2VariantMethod()); + pMD = (pChunk != NULL) ? pChunk->GetFirstMethodDesc() : NULL; + } return pMD; } diff --git a/src/coreclr/vm/runtimehandles.cpp b/src/coreclr/vm/runtimehandles.cpp index 434849f278d079..a5015847d6e697 100644 --- a/src/coreclr/vm/runtimehandles.cpp +++ b/src/coreclr/vm/runtimehandles.cpp @@ -191,6 +191,9 @@ FCIMPL1(MethodDesc *, RuntimeTypeHandle::GetFirstIntroducedMethod, ReflectClassB MethodTable* pMT = typeHandle.AsMethodTable(); MethodDesc* pMethod = MethodTable::IntroducedMethodIterator::GetFirst(pMT); + while (pMethod && pMethod->IsAsync2VariantMethod()) + pMethod = MethodTable::IntroducedMethodIterator::GetNext(pMethod); + return pMethod; } FCIMPLEND @@ -205,6 +208,8 @@ FCIMPL1(void, RuntimeTypeHandle::GetNextIntroducedMethod, MethodDesc ** ppMethod CONTRACTL_END; MethodDesc *pMethod = MethodTable::IntroducedMethodIterator::GetNext(*ppMethod); + while (pMethod && pMethod->IsAsync2VariantMethod()) + pMethod = MethodTable::IntroducedMethodIterator::GetNext(pMethod); *ppMethod = pMethod; }