From 9a936b51c9c794cd806cd86d47ac3632a50d22da Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Fri, 27 Jun 2025 22:54:30 +0200 Subject: [PATCH 1/5] Handle async continuation in TransitionFrame and ArgIterator --- src/coreclr/jit/importercalls.cpp | 12 +-- src/coreclr/jit/lclvars.cpp | 4 +- src/coreclr/vm/callingconvention.h | 126 ++++++++++++++++++++---- src/coreclr/vm/frames.cpp | 11 +++ src/coreclr/vm/jitinterface.cpp | 9 +- src/coreclr/vm/prestub.cpp | 19 ++-- src/coreclr/vm/reflectioninvocation.cpp | 7 ++ 7 files changed, 144 insertions(+), 44 deletions(-) diff --git a/src/coreclr/jit/importercalls.cpp b/src/coreclr/jit/importercalls.cpp index 10ac50c6dabbad..0d3b76426efec2 100644 --- a/src/coreclr/jit/importercalls.cpp +++ b/src/coreclr/jit/importercalls.cpp @@ -899,18 +899,18 @@ var_types Compiler::impImportCall(OPCODE opcode, } else { - if (instParam != nullptr) - { - call->AsCall()->gtArgs.PushBack(this, - NewCallArg::Primitive(instParam).WellKnown(WellKnownArg::InstParam)); - } - if (call->AsCall()->IsAsync()) { call->AsCall()->gtArgs.PushBack(this, NewCallArg::Primitive(gtNewNull(), TYP_REF) .WellKnown(WellKnownArg::AsyncContinuation)); } + if (instParam != nullptr) + { + call->AsCall()->gtArgs.PushBack(this, + NewCallArg::Primitive(instParam).WellKnown(WellKnownArg::InstParam)); + } + if (varArgsCookie != nullptr) { call->AsCall()->gtArgs.PushBack(this, NewCallArg::Primitive(varArgsCookie) diff --git a/src/coreclr/jit/lclvars.cpp b/src/coreclr/jit/lclvars.cpp index 246cccee168018..07e584dbe441eb 100644 --- a/src/coreclr/jit/lclvars.cpp +++ b/src/coreclr/jit/lclvars.cpp @@ -387,12 +387,12 @@ void Compiler::lvaInitArgs(bool hasRetBuffArg) //------------------------------------------------------------------------- lvaInitUserArgs(&varNum, numUserArgsToSkip, numUserArgs); #if !USER_ARGS_COME_LAST + lvaInitAsyncContinuation(&varNum); + //@GENERICS: final instantiation-info argument for shared generic methods // and shared generic struct instance methods lvaInitGenericsCtxt(&varNum); - lvaInitAsyncContinuation(&varNum); - /* If the method is varargs, process the varargs cookie */ lvaInitVarArgsHandle(&varNum); #endif diff --git a/src/coreclr/vm/callingconvention.h b/src/coreclr/vm/callingconvention.h index 98e6eee147fcd6..5fbff84b36523d 100644 --- a/src/coreclr/vm/callingconvention.h +++ b/src/coreclr/vm/callingconvention.h @@ -641,6 +641,7 @@ class ArgIteratorTemplate : public ARGITERATOR_BASE int GetRetBuffArgOffset(); int GetVASigCookieOffset(); int GetParamTypeArgOffset(); + int GetAsyncContinuationArgOffset(); //------------------------------------------------------------ // Each time this is called, this returns a byte offset of the next @@ -1000,21 +1001,26 @@ class ArgIteratorTemplate : public ARGITERATOR_BASE #endif enum { - ITERATION_STARTED = 0x0001, // Started iterating over arguments - SIZE_OF_ARG_STACK_COMPUTED = 0x0002, - RETURN_FLAGS_COMPUTED = 0x0004, - RETURN_HAS_RET_BUFFER = 0x0008, // Cached value of HasRetBuffArg + ITERATION_STARTED = 0x0001, // Started iterating over arguments + SIZE_OF_ARG_STACK_COMPUTED = 0x0002, + RETURN_FLAGS_COMPUTED = 0x0004, + RETURN_HAS_RET_BUFFER = 0x0008, // Cached value of HasRetBuffArg #ifdef TARGET_X86 - PARAM_TYPE_REGISTER_MASK = 0x0030, - PARAM_TYPE_REGISTER_STACK = 0x0010, - PARAM_TYPE_REGISTER_ECX = 0x0020, - PARAM_TYPE_REGISTER_EDX = 0x0030, + PARAM_TYPE_REGISTER_MASK = 0x0030, + PARAM_TYPE_REGISTER_STACK = 0x0010, + PARAM_TYPE_REGISTER_ECX = 0x0020, + PARAM_TYPE_REGISTER_EDX = 0x0030, + + ASYNC_CONTINUATION_REGISTER_MASK = 0x00C0, + ASYNC_CONTINUATION_REGISTER_STACK = 0x0040, + ASYNC_CONTINUATION_REGISTER_ECX = 0x0080, + ASYNC_CONTINUATION_REGISTER_EDX = 0x00C0, #endif - METHOD_INVOKE_NEEDS_ACTIVATION = 0x0040, // Flag used by ArgIteratorForMethodInvoke + METHOD_INVOKE_NEEDS_ACTIVATION = 0x0100, // Flag used by ArgIteratorForMethodInvoke - RETURN_FP_SIZE_SHIFT = 8, // The rest of the flags is cached value of GetFPReturnSize + RETURN_FP_SIZE_SHIFT = 10, // The rest of the flags is cached value of GetFPReturnSize }; void ComputeReturnFlags(); @@ -1158,6 +1164,68 @@ int ArgIteratorTemplate::GetParamTypeArgOffset() #endif } +template +int ArgIteratorTemplate::GetAsyncContinuationArgOffset() +{ + CONTRACTL + { + INSTANCE_CHECK; + if (FORBIDGC_LOADER_USE_ENABLED()) NOTHROW; else THROWS; + if (FORBIDGC_LOADER_USE_ENABLED()) GC_NOTRIGGER; else GC_TRIGGERS; + if (FORBIDGC_LOADER_USE_ENABLED()) FORBID_FAULT; else { INJECT_FAULT(COMPlusThrowOM()); } + MODE_ANY; + } + CONTRACTL_END + + _ASSERTE(this->HasAsyncContinuation()); + +#ifdef TARGET_X86 + // x86 is special as always + if (!(m_dwFlags & SIZE_OF_ARG_STACK_COMPUTED)) + ForceSigWalk(); + + switch (m_dwFlags & ASYNC_CONTINUATION_REGISTER_MASK) + { + case ASYNC_CONTINUATION_REGISTER_ECX: + return TransitionBlock::GetOffsetOfArgumentRegisters() + offsetof(ArgumentRegisters, ECX); + case ASYNC_CONTINUATION_REGISTER_EDX: + return TransitionBlock::GetOffsetOfArgumentRegisters() + offsetof(ArgumentRegisters, EDX); + default: + break; + } + + // If the async continuation is a stack arg, then it comes last unless + // there also is a param type arg on the stack, in which case it comes + // before it. + if (this->HasParamType() && (m_dwFlags & PARAM_TYPE_REGISTER_MASK) == PARAM_TYPE_REGISTER_STACK) + { + return sizeof(TransitionBlock) + sizeof(void*); + } + + return sizeof(TransitionBlock); +#else + // The hidden arg is after this, retbuf and param type arguments by default. + int ret = TransitionBlock::GetOffsetOfArgumentRegisters(); + + if (this->HasThis()) + { + ret += TARGET_POINTER_SIZE; + } + + if (this->HasRetBuffArg() && IsRetBuffPassedAsFirstArg()) + { + ret += TARGET_POINTER_SIZE; + } + + if (this->HasParamType()) + { + ret += TARGET_POINTER_SIZE; + } + + return ret; +#endif +} + // To avoid corner case bugs, limit maximum size of the arguments with sufficient margin #define MAX_ARG_SIZE 0xFFFFFF @@ -1190,6 +1258,11 @@ int ArgIteratorTemplate::GetNextOffset() { numRegistersUsed++; } + + if (this->HasAsyncContinuation()) + { + numRegistersUsed++; + } #endif #ifdef TARGET_X86 @@ -2002,6 +2075,23 @@ void ArgIteratorTemplate::ForceSigWalk() } } + if (this->HasAsyncContinuation()) + { + DWORD asyncContFlags = 0; + if (numRegistersUsed < NUM_ARGUMENT_REGISTERS) + { + numRegistersUsed++; + asyncContFlags = (numRegistersUsed == 1) ? + ASYNC_CONTINUATION_REGISTER_ECX : ASYNC_CONTINUATION_REGISTER_EDX; + } + else + { + nSizeOfArgStack += sizeof(void *); + asyncContFlags = ASYNC_CONTINUATION_REGISTER_STACK; + } + m_dwFlags |= asyncContFlags; + } + if (this->HasParamType()) { DWORD paramTypeFlags = 0; @@ -2139,25 +2229,19 @@ class ArgIteratorBase BOOL HasParamType() { LIMITED_METHOD_CONTRACT; - return m_pSig->GetCallingConventionInfo() & CORINFO_CALLCONV_PARAMTYPE; + return m_pSig->HasGenericContextArg(); } - BOOL IsVarArg() - { - LIMITED_METHOD_CONTRACT; - return m_pSig->IsVarArg() || m_pSig->IsTreatAsVarArg(); - } - - BOOL IsAsyncCall() + BOOL HasAsyncContinuation() { LIMITED_METHOD_CONTRACT; - return m_pSig->IsAsyncCall(); + return m_pSig->HasAsyncContinuation(); } - BOOL HasAsyncContinuation() + BOOL IsVarArg() { LIMITED_METHOD_CONTRACT; - return m_pSig->HasAsyncContinuation(); + return m_pSig->IsVarArg() || m_pSig->IsTreatAsVarArg(); } DWORD NumFixedArgs() diff --git a/src/coreclr/vm/frames.cpp b/src/coreclr/vm/frames.cpp index ad434fb4464c60..41c872ec9f9f67 100644 --- a/src/coreclr/vm/frames.cpp +++ b/src/coreclr/vm/frames.cpp @@ -1530,6 +1530,17 @@ void TransitionFrame::PromoteCallerStackHelper(promote_func* fn, ScanContext* sc (fn)(PTR_PTR_Object(pThis), sc, CHECK_APP_DOMAIN); } + // Promote async continuation for async methods + if (argit.HasAsyncContinuation()) + { + PTR_PTR_VOID pAsyncCont = dac_cast(pTransitionBlock + argit.GetAsyncContinuationArgOffset()); + LOG((LF_GC, INFO3, + " async continuation argument at " FMT_ADDR "promoted from" FMT_ADDR "\n", + DBG_ADDR(pAsyncCont), DBG_ADDR(*pAsyncCont) )); + + (fn)(PTR_PTR_Object(pAsyncCont), sc, CHECK_APP_DOMAIN); + } + int argOffset; while ((argOffset = argit.GetNextOffset()) != TransitionBlock::InvalidOffset) { diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index 784043ae4b4cb4..58ab3747478a9d 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -14472,12 +14472,12 @@ static Signature BuildResumptionStubCalliSignature(MetaSig& msig, MethodTable* m } #ifdef TARGET_X86 + sigBuilder.AppendElementType(ELEMENT_TYPE_OBJECT); // continuation + if (msig.HasGenericContextArg()) { sigBuilder.AppendElementType(ELEMENT_TYPE_I); } - - sigBuilder.AppendElementType(ELEMENT_TYPE_OBJECT); // continuation #endif return AllocateSignature(alloc, sigBuilder, pamTracker); @@ -14548,14 +14548,15 @@ CORINFO_METHOD_HANDLE CEEJitInfo::getAsyncResumptionStub() } #ifdef TARGET_X86 + // Continuation + pCode->EmitLDARG(0); + if (msig.HasGenericContextArg()) { pCode->EmitLDC(0); numArgs++; } - // Continuation - pCode->EmitLDARG(0); numArgs++; #endif diff --git a/src/coreclr/vm/prestub.cpp b/src/coreclr/vm/prestub.cpp index d63251294dab6a..9b3ea8b7c4dfbd 100644 --- a/src/coreclr/vm/prestub.cpp +++ b/src/coreclr/vm/prestub.cpp @@ -1438,15 +1438,15 @@ void MethodDesc::CreateDerivedTargetSigWithExtraParams(MetaSig& msig, SigBuilder } #ifdef TARGET_X86 - if (msig.HasGenericContextArg()) + if (msig.HasAsyncContinuation()) { - // The hidden context parameter - stubSigBuilder->AppendElementType(ELEMENT_TYPE_I); + stubSigBuilder->AppendElementType(ELEMENT_TYPE_OBJECT); } - if (msig.HasAsyncContinuation()) + if (msig.HasGenericContextArg()) { - stubSigBuilder->AppendElementType(ELEMENT_TYPE_OBJECT); + // The hidden context parameter + stubSigBuilder->AppendElementType(ELEMENT_TYPE_I); } #endif // TARGET_X86 } @@ -1469,6 +1469,7 @@ Stub * CreateUnboxingILStubForSharedGenericValueTypeMethods(MethodDesc* pTargetM MetaSig msig(pTargetMD); _ASSERTE(msig.HasThis()); + _ASSERTE(!msig.HasAsyncContinuation()); ILStubLinker sl(pTargetMD->GetModule(), pTargetMD->GetSignature(), @@ -1585,6 +1586,8 @@ Stub * CreateInstantiatingILStub(MethodDesc* pTargetMD, void* pHiddenArg) ILCodeStream *pCode = sl.NewCodeStream(ILStubLinker::kDispatch); + _ASSERTE(!msig.HasAsyncContinuation()); + // Build the new signature SigBuilder stubSigBuilder; MethodDesc::CreateDerivedTargetSigWithExtraParams(msig, &stubSigBuilder); @@ -1608,12 +1611,6 @@ Stub * CreateInstantiatingILStub(MethodDesc* pTargetMD, void* pHiddenArg) // InstantiatingStub pCode->EmitLDC((TADDR)pHiddenArg); - // Push the async continuation - if (msig.HasAsyncContinuation()) - { - pCode->EmitLDNULL(); - } - #if !defined(TARGET_X86) // Push the rest of the arguments for not x86 for (unsigned i = 0; i < msig.NumFixedArgs();i++) diff --git a/src/coreclr/vm/reflectioninvocation.cpp b/src/coreclr/vm/reflectioninvocation.cpp index b3a8b99bb92a52..e46be5f69ad5b6 100644 --- a/src/coreclr/vm/reflectioninvocation.cpp +++ b/src/coreclr/vm/reflectioninvocation.cpp @@ -242,6 +242,13 @@ class ArgIteratorBaseForMethodInvoke return FALSE; } + BOOL HasAsyncContinuation() + { + LIMITED_METHOD_CONTRACT; + // async calls are also not supported for reflection invoke + return FALSE; + } + BOOL IsVarArg() { LIMITED_METHOD_CONTRACT; From ec203246d67e94e7eebc587d48f35cd45b97c478 Mon Sep 17 00:00:00 2001 From: vsadov <8218165+VSadov@users.noreply.github.com> Date: Thu, 3 Jul 2025 03:27:44 -0700 Subject: [PATCH 2/5] gcstress support for async continuation on x86 --- src/coreclr/vm/gccover.cpp | 56 ++++++++++++++++++++++++++++++-------- src/coreclr/vm/gccover.h | 8 ++++-- 2 files changed, 50 insertions(+), 14 deletions(-) diff --git a/src/coreclr/vm/gccover.cpp b/src/coreclr/vm/gccover.cpp index 063857a8bd98d4..036c6d675e44d7 100644 --- a/src/coreclr/vm/gccover.cpp +++ b/src/coreclr/vm/gccover.cpp @@ -444,13 +444,27 @@ void ReplaceInstrAfterCall(PBYTE instrToReplace, MethodDesc* callMD) return; } - if (IsPointerReturnKind(returnKind)) + if (callMD->IsAsyncMethod()) { - *instrToReplace = INTERRUPT_INSTR_PROTECT_FIRST_RET; + if (IsPointerReturnKind(returnKind)) + { + *instrToReplace = INTERRUPT_INSTR_PROTECT_CONT_AND_RET; + } + else + { + *instrToReplace = INTERRUPT_INSTR_PROTECT_CONT; + } } else { - *instrToReplace = INTERRUPT_INSTR; + if (IsPointerReturnKind(returnKind)) + { + *instrToReplace = INTERRUPT_INSTR_PROTECT_RET; + } + else + { + *instrToReplace = INTERRUPT_INSTR; + } } } @@ -639,14 +653,22 @@ void DoGcStress (PCONTEXT regs, NativeCodeVersion nativeCodeVersion) // `if (!IsGcCoverageInterruptInstruction(instrPtr))` after we read `*instrPtr`. bool atCall; - bool afterCallProtect = false; + bool afterCallRetProtect = false; + bool afterCallContProtect = false; BYTE instrVal = *instrPtr; atCall = (instrVal == INTERRUPT_INSTR_CALL); - if (instrVal == INTERRUPT_INSTR_PROTECT_FIRST_RET) + if (instrVal == INTERRUPT_INSTR_PROTECT_RET || + instrVal == INTERRUPT_INSTR_PROTECT_CONT_AND_RET) { - afterCallProtect = true; + afterCallRetProtect = true; + } + + if (instrVal == INTERRUPT_INSTR_PROTECT_CONT || + instrVal == INTERRUPT_INSTR_PROTECT_CONT_AND_RET) + { + afterCallContProtect = true; } if (!IsGcCoverageInterruptInstruction(instrPtr)) @@ -842,15 +864,20 @@ void DoGcStress (PCONTEXT regs, NativeCodeVersion nativeCodeVersion) // The legacy X86 GC encoder does not encode the state of return registers at // call sites, so we must add an extra frame to protect returns. - DWORD_PTR retValReg = 0; + DWORD_PTR protRegs[2] = {}; - if (afterCallProtect) + if (afterCallRetProtect) { - retValReg = regs->Eax; + protRegs[0] = regs->Eax; + } + + if (afterCallContProtect) + { + protRegs[1] = regs->Ecx; } _ASSERTE(sizeof(OBJECTREF) == sizeof(DWORD_PTR)); - GCFrame gcFrame(pThread, (OBJECTREF*)&retValReg, 1, TRUE); + GCFrame gcFrame(pThread, (OBJECTREF*)protRegs, 2, TRUE); MethodDesc *pMD = nativeCodeVersion.GetMethodDesc(); LOG((LF_GCROOTS, LL_EVERYTHING, "GCCOVER: Doing GC at method %s::%s offset 0x%x\n", @@ -876,9 +903,14 @@ void DoGcStress (PCONTEXT regs, NativeCodeVersion nativeCodeVersion) CONSISTENCY_CHECK(!pThread->HasPendingGCStressInstructionUpdate()); - if (afterCallProtect) + if (afterCallRetProtect) + { + regs->Eax = protRegs[0]; + } + + if (afterCallContProtect) { - regs->Eax = retValReg; + regs->Ecx = protRegs[1]; } if (!Thread::UseRedirectForGcStress()) diff --git a/src/coreclr/vm/gccover.h b/src/coreclr/vm/gccover.h index 182a24bb975752..3310c3d8bc9bce 100644 --- a/src/coreclr/vm/gccover.h +++ b/src/coreclr/vm/gccover.h @@ -58,7 +58,9 @@ typedef DPTR(GCCoverageInfo) PTR_GCCoverageInfo; // see code:GCCoverageInfo::sav #if defined(TARGET_X86) #define INTERRUPT_INSTR_CALL 0xFA // X86 CLI instruction -#define INTERRUPT_INSTR_PROTECT_FIRST_RET 0xFB // X86 STI instruction, protect the first return register +#define INTERRUPT_INSTR_PROTECT_RET 0xFB // X86 STI instruction, protect the first return register +#define INTERRUPT_INSTR_PROTECT_CONT 0xEC // X86 IN instruction, protect the continuation register +#define INTERRUPT_INSTR_PROTECT_CONT_AND_RET 0xED // X86 IN instruction, protect both continuation and return registers #endif #elif defined(TARGET_ARM) @@ -117,7 +119,9 @@ inline bool IsGcCoverageInterruptInstructionVal(UINT32 instrVal) { case INTERRUPT_INSTR: case INTERRUPT_INSTR_CALL: - case INTERRUPT_INSTR_PROTECT_FIRST_RET: + case INTERRUPT_INSTR_PROTECT_RET: + case INTERRUPT_INSTR_PROTECT_CONT: + case INTERRUPT_INSTR_PROTECT_CONT_AND_RET: return true; default: return false; From 5e2c285b54ff28e9d7b9ab77ec70100a2df6ff70 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Tue, 1 Jul 2025 16:25:04 +0200 Subject: [PATCH 3/5] Rename test --- .../reflection/{reflectionSimple.cs => reflection-simple.cs} | 0 .../{reflectionSimple.csproj => reflection-simple.csproj} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename src/tests/async/reflection/{reflectionSimple.cs => reflection-simple.cs} (100%) rename src/tests/async/reflection/{reflectionSimple.csproj => reflection-simple.csproj} (100%) diff --git a/src/tests/async/reflection/reflectionSimple.cs b/src/tests/async/reflection/reflection-simple.cs similarity index 100% rename from src/tests/async/reflection/reflectionSimple.cs rename to src/tests/async/reflection/reflection-simple.cs diff --git a/src/tests/async/reflection/reflectionSimple.csproj b/src/tests/async/reflection/reflection-simple.csproj similarity index 100% rename from src/tests/async/reflection/reflectionSimple.csproj rename to src/tests/async/reflection/reflection-simple.csproj From 365d51400d2affeacfbe62f887f20717f7ad3081 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Thu, 3 Jul 2025 13:54:50 +0200 Subject: [PATCH 4/5] Update ABI --- docs/design/coreclr/botr/clr-abi.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/design/coreclr/botr/clr-abi.md b/docs/design/coreclr/botr/clr-abi.md index 8d872db6bc188f..af53e558712b5c 100644 --- a/docs/design/coreclr/botr/clr-abi.md +++ b/docs/design/coreclr/botr/clr-abi.md @@ -116,12 +116,12 @@ To return `Continuation` we use a volatile/calee-trash register that cannot be u | risc-v | a2 | ### Passing `Continuation` argument -The `Continuation` parameter is passed at the same position as generic instantiation parameter or immediately after, if both present. +The `Continuation` parameter is passed at the same position as generic instantiation parameter or immediately after, if both present. For x86 the argument order is reversed. ``` call(["this" pointer] [return buffer pointer] [generics context] [continuation] [userargs]) // not x86 -call(["this" pointer] [return buffer pointer] [userargs] [generics context] [continuation]) // x86 +call(["this" pointer] [return buffer pointer] [userargs] [continuation] [generics context]) // x86 ``` ## AMD64-only: by-value value types From 083719c24384fbb5933ff5fbce7eff4ca0353f4f Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Fri, 4 Jul 2025 08:29:48 +0200 Subject: [PATCH 5/5] Apply suggestions from code review Co-authored-by: Jan Kotas --- src/coreclr/vm/prestub.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/coreclr/vm/prestub.cpp b/src/coreclr/vm/prestub.cpp index 9b3ea8b7c4dfbd..3e6ccd8b94a92f 100644 --- a/src/coreclr/vm/prestub.cpp +++ b/src/coreclr/vm/prestub.cpp @@ -1469,6 +1469,8 @@ Stub * CreateUnboxingILStubForSharedGenericValueTypeMethods(MethodDesc* pTargetM MetaSig msig(pTargetMD); _ASSERTE(msig.HasThis()); + + // TODO: (async) instantiating/unboxing stubs https://github.com/dotnet/runtime/issues/117266 _ASSERTE(!msig.HasAsyncContinuation()); ILStubLinker sl(pTargetMD->GetModule(), @@ -1586,6 +1588,7 @@ Stub * CreateInstantiatingILStub(MethodDesc* pTargetMD, void* pHiddenArg) ILCodeStream *pCode = sl.NewCodeStream(ILStubLinker::kDispatch); + // TODO: (async) instantiating/unboxing stubs https://github.com/dotnet/runtime/issues/117266 _ASSERTE(!msig.HasAsyncContinuation()); // Build the new signature