diff --git a/src/coreclr/inc/clrnt.h b/src/coreclr/inc/clrnt.h index 0e1480c3cebc5..0c87336d1336a 100644 --- a/src/coreclr/inc/clrnt.h +++ b/src/coreclr/inc/clrnt.h @@ -1069,14 +1069,6 @@ RtlVirtualUnwind( IN OUT PKNONVOLATILE_CONTEXT_POINTERS ContextPointers OPTIONAL ); -#ifndef IMAGE_REL_LOONGARCH64_PC -#define IMAGE_REL_LOONGARCH64_PC 0x0003 -#endif - -#ifndef IMAGE_REL_LOONGARCH64_JIR -#define IMAGE_REL_LOONGARCH64_JIR 0x0004 -#endif - #endif // TARGET_LOONGARCH64 #endif // CLRNT_H_ diff --git a/src/coreclr/inc/eetwain.h b/src/coreclr/inc/eetwain.h index f2c51b5f724b6..8653d5eed14ca 100644 --- a/src/coreclr/inc/eetwain.h +++ b/src/coreclr/inc/eetwain.h @@ -211,9 +211,9 @@ virtual bool UnwindStackFrame(PREGDISPLAY pContext, virtual bool IsGcSafe(EECodeInfo *pCodeInfo, DWORD dwRelOffset) = 0; -#if defined(TARGET_ARM) || defined(TARGET_ARM64) +#if defined(TARGET_ARM) || defined(TARGET_ARM64) || defined(TARGET_LOONGARCH64) virtual bool HasTailCalls(EECodeInfo *pCodeInfo) = 0; -#endif // TARGET_ARM || TARGET_ARM64 +#endif // TARGET_ARM || TARGET_ARM64 || TARGET_LOONGARCH64 #if defined(TARGET_AMD64) && defined(_DEBUG) /* @@ -455,10 +455,10 @@ virtual bool IsGcSafe( EECodeInfo *pCodeInfo, DWORD dwRelOffset); -#if defined(TARGET_ARM) || defined(TARGET_ARM64) +#if defined(TARGET_ARM) || defined(TARGET_ARM64) || defined(TARGET_LOONGARCH64) virtual bool HasTailCalls(EECodeInfo *pCodeInfo); -#endif // TARGET_ARM || TARGET_ARM64 +#endif // TARGET_ARM || TARGET_ARM64 || TARGET_LOONGARCH64 #if defined(TARGET_AMD64) && defined(_DEBUG) /* diff --git a/src/coreclr/inc/jithelpers.h b/src/coreclr/inc/jithelpers.h index e2c4e0e8fcbf0..2ba380c7f7b14 100644 --- a/src/coreclr/inc/jithelpers.h +++ b/src/coreclr/inc/jithelpers.h @@ -320,7 +320,7 @@ JITHELPER(CORINFO_HELP_GVMLOOKUP_FOR_SLOT, NULL, CORINFO_HELP_SIG_NO_ALIGN_STUB) -#ifndef TARGET_ARM64 +#if !defined(TARGET_ARM64) && !defined(TARGET_LOONGARCH64) JITHELPER(CORINFO_HELP_STACK_PROBE, JIT_StackProbe, CORINFO_HELP_SIG_REG_ONLY) #else JITHELPER(CORINFO_HELP_STACK_PROBE, NULL, CORINFO_HELP_SIG_UNDEF) diff --git a/src/coreclr/inc/stdmacros.h b/src/coreclr/inc/stdmacros.h index 2d0a0576172aa..b6b50300c1615 100644 --- a/src/coreclr/inc/stdmacros.h +++ b/src/coreclr/inc/stdmacros.h @@ -111,6 +111,20 @@ #define NOT_ARM64_ARG(x) , x #endif +#ifdef TARGET_LOONGARCH64 +#define LOONGARCH64_FIRST_ARG(x) x , +#define LOONGARCH64_ARG(x) , x +#define LOONGARCH64_ONLY(x) x +#define NOT_LOONGARCH64(x) +#define NOT_LOONGARCH64_ARG(x) +#else +#define LOONGARCH64_FIRST_ARG(x) +#define LOONGARCH64_ARG(x) +#define LOONGARCH64_ONLY(x) +#define NOT_LOONGARCH64(x) x +#define NOT_LOONGARCH64_ARG(x) , x +#endif + #ifdef TARGET_64BIT #define LOG2_PTRSIZE 3 #else diff --git a/src/coreclr/tools/superpmi/superpmi-shared/compileresult.cpp b/src/coreclr/tools/superpmi/superpmi-shared/compileresult.cpp index 729064aa1b0bc..9051458948c15 100644 --- a/src/coreclr/tools/superpmi/superpmi-shared/compileresult.cpp +++ b/src/coreclr/tools/superpmi/superpmi-shared/compileresult.cpp @@ -861,6 +861,11 @@ void CompileResult::applyRelocs(unsigned char* block1, ULONG blocksize1, void* o } } + if (targetArch == SPMI_TARGET_ARCHITECTURE_LOONGARCH64) + { + Assert(!"FIXME: Not Implements on loongarch64"); + } + if (IsSpmiTarget64Bit()) { if (relocType == IMAGE_REL_BASED_DIR64) diff --git a/src/coreclr/tools/superpmi/superpmi-shared/spmiutil.cpp b/src/coreclr/tools/superpmi/superpmi-shared/spmiutil.cpp index 4d21d7d2d5643..97f4ca40631da 100644 --- a/src/coreclr/tools/superpmi/superpmi-shared/spmiutil.cpp +++ b/src/coreclr/tools/superpmi/superpmi-shared/spmiutil.cpp @@ -251,6 +251,8 @@ static SPMI_TARGET_ARCHITECTURE SpmiTargetArchitecture = SPMI_TARGET_ARCHITECTUR static SPMI_TARGET_ARCHITECTURE SpmiTargetArchitecture = SPMI_TARGET_ARCHITECTURE_ARM; #elif defined(TARGET_ARM64) static SPMI_TARGET_ARCHITECTURE SpmiTargetArchitecture = SPMI_TARGET_ARCHITECTURE_ARM64; +#elif defined(TARGET_LOONGARCH64) +static SPMI_TARGET_ARCHITECTURE SpmiTargetArchitecture = SPMI_TARGET_ARCHITECTURE_LOONGARCH64; #else #error Unsupported architecture #endif diff --git a/src/coreclr/tools/superpmi/superpmi-shared/spmiutil.h b/src/coreclr/tools/superpmi/superpmi-shared/spmiutil.h index bc6bfa4f34fc2..16543880d944b 100644 --- a/src/coreclr/tools/superpmi/superpmi-shared/spmiutil.h +++ b/src/coreclr/tools/superpmi/superpmi-shared/spmiutil.h @@ -53,7 +53,8 @@ enum SPMI_TARGET_ARCHITECTURE SPMI_TARGET_ARCHITECTURE_X86, SPMI_TARGET_ARCHITECTURE_AMD64, SPMI_TARGET_ARCHITECTURE_ARM64, - SPMI_TARGET_ARCHITECTURE_ARM + SPMI_TARGET_ARCHITECTURE_ARM, + SPMI_TARGET_ARCHITECTURE_LOONGARCH64 }; SPMI_TARGET_ARCHITECTURE GetSpmiTargetArchitecture(); @@ -61,7 +62,7 @@ void SetSpmiTargetArchitecture(SPMI_TARGET_ARCHITECTURE spmiTargetArchitecture); inline bool IsSpmiTarget64Bit() { - return (GetSpmiTargetArchitecture() == SPMI_TARGET_ARCHITECTURE_AMD64) || (GetSpmiTargetArchitecture() == SPMI_TARGET_ARCHITECTURE_ARM64); + return (GetSpmiTargetArchitecture() == SPMI_TARGET_ARCHITECTURE_AMD64) || (GetSpmiTargetArchitecture() == SPMI_TARGET_ARCHITECTURE_ARM64) || (GetSpmiTargetArchitecture() == SPMI_TARGET_ARCHITECTURE_LOONGARCH64); } inline size_t SpmiTargetPointerSize() diff --git a/src/coreclr/tools/superpmi/superpmi/superpmi.cpp b/src/coreclr/tools/superpmi/superpmi/superpmi.cpp index e5a7d7f326373..d7a8d80bf6545 100644 --- a/src/coreclr/tools/superpmi/superpmi/superpmi.cpp +++ b/src/coreclr/tools/superpmi/superpmi/superpmi.cpp @@ -53,6 +53,10 @@ void SetSuperPmiTargetArchitecture(const char* targetArchitecture) { SetSpmiTargetArchitecture(SPMI_TARGET_ARCHITECTURE_ARM64); } + else if (0 == _stricmp(targetArchitecture, "loongarch64")) + { + SetSpmiTargetArchitecture(SPMI_TARGET_ARCHITECTURE_LOONGARCH64); + } else { LogError("Illegal target architecture '%s'", targetArchitecture); diff --git a/src/coreclr/vm/CMakeLists.txt b/src/coreclr/vm/CMakeLists.txt index b9147ba7ca93f..6a1b5d919eb27 100644 --- a/src/coreclr/vm/CMakeLists.txt +++ b/src/coreclr/vm/CMakeLists.txt @@ -762,6 +762,14 @@ else(CLR_CMAKE_TARGET_WIN32) ${ARCH_SOURCES_DIR}/pinvokestubs.S ${ARCH_SOURCES_DIR}/thunktemplates.S ) + elseif(CLR_CMAKE_TARGET_ARCH_LOONGARCH64) + set(VM_SOURCES_WKS_ARCH_ASM + ${ARCH_SOURCES_DIR}/asmhelpers.S + ${ARCH_SOURCES_DIR}/calldescrworkerloongarch64.S + ${ARCH_SOURCES_DIR}/crthelpers.S + ${ARCH_SOURCES_DIR}/pinvokestubs.S + ${ARCH_SOURCES_DIR}/thunktemplates.S + ) endif() endif(CLR_CMAKE_TARGET_WIN32) @@ -864,6 +872,21 @@ elseif(CLR_CMAKE_TARGET_ARCH_ARM64) ${ARCH_SOURCES_DIR}/arm64singlestepper.cpp ) endif(CLR_CMAKE_HOST_UNIX) +elseif(CLR_CMAKE_TARGET_ARCH_LOONGARCH64) + set(VM_SOURCES_DAC_AND_WKS_ARCH + ${ARCH_SOURCES_DIR}/stubs.cpp + exceptionhandling.cpp + ) + + set(VM_HEADERS_DAC_AND_WKS_ARCH + ${ARCH_SOURCES_DIR}/virtualcallstubcpu.hpp + exceptionhandling.h + ) + + set(VM_SOURCES_WKS_ARCH + ${ARCH_SOURCES_DIR}/profiler.cpp + gcinfodecoder.cpp + ) endif() if(CLR_CMAKE_HOST_UNIX) diff --git a/src/coreclr/vm/argdestination.h b/src/coreclr/vm/argdestination.h index d4db4b6d44270..59f1514d2365f 100644 --- a/src/coreclr/vm/argdestination.h +++ b/src/coreclr/vm/argdestination.h @@ -29,8 +29,8 @@ class ArgDestination LIMITED_METHOD_CONTRACT; #if defined(UNIX_AMD64_ABI) _ASSERTE((argLocDescForStructInRegs != NULL) || (offset != TransitionBlock::StructInRegsOffset)); -#elif defined(TARGET_ARM64) - // This assert is not interesting on arm64. argLocDescForStructInRegs could be +#elif defined(TARGET_ARM64) || defined(TARGET_LOONGARCH64) + // This assert is not interesting on arm64/loongarch64. argLocDescForStructInRegs could be // initialized if the args are being enregistered. #else _ASSERTE(argLocDescForStructInRegs == NULL); @@ -83,6 +83,67 @@ class ArgDestination #endif // !DACCESS_COMPILE #endif // defined(TARGET_ARM64) +#if defined(TARGET_LOONGARCH64) + bool IsStructPassedInRegs() + { + return m_argLocDescForStructInRegs != NULL; + } + +#ifndef DACCESS_COMPILE + void CopyStructToRegisters(void *src, int fieldBytes) + { + _ASSERTE(IsStructPassedInRegs()); + _ASSERTE(fieldBytes <= 16); + + int argOfs = TransitionBlock::GetOffsetOfFloatArgumentRegisters() + m_argLocDescForStructInRegs->m_idxFloatReg * 8; + + if (m_argLocDescForStructInRegs->m_structFields == STRUCT_FLOAT_FIELD_ONLY_TWO) + { // struct with two floats. + _ASSERTE(m_argLocDescForStructInRegs->m_cFloatReg == 2); + _ASSERTE(m_argLocDescForStructInRegs->m_cGenReg == 0); + *(INT64*)((char*)m_base + argOfs) = *(INT32*)src; + *(INT64*)((char*)m_base + argOfs + 8) = *((INT32*)src + 1); + } + else if ((m_argLocDescForStructInRegs->m_structFields & STRUCT_FLOAT_FIELD_FIRST) != 0) + { // the first field is float or double. + if ((m_argLocDescForStructInRegs->m_structFields & STRUCT_FIRST_FIELD_SIZE_IS8) == 0) + *(INT64*)((char*)m_base + argOfs) = *(INT32*)src; // the first field is float + else + *(UINT64*)((char*)m_base + argOfs) = *(UINT64*)src; // the first field is double. + _ASSERTE(m_argLocDescForStructInRegs->m_cFloatReg == 1); + _ASSERTE(m_argLocDescForStructInRegs->m_cGenReg == 1); + _ASSERTE((m_argLocDescForStructInRegs->m_structFields & STRUCT_FLOAT_FIELD_SECOND) == 0);//the second field is integer. + argOfs = TransitionBlock::GetOffsetOfArgumentRegisters() + m_argLocDescForStructInRegs->m_idxGenReg * 8; + if ((m_argLocDescForStructInRegs->m_structFields & STRUCT_HAS_8BYTES_FIELDS_MASK) != 0) + *(UINT64*)((char*)m_base + argOfs) = *((UINT64*)src + 1); + else + *(INT64*)((char*)m_base + argOfs) = *((INT32*)src + 1); // the second field is int32. + } + else if ((m_argLocDescForStructInRegs->m_structFields & STRUCT_FLOAT_FIELD_SECOND) != 0) + { // the second field is float or double. + *(UINT64*)((char*)m_base + argOfs) = *(UINT64*)src; // NOTE: here ignoring the first size. + if ((m_argLocDescForStructInRegs->m_structFields & STRUCT_HAS_8BYTES_FIELDS_MASK) == 0) + *(UINT64*)((char*)m_base + argOfs) = *((INT32*)src + 1); // the second field is int32. + else + *(UINT64*)((char*)m_base + argOfs) = *((UINT64*)src + 1); + _ASSERTE(m_argLocDescForStructInRegs->m_cFloatReg == 1); + _ASSERTE(m_argLocDescForStructInRegs->m_cGenReg == 1); + _ASSERTE((m_argLocDescForStructInRegs->m_structFields & STRUCT_FLOAT_FIELD_FIRST) == 0);//the first field is integer. + argOfs = TransitionBlock::GetOffsetOfArgumentRegisters() + m_argLocDescForStructInRegs->m_idxGenReg * 8; + } + else + _ASSERTE(!"---------UNReachable-------LoongArch64!!!"); + } +#endif // !DACCESS_COMPILE + + PTR_VOID GetStructGenRegDestinationAddress() + { + _ASSERTE(IsStructPassedInRegs()); + int argOfs = TransitionBlock::GetOffsetOfArgumentRegisters() + m_argLocDescForStructInRegs->m_idxGenReg * 8; + return dac_cast(dac_cast(m_base) + argOfs); + } +#endif // defined(TARGET_LOONGARCH64) + #if defined(UNIX_AMD64_ABI) // Returns true if the ArgDestination represents a struct passed in registers. diff --git a/src/coreclr/vm/callcounting.h b/src/coreclr/vm/callcounting.h index 089702e066cb1..3d25e1c282626 100644 --- a/src/coreclr/vm/callcounting.h +++ b/src/coreclr/vm/callcounting.h @@ -97,6 +97,8 @@ class CallCountingStub static const int CodeSize = 40; #elif defined(TARGET_ARM) static const int CodeSize = 32; +#elif defined(TARGET_LOONGARCH64) + static const int CodeSize = 40; #endif private: diff --git a/src/coreclr/vm/callhelpers.cpp b/src/coreclr/vm/callhelpers.cpp index 711d6d55cfa9e..7b99261c6fbde 100644 --- a/src/coreclr/vm/callhelpers.cpp +++ b/src/coreclr/vm/callhelpers.cpp @@ -462,17 +462,44 @@ void MethodDescCallSite::CallTargetWorker(const ARG_SLOT *pArguments, ARG_SLOT * argDest.CopyStructToRegisters(pSrc, th.AsMethodTable()->GetNumInstanceFieldBytes(), 0); } else -#endif // UNIX_AMD64_ABI +#elif defined(TARGET_LOONGARCH64) + if (argDest.IsStructPassedInRegs()) + { + argDest.CopyStructToRegisters(pSrc, stackSize); + } + else +#endif // TARGET_LOONGARCH64 { PVOID pDest = argDest.GetDestinationAddress(); switch (stackSize) { +#if defined(TARGET_LOONGARCH64) + case 1: + if (m_argIt.GetArgType() == ELEMENT_TYPE_U1 || m_argIt.GetArgType() == ELEMENT_TYPE_BOOLEAN) + *((INT64*)pDest) = (UINT8)pArguments[arg]; + else + *((INT64*)pDest) = (INT8)pArguments[arg]; + break; + case 2: + if (m_argIt.GetArgType() == ELEMENT_TYPE_U2 || m_argIt.GetArgType() == ELEMENT_TYPE_CHAR) + *((INT64*)pDest) = (UINT16)pArguments[arg]; + else + *((INT64*)pDest) = (INT16)pArguments[arg]; + break; + case 4: + if (m_argIt.GetArgType() == ELEMENT_TYPE_U4) + *((INT64*)pDest) = (UINT32)pArguments[arg]; + else + *((INT64*)pDest) = (INT32)pArguments[arg]; + break; +#else case 1: case 2: case 4: *((INT32*)pDest) = (INT32)pArguments[arg]; break; +#endif case 8: *((INT64*)pDest) = pArguments[arg]; diff --git a/src/coreclr/vm/callingconvention.h b/src/coreclr/vm/callingconvention.h index 1fe96fde98922..2e5b1b5d01db5 100644 --- a/src/coreclr/vm/callingconvention.h +++ b/src/coreclr/vm/callingconvention.h @@ -41,6 +41,9 @@ struct ArgLocDesc int m_byteStackIndex; // Stack offset in bytes (or -1) int m_byteStackSize; // Stack size in bytes +#if defined(TARGET_LOONGARCH64) + int m_structFields; // Struct field info when using Float-register except two-doubles case. +#endif #if defined(UNIX_AMD64_ABI) @@ -93,6 +96,9 @@ struct ArgLocDesc #if defined(TARGET_ARM64) m_hfaFieldSize = 0; #endif // defined(TARGET_ARM64) +#if defined(TARGET_LOONGARCH64) + m_structFields = STRUCT_NO_FLOAT_FIELD; +#endif #if defined(UNIX_AMD64_ABI) m_eeClass = NULL; #endif @@ -138,6 +144,26 @@ struct TransitionBlock TADDR padding; // Keep size of TransitionBlock as multiple of 16-byte. Simplifies code in PROLOG_WITH_TRANSITION_BLOCK INT64 m_x8RetBuffReg; ArgumentRegisters m_argumentRegisters; +#elif defined(TARGET_LOONGARCH64) + union { + CalleeSavedRegisters m_calleeSavedRegisters; + struct { + INT64 fp; // frame pointer + TADDR m_ReturnAddress; + INT64 s0; + INT64 s1; + INT64 s2; + INT64 s3; + INT64 s4; + INT64 s5; + INT64 s6; + INT64 s7; + INT64 s8; + INT64 tp; + }; + }; + //TADDR padding; // Keep size of TransitionBlock as multiple of 16-byte. Simplifies code in PROLOG_WITH_TRANSITION_BLOCK + ArgumentRegisters m_argumentRegisters; #else PORTABILITY_ASSERT("TransitionBlock"); #endif @@ -476,6 +502,9 @@ class ArgIteratorTemplate : public ARGITERATOR_BASE #elif defined(TARGET_ARM64) // Composites greater than 16 bytes are passed by reference return ((size > ENREGISTERED_PARAMTYPE_MAXSIZE) && !th.IsHFA()); +#elif defined(TARGET_LOONGARCH64) + // Composites greater than 16 bytes are passed by reference + return (size > ENREGISTERED_PARAMTYPE_MAXSIZE); #else PORTABILITY_ASSERT("ArgIteratorTemplate::IsArgPassedByRef"); return FALSE; @@ -530,6 +559,13 @@ class ArgIteratorTemplate : public ARGITERATOR_BASE return ((m_argSize > ENREGISTERED_PARAMTYPE_MAXSIZE) && (!m_argTypeHandle.IsHFA() || this->IsVarArg())); } return FALSE; +#elif defined(TARGET_LOONGARCH64) + if (m_argType == ELEMENT_TYPE_VALUETYPE) + { + _ASSERTE(!m_argTypeHandle.IsNull()); + return ((m_argSize > ENREGISTERED_PARAMTYPE_MAXSIZE) && (!m_argTypeHandle.IsHFA() || this->IsVarArg())); + } + return FALSE; #else PORTABILITY_ASSERT("ArgIteratorTemplate::IsArgPassedByRef"); return FALSE; @@ -591,7 +627,7 @@ class ArgIteratorTemplate : public ARGITERATOR_BASE ArgLocDesc* GetArgLocDescForStructInRegs() { -#if defined(UNIX_AMD64_ABI) || defined (TARGET_ARM64) +#if defined(UNIX_AMD64_ABI) || defined (TARGET_ARM64) || defined(TARGET_LOONGARCH64) return m_hasArgLocDescForStructInRegs ? &m_argLocDescForStructInRegs : NULL; #else return NULL; @@ -788,6 +824,58 @@ class ArgIteratorTemplate : public ARGITERATOR_BASE } #endif // TARGET_AMD64 +#ifdef TARGET_LOONGARCH64 + // Get layout information for the argument that the ArgIterator is currently visiting. + void GetArgLoc(int argOffset, ArgLocDesc *pLoc) + { + LIMITED_METHOD_CONTRACT; + + pLoc->Init(); + + if (m_hasArgLocDescForStructInRegs) + { + *pLoc = m_argLocDescForStructInRegs; + return; + } + + if (TransitionBlock::IsFloatArgumentRegisterOffset(argOffset)) + { + // TODO-LOONGARCH64: support SIMD. + // Dividing by 8 as size of each register in FloatArgumentRegisters is 8 bytes. + pLoc->m_idxFloatReg = (argOffset - TransitionBlock::GetOffsetOfFloatArgumentRegisters()) / 8; + + assert(!m_argTypeHandle.IsHFA()); + + pLoc->m_cFloatReg = 1; + + return; + } + + int cSlots = (GetArgSize() + 7)/ 8; + + // Composites greater than 16bytes are passed by reference + if (GetArgType() == ELEMENT_TYPE_VALUETYPE && GetArgSize() > ENREGISTERED_PARAMTYPE_MAXSIZE) + { + cSlots = 1; + } + + if (!TransitionBlock::IsStackArgumentOffset(argOffset)) + { + // At least one used integer register passed. + pLoc->m_idxGenReg = TransitionBlock::GetArgumentIndexFromOffset(argOffset); + pLoc->m_cGenReg = cSlots; + } + else + { + pLoc->m_byteStackIndex = TransitionBlock::GetStackArgumentByteIndexFromOffset(argOffset); + pLoc->m_byteStackSize = cSlots << 3; + } + + return; + } + +#endif // TARGET_LOONGARCH64 + protected: DWORD m_dwFlags; // Cached flags int m_nSizeOfArgStack; // Cached value of SizeOfArgStack @@ -798,10 +886,10 @@ class ArgIteratorTemplate : public ARGITERATOR_BASE CorElementType m_argType; int m_argSize; TypeHandle m_argTypeHandle; -#if (defined(TARGET_AMD64) && defined(UNIX_AMD64_ABI)) || defined(TARGET_ARM64) +#if (defined(TARGET_AMD64) && defined(UNIX_AMD64_ABI)) || defined(TARGET_ARM64) || defined(TARGET_LOONGARCH64) ArgLocDesc m_argLocDescForStructInRegs; bool m_hasArgLocDescForStructInRegs; -#endif // (TARGET_AMD64 && UNIX_AMD64_ABI) || TARGET_ARM64 +#endif // (TARGET_AMD64 && UNIX_AMD64_ABI) || TARGET_ARM64 || TARGET_LOONGARCH64 int m_ofsStack; // Current position of the stack iterator, in bytes @@ -829,6 +917,12 @@ class ArgIteratorTemplate : public ARGITERATOR_BASE int m_idxFPReg; // Next FP register to be assigned a value #endif +#ifdef TARGET_LOONGARCH64 + int m_idxGenReg; // Next general register to be assigned a value + int m_idxStack; // Next stack slot to be assigned a value + int m_idxFPReg; // Next FP register to be assigned a value +#endif + enum { ITERATION_STARTED = 0x0001, // Started iterating over arguments SIZE_OF_ARG_STACK_COMPUTED = 0x0002, @@ -1071,6 +1165,10 @@ int ArgIteratorTemplate::GetNextOffset() m_idxGenReg = numRegistersUsed; m_ofsStack = 0; + m_idxFPReg = 0; +#elif defined(TARGET_LOONGARCH64) + m_idxGenReg = numRegistersUsed; + m_ofsStack = 0; m_idxFPReg = 0; #else PORTABILITY_ASSERT("ArgIteratorTemplate::GetNextOffset"); @@ -1094,7 +1192,7 @@ int ArgIteratorTemplate::GetNextOffset() m_argSize = argSize; m_argTypeHandle = thValueType; -#if defined(UNIX_AMD64_ABI) || defined (TARGET_ARM64) +#if defined(UNIX_AMD64_ABI) || defined (TARGET_ARM64) || defined (TARGET_LOONGARCH64) m_hasArgLocDescForStructInRegs = false; #endif @@ -1521,6 +1619,132 @@ int ArgIteratorTemplate::GetNextOffset() int argOfs = TransitionBlock::GetOffsetOfArgs() + m_ofsStack; m_ofsStack += cbArg; + return argOfs; +#elif defined(TARGET_LOONGARCH64) + + int cFPRegs = 0; + int flags = 0; + + switch (argType) + { + + case ELEMENT_TYPE_R4: + // 32-bit floating point argument. + cFPRegs = 1; + break; + + case ELEMENT_TYPE_R8: + // 64-bit floating point argument. + cFPRegs = 1; + break; + + case ELEMENT_TYPE_VALUETYPE: + { + // Handle struct which containing floats or doubles that can be passed + // in FP registers if possible. + + // Composite greater than 16bytes should be passed by reference + if (argSize > ENREGISTERED_PARAMTYPE_MAXSIZE) + { + argSize = sizeof(TADDR); + } + else + { + MethodTable* pMethodTable = nullptr; + + if (!thValueType.IsTypeDesc()) + pMethodTable = thValueType.AsMethodTable(); + else + { + _ASSERTE(thValueType.IsNativeValueType()); + pMethodTable = thValueType.AsNativeValueType(); + } + _ASSERTE(pMethodTable != nullptr); + flags = MethodTable::GetLoongArch64PassStructInRegisterFlags((CORINFO_CLASS_HANDLE)pMethodTable); + if (flags & STRUCT_HAS_FLOAT_FIELDS_MASK) + { + cFPRegs = (flags & STRUCT_FLOAT_FIELD_ONLY_TWO) ? 2 : 1; + } + } + + break; + } + + default: + break; + } + + const bool isValueType = (argType == ELEMENT_TYPE_VALUETYPE); + const bool isFloatHfa = thValueType.IsFloatHfa(); + const int cbArg = StackElemSize(argSize, isValueType, isFloatHfa); + + if (cFPRegs > 0 && !this->IsVarArg()) + { + if (flags & (STRUCT_FLOAT_FIELD_FIRST | STRUCT_FLOAT_FIELD_SECOND)) + { + assert(cFPRegs == 1); + assert((STRUCT_FLOAT_FIELD_FIRST == (flags & STRUCT_HAS_FLOAT_FIELDS_MASK)) || (STRUCT_FLOAT_FIELD_SECOND == (flags & STRUCT_HAS_FLOAT_FIELDS_MASK))); + + if ((1 + m_idxFPReg <= NUM_ARGUMENT_REGISTERS) && (m_idxGenReg + 1 <= NUM_ARGUMENT_REGISTERS)) + { + m_argLocDescForStructInRegs.Init(); + m_argLocDescForStructInRegs.m_idxFloatReg = m_idxFPReg; + m_argLocDescForStructInRegs.m_cFloatReg = 1; + int argOfs = TransitionBlock::GetOffsetOfFloatArgumentRegisters() + m_idxFPReg * 8; + m_idxFPReg += 1; + + m_argLocDescForStructInRegs.m_structFields = flags; + + m_argLocDescForStructInRegs.m_idxGenReg = m_idxGenReg; + m_argLocDescForStructInRegs.m_cGenReg = 1; + m_idxGenReg += 1; + + m_hasArgLocDescForStructInRegs = true; + + return argOfs; + } + } + else if (cFPRegs + m_idxFPReg <= NUM_ARGUMENT_REGISTERS) + { + int argOfs = TransitionBlock::GetOffsetOfFloatArgumentRegisters() + m_idxFPReg * 8; + if (flags == STRUCT_FLOAT_FIELD_ONLY_TWO) // struct with two float-fields. + { + m_argLocDescForStructInRegs.Init(); + m_hasArgLocDescForStructInRegs = true; + m_argLocDescForStructInRegs.m_idxFloatReg = m_idxFPReg; + assert(cFPRegs == 2); + m_argLocDescForStructInRegs.m_cFloatReg = 2; + assert(argSize == 8); + m_argLocDescForStructInRegs.m_structFields = STRUCT_FLOAT_FIELD_ONLY_TWO; + } + m_idxFPReg += cFPRegs; + return argOfs; + } + else + m_idxFPReg = NUM_ARGUMENT_REGISTERS; + } + + { + const int regSlots = ALIGN_UP(cbArg, TARGET_POINTER_SIZE) / TARGET_POINTER_SIZE; + if (m_idxGenReg + regSlots <= NUM_ARGUMENT_REGISTERS) + { + int argOfs = TransitionBlock::GetOffsetOfArgumentRegisters() + m_idxGenReg * 8; + m_idxGenReg += regSlots; + return argOfs; + } + else if (m_idxGenReg < NUM_ARGUMENT_REGISTERS) + { + int argOfs = TransitionBlock::GetOffsetOfArgumentRegisters() + m_idxGenReg * 8; + m_ofsStack += (m_idxGenReg + regSlots - NUM_ARGUMENT_REGISTERS)*8; + assert(m_ofsStack == 8); + m_idxGenReg = NUM_ARGUMENT_REGISTERS; + return argOfs; + } + } + + int argOfs = TransitionBlock::GetOffsetOfArgs() + m_ofsStack; + m_ofsStack += ALIGN_UP(cbArg, TARGET_POINTER_SIZE); + return argOfs; #else PORTABILITY_ASSERT("ArgIteratorTemplate::GetNextOffset"); @@ -1557,14 +1781,22 @@ void ArgIteratorTemplate::ComputeReturnFlags() break; case ELEMENT_TYPE_R4: +#if defined(TARGET_LOONGARCH64) + flags |= STRUCT_FLOAT_FIELD_ONLY_ONE << RETURN_FP_SIZE_SHIFT; +#else #ifndef ARM_SOFTFP flags |= sizeof(float) << RETURN_FP_SIZE_SHIFT; +#endif #endif break; case ELEMENT_TYPE_R8: +#if defined(TARGET_LOONGARCH64) + flags |= (STRUCT_FLOAT_FIELD_ONLY_ONE | STRUCT_FIRST_FIELD_SIZE_IS8) << RETURN_FP_SIZE_SHIFT; +#else #ifndef ARM_SOFTFP flags |= sizeof(double) << RETURN_FP_SIZE_SHIFT; +#endif #endif break; @@ -1629,8 +1861,19 @@ void ArgIteratorTemplate::ComputeReturnFlags() } #endif // defined(TARGET_X86) || defined(TARGET_AMD64) +#if defined(TARGET_LOONGARCH64) if (size <= ENREGISTERED_RETURNTYPE_INTEGER_MAXSIZE) + { + assert(!thValueType.IsTypeDesc()); + + MethodTable *pMethodTable = thValueType.AsMethodTable(); + flags = (MethodTable::GetLoongArch64PassStructInRegisterFlags((CORINFO_CLASS_HANDLE)pMethodTable) & 0xff) << RETURN_FP_SIZE_SHIFT; break; + } +#else + if (size <= ENREGISTERED_RETURNTYPE_INTEGER_MAXSIZE) + break; +#endif #endif // UNIX_AMD64_ABI } #endif // ENREGISTERED_RETURNTYPE_INTEGER_MAXSIZE diff --git a/src/coreclr/vm/ceeload.h b/src/coreclr/vm/ceeload.h index 9c2adf378c866..a364179e03ed4 100644 --- a/src/coreclr/vm/ceeload.h +++ b/src/coreclr/vm/ceeload.h @@ -87,6 +87,8 @@ class JITInlineTrackingMap; #define NATIVE_SYMBOL_READER_DLL W("Microsoft.DiaSymReader.Native.arm.dll") #elif defined(HOST_ARM64) #define NATIVE_SYMBOL_READER_DLL W("Microsoft.DiaSymReader.Native.arm64.dll") +#elif defined(HOST_LOONGARCH64) +#define NATIVE_SYMBOL_READER_DLL W("Microsoft.DiaSymReader.Native.loongarch64.dll") #endif typedef DPTR(JITInlineTrackingMap) PTR_JITInlineTrackingMap; diff --git a/src/coreclr/vm/cgensys.h b/src/coreclr/vm/cgensys.h index f66614a63d25f..868e9cf26bf3f 100644 --- a/src/coreclr/vm/cgensys.h +++ b/src/coreclr/vm/cgensys.h @@ -58,7 +58,6 @@ extern "C" void STDCALL GenericPInvokeCalliStubWorker(TransitionBlock * pTransit extern "C" void STDCALL GenericPInvokeCalliHelper(void); extern "C" PCODE STDCALL ExternalMethodFixupWorker(TransitionBlock * pTransitionBlock, TADDR pIndirection, DWORD sectionIndex, Module * pModule); -extern "C" void STDCALL ExternalMethodFixupStub(void); extern "C" void STDCALL ExternalMethodFixupPatchLabel(void); extern "C" void STDCALL VirtualMethodFixupStub(void); diff --git a/src/coreclr/vm/codeman.cpp b/src/coreclr/vm/codeman.cpp index aece10d072ac2..87536b507d134 100644 --- a/src/coreclr/vm/codeman.cpp +++ b/src/coreclr/vm/codeman.cpp @@ -812,7 +812,7 @@ ExecutionManager::DeleteRangeHelper //----------------------------------------------------------------------------- -#if defined(TARGET_ARM) || defined(TARGET_ARM64) +#if defined(TARGET_ARM) || defined(TARGET_ARM64) || defined(TARGET_LOONGARCH64) #define EXCEPTION_DATA_SUPPORTS_FUNCTION_FRAGMENTS #endif @@ -881,6 +881,41 @@ BOOL IsFunctionFragment(TADDR baseAddress, PTR_RUNTIME_FUNCTION pFunctionEntry) pUnwindCodes += EpilogCount; } + return ((*pUnwindCodes & 0xFF) == 0xE5); +#elif defined(TARGET_LOONGARCH64) + + // LOONGARCH64 is a little bit more flexible, in the sense that it supports partial prologs. However only one of the + // prolog regions are allowed to alter SP and that's the Host Record. Partial prologs are used in ShrinkWrapping + // scenarios which is not supported, hence we don't need to worry about them. discarding partial prologs + // simplifies identifying a host record a lot. + // + // 1. Prolog only: The host record. Epilog Count and E bit are all 0. + // 2. Prolog and some epilogs: The host record with accompanying epilog-only records + // 3. Epilogs only: First unwind code is Phantom prolog (Starting with an end_c, indicating an empty prolog) + // 4. No prologs or epilogs: First unwind code is Phantom prolog (Starting with an end_c, indicating an empty prolog) + // + + int EpilogCount = (int)(unwindHeader >> 22) & 0x1F; + int CodeWords = unwindHeader >> 27; + PTR_DWORD pUnwindCodes = (PTR_DWORD)(baseAddress + pFunctionEntry->UnwindData); + // Skip header. + pUnwindCodes++; + + // Skip extended header. + if ((CodeWords == 0) && (EpilogCount == 0)) + { + EpilogCount = (*pUnwindCodes) & 0xFFFF; + pUnwindCodes++; + } + + // Skip epilog scopes. + BOOL Ebit = (unwindHeader >> 21) & 0x1; + if (!Ebit && (EpilogCount != 0)) + { + // EpilogCount is the number of exception scopes defined right after the unwindHeader + pUnwindCodes += EpilogCount; + } + return ((*pUnwindCodes & 0xFF) == 0xE5); #else PORTABILITY_ASSERT("IsFunctionFragnent - NYI on this platform"); @@ -1045,6 +1080,47 @@ PTR_VOID GetUnwindDataBlob(TADDR moduleBase, PTR_RUNTIME_FUNCTION pRuntimeFuncti *pSize = size; return xdata; + +#elif defined(TARGET_LOONGARCH64) + // TODO: maybe optimize further. + // if this function uses packed unwind data then at least one of the two least significant bits + // will be non-zero. if this is the case then there will be no xdata record to enumerate. + _ASSERTE((pRuntimeFunction->UnwindData & 0x3) == 0); + + // compute the size of the unwind info + PTR_ULONG xdata = dac_cast(pRuntimeFunction->UnwindData + moduleBase); + ULONG epilogScopes = 0; + ULONG unwindWords = 0; + ULONG size = 0; + + //If both Epilog Count and Code Word is not zero + //Info of Epilog and Unwind scopes are given by 1 word header + //Otherwise this info is given by a 2 word header + if ((xdata[0] >> 27) != 0) + { + size = 4; + epilogScopes = (xdata[0] >> 22) & 0x1f; + unwindWords = (xdata[0] >> 27) & 0x1f; + } + else + { + size = 8; + epilogScopes = xdata[1] & 0xffff; + unwindWords = (xdata[1] >> 16) & 0xff; + } + + if (!(xdata[0] & (1 << 21))) + size += 4 * epilogScopes; + + size += 4 * unwindWords; + + _ASSERTE(xdata[0] & (1 << 20)); // personality routine should be always present + size += 4; // exception handler RVA + + *pSize = size; + return xdata; + + #else PORTABILITY_ASSERT("GetUnwindDataBlob"); return NULL; @@ -1623,6 +1699,10 @@ void EEJitManager::SetCpuInfo() } #endif +#if defined(TARGET_LOONGARCH64) + // TODO-LoongArch64: set LoongArch64's InstructionSet features ! +#endif // TARGET_LOONGARCH64 + // These calls are very important as it ensures the flags are consistent with any // removals specified above. This includes removing corresponding 64-bit ISAs // and any other implications such as SSE2 depending on SSE or AdvSimd on ArmBase @@ -1933,6 +2013,8 @@ BOOL EEJitManager::LoadJIT() altJitName = MAKEDLLNAME_W(W("clrjit_unix_x86_x86")); #elif defined(TARGET_AMD64) altJitName = MAKEDLLNAME_W(W("clrjit_unix_x64_x64")); +#elif defined(TARGET_LOONGARCH64) + altJitName = MAKEDLLNAME_W(W("clrjit_unix_loongarch64_loongarch64")); #endif #endif // TARGET_WINDOWS @@ -2316,7 +2398,7 @@ static size_t GetDefaultReserveForJumpStubs(size_t codeHeapSize) { LIMITED_METHOD_CONTRACT; -#if defined(TARGET_AMD64) || defined(TARGET_ARM64) +#if defined(TARGET_AMD64) || defined(TARGET_ARM64) || defined(TARGET_LOONGARCH64) // // Keep a small default reserve at the end of the codeheap for jump stubs. It should reduce // chance that we won't be able allocate jump stub because of lack of suitable address space. @@ -2411,7 +2493,7 @@ HeapList* LoaderCodeHeap::CreateCodeHeap(CodeHeapRequestInfo *pInfo, LoaderHeap // this first allocation is critical as it sets up correctly the loader heap info HeapList *pHp = new HeapList; -#if defined(TARGET_AMD64) || defined(TARGET_ARM64) +#if defined(TARGET_AMD64) || defined(TARGET_ARM64) || defined(TARGET_LOONGARCH64) pHp->CLRPersonalityRoutine = (BYTE *)pCodeHeap->m_LoaderHeap.AllocMem(JUMP_ALLOCATE_SIZE); #else // Ensure that the heap has a reserved block of memory and so the GetReservedBytesFree() @@ -2564,7 +2646,7 @@ HeapList* EEJitManager::NewCodeHeap(CodeHeapRequestInfo *pInfo, DomainCodeHeapLi size_t reserveSize = initialRequestSize; -#if defined(TARGET_AMD64) || defined(TARGET_ARM64) +#if defined(TARGET_AMD64) || defined(TARGET_ARM64) || defined(TARGET_LOONGARCH64) reserveSize += JUMP_ALLOCATE_SIZE; #endif @@ -4099,10 +4181,10 @@ PTR_RUNTIME_FUNCTION EEJitManager::LazyGetFunctionEntry(EECodeInfo * pCodeInfo) if (RUNTIME_FUNCTION__BeginAddress(pFunctionEntry) <= address && address < RUNTIME_FUNCTION__EndAddress(pFunctionEntry, baseAddress)) { -#if defined(EXCEPTION_DATA_SUPPORTS_FUNCTION_FRAGMENTS) && defined(TARGET_ARM64) - // If we might have fragmented unwind, and we're on ARM64, make sure - // to returning the root record, as the trailing records don't have - // prolog unwind codes. +#if defined(EXCEPTION_DATA_SUPPORTS_FUNCTION_FRAGMENTS) && (defined(TARGET_ARM64) || defined(TARGET_LOONGARCH64)) + // If we might have fragmented unwind, and we're on ARM64/LoongArch64, + // make sure to returning the root record, + // as the trailing records don't have prolog unwind codes. pFunctionEntry = FindRootEntry(pFunctionEntry, baseAddress); #endif diff --git a/src/coreclr/vm/codeman.h b/src/coreclr/vm/codeman.h index 6cb9edf8d429b..ca3614bb5f6d0 100644 --- a/src/coreclr/vm/codeman.h +++ b/src/coreclr/vm/codeman.h @@ -478,7 +478,7 @@ struct HeapList size_t maxCodeHeapSize;// Size of the entire contiguous block of memory size_t reserveForJumpStubs; // Amount of memory reserved for jump stubs in this block -#if defined(TARGET_AMD64) || defined(TARGET_ARM64) +#if defined(TARGET_AMD64) || defined(TARGET_ARM64) || defined(TARGET_LOONGARCH64) BYTE* CLRPersonalityRoutine; // jump thunk to personality routine #endif diff --git a/src/coreclr/vm/comdelegate.cpp b/src/coreclr/vm/comdelegate.cpp index 0ca103293d3e9..6a656571414af 100644 --- a/src/coreclr/vm/comdelegate.cpp +++ b/src/coreclr/vm/comdelegate.cpp @@ -171,16 +171,34 @@ class ShuffleIterator // Shuffle float registers first if (m_currentFloatRegIndex < m_argLocDesc->m_cFloatReg) { - index = m_argLocDesc->m_idxFloatReg + m_currentFloatRegIndex; - m_currentFloatRegIndex++; +#if defined(TARGET_LOONGARCH64) + if ((m_argLocDesc->m_structFields & STRUCT_FLOAT_FIELD_SECOND) && (m_currentGenRegIndex < m_argLocDesc->m_cGenReg)) + { + // the first field is integer so just skip this. + } + else +#endif + { + index = m_argLocDesc->m_idxFloatReg + m_currentFloatRegIndex; + m_currentFloatRegIndex++; - return index | ShuffleEntry::REGMASK | ShuffleEntry::FPREGMASK; + return index | ShuffleEntry::REGMASK | ShuffleEntry::FPREGMASK; + } } // Shuffle any registers first (the order matters since otherwise we could end up shuffling a stack slot // over a register we later need to shuffle down as well). if (m_currentGenRegIndex < m_argLocDesc->m_cGenReg) { +#if defined(TARGET_LOONGARCH64) + if (7 < (m_currentGenRegIndex + m_argLocDesc->m_idxGenReg)) + { + m_currentGenRegIndex++; + index = m_currentByteStackIndex; + m_currentByteStackIndex += TARGET_POINTER_SIZE; + return index; + } +#endif index = m_argLocDesc->m_idxGenReg + m_currentGenRegIndex; m_currentGenRegIndex++; diff --git a/src/coreclr/vm/dynamicmethod.cpp b/src/coreclr/vm/dynamicmethod.cpp index 4ee05faaafd8a..1a3871fb242a8 100644 --- a/src/coreclr/vm/dynamicmethod.cpp +++ b/src/coreclr/vm/dynamicmethod.cpp @@ -437,7 +437,7 @@ HeapList* HostCodeHeap::InitializeHeapList(CodeHeapRequestInfo *pInfo) TrackAllocation *pTracker = NULL; -#if defined(TARGET_AMD64) || defined(TARGET_ARM64) +#if defined(TARGET_AMD64) || defined(TARGET_ARM64) || defined(TARGET_LOONGARCH64) pTracker = AllocMemory_NoThrow(0, JUMP_ALLOCATE_SIZE, sizeof(void*), 0); if (pTracker == NULL) diff --git a/src/coreclr/vm/eetwain.cpp b/src/coreclr/vm/eetwain.cpp index c2fef85d92b55..25c0c0a05cbc0 100644 --- a/src/coreclr/vm/eetwain.cpp +++ b/src/coreclr/vm/eetwain.cpp @@ -1461,7 +1461,7 @@ bool EECodeManager::IsGcSafe( EECodeInfo *pCodeInfo, return gcInfoDecoder.IsInterruptible(); } -#if defined(TARGET_ARM) || defined(TARGET_ARM64) +#if defined(TARGET_ARM) || defined(TARGET_ARM64) || defined(TARGET_LOONGARCH64) bool EECodeManager::HasTailCalls( EECodeInfo *pCodeInfo) { CONTRACTL { @@ -1479,7 +1479,7 @@ bool EECodeManager::HasTailCalls( EECodeInfo *pCodeInfo) return gcInfoDecoder.HasTailCalls(); } -#endif // TARGET_ARM || TARGET_ARM64 +#endif // TARGET_ARM || TARGET_ARM64 || TARGET_LOONGARCH64 #if defined(TARGET_AMD64) && defined(_DEBUG) diff --git a/src/coreclr/vm/encee.cpp b/src/coreclr/vm/encee.cpp index 3f745d299427c..07586ac3995cf 100644 --- a/src/coreclr/vm/encee.cpp +++ b/src/coreclr/vm/encee.cpp @@ -609,7 +609,7 @@ HRESULT EditAndContinueModule::ResumeInUpdatedFunction( SIZE_T newILOffset, CONTEXT *pOrigContext) { -#if defined(TARGET_ARM64) || defined(TARGET_ARM) +#if defined(TARGET_ARM64) || defined(TARGET_ARM) || defined(TARGET_LOONGARCH64) return E_NOTIMPL; #else LOG((LF_ENC, LL_INFO100, "EnCModule::ResumeInUpdatedFunction for %s at IL offset 0x%x, ", diff --git a/src/coreclr/vm/excep.cpp b/src/coreclr/vm/excep.cpp index dd80bf37ec679..04c912995fc23 100644 --- a/src/coreclr/vm/excep.cpp +++ b/src/coreclr/vm/excep.cpp @@ -6601,7 +6601,7 @@ IsDebuggerFault(EXCEPTION_RECORD *pExceptionRecord, #endif // TARGET_UNIX -#ifndef TARGET_ARM64 +#if !defined(TARGET_ARM64) && !defined(TARGET_LOONGARCH64) EXTERN_C void JIT_StackProbe_End(); #endif // TARGET_ARM64 @@ -6668,7 +6668,7 @@ bool IsIPInMarkedJitHelper(UINT_PTR uControlPc) CHECK_RANGE(JIT_WriteBarrier) CHECK_RANGE(JIT_CheckedWriteBarrier) CHECK_RANGE(JIT_ByRefWriteBarrier) -#if !defined(TARGET_ARM64) +#if !defined(TARGET_ARM64) && !defined(TARGET_LOONGARCH64) CHECK_RANGE(JIT_StackProbe) #endif // !TARGET_ARM64 #else @@ -6792,7 +6792,7 @@ AdjustContextForJITHelpers( Thread::VirtualUnwindToFirstManagedCallFrame(pContext); -#if defined(TARGET_ARM) || defined(TARGET_ARM64) +#if defined(TARGET_ARM) || defined(TARGET_ARM64) || defined(TARGET_LOONGARCH64) // We had an AV in the writebarrier that needs to be treated // as originating in managed code. At this point, the stack (growing // from left->right) looks like this: @@ -6816,7 +6816,7 @@ AdjustContextForJITHelpers( // Now we save the address back into the context so that it gets used // as the faulting address. SetIP(pContext, ControlPCPostAdjustment); -#endif // TARGET_ARM || TARGET_ARM64 +#endif // TARGET_ARM || TARGET_ARM64 || TARGET_LOONGARCH64 // Unwind the frame chain - On Win64, this is required since we may handle the managed fault and to do so, // we will replace the exception context with the managed context and "continue execution" there. Thus, we do not diff --git a/src/coreclr/vm/exceptionhandling.cpp b/src/coreclr/vm/exceptionhandling.cpp index 90cac3a5789f6..ec40eda20c54a 100644 --- a/src/coreclr/vm/exceptionhandling.cpp +++ b/src/coreclr/vm/exceptionhandling.cpp @@ -21,12 +21,12 @@ #define USE_CURRENT_CONTEXT_IN_FILTER #endif // TARGET_X86 -#if defined(TARGET_ARM) || defined(TARGET_ARM64) +#if defined(TARGET_ARM) || defined(TARGET_ARM64) || defined(TARGET_LOONGARCH64) // ARM/ARM64 uses Caller-SP to locate PSPSym in the funclet frame. #define USE_CALLER_SP_IN_FUNCLET -#endif // TARGET_ARM || TARGET_ARM64 +#endif // TARGET_ARM || TARGET_ARM64 || TARGET_LOONGARCH64 -#if defined(TARGET_ARM) || defined(TARGET_ARM64) || defined(TARGET_X86) +#if defined(TARGET_ARM) || defined(TARGET_ARM64) || defined(TARGET_X86) || defined(TARGET_LOONGARCH64) #define ADJUST_PC_UNWOUND_TO_CALL #define STACK_RANGE_BOUNDS_ARE_CALLER_SP #define USE_FUNCLET_CALL_HELPER @@ -35,7 +35,7 @@ // // For x86/Linux, RtlVirtualUnwind sets EstablisherFrame as Caller-SP. #define ESTABLISHER_FRAME_ADDRESS_IS_CALLER_SP -#endif // TARGET_ARM || TARGET_ARM64 || TARGET_X86 +#endif // TARGET_ARM || TARGET_ARM64 || TARGET_X86 || TARGET_LOONGARCH64 #ifndef TARGET_UNIX void NOINLINE @@ -528,6 +528,19 @@ void ExceptionTracker::UpdateNonvolatileRegisters(CONTEXT *pContextRecord, REGDI UPDATEREG(X28); UPDATEREG(Fp); +#elif defined(TARGET_LOONGARCH64) + + UPDATEREG(S0); + UPDATEREG(S1); + UPDATEREG(S2); + UPDATEREG(S3); + UPDATEREG(S4); + UPDATEREG(S5); + UPDATEREG(S6); + UPDATEREG(S7); + UPDATEREG(S8); + UPDATEREG(Fp); + #else PORTABILITY_ASSERT("ExceptionTracker::UpdateNonvolatileRegisters"); #endif @@ -808,6 +821,8 @@ UINT_PTR ExceptionTracker::FinishSecondPass( // On ARM & ARM64, we save off the original PC in Lr. This is the same as done // in HandleManagedFault for H/W generated exceptions. pContextRecord->Lr = uResumePC; +#elif defined(TARGET_LOONGARCH64) + pContextRecord->Ra = uResumePC; #endif uResumePC = uAbortAddr; @@ -1470,10 +1485,10 @@ void ExceptionTracker::InitializeCrawlFrame(CrawlFrame* pcfThisFrame, Thread* pT } else { -#if defined(TARGET_ARM) || defined(TARGET_ARM64) +#if defined(TARGET_ARM) || defined(TARGET_ARM64) || defined(TARGET_LOONGARCH64) // See the comment above the call to InitRegDisplay for this assertion. _ASSERTE(pDispatcherContext->ControlPc == GetIP(pDispatcherContext->ContextRecord)); -#endif // TARGET_ARM || TARGET_ARM64 +#endif // TARGET_ARM || TARGET_ARM64 || TARGET_LOONGARCH64 #ifdef ESTABLISHER_FRAME_ADDRESS_IS_CALLER_SP // Simply setup the callerSP during the second pass in the caller context. @@ -3249,6 +3264,8 @@ static inline UINT_PTR *GetFirstNonVolatileRegisterAddress(PCONTEXT pContextReco return (UINT_PTR*)&(pContextRecord->R4); #elif defined(TARGET_ARM64) return (UINT_PTR*)&(pContextRecord->X19); +#elif defined(TARGET_LOONGARCH64) + return (UINT_PTR*)&(pContextRecord->S0); #elif defined(TARGET_X86) return (UINT_PTR*)&(pContextRecord->Edi); #else @@ -3259,7 +3276,7 @@ static inline UINT_PTR *GetFirstNonVolatileRegisterAddress(PCONTEXT pContextReco static inline TADDR GetFrameRestoreBase(PCONTEXT pContextRecord) { -#if defined(TARGET_ARM) || defined(TARGET_ARM64) +#if defined(TARGET_ARM) || defined(TARGET_ARM64) || defined(TARGET_LOONGARCH64) return GetSP(pContextRecord); #elif defined(TARGET_X86) return pContextRecord->Ebp; @@ -5284,8 +5301,8 @@ BOOL HandleHardwareException(PAL_SEHException* ex) Thread *pThread = GetThreadNULLOk(); if (pThread != NULL && g_pDebugInterface != NULL) { -#if (defined(TARGET_ARM) || defined(TARGET_ARM64)) - // On ARM and ARM64 exception point to the break instruction. +#if (defined(TARGET_ARM) || defined(TARGET_ARM64) || defined(TARGET_LOONGARCH64)) + // On ARM and ARM64 and LOONGARCH64 exception point to the break instruction. // See https://static.docs.arm.com/ddi0487/db/DDI0487D_b_armv8_arm.pdf#page=6916&zoom=100,0,152 // at aarch64/exceptions/debug/AArch64.SoftwareBreakpoint // However, the rest of the code expects that it points to an instruction after the break. @@ -5500,7 +5517,7 @@ void FixupDispatcherContext(DISPATCHER_CONTEXT* pDispatcherContext, CONTEXT* pCo pDispatcherContext->ControlPc = (UINT_PTR) GetIP(pDispatcherContext->ContextRecord); -#if defined(TARGET_ARM) || defined(TARGET_ARM64) +#if defined(TARGET_ARM) || defined(TARGET_ARM64) || defined(TARGET_LOONGARCH64) // Since this routine is used to fixup contexts for async exceptions, // clear the CONTEXT_UNWOUND_TO_CALL flag since, semantically, frames // where such exceptions have happened do not have callsites. On a similar @@ -5523,12 +5540,15 @@ void FixupDispatcherContext(DISPATCHER_CONTEXT* pDispatcherContext, CONTEXT* pCo #ifdef TARGET_ARM // But keep the architecture flag set (its part of CONTEXT_DEBUG_REGISTERS) pDispatcherContext->ContextRecord->ContextFlags |= CONTEXT_ARM; +#elif defined(TARGET_LOONGARCH64) + // But keep the architecture flag set (its part of CONTEXT_DEBUG_REGISTERS) + pDispatcherContext->ContextRecord->ContextFlags |= CONTEXT_LOONGARCH64; #else // TARGET_ARM64 // But keep the architecture flag set (its part of CONTEXT_DEBUG_REGISTERS) pDispatcherContext->ContextRecord->ContextFlags |= CONTEXT_ARM64; #endif // TARGET_ARM -#endif // TARGET_ARM || TARGET_ARM64 +#endif // TARGET_ARM || TARGET_ARM64 || TARGET_LOONGARCH64 INDEBUG(pDispatcherContext->FunctionEntry = (PT_RUNTIME_FUNCTION)INVALID_POINTER_CD); INDEBUG(pDispatcherContext->ImageBase = INVALID_POINTER_CD); diff --git a/src/coreclr/vm/frames.h b/src/coreclr/vm/frames.h index 82e57cbbb0401..68f38e9263929 100644 --- a/src/coreclr/vm/frames.h +++ b/src/coreclr/vm/frames.h @@ -860,6 +860,9 @@ class RedirectedThreadFrame : public ResumableFrame #elif defined(TARGET_ARM64) Object** firstIntReg = (Object**)&this->GetContext()->X0; Object** lastIntReg = (Object**)&this->GetContext()->X28; +#elif defined(TARGET_LOONGARCH64) + Object** firstIntReg = (Object**)&this->GetContext()->Tp; + Object** lastIntReg = (Object**)&this->GetContext()->S8; #else _ASSERTE(!"nyi for platform"); #endif @@ -1901,6 +1904,10 @@ class UnmanagedToManagedFrame : public Frame TADDR m_ReturnAddress; TADDR m_x8; // ret buff arg ArgumentRegisters m_argumentRegisters; +#elif defined (TARGET_LOONGARCH64) + TADDR m_fp; + TADDR m_ReturnAddress; + ArgumentRegisters m_argumentRegisters; #else TADDR m_ReturnAddress; // return address into unmanaged code #endif diff --git a/src/coreclr/vm/gccover.cpp b/src/coreclr/vm/gccover.cpp index b0e6aa953b4e4..591c61190cd2c 100644 --- a/src/coreclr/vm/gccover.cpp +++ b/src/coreclr/vm/gccover.cpp @@ -36,7 +36,7 @@ MethodDesc* AsMethodDesc(size_t addr); static PBYTE getTargetOfCall(PBYTE instrPtr, PCONTEXT regs, PBYTE*nextInstr); -#if defined(TARGET_ARM) || defined(TARGET_ARM64) +#if defined(TARGET_ARM) || defined(TARGET_ARM64) || defined(TARGET_LOONGARCH64) static void replaceSafePointInstructionWithGcStressInstr(UINT32 safePointOffset, LPVOID codeStart); static bool replaceInterruptibleRangesWithGcStressInstr (UINT32 startOffset, UINT32 stopOffset, LPVOID codeStart); #endif @@ -97,7 +97,7 @@ bool IsGcCoverageInterruptInstruction(PBYTE instrPtr) { UINT32 instrVal; -#if defined(TARGET_ARM64) +#if defined(TARGET_ARM64) || defined(TARGET_LOONGARCH64) instrVal = *reinterpret_cast(instrPtr); #elif defined(TARGET_ARM) size_t instrLen = GetARMInstructionLength(instrPtr); @@ -118,7 +118,7 @@ bool IsGcCoverageInterruptInstruction(PBYTE instrPtr) bool IsOriginalInstruction(PBYTE instrPtr, GCCoverageInfo* gcCover, DWORD offset) { -#if defined(TARGET_ARM64) +#if defined(TARGET_ARM64) || defined(TARGET_LOONGARCH64) UINT32 instrVal = *reinterpret_cast(instrPtr); UINT32 origInstrVal = *reinterpret_cast(gcCover->savedCode + offset); return (instrVal == origInstrVal); @@ -174,7 +174,7 @@ void SetupAndSprinkleBreakpoints( fZapped); // This is not required for ARM* as the above call does the work for both hot & cold regions -#if !defined(TARGET_ARM) && !defined(TARGET_ARM64) +#if !defined(TARGET_ARM) && !defined(TARGET_ARM64) && !defined(TARGET_LOONGARCH64) if (gcCover->methodRegion.coldSize != 0) { gcCover->SprinkleBreakpoints(gcCover->savedCode + gcCover->methodRegion.hotSize, @@ -335,6 +335,12 @@ void ReplaceInstrAfterCall(PBYTE instrToReplace, MethodDesc* callMD) { *instrToReplace = INTERRUPT_INSTR; } +#elif defined(TARGET_LOONGARCH64) + bool protectReturn = ispointerKind; + if (protectReturn) + *(DWORD*)instrToReplace = INTERRUPT_INSTR_PROTECT_RET; + else + *(DWORD*)instrToReplace = INTERRUPT_INSTR; #else _ASSERTE(!"not implemented for platform"); #endif @@ -618,7 +624,7 @@ void GCCoverageInfo::SprinkleBreakpoints( if ((regionOffsetAdj==0) && (*codeStart != INTERRUPT_INSTR)) doingEpilogChecks = false; -#elif defined(TARGET_ARM) || defined(TARGET_ARM64) +#elif defined(TARGET_ARM) || defined(TARGET_ARM64) || defined(TARGET_LOONGARCH64) //Save the method code from hotRegion memcpy(saveAddr, (BYTE*)methodRegion.hotStartAddress, methodRegion.hotSize); @@ -662,7 +668,7 @@ void GCCoverageInfo::SprinkleBreakpoints( #endif // TARGET_X86 } -#if defined(TARGET_ARM) || defined(TARGET_ARM64) +#if defined(TARGET_ARM) || defined(TARGET_ARM64) || defined(TARGET_LOONGARCH64) #ifdef PARTIALLY_INTERRUPTIBLE_GC_SUPPORTED @@ -749,6 +755,20 @@ void replaceSafePointInstructionWithGcStressInstr(UINT32 safePointOffset, LPVOID { instructionIsACallThroughRegister = TRUE; } +#elif defined(TARGET_LOONGARCH64) + DWORD instr = *((DWORD*)savedInstrPtr - 1); + + // Is the call through a register or an immediate offset + // bl + if (((instr >> 26) & 0x3F) == 0x15) + { + instructionIsACallThroughImmediate = TRUE; + } + // jirl + else if (((instr >> 26) & 0x3F) == 0x13) + { + instructionIsACallThroughRegister = TRUE; + } #endif // _TARGET_XXXX_ // safe point must always be after a call instruction @@ -772,7 +792,7 @@ void replaceSafePointInstructionWithGcStressInstr(UINT32 safePointOffset, LPVOID // safe point will be replaced with appropriate illegal instruction at execution time when reg value is known #if defined(TARGET_ARM) *((WORD*)instrPtrWriterHolder.GetRW()) = INTERRUPT_INSTR_CALL; -#elif defined(TARGET_ARM64) +#elif defined(TARGET_ARM64) || defined(TARGET_LOONGARCH64) *((DWORD*)instrPtrWriterHolder.GetRW()) = INTERRUPT_INSTR_CALL; #endif // _TARGET_XXXX_ } @@ -893,7 +913,7 @@ bool replaceInterruptibleRangesWithGcStressInstr (UINT32 startOffset, UINT32 sto } instrPtrRW += instrLen; -#elif defined(TARGET_ARM64) +#elif defined(TARGET_ARM64) || defined(TARGET_LOONGARCH64) *((DWORD*)instrPtrRW) = INTERRUPT_INSTR; instrPtrRW += 4; #endif // TARGET_XXXX_ @@ -975,6 +995,26 @@ static PBYTE getTargetOfCall(PBYTE instrPtr, PCONTEXT regs, PBYTE* nextInstr) { { return 0; // Fail } +#elif defined(TARGET_LOONGARCH64) + if ((((*reinterpret_cast(instrPtr)) >> 26) & 0x3F) == 0x15) + { + int imm26 = (((*reinterpret_cast(instrPtr)) & 0x3ff) << 16) | (((*reinterpret_cast(instrPtr)) >> 10) & 0xffff); + *nextInstr = instrPtr + 4; + return PC + imm26; + } + else if ((((*reinterpret_cast(instrPtr)) >> 26) & 0x3F) == 0x13) + { + // call through register + *nextInstr = instrPtr + 4; + + assert((((*reinterpret_cast(instrPtr)) >> 10) & 0xffff) == 0); + unsigned int regnum = ((*reinterpret_cast(instrPtr)) >> 5) & 0x1F; + return (BYTE *)getRegVal(regnum, regs); + } + else + { + return 0; // Fail + } #endif #ifdef TARGET_AMD64 @@ -1214,6 +1254,8 @@ void RemoveGcCoverageInterrupt(TADDR instrPtr, BYTE * savedInstrPtr, GCCoverageI *(DWORD *)instrPtrWriterHolder.GetRW() = *(DWORD *)savedInstrPtr; #elif defined(TARGET_ARM64) *(DWORD *)instrPtrWriterHolder.GetRW() = *(DWORD *)savedInstrPtr; +#elif defined(TARGET_LOONGARCH64) + *(DWORD *)instrPtrWriterHolder.GetRW() = *(DWORD *)savedInstrPtr; #else *(BYTE *)instrPtrWriterHolder.GetRW() = *savedInstrPtr; #endif @@ -1428,6 +1470,12 @@ void DoGcStress (PCONTEXT regs, NativeCodeVersion nativeCodeVersion) atCall = (instrVal == INTERRUPT_INSTR_CALL); afterCallProtect[0] = (instrVal == INTERRUPT_INSTR_PROTECT_RET); +#elif defined(TARGET_LOONGARCH64) + DWORD instrVal = *(DWORD *)instrPtr; + forceStack[6] = &instrVal; // This is so I can see it fastchecked + + atCall = (instrVal == INTERRUPT_INSTR_CALL); + afterCallProtect[0] = (instrVal == INTERRUPT_INSTR_PROTECT_RET); #endif // _TARGET_* if (!IsGcCoverageInterruptInstruction(instrPtr)) @@ -1546,7 +1594,7 @@ void DoGcStress (PCONTEXT regs, NativeCodeVersion nativeCodeVersion) } #endif // TARGET_X86 -#if defined(TARGET_X86) || defined(TARGET_AMD64) || defined(TARGET_ARM) || defined(TARGET_ARM64) +#if defined(TARGET_X86) || defined(TARGET_AMD64) || defined(TARGET_ARM) || defined(TARGET_ARM64) || defined(TARGET_LOONGARCH64) /* In non-fully interruptible code, if the EIP is just after a call instr means something different because it expects that we are IN the @@ -1600,6 +1648,8 @@ void DoGcStress (PCONTEXT regs, NativeCodeVersion nativeCodeVersion) *(DWORD*)nextInstrWriterHolder.GetRW() = INTERRUPT_INSTR_32; #elif defined(TARGET_ARM64) *(DWORD*)nextInstrWriterHolder.GetRW() = INTERRUPT_INSTR; +#elif defined(TARGET_LOONGARCH64) + *(DWORD*)nextInstrWriterHolder.GetRW() = INTERRUPT_INSTR; #else *nextInstrWriterHolder.GetRW() = INTERRUPT_INSTR; #endif @@ -1681,6 +1731,8 @@ void DoGcStress (PCONTEXT regs, NativeCodeVersion nativeCodeVersion) retValRegs[numberOfRegs++] = regs->R0; #elif defined(TARGET_ARM64) retValRegs[numberOfRegs++] = regs->X0; +#elif defined(TARGET_LOONGARCH64) + retValRegs[numberOfRegs++] = regs->A0; #endif // TARGET_ARM64 } @@ -1732,6 +1784,8 @@ void DoGcStress (PCONTEXT regs, NativeCodeVersion nativeCodeVersion) regs->R0 = retValRegs[0]; #elif defined(TARGET_ARM64) regs->X[0] = retValRegs[0]; +#elif defined(TARGET_LOONGARCH64) + regs->A0 = retValRegs[0]; #else PORTABILITY_ASSERT("DoGCStress - return register"); #endif diff --git a/src/coreclr/vm/gccover.h b/src/coreclr/vm/gccover.h index abd07de2cda0a..35207372671d7 100644 --- a/src/coreclr/vm/gccover.h +++ b/src/coreclr/vm/gccover.h @@ -105,6 +105,13 @@ typedef DPTR(GCCoverageInfo) PTR_GCCoverageInfo; // see code:GCCoverageInfo::sav #define INTERRUPT_INSTR_CALL 0xBADC0DE1 #define INTERRUPT_INSTR_PROTECT_RET 0xBADC0DE2 +#elif defined(TARGET_LOONGARCH64) + +// The following encodings are undefined. +#define INTERRUPT_INSTR 0xffffff0f +#define INTERRUPT_INSTR_CALL 0xffffff0e +#define INTERRUPT_INSTR_PROTECT_RET 0xffffff0d + #endif // _TARGET_* // The body of this method is in this header file to allow @@ -124,6 +131,18 @@ inline bool IsGcCoverageInterruptInstructionVal(UINT32 instrVal) return false; } +#elif defined(TARGET_LOONGARCH64) + + switch (instrVal) + { + case INTERRUPT_INSTR: + case INTERRUPT_INSTR_CALL: + case INTERRUPT_INSTR_PROTECT_RET: + return true; + default: + return false; + } + #elif defined(TARGET_ARM) UINT16 instrVal16 = static_cast(instrVal); diff --git a/src/coreclr/vm/gcenv.ee.cpp b/src/coreclr/vm/gcenv.ee.cpp index 2085ff2a1024b..1e206321b4581 100644 --- a/src/coreclr/vm/gcenv.ee.cpp +++ b/src/coreclr/vm/gcenv.ee.cpp @@ -927,7 +927,7 @@ void GCToEEInterface::StompWriteBarrier(WriteBarrierParameters* args) // On architectures with strong ordering, we only need to prevent compiler reordering. // Otherwise we put a process-wide fence here (so that we could use an ordinary read in the barrier) -#if defined(HOST_ARM64) || defined(HOST_ARM) +#if defined(HOST_ARM64) || defined(HOST_ARM) || defined(HOST_LOONGARCH64) if (!is_runtime_suspended) { // If runtime is not suspended, force all threads to see the changed table before seeing updated heap boundaries. @@ -939,7 +939,7 @@ void GCToEEInterface::StompWriteBarrier(WriteBarrierParameters* args) g_lowest_address = args->lowest_address; g_highest_address = args->highest_address; -#if defined(HOST_ARM64) || defined(HOST_ARM) +#if defined(HOST_ARM64) || defined(HOST_ARM) || defined(HOST_LOONGARCH64) // Need to reupdate for changes to g_highest_address g_lowest_address stompWBCompleteActions |= ::StompWriteBarrierResize(is_runtime_suspended, args->requires_upper_bounds_check); @@ -979,7 +979,7 @@ void GCToEEInterface::StompWriteBarrier(WriteBarrierParameters* args) // (we care only about managed threads and suspend/resume will do full fences - good enough for us). // -#if defined(HOST_ARM64) || defined(HOST_ARM) +#if defined(HOST_ARM64) || defined(HOST_ARM) || defined(HOST_LOONGARCH64) is_runtime_suspended = (stompWBCompleteActions & SWB_EE_RESTART) || is_runtime_suspended; if (!is_runtime_suspended) { diff --git a/src/coreclr/vm/gcinfodecoder.cpp b/src/coreclr/vm/gcinfodecoder.cpp index 7914aacdf50db..9018c66b11322 100644 --- a/src/coreclr/vm/gcinfodecoder.cpp +++ b/src/coreclr/vm/gcinfodecoder.cpp @@ -132,7 +132,7 @@ GcInfoDecoder::GcInfoDecoder( int hasStackBaseRegister = headerFlags & GC_INFO_HAS_STACK_BASE_REGISTER; #ifdef TARGET_AMD64 m_WantsReportOnlyLeaf = ((headerFlags & GC_INFO_WANTS_REPORT_ONLY_LEAF) != 0); -#elif defined(TARGET_ARM) || defined(TARGET_ARM64) +#elif defined(TARGET_ARM) || defined(TARGET_ARM64) || defined(TARGET_LOONGARCH64) m_HasTailCalls = ((headerFlags & GC_INFO_HAS_TAILCALLS) != 0); #endif // TARGET_AMD64 int hasSizeOfEditAndContinuePreservedArea = headerFlags & GC_INFO_HAS_EDIT_AND_CONTINUE_PRESERVED_SLOTS; @@ -363,7 +363,7 @@ bool GcInfoDecoder::IsSafePoint(UINT32 codeOffset) if(m_NumSafePoints == 0) return false; -#if defined(TARGET_AMD64) || defined(TARGET_ARM) || defined(TARGET_ARM64) +#if defined(TARGET_AMD64) || defined(TARGET_ARM) || defined(TARGET_ARM64) || defined(TARGET_LOONGARCH64) // Safepoints are encoded with a -1 adjustment codeOffset--; #endif @@ -383,7 +383,7 @@ UINT32 GcInfoDecoder::FindSafePoint(UINT32 breakOffset) const UINT32 numBitsPerOffset = CeilOfLog2(NORMALIZE_CODE_OFFSET(m_CodeLength)); UINT32 result = m_NumSafePoints; -#if defined(TARGET_ARM) || defined(TARGET_ARM64) +#if defined(TARGET_ARM) || defined(TARGET_ARM64) || defined(TARGET_LOONGARCH64) // Safepoints are encoded with a -1 adjustment // but normalizing them masks off the low order bit // Thus only bother looking if the address is odd @@ -430,7 +430,7 @@ void GcInfoDecoder::EnumerateSafePoints(EnumerateSafePointsCallback *pCallback, UINT32 normOffset = (UINT32)m_Reader.Read(numBitsPerOffset); UINT32 offset = DENORMALIZE_CODE_OFFSET(normOffset) + 2; -#if defined(TARGET_AMD64) || defined(TARGET_ARM) || defined(TARGET_ARM64) +#if defined(TARGET_AMD64) || defined(TARGET_ARM) || defined(TARGET_ARM64) || defined(TARGET_LOONGARCH64) // Safepoints are encoded with a -1 adjustment offset--; #endif @@ -522,13 +522,13 @@ bool GcInfoDecoder::GetIsVarArg() return m_IsVarArg; } -#if defined(TARGET_ARM) || defined(TARGET_ARM64) +#if defined(TARGET_ARM) || defined(TARGET_ARM64) || defined(TARGET_LOONGARCH64) bool GcInfoDecoder::HasTailCalls() { _ASSERTE( m_Flags & DECODE_HAS_TAILCALLS ); return m_HasTailCalls; } -#endif // TARGET_ARM || TARGET_ARM64 +#endif // TARGET_ARM || TARGET_ARM64 || TARGET_LOONGARCH64 bool GcInfoDecoder::WantsReportOnlyLeaf() { @@ -1744,6 +1744,133 @@ OBJECTREF* GcInfoDecoder::GetCapturedRegister( } #endif // TARGET_UNIX && !FEATURE_REDHAWK +#elif defined(TARGET_LOONGARCH64) + +#if defined(TARGET_UNIX) && !defined(FEATURE_REDHAWK) +OBJECTREF* GcInfoDecoder::GetCapturedRegister( + int regNum, + PREGDISPLAY pRD + ) +{ + _ASSERTE(regNum >= 1 && regNum <= 31); + + // The fields of CONTEXT are in the same order as + // the processor encoding numbers. + + DWORD64 *pR0 = &pRD->pCurrentContext->R0; + + return (OBJECTREF*)(pR0 + regNum); +} +#endif // TARGET_UNIX && !FEATURE_REDHAWK + +OBJECTREF* GcInfoDecoder::GetRegisterSlot( + int regNum, + PREGDISPLAY pRD + ) +{ + _ASSERTE((regNum == 1) || (regNum >= 4 && regNum <= 31)); + +#ifdef FEATURE_REDHAWK + PTR_UIntNative* ppReg = &pRD->pR0; + + return (OBJECTREF*)*(ppReg + regNum); +#else + if(regNum == 1) + { + return (OBJECTREF*) pRD->pCurrentContextPointers->Ra; + } + else if(regNum == 22) + { + return (OBJECTREF*) pRD->pCurrentContextPointers->Fp; + } + else if (regNum < 22) + { + return (OBJECTREF*)*(DWORD64**)(&pRD->volatileCurrContextPointers.A0 + (regNum - 4));//A0=4. + } + + return (OBJECTREF*)*(DWORD64**)(&pRD->pCurrentContextPointers->S0 + (regNum-23)); +#endif +} + +bool GcInfoDecoder::IsScratchRegister(int regNum, PREGDISPLAY pRD) +{ + _ASSERTE(regNum >= 0 && regNum <= 31); + + return (regNum <= 21 && regNum >= 4); +} + +bool GcInfoDecoder::IsScratchStackSlot(INT32 spOffset, GcStackSlotBase spBase, PREGDISPLAY pRD) +{ +#ifdef FIXED_STACK_PARAMETER_SCRATCH_AREA + _ASSERTE( m_Flags & DECODE_GC_LIFETIMES ); + + TADDR pSlot = (TADDR) GetStackSlot(spOffset, spBase, pRD); + _ASSERTE(pSlot >= pRD->SP); + + return (pSlot < pRD->SP + m_SizeOfStackOutgoingAndScratchArea); +#else + return FALSE; +#endif +} + +void GcInfoDecoder::ReportRegisterToGC( + int regNum, + unsigned gcFlags, + PREGDISPLAY pRD, + unsigned flags, + GCEnumCallback pCallBack, + void * hCallBack) +{ + GCINFODECODER_CONTRACT; + + _ASSERTE(regNum > 0 && regNum <= 31); + + LOG((LF_GCROOTS, LL_INFO1000, "Reporting " FMT_REG, regNum )); + + OBJECTREF* pObjRef = GetRegisterSlot( regNum, pRD ); +#if defined(TARGET_UNIX) && !defined(FEATURE_REDHAWK) && !defined(SOS_TARGET_AMD64) + + // On PAL, we don't always have the context pointers available due to + // a limitation of an unwinding library. In such case, the context + // pointers for some nonvolatile registers are NULL. + // In such case, we let the pObjRef point to the captured register + // value in the context and pin the object itself. + if (pObjRef == NULL) + { + // Report a pinned object to GC only in the promotion phase when the + // GC is scanning roots. + GCCONTEXT* pGCCtx = (GCCONTEXT*)(hCallBack); + if (!pGCCtx->sc->promotion) + { + return; + } + + pObjRef = GetCapturedRegister(regNum, pRD); + + gcFlags |= GC_CALL_PINNED; + } +#endif // TARGET_UNIX && !SOS_TARGET_ARM64 + +#ifdef _DEBUG + if(IsScratchRegister(regNum, pRD)) + { + // Scratch registers cannot be reported for non-leaf frames + _ASSERTE(flags & ActiveStackFrame); + } + + LOG((LF_GCROOTS, LL_INFO1000, /* Part Two */ + "at" FMT_ADDR "as ", DBG_ADDR(pObjRef) )); + + VALIDATE_ROOT((gcFlags & GC_CALL_INTERIOR), hCallBack, pObjRef); + + LOG_PIPTR(pObjRef, gcFlags, hCallBack); +#endif //_DEBUG + + gcFlags |= CHECK_APP_DOMAIN; + + pCallBack(hCallBack, pObjRef, gcFlags DAC_ARG(DacSlotLocation(regNum, 0, false))); +} + #else // Unknown platform OBJECTREF* GcInfoDecoder::GetRegisterSlot( @@ -1829,6 +1956,8 @@ int GcInfoDecoder::GetStackReg(int spBase) int esp = 13; #elif defined(TARGET_ARM64) int esp = 31; +#elif defined(TARGET_LOONGARCH64) + int esp = 3; #endif if( GC_SP_REL == spBase ) diff --git a/src/coreclr/vm/gdbjit.cpp b/src/coreclr/vm/gdbjit.cpp index 3f20e5c49a9b0..d1939273ada7b 100644 --- a/src/coreclr/vm/gdbjit.cpp +++ b/src/coreclr/vm/gdbjit.cpp @@ -3672,6 +3672,8 @@ Elf64_Ehdr::Elf64_Ehdr() e_machine = EM_X86_64; #elif defined(TARGET_ARM64) e_machine = EM_AARCH64; +#elif defined(TARGET_LOONGARCH64) + e_machine = EM_LOONGARCH; #endif e_flags = 0; e_version = 1; diff --git a/src/coreclr/vm/interpreter.cpp b/src/coreclr/vm/interpreter.cpp index 82f6fad4a69b8..0200c71a4887b 100644 --- a/src/coreclr/vm/interpreter.cpp +++ b/src/coreclr/vm/interpreter.cpp @@ -91,7 +91,7 @@ InterpreterMethodInfo::InterpreterMethodInfo(CEEInfo* comp, CORINFO_METHOD_INFO* } #endif -#if defined(UNIX_AMD64_ABI) +#if defined(UNIX_AMD64_ABI) || defined(HOST_LOONGARCH64) // ...or it fits into two registers. if (hasRetBuff && getClassSize(methInfo->args.retTypeClass) <= 2 * sizeof(void*)) { @@ -534,6 +534,9 @@ void Interpreter::ArgState::AddArg(unsigned canonIndex, short numSlots, bool noR // On ARM, args are pushed in *reverse* order. So we will create an offset relative to the address // of the first stack arg; later, we will add the size of the non-stack arguments. ClrSafeInt offset(callerArgStackSlots); +#elif defined(HOST_LOONGARCH64) + callerArgStackSlots += numSlots; + ClrSafeInt offset(-callerArgStackSlots); #endif offset *= static_cast(sizeof(void*)); _ASSERTE(!offset.IsOverflow()); @@ -685,6 +688,19 @@ void Interpreter::ArgState::AddFPArg(unsigned canonIndex, unsigned short numSlot } numFPRegArgSlots += numSlots; +#elif defined(HOST_LOONGARCH64) + + assert(numFPRegArgSlots + numSlots <= MaxNumFPRegArgSlots); + assert(!twoSlotAlign); + argIsReg[canonIndex] = ARS_FloatReg; + + argOffsets[canonIndex] = numFPRegArgSlots * sizeof(void*); + for (unsigned i = 0; i < numSlots; i++) + { + fpArgsUsed |= (0x1 << (numFPRegArgSlots + i)); + } + numFPRegArgSlots += numSlots; + #else #error "Unsupported architecture" #endif @@ -882,7 +898,6 @@ CorJitResult Interpreter::GenerateInterpreterStub(CEEInfo* comp, // x8 through x15 are scratch registers on ARM64. IntReg x8 = IntReg(8); IntReg x9 = IntReg(9); -#else #error unsupported platform #endif } @@ -1110,7 +1125,7 @@ CorJitResult Interpreter::GenerateInterpreterStub(CEEInfo* comp, #elif defined(HOST_ARM) // LONGS have 2-reg alignment; inc reg if necessary. argState.AddArg(k, 2, /*noReg*/false, /*twoSlotAlign*/true); -#elif defined(HOST_AMD64) || defined(HOST_ARM64) +#elif defined(HOST_AMD64) || defined(HOST_ARM64) || defined(HOST_LOONGARCH64) argState.AddArg(k); #else #error unknown platform @@ -1123,7 +1138,7 @@ CorJitResult Interpreter::GenerateInterpreterStub(CEEInfo* comp, argState.AddArg(k, 1, /*noReg*/true); #elif defined(HOST_ARM) argState.AddFPArg(k, 1, /*twoSlotAlign*/false); -#elif defined(HOST_AMD64) || defined(HOST_ARM64) +#elif defined(HOST_AMD64) || defined(HOST_ARM64) || defined(HOST_LOONGARCH64) argState.AddFPArg(k, 1, false); #else #error unknown platform @@ -1136,7 +1151,7 @@ CorJitResult Interpreter::GenerateInterpreterStub(CEEInfo* comp, argState.AddArg(k, 2, /*noReg*/true); #elif defined(HOST_ARM) argState.AddFPArg(k, 2, /*twoSlotAlign*/true); -#elif defined(HOST_AMD64) || defined(HOST_ARM64) +#elif defined(HOST_AMD64) || defined(HOST_ARM64) || defined(HOST_LOONGARCH64) argState.AddFPArg(k, 1, false); #else #error unknown platform @@ -1177,6 +1192,8 @@ CorJitResult Interpreter::GenerateInterpreterStub(CEEInfo* comp, #endif ); } +#elif defined(HOST_LOONGARCH64) + argState.AddArg(k, static_cast(szSlots)); #else #error unknown platform #endif @@ -1229,6 +1246,10 @@ CorJitResult Interpreter::GenerateInterpreterStub(CEEInfo* comp, unsigned short stackArgBaseOffset = (2 + argState.numRegArgs + argState.numFPRegArgSlots) * sizeof(void*); #elif defined(HOST_AMD64) unsigned short stackArgBaseOffset = (argState.numRegArgs) * sizeof(void*); +#elif defined(HOST_LOONGARCH64) + // See StubLinkerCPU::EmitProlog for the layout of the stack + unsigned intRegArgBaseOffset = (argState.numFPRegArgSlots) * sizeof(void*); + unsigned short stackArgBaseOffset = (unsigned short) ((argState.numRegArgs + argState.numFPRegArgSlots) * sizeof(void*)); #else #error unsupported platform #endif @@ -1277,6 +1298,8 @@ CorJitResult Interpreter::GenerateInterpreterStub(CEEInfo* comp, X86Reg argRegs[] = { kECX, kEDX, kR8, kR9 }; if (!jmpCall) { sl.X86EmitIndexRegStoreRSP(regArgsFound * sizeof(void*), argRegs[regArgsFound - 1]); } argState.argOffsets[k] = (regArgsFound - 1) * sizeof(void*); +#elif defined(HOST_LOONGARCH64) + argState.argOffsets[k] += intRegArgBaseOffset; #else #error unsupported platform #endif @@ -1589,6 +1612,8 @@ CorJitResult Interpreter::GenerateInterpreterStub(CEEInfo* comp, sl.EmitEpilog(); +#elif defined(HOST_LOONGARCH64) + assert(!"unimplemented on LOONGARCH yet"); #else #error unsupported platform @@ -6280,6 +6305,9 @@ void Interpreter::MkRefany() #elif defined(HOST_ARM64) tbr = NULL; NYI_INTERP("Unimplemented code: MkRefAny"); +#elif defined(HOST_LOONGARCH64) + tbr = NULL; + NYI_INTERP("Unimplemented code: MkRefAny on LOONGARCH"); #else #error "unsupported platform" #endif @@ -9424,6 +9452,8 @@ void Interpreter::DoCallWork(bool virtualCall, void* thisArg, CORINFO_RESOLVED_T unsigned totalArgSlots = nSlots + HFAReturnArgSlots; #elif defined(HOST_AMD64) unsigned totalArgSlots = nSlots; +#elif defined(HOST_LOONGARCH64) + unsigned totalArgSlots = nSlots; #else #error "unsupported platform" #endif diff --git a/src/coreclr/vm/interpreter.h b/src/coreclr/vm/interpreter.h index 041484855c4dc..7856f97fbeba8 100644 --- a/src/coreclr/vm/interpreter.h +++ b/src/coreclr/vm/interpreter.h @@ -1002,6 +1002,8 @@ class Interpreter #else static const int MaxNumFPRegArgSlots = 4; #endif +#elif defined(HOST_LOONGARCH64) + static const int MaxNumFPRegArgSlots = 8; #endif ~ArgState() @@ -2062,6 +2064,8 @@ unsigned short Interpreter::NumberOfIntegerRegArgs() unsigned short Interpreter::NumberOfIntegerRegArgs() { return 4; } #elif defined(HOST_ARM64) unsigned short Interpreter::NumberOfIntegerRegArgs() { return 8; } +#elif defined(HOST_LOONGARCH64) +unsigned short Interpreter::NumberOfIntegerRegArgs() { return 8; } #else #error Unsupported architecture. #endif diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index 73609a9e009b2..fdea3ad3c8725 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -9480,434 +9480,11 @@ uint32_t CEEInfo::getLoongArch64PassStructInRegisterFlags(CORINFO_CLASS_HANDLE c JIT_TO_EE_TRANSITION_LEAF(); - TypeHandle th(cls); - - bool useNativeLayout = false; uint32_t size = STRUCT_NO_FLOAT_FIELD; - MethodTable* pMethodTable = nullptr; - - if (!th.IsTypeDesc()) - { - pMethodTable = th.AsMethodTable(); - if (pMethodTable->HasLayout()) - useNativeLayout = true; - else if (th.GetSize() <= 16 /*MAX_PASS_MULTIREG_BYTES*/) - { - DWORD numIntroducedFields = pMethodTable->GetNumIntroducedInstanceFields(); - - if (numIntroducedFields == 1) - { - FieldDesc *pFieldStart = pMethodTable->GetApproxFieldDescListRaw(); - - CorElementType fieldType = pFieldStart[0].GetFieldType(); - - if (CorTypeInfo::IsPrimitiveType_NoThrow(fieldType)) - { - if ((fieldType == ELEMENT_TYPE_R4) || (fieldType == ELEMENT_TYPE_R8)) - size = STRUCT_FLOAT_FIELD_ONLY_ONE; - } - else if (fieldType == ELEMENT_TYPE_VALUETYPE) - { - pMethodTable = pFieldStart->GetFieldTypeHandleThrowing().GetMethodTable(); - if (pMethodTable->GetNumIntroducedInstanceFields() == 1) - { - size = getLoongArch64PassStructInRegisterFlags((CORINFO_CLASS_HANDLE)pMethodTable); - } - else if (pMethodTable->GetNumIntroducedInstanceFields() == 2) - { - size = getLoongArch64PassStructInRegisterFlags((CORINFO_CLASS_HANDLE)pMethodTable); - } - } - } - else if (numIntroducedFields == 2) - { - FieldDesc *pFieldStart = pMethodTable->GetApproxFieldDescListRaw(); - - CorElementType fieldType = pFieldStart[0].GetFieldType(); - if (CorTypeInfo::IsPrimitiveType_NoThrow(fieldType)) - { - if (fieldType == ELEMENT_TYPE_R4) - size = STRUCT_FLOAT_FIELD_FIRST; - else if (fieldType == ELEMENT_TYPE_R8) - size = STRUCT_FIRST_FIELD_DOUBLE; - else if (pFieldStart[0].GetSize() == 8) - size = STRUCT_FIRST_FIELD_SIZE_IS8; - - } - else if (fieldType == ELEMENT_TYPE_VALUETYPE) - { - pMethodTable = pFieldStart->GetFieldTypeHandleThrowing().GetMethodTable(); - if (pMethodTable->GetNumIntroducedInstanceFields() == 1) - { - size = getLoongArch64PassStructInRegisterFlags((CORINFO_CLASS_HANDLE)pMethodTable); - if (size == STRUCT_FLOAT_FIELD_ONLY_ONE) - { - size = pFieldStart[0].GetSize() == 8 ? STRUCT_FIRST_FIELD_DOUBLE : STRUCT_FLOAT_FIELD_FIRST; - } - else if (size == STRUCT_NO_FLOAT_FIELD) - { - size = pFieldStart[0].GetSize() == 8 ? STRUCT_FIRST_FIELD_SIZE_IS8: 0; - } - else - { - size = STRUCT_NO_FLOAT_FIELD; - goto _End_arg; - } - } - else - { - size = STRUCT_NO_FLOAT_FIELD; - goto _End_arg; - } - } - else if (pFieldStart[0].GetSize() == 8) - size = STRUCT_FIRST_FIELD_SIZE_IS8; - - fieldType = pFieldStart[1].GetFieldType(); - if (CorTypeInfo::IsPrimitiveType_NoThrow(fieldType)) - { - if (fieldType == ELEMENT_TYPE_R4) - size = size & STRUCT_FLOAT_FIELD_FIRST ? (size ^ STRUCT_MERGE_FIRST_SECOND) : (size | STRUCT_FLOAT_FIELD_SECOND); - else if (fieldType == ELEMENT_TYPE_R8) - size = size & STRUCT_FLOAT_FIELD_FIRST ? (size ^ STRUCT_MERGE_FIRST_SECOND_8) : (size | STRUCT_SECOND_FIELD_DOUBLE); - else if (pFieldStart[1].GetSize() == 8) - size |= STRUCT_SECOND_FIELD_SIZE_IS8; - } - else if (fieldType == ELEMENT_TYPE_VALUETYPE) - { - pMethodTable = pFieldStart[1].GetFieldTypeHandleThrowing().GetMethodTable(); - if (pMethodTable->GetNumIntroducedInstanceFields() == 1) - { - DWORD size2 = getLoongArch64PassStructInRegisterFlags((CORINFO_CLASS_HANDLE)pMethodTable); - if (size2 == STRUCT_FLOAT_FIELD_ONLY_ONE) - { - if (pFieldStart[1].GetSize() == 8) - size = size & STRUCT_FLOAT_FIELD_FIRST ? (size ^ STRUCT_MERGE_FIRST_SECOND_8) : (size | STRUCT_SECOND_FIELD_DOUBLE); - else - size = size & STRUCT_FLOAT_FIELD_FIRST ? (size ^ STRUCT_MERGE_FIRST_SECOND) : (size | STRUCT_FLOAT_FIELD_SECOND); - } - else if (size2 == 0) - { - size |= pFieldStart[1].GetSize() == 8 ? STRUCT_SECOND_FIELD_SIZE_IS8 : 0; - } - else - { - size = STRUCT_NO_FLOAT_FIELD; - goto _End_arg; - } - } - else - { - size = STRUCT_NO_FLOAT_FIELD; - goto _End_arg; - } - } - else if (pFieldStart[1].GetSize() == 8) - size |= STRUCT_SECOND_FIELD_SIZE_IS8; - } - goto _End_arg; - } - } - else - { - _ASSERTE(th.IsNativeValueType()); - - useNativeLayout = true; - pMethodTable = th.AsNativeValueType(); - } - _ASSERTE(pMethodTable != nullptr); - - if (useNativeLayout) - { - if (th.GetSize() <= 16 /*MAX_PASS_MULTIREG_BYTES*/) - { - DWORD numIntroducedFields = pMethodTable->GetNumIntroducedInstanceFields(); - FieldDesc *pFieldStart = nullptr; - if (numIntroducedFields == 1) - { - pFieldStart = pMethodTable->GetApproxFieldDescListRaw(); - - CorElementType fieldType = pFieldStart->GetFieldType(); - - bool isFixedBuffer = (CorTypeInfo::IsPrimitiveType_NoThrow(fieldType) - || fieldType == ELEMENT_TYPE_VALUETYPE) - && (pFieldStart->GetOffset() == 0) - && pMethodTable->HasLayout() - && (pMethodTable->GetNumInstanceFieldBytes() % pFieldStart->GetSize() == 0); - - if (isFixedBuffer) - { - numIntroducedFields = pMethodTable->GetNumInstanceFieldBytes() / pFieldStart->GetSize(); - if (numIntroducedFields > 2) - goto _End_arg; - if (fieldType == ELEMENT_TYPE_R4) - { - if (numIntroducedFields == 1) - size = STRUCT_FLOAT_FIELD_ONLY_ONE; - else if (numIntroducedFields == 2) - size = STRUCT_FLOAT_FIELD_ONLY_TWO; - goto _End_arg; - } - else if (fieldType == ELEMENT_TYPE_R8) - { - if (numIntroducedFields == 1) - size = STRUCT_FLOAT_FIELD_ONLY_ONE; - else if (numIntroducedFields == 2) - size = STRUCT_FIELD_TWO_DOUBLES; - goto _End_arg; - } - } - - if (CorTypeInfo::IsPrimitiveType_NoThrow(fieldType)) - { - if ((fieldType == ELEMENT_TYPE_R4) || (fieldType == ELEMENT_TYPE_R8)) - size = STRUCT_FLOAT_FIELD_ONLY_ONE; - } - else if (fieldType == ELEMENT_TYPE_VALUETYPE) - { - const NativeFieldDescriptor *pNativeFieldDescs = pMethodTable->GetNativeLayoutInfo()->GetNativeFieldDescriptors(); - if (pNativeFieldDescs->GetCategory() == NativeFieldCategory::NESTED) - { - size = getLoongArch64PassStructInRegisterFlags((CORINFO_CLASS_HANDLE)pNativeFieldDescs->GetNestedNativeMethodTable()); - return size; - } - else if (pNativeFieldDescs->GetCategory() == NativeFieldCategory::FLOAT) - { - if (pNativeFieldDescs->NativeSize() == 4) - { - size = STRUCT_FLOAT_FIELD_ONLY_ONE; - } - else if (pNativeFieldDescs->NativeSize() == 8) - { - size = STRUCT_FLOAT_FIELD_ONLY_ONE; - } - else - { - UNREACHABLE_MSG("Invalid NativeFieldCategory.----LoongArch64----"); - } - } - else - { - pMethodTable = pNativeFieldDescs->GetNestedNativeMethodTable(); - if (pNativeFieldDescs->GetNumElements() == 1) - { - size = getLoongArch64PassStructInRegisterFlags((CORINFO_CLASS_HANDLE)pMethodTable); - } - else if (pNativeFieldDescs->GetNumElements() == 2) - { - size = getLoongArch64PassStructInRegisterFlags((CORINFO_CLASS_HANDLE)pMethodTable); - } - } - } - } - else if (numIntroducedFields == 2) - { - pFieldStart = pMethodTable->GetApproxFieldDescListRaw(); - - if (pFieldStart->GetOffset() || !pFieldStart[1].GetOffset() || (pFieldStart[0].GetSize() > pFieldStart[1].GetOffset())) - { - goto _End_arg; - } - - CorElementType fieldType = pFieldStart[0].GetFieldType(); - if (CorTypeInfo::IsPrimitiveType_NoThrow(fieldType)) - { - if (fieldType == ELEMENT_TYPE_R4) - size = STRUCT_FLOAT_FIELD_FIRST; - else if (fieldType == ELEMENT_TYPE_R8) - size = STRUCT_FIRST_FIELD_DOUBLE; - else if (pFieldStart[0].GetSize() == 8) - size = STRUCT_FIRST_FIELD_SIZE_IS8; - - fieldType = pFieldStart[1].GetFieldType(); - if (CorTypeInfo::IsPrimitiveType_NoThrow(fieldType)) - { - if (fieldType == ELEMENT_TYPE_R4) - size = size & STRUCT_FLOAT_FIELD_FIRST ? (size ^ STRUCT_MERGE_FIRST_SECOND) : (size | STRUCT_FLOAT_FIELD_SECOND); - else if (fieldType == ELEMENT_TYPE_R8) - size = size & STRUCT_FLOAT_FIELD_FIRST ? (size ^ STRUCT_MERGE_FIRST_SECOND_8) : (size | STRUCT_SECOND_FIELD_DOUBLE); - else if (pFieldStart[1].GetSize() == 8) - size |= STRUCT_SECOND_FIELD_SIZE_IS8; - goto _End_arg; - } - } - else if (fieldType == ELEMENT_TYPE_VALUETYPE) - { - const NativeFieldDescriptor *pNativeFieldDescs = pMethodTable->GetNativeLayoutInfo()->GetNativeFieldDescriptors(); - - if (pNativeFieldDescs->GetCategory() == NativeFieldCategory::NESTED) - { - MethodTable* pMethodTable2 = pNativeFieldDescs->GetNestedNativeMethodTable(); - - if ((pMethodTable2->GetNumInstanceFieldBytes() > 8) || (pMethodTable2->GetNumIntroducedInstanceFields() > 1)) - goto _End_arg; - size = getLoongArch64PassStructInRegisterFlags((CORINFO_CLASS_HANDLE)pMethodTable2); - if (size == STRUCT_FLOAT_FIELD_ONLY_ONE) - { - if (pFieldStart[0].GetSize() == 8) - size = STRUCT_FIRST_FIELD_DOUBLE; - else - size = STRUCT_FLOAT_FIELD_FIRST; - } - else if (pFieldStart[0].GetSize() == 8) - { - size = STRUCT_FIRST_FIELD_SIZE_IS8; - } - else - size = STRUCT_NO_FLOAT_FIELD; - } - else if (pNativeFieldDescs->GetCategory() == NativeFieldCategory::FLOAT) - { - if (pNativeFieldDescs->NativeSize() == 4) - { - size = STRUCT_FLOAT_FIELD_FIRST; - } - else if (pNativeFieldDescs->NativeSize() == 8) - { - size = STRUCT_FIRST_FIELD_DOUBLE; - } - else - { - UNREACHABLE_MSG("Invalid NativeFieldCategory.----LoongArch64----2"); - } - } - else - { - MethodTable* pMethodTable2 = pFieldStart[0].GetFieldTypeHandleThrowing().AsMethodTable(); - if (pMethodTable2->GetNumIntroducedInstanceFields() == 1) - { - size = getLoongArch64PassStructInRegisterFlags((CORINFO_CLASS_HANDLE)pMethodTable2); - if (size == STRUCT_FLOAT_FIELD_ONLY_ONE) - { - if (pFieldStart[0].GetSize() == 8) - size = STRUCT_FIRST_FIELD_DOUBLE; - else - size = STRUCT_FLOAT_FIELD_FIRST; - } - else if (pFieldStart[0].GetSize() == 8) - { - size = STRUCT_FIRST_FIELD_SIZE_IS8; - } - else - size = STRUCT_NO_FLOAT_FIELD; - } - else - goto _End_arg; - } - } - else if (pFieldStart[0].GetSize() == 8) - size = STRUCT_FIRST_FIELD_SIZE_IS8; - - fieldType = pFieldStart[1].GetFieldType(); - if (CorTypeInfo::IsPrimitiveType_NoThrow(fieldType)) - { - if (fieldType == ELEMENT_TYPE_R4) - size = size & STRUCT_FLOAT_FIELD_FIRST ? (size ^ STRUCT_MERGE_FIRST_SECOND) : (size | STRUCT_FLOAT_FIELD_SECOND); - else if (fieldType == ELEMENT_TYPE_R8) - size = size & STRUCT_FLOAT_FIELD_FIRST ? (size ^ STRUCT_MERGE_FIRST_SECOND_8) : (size | STRUCT_SECOND_FIELD_DOUBLE); - else if (pFieldStart[1].GetSize() == 8) - size |= STRUCT_SECOND_FIELD_SIZE_IS8; - } - else if (fieldType == ELEMENT_TYPE_VALUETYPE) - { - MethodTable* pMethodTable2 = pFieldStart[1].GetFieldTypeHandleThrowing().AsMethodTable(); - if ((pMethodTable2->GetNumInstanceFieldBytes() > 8) || (pMethodTable2->GetNumIntroducedInstanceFields() > 1)) - { - size = STRUCT_NO_FLOAT_FIELD; - goto _End_arg; - } - if (pMethodTable2->HasLayout()) - { - const NativeFieldDescriptor *pNativeFieldDescs = pMethodTable2->GetNativeLayoutInfo()->GetNativeFieldDescriptors(); - - if (pNativeFieldDescs->NativeSize() > 8) - { - size = STRUCT_NO_FLOAT_FIELD; - goto _End_arg; - } - - if (pNativeFieldDescs->GetCategory() == NativeFieldCategory::NESTED) - { - pMethodTable = pNativeFieldDescs->GetNestedNativeMethodTable(); - - if (pMethodTable->GetNumIntroducedInstanceFields() > 1) - { - size = STRUCT_NO_FLOAT_FIELD; - goto _End_arg; - } - - if (getLoongArch64PassStructInRegisterFlags((CORINFO_CLASS_HANDLE)pMethodTable) == STRUCT_FLOAT_FIELD_ONLY_ONE) - { - if (pMethodTable->GetNumInstanceFieldBytes() == 4) - size = size & STRUCT_FLOAT_FIELD_FIRST ? (size ^ STRUCT_MERGE_FIRST_SECOND) : (size | STRUCT_FLOAT_FIELD_SECOND); - else if (pMethodTable->GetNumInstanceFieldBytes() == 8) - size = size & STRUCT_FLOAT_FIELD_FIRST ? (size ^ STRUCT_MERGE_FIRST_SECOND_8) : (size | STRUCT_SECOND_FIELD_DOUBLE); - } - else if (pMethodTable->GetNumInstanceFieldBytes() == 8) - size |= STRUCT_SECOND_FIELD_SIZE_IS8; - else - { - size = STRUCT_NO_FLOAT_FIELD; - } - } - else if (pNativeFieldDescs->GetCategory() == NativeFieldCategory::FLOAT) - { - if (pNativeFieldDescs->NativeSize() == 4) - { - size = size & STRUCT_FLOAT_FIELD_FIRST ? (size ^ STRUCT_MERGE_FIRST_SECOND) : (size | STRUCT_FLOAT_FIELD_SECOND); - } - else if (pNativeFieldDescs->NativeSize() == 8) - { - size = size & STRUCT_FLOAT_FIELD_FIRST ? (size ^ STRUCT_MERGE_FIRST_SECOND_8) : (size | STRUCT_SECOND_FIELD_DOUBLE); - } - else - { - UNREACHABLE_MSG("Invalid NativeFieldCategory.----LoongArch64----3"); - } - } - else - { - if (pNativeFieldDescs->GetNumElements() == 1) - { - fieldType = pNativeFieldDescs->GetFieldDesc()[0].GetFieldType(); - if (fieldType == ELEMENT_TYPE_R4) - size = size & STRUCT_FLOAT_FIELD_FIRST ? (size ^ STRUCT_MERGE_FIRST_SECOND) : (size | STRUCT_FLOAT_FIELD_SECOND); - else if (fieldType == ELEMENT_TYPE_R8) - size = size & STRUCT_FLOAT_FIELD_FIRST ? (size ^ STRUCT_MERGE_FIRST_SECOND_8) : (size | STRUCT_SECOND_FIELD_DOUBLE); - else if ((size & STRUCT_FLOAT_FIELD_FIRST) == 0) - { - size = STRUCT_NO_FLOAT_FIELD; - goto _End_arg; - } - else if (pNativeFieldDescs->NativeSize() == 8) - size |= STRUCT_SECOND_FIELD_SIZE_IS8; - } - else - { - size = STRUCT_NO_FLOAT_FIELD; - } - } - } - else - { - if (getLoongArch64PassStructInRegisterFlags((CORINFO_CLASS_HANDLE)pMethodTable2) == 1) - { - if (pMethodTable2->GetNumInstanceFieldBytes() == 4) - size = size & STRUCT_FLOAT_FIELD_FIRST ? (size ^ STRUCT_MERGE_FIRST_SECOND) : (size | STRUCT_FLOAT_FIELD_SECOND); - else if (pMethodTable2->GetNumInstanceFieldBytes() == 8) - size = size & STRUCT_FLOAT_FIELD_FIRST ? (size ^ STRUCT_MERGE_FIRST_SECOND_8) : (size | STRUCT_SECOND_FIELD_DOUBLE); - } - else if (pMethodTable2->GetNumInstanceFieldBytes() == 8) - size |= STRUCT_SECOND_FIELD_SIZE_IS8; - } - } - else if (pFieldStart[1].GetSize() == 8) - size |= STRUCT_SECOND_FIELD_SIZE_IS8; - } - } - } -_End_arg: +#if defined(TARGET_LOONGARCH64) + size = (uint32_t)MethodTable::GetLoongArch64PassStructInRegisterFlags(cls); +#endif EE_TO_JIT_TRANSITION_LEAF(); @@ -11466,6 +11043,12 @@ void reservePersonalityRoutineSpace(uint32_t &unwindSize) // The JIT passes in a 4-byte aligned block of unwind data. _ASSERTE(IS_ALIGNED(unwindSize, sizeof(ULONG))); + // Add space for personality routine, it must be 4-byte aligned. + unwindSize += sizeof(ULONG); +#elif defined(TARGET_LOONGARCH64) + // The JIT passes in a 4-byte aligned block of unwind data. + _ASSERTE(IS_ALIGNED(unwindSize, sizeof(ULONG))); + // Add space for personality routine, it must be 4-byte aligned. unwindSize += sizeof(ULONG); #else @@ -11683,6 +11266,13 @@ void CEEJitInfo::allocUnwindInfo ( ULONG * pPersonalityRoutineRW = (ULONG*)((BYTE *)pUnwindInfoRW + ALIGN_UP(unwindSize, sizeof(ULONG))); *pPersonalityRoutineRW = (TADDR)ProcessCLRException - baseAddress; +#elif defined(TARGET_LOONGARCH64) + + *(LONG *)pUnwindInfoRW |= (1 << 20); // X bit + + ULONG * pPersonalityRoutineRW = (ULONG*)((BYTE *)pUnwindInfoRW + ALIGN_UP(unwindSize, sizeof(ULONG))); + *pPersonalityRoutineRW = ExecutionManager::GetCLRPersonalityRoutineValue(); + #endif EE_TO_JIT_TRANSITION(); diff --git a/src/coreclr/vm/jitinterface.h b/src/coreclr/vm/jitinterface.h index 87bab26590f68..349be865c8895 100644 --- a/src/coreclr/vm/jitinterface.h +++ b/src/coreclr/vm/jitinterface.h @@ -394,7 +394,7 @@ extern "C" void STDCALL JIT_MemCpy(void *dest, const void *src, SIZE_T count); void STDMETHODCALLTYPE JIT_ProfilerEnterLeaveTailcallStub(UINT_PTR ProfilerHandle); -#ifndef TARGET_ARM64 +#if !defined(TARGET_ARM64) && !defined(TARGET_LOONGARCH64) void STDCALL JIT_StackProbe(); #endif // TARGET_ARM64 }; diff --git a/src/coreclr/vm/loongarch64/asmconstants.h b/src/coreclr/vm/loongarch64/asmconstants.h new file mode 100644 index 0000000000000..4bd74d48f7b83 --- /dev/null +++ b/src/coreclr/vm/loongarch64/asmconstants.h @@ -0,0 +1,258 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +// asmconstants.h - +// +// This header defines field offsets and constants used by assembly code +// Be sure to rebuild clr/src/vm/ceemain.cpp after changing this file, to +// ensure that the constants match the expected C/C++ values + +// #ifndef HOST_LOONGARCH64 +// #error this file should only be used on an LOONGARCH platform +// #endif // HOST_LOONGARCH64 + +#include "../../inc/switches.h" + +//----------------------------------------------------------------------------- + +#ifndef ASMCONSTANTS_C_ASSERT +#define ASMCONSTANTS_C_ASSERT(cond) +#endif + +#ifndef ASMCONSTANTS_RUNTIME_ASSERT +#define ASMCONSTANTS_RUNTIME_ASSERT(cond) +#endif + +// Some contants are different in _DEBUG builds. This macro factors out ifdefs from below. +#ifdef _DEBUG +#define DBG_FRE(dbg,fre) dbg +#else +#define DBG_FRE(dbg,fre) fre +#endif + +#define DynamicHelperFrameFlags_Default 0 +#define DynamicHelperFrameFlags_ObjectArg 1 +#define DynamicHelperFrameFlags_ObjectArg2 2 + +#define Thread__m_fPreemptiveGCDisabled 0x0C +#define Thread__m_pFrame 0x10 + +ASMCONSTANTS_C_ASSERT(Thread__m_fPreemptiveGCDisabled == offsetof(Thread, m_fPreemptiveGCDisabled)); +ASMCONSTANTS_C_ASSERT(Thread__m_pFrame == offsetof(Thread, m_pFrame)); + +#define Thread_m_pFrame Thread__m_pFrame +#define Thread_m_fPreemptiveGCDisabled Thread__m_fPreemptiveGCDisabled + +#define METHODDESC_REGISTER t2 + +#define SIZEOF__ArgumentRegisters 0x40 +ASMCONSTANTS_C_ASSERT(SIZEOF__ArgumentRegisters == sizeof(ArgumentRegisters)) + +//8*8=0x40, f12-f19. +#define SIZEOF__FloatArgumentRegisters 0x40 +ASMCONSTANTS_C_ASSERT(SIZEOF__FloatArgumentRegisters == sizeof(FloatArgumentRegisters)) + +#define ASM_ENREGISTERED_RETURNTYPE_MAXSIZE 0x10 +ASMCONSTANTS_C_ASSERT(ASM_ENREGISTERED_RETURNTYPE_MAXSIZE == ENREGISTERED_RETURNTYPE_MAXSIZE) + +#define CallDescrData__pSrc 0x00 +#define CallDescrData__numStackSlots 0x08 +#define CallDescrData__pArgumentRegisters 0x10 +#define CallDescrData__pFloatArgumentRegisters 0x18 +#define CallDescrData__fpReturnSize 0x20 +#define CallDescrData__pTarget 0x28 +#define CallDescrData__returnValue 0x30 + +ASMCONSTANTS_C_ASSERT(CallDescrData__pSrc == offsetof(CallDescrData, pSrc)) +ASMCONSTANTS_C_ASSERT(CallDescrData__numStackSlots == offsetof(CallDescrData, numStackSlots)) +ASMCONSTANTS_C_ASSERT(CallDescrData__pArgumentRegisters == offsetof(CallDescrData, pArgumentRegisters)) +ASMCONSTANTS_C_ASSERT(CallDescrData__pFloatArgumentRegisters == offsetof(CallDescrData, pFloatArgumentRegisters)) +ASMCONSTANTS_C_ASSERT(CallDescrData__fpReturnSize == offsetof(CallDescrData, fpReturnSize)) +ASMCONSTANTS_C_ASSERT(CallDescrData__pTarget == offsetof(CallDescrData, pTarget)) +ASMCONSTANTS_C_ASSERT(CallDescrData__returnValue == offsetof(CallDescrData, returnValue)) + +#define CallDescrData__flagOneFloat 0x1 +#define CallDescrData__flagOneDouble 0x11 +#define CallDescrData__flagFloatInt 0x2 +#define CallDescrData__flagFloatLong 0x22 +#define CallDescrData__flagDoubleInt 0x12 +#define CallDescrData__flagDoubleLong 0x32 +#define CallDescrData__flagIntFloat 0x4 +#define CallDescrData__flagIntDouble 0x24 +#define CallDescrData__flagLongFloat 0x14 +#define CallDescrData__flagLongDouble 0x34 +#define CallDescrData__flagFloatFloat 0x8 +#define CallDescrData__flagFloatDouble 0x28 +#define CallDescrData__flagDoubleFloat 0x18 +#define CallDescrData__flagDoubleDouble 0x38 + +ASMCONSTANTS_C_ASSERT(CallDescrData__flagOneFloat == (int)STRUCT_FLOAT_FIELD_ONLY_ONE) +ASMCONSTANTS_C_ASSERT(CallDescrData__flagOneDouble == (int)(STRUCT_FLOAT_FIELD_ONLY_ONE | STRUCT_FIRST_FIELD_SIZE_IS8)) +ASMCONSTANTS_C_ASSERT(CallDescrData__flagFloatInt == (int)STRUCT_FLOAT_FIELD_FIRST) +ASMCONSTANTS_C_ASSERT(CallDescrData__flagFloatLong == (int)(STRUCT_FLOAT_FIELD_FIRST | STRUCT_SECOND_FIELD_SIZE_IS8)) +ASMCONSTANTS_C_ASSERT(CallDescrData__flagDoubleInt == (int)(STRUCT_FLOAT_FIELD_FIRST | STRUCT_FIRST_FIELD_SIZE_IS8)) +ASMCONSTANTS_C_ASSERT(CallDescrData__flagDoubleLong == (int)(CallDescrData__flagDoubleInt | STRUCT_SECOND_FIELD_SIZE_IS8)) +ASMCONSTANTS_C_ASSERT(CallDescrData__flagIntFloat == (int)STRUCT_FLOAT_FIELD_SECOND) +ASMCONSTANTS_C_ASSERT(CallDescrData__flagIntDouble == (int)(STRUCT_FLOAT_FIELD_SECOND | STRUCT_SECOND_FIELD_SIZE_IS8)) +ASMCONSTANTS_C_ASSERT(CallDescrData__flagLongFloat == (int)(STRUCT_FLOAT_FIELD_SECOND | STRUCT_FIRST_FIELD_SIZE_IS8)) +ASMCONSTANTS_C_ASSERT(CallDescrData__flagLongDouble == (int)(CallDescrData__flagLongFloat | STRUCT_SECOND_FIELD_SIZE_IS8)) +ASMCONSTANTS_C_ASSERT(CallDescrData__flagFloatFloat == (int)STRUCT_FLOAT_FIELD_ONLY_TWO) +ASMCONSTANTS_C_ASSERT(CallDescrData__flagFloatDouble == (int)(STRUCT_FLOAT_FIELD_ONLY_TWO | STRUCT_SECOND_FIELD_SIZE_IS8)) +ASMCONSTANTS_C_ASSERT(CallDescrData__flagDoubleFloat == (int)(STRUCT_FLOAT_FIELD_ONLY_TWO | STRUCT_FIRST_FIELD_SIZE_IS8)) +ASMCONSTANTS_C_ASSERT(CallDescrData__flagDoubleDouble == (int)(CallDescrData__flagDoubleFloat | STRUCT_SECOND_FIELD_SIZE_IS8)) + +#define CORINFO_NullReferenceException_ASM 0 +ASMCONSTANTS_C_ASSERT( CORINFO_NullReferenceException_ASM + == CORINFO_NullReferenceException); + + +#define CORINFO_IndexOutOfRangeException_ASM 3 +ASMCONSTANTS_C_ASSERT( CORINFO_IndexOutOfRangeException_ASM + == CORINFO_IndexOutOfRangeException); + + +// Offset of the array containing the address of captured registers in MachState +#define MachState__captureCalleeSavedRegisters 0x0 +ASMCONSTANTS_C_ASSERT(MachState__captureCalleeSavedRegisters == offsetof(MachState, captureCalleeSavedRegisters)) + +// Offset of the array containing the address of preserved registers in MachState +#define MachState__ptrCalleeSavedRegisters 0x58 +ASMCONSTANTS_C_ASSERT(MachState__ptrCalleeSavedRegisters == offsetof(MachState, ptrCalleeSavedRegisters)) + +#define MachState__isValid 0xc0 +ASMCONSTANTS_C_ASSERT(MachState__isValid == offsetof(MachState, _isValid)) + +#define LazyMachState_captureCalleeSavedRegisters MachState__captureCalleeSavedRegisters +ASMCONSTANTS_C_ASSERT(LazyMachState_captureCalleeSavedRegisters == offsetof(LazyMachState, captureCalleeSavedRegisters)) + +#define LazyMachState_captureSp (MachState__isValid+8) // padding for alignment +ASMCONSTANTS_C_ASSERT(LazyMachState_captureSp == offsetof(LazyMachState, captureSp)) + +#define LazyMachState_captureIp (LazyMachState_captureSp+8) +ASMCONSTANTS_C_ASSERT(LazyMachState_captureIp == offsetof(LazyMachState, captureIp)) + +#define VASigCookie__pNDirectILStub 0x8 +ASMCONSTANTS_C_ASSERT(VASigCookie__pNDirectILStub == offsetof(VASigCookie, pNDirectILStub)) + +#define DelegateObject___methodPtr 0x18 +ASMCONSTANTS_C_ASSERT(DelegateObject___methodPtr == offsetof(DelegateObject, _methodPtr)); + +#define DelegateObject___target 0x08 +ASMCONSTANTS_C_ASSERT(DelegateObject___target == offsetof(DelegateObject, _target)); + +#define SIZEOF__GSCookie 0x8 +ASMCONSTANTS_C_ASSERT(SIZEOF__GSCookie == sizeof(GSCookie)); + +#define SIZEOF__Frame 0x10 +ASMCONSTANTS_C_ASSERT(SIZEOF__Frame == sizeof(Frame)); + +#define SIZEOF__CONTEXT 0x220 +ASMCONSTANTS_C_ASSERT(SIZEOF__CONTEXT == sizeof(T_CONTEXT)); + +//========================================= +#define MethodTable__m_dwFlags 0x0 +ASMCONSTANTS_C_ASSERT(MethodTable__m_dwFlags == offsetof(MethodTable, m_dwFlags)); + +#define MethodTable__m_BaseSize 0x04 +ASMCONSTANTS_C_ASSERT(MethodTable__m_BaseSize == offsetof(MethodTable, m_BaseSize)); + +#define MethodTable__m_ElementType DBG_FRE(0x38, 0x30) +ASMCONSTANTS_C_ASSERT(MethodTable__m_ElementType == offsetof(MethodTable, m_pMultipurposeSlot1)); + +#define ArrayBase__m_NumComponents 0x8 +ASMCONSTANTS_C_ASSERT(ArrayBase__m_NumComponents == offsetof(ArrayBase, m_NumComponents)); + +#define PtrArray__m_Array 0x10 +ASMCONSTANTS_C_ASSERT(PtrArray__m_Array == offsetof(PtrArray, m_Array)); + +#define TypeHandle_CanCast 0x1 // TypeHandle::CanCast + +//========================================= + +#ifdef FEATURE_COMINTEROP + +#define SIZEOF__ComMethodFrame 0x70 +ASMCONSTANTS_C_ASSERT(SIZEOF__ComMethodFrame == sizeof(ComMethodFrame)); + +#define UnmanagedToManagedFrame__m_pvDatum 0x10 +ASMCONSTANTS_C_ASSERT(UnmanagedToManagedFrame__m_pvDatum == offsetof(UnmanagedToManagedFrame, m_pvDatum)); + +#endif // FEATURE_COMINTEROP + +#define REDIRECTSTUB_SP_OFFSET_CONTEXT 0 + +#define CONTEXT_Pc 0x108 +ASMCONSTANTS_C_ASSERT(CONTEXT_Pc == offsetof(T_CONTEXT,Pc)) + +#define SIZEOF__FaultingExceptionFrame (SIZEOF__Frame + 0x10 + SIZEOF__CONTEXT) +#define FaultingExceptionFrame__m_fFilterExecuted SIZEOF__Frame +ASMCONSTANTS_C_ASSERT(SIZEOF__FaultingExceptionFrame == sizeof(FaultingExceptionFrame)); +ASMCONSTANTS_C_ASSERT(FaultingExceptionFrame__m_fFilterExecuted == offsetof(FaultingExceptionFrame, m_fFilterExecuted)); + +#define SIZEOF__FixupPrecode 32 +//#define Offset_MethodDescChunkIndex 24 +//#define Offset_PrecodeChunkIndex 25 +#define MethodDesc_ALIGNMENT_SHIFT 3 +#define FixupPrecode_ALIGNMENT_SHIFT_1 5 + +ASMCONSTANTS_C_ASSERT(SIZEOF__FixupPrecode == sizeof(FixupPrecode)); +//ASMCONSTANTS_C_ASSERT(Offset_PrecodeChunkIndex == offsetof(FixupPrecode, m_PrecodeChunkIndex)); +//ASMCONSTANTS_C_ASSERT(Offset_MethodDescChunkIndex == offsetof(FixupPrecode, m_MethodDescChunkIndex)); +ASMCONSTANTS_C_ASSERT(MethodDesc_ALIGNMENT_SHIFT == MethodDesc::ALIGNMENT_SHIFT); +ASMCONSTANTS_C_ASSERT((1<= g_GCShadowEnd) goto end + la.local $t3, g_GCShadowEnd + ld.d $t3, $t3, 0 + + slt $t4, $t0, $t3 + beq $t4, $zero, 22f //LOCAL_LABEL(ShadowUpdateEnd) + + // *pShadow = $t7 + st.d $t7, $t0, 0 + + // Ensure that the write to the shadow heap occurs before the read from the GC heap so that race + // conditions are caught by INVALIDGCVALUE. + dbar 0 + + // if (*$t6 == $t7) goto end + ld.d $t3, $t6, 0 + beq $t3, $t7, 22f //LOCAL_LABEL(ShadowUpdateEnd) + + // *pShadow = INVALIDGCVALUE (0xcccccccd) + //lu12i.w $t3, 0xccccc + lu12i.w $t3, -209716 + ori $t3, $t3, 0xccd + st.d $t3, $t0, 0 +22: +//LOCAL_LABEL(ShadowUpdateEnd): +//LOCAL_LABEL(ShadowUpdateDisabled): +#endif + +#ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP + // Update the write watch table if necessary + + la.local $t3, wbs_sw_ww_table + ld.d $t3, $t3, 0 + beq $t3, $zero, 1f //LOCAL_LABEL(CheckCardTable) + + srli.d $t4, $t6, 0xc + add.d $t3, $t3, $t4 // SoftwareWriteWatch::AddressToTableByteIndexShift + ld.b $t4, $t3, 0 + bne $t4, $zero, 1f //LOCAL_LABEL(CheckCardTable) + + ori $t4, $zero, 0xFF + st.b $t4, $t3, 0 +1: +//LOCAL_LABEL(CheckCardTable): +#endif + // Branch to Exit if the reference is not in the Gen0 heap + la.local $t3, wbs_ephemeral_low + ld.d $t3, $t3, 0 + beq $t3, $zero, 2f //LOCAL_LABEL(SkipEphemeralCheck) + + slt $t4, $t7, $t3 + la.local $t3, wbs_ephemeral_high + ld.d $t3, $t3, 0 + slt $t1, $t3, $t7 + or $t4, $t1, $t4 + bne $t4, $zero, LOCAL_LABEL(Exit) +2: +//LOCAL_LABEL(SkipEphemeralCheck): + // Check if we need to update the card table + la.local $t3, wbs_card_table + ld.d $t3, $t3, 0 + srli.d $t4, $t6, 11 + add.d $t7, $t3, $t4 + ld.bu $t1, $t7, 0 + ori $t4, $zero, 0xFF + beq $t1, $t4, LOCAL_LABEL(Exit) + + st.b $t4, $t7, 0 + +#ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES + // Check if we need to update the card bundle table + la.local $t3, wbs_card_bundle_table + ld.d $t3, $t3, 0 + srli.d $t4, $t6, 21 + add.d $t7, $t3, $t4 + + ld.bu $t3, $t7, 0 + ori $t4, $zero, 0xFF + beq $t3, $t4, LOCAL_LABEL(Exit) + + st.b $t4, $t7, 0 +#endif +LOCAL_LABEL(Exit): + addi.d $t6, $t6, 8 + jirl $r0, $ra, 0 +WRITE_BARRIER_END JIT_WriteBarrier + +NESTED_ENTRY ThePreStub, _TEXT, NoHandler + PROLOG_WITH_TRANSITION_BLOCK + + ori $a1, $METHODDESC_REGISTER, 0 // pMethodDesc + + addi.d $a0, $sp, __PWTB_TransitionBlock // pTransitionBlock + bl PreStubWorker + ori $t4,$a0,0 + + EPILOG_WITH_TRANSITION_BLOCK_TAILCALL + EPILOG_BRANCH_REG $t4 +NESTED_END ThePreStub, _TEXT + +// ------------------------------------------------------------------\ + +// EXTERN_C int __fastcall HelperMethodFrameRestoreState( +// INDEBUG_COMMA(HelperMethodFrame *pFrame) +// MachState *pState +// ) +LEAF_ENTRY HelperMethodFrameRestoreState, _TEXT +#ifdef _DEBUG + ori $a0, $a1, 0 +#endif + + // If machine state is invalid, then simply exit + ld.w $a1, $a0, MachState__isValid + beq $a1, $zero, LOCAL_LABEL(Done) + + RestoreRegMS 23, s0 + RestoreRegMS 24, s1 + RestoreRegMS 25, s2 + RestoreRegMS 26, s3 + RestoreRegMS 27, s4 + RestoreRegMS 28, s5 + RestoreRegMS 29, s6 + RestoreRegMS 30, s7 + RestoreRegMS 31, s8 + RestoreRegMS 32, fp //NOTE: here 32 is not the real fp register number. + RestoreRegMS 33, tp //NOTE: here 33 is not the real tp register number. +LOCAL_LABEL(Done): + // Its imperative that the return value of HelperMethodFrameRestoreState is zero + // as it is used in the state machine to loop until it becomes zero. + // Refer to HELPER_METHOD_FRAME_END macro for details. + ori $v0, $zero, 0 + jirl $r0, $ra, 0 +LEAF_END HelperMethodFrameRestoreState, _TEXT + +//----------------------------------------------------------------------------- +// This routine captures the machine state. It is used by helper method frame +//----------------------------------------------------------------------------- +//void LazyMachStateCaptureState(struct LazyMachState *pState)// +LEAF_ENTRY LazyMachStateCaptureState, _TEXT + // marks that this is not yet valid + st.w $zero, $a0, MachState__isValid + + st.d $ra, $a0, LazyMachState_captureIp + + // save $sp register. + st.d $sp, $a0, LazyMachState_captureSp + + // save non-volatile registers that can contain object references + addi.d $a1, $a0, LazyMachState_captureCalleeSavedRegisters + st.d $s0, $a1, 0 + st.d $s1, $a1, 8 + st.d $s2, $a1, 16 + st.d $s3, $a1, 24 + st.d $s4, $a1, 32 + st.d $s5, $a1, 40 + st.d $s6, $a1, 48 + st.d $s7, $a1, 56 + st.d $s8, $a1, 64 + st.d $fp, $a1, 72 + st.d $tp, $a1, 80 + + jirl $r0, $ra, 0 +LEAF_END LazyMachStateCaptureState, _TEXT + +// ------------------------------------------------------------------ +// The call in ndirect import precode points to this function. +NESTED_ENTRY NDirectImportThunk, _TEXT, NoHandler + + // $fp,$ra + PROLOG_SAVE_REG_PAIR_INDEXED 22, 1, 0xa0 + //PROLOG_SAVE_REG gp, 16 + SAVE_ARGUMENT_REGISTERS $sp, 0x20 + SAVE_FLOAT_ARGUMENT_REGISTERS $sp, 0x60 + + ori $a0, $t2, 0 + bl C_FUNC(NDirectImportWorker) + ori $t4,$a0,0 + + // pop the stack and restore original register state + RESTORE_FLOAT_ARGUMENT_REGISTERS $sp, 0x60 + RESTORE_ARGUMENT_REGISTERS $sp, 0x20 + //EPILOG_RESTORE_REG gp, 16 + // $fp,$ra + EPILOG_RESTORE_REG_PAIR_INDEXED 22, 1, 0xa0 + + // If we got back from NDirectImportWorker, the MD has been successfully + // linked. Proceed to execute the original DLL call. + EPILOG_BRANCH_REG $t4 +NESTED_END NDirectImportThunk, _TEXT + +#ifdef FEATURE_PREJIT +//------------------------------------------------ +// VirtualMethodFixupStub +// +// In NGEN images, virtual slots inherited from cross-module dependencies +// point to a jump thunk that calls into the following function that will +// call into a VM helper. The VM helper is responsible for patching up +// thunk, upon executing the precode, so that all subsequent calls go directly +// to the actual method body. +// +// This is done lazily for performance reasons. +// +// On entry: +// +// $a0 = "this" pointer +// $t2 = Address of thunk + +NESTED_ENTRY VirtualMethodFixupStub, _TEXT, NoHandler + + // Save arguments and return address + // $fp,$ra + PROLOG_SAVE_REG_PAIR_INDEXED 22, 1, 0xa0 + //PROLOG_SAVE_REG gp, 16 + SAVE_ARGUMENT_REGISTERS $sp, 32 + SAVE_FLOAT_ARGUMENT_REGISTERS $sp, 96 + + + // Call the helper in the VM to perform the actual fixup + // and tell us where to tail call. $a0 already contains + // the this pointer. + + // Move the thunk start address in $a1 + ori $a1, $t2, 0 + bl VirtualMethodFixupWorker + ori $t4,$a0,0 + + // On return, v0 contains the target to tailcall to + + // pop the stack and restore original register state + RESTORE_FLOAT_ARGUMENT_REGISTERS $sp, 96 + RESTORE_ARGUMENT_REGISTERS $sp, 32 + //EPILOG_RESTORE_REG gp, 16 + // $fp,$ra + EPILOG_RESTORE_REG_PAIR_INDEXED 22, 1, 0xa0 + + PATCH_LABEL VirtualMethodFixupPatchLabel + + // and tailcall to the actual method + EPILOG_BRANCH_REG $t4 +NESTED_END VirtualMethodFixupStub, _TEXT +#endif // FEATURE_PREJIT + +// void SinglecastDelegateInvokeStub(Delegate *pThis) +LEAF_ENTRY SinglecastDelegateInvokeStub, _TEXT + beq $a0, $zero, LOCAL_LABEL(LNullThis) + + ld.d $t4, $a0, DelegateObject___methodPtr + ld.d $a0, $a0, DelegateObject___target + jirl $r0, $t4, 0 + +LOCAL_LABEL(LNullThis): + addi.d $a0, $zero, CORINFO_NullReferenceException_ASM + b JIT_InternalThrow +LEAF_END SinglecastDelegateInvokeStub, _TEXT + +// void JIT_ByRefWriteBarrier +// +// On entry: +// t8 : the source address (points to object reference to write) +// t6: the destination address (object reference written here) +// +// On exit: +// t8 : incremented by 8 +// t7 : trashed +// +WRITE_BARRIER_ENTRY JIT_ByRefWriteBarrier + ld.d $t7, $t8, 0 + addi.d $t8, $t8, 8 + b C_FUNC(JIT_CheckedWriteBarrier) +WRITE_BARRIER_END JIT_ByRefWriteBarrier + +//----------------------------------------------------------------------------- +// Simple WriteBarriers +// void JIT_CheckedWriteBarrier(Object** dst, Object* src) +// +// On entry: +// t6 : the destination address (LHS of the assignment) +// t7 : the object reference (RHS of the assignment) +// +// On exit: +// $t1 : trashed +// $t0 : trashed +// $t3 : trashed +// $t4 : trashed +// t6 : trashed (incremented by 8 to implement JIT_ByRefWriteBarrier contract) +// +WRITE_BARRIER_ENTRY JIT_CheckedWriteBarrier + + la.local $t3, wbs_lowest_address + ld.d $t3, $t3, 0 + slt $t4, $t6, $t3 + + la.local $t1, wbs_highest_address + ld.d $t1, $t1, 0 + slt $t0, $t1, $t6 + or $t4, $t0, $t4 + beq $t4, $zero, C_FUNC(JIT_WriteBarrier) + + st.d $t7, $t6, 0 + addi.d $t6, $t6, 8 + jirl $r0, $ra, 0 +WRITE_BARRIER_END JIT_CheckedWriteBarrier + +// ------------------------------------------------------------------ +// ThePreStubPatch() +LEAF_ENTRY ThePreStubPatch, _TEXT +.globl C_FUNC(ThePreStubPatchLabel) +C_FUNC(ThePreStubPatchLabel): + jirl $r0, $ra, 0 +LEAF_END ThePreStubPatch, _TEXT + +#ifdef FEATURE_PREJIT +// ------------------------------------------------------------------ +// void StubDispatchFixupStub(args in regs $a0-$a7 & stack, $t1:IndirectionCellAndFlags) +// +// The stub dispatch thunk which transfers control to StubDispatchFixupWorker. +NESTED_ENTRY StubDispatchFixupStub, _TEXT, NoHandler + + PROLOG_WITH_TRANSITION_BLOCK + + srli.d $a1, $t8, 2 + slli.d $a1, $a1, 2 // Indirection cell + + addi.d $a0, $sp, __PWTB_TransitionBlock // pTransitionBlock + ori $a2, $zero, 0 // sectionIndex + ori $a3, $zero, 0 // pModule + bl StubDispatchFixupWorker + ori $t4,$a0,0 + + EPILOG_WITH_TRANSITION_BLOCK_TAILCALL + PATCH_LABEL StubDispatchFixupPatchLabel + EPILOG_BRANCH_REG $t4 +NESTED_END StubDispatchFixupStub, _TEXT +#endif + +// +// $t2 = UMEntryThunk* +// +NESTED_ENTRY TheUMEntryPrestub, _TEXT, UnhandledExceptionHandlerUnix + + // Save arguments and return address + // $fp,$ra + PROLOG_SAVE_REG_PAIR_INDEXED 22, 1, 0xa0 + //PROLOG_SAVE_REG gp, 16 + SAVE_ARGUMENT_REGISTERS $sp, 32 + SAVE_FLOAT_ARGUMENT_REGISTERS $sp, 96 + + + ori $a0, $t2, 0 + bl TheUMEntryPrestubWorker + ori $t4,$a0,0 + + // pop the stack and restore original register state + RESTORE_FLOAT_ARGUMENT_REGISTERS $sp, 96 + RESTORE_ARGUMENT_REGISTERS $sp, 32 + //EPILOG_RESTORE_REG gp, 16 + // $fp,$ra + EPILOG_RESTORE_REG_PAIR_INDEXED 22, 1, 0xa0 + + // and tailcall to the actual method + EPILOG_BRANCH_REG $t4 +NESTED_END TheUMEntryPrestub, _TEXT + +// ------------------------------------------------------------------ +// void* JIT_GetSharedGCStaticBase(SIZE_T moduleDomainID, DWORD dwClassDomainID) + +LEAF_ENTRY JIT_GetSharedGCStaticBase_SingleAppDomain, _TEXT + // If class is not initialized, bail to C++ helper + addi.d $a2, $a0, DomainLocalModule__m_pDataBlob + add.d $a2, $a2, $a1 + ld.b $a2, $a2, 0 + andi $t8, $a2, 1 + beq $t8, $zero, 1f //LOCAL_LABEL(JIT_GetSharedGCStaticBase_SingleAppDomain_CallHelper) + + ld.d $v0, $a0, DomainLocalModule__m_pGCStatics + jirl $r0, $ra, 0 + +1: +//LOCAL_LABEL(JIT_GetSharedGCStaticBase_SingleAppDomain_CallHelper): + // Tail call JIT_GetSharedGCStaticBase_Helper + bl JIT_GetSharedGCStaticBase_Helper +LEAF_END JIT_GetSharedGCStaticBase_SingleAppDomain, _TEXT + +// ------------------------------------------------------------------ +// ResolveWorkerChainLookupAsmStub +// +// This method will perform a quick chained lookup of the entry if the +// initial cache lookup fails. +// +// On Entry: +// $t1 contains the pointer to the current ResolveCacheElem +// t8 contains the address of the indirection (and the flags in the low two bits) +// $t2 contains our contract the DispatchToken +// Must be preserved: +// $a0 contains the instance object ref that we are making an interface call on +// $t1 Must point to a ResolveCacheElem [For Sanity] +// [$a1-$a7] contains any additional register arguments for the interface method +// +// Loaded from $a0 +// $t3 contains our type the MethodTable (from object ref in $a0) +// +// On Exit: +// $a0, [$a1-$a7] arguments for the interface implementation target +// +// On Exit (to ResolveWorkerAsmStub): +// t8 contains the address of the indirection and the flags in the low two bits. +// $t2 contains our contract (DispatchToken) +// t4 will be trashed +// + +#define BACKPATCH_FLAG 1 +#define PROMOTE_CHAIN_FLAG 2 + +NESTED_ENTRY ResolveWorkerChainLookupAsmStub, _TEXT, NoHandler + andi $t4, $t8, BACKPATCH_FLAG // First we check if t8 has the BACKPATCH_FLAG set + bne $t4, $zero, LOCAL_LABEL(Fail) // If the BACKPATCH_FLAGS is set we will go directly to the ResolveWorkerAsmStub + + ld.d $t3, $a0, 0 // retrieve the MethodTable from the object ref in $a0 +LOCAL_LABEL(MainLoop): + ld.d $t1, $t1, ResolveCacheElem__pNext // $t1 <= the next entry in the chain + beq $t1, $zero, LOCAL_LABEL(Fail) + + ld.d $t4, $t1, 0 + // compare our MT with the one in the ResolveCacheElem + bne $t4, $t3, LOCAL_LABEL(MainLoop) + + ld.d $t4, $t1, 8 + // compare our DispatchToken with one in the ResolveCacheElem + bne $t2, $t4, LOCAL_LABEL(MainLoop) + +LOCAL_LABEL(Success): + PREPARE_EXTERNAL_VAR g_dispatch_cache_chain_success_counter, $t3 + ld.d $t4, $t3, 0 + addi.d $t4, $t4, -1 + st.d $t4, $t3, 0 + blt $t4, $zero, LOCAL_LABEL(Promote) + + ld.d $t4, $t1, ResolveCacheElem__target // get the ImplTarget + jirl $r0, $t4, 0 // branch to interface implemenation target + +LOCAL_LABEL(Promote): + // Move this entry to head postion of the chain + addi.d $t4, $zero, 256 + st.d $t4, $t3, 0 // be quick to reset the counter so we don't get a bunch of contending threads + ori $t8, $t8, PROMOTE_CHAIN_FLAG // set PROMOTE_CHAIN_FLAG + ori $t2, $t1, 0 // We pass the ResolveCacheElem to ResolveWorkerAsmStub instead of the DispatchToken + +LOCAL_LABEL(Fail): + b C_FUNC(ResolveWorkerAsmStub) // call the ResolveWorkerAsmStub method to transition into the VM +NESTED_END ResolveWorkerChainLookupAsmStub, _TEXT + + +//NOTE: Frame_Size = SIZEOF__ArgumentRegisters + SIZEOF__FloatArgumentRegisters + extra. +// +// |gp | +// |s0 | +// |$t2 | +// |t9 | +// |$a7 | +// |$a6 | +// |$a5 | +// |$a4 | +// |$a3 | +// |$a2 | +// |$a1 | +// |$a0 | +// |$ra | $sp+8 +// |fp | $sp +// +// |f19 | if needed. +// |f18 | +// |f17 | +// |f16 | +// |f15 | +// |f14 | +// |f13 | +// |f12 | +// +//#define UMThunkStub_Offset_t9 0x50 +#define UMThunkStub_Offset_Entry 0x58 // offset of saved UMEntryThunk * +#define UMThunkStub_Offset_s0 0x60 +#define UMThunkStub_StackArgs 0x70 // Frame size. + + +// ------------------------------------------------------------------ +// End of the writeable code region +LEAF_ENTRY JIT_PatchedCodeLast, _TEXT + jirl $r0, $ra, 0 +LEAF_END JIT_PatchedCodeLast, _TEXT + +// ------------------------------------------------------------------ +// void ResolveWorkerAsmStub(args in regs $a0-$a7 & stack, t8:IndirectionCellAndFlags, $t2:DispatchToken) +// +// The stub dispatch thunk which transfers control to VSD_ResolveWorker. +NESTED_ENTRY ResolveWorkerAsmStub, _TEXT, NoHandler + + PROLOG_WITH_TRANSITION_BLOCK + + ori $a2, $t2, 0 // DispatchToken + addi.d $a0, $sp, __PWTB_TransitionBlock // pTransitionBlock + srli.d $a1, $t8, 2 + andi $a3, $t8, 3 // flag + slli.d $a1, $a1, 2 + bl C_FUNC(VSD_ResolveWorker) + ori $t4,$a0,0 + + EPILOG_WITH_TRANSITION_BLOCK_TAILCALL + + EPILOG_BRANCH_REG $t4 +NESTED_END ResolveWorkerAsmStub, _TEXT + +// ------------------------------------------------------------------ +// void* JIT_GetSharedNonGCStaticBaseNoCtor(SIZE_T moduleDomainID, DWORD dwClassDomainID) + +LEAF_ENTRY JIT_GetSharedNonGCStaticBaseNoCtor_SingleAppDomain, _TEXT + jirl $r0, $ra, 0 +LEAF_END JIT_GetSharedNonGCStaticBaseNoCtor_SingleAppDomain, _TEXT + +// ------------------------------------------------------------------ +// void* JIT_GetSharedGCStaticBaseNoCtor(SIZE_T moduleDomainID, DWORD dwClassDomainID) + +LEAF_ENTRY JIT_GetSharedGCStaticBaseNoCtor_SingleAppDomain, _TEXT + ld.d $a0, $a0, DomainLocalModule__m_pGCStatics + jirl $r0, $ra, 0 +LEAF_END JIT_GetSharedGCStaticBaseNoCtor_SingleAppDomain, _TEXT + + +#ifdef FEATURE_HIJACK +// ------------------------------------------------------------------ +// Hijack function for functions which return a scalar type or a struct (value type) +NESTED_ENTRY OnHijackTripThread, _TEXT, NoHandler + // $fp,$ra + PROLOG_SAVE_REG_PAIR_INDEXED 22, 1, 0x80 + + // Spill callee saved registers + // $s0,$s1 + PROLOG_SAVE_REG_PAIR 23, 24, 16 + PROLOG_SAVE_REG_PAIR 25, 26, 32 + PROLOG_SAVE_REG_PAIR 27, 28, 48 + PROLOG_SAVE_REG_PAIR 29, 30, 64 + PROLOG_SAVE_REG 31, 80 + + // save any integral return value(s) + st.d $v0, $sp, 96 + st.d $v1, $sp, 104 + + // save any FP/HFA return value(s) + fst.d $f0, $sp, 112 + fst.d $f1, $sp, 120 + + ori $a0, $sp, 0 + bl C_FUNC(OnHijackWorker) + + // restore callee saved registers + + // restore any integral return value(s) + ld.d $v0, $sp, 96 + ld.d $v1, $sp, 104 + + // restore any FP/HFA return value(s) + fst.d $f0, $sp, 112 + fst.d $f1, $sp, 120 + + EPILOG_RESTORE_REG_PAIR 23, 24, 16 + EPILOG_RESTORE_REG_PAIR 25, 26, 32 + EPILOG_RESTORE_REG_PAIR 27, 28, 48 + EPILOG_RESTORE_REG_PAIR 29, 30, 64 + EPILOG_RESTORE_REG 31, 80 + // $fp,$ra + EPILOG_RESTORE_REG_PAIR_INDEXED 22, 1, 0x80 + EPILOG_RETURN +NESTED_END OnHijackTripThread, _TEXT + +#endif // FEATURE_HIJACK + +// ------------------------------------------------------------------ +// Redirection Stub for GC in fully interruptible method +//GenerateRedirectedHandledJITCaseStub GCThreadControl +// ------------------------------------------------------------------ +//GenerateRedirectedHandledJITCaseStub DbgThreadControl +// ------------------------------------------------------------------ +//GenerateRedirectedHandledJITCaseStub UserSuspend + +#ifdef _DEBUG +// ------------------------------------------------------------------ +// Redirection Stub for GC Stress +GenerateRedirectedHandledJITCaseStub GCStress +#endif + + +// ------------------------------------------------------------------ +// This helper enables us to call into a funclet after restoring Fp register +NESTED_ENTRY CallEHFunclet, _TEXT, NoHandler + // On entry: + // + // $a0 = throwable + // $a1 = PC to invoke + // $a2 = address of s0 register in CONTEXT record// used to restore the non-volatile registers of CrawlFrame + // $a3 = address of the location where the SP of funclet's caller (i.e. this helper) should be saved. + // + + // $fp,$ra + PROLOG_SAVE_REG_PAIR_INDEXED 22, 1, 96, 0 + + // Spill callee saved registers + // $s0,$s1 + PROLOG_SAVE_REG_PAIR 23, 24, 16 + PROLOG_SAVE_REG_PAIR 25, 26, 32 + PROLOG_SAVE_REG_PAIR 27, 28, 48 + PROLOG_SAVE_REG_PAIR 29, 30, 64 + // $s8,$tp + PROLOG_SAVE_REG_PAIR 31, 2, 80 + //PROLOG_SAVE_REG 31, 80 + + // Save the SP of this function + st.d $sp, $a3, 0 + + ld.d $tp, $a2, -168 // offset of tp in PCONTEXT relative to S0. + ld.d $fp, $a2, -8 // offset of fp in PCONTEXT relative to S0. + ld.d $s0, $a2, 0 + ld.d $s1, $a2, 8 + ld.d $s2, $a2, 16 + ld.d $s3, $a2, 24 + ld.d $s4, $a2, 32 + ld.d $s5, $a2, 40 + ld.d $s6, $a2, 48 + ld.d $s7, $a2, 56 + ld.d $s8, $a2, 64 // offset of fp in PCONTEXT relative to S0. + + // Invoke the funclet + jirl $ra, $a1, 0 + + EPILOG_RESTORE_REG_PAIR 23, 24, 16 + EPILOG_RESTORE_REG_PAIR 25, 26, 32 + EPILOG_RESTORE_REG_PAIR 27, 28, 48 + EPILOG_RESTORE_REG_PAIR 29, 30, 64 + EPILOG_RESTORE_REG_PAIR 31, 2, 80 + //EPILOG_RESTORE_REG 31, 80 + // $fp,$ra + EPILOG_RESTORE_REG_PAIR_INDEXED 22, 1, 96 + EPILOG_RETURN +NESTED_END CallEHFunclet, _TEXT + +// This helper enables us to call into a filter funclet by passing it the CallerSP to lookup the +// frame pointer for accessing the locals in the parent method. +NESTED_ENTRY CallEHFilterFunclet, _TEXT, NoHandler + // $fp,$ra + PROLOG_SAVE_REG_PAIR_INDEXED 22, 1, 16 + + // On entry: + // + // $a0 = throwable + // $a1 = SP of the caller of the method/funclet containing the filter + // $a2 = PC to invoke + // $a3 = address of the location where the SP of funclet's caller (i.e. this helper) should be saved. + // + // Save the SP of this function + st.d $fp, $a3, 0 + // Invoke the filter funclet + jirl $ra, $a2, 0 + + EPILOG_RESTORE_REG_PAIR_INDEXED 22, 1, 16 + EPILOG_RETURN +NESTED_END CallEHFilterFunclet, _TEXT + +#ifdef FEATURE_COMINTEROP +// Function used by COM interop to get floating point return value (since it's not in the same +// register(s) as non-floating point values). +// +// On entry// +// $a0 : size of the FP result (4 or 8 bytes) +// $a1 : pointer to 64-bit buffer to receive result +// +// On exit: +// buffer pointed to by $a1 on entry contains the float or double argument as appropriate +// +LEAF_ENTRY getFPReturn, _TEXT + fst.d $f0, $a1, 0 +LEAF_END getFPReturn, _TEXT + +// ------------------------------------------------------------------ +// Function used by COM interop to set floating point return value (since it's not in the same +// register(s) as non-floating point values). +// +// On entry: +// $a0 : size of the FP result (4 or 8 bytes) +// $a1 : 32-bit or 64-bit FP result +// +// On exit: +// f0 : float result if x0 == 4 +// f0 : double result if x0 == 8 +// +LEAF_ENTRY setFPReturn, _TEXT + movgr2fr.d $f0, $a1 +LEAF_END setFPReturn, _TEXT + +#endif // FEATURE_COMINTEROP + +// +// JIT Static access helpers when coreclr host specifies single appdomain flag +// + +// ------------------------------------------------------------------ +// void* JIT_GetSharedNonGCStaticBase(SIZE_T moduleDomainID, DWORD dwClassDomainID) + +LEAF_ENTRY JIT_GetSharedNonGCStaticBase_SingleAppDomain, _TEXT + // If class is not initialized, bail to C++ helper + //dext $a1, $a1, 0, 32 + addi.d $a2, $a0, DomainLocalModule__m_pDataBlob + + ldx.b $a2, $a2, $a1 + andi $t4, $a2, 1 + beq $t4, $zero, LOCAL_LABEL(JIT_GetSharedNonGCStaticBase_SingleAppDomain_CallHelper) + + jirl $r0, $ra, 0 + +LOCAL_LABEL(JIT_GetSharedNonGCStaticBase_SingleAppDomain_CallHelper): + // Tail call JIT_GetSharedNonGCStaticBase_Helper + b JIT_GetSharedNonGCStaticBase_Helper +LEAF_END JIT_GetSharedNonGCStaticBase_SingleAppDomain, _TEXT + +#ifdef FEATURE_READYTORUN + +NESTED_ENTRY DelayLoad_MethodCall_FakeProlog, _TEXT, NoHandler +C_FUNC(DelayLoad_MethodCall): + .global C_FUNC(DelayLoad_MethodCall) + PROLOG_WITH_TRANSITION_BLOCK + + ori $a1, $t8, 0 // Indirection cell + ori $a2, $t0, 0 // sectionIndex + ori $a3, $t1, 0 // Module* + + addi.d $a0, $sp, __PWTB_TransitionBlock // pTransitionBlock + bl C_FUNC(ExternalMethodFixupWorker) + ori $t4,$a0,0 + + EPILOG_WITH_TRANSITION_BLOCK_TAILCALL + PATCH_LABEL ExternalMethodFixupPatchLabel + EPILOG_BRANCH_REG $t4 +NESTED_END DelayLoad_MethodCall_FakeProlog, _TEXT + + +.macro DynamicHelper frameFlags, suffix +NESTED_ENTRY DelayLoad_Helper\suffix\()_FakeProlog, _TEXT, NoHandler +DelayLoad_Helper\suffix: + .global DelayLoad_Helper\suffix + + PROLOG_WITH_TRANSITION_BLOCK + + //DynamicHelperWorker(TransitionBlock * pTransitionBlock, TADDR * pCell, + // DWORD sectionIndex, Module * pModule, INT frameFlags) + ori $a1, $t8, 0 // Indirection cell + ori $a2, $t0, 0 // sectionIndex + ori $a3, $t1, 0 // Module* + ori $a4, $r0, \frameFlags + + addi.d $a0, $sp, __PWTB_TransitionBlock // pTransitionBlock + bl DynamicHelperWorker + + bne $v0, $r0, LOCAL_LABEL(FakeProlog\suffix\()_0) + + ld.d $v0, $sp, __PWTB_ArgumentRegisters + EPILOG_WITH_TRANSITION_BLOCK_RETURN +LOCAL_LABEL(FakeProlog\suffix\()_0): + ori $t4,$a0,0 + EPILOG_WITH_TRANSITION_BLOCK_TAILCALL + EPILOG_BRANCH_REG $t4 + +NESTED_END DelayLoad_Helper\suffix\()_FakeProlog, _TEXT +.endm + +DynamicHelper DynamicHelperFrameFlags_Default +DynamicHelper DynamicHelperFrameFlags_ObjectArg, _Obj +DynamicHelper DynamicHelperFrameFlags_ObjectArg | DynamicHelperFrameFlags_ObjectArg2, _ObjObj +#endif + + +#ifdef PROFILING_SUPPORTED + +// ------------------------------------------------------------------ +LEAF_ENTRY JIT_ProfilerEnterLeaveTailcallStub, _TEXT + jirl $r0, $ra, 0 +LEAF_END JIT_ProfilerEnterLeaveTailcallStub, _TEXT + +// ------------------------------------------------------------------ +#define PROFILE_ENTER 1 +#define PROFILE_LEAVE 2 +#define PROFILE_TAILCALL 4 +#define SIZEOF__PROFILE_PLATFORM_SPECIFIC_DATA 272 + +// ------------------------------------------------------------------ +.macro GenerateProfileHelper helper, flags +NESTED_ENTRY \helper\()Naked, _TEXT, NoHandler + // On entry: + // $t1 = functionIDOrClientID + // $t2 = profiledSp + // $t3 = throwable + // + // On exit: + // Values of $a0-$a7, $fa0-$fa7, $fp are preserved. + // Values of other volatile registers are not preserved. + + // $fp,$ra + PROLOG_SAVE_REG_PAIR_INDEXED 22, 1, SIZEOF__PROFILE_PLATFORM_SPECIFIC_DATA // Allocate space and save Fp, Pc. + SAVE_ARGUMENT_REGISTERS $sp, 16 // Save $t0 and argument registers ($a0-$a7). + st.d $zero, $sp, 88 // Clear functionId. + SAVE_FLOAT_ARGUMENT_REGISTERS $sp, 96 // Save floating-point/SIMD registers ($fa0-$fa7). + addi.d $t3, $fp, SIZEOF__PROFILE_PLATFORM_SPECIFIC_DATA // Compute probeSp - initial value of Sp on entry to the helper. + st.d $t3, $sp, 224 // Save probeSp. + st.d $t2, $sp, 232 // Save profiledSp. + + st.d $zero, $sp, 240 // Clear hiddenArg. + addi.d $t3, $zero, \flags + st.w $t3, $sp, 248 // Save flags. + st.d $zero, $sp, 256 // clear unused field. + + ori $a1, $t1, 0 + ori $a2, $sp, 0 + bl C_FUNC(\helper) + + RESTORE_ARGUMENT_REGISTERS $sp, 16 // Restore $t0 and argument registers. + RESTORE_FLOAT_ARGUMENT_REGISTERS $sp, 96 // Restore floating-point/SIMD registers. + // $fp, $ra + EPILOG_RESTORE_REG_PAIR_INDEXED 22, 1, SIZEOF__PROFILE_PLATFORM_SPECIFIC_DATA + EPILOG_RETURN + +NESTED_END \helper\()Naked, _TEXT +.endm + +GenerateProfileHelper ProfileEnter, PROFILE_ENTER +GenerateProfileHelper ProfileLeave, PROFILE_LEAVE +GenerateProfileHelper ProfileTailcall, PROFILE_TAILCALL + +#endif // PROFILING_SUPPORTED + + +#ifdef FEATURE_TIERED_COMPILATION + +NESTED_ENTRY OnCallCountThresholdReachedStub, _TEXT, NoHandler + PROLOG_WITH_TRANSITION_BLOCK + + addi.d $a0, $sp, __PWTB_TransitionBlock // TransitionBlock * + ori $a1, $t1, 0 // stub-identifying token + bl C_FUNC(OnCallCountThresholdReached) + ori $t4,$a0,0 + + EPILOG_WITH_TRANSITION_BLOCK_TAILCALL + EPILOG_BRANCH_REG $t4 +NESTED_END OnCallCountThresholdReachedStub, _TEXT + +#endif // FEATURE_TIERED_COMPILATION diff --git a/src/coreclr/vm/loongarch64/calldescrworkerloongarch64.S b/src/coreclr/vm/loongarch64/calldescrworkerloongarch64.S new file mode 100644 index 0000000000000..66373c23e79d4 --- /dev/null +++ b/src/coreclr/vm/loongarch64/calldescrworkerloongarch64.S @@ -0,0 +1,208 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include "unixasmmacros.inc" +#include "asmconstants.h" + +//----------------------------------------------------------------------------- +// This helper routine enregisters the appropriate arguments and makes the +// actual call. +//----------------------------------------------------------------------------- +//void CallDescrWorkerInternal(CallDescrData * pCallDescrData); + +NESTED_ENTRY CallDescrWorkerInternal, _TEXT, NoHandler + // $fp,$ra + PROLOG_SAVE_REG_PAIR_INDEXED 22, 1, 0x20 + // $s0=23. + PROLOG_SAVE_REG 23, 16 + ld.wu $a1, $a0, CallDescrData__numStackSlots + + ori $s0, $a0, 0 // save pCallDescrData in s0 + beq $a1, $zero, LOCAL_LABEL(donestack) + + slli.d $a2, $a1, 3 + andi $a0, $a2, 0x8 + sub.d $t4, $sp, $a0 //padding on high-addr. + add.d $a0, $a0, $a2 + sub.d $sp, $sp, $a0 //stack-16byte aligned. + + ld.d $a0, $s0, CallDescrData__pSrc + + add.d $a2, $a0, $a2 // pSrcEnd=pSrc+8*numStackSlots + + // This loop copies numStackSlots words + // from [pSrcEnd-8,pSrcEnd-16,...] to [sp-8,sp-16,...] +LOCAL_LABEL(stackloop): + addi.d $a2, $a2, -8 + ld.d $a4, $a2, 0 + addi.d $t4, $t4, -8 + st.d $a4, $t4, 0 + addi.d $a1, $a1, -1 + bne $a1, $zero, LOCAL_LABEL(stackloop) + +LOCAL_LABEL(donestack): + // If FP arguments are supplied in registers ($t4 != NULL) + ld.d $t4, $s0, CallDescrData__pFloatArgumentRegisters + beq $t4, $zero, LOCAL_LABEL(NoFloatingPoint) + + fld.d $fa0, $t4, 0 + fld.d $fa1, $t4, 8 + fld.d $fa2, $t4, 16 + fld.d $fa3, $t4, 24 + fld.d $fa4, $t4, 32 + fld.d $fa5, $t4, 40 + fld.d $fa6, $t4, 48 + fld.d $fa7, $t4, 56 +LOCAL_LABEL(NoFloatingPoint): + + // Copy [pArgumentRegisters, ..., pArgumentRegisters + 56] + // into $a0, ..., a7 + + ld.d $t4, $s0, CallDescrData__pArgumentRegisters + ld.d $a0, $t4, 0 + ld.d $a1, $t4, 8 + ld.d $a2, $t4, 16 + ld.d $a3, $t4, 24 + ld.d $a4, $t4, 32 + ld.d $a5, $t4, 40 + ld.d $a6, $t4, 48 + ld.d $a7, $t4, 56 + + ld.d $t4, $s0, CallDescrData__pTarget + + // call pTarget + jirl $ra, $t4, 0 + + ld.w $a3, $s0, CallDescrData__fpReturnSize + + // Int return case + beq $a3, $zero, LOCAL_LABEL(IntReturn) + + // Struct with Float/Double field return case. + ori $t4, $zero, CallDescrData__flagOneFloat + beq $t4, $a3, LOCAL_LABEL(FloatReturn) + + ori $t4, $zero, CallDescrData__flagOneDouble + beq $t4, $a3, LOCAL_LABEL(DoubleReturn) + + ori $t4, $zero, CallDescrData__flagFloatInt + beq $t4, $a3, LOCAL_LABEL(FloatIntReturn) + + ori $t4, $zero, CallDescrData__flagDoubleInt + beq $t4, $a3, LOCAL_LABEL(DoubleIntReturn) + + ori $t4, $zero, CallDescrData__flagFloatLong + beq $t4, $a3, LOCAL_LABEL(FloatLongReturn) + + ori $t4, $zero, CallDescrData__flagDoubleLong + beq $t4, $a3, LOCAL_LABEL(DoubleLongReturn) + + ori $t4, $zero, CallDescrData__flagIntFloat + beq $t4, $a3, LOCAL_LABEL(IntFloatReturn) + + ori $t4, $zero, CallDescrData__flagLongFloat + beq $t4, $a3, LOCAL_LABEL(LongFloatReturn) + + ori $t4, $zero, CallDescrData__flagIntDouble + beq $t4, $a3, LOCAL_LABEL(IntDoubleReturn) + + ori $t4, $zero, CallDescrData__flagLongDouble + beq $t4, $a3, LOCAL_LABEL(LongDoubleReturn) + + ori $t4, $zero, CallDescrData__flagFloatFloat + beq $t4, $a3, LOCAL_LABEL(FloatFloatReturn) + + ori $t4, $zero, CallDescrData__flagDoubleFloat + beq $t4, $a3, LOCAL_LABEL(DoubleFloatReturn) + + ori $t4, $zero, CallDescrData__flagFloatDouble + beq $t4, $a3, LOCAL_LABEL(FloatDoubleReturn) + + ori $t4, $zero, CallDescrData__flagDoubleDouble + beq $t4, $a3, LOCAL_LABEL(DoubleDoubleReturn) + + //b LOCAL_LABEL(NotCorrectReturn) +LOCAL_LABEL(NotCorrectReturn): + st.w $ra, $zero, 0 + EMIT_BREAKPOINT // Unreachable + +LOCAL_LABEL(FloatReturn): + fst.s $f0, $s0, CallDescrData__returnValue + b LOCAL_LABEL(ReturnDone) + +LOCAL_LABEL(DoubleReturn): + fst.d $f0, $s0, CallDescrData__returnValue + b LOCAL_LABEL(ReturnDone) + +LOCAL_LABEL(FloatIntReturn): + fst.s $f0, $s0, CallDescrData__returnValue + st.w $a0, $s0, CallDescrData__returnValue + 4 + b LOCAL_LABEL(ReturnDone) + +LOCAL_LABEL(DoubleIntReturn): + fst.d $f0, $s0, CallDescrData__returnValue + st.w $a0, $s0, CallDescrData__returnValue + 8 + b LOCAL_LABEL(ReturnDone) + +LOCAL_LABEL(FloatLongReturn): + fst.s $f0, $s0, CallDescrData__returnValue + st.d $a0, $s0, CallDescrData__returnValue + 8 + b LOCAL_LABEL(ReturnDone) + +LOCAL_LABEL(DoubleLongReturn): + fst.d $f0, $s0, CallDescrData__returnValue + st.d $a0, $s0, CallDescrData__returnValue + 8 + b LOCAL_LABEL(ReturnDone) + +LOCAL_LABEL(IntFloatReturn): + st.w $a0, $s0, CallDescrData__returnValue + fst.s $f0, $s0, CallDescrData__returnValue + 4 + b LOCAL_LABEL(ReturnDone) + +LOCAL_LABEL(LongFloatReturn): + st.d $a0, $s0, CallDescrData__returnValue + fst.s $f0, $s0, CallDescrData__returnValue + 8 + b LOCAL_LABEL(ReturnDone) + +LOCAL_LABEL(IntDoubleReturn): + st.w $a0, $s0, CallDescrData__returnValue + fst.d $f0, $s0, CallDescrData__returnValue + 8 + b LOCAL_LABEL(ReturnDone) + +LOCAL_LABEL(LongDoubleReturn): + st.d $a0, $s0, CallDescrData__returnValue + fst.d $f0, $s0, CallDescrData__returnValue + 8 + b LOCAL_LABEL(ReturnDone) + +LOCAL_LABEL(FloatFloatReturn): + fst.s $f0, $s0, CallDescrData__returnValue + fst.s $f1, $s0, CallDescrData__returnValue + 4 + b LOCAL_LABEL(ReturnDone) + +LOCAL_LABEL(DoubleFloatReturn): + fst.d $f0, $s0, CallDescrData__returnValue + fst.s $f1, $s0, CallDescrData__returnValue + 8 + b LOCAL_LABEL(ReturnDone) + +LOCAL_LABEL(FloatDoubleReturn): + fst.s $f0, $s0, CallDescrData__returnValue + fst.d $f1, $s0, CallDescrData__returnValue + 8 + b LOCAL_LABEL(ReturnDone) + +LOCAL_LABEL(DoubleDoubleReturn): + fst.d $f0, $s0, CallDescrData__returnValue + fst.d $f1, $s0, CallDescrData__returnValue + 8 + b LOCAL_LABEL(ReturnDone) + +LOCAL_LABEL(IntReturn): + // Save return value into retbuf for int + st.d $a0, $s0, CallDescrData__returnValue + st.d $a1, $s0, CallDescrData__returnValue + 8 + +LOCAL_LABEL(ReturnDone): + + EPILOG_STACK_RESTORE + EPILOG_RESTORE_REG 23, 16 + EPILOG_RESTORE_REG_PAIR_INDEXED 22, 1, 0x20 + jirl $r0, $ra, 0 +NESTED_END CallDescrWorkerInternal, _TEXT diff --git a/src/coreclr/vm/loongarch64/cgencpu.h b/src/coreclr/vm/loongarch64/cgencpu.h new file mode 100644 index 0000000000000..277724276d21a --- /dev/null +++ b/src/coreclr/vm/loongarch64/cgencpu.h @@ -0,0 +1,511 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#ifndef TARGET_LOONGARCH64 +#error Should only include "cgencpu.h" for LOONGARCH64 builds +#endif + +#ifndef __cgencpu_h__ +#define __cgencpu_h__ + +#define INSTRFMT_K64 +#include + +#ifndef TARGET_UNIX +#define USE_REDIRECT_FOR_GCSTRESS +#endif // TARGET_UNIX + +EXTERN_C void getFPReturn(int fpSize, INT64 *pRetVal); +EXTERN_C void setFPReturn(int fpSize, INT64 retVal); + + +class ComCallMethodDesc; + +extern PCODE GetPreStubEntryPoint(); + +#define COMMETHOD_PREPAD 24 // # extra bytes to allocate in addition to sizeof(ComCallMethodDesc) +#ifdef FEATURE_COMINTEROP +#define COMMETHOD_CALL_PRESTUB_SIZE 24 +#define COMMETHOD_CALL_PRESTUB_ADDRESS_OFFSET 16 // the offset of the call target address inside the prestub +#endif // FEATURE_COMINTEROP + +#define STACK_ALIGN_SIZE 16 + +#define JUMP_ALLOCATE_SIZE 40 // # bytes to allocate for a jump instruction +#define BACK_TO_BACK_JUMP_ALLOCATE_SIZE 40 // # bytes to allocate for a back to back jump instruction + +#define HAS_NDIRECT_IMPORT_PRECODE 1 + +#define USE_INDIRECT_CODEHEADER + +#define HAS_FIXUP_PRECODE 1 +#define HAS_FIXUP_PRECODE_CHUNKS 1 + +// ThisPtrRetBufPrecode one is necessary for closed delegates over static methods with return buffer +#define HAS_THISPTR_RETBUF_PRECODE 1 + +#define CODE_SIZE_ALIGN 8 +#define CACHE_LINE_SIZE 64 +#define LOG2SLOT LOG2_PTRSIZE + +#define ENREGISTERED_RETURNTYPE_MAXSIZE 16 // bytes (two FP registers: f0 and f1) +#define ENREGISTERED_RETURNTYPE_INTEGER_MAXSIZE 16 // bytes (two int registers: v0 and v1) +#define ENREGISTERED_PARAMTYPE_MAXSIZE 16 // bytes (max value type size that can be passed by value) + +#define CALLDESCR_ARGREGS 1 // CallDescrWorker has ArgumentRegister parameter +#define CALLDESCR_FPARGREGS 1 // CallDescrWorker has FloatArgumentRegisters parameter + +#define FLOAT_REGISTER_SIZE 16 // each register in FloatArgumentRegisters is 16 bytes. Loongarch size is ?? + +// Given a return address retrieved during stackwalk, +// this is the offset by which it should be decremented to arrive at the callsite. +#define STACKWALK_CONTROLPC_ADJUST_OFFSET 8 + +//********************************************************************** +// Parameter size +//********************************************************************** + +inline unsigned StackElemSize(unsigned parmSize, bool isValueType, bool isFloatHfa) +{ + const unsigned stackSlotSize = 8; + return ALIGN_UP(parmSize, stackSlotSize); +} + +// +// JIT HELPERS. +// +// Create alias for optimized implementations of helpers provided on this platform +// +#define JIT_GetSharedGCStaticBase JIT_GetSharedGCStaticBase_SingleAppDomain +#define JIT_GetSharedNonGCStaticBase JIT_GetSharedNonGCStaticBase_SingleAppDomain +#define JIT_GetSharedGCStaticBaseNoCtor JIT_GetSharedGCStaticBaseNoCtor_SingleAppDomain +#define JIT_GetSharedNonGCStaticBaseNoCtor JIT_GetSharedNonGCStaticBaseNoCtor_SingleAppDomain + + +//********************************************************************** +// Frames +//********************************************************************** + +//-------------------------------------------------------------------- +// This represents the callee saved (non-volatile) registers saved as +// of a FramedMethodFrame. +//-------------------------------------------------------------------- +typedef DPTR(struct CalleeSavedRegisters) PTR_CalleeSavedRegisters; +struct CalleeSavedRegisters { + INT64 fp; // frame pointer. + INT64 ra; // return register + INT64 s0; + INT64 s1; + INT64 s2; + INT64 s3; + INT64 s4; + INT64 s5; + INT64 s6; + INT64 s7; + INT64 s8; + INT64 tp; +}; + +//-------------------------------------------------------------------- +// This represents the arguments that are stored in volatile registers. +// This should not overlap the CalleeSavedRegisters since those are already +// saved separately and it would be wasteful to save the same register twice. +// If we do use a non-volatile register as an argument, then the ArgIterator +// will probably have to communicate this back to the PromoteCallerStack +// routine to avoid a double promotion. +//-------------------------------------------------------------------- +typedef DPTR(struct ArgumentRegisters) PTR_ArgumentRegisters; +struct ArgumentRegisters { + INT64 a[8]; // a0 ....a7 +}; +#define NUM_ARGUMENT_REGISTERS 8 + +#define ARGUMENTREGISTERS_SIZE sizeof(ArgumentRegisters) + + +//-------------------------------------------------------------------- +// This represents the floating point argument registers which are saved +// as part of the NegInfo for a FramedMethodFrame. Note that these +// might not be saved by all stubs: typically only those that call into +// C++ helpers will need to preserve the values in these volatile +// registers. +//-------------------------------------------------------------------- +typedef DPTR(struct FloatArgumentRegisters) PTR_FloatArgumentRegisters; +struct FloatArgumentRegisters { + //TODO: not supports LOONGARCH-SIMD. + double f[8]; // f0-f7 +}; +#define NUM_FLOAT_ARGUMENT_REGISTERS 8 + + +//********************************************************************** +// Exception handling +//********************************************************************** + +inline PCODE GetIP(const T_CONTEXT * context) { + LIMITED_METHOD_DAC_CONTRACT; + return context->Pc; +} + +inline void SetIP(T_CONTEXT *context, PCODE ip) { + LIMITED_METHOD_DAC_CONTRACT; + context->Pc = ip; +} + +inline TADDR GetSP(const T_CONTEXT * context) { + LIMITED_METHOD_DAC_CONTRACT; + return TADDR(context->Sp); +} + +inline TADDR GetRA(const T_CONTEXT * context) { + LIMITED_METHOD_DAC_CONTRACT; + return context->Ra; +} + +inline void SetRA(T_CONTEXT * context, TADDR ip) { + LIMITED_METHOD_DAC_CONTRACT; + context->Ra = ip; +} + +extern "C" LPVOID __stdcall GetCurrentSP(); + +inline void SetSP(T_CONTEXT *context, TADDR sp) { + LIMITED_METHOD_DAC_CONTRACT; + context->Sp = DWORD64(sp); +} + +inline void SetFP(T_CONTEXT *context, TADDR fp) { + LIMITED_METHOD_DAC_CONTRACT; + context->Fp = DWORD64(fp); +} + +inline TADDR GetFP(const T_CONTEXT * context) +{ + LIMITED_METHOD_DAC_CONTRACT; + return (TADDR)(context->Fp); +} + +inline TADDR GetMem(PCODE address, SIZE_T size, bool signExtend) +{ + TADDR mem; + LIMITED_METHOD_DAC_CONTRACT; + EX_TRY + { + switch (size) + { + case 4: + if (signExtend) + mem = *(int32_t*)address; + else + mem = *(uint32_t*)address; + break; + case 8: + mem = *(uint64_t*)address; + break; + default: + UNREACHABLE(); + } + } + EX_CATCH + { + _ASSERTE(!"Memory read within jitted Code Failed, this should not happen!!!!"); + } + EX_END_CATCH(SwallowAllExceptions); + return mem; +} + + +#ifdef FEATURE_COMINTEROP +void emitCOMStubCall (ComCallMethodDesc *pCOMMethodRX, ComCallMethodDesc *pCOMMethodRW, PCODE target); +#endif // FEATURE_COMINTEROP + +inline BOOL ClrFlushInstructionCache(LPCVOID pCodeAddr, size_t sizeOfCode) +{ + return FlushInstructionCache(GetCurrentProcess(), pCodeAddr, sizeOfCode); +} + +//------------------------------------------------------------------------ +inline void emitJump(LPBYTE pBufferRX, LPBYTE pBufferRW, LPVOID target) +{ + LIMITED_METHOD_CONTRACT; + UINT32* pCode = (UINT32*)pBufferRW; + + // We require 8-byte alignment so the LD instruction is aligned properly + _ASSERTE(((UINT_PTR)pCode & 7) == 0); + + // pcaddi $r21,4 + // ld.d $r21,$r21,0 + // jirl $r0,$r21,0 + // nop //padding. + + pCode[0] = 0x18000095; //pcaddi $r21,4 + pCode[1] = 0x28c002b5; //ld.d $r21,$r21,0 + pCode[2] = 0x4c0002a0; //jirl $r0,$r21,0 + pCode[3] = 0x03400000; //padding nop. Also used for isJump. + + // Ensure that the updated instructions get updated in the I-Cache + ClrFlushInstructionCache(pBufferRX, 16); + + *((LPVOID *)(pCode + 4)) = target; // 64-bit target address + +} + +//------------------------------------------------------------------------ +// Given the same pBuffer that was used by emitJump this method +// decodes the instructions and returns the jump target +inline PCODE decodeJump(PCODE pCode) +{ + LIMITED_METHOD_CONTRACT; + + TADDR pInstr = PCODEToPINSTR(pCode); + + return *dac_cast(pInstr + 16); +} + +//------------------------------------------------------------------------ +inline BOOL isJump(PCODE pCode) +{ + LIMITED_METHOD_DAC_CONTRACT; + + TADDR pInstr = PCODEToPINSTR(pCode); + + return *dac_cast(pInstr+12) == 0x03400000; //nop +} + +//------------------------------------------------------------------------ +inline BOOL isBackToBackJump(PCODE pBuffer) +{ + WRAPPER_NO_CONTRACT; + SUPPORTS_DAC; + return isJump(pBuffer); +} + +//------------------------------------------------------------------------ +inline void emitBackToBackJump(LPBYTE pBufferRX, LPBYTE pBufferRW, LPVOID target) +{ + WRAPPER_NO_CONTRACT; + emitJump(pBufferRX, pBufferRW, target); +} + +//------------------------------------------------------------------------ +inline PCODE decodeBackToBackJump(PCODE pBuffer) +{ + WRAPPER_NO_CONTRACT; + return decodeJump(pBuffer); +} + + +//---------------------------------------------------------------------- + +struct IntReg +{ + int reg; + IntReg(int reg):reg(reg) + { + _ASSERTE(0 <= reg && reg < 32); + } + + operator int () { return reg; } + operator int () const { return reg; } + int operator == (IntReg other) { return reg == other.reg; } + int operator != (IntReg other) { return reg != other.reg; } + WORD Mask() const { return 1 << reg; } +}; + +struct FloatReg +{ + int reg; + FloatReg(int reg):reg(reg) + { + _ASSERTE(0 <= reg && reg < 32); + } + + operator int () { return reg; } + operator int () const { return reg; } + int operator == (FloatReg other) { return reg == other.reg; } + int operator != (FloatReg other) { return reg != other.reg; } + WORD Mask() const { return 1 << reg; } +}; + +struct VecReg +{ + int reg; + VecReg(int reg):reg(reg) + { + _ASSERTE(0 <= reg && reg < 32); + } + + operator int() { return reg; } + int operator == (VecReg other) { return reg == other.reg; } + int operator != (VecReg other) { return reg != other.reg; } + WORD Mask() const { return 1 << reg; } +}; + +const IntReg RegSp = IntReg(3); +const IntReg RegFp = IntReg(22); +const IntReg RegRa = IntReg(1); + +#define GetEEFuncEntryPoint(pfn) GFN_TADDR(pfn) + +class StubLinkerCPU : public StubLinker +{ + +private: + void EmitLoadStoreRegPairImm(DWORD flags, int regNum1, int regNum2, IntReg Rn, int offset, BOOL isVec); + void EmitLoadStoreRegImm(DWORD flags, int regNum, IntReg Rn, int offset, BOOL isVec, int log2Size = 3); +public: + + // BitFlags for EmitLoadStoreReg(Pair)Imm methods + enum { + eSTORE = 0x0, + eLOAD = 0x1, + }; + + static void Init(); + static bool isValidSimm12(int value) { + return -( ((int)1) << 11 ) <= value && value < ( ((int)1) << 11 ); + } + static int emitIns_O_R_R_I(int op, int rd, int rj, int imm) { + _ASSERTE(isValidSimm12(imm)); + _ASSERTE(!(rd >> 5)); + _ASSERTE(!(rj >> 5)); + _ASSERTE(op < 0x400); + return ((op & 0x3ff)<<22) | ((rj & 0x1f)<<5) | (rd & 0x1f) | ((imm & 0xfff)<<10); + } + + void EmitCallManagedMethod(MethodDesc *pMD, BOOL fTailCall); + void EmitCallLabel(CodeLabel *target, BOOL fTailCall, BOOL fIndirect); + + void EmitShuffleThunk(struct ShuffleEntry *pShuffleEntryArray); + + void EmitNop() { Emit32(0x03400000); } + void EmitBreakPoint() { Emit32(0x002a0000); } + +#if defined(FEATURE_SHARE_GENERIC_CODE) + void EmitComputedInstantiatingMethodStub(MethodDesc* pSharedMD, struct ShuffleEntry *pShuffleEntryArray, void* extraArg); +#endif // FEATURE_SHARE_GENERIC_CODE + + void EmitMovConstant(IntReg Rd, UINT64 constant); + void EmitJumpRegister(IntReg regTarget); + void EmitMovReg(IntReg dest, IntReg source); + void EmitMovFloatReg(FloatReg Fd, FloatReg Fs); + + void EmitSubImm(IntReg Rd, IntReg Rn, unsigned int value); + void EmitAddImm(IntReg Rd, IntReg Rn, unsigned int value); + + void EmitLoadStoreRegPairImm(DWORD flags, IntReg Rt1, IntReg Rt2, IntReg Rn, int offset=0); + void EmitLoadStoreRegPairImm(DWORD flags, VecReg Vt1, VecReg Vt2, IntReg Xn, int offset=0); + + void EmitLoadStoreRegImm(DWORD flags, IntReg Rt, IntReg Rn, int offset=0, int log2Size = 3); + +#if defined(TARGET_LOONGARCH64) + void EmitFloatLoadStoreRegImm(DWORD flags, FloatReg Ft, IntReg Xn, int offset=0); +#else + void EmitLoadStoreRegImm(DWORD flags, VecReg Vt, IntReg Xn, int offset=0); +#endif + void EmitLoadFloatRegImm(FloatReg ft, IntReg base, int offset); +}; + +extern "C" void SinglecastDelegateInvokeStub(); + + +// preferred alignment for data +#define DATA_ALIGNMENT 8 + +struct DECLSPEC_ALIGN(16) UMEntryThunkCode +{ + DWORD m_code[4]; + + TADDR m_pTargetCode; + TADDR m_pvSecretParam; + + void Encode(UMEntryThunkCode *pEntryThunkCodeRX, BYTE* pTargetCode, void* pvSecretParam); + void Poison(); + + LPCBYTE GetEntryPoint() const + { + LIMITED_METHOD_CONTRACT; + + return (LPCBYTE)this; + } + + static int GetEntryPointOffset() + { + LIMITED_METHOD_CONTRACT; + + return 0; + } +}; + +struct HijackArgs +{ + union + { + struct { + DWORD64 V0; + DWORD64 V1; + }; + size_t ReturnValue[2]; + }; + union + { + struct { + DWORD64 F0; + DWORD64 F1; + }; + size_t FPReturnValue[2]; + }; + DWORD64 S0, S1, S2, S3, S4, S5, S6, S7, S8, Tp; + DWORD64 Fp; // frame pointer + union + { + DWORD64 Ra; + size_t ReturnAddress; + }; +}; + +EXTERN_C VOID STDCALL PrecodeFixupThunk(); + +// Precode to shuffle this and retbuf for closed delegates over static methods with return buffer +struct ThisPtrRetBufPrecode { + + static const int Type = 2;//2, for Type encoding. + + UINT32 m_rgCode[6]; + TADDR m_pTarget; + TADDR m_pMethodDesc; + + void Init(MethodDesc* pMD, LoaderAllocator *pLoaderAllocator); + + TADDR GetMethodDesc() + { + LIMITED_METHOD_DAC_CONTRACT; + + return m_pMethodDesc; + } + + PCODE GetTarget() + { + LIMITED_METHOD_DAC_CONTRACT; + return m_pTarget; + } + +#ifndef DACCESS_COMPILE + BOOL SetTargetInterlocked(TADDR target, TADDR expected) + { + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + } + CONTRACTL_END; + + ExecutableWriterHolder precodeWriterHolder(this, sizeof(ThisPtrRetBufPrecode)); + return (TADDR)InterlockedCompareExchange64( + (LONGLONG*)&precodeWriterHolder.GetRW()->m_pTarget, (TADDR)target, (TADDR)expected) == expected; + } +#endif // !DACCESS_COMPILE +}; +typedef DPTR(ThisPtrRetBufPrecode) PTR_ThisPtrRetBufPrecode; + +#endif // __cgencpu_h__ diff --git a/src/coreclr/vm/loongarch64/crthelpers.S b/src/coreclr/vm/loongarch64/crthelpers.S new file mode 100644 index 0000000000000..88fd21938fdaa --- /dev/null +++ b/src/coreclr/vm/loongarch64/crthelpers.S @@ -0,0 +1,37 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include "unixasmmacros.inc" + +// JIT_MemSet/JIT_MemCpy +// +// It is IMPORTANT that the exception handling code is able to find these guys +// on the stack, but on non-windows platforms we can just defer to the platform +// implementation. +// +LEAF_ENTRY JIT_MemSet, _TEXT + beq $a2, $zero, LOCAL_LABEL(JIT_MemSet_ret) + + ld.b $zero, $a0, 0 //Is this really needed ? + + b memset + +LOCAL_LABEL(JIT_MemSet_ret): + jirl $r0, $ra, 0 + +////NOTO: Here must use LEAF_END_MARKED! not LEAF_END !!! +LEAF_END_MARKED JIT_MemSet, _TEXT + +LEAF_ENTRY JIT_MemCpy, _TEXT + beq $a2, $zero, LOCAL_LABEL(JIT_MemCpy_ret) + + ld.b $zero, $a0, 0 + ld.b $zero, $a1, 0 //Is this really needed ? + + b memcpy + +LOCAL_LABEL(JIT_MemCpy_ret): + jirl $r0, $ra, 0 + +////NOTO: Here must use LEAF_END_MARKED! not LEAF_END !!! +LEAF_END_MARKED JIT_MemCpy, _TEXT diff --git a/src/coreclr/vm/loongarch64/excepcpu.h b/src/coreclr/vm/loongarch64/excepcpu.h new file mode 100644 index 0000000000000..14a8fd19dd8b3 --- /dev/null +++ b/src/coreclr/vm/loongarch64/excepcpu.h @@ -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. + +#ifndef __excepcpu_h__ +#define __excepcpu_h__ + + +#define THROW_CONTROL_FOR_THREAD_FUNCTION RedirectForThreadAbort +EXTERN_C void RedirectForThreadAbort(); + + +#define STATUS_CLR_GCCOVER_CODE STATUS_ILLEGAL_INSTRUCTION + +class Thread; +class FaultingExceptionFrame; + +#define INSTALL_EXCEPTION_HANDLING_RECORD(record) +#define UNINSTALL_EXCEPTION_HANDLING_RECORD(record) +// On LOONGARCH, the COMPlusFrameHandler's work is done by our personality routine. ??? +// +#define DECLARE_CPFH_EH_RECORD(pCurThread) + +// +// Retrieves the redirected CONTEXT* from the stack frame of one of the +// RedirectedHandledJITCaseForXXX_Stub's. +// +PTR_CONTEXT GetCONTEXTFromRedirectedStubStackFrame(T_DISPATCHER_CONTEXT * pDispatcherContext); +PTR_CONTEXT GetCONTEXTFromRedirectedStubStackFrame(T_CONTEXT * pContext); + +// +// Retrieves the FaultingExceptionFrame* from the stack frame of +// RedirectForThrowControl or NakedThrowHelper. +// +FaultingExceptionFrame *GetFrameFromRedirectedStubStackFrame (T_DISPATCHER_CONTEXT *pDispatcherContext); + +inline +PCODE GetAdjustedCallAddress(PCODE returnAddress) +{ + LIMITED_METHOD_CONTRACT; + + return returnAddress - 4; +} + +BOOL AdjustContextForVirtualStub(EXCEPTION_RECORD *pExceptionRecord, T_CONTEXT *pContext); + +#endif // __excepcpu_h__ diff --git a/src/coreclr/vm/loongarch64/gmscpu.h b/src/coreclr/vm/loongarch64/gmscpu.h new file mode 100644 index 0000000000000..01420a8157162 --- /dev/null +++ b/src/coreclr/vm/loongarch64/gmscpu.h @@ -0,0 +1,102 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +/**************************************************************/ +/* gmscpu.h */ +/**************************************************************/ +/* HelperFrame is defines 'GET_STATE(machState)' macro, which + figures out what the state of the machine will be when the + current method returns. It then stores the state in the + JIT_machState structure. */ + +/**************************************************************/ + +#ifndef __gmscpu_h__ +#define __gmscpu_h__ + +#define __gmscpu_h__ + +// S0 - S8, FP, TP +#define NUM_CALLEESAVED_REGISTERS 11 + +struct MachState { + ULONG64 captureCalleeSavedRegisters[NUM_CALLEESAVED_REGISTERS]; // preserved registers + PTR_ULONG64 ptrCalleeSavedRegisters[NUM_CALLEESAVED_REGISTERS]; // pointers to preserved registers + TADDR _pc; // program counter after the function returns + TADDR _sp; // stack pointer after the function returns + BOOL _isValid; + + BOOL isValid() { LIMITED_METHOD_DAC_CONTRACT; return _isValid; } + TADDR GetRetAddr() { LIMITED_METHOD_DAC_CONTRACT; return _pc; } +}; + +struct LazyMachState : public MachState{ + + TADDR captureSp; // Stack pointer at the time of capture + TADDR captureIp; // Instruction pointer at the time of capture + + void setLazyStateFromUnwind(MachState* copy); + static void unwindLazyState(LazyMachState* baseState, + MachState* lazyState, + DWORD threadId, + int funCallDepth = 1, + HostCallPreference hostCallPreference = AllowHostCalls); +}; + +inline void LazyMachState::setLazyStateFromUnwind(MachState* copy) +{ +#if defined(DACCESS_COMPILE) + // This function cannot be called in DAC because DAC cannot update target memory. + DacError(E_FAIL); + return; + +#else // !DACCESS_COMPILE + + _sp = copy->_sp; + _pc = copy->_pc; + + // Capture* has already been set, so there is no need to touch it + + // loop over the nonvolatile context pointers and make + // sure to properly copy interior pointers into the + // new struct + + PULONG64* pSrc = (PULONG64 *)©->ptrCalleeSavedRegisters; + PULONG64* pDst = (PULONG64 *)&this->ptrCalleeSavedRegisters; + + const PULONG64 LowerBoundDst = (PULONG64) this; + const PULONG64 LowerBoundSrc = (PULONG64) copy; + + const PULONG64 UpperBoundSrc = (PULONG64) ((BYTE*)LowerBoundSrc + sizeof(*copy)); + + for (int i = 0; i < NUM_CALLEESAVED_REGISTERS; i++) + { + PULONG64 valueSrc = *pSrc++; + + if ((LowerBoundSrc <= valueSrc) && (valueSrc < UpperBoundSrc)) + { + // make any pointer interior to 'src' interior to 'dst' + valueSrc = (PULONG64)((BYTE*)valueSrc - (BYTE*)LowerBoundSrc + (BYTE*)LowerBoundDst); + } + + *pDst++ = valueSrc; + captureCalleeSavedRegisters[i] = copy->captureCalleeSavedRegisters[i]; + } + + + // this has to be last because we depend on write ordering to + // synchronize the race implicit in updating this struct + VolatileStore(&_isValid, TRUE); +#endif // DACCESS_COMPILE +} + +// Do the initial capture of the machine state. This is meant to be +// as light weight as possible, as we may never need the state that +// we capture. +EXTERN_C void LazyMachStateCaptureState(struct LazyMachState *pState); + +#define CAPTURE_STATE(machState, ret) \ + LazyMachStateCaptureState(machState) + + +#endif diff --git a/src/coreclr/vm/loongarch64/pinvokestubs.S b/src/coreclr/vm/loongarch64/pinvokestubs.S new file mode 100644 index 0000000000000..48a4ec9cf668d --- /dev/null +++ b/src/coreclr/vm/loongarch64/pinvokestubs.S @@ -0,0 +1,193 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include "asmconstants.h" +#include "unixasmmacros.inc" + +// ------------------------------------------------------------------ +// Macro to generate PInvoke Stubs. +// $__PInvokeStubFuncName : function which calls the actual stub obtained from VASigCookie +// $__PInvokeGenStubFuncName : function which generates the IL stubs for PInvoke +// +// Params :- +// $FuncPrefix : prefix of the function name for the stub +// Eg. VarargPinvoke, GenericPInvokeCalli +// $VASigCookieReg : register which contains the VASigCookie +// $SaveFPArgs : "Yes" or "No" . For varidic functions FP Args are not present in FP regs +// So need not save FP Args registers for vararg Pinvoke + +.macro PINVOKE_STUB __PInvokeStubFuncName,__PInvokeGenStubFuncName,__PInvokeStubWorkerName,VASigCookieReg,HiddenArg,SaveFPArgs,ShiftLeftAndOrSecret=0 + + NESTED_ENTRY \__PInvokeStubFuncName, _TEXT, NoHandler + + // get the stub + ld.d $t0, \VASigCookieReg, VASigCookie__pNDirectILStub + + // if null goto stub generation + beq $t0, $zero, \__PInvokeGenStubFuncName + + .if (\ShiftLeftAndOrSecret == 1) + // + // We need to distinguish between a MethodDesc* and an unmanaged target. + // The way we do this is to shift the managed target to the left by one bit and then set the + // least significant bit to 1. This works because MethodDesc* are always 8-byte aligned. + // + slli.d \HiddenArg, \HiddenArg, 1 + ori \HiddenArg, \HiddenArg, 1 + .endif + + jirl $r0, $t0, 0 + + NESTED_END \__PInvokeStubFuncName, _TEXT + + NESTED_ENTRY \__PInvokeGenStubFuncName, _TEXT, NoHandler + + PROLOG_WITH_TRANSITION_BLOCK 0, 0, \SaveFPArgs + + // $a2 = Umanaged Target\MethodDesc + ori $a2, \HiddenArg, 0 + + // $a1 = VaSigCookie + ori $a1, \VASigCookieReg, 0 + + // $a0 = pTransitionBlock + addi.d $a0, $sp, __PWTB_TransitionBlock + + // save hidden arg + ori $s0, \HiddenArg, 0 + + // save VASigCookieReg + ori $s1, \VASigCookieReg, 0 + + bl C_FUNC(\__PInvokeStubWorkerName) + + // restore VASigCookieReg + ori \VASigCookieReg, $s1, 0 + + // restore hidden arg (method desc or unmanaged target) + ori \HiddenArg, $s0, 0 + + EPILOG_WITH_TRANSITION_BLOCK_TAILCALL + + EPILOG_BRANCH C_FUNC(\__PInvokeStubFuncName) + + NESTED_END \__PInvokeGenStubFuncName, _TEXT +.endm + +// ------------------------------------------------------------------ +// IN: +// InlinedCallFrame ($a0) = pointer to the InlinedCallFrame data, including the GS cookie slot (GS cookie right +// before actual InlinedCallFrame data) +// +// +NESTED_ENTRY JIT_PInvokeBegin, _TEXT, NoHandler + + // $fp,$ra + PROLOG_SAVE_REG_PAIR_INDEXED 22, 1, 32 + // $s0=23. + PROLOG_SAVE_REG 23, 16 //the stack slot at $sp+24 is empty for 16 byte alignment + + PREPARE_EXTERNAL_VAR s_gsCookie, $t0 + ld.d $t4, $t0, 0 + st.d $t4, $a0, 0 + addi.d $s0, $a0, SIZEOF__GSCookie + + // s0 = pFrame + // set first slot to the value of InlinedCallFrame::`vftable' (checked by runtime code) + PREPARE_EXTERNAL_VAR _ZTV16InlinedCallFrame, $t0 + addi.d $t4, $t0, 16 + st.d $t4, $s0, 0 + + st.d $zero, $s0, InlinedCallFrame__m_Datum + + addi.d $t0, $sp, 32 + st.d $t0, $s0, InlinedCallFrame__m_pCallSiteSP + st.d $ra, $s0, InlinedCallFrame__m_pCallerReturnAddress + + ld.d $t4, $sp, 0 + st.d $t4, $s0, InlinedCallFrame__m_pCalleeSavedFP + + // v0 = GetThread() + bl GetThreadHelper + + st.d $v0, $s0, InlinedCallFrame__m_pThread + + // pFrame->m_Next = pThread->m_pFrame; + ld.d $t4, $v0, Thread_m_pFrame + st.d $t4, $s0, Frame__m_Next + + // pThread->m_pFrame = pFrame; + st.d $s0, $v0, Thread_m_pFrame + + // pThread->m_fPreemptiveGCDisabled = 0 + st.w $zero, $v0, Thread_m_fPreemptiveGCDisabled + + EPILOG_RESTORE_REG 23, 16 //the stack slot at $sp+24 is empty for 16 byte alignment + EPILOG_RESTORE_REG_PAIR_INDEXED 22, 1, 32 + EPILOG_RETURN + +NESTED_END JIT_PInvokeBegin, _TEXT + +// ------------------------------------------------------------------ +// IN: +// InlinedCallFrame ($a0) = pointer to the InlinedCallFrame data, including the GS cookie slot (GS cookie right +// before actual InlinedCallFrame data) +// +// +LEAF_ENTRY JIT_PInvokeEnd, _TEXT + + addi.d $a0, $a0, SIZEOF__GSCookie + ld.d $a1, $a0, InlinedCallFrame__m_pThread + // $a0 = pFrame + // $a1 = pThread + + // pThread->m_fPreemptiveGCDisabled = 1 + ori $t4, $r0, 1 + st.w $t4, $a1, Thread_m_fPreemptiveGCDisabled + + // Check return trap + PREPARE_EXTERNAL_VAR g_TrapReturningThreads, $t0 + ld.w $t4, $t0, 0 + bne $t4, $zero, LOCAL_LABEL(RarePath) + + // pThread->m_pFrame = pFrame->m_Next + ld.d $t4, $a0, Frame__m_Next + st.d $t4, $a1, Thread_m_pFrame + + jirl $r0, $ra, 0 + +LOCAL_LABEL(RarePath): + b JIT_PInvokeEndRarePath + +LEAF_END JIT_PInvokeEnd, _TEXT + +// ------------------------------------------------------------------ +// VarargPInvokeStub & VarargPInvokeGenILStub +// There is a separate stub when the method has a hidden return buffer arg. +// +// in: +// $a0 = VASigCookie* +// $t2 = MethodDesc * +// +PINVOKE_STUB VarargPInvokeStub, VarargPInvokeGenILStub, VarargPInvokeStubWorker, $a0, $t2, 0 + + +// ------------------------------------------------------------------ +// GenericPInvokeCalliHelper & GenericPInvokeCalliGenILStub +// Helper for generic pinvoke calli instruction +// +// in: +// $t3 = VASigCookie* +// $t2 = Unmanaged target +// +PINVOKE_STUB GenericPInvokeCalliHelper, GenericPInvokeCalliGenILStub, GenericPInvokeCalliStubWorker, $t3, $t2, 1, 1 + +//// ------------------------------------------------------------------ +//// VarargPInvokeStub_RetBuffArg & VarargPInvokeGenILStub_RetBuffArg +//// Vararg PInvoke Stub when the method has a hidden return buffer arg +//// +//// in: +//// $a1 = VASigCookie* //not used ??? +//// $t2 = MethodDesc* +//// +//PINVOKE_STUB VarargPInvokeStub_RetBuffArg, VarargPInvokeGenILStub_RetBuffArg, VarargPInvokeStubWorker, $a1, t8, 0 diff --git a/src/coreclr/vm/loongarch64/profiler.cpp b/src/coreclr/vm/loongarch64/profiler.cpp new file mode 100644 index 0000000000000..4668e7b9d9d73 --- /dev/null +++ b/src/coreclr/vm/loongarch64/profiler.cpp @@ -0,0 +1,303 @@ +// 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" + +#ifdef PROFILING_SUPPORTED +#include "proftoeeinterfaceimpl.h" + +#define PROFILE_ENTER 1 +#define PROFILE_LEAVE 2 +#define PROFILE_TAILCALL 4 + +// Scratch space to store HFA return values (max 16 bytes) +#define PROFILE_PLATFORM_SPECIFIC_DATA_BUFFER_SIZE 16 + +typedef struct _PROFILE_PLATFORM_SPECIFIC_DATA +{ + void* Fp; + void* Pc; + void* x8; + ArgumentRegisters argumentRegisters; + FunctionID functionId; + FloatArgumentRegisters floatArgumentRegisters; + void* probeSp; + void* profiledSp; + void* hiddenArg; + UINT32 flags; + UINT32 unused; + BYTE buffer[PROFILE_PLATFORM_SPECIFIC_DATA_BUFFER_SIZE]; +} PROFILE_PLATFORM_SPECIFIC_DATA, *PPROFILE_PLATFORM_SPECIFIC_DATA; + +UINT_PTR ProfileGetIPFromPlatformSpecificHandle(void* pPlatformSpecificHandle) +{ + LIMITED_METHOD_CONTRACT; + + PROFILE_PLATFORM_SPECIFIC_DATA* pData = reinterpret_cast(pPlatformSpecificHandle); + return (UINT_PTR)pData->Pc; +} + +void ProfileSetFunctionIDInPlatformSpecificHandle(void* pPlatformSpecificHandle, FunctionID functionId) +{ + LIMITED_METHOD_CONTRACT; + + _ASSERTE(pPlatformSpecificHandle != nullptr); + _ASSERTE(functionId != 0); + + PROFILE_PLATFORM_SPECIFIC_DATA* pData = reinterpret_cast(pPlatformSpecificHandle); + pData->functionId = functionId; +} + +ProfileArgIterator::ProfileArgIterator(MetaSig* pSig, void* pPlatformSpecificHandle) + : m_argIterator(pSig) +{ + WRAPPER_NO_CONTRACT; + + _ASSERTE(pSig != nullptr); + _ASSERTE(pPlatformSpecificHandle != nullptr); + + m_handle = pPlatformSpecificHandle; + + PROFILE_PLATFORM_SPECIFIC_DATA* pData = reinterpret_cast(pPlatformSpecificHandle); +#ifdef _DEBUG + // Unwind a frame and get the SP for the profiled method to make sure it matches + // what the JIT gave us + + // Setup the context to represent the frame that called ProfileEnterNaked + CONTEXT ctx; + memset(&ctx, 0, sizeof(CONTEXT)); + + ctx.Sp = (DWORD64)pData->probeSp; + ctx.Fp = (DWORD64)pData->Fp; + ctx.Pc = (DWORD64)pData->Pc; + + // Walk up a frame to the caller frame (called the managed method which called ProfileEnterNaked) + Thread::VirtualUnwindCallFrame(&ctx); + + _ASSERTE(pData->profiledSp == (void*)ctx.Sp); +#endif + + // Get the hidden arg if there is one + MethodDesc* pMD = FunctionIdToMethodDesc(pData->functionId); + + if ((pData->hiddenArg == nullptr) && (pMD->RequiresInstArg() || pMD->AcquiresInstMethodTableFromThis())) + { + if ((pData->flags & PROFILE_ENTER) != 0) + { + if (pMD->AcquiresInstMethodTableFromThis()) + { + pData->hiddenArg = GetThis(); + } + else + { + // On ARM64 the generic instantiation parameter comes after the optional "this" pointer. + if (m_argIterator.HasThis()) + { + pData->hiddenArg = (void*)pData->argumentRegisters.a[1]; + } + else + { + pData->hiddenArg = (void*)pData->argumentRegisters.a[0]; + } + } + } + else + { + EECodeInfo codeInfo((PCODE)pData->Pc); + + // We want to pass the caller SP here. + pData->hiddenArg = EECodeManager::GetExactGenericsToken((SIZE_T)(pData->profiledSp), &codeInfo); + } + } +} + +ProfileArgIterator::~ProfileArgIterator() +{ + LIMITED_METHOD_CONTRACT; + + m_handle = nullptr; +} + +LPVOID ProfileArgIterator::GetNextArgAddr() +{ + WRAPPER_NO_CONTRACT; + + _ASSERTE(m_handle != nullptr); + + PROFILE_PLATFORM_SPECIFIC_DATA* pData = reinterpret_cast(m_handle); + + if ((pData->flags & (PROFILE_LEAVE | PROFILE_TAILCALL)) != 0) + { + _ASSERTE(!"GetNextArgAddr() - arguments are not available in leave and tailcall probes"); + return nullptr; + } + + int argOffset = m_argIterator.GetNextOffset(); + + if (argOffset == TransitionBlock::InvalidOffset) + { + return nullptr; + } + + if (TransitionBlock::IsFloatArgumentRegisterOffset(argOffset)) + { + return (LPBYTE)&pData->floatArgumentRegisters + (argOffset - TransitionBlock::GetOffsetOfFloatArgumentRegisters()); + } + + LPVOID pArg = nullptr; + + if (TransitionBlock::IsArgumentRegisterOffset(argOffset)) + { + pArg = (LPBYTE)&pData->argumentRegisters + (argOffset - TransitionBlock::GetOffsetOfArgumentRegisters()); + } + else + { + _ASSERTE(TransitionBlock::IsStackArgumentOffset(argOffset)); + + pArg = (LPBYTE)pData->profiledSp + (argOffset - TransitionBlock::GetOffsetOfArgs()); + } + + if (m_argIterator.IsArgPassedByRef()) + { + pArg = *(LPVOID*)pArg; + } + + return pArg; +} + +LPVOID ProfileArgIterator::GetHiddenArgValue(void) +{ + LIMITED_METHOD_CONTRACT; + + PROFILE_PLATFORM_SPECIFIC_DATA* pData = reinterpret_cast(m_handle); + + return pData->hiddenArg; +} + +LPVOID ProfileArgIterator::GetThis(void) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + PROFILE_PLATFORM_SPECIFIC_DATA* pData = (PROFILE_PLATFORM_SPECIFIC_DATA*)m_handle; + MethodDesc* pMD = FunctionIdToMethodDesc(pData->functionId); + + // We guarantee to return the correct "this" pointer in the enter probe. + // For the leave and tailcall probes, we only return a valid "this" pointer if it is the generics token. + if (pData->hiddenArg != nullptr) + { + if (pMD->AcquiresInstMethodTableFromThis()) + { + return pData->hiddenArg; + } + } + + if ((pData->flags & PROFILE_ENTER) != 0) + { + if (m_argIterator.HasThis()) + { + return (LPVOID)pData->argumentRegisters.a[0]; + } + } + + return nullptr; +} + +LPVOID ProfileArgIterator::GetReturnBufferAddr(void) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + PROFILE_PLATFORM_SPECIFIC_DATA* pData = reinterpret_cast(m_handle); + + if ((pData->flags & PROFILE_TAILCALL) != 0) + { + _ASSERTE(!"GetReturnBufferAddr() - return buffer address is not available in tailcall probe"); + return nullptr; + } + + if (m_argIterator.HasRetBuffArg()) + { + if ((pData->flags & PROFILE_ENTER) != 0) + { + return (LPVOID)pData->x8; + } + else + { + // On ARM64 there is no requirement for the method to preserve the value stored in x8. + // In order to workaround this JIT will explicitly return the return buffer address in x0. + _ASSERTE((pData->flags & PROFILE_LEAVE) != 0); + return (LPVOID)pData->argumentRegisters.a[0]; + } + } + + UINT fpReturnSize = m_argIterator.GetFPReturnSize(); + if (fpReturnSize != 0) + { + TypeHandle thReturnValueType; + m_argIterator.GetSig()->GetReturnTypeNormalized(&thReturnValueType); + if (!thReturnValueType.IsNull() && thReturnValueType.IsHFA()) + { + UINT hfaFieldSize = fpReturnSize / 4; + UINT totalSize = m_argIterator.GetSig()->GetReturnTypeSize(); + _ASSERTE(totalSize % hfaFieldSize == 0); + _ASSERTE(totalSize <= 16); + + BYTE *dest = pData->buffer; + for (UINT floatRegIdx = 0; floatRegIdx < totalSize / hfaFieldSize; ++floatRegIdx) + { + if (hfaFieldSize == 4) + { + *(UINT32*)dest = *(UINT32*)&pData->floatArgumentRegisters.f[floatRegIdx]; + dest += 4; + } + else if (hfaFieldSize == 8) + { + *(UINT64*)dest = *(UINT64*)&pData->floatArgumentRegisters.f[floatRegIdx]; + dest += 8; + } + else + { + _ASSERTE(!"unimplemented on LOONGARCH yet!"); +#if 0 + _ASSERTE(hfaFieldSize == 16); + *(NEON128*)dest = pData->floatArgumentRegisters.f[floatRegIdx]; + dest += 16; +#endif + } + + if (floatRegIdx > 8) + { + // There's only space for 8 arguments in buffer + _ASSERTE(FALSE); + break; + } + } + + return pData->buffer; + } + + return &pData->floatArgumentRegisters.f[0]; + } + + if (!m_argIterator.GetSig()->IsReturnTypeVoid()) + { + return &pData->argumentRegisters.a[0]; + } + + return nullptr; +} + +#undef PROFILE_ENTER +#undef PROFILE_LEAVE +#undef PROFILE_TAILCALL + +#endif // PROFILING_SUPPORTED diff --git a/src/coreclr/vm/loongarch64/stubs.cpp b/src/coreclr/vm/loongarch64/stubs.cpp new file mode 100644 index 0000000000000..1d2ca897cdc07 --- /dev/null +++ b/src/coreclr/vm/loongarch64/stubs.cpp @@ -0,0 +1,1966 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// +// File: stubs.cpp +// +// This file contains stub functions for unimplemented features need to +// run on the LOONGARCH64 platform. + +#include "common.h" +#include "dllimportcallback.h" +#include "comdelegate.h" +#include "asmconstants.h" +#include "virtualcallstub.h" +#include "jitinterface.h" +#include "ecall.h" + + + +#ifndef DACCESS_COMPILE +//----------------------------------------------------------------------- +// InstructionFormat for B.cond +//----------------------------------------------------------------------- +class ConditionalBranchInstructionFormat : public InstructionFormat +{ + + public: + ConditionalBranchInstructionFormat() : InstructionFormat(InstructionFormat::k32) + { + LIMITED_METHOD_CONTRACT; + } + + virtual UINT GetSizeOfInstruction(UINT refsize, UINT variationCode) + { + LIMITED_METHOD_CONTRACT; + + _ASSERTE(!"LOONGARCH64: not implementation on loongarch64!!!"); + _ASSERTE(refsize == InstructionFormat::k32); + + return 4; + } + + virtual UINT GetHotSpotOffset(UINT refsize, UINT variationCode) + { + WRAPPER_NO_CONTRACT; + return 0; + } + + + virtual BOOL CanReach(UINT refSize, UINT variationCode, BOOL fExternal, INT_PTR offset) + { + _ASSERTE(!fExternal || "LOONGARCH64:NYI - CompareAndBranchInstructionFormat::CanReach external"); + if (fExternal) + return false; + + if (offset < -1048576 || offset > 1048572) + return false; + return true; + } + ////TODO: add for LOONGARCH. unused now! + // B.