From 4f6069334217c59a72f85d23153e2ece04c04318 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Sowi=C5=84ski?= Date: Wed, 9 Oct 2024 22:55:58 +0200 Subject: [PATCH] [RISC-V] Transfer arguments between calling conventions in shuffling thunks (#107282) * Log ShuffleEntries from GenerateShuffleArray * Add failing tests for passing FP structs through shuffling thunks * Report proper ShuffleEntries for lowered FP structs * Implement shuffling thunk generation in tighter, more focused loops instead of an omnibus loop handling ShuffleEntries * Generate ShuffleEntries for delowered arguments * Better ShuffleEntry mask names, one more bit for field offset * Fix FpStruct for dst arg loc * Fold ShuffleEntry generation code for lowering and delowering FpStructs * ShuffleEntry mask doc update * Implement forward shuffling of floating registers and delowering of FpStructs. EmptyStructs test passes except for ShufflingThunk_FloatEmptyShort_DoubleFloatNestedEmpty_RiscV * Fix shuffling of integer registers for member functions * The delowered argument can also be put in the first stack slot * Stask shuffling after delowered argument doesn't start with 0. Fixes Regressions/coreclr/GitHub_16833/Test16833 * Code cleanup, fewer indents * Support second lowering * Remove unused CondCode * Handle stack slot shuffling to the right * Add some stack slots to shuffle in the growing stack test case * Fix Equals signature on test structs * Remodel the shuffling with calling convention transfer to recognize the key points first, which simplifies code and solves some corner cases e.g. where we can't assume struct stack size by checking the size + offset of the last field * Use helper functions in EmitComputedInstantiatingMethodStub * Implement stack growing in shuffling thunk * Use signed immediate in EmitSubImm to be consistent with EmitAddImm * Use ABI register names in logs * Remove LoadRegPair because it's not used * Add logging for slli and lui * Remove stack growing from hand-emitted shuffle thunks * Minor FloatFloatEmpty test cleanup * Implement IL shuffling thunks for cases where the stack is growing * Test stack walking in frames laid by the IL shuffle thunk * Add assert and comment in CreateILDelegateShuffleThunk * Fix release build * Fixes for static delegates from member methods * Fix log and comment * Remove EmitJumpAndLinkRegister because it's no longer used * Use TransferredField.reg in delowering (cosmetic fix to restart CI) * New stub type for delegate shuffle thunk so it doesn't go in multidelegate code paths * Make Test_ShufflingThunk_MemberGrowsStack_RiscV harder by returning via buffer * Explain lowering * Fold 12-bit sign extension branch in EmitMovConstant * Harden Test_ShufflingThunk_PackedEmptyUintEmptyFloat_PackedEmptyDouble to cover interleaving FP and int arguments * Handle shuffles between calling conventions in IL stubs * Update comments * Don't use NewStub for IL stubs * Fold some more duplicated code into SetupShuffleThunk * Clean up unnecessary diffs * IL shuffle thunk takes target function address from delegate object. Cache the generated thunk on DelegateEEClass * Build target signature based on delegate signature instead of just using the signature from target method to retain type context * Test calling instance and static methods via the same delegate type * Simplify shuffle thunk caching on DelegateEEClass * Clean up CreateILDelegateShuffleThunk * Delete Windows X86 stack size check * Remove #ifdefs around ILSTUB_DELEGATE_SHUFFLE_THUNK, fix typo in GetStubMethodName * Fix DecRef'ing path when the IL thunk is already cached on DelegateEEClass * Fix shuffle thunk destruction in EEClass::Destruct: properly handle IL shuffle thunks and call RemoveStubRange if m_pInstRetBuffCallStub was deleted * Don't use RemoveStubRange in the destructor, make code for dereferencing shuffle thunk when caching fails the same as destructor * Remove unused RemoveStubRange * Cover IL shuffle thunks in ILStubManager::TraceManager * Remove unused start, end arguments from RangeList::RemoveRanges[Worker] * Update src/coreclr/vm/comdelegate.cpp --------- Co-authored-by: Jan Kotas --- src/coreclr/inc/sigparser.h | 2 +- src/coreclr/inc/utilcode.h | 12 +- src/coreclr/utilcode/loaderheap.cpp | 21 +- src/coreclr/vm/callingconvention.h | 4 +- src/coreclr/vm/class.cpp | 29 +- src/coreclr/vm/comdelegate.cpp | 235 +++++-- src/coreclr/vm/dllimport.h | 2 + src/coreclr/vm/ilstubcache.cpp | 8 +- src/coreclr/vm/loaderallocator.hpp | 8 +- src/coreclr/vm/lockedrangelist.h | 4 +- src/coreclr/vm/method.hpp | 7 + src/coreclr/vm/riscv64/cgencpu.h | 15 +- src/coreclr/vm/riscv64/stubs.cpp | 248 +++---- src/coreclr/vm/stubmgr.cpp | 14 +- src/coreclr/vm/stubmgr.h | 2 - .../JIT/Directed/StructABI/EmptyStructs.cs | 650 +++++++++++++++++- 16 files changed, 964 insertions(+), 297 deletions(-) diff --git a/src/coreclr/inc/sigparser.h b/src/coreclr/inc/sigparser.h index 6aad0669d94ed7..c5f539599175e6 100644 --- a/src/coreclr/inc/sigparser.h +++ b/src/coreclr/inc/sigparser.h @@ -116,7 +116,7 @@ class SigParser void GetSignature( PCCOR_SIGNATURE * pSig, - uint32_t * pcbSigSize) + uint32_t * pcbSigSize) const { *pSig = m_ptr; *pcbSigSize = m_dwLen; diff --git a/src/coreclr/inc/utilcode.h b/src/coreclr/inc/utilcode.h index 9eefb47875f5be..18adfa7650e3d6 100644 --- a/src/coreclr/inc/utilcode.h +++ b/src/coreclr/inc/utilcode.h @@ -3098,9 +3098,9 @@ class RangeList return this->AddRangeWorker(start, end, id); } - void RemoveRanges(void *id, const BYTE *start = NULL, const BYTE *end = NULL) + void RemoveRanges(void *id) { - return this->RemoveRangesWorker(id, start, end); + return this->RemoveRangesWorker(id); } BOOL IsInRange(TADDR address, TADDR *pID = NULL) @@ -3114,16 +3114,14 @@ class RangeList // You can overload these two for synchronization (as LockedRangeList does) virtual BOOL AddRangeWorker(const BYTE *start, const BYTE *end, void *id); - // If both "start" and "end" are NULL, then this method deletes all ranges with - // the given id (i.e. the original behaviour). Otherwise, it ignores the given - // id and deletes all ranges falling in the region [start, end). - virtual void RemoveRangesWorker(void *id, const BYTE *start = NULL, const BYTE *end = NULL); + // Deletes all ranges with the given id + virtual void RemoveRangesWorker(void *id); #else virtual BOOL AddRangeWorker(const BYTE *start, const BYTE *end, void *id) { return TRUE; } - virtual void RemoveRangesWorker(void *id, const BYTE *start = NULL, const BYTE *end = NULL) { } + virtual void RemoveRangesWorker(void *id) { } #endif // !DACCESS_COMPILE virtual BOOL IsInRangeWorker(TADDR address, TADDR *pID = NULL); diff --git a/src/coreclr/utilcode/loaderheap.cpp b/src/coreclr/utilcode/loaderheap.cpp index 9cbda2a5244567..50f51486d1cbb6 100644 --- a/src/coreclr/utilcode/loaderheap.cpp +++ b/src/coreclr/utilcode/loaderheap.cpp @@ -150,7 +150,7 @@ BOOL RangeList::AddRangeWorker(const BYTE *start, const BYTE *end, void *id) } } -void RangeList::RemoveRangesWorker(void *id, const BYTE* start, const BYTE* end) +void RangeList::RemoveRangesWorker(void *id) { CONTRACTL { @@ -177,24 +177,9 @@ void RangeList::RemoveRangesWorker(void *id, const BYTE* start, const BYTE* end) while (r < rEnd) { - if (r->id != (TADDR)NULL) + if (r->id == (TADDR)id) { - if (start != NULL) - { - _ASSERTE(end != NULL); - - if (r->start >= (TADDR)start && r->start < (TADDR)end) - { - CONSISTENCY_CHECK_MSGF(r->end >= (TADDR)start && - r->end <= (TADDR)end, - ("r: %p start: %p end: %p", r, start, end)); - r->id = (TADDR)NULL; - } - } - else if (r->id == (TADDR)id) - { - r->id = (TADDR)NULL; - } + r->id = (TADDR)NULL; } r++; diff --git a/src/coreclr/vm/callingconvention.h b/src/coreclr/vm/callingconvention.h index 1744870fe9f159..ed8f4ae98e1f94 100644 --- a/src/coreclr/vm/callingconvention.h +++ b/src/coreclr/vm/callingconvention.h @@ -642,7 +642,7 @@ class ArgIteratorTemplate : public ARGITERATOR_BASE //------------------------------------------------------------ int GetNextOffset(); - CorElementType GetArgType(TypeHandle *pTypeHandle = NULL) + CorElementType GetArgType(TypeHandle *pTypeHandle = NULL) const { LIMITED_METHOD_CONTRACT; if (pTypeHandle != NULL) @@ -652,7 +652,7 @@ class ArgIteratorTemplate : public ARGITERATOR_BASE return m_argType; } - int GetArgSize() + int GetArgSize() const { LIMITED_METHOD_CONTRACT; return m_argSize; diff --git a/src/coreclr/vm/class.cpp b/src/coreclr/vm/class.cpp index 58bd150972a759..6120ee32ea696e 100644 --- a/src/coreclr/vm/class.cpp +++ b/src/coreclr/vm/class.cpp @@ -124,25 +124,22 @@ void EEClass::Destruct(MethodTable * pOwningMT) if (IsDelegate()) { DelegateEEClass* pDelegateEEClass = (DelegateEEClass*)this; - - if (pDelegateEEClass->m_pStaticCallStub) + for (Stub* pThunk : {pDelegateEEClass->m_pStaticCallStub, pDelegateEEClass->m_pInstRetBuffCallStub}) { - // Collect data to remove stub entry from StubManager if - // stub is deleted. - BYTE* entry = (BYTE*)pDelegateEEClass->m_pStaticCallStub->GetEntryPoint(); - UINT length = pDelegateEEClass->m_pStaticCallStub->GetNumCodeBytes(); - - ExecutableWriterHolder stubWriterHolder(pDelegateEEClass->m_pStaticCallStub, sizeof(Stub)); - BOOL fStubDeleted = stubWriterHolder.GetRW()->DecRef(); - if (fStubDeleted) + if (pThunk == nullptr) + continue; + + _ASSERTE(pThunk->IsShuffleThunk()); + + if (pThunk->HasExternalEntryPoint()) // IL thunk { - StubLinkStubManager::g_pManager->RemoveStubRange(entry, length); + pThunk->DecRef(); + } + else + { + ExecutableWriterHolder stubWriterHolder(pThunk, sizeof(Stub)); + stubWriterHolder.GetRW()->DecRef(); } - } - if (pDelegateEEClass->m_pInstRetBuffCallStub) - { - ExecutableWriterHolder stubWriterHolder(pDelegateEEClass->m_pInstRetBuffCallStub, sizeof(Stub)); - stubWriterHolder.GetRW()->DecRef(); } } diff --git a/src/coreclr/vm/comdelegate.cpp b/src/coreclr/vm/comdelegate.cpp index 8f20a405f7a5f8..104a82d9bf5766 100644 --- a/src/coreclr/vm/comdelegate.cpp +++ b/src/coreclr/vm/comdelegate.cpp @@ -337,6 +337,18 @@ struct ShuffleGraphNode BOOL AddNextShuffleEntryToArray(ArgLocDesc sArgSrc, ArgLocDesc sArgDst, SArray * pShuffleEntryArray, ShuffleComputationType shuffleType) { +#if defined(TARGET_RISCV64) + if (sArgSrc.m_structFields.flags != sArgDst.m_structFields.flags) + { + _ASSERT(sArgSrc.m_structFields.flags == FpStruct::UseIntCallConv + || sArgDst.m_structFields.flags == FpStruct::UseIntCallConv); + // StubLinkerCPU::EmitShuffleThunk supports shuffles only within the integer calling convention (floating-point + // arguments may be passed in registers but these are not shuffled then). Transferring arguments between + // calling conventions is handled by IL stubs. + return FALSE; + } +#endif // TARGET_RISCV64 + ShuffleEntry entry; ZeroMemory(&entry, sizeof(entry)); @@ -408,25 +420,6 @@ BOOL GenerateShuffleArrayPortable(MethodDesc* pMethodSrc, MethodDesc *pMethodDst return FALSE; } - UINT stackSizeDelta = 0; - -#if defined(TARGET_X86) && !defined(UNIX_X86_ABI) - { - UINT stackSizeSrc = sArgPlacerSrc.SizeOfArgStack(); - UINT stackSizeDst = sArgPlacerDst.SizeOfArgStack(); - - // Windows X86 calling convention requires the stack to shrink when removing - // arguments, as it is callee pop - if (stackSizeDst > stackSizeSrc) - { - // we can drop arguments but we can never make them up - this is definitely not allowed - COMPlusThrow(kVerificationException); - } - - stackSizeDelta = stackSizeSrc - stackSizeDst; - } -#endif // Callee pop architectures - defined(TARGET_X86) && !defined(UNIX_X86_ABI) - INT ofsSrc; INT ofsDst; ArgLocDesc sArgSrc; @@ -623,20 +616,21 @@ BOOL GenerateShuffleArrayPortable(MethodDesc* pMethodSrc, MethodDesc *pMethodDst } entry.srcofs = ShuffleEntry::SENTINEL; - entry.dstofs = 0; + entry.stacksizedelta = 0; pShuffleEntryArray->Append(entry); return TRUE; } #endif // FEATURE_PORTABLE_SHUFFLE_THUNKS -VOID GenerateShuffleArray(MethodDesc* pInvoke, MethodDesc *pTargetMeth, SArray * pShuffleEntryArray) +BOOL GenerateShuffleArray(MethodDesc* pInvoke, MethodDesc *pTargetMeth, SArray * pShuffleEntryArray) { STANDARD_VM_CONTRACT; #ifdef FEATURE_PORTABLE_SHUFFLE_THUNKS // Portable default implementation - GenerateShuffleArrayPortable(pInvoke, pTargetMeth, pShuffleEntryArray, ShuffleComputationType::DelegateShuffleThunk); + if (!GenerateShuffleArrayPortable(pInvoke, pTargetMeth, pShuffleEntryArray, ShuffleComputationType::DelegateShuffleThunk)) + return FALSE; #elif defined(TARGET_X86) ShuffleEntry entry; ZeroMemory(&entry, sizeof(entry)); @@ -724,6 +718,48 @@ VOID GenerateShuffleArray(MethodDesc* pInvoke, MethodDesc *pTargetMeth, SArray %s.%s:\n", + pShuffleEntryArray->GetCount(), + pInvoke->GetMethodTable()->GetDebugClassName(), pInvoke->GetName(), + pTargetMeth->GetMethodTable()->GetDebugClassName(), pTargetMeth->GetName())); + + for (COUNT_T i = 0; i < pShuffleEntryArray->GetCount(); ++i) + { + ShuffleEntry entry = (*pShuffleEntryArray)[i]; + if (entry.srcofs == ShuffleEntry::SENTINEL) + { + LOGALWAYS((" [%u] sentinel, stack size delta %u\n", i, entry.stacksizedelta)); + _ASSERTE(i == pShuffleEntryArray->GetCount() - 1); + break; + } + + struct ShuffleInfo { const char* type; int offset; }; + auto getShuffleInfo = [](UINT16 offset) -> ShuffleInfo { + if (offset == ShuffleEntry::HELPERREG) + { + return { "helper register" }; + } + else if (offset & ShuffleEntry::REGMASK) + { + const char* type = (offset & ShuffleEntry::FPREGMASK) + ? ((offset & ShuffleEntry::FPSINGLEMASK) ? "single-FP register" : "FP register") + : "integer register"; + return { type, offset & ShuffleEntry::OFSREGMASK }; + } + else + { + return {"stack slot", offset & ShuffleEntry::OFSMASK }; + } + }; + ShuffleInfo src = getShuffleInfo(entry.srcofs); + ShuffleInfo dst = getShuffleInfo(entry.dstofs); + LOGALWAYS((" [%u] %s %i -> %s %i\n", i, src.type, src.offset, dst.type, dst.offset)); + } + } + return TRUE; } static ShuffleThunkCache* s_pShuffleThunkCache = NULL; @@ -789,8 +825,69 @@ LoaderHeap *DelegateEEClass::GetStubHeap() return GetInvokeMethod()->GetLoaderAllocator()->GetStubHeap(); } +#if defined(TARGET_RISCV64) +static Stub* CreateILDelegateShuffleThunk(MethodDesc* pDelegateMD, bool callTargetWithThis) +{ + SigTypeContext typeContext(pDelegateMD); + MetaSig sig(pDelegateMD); + if (LoggingOn(LF_STUBS, LL_INFO1000000)) + { + SString delegateName; + pDelegateMD->GetFullMethodInfo(delegateName); + LOGALWAYS(("CreateILDelegateShuffleThunk %s (%i args, callTargetWithThis:%i)\n", + delegateName.GetUTF8(), sig.NumFixedArgs(), callTargetWithThis)); + } + _ASSERTE(sig.HasThis()); + + Module* pModule = sig.GetModule(); + Signature signature = pDelegateMD->GetSignature(); + + ILStubLinkerFlags flags = ILSTUB_LINKER_FLAG_STUB_HAS_THIS; + ILStubLinker stubLinker(pModule, signature, &typeContext, pDelegateMD, flags); + ILCodeStream *pCode = stubLinker.NewCodeStream(ILStubLinker::kDispatch); -static Stub* SetupShuffleThunk(MethodTable* pDelMT, MethodDesc* pTargetMeth) + for (unsigned i = 0; i < sig.NumFixedArgs(); ++i) + pCode->EmitLDARG(i); + + pCode->EmitLoadThis(); + pCode->EmitLDFLD(pCode->GetToken(CoreLibBinder::GetField(FIELD__DELEGATE__METHOD_PTR_AUX))); + pCode->EmitCALLI(TOKEN_ILSTUB_TARGET_SIG, sig.NumFixedArgs(), sig.IsReturnTypeVoid() ? 0 : 1); + pCode->EmitRET(); + + MethodDesc* pStubMD = ILStubCache::CreateAndLinkNewILStubMethodDesc( + pDelegateMD->GetLoaderAllocator(), pDelegateMD->GetMethodTable(), ILSTUB_DELEGATE_SHUFFLE_THUNK, + pModule, signature.GetRawSig(), signature.GetRawSigLen(), &typeContext, &stubLinker); + + // Build target signature + SigBuilder sigBuilder(signature.GetRawSigLen()); + sigBuilder.AppendByte(callTargetWithThis + ? IMAGE_CEE_CS_CALLCONV_DEFAULT_HASTHIS + : IMAGE_CEE_CS_CALLCONV_DEFAULT); + + unsigned numFixedArgs = sig.NumFixedArgs() - callTargetWithThis; + sigBuilder.AppendData(numFixedArgs); + + SigPointer pReturn = sig.GetReturnProps(); + pReturn.ConvertToInternalExactlyOne(pModule, &typeContext, &sigBuilder); + + sig.SkipArg(); // skip delegate object + if (callTargetWithThis) + sig.SkipArg(); + + SigPointer pArgs = sig.GetArgProps(); + for (unsigned i = 0; i < numFixedArgs; ++i) + pArgs.ConvertToInternalExactlyOne(pModule, &typeContext, &sigBuilder); + + DWORD cbTargetSig; + PCCOR_SIGNATURE pTargetSig = (PCCOR_SIGNATURE)sigBuilder.GetSignature(&cbTargetSig); + ILStubResolver* pResolver = pStubMD->AsDynamicMethodDesc()->GetILStubResolver(); + pResolver->SetStubTargetMethodSig(pTargetSig, cbTargetSig); + + return Stub::NewStub(JitILStub(pStubMD), NEWSTUB_FL_SHUFFLE_THUNK); +} +#endif // TARGET_RISCV64 + +static PCODE SetupShuffleThunk(MethodTable * pDelMT, MethodDesc *pTargetMeth) { CONTRACTL { @@ -801,49 +898,66 @@ static Stub* SetupShuffleThunk(MethodTable* pDelMT, MethodDesc* pTargetMeth) } CONTRACTL_END; - GCX_PREEMP(); - + bool isInstRetBuff = (!pTargetMeth->IsStatic() && pTargetMeth->HasRetBuffArg() && IsRetBuffPassedAsFirstArg()); DelegateEEClass * pClass = (DelegateEEClass *)pDelMT->GetClass(); + // Look for a thunk cached on the delegate class first. Note we need a different thunk for instance methods with a + // hidden return buffer argument because the extra argument switches place with the target when coming from the caller. + Stub* pShuffleThunk = isInstRetBuff ? pClass->m_pInstRetBuffCallStub : pClass->m_pStaticCallStub; + if (pShuffleThunk) + return pShuffleThunk->GetEntryPoint(); + + GCX_PREEMP(); + MethodDesc *pMD = pClass->GetInvokeMethod(); + // We haven't already setup a shuffle thunk, go do it now (which will cache the result automatically). StackSArray rShuffleEntryArray; - GenerateShuffleArray(pMD, pTargetMeth, &rShuffleEntryArray); + if (GenerateShuffleArray(pMD, pTargetMeth, &rShuffleEntryArray)) + { + ShuffleThunkCache* pShuffleThunkCache = s_pShuffleThunkCache; - ShuffleThunkCache* pShuffleThunkCache = s_pShuffleThunkCache; + LoaderAllocator* pLoaderAllocator = pDelMT->GetLoaderAllocator(); + if (pLoaderAllocator->IsCollectible()) + { + pShuffleThunkCache = ((AssemblyLoaderAllocator*)pLoaderAllocator)->GetShuffleThunkCache(); + } - LoaderAllocator* pLoaderAllocator = pDelMT->GetLoaderAllocator(); - if (pLoaderAllocator->IsCollectible()) + pShuffleThunk = pShuffleThunkCache->Canonicalize((const BYTE *)&rShuffleEntryArray[0]); + } + else { - pShuffleThunkCache = ((AssemblyLoaderAllocator*)pLoaderAllocator)->GetShuffleThunkCache(); +#if defined(TARGET_RISCV64) + pShuffleThunk = CreateILDelegateShuffleThunk(pMD, isInstRetBuff); +#else + _ASSERTE(FALSE); + return (PCODE)NULL; +#endif // TARGET_RISCV64 } - Stub* pShuffleThunk = pShuffleThunkCache->Canonicalize((const BYTE *)&rShuffleEntryArray[0]); if (!pShuffleThunk) { COMPlusThrowOM(); } - if (!pTargetMeth->IsStatic() && pTargetMeth->HasRetBuffArg() && IsRetBuffPassedAsFirstArg()) + // Cache the shuffle thunk + Stub** ppThunk = isInstRetBuff ? &pClass->m_pInstRetBuffCallStub : &pClass->m_pStaticCallStub; + Stub* pExistingThunk = InterlockedCompareExchangeT(ppThunk, pShuffleThunk, NULL); + if (pExistingThunk != NULL) { - if (InterlockedCompareExchangeT(&pClass->m_pInstRetBuffCallStub, pShuffleThunk, NULL ) != NULL) + if (pShuffleThunk->HasExternalEntryPoint()) // IL thunk { - ExecutableWriterHolder shuffleThunkWriterHolder(pShuffleThunk, sizeof(Stub)); - shuffleThunkWriterHolder.GetRW()->DecRef(); - pShuffleThunk = pClass->m_pInstRetBuffCallStub; + pShuffleThunk->DecRef(); } - } - else - { - if (InterlockedCompareExchangeT(&pClass->m_pStaticCallStub, pShuffleThunk, NULL ) != NULL) + else { ExecutableWriterHolder shuffleThunkWriterHolder(pShuffleThunk, sizeof(Stub)); shuffleThunkWriterHolder.GetRW()->DecRef(); - pShuffleThunk = pClass->m_pStaticCallStub; } + pShuffleThunk = pExistingThunk; } - return pShuffleThunk; + return pShuffleThunk->GetEntryPoint(); } static PCODE GetVirtualCallStub(MethodDesc *method, TypeHandle scopeType) @@ -1101,22 +1215,10 @@ void COMDelegate::BindToMethod(DELEGATEREF *pRefThis, // We need to shuffle arguments for open delegates since the first argument on the calling side is not meaningful to the // callee. MethodTable * pDelegateMT = (*pRefThis)->GetMethodTable(); - DelegateEEClass *pDelegateClass = (DelegateEEClass*)pDelegateMT->GetClass(); - Stub *pShuffleThunk = NULL; - - // Look for a thunk cached on the delegate class first. Note we need a different thunk for instance methods with a - // hidden return buffer argument because the extra argument switches place with the target when coming from the caller. - if (!pTargetMethod->IsStatic() && pTargetMethod->HasRetBuffArg() && IsRetBuffPassedAsFirstArg()) - pShuffleThunk = pDelegateClass->m_pInstRetBuffCallStub; - else - pShuffleThunk = pDelegateClass->m_pStaticCallStub; - - // If we haven't already setup a shuffle thunk go do it now (which will cache the result automatically). - if (!pShuffleThunk) - pShuffleThunk = SetupShuffleThunk(pDelegateMT, pTargetMethod); + PCODE pEntryPoint = SetupShuffleThunk(pDelegateMT, pTargetMethod); // Indicate that the delegate will jump to the shuffle thunk rather than directly to the target method. - refRealDelegate->SetMethodPtr(pShuffleThunk->GetEntryPoint()); + refRealDelegate->SetMethodPtr(pEntryPoint); // Use stub dispatch for all virtuals. // Investigate not using this for non-interface virtuals. @@ -1612,7 +1714,6 @@ extern "C" void QCALLTYPE Delegate_Construct(QCall::ObjectHandleOnStack _this, Q if (Nullable::IsNullableType(pMeth->GetMethodTable())) COMPlusThrow(kNotSupportedException); - DelegateEEClass* pDelCls = (DelegateEEClass*)pDelMT->GetClass(); MethodDesc* pDelegateInvoke = COMDelegate::FindDelegateInvokeMethod(pDelMT); UINT invokeArgCount = MethodDescToNumFixedArgs(pDelegateInvoke); @@ -1634,14 +1735,8 @@ extern "C" void QCALLTYPE Delegate_Construct(QCall::ObjectHandleOnStack _this, Q refThis->SetTarget(refThis); // set the shuffle thunk - Stub *pShuffleThunk = (!pMeth->IsStatic() && pMeth->HasRetBuffArg() && IsRetBuffPassedAsFirstArg()) - ? pDelCls->m_pInstRetBuffCallStub - : pDelCls->m_pStaticCallStub; - - if (pShuffleThunk == NULL) - pShuffleThunk = SetupShuffleThunk(pDelMT, pMeth); - - refThis->SetMethodPtr(pShuffleThunk->GetEntryPoint()); + PCODE pEntryPoint = SetupShuffleThunk(pDelMT, pMeth); + refThis->SetMethodPtr(pEntryPoint); // set the ptr aux according to what is needed, if virtual need to call make virtual stub dispatch if (!pMeth->IsStatic() && pMeth->IsVirtual() && !pMeth->GetMethodTable()->IsValueType()) @@ -2705,7 +2800,6 @@ MethodDesc* COMDelegate::GetDelegateCtor(TypeHandle delegateType, MethodDesc *pT MethodDesc *pRealCtor = NULL; MethodTable *pDelMT = delegateType.AsMethodTable(); - DelegateEEClass *pDelCls = (DelegateEEClass*)(pDelMT->GetClass()); MethodDesc *pDelegateInvoke = COMDelegate::FindDelegateInvokeMethod(pDelMT); @@ -2847,15 +2941,8 @@ MethodDesc* COMDelegate::GetDelegateCtor(TypeHandle delegateType, MethodDesc *pT else pRealCtor = CoreLibBinder::GetMethod(METHOD__MULTICAST_DELEGATE__CTOR_OPENED); } - Stub *pShuffleThunk = NULL; - if (!pTargetMethod->IsStatic() && pTargetMethod->HasRetBuffArg() && IsRetBuffPassedAsFirstArg()) - pShuffleThunk = pDelCls->m_pInstRetBuffCallStub; - else - pShuffleThunk = pDelCls->m_pStaticCallStub; - if (!pShuffleThunk) - pShuffleThunk = SetupShuffleThunk(pDelMT, pTargetMethod); - pCtorData->pArg3 = (void*)pShuffleThunk->GetEntryPoint(); + pCtorData->pArg3 = (void*)SetupShuffleThunk(pDelMT, pTargetMethod); if (isCollectible) { pCtorData->pArg4 = pTargetMethodLoaderAllocator->GetLoaderAllocatorObjectHandle(); diff --git a/src/coreclr/vm/dllimport.h b/src/coreclr/vm/dllimport.h index b61f8a7f35c3ac..915c298bc5ef54 100644 --- a/src/coreclr/vm/dllimport.h +++ b/src/coreclr/vm/dllimport.h @@ -203,6 +203,7 @@ enum ILStubTypes ILSTUB_TAILCALL_CALLTARGET = 0x80000009, ILSTUB_STATIC_VIRTUAL_DISPATCH_STUB = 0x8000000A, ILSTUB_DELEGATE_INVOKE_METHOD = 0x8000000B, + ILSTUB_DELEGATE_SHUFFLE_THUNK = 0x8000000C, }; #ifdef FEATURE_COMINTEROP @@ -240,6 +241,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_IsDelegateShuffleThunk (DWORD dwStubFlags) { LIMITED_METHOD_CONTRACT; return (dwStubFlags == ILSTUB_DELEGATE_SHUFFLE_THUNK); } 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)); } diff --git a/src/coreclr/vm/ilstubcache.cpp b/src/coreclr/vm/ilstubcache.cpp index 89cac9112463a0..655a6b2d169f3f 100644 --- a/src/coreclr/vm/ilstubcache.cpp +++ b/src/coreclr/vm/ilstubcache.cpp @@ -156,7 +156,8 @@ namespace case DynamicMethodDesc::StubWrapperDelegate: return "IL_STUB_WrapperDelegate_Invoke"; case DynamicMethodDesc::StubTailCallStoreArgs: return "IL_STUB_StoreTailCallArgs"; case DynamicMethodDesc::StubTailCallCallTarget: return "IL_STUB_CallTailCallTarget"; - case DynamicMethodDesc::StubVirtualStaticMethodDispatch: return "IL_STUB_bVirtualStaticMethodDispatch"; + case DynamicMethodDesc::StubVirtualStaticMethodDispatch: return "IL_STUB_VirtualStaticMethodDispatch"; + case DynamicMethodDesc::StubDelegateShuffleThunk: return "IL_STUB_DelegateShuffleThunk"; default: UNREACHABLE_MSG("Unknown stub type"); } @@ -303,6 +304,11 @@ MethodDesc* ILStubCache::CreateNewMethodDesc(LoaderHeap* pCreationHeap, MethodTa pMD->SetILStubType(DynamicMethodDesc::StubVirtualStaticMethodDispatch); } else + if (SF_IsDelegateShuffleThunk(dwStubFlags)) + { + pMD->SetILStubType(DynamicMethodDesc::StubDelegateShuffleThunk); + } + else { // mark certain types of stub MDs with random flags so ILStubManager recognizes them if (SF_IsReverseStub(dwStubFlags)) diff --git a/src/coreclr/vm/loaderallocator.hpp b/src/coreclr/vm/loaderallocator.hpp index 2bce73a85cc241..f2a200ef761730 100644 --- a/src/coreclr/vm/loaderallocator.hpp +++ b/src/coreclr/vm/loaderallocator.hpp @@ -67,7 +67,7 @@ class CodeRangeMapRangeList : public RangeList ~CodeRangeMapRangeList() { LIMITED_METHOD_CONTRACT; - RemoveRangesWorker(_id, NULL, NULL); + RemoveRangesWorker(_id); } StubCodeBlockKind GetCodeBlockKind() @@ -132,7 +132,7 @@ class CodeRangeMapRangeList : public RangeList #endif // DACCESS_COMPILE } - virtual void RemoveRangesWorker(void *id, const BYTE *start, const BYTE *end) + virtual void RemoveRangesWorker(void *id) { CONTRACTL { @@ -142,10 +142,6 @@ class CodeRangeMapRangeList : public RangeList CONTRACTL_END; #ifndef DACCESS_COMPILE - // This implementation only works for the case where the RangeList is used in a single LoaderHeap - _ASSERTE(start == NULL); - _ASSERTE(end == NULL); - SimpleWriteLockHolder lh(&_RangeListRWLock); _ASSERTE(id == _id || (_id == NULL && _starts.IsEmpty())); diff --git a/src/coreclr/vm/lockedrangelist.h b/src/coreclr/vm/lockedrangelist.h index 8f3b7acfc30205..44e5bb63c444a3 100644 --- a/src/coreclr/vm/lockedrangelist.h +++ b/src/coreclr/vm/lockedrangelist.h @@ -40,11 +40,11 @@ class LockedRangeList : public RangeList return RangeList::AddRangeWorker(start,end,id); } - virtual void RemoveRangesWorker(void *id, const BYTE *start = NULL, const BYTE *end = NULL) + virtual void RemoveRangesWorker(void *id) { WRAPPER_NO_CONTRACT; SimpleWriteLockHolder lh(&m_RangeListRWLock); - RangeList::RemoveRangesWorker(id,start,end); + RangeList::RemoveRangesWorker(id); } virtual BOOL IsInRangeWorker(TADDR address, TADDR *pID = NULL) diff --git a/src/coreclr/vm/method.hpp b/src/coreclr/vm/method.hpp index 956765b76ff050..a3f4d4feeae788 100644 --- a/src/coreclr/vm/method.hpp +++ b/src/coreclr/vm/method.hpp @@ -2512,6 +2512,7 @@ class DynamicMethodDesc : public StoredSigMethodDesc StubTailCallCallTarget, StubVirtualStaticMethodDispatch, + StubDelegateShuffleThunk, StubDelegateInvokeMethod, @@ -2682,6 +2683,12 @@ class DynamicMethodDesc : public StoredSigMethodDesc return GetILStubType() == DynamicMethodDesc::StubUnboxingIL; } #endif + bool IsDelegateShuffleThunk() const + { + LIMITED_METHOD_DAC_CONTRACT; + _ASSERTE(IsILStub()); + return GetILStubType() == DynamicMethodDesc::StubDelegateShuffleThunk; + } // Whether the stub takes a context argument that is an interop MethodDesc. bool HasMDContextArg() const diff --git a/src/coreclr/vm/riscv64/cgencpu.h b/src/coreclr/vm/riscv64/cgencpu.h index 4a2ab29fc95b11..c08f6a144f5562 100644 --- a/src/coreclr/vm/riscv64/cgencpu.h +++ b/src/coreclr/vm/riscv64/cgencpu.h @@ -360,15 +360,6 @@ struct FloatReg WORD Mask() const { return 1 << reg; } }; -struct CondCode -{ - int cond; - CondCode(int cond):cond(cond) - { - _ASSERTE(0 <= cond && cond < 16); - } -}; - const IntReg RegSp = IntReg(2); const IntReg RegFp = IntReg(8); const IntReg RegRa = IntReg(1); @@ -400,15 +391,15 @@ class StubLinkerCPU : public StubLinker void EmitMovConstant(IntReg target, UINT64 constant); void EmitJumpRegister(IntReg regTarget); void EmitMovReg(IntReg dest, IntReg source); - void EmitMovReg(FloatReg dest, FloatReg source); - void EmitSubImm(IntReg Xd, IntReg Xn, unsigned int value); - void EmitAddImm(IntReg Xd, IntReg Xn, unsigned int value); + void EmitSubImm(IntReg Xd, IntReg Xn, int value); + void EmitAddImm(IntReg Xd, IntReg Xn, int value); void EmitSllImm(IntReg Xd, IntReg Xn, unsigned int value); void EmitLuImm(IntReg Xd, unsigned int value); void EmitLoad(IntReg dest, IntReg srcAddr, int offset = 0); void EmitLoad(FloatReg dest, IntReg srcAddr, int offset = 0); + void EmitStore(IntReg src, IntReg destAddr, int offset = 0); void EmitStore(FloatReg src, IntReg destAddr, int offset = 0); diff --git a/src/coreclr/vm/riscv64/stubs.cpp b/src/coreclr/vm/riscv64/stubs.cpp index 1273bb6e739e7f..2d947b231eb9ca 100644 --- a/src/coreclr/vm/riscv64/stubs.cpp +++ b/src/coreclr/vm/riscv64/stubs.cpp @@ -1066,19 +1066,12 @@ void StubLinkerCPU::EmitMovConstant(IntReg reg, UINT64 imm) // Since ADDIW use sign extension for immediate // we have to adjust higher 19 bit loaded by LUI - // for case when low part is bigger than 0x800. + // for case when the low 12-bit part is negative. UINT32 high19 = (high31 + 0x800) >> 12; EmitLuImm(reg, high19); - if (high31 & 0x800) - { - // EmitAddImm does not allow negative immediate values, so use EmitSubImm. - EmitSubImm(reg, reg, (~high31 + 1) & 0xFFF); - } - else - { - EmitAddImm(reg, reg, high31 & 0x7FF); - } + int low12 = int(high31) << (32-12) >> (32-12); + EmitAddImm(reg, reg, low12); // And load remaining part by batches of 11 bits size. INT32 remainingShift = msb - 30; @@ -1110,10 +1103,6 @@ void StubLinkerCPU::EmitMovConstant(IntReg reg, UINT64 imm) } } -void StubLinkerCPU::EmitJumpRegister(IntReg regTarget) -{ - Emit32(0x00000067 | (regTarget << 15)); -} void StubLinkerCPU::EmitProlog(unsigned short cIntRegArgs, unsigned short cFpRegArgs, unsigned short cbStackSpace) { @@ -1186,8 +1175,9 @@ void StubLinkerCPU::EmitProlog(unsigned short cIntRegArgs, unsigned short cFpReg EmitStore(RegFp, RegSp, cbStackSpace); EmitStore(RegRa, RegSp, cbStackSpace + sizeof(void*)); - // 3. Set the frame pointer - EmitMovReg(RegFp, RegSp); + // 3. Set the frame pointer to the Canonical Frame Address or CFA, which is the stack pointer value on entry to the + // current procedure + EmitAddImm(RegFp, RegSp, totalPaddedFrameSize); // 4. Store floating point argument registers _ASSERTE(cFpRegArgs <= 8); @@ -1282,51 +1272,78 @@ static unsigned BTypeInstr(unsigned opcode, unsigned funct3, unsigned rs1, unsig return opcode | (immLo4 << 8) | (funct3 << 12) | (rs1 << 15) | (rs2 << 20) | (immHi6 << 25) | (immLo1 << 7) | (immHi1 << 31); } +static const char* intRegAbiNames[] = { + "zero", "ra", "sp", "gp", "tp", + "t0", "t1", "t2", + "fp", "s1", + "a0", "a1", "a2", "a3", "a4", "a5", "a6", "a7", + "s2", "s3", "s4", "s5", "s6", "s7", "s8", "s9", "s10", "s11", + "t3", "t4", "t5", "t6" +}; + +static const char* fpRegAbiNames[] = { + "ft0", "ft1", "ft2", "ft3", "ft4", "ft5", "ft6", "ft7", + "fs0", "fs1", + "fa0", "fa1", "fa2", "fa3", "fa4", "fa5", "fa6", "fa7", + "fs2", "fs3", "fs4", "fs5", "fs6", "fs7", "fs8", "fs9", "fs10", "fs11", + "ft8", "ft9", "ft10", "ft11" +}; + +void StubLinkerCPU::EmitJumpRegister(IntReg regTarget) +{ + Emit32(0x00000067 | (regTarget << 15)); + LOG((LF_STUBS, LL_EVERYTHING, "jalr zero, 0(%s)\n", intRegAbiNames[regTarget])); +} + void StubLinkerCPU::EmitLoad(IntReg dest, IntReg srcAddr, int offset) { Emit32(ITypeInstr(0x3, 0x3, dest, srcAddr, offset)); // ld + LOG((LF_STUBS, LL_EVERYTHING, "ld %s, %i(%s)\n", intRegAbiNames[dest], offset, intRegAbiNames[srcAddr])); } void StubLinkerCPU::EmitLoad(FloatReg dest, IntReg srcAddr, int offset) { Emit32(ITypeInstr(0x7, 0x3, dest, srcAddr, offset)); // fld + LOG((LF_STUBS, LL_EVERYTHING, "fld %s, %i(%s)\n", fpRegAbiNames[dest], offset, intRegAbiNames[srcAddr])); } void StubLinkerCPU:: EmitStore(IntReg src, IntReg destAddr, int offset) { Emit32(STypeInstr(0x23, 0x3, destAddr, src, offset)); // sd + LOG((LF_STUBS, LL_EVERYTHING, "sd %s, %i(%s)\n", intRegAbiNames[src], offset, intRegAbiNames[destAddr])); } void StubLinkerCPU::EmitStore(FloatReg src, IntReg destAddr, int offset) { Emit32(STypeInstr(0x27, 0x3, destAddr, src, offset)); // fsd + LOG((LF_STUBS, LL_EVERYTHING, "fsd %s, %i(%s)\n", fpRegAbiNames[src], offset, intRegAbiNames[destAddr])); } void StubLinkerCPU::EmitMovReg(IntReg Xd, IntReg Xm) { EmitAddImm(Xd, Xm, 0); } -void StubLinkerCPU::EmitMovReg(FloatReg dest, FloatReg source) +void StubLinkerCPU::EmitSubImm(IntReg Xd, IntReg Xn, int value) { - Emit32(RTypeInstr(0x53, 0, 0x11, dest, source, source)); // fsgnj.d + EmitAddImm(Xd, Xn, -value); } - -void StubLinkerCPU::EmitSubImm(IntReg Xd, IntReg Xn, unsigned int value) -{ - _ASSERTE(value <= 0x800); - EmitAddImm(Xd, Xn, ~value + 0x1); -} -void StubLinkerCPU::EmitAddImm(IntReg Xd, IntReg Xn, unsigned int value) +void StubLinkerCPU::EmitAddImm(IntReg Xd, IntReg Xn, int value) { Emit32(ITypeInstr(0x13, 0, Xd, Xn, value)); // addi + if (value) + LOG((LF_STUBS, LL_EVERYTHING, "addi %s, %s, %i\n", intRegAbiNames[Xd], intRegAbiNames[Xn], value)); + else + LOG((LF_STUBS, LL_EVERYTHING, "mv %s, %s\n", intRegAbiNames[Xd], intRegAbiNames[Xn])); } void StubLinkerCPU::EmitSllImm(IntReg Xd, IntReg Xn, unsigned int value) { _ASSERTE(!(value >> 6)); Emit32(ITypeInstr(0x13, 0x1, Xd, Xn, value)); // slli + LOG((LF_STUBS, LL_EVERYTHING, "slli %s, %s, %u\n", intRegAbiNames[Xd], intRegAbiNames[Xn], value)); } void StubLinkerCPU::EmitLuImm(IntReg Xd, unsigned int value) { _ASSERTE(value <= 0xFFFFF); Emit32((DWORD)(0x00000037 | (value << 12) | (Xd << 7))); // lui Xd, value + LOG((LF_STUBS, LL_EVERYTHING, "lui %s, %u\n", intRegAbiNames[Xd], value)); } void StubLinkerCPU::Init() @@ -1334,10 +1351,35 @@ void StubLinkerCPU::Init() new (gBranchIF) BranchInstructionFormat(); } +static bool InRegister(UINT16 ofs) +{ + _ASSERTE(ofs != ShuffleEntry::SENTINEL); + return (ofs & ShuffleEntry::REGMASK); +} +static bool IsRegisterFloating(UINT16 ofs) +{ + _ASSERTE(InRegister(ofs)); + return (ofs & ShuffleEntry::FPREGMASK); +} + +static const int argRegBase = 10; // first argument register: a0, fa0 +static const IntReg lastIntArgReg = argRegBase + NUM_ARGUMENT_REGISTERS - 1; // a7 +static const IntReg intTempReg = 29; // t4 + +static int GetRegister(UINT16 ofs) +{ + _ASSERTE(InRegister(ofs)); + return (ofs & ShuffleEntry::OFSREGMASK) + argRegBase; +} +static unsigned GetStackSlot(UINT16 ofs) +{ + _ASSERTE(!InRegister(ofs)); + return ofs; +} + // Emits code to adjust arguments for static delegate target. VOID StubLinkerCPU::EmitShuffleThunk(ShuffleEntry *pShuffleEntryArray) { - static const int argRegBase = 10; // first argument register: a0, fa0 static const IntReg t6 = 31, t5 = 30, a0 = argRegBase + 0; // On entry a0 holds the delegate instance. Look up the real target address stored in the MethodPtrAux // field and saved in t6. Tailcall to the target method after re-arranging the arguments @@ -1345,113 +1387,38 @@ VOID StubLinkerCPU::EmitShuffleThunk(ShuffleEntry *pShuffleEntryArray) // load the indirection cell into t5 used by ResolveWorkerAsmStub EmitAddImm(t5, a0, DelegateObject::GetOffsetOfMethodPtrAux()); - int delay_index[8] = {-1}; - bool is_store = false; - UINT16 index = 0; - int i = 0; - for (ShuffleEntry* pEntry = pShuffleEntryArray; pEntry->srcofs != ShuffleEntry::SENTINEL; pEntry++, i++) + const ShuffleEntry* entry = pShuffleEntryArray; + // Shuffle integer argument registers + for (; entry->srcofs != ShuffleEntry::SENTINEL && InRegister(entry->dstofs) && InRegister(entry->srcofs); ++entry) { - if (pEntry->srcofs & ShuffleEntry::REGMASK) - { - // Source in register, destination in register - - // Both the srcofs and dstofs must be of the same kind of registers - float or general purpose. - // If source is present in register then destination may be a stack-slot. - _ASSERTE(((pEntry->dstofs & ShuffleEntry::FPREGMASK) == (pEntry->srcofs & ShuffleEntry::FPREGMASK)) || !(pEntry->dstofs & (ShuffleEntry::FPREGMASK | ShuffleEntry::REGMASK))); - _ASSERTE((pEntry->dstofs & ShuffleEntry::OFSREGMASK) <= 8);//should amend for offset! - _ASSERTE((pEntry->srcofs & ShuffleEntry::OFSREGMASK) <= 8); - - if (pEntry->srcofs & ShuffleEntry::FPREGMASK) - { - int j = 1; - while (pEntry[j].srcofs & ShuffleEntry::FPREGMASK) - { - j++; - } - assert((pEntry->dstofs - pEntry->srcofs) == index); - assert(8 > index); - - int tmp_reg = 0; // ft0. - ShuffleEntry* tmp_entry = pShuffleEntryArray + delay_index[0]; - while (index) - { - EmitLoad(FloatReg(tmp_reg), RegSp, tmp_entry->srcofs * sizeof(void*)); - tmp_reg++; - index--; - tmp_entry++; - } - - j -= 1; - tmp_entry = pEntry + j; - i += j; - while (pEntry[j].srcofs & ShuffleEntry::FPREGMASK) - { - FloatReg src = (pEntry[j].srcofs & ShuffleEntry::OFSREGMASK) + argRegBase; - if (pEntry[j].dstofs & ShuffleEntry::FPREGMASK) { - FloatReg dst = (pEntry[j].dstofs & ShuffleEntry::OFSREGMASK) + argRegBase; - EmitMovReg(dst, src); - } - else - { - EmitStore(src, RegSp, pEntry[j].dstofs * sizeof(void*)); - } - j--; - } - - assert(tmp_reg <= 7); - while (tmp_reg > 0) - { - tmp_reg--; - EmitMovReg(FloatReg(index + argRegBase), FloatReg(tmp_reg)); - index++; - } - index = 0; - pEntry = tmp_entry; - } - else - { - assert(pEntry->dstofs & ShuffleEntry::REGMASK); - IntReg dst = (pEntry->dstofs & ShuffleEntry::OFSREGMASK) + argRegBase; - IntReg src = (pEntry->srcofs & ShuffleEntry::OFSREGMASK) + argRegBase; - assert(dst < src); - EmitMovReg(dst, src); - } - } - else if (pEntry->dstofs & ShuffleEntry::REGMASK) - { - // source must be on the stack - _ASSERTE(!(pEntry->srcofs & ShuffleEntry::REGMASK)); + _ASSERTE(!IsRegisterFloating(entry->srcofs)); + _ASSERTE(!IsRegisterFloating(entry->dstofs)); + IntReg src = GetRegister(entry->srcofs); + IntReg dst = GetRegister(entry->dstofs); + _ASSERTE((src - dst) == 1 || (src - dst) == 2); + EmitMovReg(dst, src); + } - int dstReg = (pEntry->dstofs & ShuffleEntry::OFSREGMASK) + argRegBase; - int srcOfs = (pEntry->srcofs & ShuffleEntry::OFSMASK) * sizeof(void*); - if (pEntry->dstofs & ShuffleEntry::FPREGMASK) - { - if (!is_store) - { - delay_index[index++] = i; - continue; - } - EmitLoad(FloatReg(dstReg), RegSp, srcOfs); - } - else - { - EmitLoad(IntReg(dstReg), RegSp, srcOfs); - } - } - else + if (entry->srcofs != ShuffleEntry::SENTINEL) + { + _ASSERTE(!IsRegisterFloating(entry->dstofs)); + _ASSERTE(GetStackSlot(entry->srcofs) == 0); + _ASSERTE(lastIntArgReg == GetRegister(entry->dstofs)); + EmitLoad(lastIntArgReg, RegSp, 0); + ++entry; + + // All further shuffling is (stack) <- (stack+1) + for (unsigned dst = 0; entry->srcofs != ShuffleEntry::SENTINEL; ++entry, ++dst) { - // source & dest must be on the stack - _ASSERTE(!(pEntry->srcofs & ShuffleEntry::REGMASK)); - _ASSERTE(!(pEntry->dstofs & ShuffleEntry::REGMASK)); - - IntReg t4 = 29; - EmitLoad(t4, RegSp, pEntry->srcofs * sizeof(void*)); - EmitStore(t4, RegSp, pEntry->dstofs * sizeof(void*)); + unsigned src = dst + 1; + _ASSERTE(src == GetStackSlot(entry->srcofs)); + _ASSERTE(dst == GetStackSlot(entry->dstofs)); + EmitLoad (intTempReg, RegSp, src * sizeof(void*)); + EmitStore(intTempReg, RegSp, dst * sizeof(void*)); } } - // Tailcall to target - // jalr x0, 0(t6) - EmitJumpRegister(t6); + + EmitJumpRegister(t6); // Tailcall to target } // Emits code to adjust arguments for static delegate target. @@ -1461,26 +1428,24 @@ VOID StubLinkerCPU::EmitComputedInstantiatingMethodStub(MethodDesc* pSharedMD, s for (ShuffleEntry* pEntry = pShuffleEntryArray; pEntry->srcofs != ShuffleEntry::SENTINEL; pEntry++) { - _ASSERTE(pEntry->dstofs & ShuffleEntry::REGMASK); - _ASSERTE(pEntry->srcofs & ShuffleEntry::REGMASK); - _ASSERTE(!(pEntry->dstofs & ShuffleEntry::FPREGMASK)); - _ASSERTE(!(pEntry->srcofs & ShuffleEntry::FPREGMASK)); + _ASSERTE(!IsRegisterFloating(pEntry->srcofs)); + _ASSERTE(!IsRegisterFloating(pEntry->dstofs)); _ASSERTE(pEntry->dstofs != ShuffleEntry::HELPERREG); _ASSERTE(pEntry->srcofs != ShuffleEntry::HELPERREG); - - EmitMovReg(IntReg((pEntry->dstofs & ShuffleEntry::OFSREGMASK) + 10), IntReg((pEntry->srcofs & ShuffleEntry::OFSREGMASK) + 10)); + EmitMovReg(IntReg(GetRegister(pEntry->dstofs)), IntReg(GetRegister(pEntry->srcofs))); } MetaSig msig(pSharedMD); ArgIterator argit(&msig); + static const IntReg a0 = argRegBase + 0; if (argit.HasParamType()) { ArgLocDesc sInstArgLoc; argit.GetParamTypeLoc(&sInstArgLoc); int regHidden = sInstArgLoc.m_idxGenReg; _ASSERTE(regHidden != -1); - regHidden += 10;//NOTE: RISCV64 should start at a0=10; + regHidden += argRegBase;//NOTE: RISCV64 should start at a0=10; if (extraArg == NULL) { @@ -1488,8 +1453,7 @@ VOID StubLinkerCPU::EmitComputedInstantiatingMethodStub(MethodDesc* pSharedMD, s { // Unboxing stub case // Fill param arg with methodtable of this pointer - // ld regHidden, a0, 0 - EmitLoad(IntReg(regHidden), IntReg(10)); + EmitLoad(IntReg(regHidden), a0); } } else @@ -1502,8 +1466,7 @@ VOID StubLinkerCPU::EmitComputedInstantiatingMethodStub(MethodDesc* pSharedMD, s { // Unboxing stub case // Address of the value type is address of the boxed instance plus sizeof(MethodDesc*). - // addi a0, a0, sizeof(MethodDesc*) - EmitAddImm(IntReg(10), IntReg(10), sizeof(MethodDesc*)); + EmitAddImm(a0, a0, sizeof(MethodDesc*)); } // Tail call the real target. @@ -1563,13 +1526,6 @@ void StubLinkerCPU::EmitCallManagedMethod(MethodDesc *pMD, BOOL fTailCall) while (p < pStart + cbAligned) { *(DWORD*)p = 0xffffff0f/*badcode*/; p += 4; }\ ClrFlushInstructionCache(pStart, cbAligned); \ return (PCODE)pStart -// Uses x8 as scratch register to store address of data label -// After load x8 is increment to point to next data -// only accepts positive offsets -static void LoadRegPair(BYTE* p, int reg1, int reg2, UINT32 offset) -{ - _ASSERTE(!"RISCV64: not implementation on riscv64!!!"); -} PCODE DynamicHelpers::CreateHelper(LoaderAllocator * pAllocator, TADDR arg, PCODE target) { diff --git a/src/coreclr/vm/stubmgr.cpp b/src/coreclr/vm/stubmgr.cpp index 31a01fc83986f5..225310c646cd15 100644 --- a/src/coreclr/vm/stubmgr.cpp +++ b/src/coreclr/vm/stubmgr.cpp @@ -1237,15 +1237,6 @@ BOOL StubLinkStubManager::TraceDelegateObject(BYTE* pbDel, TraceDestination *tra #endif // DACCESS_COMPILE -void StubLinkStubManager::RemoveStubRange(BYTE* start, UINT length) -{ - WRAPPER_NO_CONTRACT; - _ASSERTE(start != NULL && length > 0); - - BYTE* end = start + length; - GetRangeList()->RemoveRanges((LPVOID)start, start, end); -} - BOOL StubLinkStubManager::CheckIsStub_Internal(PCODE stubStartAddress) { WRAPPER_NO_CONTRACT; @@ -1879,6 +1870,11 @@ BOOL ILStubManager::TraceManager(Thread *thread, LOG((LF_CORDB, LL_INFO1000, "ILSM::TraceManager: Delegate Invoke Method\n")); return StubLinkStubManager::TraceDelegateObject((BYTE*)pThis, trace); } + else if (pStubMD->IsDelegateShuffleThunk()) + { + LOG((LF_CORDB, LL_INFO1000, "ILSM::TraceManager: Delegate Shuffle Thunk\n")); + return TraceShuffleThunk(trace, pContext, pRetAddr); + } else { LOG((LF_CORDB, LL_INFO1000, "ILSM::TraceManager: No known target, IL Stub is a leaf\n")); diff --git a/src/coreclr/vm/stubmgr.h b/src/coreclr/vm/stubmgr.h index 6d3174366a729b..9c8deefcc37358 100644 --- a/src/coreclr/vm/stubmgr.h +++ b/src/coreclr/vm/stubmgr.h @@ -475,8 +475,6 @@ class StubLinkStubManager : public StubManager return PTR_RangeList(addr); } - void RemoveStubRange(BYTE* start, UINT length); - virtual BOOL CheckIsStub_Internal(PCODE stubStartAddress); virtual BOOL DoTraceStub(PCODE stubStartAddress, TraceDestination *trace); diff --git a/src/tests/JIT/Directed/StructABI/EmptyStructs.cs b/src/tests/JIT/Directed/StructABI/EmptyStructs.cs index 423015b831b7a8..6be88b68dea71e 100644 --- a/src/tests/JIT/Directed/StructABI/EmptyStructs.cs +++ b/src/tests/JIT/Directed/StructABI/EmptyStructs.cs @@ -1437,7 +1437,6 @@ public static void Test_PackedEmptyFloatLong_OnStack_ByReflection_RiscV() } #endregion - #region PackedFloatEmptyByte_RiscVTests [StructLayout(LayoutKind.Sequential, Pack=1)] public struct PackedFloatEmptyByte @@ -1582,4 +1581,653 @@ public static void Test_PackedFloatEmptyByte_OnStack_ByReflection_RiscV() Assert.Equal(expected, managed); } #endregion + +#region ShufflingThunks_RiscVTests + [MethodImpl(MethodImplOptions.NoInlining)] + private static void ShufflingThunk_EmptyFloatEmpty5Byte_RiscV( + int a1_to_a0, int a2_to_a1, int a3_to_a2, int a4_to_a3, int a5_to_a4, int a6_to_a5, int a7_to_a6, + float fa0, float fa1, float fa2, float fa3, float fa4, float fa5, float fa6, + EmptyFloatEmpty5Byte stack0_stack1_to_fa7_a7, + int stack2_to_stack0, float fa7_to_stack1) + { + Assert.Equal(0, a1_to_a0); + Assert.Equal(1, a2_to_a1); + Assert.Equal(2, a3_to_a2); + Assert.Equal(3, a4_to_a3); + Assert.Equal(4, a5_to_a4); + Assert.Equal(5, a6_to_a5); + Assert.Equal(6, a7_to_a6); + Assert.Equal(0f, fa0); + Assert.Equal(1f, fa1); + Assert.Equal(2f, fa2); + Assert.Equal(3f, fa3); + Assert.Equal(4f, fa4); + Assert.Equal(5f, fa5); + Assert.Equal(6f, fa6); + Assert.Equal(EmptyFloatEmpty5Byte.Get(), stack0_stack1_to_fa7_a7); + Assert.Equal(7, stack2_to_stack0); + Assert.Equal(7f, fa7_to_stack1); + } + + [Fact] + public static void Test_ShufflingThunk_EmptyFloatEmpty5Byte_RiscV() + { + var getDelegate = [MethodImpl(MethodImplOptions.NoOptimization)] () + => ShufflingThunk_EmptyFloatEmpty5Byte_RiscV; + getDelegate()(0, 1, 2, 3, 4, 5, 6, 0f, 1f, 2f, 3f, 4f, 5f, 6f, + EmptyFloatEmpty5Byte.Get(), 7, 7f); + } + + + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void ShufflingThunk_EmptyFloatEmpty5Sbyte_Empty8Float_RiscV( + int a1_to_a0, int a2_to_a1, int a3_to_a2, int a4_to_a3, int a5_to_a4, int a6_to_a5, int a7_to_a6, + float fa0, float fa1, float fa2, float fa3, float fa4, float fa5, float fa6, + EmptyFloatEmpty5Sbyte stack0_stack1_to_fa7_a7, + int stack2_to_stack0, + Empty8Float fa7_to_stack1_stack2) + { + Assert.Equal(0, a1_to_a0); + Assert.Equal(1, a2_to_a1); + Assert.Equal(2, a3_to_a2); + Assert.Equal(3, a4_to_a3); + Assert.Equal(4, a5_to_a4); + Assert.Equal(5, a6_to_a5); + Assert.Equal(6, a7_to_a6); + Assert.Equal(0f, fa0); + Assert.Equal(1f, fa1); + Assert.Equal(2f, fa2); + Assert.Equal(3f, fa3); + Assert.Equal(4f, fa4); + Assert.Equal(5f, fa5); + Assert.Equal(6f, fa6); + Assert.Equal(EmptyFloatEmpty5Sbyte.Get(), stack0_stack1_to_fa7_a7); + Assert.Equal(7, stack2_to_stack0); + Assert.Equal(Empty8Float.Get(), fa7_to_stack1_stack2); + } + + [Fact] + public static void Test_ShufflingThunk_EmptyFloatEmpty5Sbyte_Empty8Float_RiscV() + { + var getDelegate = [MethodImpl(MethodImplOptions.NoOptimization)] () + => ShufflingThunk_EmptyFloatEmpty5Sbyte_Empty8Float_RiscV; + getDelegate()(0, 1, 2, 3, 4, 5, 6, 0f, 1f, 2f, 3f, 4f, 5f, 6f, + EmptyFloatEmpty5Sbyte.Get(), 7, Empty8Float.Get()); + } + + + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void ShufflingThunk_EmptyUshortAndDouble_FloatEmpty8Float_Empty8Float_RiscV( + int a1_to_a0, int a2_to_a1, int a3_to_a2, int a4_to_a3, int a5_to_a4, int a6_to_a5, int a7_to_a6, + float fa0, float fa1, float fa2, float fa3, float fa4, float fa5, + EmptyUshortAndDouble stack0_stack1_to_a7_fa6, // 1st lowering + FloatEmpty8Float fa6_fa7_to_stack0_stack1, // delowering + Empty8Float stack1_stack2_to_fa7, // 2nd lowering + int stack3_to_stack2) + { + Assert.Equal(0, a1_to_a0); + Assert.Equal(1, a2_to_a1); + Assert.Equal(2, a3_to_a2); + Assert.Equal(3, a4_to_a3); + Assert.Equal(4, a5_to_a4); + Assert.Equal(5, a6_to_a5); + Assert.Equal(6, a7_to_a6); + Assert.Equal(0f, fa0); + Assert.Equal(1f, fa1); + Assert.Equal(2f, fa2); + Assert.Equal(3f, fa3); + Assert.Equal(4f, fa4); + Assert.Equal(5f, fa5); + Assert.Equal(EmptyUshortAndDouble.Get(), stack0_stack1_to_a7_fa6); + Assert.Equal(FloatEmpty8Float.Get(), fa6_fa7_to_stack0_stack1); + Assert.Equal(Empty8Float.Get(), stack1_stack2_to_fa7); + Assert.Equal(7, stack3_to_stack2); + } + + [Fact] + public static void Test_ShufflingThunk_EmptyUshortAndDouble_FloatEmpty8Float_Empty8Float_RiscV() + { + var getDelegate = [MethodImpl(MethodImplOptions.NoOptimization)] () + => ShufflingThunk_EmptyUshortAndDouble_FloatEmpty8Float_Empty8Float_RiscV; + getDelegate()(0, 1, 2, 3, 4, 5, 6, 0f, 1f, 2f, 3f, 4f, 5f, + EmptyUshortAndDouble.Get(), FloatEmpty8Float.Get(), Empty8Float.Get(), 7); + } + + + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void ShufflingThunk_FloatEmptyShort_DoubleFloatNestedEmpty_Float_RiscV( + int a1_to_a0, int a2_to_a1, int a3_to_a2, int a4_to_a3, int a5_to_a4, int a6_to_a5, int a7_to_a6, + float fa0, float fa1, float fa2, float fa3, float fa4, float fa5, + FloatEmptyShort stack0_to_fa6_a7, // frees 1 stack slot + int stack1_to_stack0, + DoubleFloatNestedEmpty fa6_fa7_to_stack1_stack2, // takes 2 stack slots + int stack2_to_stack3, // shuffle stack slots to the right + int stack3_to_stack4, + Empty8Float stack4_stack5_to_fa7, // frees 2 stack slots + int stack6_to_stack5, // shuffle stack slots to the left + int stack7_to_stack6) + { + Assert.Equal(0, a1_to_a0); + Assert.Equal(1, a2_to_a1); + Assert.Equal(2, a3_to_a2); + Assert.Equal(3, a4_to_a3); + Assert.Equal(4, a5_to_a4); + Assert.Equal(5, a6_to_a5); + Assert.Equal(6, a7_to_a6); + Assert.Equal(0f, fa0); + Assert.Equal(1f, fa1); + Assert.Equal(2f, fa2); + Assert.Equal(3f, fa3); + Assert.Equal(4f, fa4); + Assert.Equal(5f, fa5); + Assert.Equal(FloatEmptyShort.Get(), stack0_to_fa6_a7); + Assert.Equal(7, stack1_to_stack0); + Assert.Equal(DoubleFloatNestedEmpty.Get(), fa6_fa7_to_stack1_stack2); + Assert.Equal(8, stack2_to_stack3); + Assert.Equal(9, stack3_to_stack4); + Assert.Equal(Empty8Float.Get(), stack4_stack5_to_fa7); + Assert.Equal(10, stack6_to_stack5); + Assert.Equal(11, stack7_to_stack6); + } + + [Fact] + public static void Test_ShufflingThunk_FloatEmptyShort_DoubleFloatNestedEmpty_Float_RiscV() + { + var getDelegate = [MethodImpl(MethodImplOptions.NoOptimization)] () + => ShufflingThunk_FloatEmptyShort_DoubleFloatNestedEmpty_Float_RiscV; + getDelegate()(0, 1, 2, 3, 4, 5, 6, 0f, 1f, 2f, 3f, 4f, 5f, + FloatEmptyShort.Get(), 7, DoubleFloatNestedEmpty.Get(), 8, 9, Empty8Float.Get(), 10, 11); + } + + + + public struct FloatFloat + { + public float Float0; + public float Float1; + + public static FloatFloat Get() + => new FloatFloat { Float0 = 2.71828f, Float1 = 1.61803f }; + + public override bool Equals(object other) + => other is FloatFloat o && Float0 == o.Float0 && Float1 == o.Float1; + + public override string ToString() + => $"{{Float0:{Float0}, Float1:{Float1}}}"; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void ShufflingThunk_PackedEmptyFloatLong_FloatFloat_RiscV( + int a1_to_a0, int a2_to_a1, int a3_to_a2, int a4_to_a3, int a5_to_a4, int a6_to_a5, int a7_to_a6, + float fa0, float fa1, float fa2, float fa3, float fa4, float fa5, + PackedEmptyFloatLong stack0_stack1_to_fa7_a7, + int stack2_to_stack0, + FloatFloat fa6_fa7_to_stack1) + { + Assert.Equal(0, a1_to_a0); + Assert.Equal(1, a2_to_a1); + Assert.Equal(2, a3_to_a2); + Assert.Equal(3, a4_to_a3); + Assert.Equal(4, a5_to_a4); + Assert.Equal(5, a6_to_a5); + Assert.Equal(6, a7_to_a6); + Assert.Equal(0f, fa0); + Assert.Equal(1f, fa1); + Assert.Equal(2f, fa2); + Assert.Equal(3f, fa3); + Assert.Equal(4f, fa4); + Assert.Equal(5f, fa5); + Assert.Equal(PackedEmptyFloatLong.Get(), stack0_stack1_to_fa7_a7); + Assert.Equal(7, stack2_to_stack0); + Assert.Equal(FloatFloat.Get(), fa6_fa7_to_stack1); + } + + [Fact] + public static void Test_ShufflingThunk_PackedEmptyFloatLong_FloatFloat_RiscV() + { + var getDelegate = [MethodImpl(MethodImplOptions.NoOptimization)] () + => ShufflingThunk_PackedEmptyFloatLong_FloatFloat_RiscV; + getDelegate()(0, 1, 2, 3, 4, 5, 6, 0f, 1f, 2f, 3f, 4f, 5f, + PackedEmptyFloatLong.Get(), 7, FloatFloat.Get()); + } + + + + [StructLayout(LayoutKind.Sequential, Pack=1)] + public struct PackedEmptyUintEmptyFloat + { + public Empty Empty0; + public uint Uint0; + public Empty Empty1; + public float Float0; + + public static PackedEmptyUintEmptyFloat Get() + => new PackedEmptyUintEmptyFloat { Uint0 = 0xB1ed0c1e, Float0 = 2.71828f }; + + public override bool Equals(object other) + => other is PackedEmptyUintEmptyFloat o && Uint0 == o.Uint0 && Float0 == o.Float0; + + public override string ToString() + => $"{{Uint0:{Uint0}, Float0:{Float0}}}"; + } + + [StructLayout(LayoutKind.Sequential, Pack=1)] + public struct PackedEmptyDouble + { + public Empty Empty0; + public double Double0; + + public static PackedEmptyDouble Get() + => new PackedEmptyDouble { Double0 = 1.61803 }; + + public override bool Equals(object other) + => other is PackedEmptyDouble o && Double0 == o.Double0; + + public override string ToString() + => $"{{Double0:{Double0}}}"; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void ShufflingThunk_PackedEmptyUintEmptyFloat_PackedEmptyDouble( + int a1_to_a0, int a2_to_a1, int a3_to_a2, int a4_to_a3, int a5_to_a4, int a6_to_a5, int a7_to_a6, + float fa0, float fa1, + PackedEmptyUintEmptyFloat stack0_stack1_to_a7_fa2, + float fa2_to_fa3, float fa3_to_fa4, float fa4_to_fa5, + int stack2_to_stack0, + float fa5_to_fa6, float fa6_to_fa7, + PackedEmptyDouble fa7_to_stack1_stack2) + { + Assert.Equal(0, a1_to_a0); + Assert.Equal(1, a2_to_a1); + Assert.Equal(2, a3_to_a2); + Assert.Equal(3, a4_to_a3); + Assert.Equal(4, a5_to_a4); + Assert.Equal(5, a6_to_a5); + Assert.Equal(6, a7_to_a6); + Assert.Equal(0f, fa0); + Assert.Equal(1f, fa1); + Assert.Equal(PackedEmptyUintEmptyFloat.Get(), stack0_stack1_to_a7_fa2); + Assert.Equal(2f, fa2_to_fa3); + Assert.Equal(3f, fa3_to_fa4); + Assert.Equal(4f, fa4_to_fa5); + Assert.Equal(7, stack2_to_stack0); + Assert.Equal(5f, fa5_to_fa6); + Assert.Equal(6f, fa6_to_fa7); + Assert.Equal(PackedEmptyDouble.Get(), fa7_to_stack1_stack2); + } + + [Fact] + public static void Test_ShufflingThunk_PackedEmptyUintEmptyFloat_PackedEmptyDouble() + { + var getDelegate = [MethodImpl(MethodImplOptions.NoOptimization)] () + => ShufflingThunk_PackedEmptyUintEmptyFloat_PackedEmptyDouble; + getDelegate()(0, 1, 2, 3, 4, 5, 6, 0f, 1f, + PackedEmptyUintEmptyFloat.Get(), 2f, 3f, 4f, 7, 5f, 6f, PackedEmptyDouble.Get()); + } + + + + public struct FloatFloatEmpty + { + public FloatFloat FloatFloat0; + public Empty Empty0; + + public static FloatFloatEmpty Get() + => new FloatFloatEmpty { FloatFloat0 = FloatFloat.Get() }; + + public override bool Equals(object other) + => other is FloatFloatEmpty o && FloatFloat0.Equals(o.FloatFloat0); + + public override string ToString() + => $"{{FloatFloat0:{FloatFloat0}}}"; + } + + public struct FloatEmpty8 + { + public float Float0; + public Eight EightEmpty0; + + public static FloatEmpty8 Get() + => new FloatEmpty8 { Float0 = 2.71828f }; + + public override bool Equals(object other) + => other is FloatEmpty8 o && Float0 == o.Float0; + + public override string ToString() + => $"{{Float0:{Float0}}}"; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void ShufflingThunk_FloatEmptyShort_FloatFloatEmpty_FloatEmpty8( + int a1_to_a0, int a2_to_a1, int a3_to_a2, int a4_to_a3, int a5_to_a4, int a6_to_a5, int a7_to_a6, + float fa0, float fa1, float fa2, float fa3, float fa4, float fa5, + FloatEmptyShort stack0_to_fa6_a7, // frees 1 stack slot + int stack1_to_stack0, + FloatFloatEmpty fa6_fa7_to_stack1_stack2, // takes 2 stack slots + int stack2_to_stack3, // shuffle stack slots to the right + int stack3_to_stack4, + FloatEmpty8 stack4_stack5_to_fa7, // frees 2 stack slots + int stack6_to_stack5, // shuffle stack slots to the left + int stack7_to_stack6) + { + Assert.Equal(0, a1_to_a0); + Assert.Equal(1, a2_to_a1); + Assert.Equal(2, a3_to_a2); + Assert.Equal(3, a4_to_a3); + Assert.Equal(4, a5_to_a4); + Assert.Equal(5, a6_to_a5); + Assert.Equal(6, a7_to_a6); + Assert.Equal(0f, fa0); + Assert.Equal(1f, fa1); + Assert.Equal(2f, fa2); + Assert.Equal(3f, fa3); + Assert.Equal(4f, fa4); + Assert.Equal(5f, fa5); + Assert.Equal(FloatEmptyShort.Get(), stack0_to_fa6_a7); + Assert.Equal(7, stack1_to_stack0); + Assert.Equal(FloatFloatEmpty.Get(), fa6_fa7_to_stack1_stack2); + Assert.Equal(8, stack2_to_stack3); + Assert.Equal(9, stack3_to_stack4); + Assert.Equal(FloatEmpty8.Get(), stack4_stack5_to_fa7); + Assert.Equal(10, stack6_to_stack5); + Assert.Equal(11, stack7_to_stack6); + } + + [Fact] + public static void Test_ShufflingThunk_FloatEmptyShort_FloatFloatEmpty_FloatEmpty8() + { + var getDelegate = [MethodImpl(MethodImplOptions.NoOptimization)] () + => ShufflingThunk_FloatEmptyShort_FloatFloatEmpty_FloatEmpty8; + getDelegate()(0, 1, 2, 3, 4, 5, 6, 0f, 1f, 2f, 3f, 4f, 5f, + FloatEmptyShort.Get(), 7, FloatFloatEmpty.Get(), 8, 9, FloatEmpty8.Get(), 10, 11); + } + + + + [MethodImpl(MethodImplOptions.NoInlining)] + private static DoubleFloatNestedEmpty ShufflingThunk_FloatEmptyShort_DoubleFloatNestedEmpty_RiscV( + int a1_to_a0, int a2_to_a1, int a3_to_a2, int a4_to_a3, int a5_to_a4, int a6_to_a5, int a7_to_a6, + float fa0, float fa1, float fa2, float fa3, float fa4, float fa5, + FloatEmptyShort stack0_to_fa6_a7, // frees 1 stack slot + int stack1_to_stack0, + DoubleFloatNestedEmpty fa6_fa7_to_stack1_stack2, // takes 2 stack slots + int stack2_to_stack3, // shuffle stack slots to the right + int stack3_to_stack4) // shuffling thunk must grow the stack + { + Assert.Equal(0, a1_to_a0); + Assert.Equal(1, a2_to_a1); + Assert.Equal(2, a3_to_a2); + Assert.Equal(3, a4_to_a3); + Assert.Equal(4, a5_to_a4); + Assert.Equal(5, a6_to_a5); + Assert.Equal(6, a7_to_a6); + Assert.Equal(0f, fa0); + Assert.Equal(1f, fa1); + Assert.Equal(2f, fa2); + Assert.Equal(3f, fa3); + Assert.Equal(4f, fa4); + Assert.Equal(5f, fa5); + Assert.Equal(FloatEmptyShort.Get(), stack0_to_fa6_a7); + Assert.Equal(7, stack1_to_stack0); + Assert.Equal(DoubleFloatNestedEmpty.Get(), fa6_fa7_to_stack1_stack2); + Assert.Equal(8, stack2_to_stack3); + Assert.Equal(9, stack3_to_stack4); + return fa6_fa7_to_stack1_stack2; + } + + [Fact] + public static void Test_ShufflingThunk_FloatEmptyShort_DoubleFloatNestedEmpty_RiscV() + { + var getDelegate = [MethodImpl(MethodImplOptions.NoOptimization)] () + => ShufflingThunk_FloatEmptyShort_DoubleFloatNestedEmpty_RiscV; + var delegat = getDelegate(); + Span stackBeforeCall = stackalloc[] {11, 22, 33, 44}; + DoubleFloatNestedEmpty ret = delegat(0, 1, 2, 3, 4, 5, 6, 0f, 1f, 2f, 3f, 4f, 5f, + FloatEmptyShort.Get(), 7, DoubleFloatNestedEmpty.Get(), 8, 9); + Assert.Equal([11, 22, 33, 44], stackBeforeCall); + Assert.Equal(DoubleFloatNestedEmpty.Get(), ret); + } + + + class EverythingIsFineException : Exception {} + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void ShufflingThunk_FloatEmptyShort_Empty8Float_RiscV( + int a1_to_a0, int a2_to_a1, int a3_to_a2, int a4_to_a3, int a5_to_a4, int a6_to_a5, int a7_to_a6, + float fa0, + FloatEmptyShort stack0_to_fa1_a7, // frees 1 stack slot + double fa1_to_fa2, + double fa2_to_fa3, + byte stack1_to_stack0, + short stack2_to_stack1, + double fa3_to_fa4, + float fa4_to_fa5, + int stack3_to_stack2, + float fa5_to_fa6, + double fa6_to_fa7, + long stack4_to_stack3, + Empty8Float fa7_to_stack4_stack5) // takes 2 stack slots, shuffling thunk must grow the stack + { + Assert.Equal(0, a1_to_a0); + Assert.Equal(1, a2_to_a1); + Assert.Equal(2, a3_to_a2); + Assert.Equal(3, a4_to_a3); + Assert.Equal(4, a5_to_a4); + Assert.Equal(5, a6_to_a5); + Assert.Equal(6, a7_to_a6); + Assert.Equal(0f, fa0); + Assert.Equal(FloatEmptyShort.Get(), stack0_to_fa1_a7); + Assert.Equal(1d, fa1_to_fa2); + Assert.Equal(2d, fa2_to_fa3); + Assert.Equal(7, stack1_to_stack0); + Assert.Equal(8, stack2_to_stack1); + Assert.Equal(3d, fa3_to_fa4); + Assert.Equal(4f, fa4_to_fa5); + Assert.Equal(9, stack3_to_stack2); + Assert.Equal(5f, fa5_to_fa6); + Assert.Equal(6d, fa6_to_fa7); + Assert.Equal(10, stack4_to_stack3); + Assert.Equal(Empty8Float.Get(), fa7_to_stack4_stack5); + throw new EverythingIsFineException(); // see if we can walk out of the stack frame laid by the shuffle thunk + } + + [Fact] + public static void Test_ShufflingThunk_FloatEmptyShort_Empty8Float_RiscV() + { + var getDelegate = [MethodImpl(MethodImplOptions.NoOptimization)] () + => ShufflingThunk_FloatEmptyShort_Empty8Float_RiscV; + var delegat = getDelegate(); + Assert.Throws(() => + delegat(0, 1, 2, 3, 4, 5, 6, 0f, + FloatEmptyShort.Get(), 1d, 2d, 7, 8, 3d, 4f, 9, 5f, 6d, 10, Empty8Float.Get()) + ); + } + + + + public struct UintFloat + { + public uint Uint0; + public float Float0; + + public static UintFloat Get() + => new UintFloat { Uint0 = 0xB1ed0c1e, Float0 = 2.71828f }; + + public override bool Equals(object other) + => other is UintFloat o && Uint0 == o.Uint0 && Float0 == o.Float0; + + public override string ToString() + => $"{{Uint0:{Uint0}, Float0:{Float0}}}"; + } + + public struct LongDoubleInt + { + public long Long0; + public double Double0; + public int Int0; + + public static LongDoubleInt Get() + => new LongDoubleInt { Long0 = 0xDadAddedC0ffee, Double0 = 3.14159, Int0 = 0xBabc1a }; + + public override bool Equals(object other) + => other is LongDoubleInt o && Long0 == o.Long0 && Double0 == o.Double0 && Int0 == o.Int0; + + public override string ToString() + => $"{{Long:{Long0}, Double0:{Double0}, Int0:{Int0}}}"; + } + + class ShufflingThunk_MemberGrowsStack_RiscV + { + public static ShufflingThunk_MemberGrowsStack_RiscV TestInstance = + new ShufflingThunk_MemberGrowsStack_RiscV(); + + public delegate FloatEmpty8Float TestDelegate( + ShufflingThunk_MemberGrowsStack_RiscV _this, + int a2_to_a1, int a3_to_a2, int a4_to_a3, int a5_to_a4, int a6_to_a5, int a7_to_a6, + float fa0, float fa1, float fa2, float fa3, float fa4, float fa5, + UintFloat stack0_to_a7_fa6, // frees 1 stack slot + DoubleFloatNestedEmpty fa6_fa7_to_stack0_stack1); // takes 2 stack slots, shuffling thunk must grow the stack + + [MethodImpl(MethodImplOptions.NoInlining)] + public FloatEmpty8Float TestMethod( + int a2_to_a1, int a3_to_a2, int a4_to_a3, int a5_to_a4, int a6_to_a5, int a7_to_a6, + float fa0, float fa1, float fa2, float fa3, float fa4, float fa5, + UintFloat stack0_to_a7_fa6, // frees 1 stack slot + DoubleFloatNestedEmpty fa6_fa7_to_stack0_stack1) // takes 2 stack slots, shuffling thunk must grow the stack + { + return StaticTestMethod(this, + a2_to_a1, a3_to_a2, a4_to_a3, a5_to_a4, a6_to_a5, a7_to_a6, + fa0, fa1, fa2, fa3, fa4, fa5, + stack0_to_a7_fa6, + fa6_fa7_to_stack0_stack1); + } + + public static FloatEmpty8Float StaticTestMethod( + ShufflingThunk_MemberGrowsStack_RiscV _this, + int a2_to_a1, int a3_to_a2, int a4_to_a3, int a5_to_a4, int a6_to_a5, int a7_to_a6, + float fa0, float fa1, float fa2, float fa3, float fa4, float fa5, + UintFloat stack0_to_a7_fa6, // frees 1 stack slot + DoubleFloatNestedEmpty fa6_fa7_to_stack0_stack1) // takes 2 stack slots, shuffling thunk must grow the stack + { + Assert.Equal(TestInstance, _this); + Assert.Equal(1, a2_to_a1); + Assert.Equal(2, a3_to_a2); + Assert.Equal(3, a4_to_a3); + Assert.Equal(4, a5_to_a4); + Assert.Equal(5, a6_to_a5); + Assert.Equal(6, a7_to_a6); + Assert.Equal(0f, fa0); + Assert.Equal(1f, fa1); + Assert.Equal(2f, fa2); + Assert.Equal(3f, fa3); + Assert.Equal(4f, fa4); + Assert.Equal(5f, fa5); + Assert.Equal(UintFloat.Get(), stack0_to_a7_fa6); + Assert.Equal(DoubleFloatNestedEmpty.Get(), fa6_fa7_to_stack0_stack1); + return FloatEmpty8Float.Get(); + } + } + + [Fact] + public static void Test_ShufflingThunk_MemberGrowsStack_RiscV() + { + var delegat = (ShufflingThunk_MemberGrowsStack_RiscV.TestDelegate)Delegate.CreateDelegate( + typeof(ShufflingThunk_MemberGrowsStack_RiscV.TestDelegate), null, + typeof(ShufflingThunk_MemberGrowsStack_RiscV).GetMethod( + nameof(ShufflingThunk_MemberGrowsStack_RiscV.TestMethod)) + ); + FloatEmpty8Float ret = delegat(ShufflingThunk_MemberGrowsStack_RiscV.TestInstance, + 1, 2, 3, 4, 5, 6, 0f, 1f, 2f, 3f, 4f, 5f, UintFloat.Get(), DoubleFloatNestedEmpty.Get()); + Assert.Equal(FloatEmpty8Float.Get(), ret); + + var getStaticMethod = [MethodImpl(MethodImplOptions.NoOptimization)] () + => (ShufflingThunk_MemberGrowsStack_RiscV.TestDelegate) + ShufflingThunk_MemberGrowsStack_RiscV.StaticTestMethod; + delegat = getStaticMethod(); + ret = delegat(ShufflingThunk_MemberGrowsStack_RiscV.TestInstance, + 1, 2, 3, 4, 5, 6, 0f, 1f, 2f, 3f, 4f, 5f, UintFloat.Get(), DoubleFloatNestedEmpty.Get()); + Assert.Equal(FloatEmpty8Float.Get(), ret); + } + + + + class ShufflingThunk_MemberGrowsStack_ReturnBuffer_RiscV + { + public static ShufflingThunk_MemberGrowsStack_ReturnBuffer_RiscV TestInstance = + new ShufflingThunk_MemberGrowsStack_ReturnBuffer_RiscV(); + + public delegate LongDoubleInt TestDelegate( + ShufflingThunk_MemberGrowsStack_ReturnBuffer_RiscV _this, + // ReturnBuffer* a2_to_a0 + int a3_to_a2, int a4_to_a3, int a5_to_a4, int a6_to_a5, int a7_to_a6, + float fa0, float fa1, float fa2, float fa3, float fa4, float fa5, + UintFloat stack0_to_a7_fa6, // frees 1 stack slot + DoubleFloatNestedEmpty fa6_fa7_to_stack0_stack1); // takes 2 stack slots, shuffling thunk must grow the stack + + [MethodImpl(MethodImplOptions.NoInlining)] + public LongDoubleInt TestMethod( + // ReturnBuffer* a2_to_a0 + int a3_to_a2, int a4_to_a3, int a5_to_a4, int a6_to_a5, int a7_to_a6, + float fa0, float fa1, float fa2, float fa3, float fa4, float fa5, + UintFloat stack0_to_a7_fa6, // frees 1 stack slot + DoubleFloatNestedEmpty fa6_fa7_to_stack0_stack1) // takes 2 stack slots, shuffling thunk must grow the stack + { + return StaticTestMethod(this, + a3_to_a2, a4_to_a3, a5_to_a4, a6_to_a5, a7_to_a6, + fa0, fa1, fa2, fa3, fa4, fa5, + stack0_to_a7_fa6, + fa6_fa7_to_stack0_stack1); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static LongDoubleInt StaticTestMethod( + ShufflingThunk_MemberGrowsStack_ReturnBuffer_RiscV _this, + // ReturnBuffer* a2_to_a0 + int a3_to_a2, int a4_to_a3, int a5_to_a4, int a6_to_a5, int a7_to_a6, + float fa0, float fa1, float fa2, float fa3, float fa4, float fa5, + UintFloat stack0_to_a7_fa6, // frees 1 stack slot + DoubleFloatNestedEmpty fa6_fa7_to_stack0_stack1) // takes 2 stack slots, shuffling thunk must grow the stack + { + Assert.Equal(TestInstance, _this); + Assert.Equal(2, a3_to_a2); + Assert.Equal(3, a4_to_a3); + Assert.Equal(4, a5_to_a4); + Assert.Equal(5, a6_to_a5); + Assert.Equal(6, a7_to_a6); + Assert.Equal(0f, fa0); + Assert.Equal(1f, fa1); + Assert.Equal(2f, fa2); + Assert.Equal(3f, fa3); + Assert.Equal(4f, fa4); + Assert.Equal(5f, fa5); + Assert.Equal(UintFloat.Get(), stack0_to_a7_fa6); + Assert.Equal(DoubleFloatNestedEmpty.Get(), fa6_fa7_to_stack0_stack1); + return LongDoubleInt.Get(); // via return buffer + } + } + + [Fact] + public static void Test_ShufflingThunk_MemberGrowsStack_ReturnBuffer_RiscV() + { + var delegat = (ShufflingThunk_MemberGrowsStack_ReturnBuffer_RiscV.TestDelegate)Delegate.CreateDelegate( + typeof(ShufflingThunk_MemberGrowsStack_ReturnBuffer_RiscV.TestDelegate), null, + typeof(ShufflingThunk_MemberGrowsStack_ReturnBuffer_RiscV).GetMethod( + nameof(ShufflingThunk_MemberGrowsStack_ReturnBuffer_RiscV.TestMethod)) + ); + LongDoubleInt ret = delegat(ShufflingThunk_MemberGrowsStack_ReturnBuffer_RiscV.TestInstance, + 2, 3, 4, 5, 6, 0f, 1f, 2f, 3f, 4f, 5f, UintFloat.Get(), DoubleFloatNestedEmpty.Get()); + Assert.Equal(LongDoubleInt.Get(), ret); + + var getStaticMethod = [MethodImpl(MethodImplOptions.NoOptimization)] () + => (ShufflingThunk_MemberGrowsStack_ReturnBuffer_RiscV.TestDelegate) + ShufflingThunk_MemberGrowsStack_ReturnBuffer_RiscV.StaticTestMethod; + delegat = getStaticMethod(); + ret = delegat(ShufflingThunk_MemberGrowsStack_ReturnBuffer_RiscV.TestInstance, + 2, 3, 4, 5, 6, 0f, 1f, 2f, 3f, 4f, 5f, UintFloat.Get(), DoubleFloatNestedEmpty.Get()); + Assert.Equal(LongDoubleInt.Get(), ret); + } +#endregion } \ No newline at end of file