diff options
78 files changed, 3160 insertions, 1173 deletions
diff --git a/BuildToolsVersion.txt b/BuildToolsVersion.txt index a7dc94e773..f03b16e558 100644 --- a/BuildToolsVersion.txt +++ b/BuildToolsVersion.txt @@ -1 +1 @@ -1.0.26-prerelease-00613-01 +1.0.26-prerelease-00625-01 diff --git a/Documentation/building/crossgen.md b/Documentation/building/crossgen.md index 0b47a25508..c74a5e680a 100644 --- a/Documentation/building/crossgen.md +++ b/Documentation/building/crossgen.md @@ -63,3 +63,4 @@ The following are some of the command errors while creating or using native imag - "Could not load file or assembly 'mscorlib.dll' or one of its dependencies. The native image could not be loaded, because it was generated for use by a different version of the runtime. (Exception from HRESULT: 0x80131059)": This error indicates that there is a mismatch between CrossGen and mscorlib.ni.dll. Make sure to use CrossGen and mscorlib.ni.dll from the same build or NuGet package. - "Error: Could not load file or assembly '...' or one of its dependencies. The system cannot find the file specified. (Exception from HRESULT: 0x80070002)": CrossGen wasn't able to find a particular dependency that it needs. Verify that you have the assembly specified in the error message, and make sure its location is included in `/Platform_Assemblies_Paths`. - CoreCLR unable to initialize: While there are many possible causes of this error, one possibility is a mismatch between mscorlib.ni.dll and coreclr.dll (or libcoreclr.so). Make sure they come from the same build or NuGet package. +- "Unable to load Jit Compiler": Please get a copy of `clrjit.dll` (or `libclrjit.so` or `libclrjit.dylib`, depending on your platform), and place it in the same directory as CrossGen. You can either build `clrjit.dll` yourself, or get it from `Microsoft.NETCore.Jit` NuGet package. To avoid possible issues, please use `clrjit.dll` from the same build as `crossgen.exe` if possible. @@ -153,7 +153,7 @@ <ProjectUrl>https://dot.net</ProjectUrl> <!-- PreReleaseSuffix for packages published from closed build (e.g. CoreCLR for Arm32, APISet, etc) --> - <ExternalExpectedPrerelease>beta-24325-00</ExternalExpectedPrerelease> + <ExternalExpectedPrerelease>beta-24326-00</ExternalExpectedPrerelease> <!-- On Windows, MSbuild still runs against Desktop FX while it runs on .NET Core on non-Windows. this requires pulling in different packaging dependencies. diff --git a/functions.cmake b/functions.cmake index a61687e7a6..bbc571b46d 100644 --- a/functions.cmake +++ b/functions.cmake @@ -47,10 +47,10 @@ endfunction(convert_to_absolute_path) #Preprocess exports definition file function(preprocess_def_file inputFilename outputFilename) get_compile_definitions(PREPROCESS_DEFINITIONS) - + get_include_directories(ASM_INCLUDE_DIRECTORIES) add_custom_command( OUTPUT ${outputFilename} - COMMAND ${CMAKE_CXX_COMPILER} /P /EP /TC ${PREPROCESS_DEFINITIONS} /Fi${outputFilename} ${inputFilename} + COMMAND ${CMAKE_CXX_COMPILER} ${ASM_INCLUDE_DIRECTORIES} /P /EP /TC ${PREPROCESS_DEFINITIONS} /Fi${outputFilename} ${inputFilename} DEPENDS ${inputFilename} COMMENT "Preprocessing ${inputFilename}" ) @@ -185,4 +185,4 @@ function(_install) if(NOT DEFINED CLR_CROSS_COMPONENTS_BUILD) install(${ARGV}) endif() -endfunction()
\ No newline at end of file +endfunction() diff --git a/src/ToolBox/SOS/Strike/disasmARM.cpp b/src/ToolBox/SOS/Strike/disasmARM.cpp index a82d4b9b65..fc51e8c8a6 100644 --- a/src/ToolBox/SOS/Strike/disasmARM.cpp +++ b/src/ToolBox/SOS/Strike/disasmARM.cpp @@ -250,7 +250,6 @@ void ARMMachine::IsReturnAddress(TADDR retAddr, TADDR* whereCalled) const } } -#ifndef FEATURE_PAL // Return 0 for non-managed call. Otherwise return MD address. static TADDR MDForCall (TADDR callee) @@ -272,8 +271,14 @@ static TADDR MDForCall (TADDR callee) // Determine if a value is MT/MD/Obj static void HandleValue(TADDR value) { +#ifndef FEATURE_PAL // remove the thumb bit (if set) value = value & ~1; +#else + // set the thumb bit (if not set) + value = value | 1; +#endif //!FEATURE_PAL + // A MethodTable? if (IsMethodTable(value)) { @@ -336,8 +341,6 @@ static void HandleValue(TADDR value) } } -#endif // !FEATURE_PAL - /**********************************************************************\ * Routine Description: * * * @@ -355,7 +358,6 @@ void ARMMachine::Unassembly ( BOOL bSuppressLines, BOOL bDisplayOffsets) const { -#ifndef FEATURE_PAL ULONG_PTR PC = PCBegin; char line[1024]; char *ptr; @@ -383,6 +385,7 @@ void ARMMachine::Unassembly ( } } +#ifndef FEATURE_PAL // // Print out any GC information corresponding to the current instruction offset. // @@ -397,7 +400,7 @@ void ARMMachine::Unassembly ( SwitchToFiber(pGCEncodingInfo->pvGCTableFiber); } } - +#endif //!FEATURE_PAL // // Print out any EH info corresponding to the current offset // @@ -529,7 +532,6 @@ void ARMMachine::Unassembly ( ExtOut ("\n"); } -#endif // !FEATURE_PAL } #if 0 // @ARMTODO: Figure out how to extract this information under CoreARM diff --git a/src/debug/daccess/dacdbiimpl.cpp b/src/debug/daccess/dacdbiimpl.cpp index 8ff1ecd9da..9b17f4cd46 100644 --- a/src/debug/daccess/dacdbiimpl.cpp +++ b/src/debug/daccess/dacdbiimpl.cpp @@ -5238,6 +5238,11 @@ void DacDbiInterfaceImpl::Hijack( ctx.R1 = (DWORD)espRecord; ctx.R2 = (DWORD)reason; ctx.R3 = (DWORD)pData; +#elif defined(_TARGET_ARM64_) + ctx.X0 = (DWORD64)espContext; + ctx.X1 = (DWORD64)espRecord; + ctx.X2 = (DWORD64)reason; + ctx.X3 = (DWORD64)pData; #else PORTABILITY_ASSERT("CordbThread::HijackForUnhandledException is not implemented on this platform."); #endif diff --git a/src/debug/di/CMakeLists.txt b/src/debug/di/CMakeLists.txt index 8894813e73..abede90d2e 100644 --- a/src/debug/di/CMakeLists.txt +++ b/src/debug/di/CMakeLists.txt @@ -34,11 +34,30 @@ if(WIN32) #use static crt add_definitions(-MT) + if (CLR_CMAKE_TARGET_ARCH_AMD64 OR CLR_CMAKE_TARGET_ARCH_ARM64) + set(CORDBDI_SOURCES_ASM_FILE ${ARCH_SOURCES_DIR}/floatconversion.asm) + endif() if (CLR_CMAKE_TARGET_ARCH_AMD64) set(CORDBDI_SOURCES ${CORDBDI_SOURCES} - ${ARCH_SOURCES_DIR}/floatconversion.asm + ${CORDBDI_SOURCES_ASM_FILE} ) + elseif (CLR_CMAKE_TARGET_ARCH_ARM64 AND NOT DEFINED CLR_CROSS_COMPONENTS_BUILD) + convert_to_absolute_path(CORDBDI_SOURCES_ASM_FILE ${CORDBDI_SOURCES_ASM_FILE}) + get_compile_definitions(ASM_DEFINITIONS) + set(ASM_OPTIONS /c /Zi /W3 /errorReport:prompt) + # asm files require preprocessing using cl.exe on arm64 + get_filename_component(name ${CORDBDI_SOURCES_ASM_FILE} NAME_WE) + set(ASM_PREPROCESSED_FILE ${CMAKE_CURRENT_BINARY_DIR}/${name}.asm) + preprocess_def_file(${CORDBDI_SOURCES_ASM_FILE} ${ASM_PREPROCESSED_FILE}) + set(CORDBDI_SOURCES_WKS_PREPROCESSED_ASM ${ASM_PREPROCESSED_FILE}) + + set_property(SOURCE ${CORDBDI_SOURCES_WKS_PREPROCESSED_ASM} PROPERTY COMPILE_DEFINITIONS ${ASM_DEFINITIONS}) + set_property(SOURCE ${CORDBDI_SOURCES_WKS_PREPROCESSED_ASM} PROPERTY COMPILE_DEFINITIONS ${ASM_OPTIONS}) + set(CORDBDI_SOURCES + ${CORDBDI_SOURCES} + ${CORDBDI_SOURCES_WKS_PREPROCESSED_ASM} + ) endif() elseif(CLR_CMAKE_PLATFORM_UNIX) add_compile_options(-fPIC) @@ -54,4 +73,5 @@ endif(WIN32) add_precompiled_header(stdafx.h stdafx.cpp CORDBDI_SOURCES) + add_library_clr(cordbdi STATIC ${CORDBDI_SOURCES}) diff --git a/src/debug/di/DI.props b/src/debug/di/DI.props index ef787d74da..1d7336dab0 100644 --- a/src/debug/di/DI.props +++ b/src/debug/di/DI.props @@ -76,7 +76,11 @@ <CppCompile Include="@(SourcesPublish)" /> <CppCompile Include="@(SourcesShim)" /> <CppCompile Include="@(SourcesRightside)" /> - <AssembleAmd64 Condition="'$(BuildArchitecture)' == 'amd64'" Include="..\amd64\floatconversion.asm" /> + <AssembleAmd64 Condition="'$(BuildArchitecture)' == 'amd64' and '$(CrossTargetArchitecture)' != 'arm64'" Include="..\amd64\floatconversion.asm" /> + </ItemGroup> + <ItemGroup Condition="'$(BuildArchitecture)' == 'arm64'"> + <PreprocessAssembleArm Include="..\arm64\floatconversion.asm" /> + <AssembleArm64 Include="$(IntermediateOutputDirectory)\floatconversion.i" /> </ItemGroup> <!--Import the targets--> </Project> diff --git a/src/debug/di/arm64/cordbregisterset.cpp b/src/debug/di/arm64/cordbregisterset.cpp index 9a7e1c140f..eab5ba403c 100644 --- a/src/debug/di/arm64/cordbregisterset.cpp +++ b/src/debug/di/arm64/cordbregisterset.cpp @@ -65,8 +65,8 @@ HRESULT CordbRegisterSet::GetRegisters(ULONG64 mask, ULONG32 regCount, // @ARM64TODO: floating point support - for (int i = REGISTER_ARM64_X0; - i <= REGISTER_ARM64_PC && iRegister < regCount; + for (int i = REGISTER_ARM64_PC; + i <= REGISTER_ARM64_LR && iRegister < regCount; i++) { if (mask & SETBITULONG64(i)) diff --git a/src/debug/di/arm64/floatconversion.asm b/src/debug/di/arm64/floatconversion.asm new file mode 100644 index 0000000000..e478fd10fd --- /dev/null +++ b/src/debug/di/arm64/floatconversion.asm @@ -0,0 +1,22 @@ +; Licensed to the .NET Foundation under one or more agreements. +; The .NET Foundation licenses this file to you under the MIT license. +; See the LICENSE file in the project root for more information. + +;; ==++== +;; + +;; +;; ==--== +#include "ksarm64.h" + +;; Arguments +;; input: (in X0) the _NEON128 value to be converted to a double +;; output: the double corresponding to the _NEON128 input value + + LEAF_ENTRY FPFillR8 + LDR Q0, [X0] + ret lr + LEAF_END + +;; Must be at very end of file + END diff --git a/src/debug/di/module.cpp b/src/debug/di/module.cpp index 78c7599455..6717e8575f 100644 --- a/src/debug/di/module.cpp +++ b/src/debug/di/module.cpp @@ -4114,9 +4114,9 @@ HRESULT CordbNativeCode::GetReturnValueLiveOffset(ULONG32 ILoffset, ULONG32 buff int CordbNativeCode::GetCallInstructionLength(BYTE *ip, ULONG32 count) { #if defined(DBG_TARGET_ARM) - return E_NOTIMPL; + return MAX_INSTRUCTION_LENGTH; #elif defined(DBG_TARGET_ARM64) - return E_NOTIMPL; + return MAX_INSTRUCTION_LENGTH; #elif defined(DBG_TARGET_X86) if (count < 2) return -1; diff --git a/src/debug/di/rspriv.h b/src/debug/di/rspriv.h index 2bee31471f..01e65ac9b4 100644 --- a/src/debug/di/rspriv.h +++ b/src/debug/di/rspriv.h @@ -6097,7 +6097,7 @@ public: // Converts the values in the floating point register area of the context to real number values. void Get32bitFPRegisters(CONTEXT * pContext); -#elif defined(DBG_TARGET_AMD64) +#elif defined(DBG_TARGET_AMD64) || defined(DBG_TARGET_ARM64) // Converts the values in the floating point register area of the context to real number values. void Get64bitFPRegisters(FPRegister64 * rgContextFPRegisters, int start, int nRegisters); #endif // DBG_TARGET_X86 diff --git a/src/debug/di/rsthread.cpp b/src/debug/di/rsthread.cpp index 633f68a747..ae9b43cd01 100644 --- a/src/debug/di/rsthread.cpp +++ b/src/debug/di/rsthread.cpp @@ -1432,8 +1432,20 @@ HRESULT CordbThread::FindFrame(ICorDebugFrame ** ppFrame, FramePointer fp) return E_FAIL; } + #if !defined(DBG_TARGET_ARM) // @ARMTODO +#if defined(CROSS_COMPILE) && defined(_TARGET_ARM64_) +extern "C" double FPFillR8(void* pFillSlot) +{ + _ASSERTE(!"nyi for platform"); + return 0; +} +#elif defined(_TARGET_AMD64_) || defined(_TARGET_ARM64_) +extern "C" double FPFillR8(void* pFillSlot); +#endif + + #if defined(_TARGET_X86_) // CordbThread::Get32bitFPRegisters @@ -1496,8 +1508,7 @@ void CordbThread::Get32bitFPRegisters(CONTEXT * pContext) m_floatStackTop = floatStackTop; } // CordbThread::Get32bitFPRegisters -#elif defined(_TARGET_AMD64_) -extern "C" double FPFillR8(void* pFillSlot); +#elif defined(_TARGET_AMD64_) || defined(_TARGET_ARM64_) // CordbThread::Get64bitFPRegisters // Converts the values in the floating point register area of the context to real number values. See @@ -1525,7 +1536,6 @@ void CordbThread::Get64bitFPRegisters(FPRegister64 * rgContextFPRegisters, int s } } // CordbThread::Get64bitFPRegisters - #endif // _TARGET_X86_ // CordbThread::LoadFloatState @@ -1556,6 +1566,10 @@ void CordbThread::LoadFloatState() #elif defined(_TARGET_AMD64_) // we have no fixed-value registers, so we begin with the first one and initialize all 16 Get64bitFPRegisters((FPRegister64*) &(tempContext.Xmm0), 0, 16); +#elif defined(_TARGET_ARM64_) + Get64bitFPRegisters((FPRegister64*) &(tempContext.V), 0, 32); +#else + _ASSERTE(!"nyi for platform"); #endif // !_TARGET_X86_ m_fFloatStateValid = true; @@ -6991,6 +7005,8 @@ HRESULT CordbNativeFrame::GetLocalRegisterValue(CorDebugRegister reg, if ((reg >= REGISTER_X86_FPSTACK_0) && (reg <= REGISTER_X86_FPSTACK_7)) #elif defined(DBG_TARGET_AMD64) if ((reg >= REGISTER_AMD64_XMM0) && (reg <= REGISTER_AMD64_XMM15)) +#elif defined(DBG_TARGET_ARM64) + if ((reg >= REGISTER_ARM64_V0) && (reg <= REGISTER_ARM64_V31)) #endif { return GetLocalFloatingPointValue(reg, pType, ppValue); @@ -7239,6 +7255,11 @@ HRESULT CordbNativeFrame::GetLocalFloatingPointValue(DWORD index, (index <= REGISTER_AMD64_XMM15))) return E_INVALIDARG; index -= REGISTER_AMD64_XMM0; +#elif defined(DBG_TARGET_ARM64) + if (!((index >= REGISTER_ARM64_V0) && + (index <= REGISTER_ARM64_V31))) + return E_INVALIDARG; + index -= REGISTER_ARM64_V0; #else if (!((index >= REGISTER_X86_FPSTACK_0) && (index <= REGISTER_X86_FPSTACK_7))) @@ -8941,20 +8962,25 @@ HRESULT CordbJITILFrame::GetReturnValueForILOffsetImpl(ULONG32 ILoffset, ICorDeb HRESULT CordbJITILFrame::GetReturnValueForType(CordbType *pType, ICorDebugValue **ppReturnValue) { -#if defined(DBG_TARGET_ARM) || defined(DBG_TARGET_ARM64) +#if defined(DBG_TARGET_ARM) return E_NOTIMPL; #else + #if defined(DBG_TARGET_X86) const CorDebugRegister floatRegister = REGISTER_X86_FPSTACK_0; #elif defined(DBG_TARGET_AMD64) const CorDebugRegister floatRegister = REGISTER_AMD64_XMM0; +#elif defined(DBG_TARGET_ARM64) + const CorDebugRegister floatRegister = REGISTER_ARM64_V0; #endif #if defined(DBG_TARGET_X86) const CorDebugRegister ptrRegister = REGISTER_X86_EAX; #elif defined(DBG_TARGET_AMD64) const CorDebugRegister ptrRegister = REGISTER_AMD64_RAX; +#elif defined(DBG_TARGET_ARM64) + const CorDebugRegister ptrRegister = REGISTER_ARM64_X0; #endif CorElementType corReturnType = pType->GetElementType(); diff --git a/src/debug/di/shimstackwalk.cpp b/src/debug/di/shimstackwalk.cpp index f3ea73bc7b..9be1ea1a78 100644 --- a/src/debug/di/shimstackwalk.cpp +++ b/src/debug/di/shimstackwalk.cpp @@ -1097,7 +1097,7 @@ void ShimStackWalk::AppendChain(ChainInfo * pChainInfo, StackWalkInfo * pStackWa // We need to send an extra enter-managed chain. _ASSERTE(pChainInfo->m_fLeafNativeContextIsValid); BYTE * sp = reinterpret_cast<BYTE *>(CORDbgGetSP(&(pChainInfo->m_leafNativeContext))); -#ifndef _TARGET_ARM_ +#if !defined(_TARGET_ARM_) && !defined(_TARGET_ARM64_) // Dev11 324806: on ARM we use the caller's SP for a frame's ending delimiter so we cannot // subtract 4 bytes from the chain's ending delimiter else the frame might never be in range. // TODO: revisit overlapping ranges on ARM, it would be nice to make it consistent with the other architectures. diff --git a/src/debug/ee/arm64/arm64walker.cpp b/src/debug/ee/arm64/arm64walker.cpp index b13f36f3df..96aff1708f 100644 --- a/src/debug/ee/arm64/arm64walker.cpp +++ b/src/debug/ee/arm64/arm64walker.cpp @@ -2,21 +2,475 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. //***************************************************************************** -// File: Amd64walker.cpp +// File: Arm64walker.cpp // // -// AMD64 instruction decoding/stepping logic +// ARM64 instruction decoding/stepping logic // //***************************************************************************** #include "stdafx.h" - #include "walker.h" - #include "frames.h" #include "openum.h" #ifdef _TARGET_ARM64_ +PCODE Expand19bitoffset(PCODE opcode) +{ + opcode = opcode >> 5; + PCODE offset = (opcode & 0x7FFFF) << 2; //imm19:00 -> 21 bits + + //Sign Extension + if ((offset & 0x100000)) //Check for 21'st bit + { + offset = offset | 0xFFFFFFFFFFE00000; + } + return offset; +} + +void NativeWalker::Decode() +{ + + PT_CONTEXT context = NULL; + int RegNum = -1; + PCODE offset = MAX_INSTRUCTION_LENGTH; + + //Reset so that we do not provide bogus info + m_type = WALK_UNKNOWN; + m_skipIP = NULL; + m_nextIP = NULL; + + if (m_registers == NULL) + { + //walker does not use WALK_NEXT + //Without registers decoding will work only for handful of instructions + return; + } + + m_skipIP = m_ip + MAX_INSTRUCTION_LENGTH; + + context = m_registers->pCurrentContext; + // Fetch first word of the current instruction.If the current instruction is a break instruction, we'll + // need to check the patch table to get the correct instruction. + PRD_TYPE opcode = CORDbgGetInstruction(m_ip); + PRD_TYPE unpatchedOpcode; + if (DebuggerController::CheckGetPatchedOpcode(m_ip, &unpatchedOpcode)) + { + opcode = unpatchedOpcode; + } + + LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Decode instruction at %p, opcode: %x\n", m_ip,opcode)); + + + + if (NativeWalker::DecodeCallInst(opcode, RegNum, m_type)) //Unconditional Branch (register) instructions + { + if (m_type == WALK_RETURN) + { + m_skipIP = NULL; + } + m_nextIP = (BYTE*)GetReg(context, RegNum); + return; + } + + + if (NativeWalker::DecodePCRelativeBranchInst(context, opcode, offset, m_type)) + { + if (m_type == WALK_BRANCH) + { + m_skipIP = NULL; + } + } + + m_nextIP = m_ip + offset; + + + return; +} + + +//When control reaches here m_pSharedPatchBypassBuffer has the original instructions in m_pSharedPatchBypassBuffer->PatchBypass +BYTE* NativeWalker::SetupOrSimulateInstructionForPatchSkip(T_CONTEXT * context, SharedPatchBypassBuffer* m_pSharedPatchBypassBuffer, const BYTE *address, PRD_TYPE opcode) +{ + + BYTE* patchBypass = m_pSharedPatchBypassBuffer->PatchBypass; + PCODE offset = 0; + PCODE ip = 0; + WALK_TYPE walk = WALK_UNKNOWN; + int RegNum =-1; + + + /* + Modify the patchBypass if the opcode is IP-relative, otherwise return it + The following are the instructions that are IP-relative : + • ADR and ADRP. + • The Load register (literal) instruction class. + • Direct branches that use an immediate offset. + • The unconditional branch with link instructions, BL and BLR, that use the PC to create the return link + address. + */ + + _ASSERTE((UINT_PTR)address == context->Pc); + + if ((opcode & 0x1F000000) == 0x10000000) //ADR & ADRP + { + + TADDR immhigh = ((opcode >> 5) & 0x007FFFF) << 2; + TADDR immlow = (opcode & 0x60000000) >> 29; + offset = immhigh | immlow; //ADR + RegNum = (opcode & 0x1F); + + //Sign Extension + if ((offset & 0x100000)) //Check for 21'st bit + { + offset = offset | 0xFFFFFFFFFFE00000; + } + + if ((opcode & 0x80000000) != 0) //ADRP + { + offset = offset << 12; + LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Simulate opcode: %x to ADRP X%d %p\n", opcode, RegNum, offset)); + } + else + { + LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Simulate opcode: %x to ADR X%d %p\n", opcode, RegNum, offset)); + } + + + } + + else if ((opcode & 0x3B000000) == 0x18000000) //LDR Literal (General or SIMD) + { + + offset = Expand19bitoffset(opcode); + RegNum = (opcode & 0x1F); + LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Simulate opcode: %x to LDR[SW] | PRFM X%d %p\n", opcode, RegNum, offset)); + } + else if (NativeWalker::DecodePCRelativeBranchInst(context,opcode, offset, walk)) + { + _ASSERTE(RegNum == -1); + } + else if (NativeWalker::DecodeCallInst(opcode, RegNum, walk)) + { + _ASSERTE(offset == 0); + } + //else Just execute the opcodes as is + //{ + //} + + if (offset != 0) // calculate the next ip from current ip + { + ip = (PCODE)address + offset; + } + else if(RegNum >= 0) + { + ip = GetReg(context, RegNum); + } + + //Do instruction emulation inplace here + + if (walk == WALK_BRANCH || walk == WALK_CALL || walk == WALK_RETURN) + { + CORDbgSetInstruction((CORDB_ADDRESS_TYPE *)patchBypass, 0xd503201f); //Add Nop in buffer + + m_pSharedPatchBypassBuffer->RipTargetFixup = ip; //Control Flow simulation alone is done DebuggerPatchSkip::TriggerExceptionHook + LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Simulate opcode: %x is a Control Flow instr \n", opcode)); + + if (walk == WALK_CALL) //initialize Lr + { + SetLR(context, (PCODE)address + MAX_INSTRUCTION_LENGTH); + LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Simulate opcode: %x is a Call instr, setting LR to %p \n", opcode,GetLR(context))); + } + } + else if(RegNum >= 0) + { + CORDbgSetInstruction((CORDB_ADDRESS_TYPE *)patchBypass, 0xd503201f); //Add Nop in buffer + + PCODE RegContents; + if ((opcode & 0x3B000000) == 0x18000000) //LDR Literal + { + RegContents = (PCODE)GetMem(ip); + if ((opcode & 0x4000000)) //LDR literal for SIMD + { + NEON128 SimdRegContents; + LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Simulate opcode: %x to LDR V%d %p\n", opcode, RegNum, offset)); + short opc = (opcode >> 30); + switch (opc) + { + case 0: //4byte data into St + RegContents = 0xFFFFFFFF & RegContents; //zero the upper 32bit + SetReg(context, RegNum, RegContents); + case 1: //8byte data into Dt + SetReg(context, RegNum, RegContents); + break; + + case 2: //SIMD 16 byte data + SimdRegContents = GetSimdMem(ip); + SetSimdReg(context, RegNum, SimdRegContents); + break; + default: + LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Simulate Unknown opcode: %x [LDR(litera,SIMD &FP)] \n", opcode)); + _ASSERTE(!("Arm64Walker::Simulated Unknown opcode")); + + } + } + else + { + short opc = (opcode >> 30); + switch (opc) + { + case 0: //4byte data into Wt + LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Simulate opcode: %x to LDR W%d %p\n", opcode, RegNum, offset)); + RegContents = 0xFFFFFFFF & RegContents; //zero the upper 32bits + SetReg(context, RegNum, RegContents); + break; + + case 1: //8byte data into Xt + LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Simulate opcode: %x to LDR X%d %p\n", opcode, RegNum, offset)); + SetReg(context, RegNum, RegContents); + break; + + case 2: //LDRSW + LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Simulate opcode: %x to LDRSW X%d %p\n", opcode, RegNum, offset)); + RegContents = 0xFFFFFFFF & RegContents; + + if (RegContents & 0x80000000) //Sign extend the Word + { + RegContents = 0xFFFFFFFF00000000 | RegContents; + } + SetReg(context, RegNum, RegContents); + break; + case 3: + LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Simulate opcode: %x as PRFM ,but do nothing \n", opcode)); + + break; + default: + LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Simulate Unknown opcode: %x [LDR(literal)] \n", opcode)); + _ASSERTE(!("Arm64Walker::Simulated Unknown opcode")); + + } + } + } + else + { + RegContents = ip; + SetReg(context, RegNum, RegContents); + } + + LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Simulate opcode: %x to update Reg X[V]%d, as %p \n", opcode, RegNum, GetReg(context, RegNum))); + } + //else Just execute the opcodes as IS + //{ + //} + + return patchBypass; +} + +//Decodes PC Relative Branch Instructions +//This code is shared between the NativeWalker and DebuggerPatchSkip. +//So ENSURE THIS FUNCTION DOES NOT CHANGE ANY STATE OF THE DEBUGEE +//This Function Decodes : +// BL offset +// B offset +// B.Cond offset +// CB[N]Z X<r> offset +// TB[N]Z X<r> offset + +//Output of the Function are: +//offset - Offset from current PC to which control will go next +//WALK_TYPE + +BOOL NativeWalker::DecodePCRelativeBranchInst(PT_CONTEXT context, const PRD_TYPE& opcode, PCODE& offset, WALK_TYPE& walk) +{ +#ifdef _DEBUG + PCODE incomingoffset = offset; + WALK_TYPE incomingwalk = walk; +#endif + + if ((opcode & 0x7C000000) == 0x14000000) // Decode B & BL + { + offset = (opcode & 0x03FFFFFF) << 2; + // Sign extension + if ((offset & 0x4000000)) //Check for 26'st bit + { + offset = offset | 0xFFFFFFFFF8000000; + } + + if ((opcode & 0x80000000) != 0) //BL + { + walk = WALK_CALL; + LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Decoded opcode: %x to BL %p \n", opcode, offset)); + } + else + { + walk = WALK_BRANCH; //B + LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Decoded opcode: %x to B %p \n", opcode, offset)); + } + return TRUE; + } + + //Conditional Branches + _ASSERTE(context != NULL); + + + if ((opcode & 0xFF000010) == 0x54000000) // B.cond + { + WORD cond = opcode & 0xF; + bool result = false; + switch (cond >> 1) + { + case 0x0: result = (context->Cpsr & NZCV_Z) != 0; // EQ or NE + break; + case 0x1: result = (context->Cpsr & NZCV_C) != 0; // CS or CC + break; + case 0x2: result = (context->Cpsr & NZCV_N) != 0; // MI or PL + break; + case 0x3: result = (context->Cpsr & NZCV_V) != 0; // VS or VC + break; + case 0x4: result = ((context->Cpsr & NZCV_C) != 0) && ((context->Cpsr & NZCV_Z) == 0); // HI or LS + break; + case 0x5: result = ((context->Cpsr & NZCV_N) >> NZCV_N_BIT) == ((context->Cpsr & NZCV_V) >> NZCV_V_BIT); // GE or LT + break; + case 0x6: result = ((context->Cpsr & NZCV_N) >> NZCV_N_BIT) == ((context->Cpsr & NZCV_V) >> NZCV_V_BIT) && ((context->Cpsr & NZCV_Z) == 0); // GT or LE + break; + case 0x7: result = true; // AL + break; + } + + if ((cond & 0x1) && (cond & 0xF) != 0) { result = !result; } + + if (result) + { + walk = WALK_BRANCH; + offset = Expand19bitoffset(opcode); + LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Decoded opcode: %x to B.cond %p \n", opcode, offset)); + } + else // NOP + { + walk = WALK_UNKNOWN; + LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Decoded opcode: %x to B.cond but evaluated as NOP \n", opcode)); + offset = MAX_INSTRUCTION_LENGTH; + } + + return TRUE; + + } + + + int RegNum = opcode & 0x1F; + PCODE RegContent = GetReg(context, RegNum); + + if ((opcode & 0xFE000000) == 0x34000000) // CBNZ || CBZ + { + bool result = false; + + if (!(opcode & 0x80000000)) //if sf == '1' the 64 else 32 + { + RegContent = 0xFFFFFFFF & RegContent; //zero the upper 32bit + } + + if (opcode & 0x01000000) //CBNZ + { + result = RegContent != 0; + LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Decoded opcode: %x to CBNZ X%d \n", opcode, RegNum)); + } + else //CBZ + { + result = RegContent == 0; + LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Decoded opcode: %x to CBZ X%d \n", opcode, RegNum)); + } + + if (result) + { + walk = WALK_BRANCH; + offset = Expand19bitoffset(opcode); + LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Decoded opcode: %x to CB[N]Z X%d %p \n", opcode, RegNum, offset)); + } + else // NOP + { + walk = WALK_UNKNOWN; + LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Decoded opcode: %x to B.cond but evaluated as NOP \n", opcode)); + offset = MAX_INSTRUCTION_LENGTH; + } + + + return TRUE; + } + if ((opcode & 0x7E000000) == 0x36000000) // TBNZ || TBZ + { + bool result = false; + int bit_pos = ((opcode >> 19) & 0x1F); + + if (opcode & 0x80000000) + { + bit_pos = bit_pos + 32; + } + + PCODE bit_val = 1 << bit_pos; + if (opcode & 0x01000000) //TBNZ + { + result = (RegContent & bit_val) != 0; + LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Decoded opcode: %x to TBNZ X%d \n", opcode, RegNum)); + } + else //TBZ + { + result = (RegContent & bit_val) == 0; + LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Decoded opcode: %x to CB[N]Z X%d \n", opcode, RegNum)); + } + if (result) + { + walk = WALK_BRANCH; + offset = ((opcode >> 5) & 0x3FFF) << 2; //imm14:00 -> 16 bits + if (offset & 0x8000) //sign extension check for 16'th bit + { + offset = offset | 0xFFFFFFFFFFFF0000; + } + LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Decoded opcode: %x to TB[N]Z X%d %p \n", opcode, RegNum, offset)); + } + else // NOP + { + walk = WALK_UNKNOWN; + LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Decoded opcode: %x to B.cond but evaluated as NOP \n", opcode)); + offset = MAX_INSTRUCTION_LENGTH; + } + + return TRUE; + } + + _ASSERTE(offset == incomingoffset); + _ASSERTE(walk == incomingwalk); + return FALSE; +} + +BOOL NativeWalker::DecodeCallInst(const PRD_TYPE& opcode, int& RegNum, WALK_TYPE& walk) +{ + if ((opcode & 0xFF9FFC1F) == 0xD61F0000) // BR, BLR or RET -Unconditional Branch (register) instructions + { + + RegNum = (opcode & 0x3E0) >> 5; + + + short op = (opcode & 0x00600000) >> 21; //Checking for 23 and 22 bits + switch (op) + { + case 0: LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Decoded opcode: %x to BR X%d\n", opcode, RegNum)); + walk = WALK_BRANCH; + break; + case 1: LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Decoded opcode: %x to BLR X%d\n", opcode, RegNum)); + walk = WALK_CALL; + break; + case 2: LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Decoded opcode: %x to Ret X%d\n", opcode, RegNum)); + walk = WALK_RETURN; + break; + default: + LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Simulate Unknown opcode: %x [Branch] \n", opcode)); + _ASSERTE(!("Arm64Walker::Decoded Unknown opcode")); + } + + return TRUE; + } + return FALSE; +} #endif diff --git a/src/debug/ee/arm64/dbghelpers.asm b/src/debug/ee/arm64/dbghelpers.asm new file mode 100644 index 0000000000..ded1a0d184 --- /dev/null +++ b/src/debug/ee/arm64/dbghelpers.asm @@ -0,0 +1,54 @@ +; Licensed to the .NET Foundation under one or more agreements. +; The .NET Foundation licenses this file to you under the MIT license. +; See the LICENSE file in the project root for more information. + +#include "ksarm64.h" +#include "asmconstants.h" +#include "asmmacros.h" + + IMPORT FuncEvalHijackWorker + IMPORT FuncEvalHijackPersonalityRoutine + IMPORT ExceptionHijackWorker + IMPORT ExceptionHijackPersonalityRoutine + EXPORT ExceptionHijackEnd +; +; hijacking stub used to perform a func-eval, see Debugger::FuncEvalSetup() for use. +; +; on entry: +; x0 : pointer to DebuggerEval object +; + + NESTED_ENTRY FuncEvalHijack,,FuncEvalHijackPersonalityRoutine + + ; NOTE: FuncEvalHijackPersonalityRoutine is dependent on the stack layout so if + ; you change the prolog you will also need to update the personality routine. + + ; push arg to the stack so our personality routine can find it + ; push lr to get good stacktrace in debugger + PROLOG_SAVE_REG_PAIR fp, lr, #-32! + str x0, [sp, #16] + ; FuncEvalHijackWorker returns the address we should jump to. + bl FuncEvalHijackWorker + + EPILOG_STACK_FREE 32 + EPILOG_BRANCH_REG x0 + NESTED_END FuncEvalHijack + + NESTED_ENTRY ExceptionHijack,,ExceptionHijackPersonalityRoutine + + ; make the call + bl ExceptionHijackWorker + + ; effective NOP to terminate unwind + mov x3, x3 + + ; *** should never get here *** + EMIT_BREAKPOINT + +; exported label so the debugger knows where the end of this function is +ExceptionHijackEnd + NESTED_END ExceptionHijack + + ; must be at end of file + END + diff --git a/src/debug/ee/controller.cpp b/src/debug/ee/controller.cpp index de19f7d6ba..7f4d44568d 100644 --- a/src/debug/ee/controller.cpp +++ b/src/debug/ee/controller.cpp @@ -1373,7 +1373,7 @@ bool DebuggerController::ApplyPatch(DebuggerControllerPatch *patch) patch->opcode = CORDbgGetInstruction(patch->address); CORDbgInsertBreakpoint((CORDB_ADDRESS_TYPE *)patch->address); - LOG((LF_CORDB, LL_EVERYTHING, "Breakpoint was inserted\n")); + LOG((LF_CORDB, LL_EVERYTHING, "Breakpoint was inserted at %p for opcode %x\n", patch->address, patch->opcode)); if (!VirtualProtect(baseAddress, CORDbg_BREAK_INSTRUCTION_SIZE, @@ -2531,7 +2531,7 @@ DPOSS_ACTION DebuggerController::ScanForTriggers(CORDB_ADDRESS_TYPE *address, CONTRACT_VIOLATION(ThrowsViolation); - LOG((LF_CORDB, LL_INFO10000, "DC::SFT: starting scan for addr:0x%x" + LOG((LF_CORDB, LL_INFO10000, "DC::SFT: starting scan for addr:0x%p" " thread:0x%x\n", address, thread)); _ASSERTE( pTpr != NULL ); @@ -4069,7 +4069,7 @@ bool DebuggerController::DispatchNativeException(EXCEPTION_RECORD *pException, CONTRACTL_END; LOG((LF_CORDB, LL_EVERYTHING, "DispatchNativeException was called\n")); - LOG((LF_CORDB, LL_INFO10000, "Native exception at 0x%x, code=0x%8x, context=0x%p, er=0x%p\n", + LOG((LF_CORDB, LL_INFO10000, "Native exception at 0x%p, code=0x%8x, context=0x%p, er=0x%p\n", pException->ExceptionAddress, dwCode, pContext, pException)); @@ -4290,7 +4290,7 @@ DebuggerPatchSkip::DebuggerPatchSkip(Thread *thread, m_address(patch->address) { LOG((LF_CORDB, LL_INFO10000, - "DPS::DPS: Patch skip 0x%x\n", patch->address)); + "DPS::DPS: Patch skip 0x%p\n", patch->address)); // On ARM the single-step emulation already utilizes a per-thread execution buffer similar to the scheme // below. As a result we can skip most of the instruction parsing logic that's instead internalized into @@ -4330,14 +4330,15 @@ DebuggerPatchSkip::DebuggerPatchSkip(Thread *thread, CORDbgSetInstruction((CORDB_ADDRESS_TYPE *)patchBypass, patch->opcode); LOG((LF_CORDB, LL_EVERYTHING, "SetInstruction was called\n")); - // // Look at instruction to get some attributes // NativeWalker::DecodeInstructionForPatchSkip(patchBypass, &(m_instrAttrib)); - + #if defined(_TARGET_AMD64_) + + // The code below handles RIP-relative addressing on AMD64. the original implementation made the assumption that // we are only using RIP-relative addressing to access read-only data (see VSW 246145 for more information). this // has since been expanded to handle RIP-relative writes as well. @@ -4392,7 +4393,7 @@ DebuggerPatchSkip::DebuggerPatchSkip(Thread *thread, // Set IP of context to point to patch bypass buffer // - CONTEXT *context = g_pEEInterface->GetThreadFilterContext(thread); + T_CONTEXT *context = g_pEEInterface->GetThreadFilterContext(thread); _ASSERTE(!ISREDIRECTEDTHREAD(thread)); CONTEXT c; if (context == NULL) @@ -4409,7 +4410,7 @@ DebuggerPatchSkip::DebuggerPatchSkip(Thread *thread, c.ContextFlags = CONTEXT_CONTROL; thread->GetThreadContext(&c); - context = &c; + context =(T_CONTEXT *) &c; ARM_ONLY(_ASSERTE(!"We should always have a filter context in DebuggerPatchSkip.")); } @@ -4436,16 +4437,21 @@ DebuggerPatchSkip::DebuggerPatchSkip(Thread *thread, thread->BypassWithSingleStep((DWORD)patch->address, patch->opcode, opcode2); m_singleStep = true; } + #else // _TARGET_ARM_ + +#ifdef _TARGET_ARM64_ + patchBypass = NativeWalker::SetupOrSimulateInstructionForPatchSkip(context, m_pSharedPatchBypassBuffer, (const BYTE *)patch->address, patch->opcode); +#endif //_TARGET_ARM64_ //set eip to point to buffer... SetIP(context, (PCODE)patchBypass); - if (context == &c) + if (context ==(T_CONTEXT*) &c) thread->SetThreadContext(&c); - LOG((LF_CORDB, LL_INFO10000, "Bypass at 0x%x\n", patchBypass)); + LOG((LF_CORDB, LL_INFO10000, "DPS::DPS Bypass at 0x%p for opcode %p \n", patchBypass, patch->opcode)); // // Turn on single step (if the platform supports it) so we can @@ -4642,7 +4648,32 @@ TP_RESULT DebuggerPatchSkip::TriggerExceptionHook(Thread *thread, CONTEXT * cont LOG((LF_CORDB,LL_INFO10000, "DPS::TEH: doing the patch-skip thing\n")); -#ifndef _TARGET_ARM_ +#if defined(_TARGET_ARM64_) + + if (!IsSingleStep(exception->ExceptionCode)) + { + LOG((LF_CORDB, LL_INFO10000, "Exception in patched Bypass instruction .\n")); + return (TPR_IGNORE_AND_STOP); + } + + _ASSERTE(m_pSharedPatchBypassBuffer); + BYTE* patchBypass = m_pSharedPatchBypassBuffer->PatchBypass; + PCODE targetIp; + if (m_pSharedPatchBypassBuffer->RipTargetFixup) + { + targetIp = m_pSharedPatchBypassBuffer->RipTargetFixup; + } + else + { + targetIp = (PCODE)((BYTE *)GetIP(context) - (patchBypass - (BYTE *)m_address)); + } + + SetIP(context, targetIp); + LOG((LF_CORDB, LL_ALWAYS, "Redirecting after Patch to 0x%p\n", GetIP(context))); + +#elif defined (_TARGET_ARM_) +//Do nothing +#else _ASSERTE(m_pSharedPatchBypassBuffer); BYTE* patchBypass = m_pSharedPatchBypassBuffer->PatchBypass; @@ -4754,7 +4785,8 @@ TP_RESULT DebuggerPatchSkip::TriggerExceptionHook(Thread *thread, CONTEXT * cont } -#endif // _TARGET_ARM_ +#endif + // Signals our thread that the debugger is done manipulating the context // during the patch skip operation. This effectively prevented other threads @@ -5906,7 +5938,7 @@ bool DebuggerStepper::TrapStep(ControllerStackInfo *info, bool in) SIZE_T offset = CodeRegionInfo::GetCodeRegionInfo(ji, info->m_activeFrame.md).AddressToOffset(ip); - LOG((LF_CORDB, LL_INFO1000, "Walking to ip 0x%x (natOff:0x%x)\n",ip,offset)); + LOG((LF_CORDB, LL_INFO1000, "Walking to ip 0x%p (natOff:0x%x)\n",ip,offset)); if (!IsInRange(offset, range, rangeCount) && !ShouldContinueStep( info, offset )) @@ -7040,7 +7072,7 @@ TP_RESULT DebuggerStepper::TriggerPatch(DebuggerControllerPatch *patch, // Grab the jit info for the method. DebuggerJitInfo *dji; dji = g_pDebugger->GetJitInfoFromAddr((TADDR) traceManagerRetAddr); - + MethodDesc * mdNative = (dji == NULL) ? g_pEEInterface->GetNativeCodeMethodDesc(dac_cast<PCODE>(traceManagerRetAddr)) : dji->m_fd; _ASSERTE(mdNative != NULL); diff --git a/src/debug/ee/controller.h b/src/debug/ee/controller.h index 0826e469cb..6611e044e5 100644 --- a/src/debug/ee/controller.h +++ b/src/debug/ee/controller.h @@ -213,6 +213,9 @@ public: *(reinterpret_cast<DWORD*>(BypassBuffer)) = SentinelValue; RipTargetFixup = 0; RipTargetFixupSize = 0; +#elif _TARGET_ARM64_ + RipTargetFixup = 0; + #endif } @@ -251,6 +254,8 @@ public: UINT_PTR RipTargetFixup; BYTE RipTargetFixupSize; +#elif defined(_TARGET_ARM64_) + UINT_PTR RipTargetFixup; #endif private: diff --git a/src/debug/ee/debugger.cpp b/src/debug/ee/debugger.cpp index 8634630ebe..a9876abc35 100644 --- a/src/debug/ee/debugger.cpp +++ b/src/debug/ee/debugger.cpp @@ -8380,7 +8380,7 @@ FramePointer GetHandlerFramePointer(BYTE *pStack) { FramePointer handlerFP; -#if !defined(_TARGET_ARM_) +#if !defined(_TARGET_ARM_) && !defined(_TARGET_ARM64_) // Refer to the comment in DispatchUnwind() to see why we have to add // sizeof(LPVOID) to the handler ebp. handlerFP = FramePointer::MakeFramePointer(LPVOID(pStack + sizeof(void*))); @@ -11888,7 +11888,7 @@ HRESULT Debugger::GetAndSendInterceptCommand(DebuggerIPCEvent *event) csi.m_activeFrame.MethodToken, csi.m_activeFrame.md, foundOffset, -#ifdef _TARGET_ARM_ +#if defined (_TARGET_ARM_ )|| defined (_TARGET_ARM64_ ) // ARM requires the caller stack pointer, not the current stack pointer CallerStackFrame::FromRegDisplay(&(csi.m_activeFrame.registers)), #else @@ -15359,6 +15359,8 @@ HRESULT Debugger::FuncEvalSetup(DebuggerIPCE_FuncEvalInfo *pEvalInfo, #endif // !UNIX_AMD64_ABI #elif defined(_TARGET_ARM_) filterContext->R0 = (DWORD)pDE; +#elif defined(_TARGET_ARM64_) + filterContext->X0 = (SIZE_T)pDE; #else PORTABILITY_ASSERT("Debugger::FuncEvalSetup is not implemented on this platform."); #endif @@ -15453,6 +15455,8 @@ HRESULT Debugger::FuncEvalSetupReAbort(Thread *pThread, Thread::ThreadAbortReque filterContext->Rcx = (SIZE_T)pDE; #elif defined(_TARGET_ARM_) filterContext->R0 = (DWORD)pDE; +#elif defined(_TARGET_ARM64_) + filterContext->X0 = (SIZE_T)pDE; #else PORTABILITY_ASSERT("FuncEvalSetupReAbort (Debugger.cpp) is not implemented on this platform."); #endif diff --git a/src/debug/ee/frameinfo.cpp b/src/debug/ee/frameinfo.cpp index 1a56a4a493..35e5bb9a09 100644 --- a/src/debug/ee/frameinfo.cpp +++ b/src/debug/ee/frameinfo.cpp @@ -1424,7 +1424,7 @@ StackWalkAction DebuggerWalkStackProc(CrawlFrame *pCF, void *data) d->info.fp = GetFramePointerForDebugger(d, pCF); -#if defined(_DEBUG) && !defined(_TARGET_ARM_) +#if defined(_DEBUG) && !defined(_TARGET_ARM_) && !defined(_TARGET_ARM64_) // Make sure the stackwalk is making progress. // On ARM this is invalid as the stack pointer does necessarily have to move when unwinding a frame. _ASSERTE(IsCloserToLeaf(d->previousFP, d->info.fp)); diff --git a/src/debug/ee/funceval.cpp b/src/debug/ee/funceval.cpp index d195fafb8b..d2e5576855 100644 --- a/src/debug/ee/funceval.cpp +++ b/src/debug/ee/funceval.cpp @@ -3962,6 +3962,12 @@ FuncEvalHijackPersonalityRoutine(IN PEXCEPTION_RECORD pExceptionRecord // in FuncEvalHijack we allocate 8 bytes of stack space and then store R0 at the current SP, so if we subtract 8 from // the establisher frame we can get the stack location where R0 was stored. pDE = *(DebuggerEval**)(pDispatcherContext->EstablisherFrame - 8); + +#elif defined(_TARGET_ARM64_) + // on ARM64 the establisher frame is the SP of the caller of FuncEvalHijack. + // in FuncEvalHijack we allocate 32 bytes of stack space and then store R0 at the current SP + 16, so if we subtract 16 from + // the establisher frame we can get the stack location where R0 was stored. + pDE = *(DebuggerEval**)(pDispatcherContext->EstablisherFrame - 16); #else _ASSERTE(!"NYI - FuncEvalHijackPersonalityRoutine()"); #endif diff --git a/src/debug/ee/walker.h b/src/debug/ee/walker.h index ee46060382..d7deb10ca4 100644 --- a/src/debug/ee/walker.h +++ b/src/debug/ee/walker.h @@ -13,6 +13,7 @@ #ifndef WALKER_H_ #define WALKER_H_ + /* ========================================================================= */ /* ------------------------------------------------------------------------- * @@ -40,6 +41,7 @@ struct InstructionAttribute bool m_fIsAbsBranch; // is this an absolute branch (either a call or a jump)? bool m_fIsRelBranch; // is this a relative branch (either a call or a jump)? bool m_fIsWrite; // does the instruction write to an address? + DWORD m_cbInstr; // the size of the instruction DWORD m_cbDisp; // the size of the displacement @@ -54,7 +56,6 @@ struct InstructionAttribute m_fIsAbsBranch = false; m_fIsRelBranch = false; m_fIsWrite = false; - m_cbInstr = 0; m_cbDisp = 0; m_dwOffsetToDisp = 0; @@ -102,7 +103,7 @@ public: // We don't currently keep the registers up to date // <TODO> Check if it really works on IA64. </TODO> virtual void Next() { m_registers = NULL; SetIP(m_nextIP); } - virtual void Skip() { m_registers = NULL; SetIP(m_skipIP); } + virtual void Skip() { m_registers = NULL; LOG((LF_CORDB, LL_INFO10000, "skipping over to %p \n", m_skipIP)); SetIP(m_skipIP); } // Decode the instruction virtual void Decode() = 0; @@ -196,7 +197,25 @@ private: DWORD m_opcode; // Current instruction or opcode }; +#elif defined (_TARGET_ARM64_) +#include "controller.h" +class NativeWalker : public Walker +{ +public: + void Init(const BYTE *ip, REGDISPLAY *pregisters) + { + Walker::Init(ip, pregisters); + } + void Decode(); + static void NativeWalker::DecodeInstructionForPatchSkip(const BYTE *address, InstructionAttribute * pInstrAttrib) + { + pInstrAttrib->Reset(); + } + static BOOL NativeWalker::DecodePCRelativeBranchInst(PT_CONTEXT context,const PRD_TYPE& opcode, PCODE& offset, WALK_TYPE& walk); + static BOOL NativeWalker::DecodeCallInst(const PRD_TYPE& opcode, int& RegNum, WALK_TYPE& walk); + static BYTE* SetupOrSimulateInstructionForPatchSkip(T_CONTEXT * context, SharedPatchBypassBuffer * m_pSharedPatchBypassBuffer, const BYTE *address, PRD_TYPE opcode); +}; #else PORTABILITY_WARNING("NativeWalker not implemented on this platform"); class NativeWalker : public Walker diff --git a/src/debug/ee/wks/CMakeLists.txt b/src/debug/ee/wks/CMakeLists.txt index 36cb25e9e6..a3cb48324b 100644 --- a/src/debug/ee/wks/CMakeLists.txt +++ b/src/debug/ee/wks/CMakeLists.txt @@ -10,19 +10,26 @@ if (CLR_CMAKE_PLATFORM_ARCH_I386) list (APPEND ASM_OPTIONS /safeseh) endif (CLR_CMAKE_PLATFORM_ARCH_I386) -# Need to compile asm file using custom command as include directories are not provided to asm compiler -add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/dbghelpers.obj - COMMAND ${CMAKE_ASM_MASM_COMPILER} ${ASM_INCLUDE_DIRECTORIES} ${ASM_DEFINITIONS} ${ASM_OPTIONS} /Fo${CMAKE_CURRENT_BINARY_DIR}/dbghelpers.obj /Ta${CORDBEE_DIR}/${ARCH_SOURCES_DIR}/dbghelpers.asm - DEPENDS ${CORDBEE_DIR}/${ARCH_SOURCES_DIR}/dbghelpers.asm - COMMENT "Compiling dbghelpers.asm") - -if(CLR_CMAKE_PLATFORM_ARCH_ARM64) - #mark obj as source that does not require compile - set_source_files_properties(${CMAKE_CURRENT_BINARY_DIR}/dbghelpers.obj PROPERTIES EXTERNAL_OBJECT TRUE) - add_library_clr(cordbee_wks ${CORDBEE_SOURCES_WKS}) -else() - add_library_clr(cordbee_wks ${CORDBEE_SOURCES_WKS} ${CMAKE_CURRENT_BINARY_DIR}/dbghelpers.obj) -endif() +set(ASM_FILE ${CORDBEE_DIR}/${ARCH_SOURCES_DIR}/dbghelpers.asm) +# asm files require preprocessing using cl.exe on arm64 + if(CLR_CMAKE_PLATFORM_ARCH_ARM64) + get_filename_component(name ${ASM_FILE} NAME_WE) + set(ASM_PREPROCESSED_FILE ${CMAKE_CURRENT_BINARY_DIR}/${name}.asm) + preprocess_def_file(${ASM_FILE} ${ASM_PREPROCESSED_FILE}) + set(CORDBEE_SOURCES_WKS_PREPROCESSED_ASM ${ASM_PREPROCESSED_FILE}) + set_property(SOURCE ${CORDBEE_SOURCES_WKS_PREPROCESSED_ASM} PROPERTY COMPILE_DEFINITIONS ${ASM_DEFINITIONS}) + set_property(SOURCE ${CORDBEE_SOURCES_WKS_PREPROCESSED_ASM} PROPERTY COMPILE_DEFINITIONS ${ASM_OPTIONS}) + add_library_clr(cordbee_wks ${CORDBEE_SOURCES_WKS} ${CORDBEE_SOURCES_WKS_PREPROCESSED_ASM}) + else () + + # Need to compile asm file using custom command as include directories are not provided to asm compiler + add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/dbghelpers.obj + COMMAND ${CMAKE_ASM_MASM_COMPILER} ${ASM_INCLUDE_DIRECTORIES} ${ASM_DEFINITIONS} ${ASM_OPTIONS} /Fo${CMAKE_CURRENT_BINARY_DIR}/dbghelpers.obj /Ta${ASM_FILE} + DEPENDS ${ASM_FILE} + COMMENT "Compiling dbghelpers.asm") + + add_library_clr(cordbee_wks ${CORDBEE_SOURCES_WKS} ${CMAKE_CURRENT_BINARY_DIR}/dbghelpers.obj) + endif(CLR_CMAKE_PLATFORM_ARCH_ARM64) else () diff --git a/src/debug/ee/wks/wks.nativeproj b/src/debug/ee/wks/wks.nativeproj index 7386b5d119..8d89ac45cc 100644 --- a/src/debug/ee/wks/wks.nativeproj +++ b/src/debug/ee/wks/wks.nativeproj @@ -32,6 +32,7 @@ <CppCompile Include="@(ArmSources)" /> <CppCompile Include="@(Arm64Sources)" /> <PreprocessAssembleArm Condition="'$(BuildArchitecture)' == 'arm'" Include="..\arm\dbghelpers.asm" /> + <PreprocessAssembleArm Condition="'$(BuildArchitecture)' == 'arm64'" Include="..\arm64\dbghelpers.asm" /> <AssembleArm Condition="'$(BuildArchitecture)' == 'arm'" Include="$(IntermediateOutputDirectory)\dbghelpers.i" /> <Assemble386 Condition="'$(BuildArchitecture)' == 'i386'" Include="..\i386\dbghelpers.asm" /> <AssembleAmd64 Condition="'$(BuildArchitecture)' == 'amd64'" Include="..\amd64\dbghelpers.asm" /> diff --git a/src/debug/inc/arm64/primitives.h b/src/debug/inc/arm64/primitives.h index 69739691f7..e9e04378f7 100644 --- a/src/debug/inc/arm64/primitives.h +++ b/src/debug/inc/arm64/primitives.h @@ -13,10 +13,11 @@ #ifndef PRIMITIVES_H_ #define PRIMITIVES_H_ +typedef NEON128 FPRegister64; typedef const BYTE CORDB_ADDRESS_TYPE; typedef DPTR(CORDB_ADDRESS_TYPE) PTR_CORDB_ADDRESS_TYPE; -#define MAX_INSTRUCTION_LENGTH 8 +#define MAX_INSTRUCTION_LENGTH 4 // Given a return address retrieved during stackwalk, // this is the offset by which it should be decremented to land at the call instruction. @@ -26,6 +27,16 @@ typedef DPTR(CORDB_ADDRESS_TYPE) PTR_CORDB_ADDRESS_TYPE; #define CORDbg_BREAK_INSTRUCTION_SIZE 4 #define CORDbg_BREAK_INSTRUCTION (LONG)0xD43E0000 +#define NZCV_N 0x80000000 +#define NZCV_Z 0x40000000 +#define NZCV_C 0x20000000 +#define NZCV_V 0x10000000 + +#define NZCV_N_BIT 0x1f +#define NZCV_Z_BIT 0x1e +#define NZCV_C_BIT 0x1d +#define NZCV_V_BIT 0x1c + inline CORDB_ADDRESS GetPatchEndAddr(CORDB_ADDRESS patchAddr) { LIMITED_METHOD_DAC_CONTRACT; @@ -170,5 +181,6 @@ inline bool IsSSFlagEnabled(DT_CONTEXT * pContext) return (pContext->Cpsr & 0x00200000) != 0; } + #include "arm_primitives.h" #endif // PRIMITIVES_H_ diff --git a/src/gc/env/gcenv.os.h b/src/gc/env/gcenv.os.h index 0cdc7a4d16..bb0153f117 100644 --- a/src/gc/env/gcenv.os.h +++ b/src/gc/env/gcenv.os.h @@ -240,9 +240,9 @@ public: // specified, it returns amount of actual physical memory. static uint64_t GetPhysicalMemoryLimit(); - // Get global memory status + // Get memory status // Parameters: - // memory_load - A number between 0 and 100 that specifies the approximate percentage of physical memory + // memory_load - A number between 0 and 100 that specifies the approximate percentage of physical memory // that is in use (0 indicates no memory use and 100 indicates full memory use). // available_physical - The amount of physical memory currently available, in bytes. // available_page_file - The maximum amount of memory the current process can commit, in bytes. diff --git a/src/gc/sample/gcenv.windows.cpp b/src/gc/sample/gcenv.windows.cpp index e35af6b6a0..76187f2185 100644 --- a/src/gc/sample/gcenv.windows.cpp +++ b/src/gc/sample/gcenv.windows.cpp @@ -13,8 +13,6 @@ #include "gcenv.h" #include "gc.h" -static LARGE_INTEGER performanceFrequency; - MethodTable * g_pFreeObjectMethodTable; int32_t g_TrapReturningThreads; @@ -23,12 +21,14 @@ bool g_fFinalizerRunOnShutDown; GCSystemInfo g_SystemInfo; +static LARGE_INTEGER g_performanceFrequency; + // Initialize the interface implementation // Return: // true if it has succeeded, false if it has failed bool GCToOSInterface::Initialize() { - if (!::QueryPerformanceFrequency(&performanceFrequency)) + if (!::QueryPerformanceFrequency(&g_performanceFrequency)) { return false; } @@ -310,9 +310,12 @@ uint64_t GCToOSInterface::GetPhysicalMemoryLimit() return memStatus.ullTotalPhys; } -// Get global memory status +// Get memory status // Parameters: -// ms - pointer to the structure that will be filled in with the memory status +// memory_load - A number between 0 and 100 that specifies the approximate percentage of physical memory +// that is in use (0 indicates no memory use and 100 indicates full memory use). +// available_physical - The amount of physical memory currently available, in bytes. +// available_page_file - The maximum amount of memory the current process can commit, in bytes. void GCToOSInterface::GetMemoryStatus(uint32_t* memory_load, uint64_t* available_physical, uint64_t* available_page_file) { MEMORYSTATUSEX memStatus; @@ -356,14 +359,7 @@ int64_t GCToOSInterface::QueryPerformanceCounter() // The counter frequency int64_t GCToOSInterface::QueryPerformanceFrequency() { - LARGE_INTEGER frequency; - if (!::QueryPerformanceFrequency(&frequency)) - { - _ASSERTE(!"Fatal Error - cannot query performance counter."); - abort(); - } - - return frequency.QuadPart; + return g_performanceFrequency.QuadPart; } // Get a time stamp with a low precision diff --git a/src/inc/cordebug.idl b/src/inc/cordebug.idl index 72549aefe5..49b8acc923 100644 --- a/src/inc/cordebug.idl +++ b/src/inc/cordebug.idl @@ -3737,6 +3737,39 @@ interface ICorDebugRegisterSet : IUnknown REGISTER_ARM64_X28, REGISTER_ARM64_LR, + REGISTER_ARM64_V0, + REGISTER_ARM64_V1, + REGISTER_ARM64_V2, + REGISTER_ARM64_V3, + REGISTER_ARM64_V4, + REGISTER_ARM64_V5, + REGISTER_ARM64_V6, + REGISTER_ARM64_V7, + REGISTER_ARM64_V8, + REGISTER_ARM64_V9, + REGISTER_ARM64_V10, + REGISTER_ARM64_V11, + REGISTER_ARM64_V12, + REGISTER_ARM64_V13, + REGISTER_ARM64_V14, + REGISTER_ARM64_V15, + REGISTER_ARM64_V16, + REGISTER_ARM64_V17, + REGISTER_ARM64_V18, + REGISTER_ARM64_V19, + REGISTER_ARM64_V20, + REGISTER_ARM64_V21, + REGISTER_ARM64_V22, + REGISTER_ARM64_V23, + REGISTER_ARM64_V24, + REGISTER_ARM64_V25, + REGISTER_ARM64_V26, + REGISTER_ARM64_V27, + REGISTER_ARM64_V28, + REGISTER_ARM64_V29, + REGISTER_ARM64_V30, + REGISTER_ARM64_V31, + // other architectures here } CorDebugRegister; diff --git a/src/jit/codegen.h b/src/jit/codegen.h index 70f21c6010..72eb676752 100755 --- a/src/jit/codegen.h +++ b/src/jit/codegen.h @@ -443,26 +443,26 @@ protected: void genPrologPadForReJit(); - void genEmitCall(int callType, - CORINFO_METHOD_HANDLE methHnd, - INDEBUG_LDISASM_COMMA(CORINFO_SIG_INFO* sigInfo) - void* addr - X86_ARG(ssize_t argSize), - emitAttr retSize - FEATURE_UNIX_AMD64_STRUCT_PASSING_ONLY_ARG(emitAttr secondRetSize), - IL_OFFSETX ilOffset, - regNumber base = REG_NA, - bool isJump = false, - bool isNoGC = false); + void genEmitCall(int callType, + CORINFO_METHOD_HANDLE methHnd, + INDEBUG_LDISASM_COMMA(CORINFO_SIG_INFO* sigInfo) + void* addr + X86_ARG(ssize_t argSize), + emitAttr retSize + MULTIREG_HAS_SECOND_GC_RET_ONLY_ARG(emitAttr secondRetSize), + IL_OFFSETX ilOffset, + regNumber base = REG_NA, + bool isJump = false, + bool isNoGC = false); - void genEmitCall(int callType, - CORINFO_METHOD_HANDLE methHnd, - INDEBUG_LDISASM_COMMA(CORINFO_SIG_INFO* sigInfo) - GenTreeIndir* indir - X86_ARG(ssize_t argSize), - emitAttr retSize - FEATURE_UNIX_AMD64_STRUCT_PASSING_ONLY_ARG(emitAttr secondRetSize), - IL_OFFSETX ilOffset); + void genEmitCall(int callType, + CORINFO_METHOD_HANDLE methHnd, + INDEBUG_LDISASM_COMMA(CORINFO_SIG_INFO* sigInfo) + GenTreeIndir* indir + X86_ARG(ssize_t argSize), + emitAttr retSize + MULTIREG_HAS_SECOND_GC_RET_ONLY_ARG(emitAttr secondRetSize), + IL_OFFSETX ilOffset); // @@ -900,7 +900,7 @@ public : void instEmit_indCall(GenTreePtr call, size_t argSize, emitAttr retSize - FEATURE_UNIX_AMD64_STRUCT_PASSING_ONLY_ARG(emitAttr secondRetSize)); + MULTIREG_HAS_SECOND_GC_RET_ONLY_ARG(emitAttr secondRetSize)); void instEmit_RM (instruction ins, GenTreePtr tree, diff --git a/src/jit/codegenarm64.cpp b/src/jit/codegenarm64.cpp index 2f8879ac0b..b6dc645112 100644 --- a/src/jit/codegenarm64.cpp +++ b/src/jit/codegenarm64.cpp @@ -2354,6 +2354,311 @@ void CodeGen::genCodeForBinary(GenTree* treeNode) genProduceReg(treeNode); } +//------------------------------------------------------------------------ +// isStructReturn: Returns whether the 'treeNode' is returning a struct. +// +// Arguments: +// treeNode - The tree node to evaluate whether is a struct return. +// +// Return Value: +// Returns true if the 'treeNode" is a GT_RETURN node of type struct. +// Otherwise returns false. +// +bool +CodeGen::isStructReturn(GenTreePtr treeNode) +{ + // This method could be called for 'treeNode' of GT_RET_FILT or GT_RETURN. + // For the GT_RET_FILT, the return is always + // a bool or a void, for the end of a finally block. + noway_assert(treeNode->OperGet() == GT_RETURN || treeNode->OperGet() == GT_RETFILT); + + return varTypeIsStruct(treeNode); +} + +//------------------------------------------------------------------------ +// genStructReturn: Generates code for returning a struct. +// +// Arguments: +// treeNode - The GT_RETURN tree node. +// +// Return Value: +// None +// +// Assumption: +// op1 of GT_RETURN node is either GT_LCL_VAR or multi-reg GT_CALL +void +CodeGen::genStructReturn(GenTreePtr treeNode) +{ + assert(treeNode->OperGet() == GT_RETURN); + assert(isStructReturn(treeNode)); + GenTreePtr op1 = treeNode->gtGetOp1(); + + if (op1->OperGet() == GT_LCL_VAR) + { + GenTreeLclVarCommon* lclVar = op1->AsLclVarCommon(); + LclVarDsc* varDsc = &(compiler->lvaTable[lclVar->gtLclNum]); + var_types lclType = genActualType(varDsc->TypeGet()); + + // Currently only multireg TYP_STRUCT types such as HFA's and 16-byte structs are supported + // In the future we could have FEATURE_SIMD types like TYP_SIMD16 + assert(lclType == TYP_STRUCT); + assert(varDsc->lvIsMultiRegRet); + + ReturnTypeDesc retTypeDesc; + unsigned regCount; + + retTypeDesc.InitializeStructReturnType(compiler, varDsc->lvVerTypeInfo.GetClassHandle()); + regCount = retTypeDesc.GetReturnRegCount(); + + assert(regCount >= 2); + assert(op1->isContained()); + + // Copy var on stack into ABI return registers + int offset = 0; + for (unsigned i = 0; i < regCount; ++i) + { + var_types type = retTypeDesc.GetReturnRegType(i); + regNumber reg = retTypeDesc.GetABIReturnReg(i); + getEmitter()->emitIns_R_S(ins_Load(type), emitTypeSize(type), reg, lclVar->gtLclNum, offset); + offset += genTypeSize(type); + } + } + else // op1 must be multi-reg GT_CALL + { + assert(op1->IsMultiRegCall() || op1->IsCopyOrReloadOfMultiRegCall()); + + genConsumeRegs(op1); + + GenTree* actualOp1 = op1->gtSkipReloadOrCopy(); + GenTreeCall* call = actualOp1->AsCall(); + + ReturnTypeDesc* pRetTypeDesc; + unsigned regCount; + unsigned matchingCount = 0; + + pRetTypeDesc = call->GetReturnTypeDesc(); + regCount = pRetTypeDesc->GetReturnRegCount(); + + var_types regType [MAX_RET_REG_COUNT]; + regNumber returnReg [MAX_RET_REG_COUNT]; + regNumber allocatedReg[MAX_RET_REG_COUNT]; + regMaskTP srcRegsMask = 0; + regMaskTP dstRegsMask = 0; + bool needToShuffleRegs = false; // Set to true if we have to move any registers + + for (unsigned i = 0; i < regCount; ++i) + { + regType[i] = pRetTypeDesc->GetReturnRegType(i); + returnReg[i] = pRetTypeDesc->GetABIReturnReg(i); + + regNumber reloadReg = REG_NA; + if (op1->IsCopyOrReload()) + { + // GT_COPY/GT_RELOAD will have valid reg for those positions + // that need to be copied or reloaded. + reloadReg = op1->AsCopyOrReload()->GetRegNumByIdx(i); + } + + if (reloadReg != REG_NA) + { + allocatedReg[i] = reloadReg; + } + else + { + allocatedReg[i] = call->GetRegNumByIdx(i); + } + + if (returnReg[i] == allocatedReg[i]) + { + matchingCount++; + } + else // We need to move this value + { + // We want to move the value from allocatedReg[i] into returnReg[i] + // so record these two registers in the src and dst masks + // + srcRegsMask |= genRegMask(allocatedReg[i]); + dstRegsMask |= genRegMask(returnReg[i]); + + needToShuffleRegs = true; + } + } + + if (needToShuffleRegs) + { + assert(matchingCount < regCount); + + unsigned remainingRegCount = regCount - matchingCount; + regMaskTP extraRegMask = treeNode->gtRsvdRegs; + + while (remainingRegCount > 0) + { + // set 'available' to the 'dst' registers that are not currently holding 'src' registers + // + regMaskTP availableMask = dstRegsMask & ~srcRegsMask; + + regMaskTP dstMask; + regNumber srcReg; + regNumber dstReg; + var_types curType = TYP_UNKNOWN; + regNumber freeUpReg = REG_NA; + + if (availableMask == 0) + { + // Circular register dependencies + // So just free up the lowest register in dstRegsMask by moving it to the 'extra' register + + assert(dstRegsMask == srcRegsMask); // this has to be true for us to reach here + assert(extraRegMask != 0); // we require an 'extra' register + assert((extraRegMask & ~dstRegsMask) != 0); // it can't be part of dstRegsMask + + availableMask = extraRegMask & ~dstRegsMask; + + regMaskTP srcMask = genFindLowestBit(srcRegsMask); + freeUpReg = genRegNumFromMask(srcMask); + } + + dstMask = genFindLowestBit(availableMask); + dstReg = genRegNumFromMask(dstMask); + srcReg = REG_NA; + + if (freeUpReg != REG_NA) + { + // We will free up the srcReg by moving it to dstReg which is an extra register + // + srcReg = freeUpReg; + + // Find the 'srcReg' and set 'curType', change allocatedReg[] to dstReg + // and add the new register mask bit to srcRegsMask + // + for (unsigned i = 0; i < regCount; ++i) + { + if (allocatedReg[i] == srcReg) + { + curType = regType[i]; + allocatedReg[i] = dstReg; + srcRegsMask |= genRegMask(dstReg); + } + } + } + else // The normal case + { + // Find the 'srcReg' and set 'curType' + // + for (unsigned i = 0; i < regCount; ++i) + { + if (returnReg[i] == dstReg) + { + srcReg = allocatedReg[i]; + curType = regType[i]; + } + } + // After we perform this move we will have one less registers to setup + remainingRegCount--; + } + assert(curType != TYP_UNKNOWN); + + inst_RV_RV(ins_Copy(curType), dstReg, srcReg, curType); + + // Clear the appropriate bits in srcRegsMask and dstRegsMask + srcRegsMask &= ~genRegMask(srcReg); + dstRegsMask &= ~genRegMask(dstReg); + + } // while (remainingRegCount > 0) + + } // (needToShuffleRegs) + + } // op1 must be multi-reg GT_CALL + +} + + +//------------------------------------------------------------------------ +// genReturn: Generates code for return statement. +// In case of struct return, delegates to the genStructReturn method. +// +// Arguments: +// treeNode - The GT_RETURN or GT_RETFILT tree node. +// +// Return Value: +// None +// +void +CodeGen::genReturn(GenTreePtr treeNode) +{ + assert(treeNode->OperGet() == GT_RETURN || treeNode->OperGet() == GT_RETFILT); + GenTreePtr op1 = treeNode->gtGetOp1(); + var_types targetType = treeNode->TypeGet(); + +#ifdef DEBUG + if (targetType == TYP_VOID) + { + assert(op1 == nullptr); + } +#endif + + if (isStructReturn(treeNode)) + { + genStructReturn(treeNode); + } + else if (targetType != TYP_VOID) + { + assert(op1 != nullptr); + noway_assert(op1->gtRegNum != REG_NA); + + genConsumeReg(op1); + + regNumber retReg = varTypeIsFloating(treeNode) ? REG_FLOATRET : REG_INTRET; + + bool movRequired = (op1->gtRegNum != retReg); + + if (!movRequired) + { + if (op1->OperGet() == GT_LCL_VAR) + { + GenTreeLclVarCommon *lcl = op1->AsLclVarCommon(); + bool isRegCandidate = compiler->lvaTable[lcl->gtLclNum].lvIsRegCandidate(); + if (isRegCandidate && ((op1->gtFlags & GTF_SPILLED) == 0)) + { + assert(op1->InReg()); + + // We may need to generate a zero-extending mov instruction to load the value from this GT_LCL_VAR + + unsigned lclNum = lcl->gtLclNum; + LclVarDsc* varDsc = &(compiler->lvaTable[lclNum]); + var_types op1Type = genActualType(op1->TypeGet()); + var_types lclType = genActualType(varDsc->TypeGet()); + + if (genTypeSize(op1Type) < genTypeSize(lclType)) + { + movRequired = true; + } + } + } + } + + if (movRequired) + { + emitAttr movSize = EA_ATTR(genTypeSize(targetType)); + getEmitter()->emitIns_R_R(INS_mov, movSize, retReg, op1->gtRegNum); + } + } + +#ifdef PROFILING_SUPPORTED + // There will be a single return block while generating profiler ELT callbacks. + // + // Reason for not materializing Leave callback as a GT_PROF_HOOK node after GT_RETURN: + // In flowgraph and other places assert that the last node of a block marked as + // GT_RETURN is either a GT_RETURN or GT_JMP or a tail call. It would be nice to + // maintain such an invariant irrespective of whether profiler hook needed or not. + // Also, there is not much to be gained by materializing it as an explicit node. + if (compiler->compCurBB == compiler->genReturnBB) + { + genProfilingLeaveCallback(); + } +#endif +} + /***************************************************************************** * * Generate code for a single node in the tree. @@ -2679,28 +2984,19 @@ CodeGen::genCodeForTreeNode(GenTreePtr treeNode) break; case GT_STORE_LCL_FLD: - case GT_STORE_LCL_VAR: { - if (targetType == TYP_STRUCT) - { - NYI("GT_STORE_LCL_VAR/FLD with TYP_STRUCT"); - } noway_assert(targetType != TYP_STRUCT); - GenTreeLclVarCommon* varNode = treeNode->AsLclVarCommon(); + // record the offset + unsigned offset = treeNode->gtLclFld.gtLclOffs; + // We must have a stack store with GT_STORE_LCL_FLD + noway_assert(!treeNode->InReg()); + noway_assert(targetReg == REG_NA); + + GenTreeLclVarCommon* varNode = treeNode->AsLclVarCommon(); unsigned varNum = varNode->gtLclNum; assert(varNum < compiler->lvaCount); LclVarDsc* varDsc = &(compiler->lvaTable[varNum]); - unsigned offset = 0; - - if (treeNode->gtOper == GT_STORE_LCL_FLD) - { - // record the offset, only used with GT_STORE_LCL_FLD - offset = treeNode->gtLclFld.gtLclOffs; - // We must have a stack store with GT_STORE_LCL_FLD - noway_assert(!treeNode->InReg()); - noway_assert(targetReg == REG_NA); - } // Ensure that lclVar nodes are typed correctly. assert(!varDsc->lvNormalizeOnStore() || targetType == genActualType(varDsc->TypeGet())); @@ -2722,114 +3018,99 @@ CodeGen::genCodeForTreeNode(GenTreePtr treeNode) } assert(dataReg != REG_NA); - if (targetReg == REG_NA) // store into stack based LclVar - { - // Only true gtLclVar subclass nodes currently have a gtLclILoffs instance field - // - if(treeNode->gtOper != GT_STORE_LCL_FLD) - { - inst_set_SV_var(varNode); - } + instruction ins = ins_Store(targetType); - instruction ins = ins_Store(targetType); - emitAttr attr = emitTypeSize(targetType); + emitAttr attr = emitTypeSize(targetType); - attr = emit->emitInsAdjustLoadStoreAttr(ins, attr); + attr = emit->emitInsAdjustLoadStoreAttr(ins, attr); - emit->emitIns_S_R(ins, attr, dataReg, varNum, offset); + emit->emitIns_S_R(ins, attr, dataReg, varNum, offset); - genUpdateLife(varNode); - - varDsc->lvRegNum = REG_STK; - } - else // store into register (i.e move into register) - { - if (dataReg != targetReg) - { - // Assign into targetReg when dataReg (from op1) is not the same register - inst_RV_RV(ins_Copy(targetType), targetReg, dataReg, targetType); - } - genProduceReg(treeNode); - } + genUpdateLife(varNode); + + varDsc->lvRegNum = REG_STK; } break; - case GT_RETFILT: - // A void GT_RETFILT is the end of a finally. For non-void filter returns we need to load the result in - // the return register, if it's not already there. The processing is the same as GT_RETURN. - if (targetType != TYP_VOID) + case GT_STORE_LCL_VAR: { - // For filters, the IL spec says the result is type int32. Further, the only specified legal values - // are 0 or 1, with the use of other values "undefined". - assert(targetType == TYP_INT); - } + GenTreeLclVarCommon* varNode = treeNode->AsLclVarCommon(); - __fallthrough; + unsigned varNum = varNode->gtLclNum; assert(varNum < compiler->lvaCount); + LclVarDsc* varDsc = &(compiler->lvaTable[varNum]); + unsigned offset = 0; - case GT_RETURN: - { - GenTreePtr op1 = treeNode->gtOp.gtOp1; - if (targetType == TYP_VOID) + // Ensure that lclVar nodes are typed correctly. + assert(!varDsc->lvNormalizeOnStore() || targetType == genActualType(varDsc->TypeGet())); + + GenTreePtr data = treeNode->gtOp.gtOp1->gtEffectiveVal(); + + // var = call, where call returns a multi-reg return value + // case is handled separately. + if (data->gtSkipReloadOrCopy()->IsMultiRegCall()) { - assert(op1 == nullptr); + genMultiRegCallStoreToLocal(treeNode); } else { - assert(op1 != nullptr); - noway_assert(op1->gtRegNum != REG_NA); + genConsumeRegs(data); + + regNumber dataReg = REG_NA; + if (data->isContainedIntOrIImmed()) + { + assert(data->IsIntegralConst(0)); + dataReg = REG_ZR; + } + else + { + assert(!data->isContained()); + genConsumeReg(data); + dataReg = data->gtRegNum; + } + assert(dataReg != REG_NA); - genConsumeReg(op1); + if (targetReg == REG_NA) // store into stack based LclVar + { + inst_set_SV_var(varNode); + + instruction ins = ins_Store(targetType); + emitAttr attr = emitTypeSize(targetType); - regNumber retReg = varTypeIsFloating(treeNode) ? REG_FLOATRET : REG_INTRET; + attr = emit->emitInsAdjustLoadStoreAttr(ins, attr); - bool movRequired = (op1->gtRegNum != retReg); + emit->emitIns_S_R(ins, attr, dataReg, varNum, offset); - if (!movRequired) + genUpdateLife(varNode); + + varDsc->lvRegNum = REG_STK; + } + else // store into register (i.e move into register) { - if (op1->OperGet() == GT_LCL_VAR) + if (dataReg != targetReg) { - GenTreeLclVarCommon *lcl = op1->AsLclVarCommon(); - bool isRegCandidate = compiler->lvaTable[lcl->gtLclNum].lvIsRegCandidate(); - if (isRegCandidate && ((op1->gtFlags & GTF_SPILLED) == 0)) - { - assert(op1->InReg()); - - // We may need to generate a zero-extending mov instruction to load the value from this GT_LCL_VAR - - unsigned lclNum = lcl->gtLclNum; - LclVarDsc* varDsc = &(compiler->lvaTable[lclNum]); - var_types op1Type = genActualType(op1->TypeGet()); - var_types lclType = genActualType(varDsc->TypeGet()); - - if (genTypeSize(op1Type) < genTypeSize(lclType)) - { - movRequired = true; - } - } + // Assign into targetReg when dataReg (from op1) is not the same register + inst_RV_RV(ins_Copy(targetType), targetReg, dataReg, targetType); } - } - - if (movRequired) - { - emitAttr movSize = EA_ATTR(genTypeSize(targetType)); - emit->emitIns_R_R(INS_mov, movSize, retReg, op1->gtRegNum); + genProduceReg(treeNode); } } + } + break; -#ifdef PROFILING_SUPPORTED - // There will be a single return block while generating profiler ELT callbacks. - // - // Reason for not materializing Leave callback as a GT_PROF_HOOK node after GT_RETURN: - // In flowgraph and other places assert that the last node of a block marked as - // GT_RETURN is either a GT_RETURN or GT_JMP or a tail call. It would be nice to - // maintain such an invariant irrespective of whether profiler hook needed or not. - // Also, there is not much to be gained by materializing it as an explicit node. - if (compiler->compCurBB == compiler->genReturnBB) - { - genProfilingLeaveCallback(); - } -#endif + case GT_RETFILT: + // A void GT_RETFILT is the end of a finally. For non-void filter returns we need to load the result in + // the return register, if it's not already there. The processing is the same as GT_RETURN. + if (targetType != TYP_VOID) + { + // For filters, the IL spec says the result is type int32. Further, the only specified legal values + // are 0 or 1, with the use of other values "undefined". + assert(targetType == TYP_INT); } + + __fallthrough; + + case GT_RETURN: + genReturn(treeNode); break; case GT_LEA: @@ -3363,6 +3644,80 @@ CodeGen::genCodeForTreeNode(GenTreePtr treeNode) } } +//---------------------------------------------------------------------------------- +// genMultiRegCallStoreToLocal: store multi-reg return value of a call node to a local +// +// Arguments: +// treeNode - Gentree of GT_STORE_LCL_VAR +// +// Return Value: +// None +// +// Assumption: +// The child of store is a multi-reg call node. +// genProduceReg() on treeNode is made by caller of this routine. +// +void +CodeGen::genMultiRegCallStoreToLocal(GenTreePtr treeNode) +{ + assert(treeNode->OperGet() == GT_STORE_LCL_VAR); + + // Structs of size >=9 and <=16 are returned in two return registers on ARM64 and HFAs. + assert(varTypeIsStruct(treeNode)); + + // Assumption: current ARM64 implementation requires that a multi-reg struct + // var in 'var = call' is flagged as lvIsMultiRegRet to prevent it from + // being struct promoted. + unsigned lclNum = treeNode->AsLclVarCommon()->gtLclNum; + LclVarDsc* varDsc = &(compiler->lvaTable[lclNum]); + noway_assert(varDsc->lvIsMultiRegRet); + + GenTree* op1 = treeNode->gtGetOp1(); + GenTree* actualOp1 = op1->gtSkipReloadOrCopy(); + GenTreeCall* call = actualOp1->AsCall(); + assert(call->HasMultiRegRetVal()); + + genConsumeRegs(op1); + + ReturnTypeDesc* pRetTypeDesc = call->GetReturnTypeDesc(); + unsigned regCount = pRetTypeDesc->GetReturnRegCount(); + + if (treeNode->gtRegNum != REG_NA) + { + // Right now the only enregistrable structs supported are SIMD types. + assert(varTypeIsSIMD(treeNode)); + NYI("GT_STORE_LCL_VAR of a SIMD enregisterable struct"); + } + else + { + // Stack store + int offset = 0; + for (unsigned i = 0; i < regCount; ++i) + { + var_types type = pRetTypeDesc->GetReturnRegType(i); + regNumber reg = call->GetRegNumByIdx(i); + if (op1->IsCopyOrReload()) + { + // GT_COPY/GT_RELOAD will have valid reg for those positions + // that need to be copied or reloaded. + regNumber reloadReg = op1->AsCopyOrReload()->GetRegNumByIdx(i); + if (reloadReg != REG_NA) + { + reg = reloadReg; + } + } + + assert(reg != REG_NA); + getEmitter()->emitIns_S_R(ins_Store(type), emitTypeSize(type), reg, lclNum, offset); + offset += genTypeSize(type); + } + + varDsc->lvRegNum = REG_STK; + } +} + + + /*********************************************************************************************** * Generate code for localloc */ @@ -4540,6 +4895,7 @@ void CodeGen::genUnspillRegIfNeeded(GenTree *tree) { unspillTree = tree->gtOp.gtOp1; } + if (unspillTree->gtFlags & GTF_SPILLED) { if (genIsRegCandidateLocal(unspillTree)) @@ -4601,22 +4957,72 @@ void CodeGen::genUnspillRegIfNeeded(GenTree *tree) regSet.AddMaskVars(genGetRegMask(varDsc)); } + + gcInfo.gcMarkRegPtrVal(dstReg, unspillTree->TypeGet()); + } + else if (unspillTree->IsMultiRegCall()) + { + GenTreeCall* call = unspillTree->AsCall(); + ReturnTypeDesc* pRetTypeDesc = call->GetReturnTypeDesc(); + unsigned regCount = pRetTypeDesc->GetReturnRegCount(); + GenTreeCopyOrReload* reloadTree = nullptr; + if (tree->OperGet() == GT_RELOAD) + { + reloadTree = tree->AsCopyOrReload(); + } + + // In case of multi-reg call node, GTF_SPILLED flag on it indicates that + // one or more of its result regs are spilled. Call node needs to be + // queried to know which specific result regs to be unspilled. + for (unsigned i = 0; i < regCount; ++i) + { + unsigned flags = call->GetRegSpillFlagByIdx(i); + if ((flags & GTF_SPILLED) != 0) + { + var_types dstType = pRetTypeDesc->GetReturnRegType(i); + regNumber unspillTreeReg = call->GetRegNumByIdx(i); + + if (reloadTree != nullptr) + { + dstReg = reloadTree->GetRegNumByIdx(i); + if (dstReg == REG_NA) + { + dstReg = unspillTreeReg; + } + } + else + { + dstReg = unspillTreeReg; + } + + TempDsc* t = regSet.rsUnspillInPlace(call, unspillTreeReg, i); + getEmitter()->emitIns_R_S(ins_Load(dstType), + emitActualTypeSize(dstType), + dstReg, + t->tdTempNum(), + 0); + compiler->tmpRlsTemp(t); + gcInfo.gcMarkRegPtrVal(dstReg, dstType); + } + } + + unspillTree->gtFlags &= ~GTF_SPILLED; + unspillTree->SetInReg(); } else { TempDsc* t = regSet.rsUnspillInPlace(unspillTree, unspillTree->gtRegNum); getEmitter()->emitIns_R_S(ins_Load(unspillTree->gtType), - emitActualTypeSize(unspillTree->gtType), - dstReg, - t->tdTempNum(), - 0); + emitActualTypeSize(unspillTree->TypeGet()), + dstReg, + t->tdTempNum(), + 0); compiler->tmpRlsTemp(t); unspillTree->gtFlags &= ~GTF_SPILLED; unspillTree->SetInReg(); - } - - gcInfo.gcMarkRegPtrVal(dstReg, unspillTree->TypeGet()); + gcInfo.gcMarkRegPtrVal(dstReg, unspillTree->TypeGet()); + } } } @@ -4920,6 +5326,7 @@ void CodeGen::genEmitCall(int callType, INDEBUG_LDISASM_COMMA(CORINFO_SIG_INFO* sigInfo) void* addr, emitAttr retSize, + emitAttr secondRetSize, IL_OFFSETX ilOffset, regNumber base, bool isJump, @@ -4932,6 +5339,7 @@ void CodeGen::genEmitCall(int callType, addr, 0, retSize, + secondRetSize, gcInfo.gcVarPtrSetCur, gcInfo.gcRegGCrefSetCur, gcInfo.gcRegByrefSetCur, @@ -4950,6 +5358,7 @@ void CodeGen::genEmitCall(int callType, INDEBUG_LDISASM_COMMA(CORINFO_SIG_INFO* sigInfo) GenTreeIndir* indir, emitAttr retSize, + emitAttr secondRetSize, IL_OFFSETX ilOffset) { genConsumeAddress(indir->Addr()); @@ -4960,6 +5369,7 @@ void CodeGen::genEmitCall(int callType, nullptr, 0, retSize, + secondRetSize, gcInfo.gcVarPtrSetCur, gcInfo.gcRegGCrefSetCur, gcInfo.gcRegByrefSetCur, @@ -5098,16 +5508,29 @@ void CodeGen::genCallInstruction(GenTreePtr node) genDefineTempLabel(genCreateTempLabel()); } - // Determine return value size. + // Determine return value size(s). + ReturnTypeDesc* pRetTypeDesc = call->GetReturnTypeDesc(); emitAttr retSize = EA_PTRSIZE; - if (call->gtType == TYP_REF || - call->gtType == TYP_ARRAY) + emitAttr secondRetSize = EA_UNKNOWN; + + if (call->HasMultiRegRetVal()) { - retSize = EA_GCREF; + retSize = emitTypeSize(pRetTypeDesc->GetReturnRegType(0)); + secondRetSize = emitTypeSize(pRetTypeDesc->GetReturnRegType(1)); } - else if (call->gtType == TYP_BYREF) + else { - retSize = EA_BYREF; + assert(!varTypeIsStruct(call)); + + if (call->gtType == TYP_REF || + call->gtType == TYP_ARRAY) + { + retSize = EA_GCREF; + } + else if (call->gtType == TYP_BYREF) + { + retSize = EA_BYREF; + } } #ifdef DEBUGGING_SUPPORT @@ -5136,6 +5559,7 @@ void CodeGen::genCallInstruction(GenTreePtr node) INDEBUG_LDISASM_COMMA(sigInfo) nullptr, //addr retSize, + secondRetSize, ilOffset, genConsumeReg(target)); } @@ -5191,6 +5615,7 @@ void CodeGen::genCallInstruction(GenTreePtr node) INDEBUG_LDISASM_COMMA(sigInfo) nullptr, //addr retSize, + secondRetSize, ilOffset, REG_IP0); #else @@ -5200,6 +5625,7 @@ void CodeGen::genCallInstruction(GenTreePtr node) INDEBUG_LDISASM_COMMA(sigInfo) addr, retSize, + secondRetSize, ilOffset); #endif } @@ -5225,11 +5651,44 @@ void CodeGen::genCallInstruction(GenTreePtr node) var_types returnType = call->TypeGet(); if (returnType != TYP_VOID) { - regNumber returnReg = (varTypeIsFloating(returnType) ? REG_FLOATRET : REG_INTRET); - if (call->gtRegNum != returnReg) + regNumber returnReg; + + if (call->HasMultiRegRetVal()) { - inst_RV_RV(ins_Copy(returnType), call->gtRegNum, returnReg, returnType); + assert(pRetTypeDesc != nullptr); + unsigned regCount = pRetTypeDesc->GetReturnRegCount(); + + // If regs allocated to call node are different from ABI return + // regs in which the call has returned its result, move the result + // to regs allocated to call node. + for (unsigned i = 0; i < regCount; ++i) + { + var_types regType = pRetTypeDesc->GetReturnRegType(i); + returnReg = pRetTypeDesc->GetABIReturnReg(i); + regNumber allocatedReg = call->GetRegNumByIdx(i); + if (returnReg != allocatedReg) + { + inst_RV_RV(ins_Copy(regType), allocatedReg, returnReg, regType); + } + } + } + else + { + if (varTypeIsFloating(returnType)) + { + returnReg = REG_FLOATRET; + } + else + { + returnReg = REG_INTRET; + } + + if (call->gtRegNum != returnReg) + { + inst_RV_RV(ins_Copy(returnType), call->gtRegNum, returnReg, returnType); + } } + genProduceReg(call); } @@ -5375,14 +5834,18 @@ void CodeGen::genJmpMethod(GenTreePtr jmp) regSet.AddMaskVars(genRegMask(argReg)); gcInfo.gcMarkRegPtrVal(argReg, loadType); - if (varDsc->lvIsMultiregStruct()) + if (compiler->lvaIsMultiregStruct(varDsc)) { if (varDsc->lvIsHfa()) { NYI_ARM64("CodeGen::genJmpMethod with multireg HFA arg"); + // use genRegArgNextFloat + } + else + { + // Restore the second register. + argRegNext = genRegArgNext(argReg); } - // Restore the next register. - argRegNext = genMapRegArgNumToRegNum(genMapRegNumToRegArgNum(argReg, loadType) + 1, loadType); loadType = compiler->getJitGCType(varDsc->lvGcLayout[1]); loadSize = emitActualTypeSize(loadType); getEmitter()->emitIns_R_S(ins_Load(loadType), loadSize, argRegNext, varNum, TARGET_POINTER_SIZE); @@ -5404,7 +5867,7 @@ void CodeGen::genJmpMethod(GenTreePtr jmp) fixedIntArgMask |= genRegMask(argReg); - if (varDsc->lvIsMultiregStruct()) + if (compiler->lvaIsMultiregStruct(varDsc)) { assert(argRegNext != REG_NA); fixedIntArgMask |= genRegMask(argRegNext); @@ -6867,19 +7330,20 @@ void CodeGen::genEmitHelperCall(unsigned helper, } getEmitter()->emitIns_Call(callType, - compiler->eeFindHelper(helper), - INDEBUG_LDISASM_COMMA(nullptr) - addr, - argSize, - retSize, - gcInfo.gcVarPtrSetCur, - gcInfo.gcRegGCrefSetCur, - gcInfo.gcRegByrefSetCur, - BAD_IL_OFFSET, /* IL offset */ - callTarget, /* ireg */ - REG_NA, 0, 0, /* xreg, xmul, disp */ - false, /* isJump */ - emitter::emitNoGChelper(helper)); + compiler->eeFindHelper(helper), + INDEBUG_LDISASM_COMMA(nullptr) + addr, + argSize, + retSize, + EA_UNKNOWN, + gcInfo.gcVarPtrSetCur, + gcInfo.gcRegGCrefSetCur, + gcInfo.gcRegByrefSetCur, + BAD_IL_OFFSET, /* IL offset */ + callTarget, /* ireg */ + REG_NA, 0, 0, /* xreg, xmul, disp */ + false, /* isJump */ + emitter::emitNoGChelper(helper)); regMaskTP killMask = compiler->compHelperCallKillSet((CorInfoHelpFunc)helper); regTracker.rsTrashRegSet(killMask); diff --git a/src/jit/codegencommon.cpp b/src/jit/codegencommon.cpp index 35360394fb..562fc08ee3 100755 --- a/src/jit/codegencommon.cpp +++ b/src/jit/codegencommon.cpp @@ -4074,7 +4074,7 @@ void CodeGen::genFnPrologCalleeRegArgs(regNumber xtraReg, slots = 1; #if FEATURE_MULTIREG_ARGS - if (varDsc->lvIsMultiregStruct()) + if (compiler->lvaIsMultiregStruct(varDsc)) { if (varDsc->lvIsHfaRegArg()) { @@ -9006,20 +9006,20 @@ void CodeGen::genFnEpilog(BasicBlock* block) */ getEmitter()->emitIns_Call(callType, - methHnd, - INDEBUG_LDISASM_COMMA(nullptr) - addr, - 0, // argSize - EA_UNKNOWN, // retSize - gcInfo.gcVarPtrSetCur, - gcInfo.gcRegGCrefSetCur, - gcInfo.gcRegByrefSetCur, - BAD_IL_OFFSET, // IL offset - indCallReg, // ireg - REG_NA, // xreg - 0, // xmul - 0, // disp - true); // isJump + methHnd, + INDEBUG_LDISASM_COMMA(nullptr) + addr, + 0, // argSize + EA_UNKNOWN, // retSize + gcInfo.gcVarPtrSetCur, + gcInfo.gcRegGCrefSetCur, + gcInfo.gcRegByrefSetCur, + BAD_IL_OFFSET, // IL offset + indCallReg, // ireg + REG_NA, // xreg + 0, // xmul + 0, // disp + true); // isJump } else { @@ -9115,16 +9115,17 @@ void CodeGen::genFnEpilog(BasicBlock* block) // Simply emit a jump to the methodHnd. This is similar to a call so we can use // the same descriptor with some minor adjustments. getEmitter()->emitIns_Call(callType, - methHnd, - INDEBUG_LDISASM_COMMA(nullptr) - addrInfo.addr, - 0, // argSize - EA_UNKNOWN, // retSize - gcInfo.gcVarPtrSetCur, - gcInfo.gcRegGCrefSetCur, - gcInfo.gcRegByrefSetCur, - BAD_IL_OFFSET, REG_NA, REG_NA, 0, 0, /* iloffset, ireg, xreg, xmul, disp */ - true); /* isJump */ + methHnd, + INDEBUG_LDISASM_COMMA(nullptr) + addrInfo.addr, + 0, // argSize + EA_UNKNOWN, // retSize + EA_UNKNOWN, // secondRetSize + gcInfo.gcVarPtrSetCur, + gcInfo.gcRegGCrefSetCur, + gcInfo.gcRegByrefSetCur, + BAD_IL_OFFSET, REG_NA, REG_NA, 0, 0, /* iloffset, ireg, xreg, xmul, disp */ + true); /* isJump */ } #if FEATURE_FASTTAILCALL else @@ -10472,22 +10473,26 @@ void CodeGen::genRestoreCalleeSavedFltRegs(unsigned lclFrameSize) } #endif // defined(_TARGET_XARCH_) && !FEATURE_STACK_FP_X87 -#ifdef FEATURE_UNIX_AMD64_STRUCT_PASSING -bool Compiler::IsRegisterPassable(CORINFO_CLASS_HANDLE hClass) +//----------------------------------------------------------------------------------- +// IsMultiRegPassedType: Returns true if the type is returned in multiple registers +// +// Arguments: +// hClass - type handle +// +// Return Value: +// true if type is passed in multiple registers, false otherwise. +// +bool Compiler::IsMultiRegPassedType(CORINFO_CLASS_HANDLE hClass) { if (hClass == NO_CLASS_HANDLE) { return false; } - SYSTEMV_AMD64_CORINFO_STRUCT_REG_PASSING_DESCRIPTOR structDesc; - eeGetSystemVAmd64PassStructInRegisterDescriptor(hClass, &structDesc); - return structDesc.passedInRegisters; -} - -bool Compiler::IsRegisterPassable(GenTreePtr tree) -{ - return IsRegisterPassable(gtGetStructHandleIfPresent(tree)); + structPassingKind howToPassStruct; + var_types returnType = getArgTypeForStruct(hClass, &howToPassStruct); + + return (returnType == TYP_STRUCT); } //----------------------------------------------------------------------------------- @@ -10498,6 +10503,7 @@ bool Compiler::IsRegisterPassable(GenTreePtr tree) // // Return Value: // true if type is returned in multiple registers, false otherwise. +// bool Compiler::IsMultiRegReturnedType(CORINFO_CLASS_HANDLE hClass) { if (hClass == NO_CLASS_HANDLE) @@ -10505,11 +10511,11 @@ bool Compiler::IsMultiRegReturnedType(CORINFO_CLASS_HANDLE hClass) return false; } - SYSTEMV_AMD64_CORINFO_STRUCT_REG_PASSING_DESCRIPTOR structDesc; - eeGetSystemVAmd64PassStructInRegisterDescriptor(hClass, &structDesc); - return structDesc.passedInRegisters && (structDesc.eightByteCount > 1); + structPassingKind howToReturnStruct; + var_types returnType = getReturnTypeForStruct(hClass, &howToReturnStruct); + + return (returnType == TYP_STRUCT); } -#endif // FEATURE_UNIX_AMD64_STRUCT_PASSING //---------------------------------------------- // Methods that support HFA's for ARM32/ARM64 diff --git a/src/jit/codegenxarch.cpp b/src/jit/codegenxarch.cpp index 5fea9fc583..6f0782dc66 100755 --- a/src/jit/codegenxarch.cpp +++ b/src/jit/codegenxarch.cpp @@ -186,7 +186,15 @@ void CodeGen::genEmitGSCookieCheck(bool pushReg) if (compiler->compMethodReturnsMultiRegRetType()) { ReturnTypeDesc retTypeDesc; - retTypeDesc.InitializeReturnType(compiler, compiler->info.compMethodInfo->args.retTypeClass); + if (varTypeIsLong(compiler->info.compRetNativeType)) + { + retTypeDesc.InitializeLongReturnType(compiler); + } + else // we must have a struct return type + { + retTypeDesc.InitializeStructReturnType(compiler, compiler->info.compMethodInfo->args.retTypeClass); + } + unsigned regCount = retTypeDesc.GetReturnRegCount(); // Only x86 and x64 Unix ABI allows multi-reg return and @@ -1614,7 +1622,7 @@ CodeGen::genStructReturn(GenTreePtr treeNode) assert(varDsc->lvIsMultiRegRet); ReturnTypeDesc retTypeDesc; - retTypeDesc.InitializeReturnType(compiler, varDsc->lvVerTypeInfo.GetClassHandle()); + retTypeDesc.InitializeStructReturnType(compiler, varDsc->lvVerTypeInfo.GetClassHandle()); unsigned regCount = retTypeDesc.GetReturnRegCount(); assert(regCount == MAX_RET_REG_COUNT); @@ -5567,17 +5575,17 @@ void CodeGen::genTransferRegGCState(regNumber dst, regNumber src) // pass in 'addr' for a relative call or 'base' for a indirect register call // methHnd - optional, only used for pretty printing // retSize - emitter type of return for GC purposes, should be EA_BYREF, EA_GCREF, or EA_PTRSIZE(not GC) -void CodeGen::genEmitCall(int callType, - CORINFO_METHOD_HANDLE methHnd, - INDEBUG_LDISASM_COMMA(CORINFO_SIG_INFO* sigInfo) - void* addr - X86_ARG(ssize_t argSize), - emitAttr retSize - FEATURE_UNIX_AMD64_STRUCT_PASSING_ONLY_ARG(emitAttr secondRetSize), - IL_OFFSETX ilOffset, - regNumber base, - bool isJump, - bool isNoGC) +void CodeGen::genEmitCall(int callType, + CORINFO_METHOD_HANDLE methHnd, + INDEBUG_LDISASM_COMMA(CORINFO_SIG_INFO* sigInfo) + void* addr + X86_ARG(ssize_t argSize), + emitAttr retSize + MULTIREG_HAS_SECOND_GC_RET_ONLY_ARG(emitAttr secondRetSize), + IL_OFFSETX ilOffset, + regNumber base, + bool isJump, + bool isNoGC) { #if !defined(_TARGET_X86_) ssize_t argSize = 0; @@ -5588,7 +5596,7 @@ void CodeGen::genEmitCall(int addr, argSize, retSize - FEATURE_UNIX_AMD64_STRUCT_PASSING_ONLY_ARG(secondRetSize), + MULTIREG_HAS_SECOND_GC_RET_ONLY_ARG(secondRetSize), gcInfo.gcVarPtrSetCur, gcInfo.gcRegGCrefSetCur, gcInfo.gcRegByrefSetCur, @@ -5601,14 +5609,14 @@ void CodeGen::genEmitCall(int // generates an indirect call via addressing mode (call []) given an indir node // methHnd - optional, only used for pretty printing // retSize - emitter type of return for GC purposes, should be EA_BYREF, EA_GCREF, or EA_PTRSIZE(not GC) -void CodeGen::genEmitCall(int callType, - CORINFO_METHOD_HANDLE methHnd, - INDEBUG_LDISASM_COMMA(CORINFO_SIG_INFO* sigInfo) - GenTreeIndir* indir - X86_ARG(ssize_t argSize), - emitAttr retSize - FEATURE_UNIX_AMD64_STRUCT_PASSING_ONLY_ARG(emitAttr secondRetSize), - IL_OFFSETX ilOffset) +void CodeGen::genEmitCall(int callType, + CORINFO_METHOD_HANDLE methHnd, + INDEBUG_LDISASM_COMMA(CORINFO_SIG_INFO* sigInfo) + GenTreeIndir* indir + X86_ARG(ssize_t argSize), + emitAttr retSize + MULTIREG_HAS_SECOND_GC_RET_ONLY_ARG(emitAttr secondRetSize), + IL_OFFSETX ilOffset) { #if !defined(_TARGET_X86_) ssize_t argSize = 0; @@ -5621,7 +5629,7 @@ void CodeGen::genEmitCall(int nullptr, argSize, retSize - FEATURE_UNIX_AMD64_STRUCT_PASSING_ONLY_ARG(secondRetSize), + MULTIREG_HAS_SECOND_GC_RET_ONLY_ARG(secondRetSize), gcInfo.gcVarPtrSetCur, gcInfo.gcRegGCrefSetCur, gcInfo.gcRegByrefSetCur, @@ -6138,7 +6146,7 @@ void CodeGen::genCallInstruction(GenTreePtr node) (void*) target->AsIndir()->Base()->AsIntConCommon()->IconValue() X86_ARG(argSizeForEmitter), retSize - FEATURE_UNIX_AMD64_STRUCT_PASSING_ONLY_ARG(secondRetSize), + MULTIREG_HAS_SECOND_GC_RET_ONLY_ARG(secondRetSize), ilOffset); } else @@ -6149,7 +6157,7 @@ void CodeGen::genCallInstruction(GenTreePtr node) target->AsIndir() X86_ARG(argSizeForEmitter), retSize - FEATURE_UNIX_AMD64_STRUCT_PASSING_ONLY_ARG(secondRetSize), + MULTIREG_HAS_SECOND_GC_RET_ONLY_ARG(secondRetSize), ilOffset); } } @@ -6164,7 +6172,7 @@ void CodeGen::genCallInstruction(GenTreePtr node) nullptr //addr X86_ARG(argSizeForEmitter), retSize - FEATURE_UNIX_AMD64_STRUCT_PASSING_ONLY_ARG(secondRetSize), + MULTIREG_HAS_SECOND_GC_RET_ONLY_ARG(secondRetSize), ilOffset, genConsumeReg(target)); } @@ -6178,7 +6186,7 @@ void CodeGen::genCallInstruction(GenTreePtr node) (void*) call->gtEntryPoint.addr X86_ARG(argSizeForEmitter), retSize - FEATURE_UNIX_AMD64_STRUCT_PASSING_ONLY_ARG(secondRetSize), + MULTIREG_HAS_SECOND_GC_RET_ONLY_ARG(secondRetSize), ilOffset); } #endif @@ -6221,7 +6229,7 @@ void CodeGen::genCallInstruction(GenTreePtr node) addr X86_ARG(argSizeForEmitter), retSize - FEATURE_UNIX_AMD64_STRUCT_PASSING_ONLY_ARG(secondRetSize), + MULTIREG_HAS_SECOND_GC_RET_ONLY_ARG(secondRetSize), ilOffset); } diff --git a/src/jit/compiler.cpp b/src/jit/compiler.cpp index 04c90d78af..15620f43fc 100644 --- a/src/jit/compiler.cpp +++ b/src/jit/compiler.cpp @@ -669,9 +669,9 @@ var_types Compiler::getPrimitiveTypeForStruct( unsigned structSize, // is the primitive type used to pass the struct. // When *wbPassStruct is SPK_ByReference this method's return value // is always TYP_UNKNOWN and the struct type is passed by reference to a copy -// When *wbPassStruct is SPK_ByValue or SPK_ByValueAsHfa this method's return value -// can be TYP_STRUCT and the struct type is passed using multiple registers. -// or can be TYP_UNKNOWN and the struct type is passed by value (for x86 and ARM32) +// When *wbPassStruct is SPK_ByValue or SPK_ByValueAsHfa this method's return value +// is always TYP_STRUCT and the struct type is passed by value either +// using multiple registers or on the stack. // // Assumptions: // The size must be the size of the given type. @@ -683,12 +683,6 @@ var_types Compiler::getPrimitiveTypeForStruct( unsigned structSize, // floating point primitive type and *wbPassStruct is SPK_PrimitiveType // If there are two or more elements in the HFA type then the this method's // return value is TYP_STRUCT and *wbPassStruct is SPK_ByValueAsHfa -// About returning TYP_STRUCT: -// Whenever this method's return value is TYP_STRUCT it usually means that multiple -// registers will be used to pass this struct. -// The only exception occurs if all of the parameters registers are used up -// then we must use stack slots instead. In such a case the amount of stack needed -// is always equal to the structSize (rounded up to the next pointer size) // var_types Compiler::getArgTypeForStruct(CORINFO_CLASS_HANDLE clsHnd, structPassingKind* wbPassStruct, @@ -718,13 +712,25 @@ var_types Compiler::getArgTypeForStruct(CORINFO_CLASS_HANDLE clsHnd, useType = GetEightByteType(structDesc, 0); } -#else // not UNIX_AMD64 +#elif defined(_TARGET_X86_) - // We set the "primitive" useType based upon the structSize - // and also examine the clsHnd to see if it is an HFA of count one - useType = getPrimitiveTypeForStruct(structSize, clsHnd); + // On x86 we never pass structs as primitive types (unless the VM unwraps them for us) + useType = TYP_UNKNOWN; -#endif // FEATURE_UNIX_AMD64_STRUCT_PASSING +#else // all other targets + + // The largest primitive type is 8 bytes (TYP_DOUBLE) + // so we can skip calling getPrimitiveTypeForStruct when we + // have a struct that is larger than that. + // + if (structSize <= sizeof(double)) + { + // We set the "primitive" useType based upon the structSize + // and also examine the clsHnd to see if it is an HFA of count one + useType = getPrimitiveTypeForStruct(structSize, clsHnd); + } + +#endif // all other targets // Did we change this struct type into a simple "primitive" type? // @@ -800,10 +806,10 @@ var_types Compiler::getArgTypeForStruct(CORINFO_CLASS_HANDLE clsHnd, #elif defined(_TARGET_X86_) || defined(_TARGET_ARM_) - // Otherwise we pass this struct by value + // Otherwise we pass this struct by value on the stack // setup wbPassType and useType indicate that this is passed by value according to the X86/ARM32 ABI howToPassStruct = SPK_ByValue; - useType = TYP_UNKNOWN; + useType = TYP_STRUCT; #else // _TARGET_XXX_ @@ -823,7 +829,7 @@ var_types Compiler::getArgTypeForStruct(CORINFO_CLASS_HANDLE clsHnd, // Otherwise we pass this struct by value on the stack // setup wbPassType and useType indicate that this is passed by value according to the X86/ARM32 ABI howToPassStruct = SPK_ByValue; - useType = TYP_UNKNOWN; + useType = TYP_STRUCT; #elif defined(_TARGET_AMD64_) || defined(_TARGET_ARM64_) @@ -900,6 +906,8 @@ var_types Compiler::getReturnTypeForStruct(CORINFO_CLASS_HANDLE clsHnd, var_types useType = TYP_UNKNOWN; structPassingKind howToReturnStruct = SPK_Unknown; // We must change this before we return + assert(clsHnd != NO_CLASS_HANDLE); + if (structSize == 0) { structSize = info.compCompHnd->getClassSize(clsHnd); @@ -919,13 +927,21 @@ var_types Compiler::getReturnTypeForStruct(CORINFO_CLASS_HANDLE clsHnd, { // Set 'useType' to the type of the first eightbyte item useType = GetEightByteType(structDesc, 0); + assert(structDesc.passedInRegisters == true); } #else // not UNIX_AMD64 - // We set the "primitive" useType based upon the structSize - // and also examine the clsHnd to see if it is an HFA of count one - useType = getPrimitiveTypeForStruct(structSize, clsHnd); + // The largest primitive type is 8 bytes (TYP_DOUBLE) + // so we can skip calling getPrimitiveTypeForStruct when we + // have a struct that is larger than that. + // + if (structSize <= sizeof(double)) + { + // We set the "primitive" useType based upon the structSize + // and also examine the clsHnd to see if it is an HFA of count one + useType = getPrimitiveTypeForStruct(structSize, clsHnd); + } #endif // FEATURE_UNIX_AMD64_STRUCT_PASSING @@ -938,8 +954,10 @@ var_types Compiler::getReturnTypeForStruct(CORINFO_CLASS_HANDLE clsHnd, // Since we what we have an 8-byte struct (float + float) we change useType to TYP_I_IMPL // so that the struct is returned instead using an 8-byte integer register. // - if ((MAX_RET_MULTIREG_BYTES == 0) && (useType == TYP_UNKNOWN) && - (structSize == 8) && IsHfa(clsHnd)) + if ((FEATURE_MULTIREG_RET == 0) && + (useType == TYP_UNKNOWN) && + (structSize == (2 * sizeof(float))) && + IsHfa(clsHnd) ) { useType = TYP_I_IMPL; } @@ -956,7 +974,7 @@ var_types Compiler::getReturnTypeForStruct(CORINFO_CLASS_HANDLE clsHnd, // See if we can return this struct by value, possibly in multiple registers // or if we should return it using a return buffer register // - if (structSize <= MAX_RET_MULTIREG_BYTES) + if ((FEATURE_MULTIREG_RET == 1) && (structSize <= MAX_RET_MULTIREG_BYTES)) { // Structs that are HFA's are returned in multiple registers if (IsHfa(clsHnd)) @@ -980,6 +998,7 @@ var_types Compiler::getReturnTypeForStruct(CORINFO_CLASS_HANDLE clsHnd, // setup wbPassType and useType indicate that this is returned by value in multiple registers howToReturnStruct = SPK_ByValue; useType = TYP_STRUCT; + assert(structDesc.passedInRegisters == true); } else { @@ -989,6 +1008,7 @@ var_types Compiler::getReturnTypeForStruct(CORINFO_CLASS_HANDLE clsHnd, // (reference to a return buffer) howToReturnStruct = SPK_ByReference; useType = TYP_UNKNOWN; + assert(structDesc.passedInRegisters == false); } #elif defined(_TARGET_ARM64_) @@ -1029,7 +1049,7 @@ var_types Compiler::getReturnTypeForStruct(CORINFO_CLASS_HANDLE clsHnd, } } - else // (structSize > MAX_RET_MULTIREG_BYTES) + else // (structSize > MAX_RET_MULTIREG_BYTES) || (FEATURE_MULTIREG_RET == 0) { // We have a (large) struct that can't be replaced with a "primitive" type // and can't be returned in multiple registers @@ -1801,6 +1821,9 @@ void Compiler::compInit(ArenaAllocator * pAlloc, InlineInfo * inl //Used by fgFindJumpTargets for inlining heuristics. opts.instrCount = 0; + // Used to track when we should consider running EarlyProp + optMethodFlags = 0; + for (unsigned i = 0; i < MAX_LOOP_NUM; i++) { AllVarSetOps::AssignNoCopy(this, optLoopTable[i].lpAsgVars, AllVarSetOps::UninitVal()); diff --git a/src/jit/compiler.h b/src/jit/compiler.h index 8f059e40eb..e3a4b519c7 100644 --- a/src/jit/compiler.h +++ b/src/jit/compiler.h @@ -681,27 +681,6 @@ public: return (unsigned)(roundUp(lvExactSize, TARGET_POINTER_SIZE)); } - bool lvIsMultiregStruct() - { -#if FEATURE_MULTIREG_ARGS_OR_RET - if (TypeGet() == TYP_STRUCT) - { - if (lvIsHfa() && (lvHfaSlots() > 1)) - { - return true; - } -#if defined(_TARGET_ARM64_) - // lvSize() performs a roundUp operation so it only returns multiples of TARGET_POINTER_SIZE - else if (lvSize() == (2 * TARGET_POINTER_SIZE)) - { - return true; - } -#endif // _TARGET_ARM64_ - } -#endif // FEATURE_MULTIREG_ARGS_OR_RET - return false; - } - #if defined(DEBUGGING_SUPPORT) || defined(DEBUG) unsigned lvSlotNum; // original slot # (if remapped) #endif @@ -1454,23 +1433,17 @@ public: // floating-point registers instead of the general purpose registers. // - bool IsHfa(CORINFO_CLASS_HANDLE hClass); - bool IsHfa(GenTreePtr tree); + bool IsHfa(CORINFO_CLASS_HANDLE hClass); + bool IsHfa(GenTreePtr tree); - var_types GetHfaType(GenTreePtr tree); - unsigned GetHfaCount(GenTreePtr tree); + var_types GetHfaType(GenTreePtr tree); + unsigned GetHfaCount(GenTreePtr tree); - var_types GetHfaType(CORINFO_CLASS_HANDLE hClass); - unsigned GetHfaCount(CORINFO_CLASS_HANDLE hClass); + var_types GetHfaType(CORINFO_CLASS_HANDLE hClass); + unsigned GetHfaCount(CORINFO_CLASS_HANDLE hClass); - //------------------------------------------------------------------------- - // The following is used for struct passing on System V system. - // -#ifdef FEATURE_UNIX_AMD64_STRUCT_PASSING - bool IsRegisterPassable(CORINFO_CLASS_HANDLE hClass); - bool IsRegisterPassable(GenTreePtr tree); - bool IsMultiRegReturnedType(CORINFO_CLASS_HANDLE hClass); -#endif // FEATURE_UNIX_AMD64_STRUCT_PASSING + bool IsMultiRegPassedType (CORINFO_CLASS_HANDLE hClass); + bool IsMultiRegReturnedType(CORINFO_CLASS_HANDLE hClass); //------------------------------------------------------------------------- // The following is used for validating format of EH table @@ -1925,8 +1898,7 @@ public: bool gtArgIsThisPtr (fgArgTabEntryPtr argEntry); GenTreePtr gtNewAssignNode (GenTreePtr dst, - GenTreePtr src - DEBUGARG(bool isPhiDefn = false)); + GenTreePtr src); GenTreePtr gtNewTempAssign (unsigned tmp, GenTreePtr val); @@ -2545,6 +2517,9 @@ public : return false; } + // Returns true if this local var is a multireg struct + bool lvaIsMultiregStruct(LclVarDsc* varDsc); + // If the class is a TYP_STRUCT, get/set a class handle describing it CORINFO_CLASS_HANDLE lvaGetStruct (unsigned varNum); @@ -2763,14 +2738,13 @@ protected : bool impMethodInfo_hasRetBuffArg(CORINFO_METHOD_INFO * methInfo); - GenTreePtr impFixupCallStructReturn(GenTreePtr call, - CORINFO_CLASS_HANDLE retClsHnd); + GenTreePtr impFixupCallStructReturn(GenTreePtr call, + CORINFO_CLASS_HANDLE retClsHnd); - GenTreePtr impInitCallReturnTypeDesc(GenTreePtr call, - CORINFO_CLASS_HANDLE retClsHnd); + GenTreePtr impInitCallLongReturn (GenTreePtr call); - GenTreePtr impFixupStructReturnType(GenTreePtr op, - CORINFO_CLASS_HANDLE retClsHnd); + GenTreePtr impFixupStructReturnType(GenTreePtr op, + CORINFO_CLASS_HANDLE retClsHnd); #ifdef DEBUG var_types impImportJitTestLabelMark(int numArgs); @@ -4759,11 +4733,11 @@ private: void fgInsertInlineeBlocks (InlineInfo* pInlineInfo); GenTreePtr fgInlinePrependStatements(InlineInfo* inlineInfo); -#if defined(FEATURE_HFA) || defined(FEATURE_UNIX_AMD64_STRUCT_PASSING) +#if FEATURE_MULTIREG_RET GenTreePtr fgGetStructAsStructPtr(GenTreePtr tree); GenTreePtr fgAssignStructInlineeToVar(GenTreePtr child, CORINFO_CLASS_HANDLE retClsHnd); void fgAttachStructInlineeToAsg(GenTreePtr tree, GenTreePtr child, CORINFO_CLASS_HANDLE retClsHnd); -#endif // defined(FEATURE_HFA) || defined(FEATURE_UNIX_AMD64_STRUCT_PASSING) +#endif // FEATURE_MULTIREG_RET static fgWalkPreFn fgUpdateInlineReturnExpressionPlaceHolder; @@ -5544,7 +5518,7 @@ public: }; #define OMF_HAS_NEWARRAY 0x00000001 // Method contains 'new' of an array -#define OMF_HAS_NEWOBJ 0x00800002 // Method contains 'new' of an object type. +#define OMF_HAS_NEWOBJ 0x00000002 // Method contains 'new' of an object type. #define OMF_HAS_ARRAYREF 0x00000004 // Method contains array element loads or stores. #define OMF_HAS_VTABLEREF 0x00000008 // Method contains method table reference. @@ -5561,8 +5535,6 @@ public: OPK_OBJ_GETTYPE }; - bool impHasArrayRef; - bool gtIsVtableRef(GenTreePtr tree); GenTreePtr getArrayLengthFromAllocation(GenTreePtr tree); GenTreePtr getObjectHandleNodeFromAllocation(GenTreePtr tree); @@ -5573,7 +5545,6 @@ public: bool optDoEarlyPropForFunc(); void optEarlyProp(); - #if ASSERTION_PROP /************************************************************************** * Value/Assertion propagation @@ -8060,22 +8031,19 @@ public : bool compMethodReturnsMultiRegRetType() { #if FEATURE_MULTIREG_RET - -#if (defined(FEATURE_UNIX_AMD64_STRUCT_PASSING) || defined(_TARGET_ARM_)) - // Methods returning a struct in two registers is considered having a return value of TYP_STRUCT. +#if defined(_TARGET_X86_) + // On x86 only 64-bit longs are returned in multiple registers + return varTypeIsLong(info.compRetNativeType); +#else // targets: X64-UNIX, ARM64 or ARM32 + // On all other targets that support multireg return values: + // Methods returning a struct in multiple registers have a return value of TYP_STRUCT. // Such method's compRetNativeType is TYP_STRUCT without a hidden RetBufArg return varTypeIsStruct(info.compRetNativeType) && (info.compRetBuffArg == BAD_VAR_NUM); -#elif defined(_TARGET_X86_) - // Longs are returned in two registers on x86 - return varTypeIsLong(info.compRetNativeType); -#else - unreached(); -#endif - -#else +#endif // TARGET_XXX +#else // not FEATURE_MULTIREG_RET + // For this architecture there are no multireg returns return false; -#endif // FEATURE_MULTIREG_RET - +#endif // FEATURE_MULTIREG_RET } #if FEATURE_MULTIREG_ARGS diff --git a/src/jit/compiler.hpp b/src/jit/compiler.hpp index eae8b3a7a0..1ff7b53348 100644 --- a/src/jit/compiler.hpp +++ b/src/jit/compiler.hpp @@ -657,10 +657,6 @@ bool Compiler::VarTypeIsMultiByteAndCanEnreg(var_types type, if (varTypeIsStruct(type)) { size = info.compCompHnd->getClassSize(typeClass); -#ifdef FEATURE_UNIX_AMD64_STRUCT_PASSING - // Account for the classification of the struct. - result = IsRegisterPassable(typeClass); -#else // !FEATURE_UNIX_AMD64_STRUCT_PASSING if (forReturn) { structPassingKind howToReturnStruct; @@ -675,7 +671,6 @@ bool Compiler::VarTypeIsMultiByteAndCanEnreg(var_types type, { result = true; } -#endif // !FEATURE_UNIX_AMD64_STRUCT_PASSING } else { @@ -752,27 +747,27 @@ unsigned __int8 getU1LittleEndian(const BYTE * ptr) inline unsigned __int16 getU2LittleEndian(const BYTE * ptr) -{ return *(UNALIGNED unsigned __int16 *)ptr; } +{ return GET_UNALIGNED_VAL16(ptr); } inline unsigned __int32 getU4LittleEndian(const BYTE * ptr) -{ return *(UNALIGNED unsigned __int32*)ptr; } +{ return GET_UNALIGNED_VAL32(ptr); } inline signed __int8 getI1LittleEndian(const BYTE * ptr) -{ return * (UNALIGNED signed __int8 *)ptr; } +{ return *(UNALIGNED signed __int8 *)ptr; } inline signed __int16 getI2LittleEndian(const BYTE * ptr) -{ return * (UNALIGNED signed __int16 *)ptr; } +{ return GET_UNALIGNED_VAL16(ptr); } inline signed __int32 getI4LittleEndian(const BYTE * ptr) -{ return *(UNALIGNED signed __int32*)ptr; } +{ return GET_UNALIGNED_VAL32(ptr); } inline signed __int64 getI8LittleEndian(const BYTE * ptr) -{ return *(UNALIGNED signed __int64*)ptr; } +{ return GET_UNALIGNED_VAL64(ptr); } inline float getR4LittleEndian(const BYTE * ptr) @@ -1517,10 +1512,13 @@ void GenTree::ChangeOperUnchecked(genTreeOps oper) inline bool GenTree::IsVarAddr() const { - if (gtOper == GT_ADDR && (gtFlags & GTF_ADDR_ONSTACK)) + if (gtOper == GT_ADDR) { - assert((gtType == TYP_BYREF) || (gtType == TYP_I_IMPL)); - return true; + if (gtFlags & GTF_ADDR_ONSTACK) + { + assert((gtType == TYP_BYREF) || (gtType == TYP_I_IMPL)); + return true; + } } return false; } diff --git a/src/jit/earlyprop.cpp b/src/jit/earlyprop.cpp index da1228e47c..ff74d3a082 100644 --- a/src/jit/earlyprop.cpp +++ b/src/jit/earlyprop.cpp @@ -43,15 +43,14 @@ bool Compiler::gtIsVtableRef(GenTreePtr tree) { if (tree->OperGet() == GT_IND) { - GenTreeIndir* indir = tree->AsIndir(); + GenTree* addr = tree->AsIndir()->Addr(); - if (!indir->HasIndex()) + if (addr->OperIsAddrMode()) { - // Check if the base is an reference pointer. - if (indir->Base()->TypeGet() == TYP_REF) - { - return true; - } + GenTreeAddrMode* addrMode = addr->AsAddrMode(); + + return (!addrMode->HasIndex() && + (addrMode->Base()->TypeGet() == TYP_REF)); } } diff --git a/src/jit/ee_il_dll.cpp b/src/jit/ee_il_dll.cpp index e4d05739b6..c726856b9b 100755 --- a/src/jit/ee_il_dll.cpp +++ b/src/jit/ee_il_dll.cpp @@ -72,16 +72,35 @@ void __stdcall jitStartup(ICorJitHost* jitHost) #else if (jitstdout == nullptr) { - int jitstdoutFd = _dup(_fileno(procstdout())); - _setmode(jitstdoutFd, _O_TEXT); - jitstdout = _fdopen(jitstdoutFd, "w"); - assert(jitstdout != nullptr); - - // Prevent the FILE* from buffering its output in order to avoid calls to - // `fflush()` throughout the code. - setvbuf(jitstdout, nullptr, _IONBF, 0); + int stdoutFd = _fileno(procstdout()); + // Check fileno error output(s) -1 may overlap with errno result + // but is included for completness. + // We want to detect the case where the initial handle is null + // or bogus and avoid making further calls. + if ((stdoutFd != -1) && (stdoutFd != -2) && (errno != EINVAL)) + { + int jitstdoutFd = _dup(_fileno(procstdout())); + // Check the error status returned by dup. + if (jitstdoutFd != -1) + { + _setmode(jitstdoutFd, _O_TEXT); + jitstdout = _fdopen(jitstdoutFd, "w"); + assert(jitstdout != nullptr); + + // Prevent the FILE* from buffering its output in order to avoid calls to + // `fflush()` throughout the code. + setvbuf(jitstdout, nullptr, _IONBF, 0); + } + } } -#endif + + // If jitstdout is still null, fallback to whatever procstdout() was + // initially set to. + if (jitstdout == nullptr) + { + jitstdout = procstdout(); + } +#endif // PLATFORM_UNIX #ifdef FEATURE_TRACELOGGING JitTelemetry::NotifyDllProcessAttach(); diff --git a/src/jit/emit.cpp b/src/jit/emit.cpp index b7d0d30b6a..0766735578 100644 --- a/src/jit/emit.cpp +++ b/src/jit/emit.cpp @@ -2953,7 +2953,7 @@ void emitter::emitDispVarSet() /*****************************************************************************/ #endif//DEBUG -#if defined(FEATURE_UNIX_AMD64_STRUCT_PASSING) +#if MULTIREG_HAS_SECOND_GC_RET //------------------------------------------------------------------------ // emitSetSecondRetRegGCType: Sets the GC type of the second return register for instrDescCGCA struct. // @@ -2980,7 +2980,7 @@ void emitter::emitSetSecondRetRegGCType(instrDescCGCA* id, emitAttr s id->idSecondGCref(GCT_NONE); } } -#endif // defined(FEATURE_UNIX_AMD64_STRUCT_PASSING) +#endif // MULTIREG_HAS_SECOND_GC_RET /***************************************************************************** * @@ -2993,13 +2993,13 @@ void emitter::emitSetSecondRetRegGCType(instrDescCGCA* id, emitAttr s * address mode displacement. */ -emitter::instrDesc * emitter::emitNewInstrCallInd(int argCnt, - ssize_t disp, - VARSET_VALARG_TP GCvars, - regMaskTP gcrefRegs, - regMaskTP byrefRegs, - emitAttr retSizeIn - FEATURE_UNIX_AMD64_STRUCT_PASSING_ONLY_ARG(emitAttr secondRetSize)) +emitter::instrDesc * emitter::emitNewInstrCallInd(int argCnt, + ssize_t disp, + VARSET_VALARG_TP GCvars, + regMaskTP gcrefRegs, + regMaskTP byrefRegs, + emitAttr retSizeIn + MULTIREG_HAS_SECOND_GC_RET_ONLY_ARG(emitAttr secondRetSize)) { emitAttr retSize = (retSizeIn != EA_UNKNOWN) ? retSizeIn : EA_PTRSIZE; @@ -3022,7 +3022,7 @@ emitter::instrDesc * emitter::emitNewInstrCallInd(int (argCnt > ID_MAX_SMALL_CNS) || // too many args (argCnt < 0) // caller pops arguments // There is a second ref/byref return register. - FEATURE_UNIX_AMD64_STRUCT_PASSING_ONLY( || EA_IS_GCREF_OR_BYREF(secondRetSize))) + MULTIREG_HAS_SECOND_GC_RET_ONLY( || EA_IS_GCREF_OR_BYREF(secondRetSize))) { instrDescCGCA* id; @@ -3036,9 +3036,9 @@ emitter::instrDesc * emitter::emitNewInstrCallInd(int id->idcArgCnt = argCnt; id->idcDisp = disp; -#ifdef FEATURE_UNIX_AMD64_STRUCT_PASSING +#if MULTIREG_HAS_SECOND_GC_RET emitSetSecondRetRegGCType(id, secondRetSize); -#endif // FEATURE_UNIX_AMD64_STRUCT_PASSING +#endif // MULTIREG_HAS_SECOND_GC_RET return id; } @@ -3073,12 +3073,12 @@ emitter::instrDesc * emitter::emitNewInstrCallInd(int * and an arbitrarily large argument count. */ -emitter::instrDesc *emitter::emitNewInstrCallDir(int argCnt, - VARSET_VALARG_TP GCvars, - regMaskTP gcrefRegs, - regMaskTP byrefRegs, - emitAttr retSizeIn - FEATURE_UNIX_AMD64_STRUCT_PASSING_ONLY_ARG(emitAttr secondRetSize)) +emitter::instrDesc *emitter::emitNewInstrCallDir(int argCnt, + VARSET_VALARG_TP GCvars, + regMaskTP gcrefRegs, + regMaskTP byrefRegs, + emitAttr retSizeIn + MULTIREG_HAS_SECOND_GC_RET_ONLY_ARG(emitAttr secondRetSize)) { emitAttr retSize = (retSizeIn != EA_UNKNOWN) ? retSizeIn : EA_PTRSIZE; @@ -3098,7 +3098,7 @@ emitter::instrDesc *emitter::emitNewInstrCallDir(int (argCnt > ID_MAX_SMALL_CNS) || // too many args (argCnt < 0) // caller pops arguments // There is a second ref/byref return register. - FEATURE_UNIX_AMD64_STRUCT_PASSING_ONLY( || EA_IS_GCREF_OR_BYREF(secondRetSize))) + MULTIREG_HAS_SECOND_GC_RET_ONLY( || EA_IS_GCREF_OR_BYREF(secondRetSize))) { instrDescCGCA* id = emitAllocInstrCGCA(retSize); @@ -3112,9 +3112,9 @@ emitter::instrDesc *emitter::emitNewInstrCallDir(int id->idcDisp = 0; id->idcArgCnt = argCnt; -#ifdef FEATURE_UNIX_AMD64_STRUCT_PASSING +#if MULTIREG_HAS_SECOND_GC_RET emitSetSecondRetRegGCType(id, secondRetSize); -#endif // FEATURE_UNIX_AMD64_STRUCT_PASSING +#endif // MULTIREG_HAS_SECOND_GC_RET return id; } diff --git a/src/jit/emit.h b/src/jit/emit.h index c8de077dc1..e3eee557d4 100644 --- a/src/jit/emit.h +++ b/src/jit/emit.h @@ -1241,14 +1241,12 @@ protected: regMaskTP idcByrefRegs; // ... byref registers unsigned idcArgCnt; // ... lots of args or (<0 ==> caller pops args) -#ifdef FEATURE_UNIX_AMD64_STRUCT_PASSING +#if MULTIREG_HAS_SECOND_GC_RET // This method handle the GC-ness of the second register in a 2 register returned struct on System V. GCtype idSecondGCref() const { return (GCtype)_idcSecondRetRegGCType; } void idSecondGCref(GCtype gctype) { _idcSecondRetRegGCType = gctype; } -#endif // FEATURE_UNIX_AMD64_STRUCT_PASSING private: -#ifdef FEATURE_UNIX_AMD64_STRUCT_PASSING // This member stores the GC-ness of the second register in a 2 register returned struct on System V. // It is added to the call struct since it is not needed by the base instrDesc struct, which keeps GC-ness // of the first register for the instCall nodes. @@ -1257,7 +1255,7 @@ protected: // since the GC-ness of the second register is only needed for call instructions. // The base struct's member keeping the GC-ness of the first return register is _idGCref. GCtype _idcSecondRetRegGCType : 2; // ... GC type for the second return register. -#endif // FEATURE_UNIX_AMD64_STRUCT_PASSING +#endif // MULTIREG_HAS_SECOND_GC_RET }; struct instrDescArmFP : instrDesc @@ -1640,9 +1638,9 @@ private: regNumber emitSyncThisObjReg; // where is "this" enregistered for synchronized methods? -#ifdef FEATURE_UNIX_AMD64_STRUCT_PASSING +#if MULTIREG_HAS_SECOND_GC_RET void emitSetSecondRetRegGCType(instrDescCGCA* id, emitAttr secondRetSize); -#endif // FEATURE_UNIX_AMD64_STRUCT_PASSING +#endif // MULTIREG_HAS_SECOND_GC_RET static void emitEncodeCallGCregs(regMaskTP regs, instrDesc *id); static unsigned emitDecodeCallGCregs(instrDesc *id); diff --git a/src/jit/emitarm64.cpp b/src/jit/emitarm64.cpp index f08af44642..317ca87935 100644 --- a/src/jit/emitarm64.cpp +++ b/src/jit/emitarm64.cpp @@ -6817,6 +6817,7 @@ void emitter::emitIns_Call(EmitCallType callType, void* addr, ssize_t argSize, emitAttr retSize, + emitAttr secondRetSize, VARSET_VALARG_TP ptrVars, regMaskTP gcrefRegs, regMaskTP byrefRegs, @@ -6918,7 +6919,13 @@ void emitter::emitIns_Call(EmitCallType callType, assert(callType == EC_INDIR_R); - id = emitNewInstrCallInd(argCnt, disp, ptrVars, gcrefRegs, byrefRegs, retSize); + id = emitNewInstrCallInd(argCnt, + disp, + ptrVars, + gcrefRegs, + byrefRegs, + retSize, + secondRetSize); } else { @@ -6928,7 +6935,12 @@ void emitter::emitIns_Call(EmitCallType callType, assert(callType == EC_FUNC_TOKEN || callType == EC_FUNC_ADDR); - id = emitNewInstrCallDir(argCnt, ptrVars, gcrefRegs, byrefRegs, retSize); + id = emitNewInstrCallDir(argCnt, + ptrVars, + gcrefRegs, + byrefRegs, + retSize, + secondRetSize); } /* Update the emitter's live GC ref sets */ @@ -8435,7 +8447,7 @@ unsigned emitter::emitOutputCall(insGroup *ig, BYTE *dst, instrDesc *id, code_t // assert(outputInstrSize == callInstrSize); - // If the method returns a GC ref, mark R0 appropriately. + // If the method returns a GC ref, mark INTRET (R0) appropriately. if (id->idGCref() == GCT_GCREF) { gcrefRegs |= RBM_INTRET; @@ -8445,6 +8457,20 @@ unsigned emitter::emitOutputCall(insGroup *ig, BYTE *dst, instrDesc *id, code_t byrefRegs |= RBM_INTRET; } + // If is a multi-register return method is called, mark INTRET_1 (X1) appropriately + if (id->idIsLargeCall()) + { + instrDescCGCA* idCall = (instrDescCGCA*)id; + if (idCall->idSecondGCref() == GCT_GCREF) + { + gcrefRegs |= RBM_INTRET_1; + } + else if (idCall->idSecondGCref() == GCT_BYREF) + { + byrefRegs |= RBM_INTRET_1; + } + } + // If the GC register set has changed, report the new set. if (gcrefRegs != emitThisGCrefRegs) { diff --git a/src/jit/emitarm64.h b/src/jit/emitarm64.h index 6538146512..b35af26311 100644 --- a/src/jit/emitarm64.h +++ b/src/jit/emitarm64.h @@ -75,14 +75,16 @@ private: VARSET_VALARG_TP GCvars, regMaskTP gcrefRegs, regMaskTP byrefRegs, - emitAttr retSize); + emitAttr retSize, + emitAttr secondRetSize); instrDesc *emitNewInstrCallInd( int argCnt, ssize_t disp, VARSET_VALARG_TP GCvars, regMaskTP gcrefRegs, regMaskTP byrefRegs, - emitAttr retSize); + emitAttr retSize, + emitAttr secondRetSize); void emitGetInsCns (instrDesc *id, CnsVal *cv); ssize_t emitGetInsAmdCns(instrDesc *id, CnsVal *cv); @@ -886,6 +888,7 @@ public: void* addr, ssize_t argSize, emitAttr retSize, + emitAttr secondRetSize, VARSET_VALARG_TP ptrVars, regMaskTP gcrefRegs, regMaskTP byrefRegs, diff --git a/src/jit/emitxarch.cpp b/src/jit/emitxarch.cpp index 632cc023e5..53f9fc341d 100644 --- a/src/jit/emitxarch.cpp +++ b/src/jit/emitxarch.cpp @@ -5437,7 +5437,7 @@ void emitter::emitIns_Call(EmitCallType callType, void* addr, ssize_t argSize, emitAttr retSize - FEATURE_UNIX_AMD64_STRUCT_PASSING_ONLY_ARG(emitAttr secondRetSize), + MULTIREG_HAS_SECOND_GC_RET_ONLY_ARG(emitAttr secondRetSize), VARSET_VALARG_TP ptrVars, regMaskTP gcrefRegs, regMaskTP byrefRegs, @@ -5637,7 +5637,7 @@ void emitter::emitIns_Call(EmitCallType callType, gcrefRegs, byrefRegs, retSize - FEATURE_UNIX_AMD64_STRUCT_PASSING_ONLY_ARG(secondRetSize)); + MULTIREG_HAS_SECOND_GC_RET_ONLY_ARG(secondRetSize)); } else { @@ -5653,7 +5653,7 @@ void emitter::emitIns_Call(EmitCallType callType, gcrefRegs, byrefRegs, retSize - FEATURE_UNIX_AMD64_STRUCT_PASSING_ONLY_ARG(secondRetSize)); + MULTIREG_HAS_SECOND_GC_RET_ONLY_ARG(secondRetSize)); } /* Update the emitter's live GC ref sets */ diff --git a/src/jit/emitxarch.h b/src/jit/emitxarch.h index 29ba156305..7c841176cc 100644 --- a/src/jit/emitxarch.h +++ b/src/jit/emitxarch.h @@ -168,7 +168,7 @@ private: regMaskTP gcrefRegs, regMaskTP byrefRegs, emitAttr retSize - FEATURE_UNIX_AMD64_STRUCT_PASSING_ONLY_ARG(emitAttr secondRegSize)); + MULTIREG_HAS_SECOND_GC_RET_ONLY_ARG(emitAttr secondRetSize)); instrDesc *emitNewInstrCallInd( int argCnt, ssize_t disp, @@ -176,7 +176,7 @@ private: regMaskTP gcrefRegs, regMaskTP byrefRegs, emitAttr retSize - FEATURE_UNIX_AMD64_STRUCT_PASSING_ONLY_ARG(emitAttr secondRegSize)); + MULTIREG_HAS_SECOND_GC_RET_ONLY_ARG(emitAttr secondRetSize)); void emitGetInsCns (instrDesc *id, CnsVal *cv); ssize_t emitGetInsAmdCns(instrDesc *id, CnsVal *cv); @@ -471,7 +471,7 @@ public: void* addr, ssize_t argSize, emitAttr retSize - FEATURE_UNIX_AMD64_STRUCT_PASSING_ONLY_ARG(emitAttr secondRegSize), + MULTIREG_HAS_SECOND_GC_RET_ONLY_ARG(emitAttr secondRetSize), VARSET_VALARG_TP ptrVars, regMaskTP gcrefRegs, regMaskTP byrefRegs, @@ -485,7 +485,7 @@ public: void* addr, ssize_t argSize, emitAttr retSize - FEATURE_UNIX_AMD64_STRUCT_PASSING_ONLY_ARG(emitAttr secondRegSize), + MULTIREG_HAS_SECOND_GC_RET_ONLY_ARG(emitAttr secondRetSize), VARSET_VALARG_TP ptrVars, regMaskTP gcrefRegs, regMaskTP byrefRegs, diff --git a/src/jit/flowgraph.cpp b/src/jit/flowgraph.cpp index 32e069f524..8d59d22ff4 100644 --- a/src/jit/flowgraph.cpp +++ b/src/jit/flowgraph.cpp @@ -21458,7 +21458,7 @@ void Compiler::fgNoteNonInlineCandidate(GenTreePtr tree, #endif -#if defined(FEATURE_HFA) || defined(FEATURE_UNIX_AMD64_STRUCT_PASSING) +#if FEATURE_MULTIREG_RET /********************************************************************************* * @@ -21590,7 +21590,7 @@ void Compiler::fgAttachStructInlineeToAsg(GenTreePtr tree, GenTreePtr child, COR tree->CopyFrom(gtNewCpObjNode(dstAddr, srcAddr, retClsHnd, false), this); } -#endif // defined(FEATURE_HFA) || defined(FEATURE_UNIX_AMD64_STRUCT_PASSING) +#endif // FEATURE_MULTIREG_RET /***************************************************************************** * Callback to replace the inline return expression place holder (GT_RET_EXPR) @@ -21602,15 +21602,17 @@ Compiler::fgWalkResult Compiler::fgUpdateInlineReturnExpressionPlaceHolder( { GenTreePtr tree = *pTree; Compiler* comp = data->compiler; + CORINFO_CLASS_HANDLE retClsHnd = NO_CLASS_HANDLE; if (tree->gtOper == GT_RET_EXPR) { -#if defined(FEATURE_HFA) || defined(FEATURE_UNIX_AMD64_STRUCT_PASSING) - // We are going to copy the tree from the inlinee, so save the handle now. - CORINFO_CLASS_HANDLE retClsHnd = varTypeIsStruct(tree) - ? tree->gtRetExpr.gtRetClsHnd - : NO_CLASS_HANDLE; -#endif // defined(FEATURE_HFA) || defined(FEATURE_UNIX_AMD64_STRUCT_PASSING) + // We are going to copy the tree from the inlinee, + // so record the handle now. + // + if (varTypeIsStruct(tree)) + { + retClsHnd = tree->gtRetExpr.gtRetClsHnd; + } do { @@ -21642,15 +21644,19 @@ Compiler::fgWalkResult Compiler::fgUpdateInlineReturnExpressionPlaceHolder( #endif // DEBUG } while (tree->gtOper == GT_RET_EXPR); + } -#if defined(FEATURE_HFA) || defined(FEATURE_UNIX_AMD64_STRUCT_PASSING) -#if defined(FEATURE_HFA) - if (retClsHnd != NO_CLASS_HANDLE && comp->IsHfa(retClsHnd)) -#elif defined(FEATURE_UNIX_AMD64_STRUCT_PASSING) - if (retClsHnd != NO_CLASS_HANDLE && comp->IsRegisterPassable(retClsHnd)) -#else - assert(!"Unhandled target"); -#endif // FEATURE_HFA +#if FEATURE_MULTIREG_RET + + // Did we record a struct return class handle above? + // + if (retClsHnd != NO_CLASS_HANDLE) + { + // Is this a type that is returned in multiple registers? + // if so we need to force into into a form we accept. + // i.e. LclVar = call() + // + if (comp->IsMultiRegReturnedType(retClsHnd)) { GenTreePtr parent = data->parent; // See assert below, we only look one level above for an asg parent. @@ -21665,18 +21671,18 @@ Compiler::fgWalkResult Compiler::fgUpdateInlineReturnExpressionPlaceHolder( tree->CopyFrom(comp->fgAssignStructInlineeToVar(tree, retClsHnd), comp); } } -#endif // defined(FEATURE_HFA) || defined(FEATURE_UNIX_AMD64_STRUCT_PASSING) } -#if defined(DEBUG) && defined(FEATURE_HFA) || defined(FEATURE_UNIX_AMD64_STRUCT_PASSING) +#if defined(DEBUG) + // Make sure we don't have a tree like so: V05 = (, , , retExpr); // Since we only look one level above for the parent for '=' and // do not check if there is a series of COMMAs. See above. // Importer and FlowGraph will not generate such a tree, so just // leaving an assert in here. This can be fixed by looking ahead // when we visit GT_ASG similar to fgAttachStructInlineeToAsg. - else if (tree->gtOper == GT_ASG && - tree->gtOp.gtOp2->gtOper == GT_COMMA) + // + if ((tree->gtOper == GT_ASG) && (tree->gtOp.gtOp2->gtOper == GT_COMMA)) { GenTreePtr comma; for (comma = tree->gtOp.gtOp2; @@ -21686,17 +21692,13 @@ Compiler::fgWalkResult Compiler::fgUpdateInlineReturnExpressionPlaceHolder( // empty } -#if defined(FEATURE_HFA) noway_assert(!varTypeIsStruct(comma) || comma->gtOper != GT_RET_EXPR || - (!comp->IsHfa(comma->gtRetExpr.gtRetClsHnd))); -#elif defined(FEATURE_UNIX_AMD64_STRUCT_PASSING) - noway_assert(!varTypeIsStruct(comma) || - comma->gtOper != GT_RET_EXPR || - (!comp->IsRegisterPassable(comma->gtRetExpr.gtRetClsHnd))); -#endif // defined(FEATURE_UNIX_AMD64_STRUCT_PASSING) + !comp->IsMultiRegReturnedType(comma->gtRetExpr.gtRetClsHnd)); } -#endif // defined(DEBUG) && defined(FEATURE_HFA) || defined(FEATURE_UNIX_AMD64_STRUCT_PASSING) + +#endif // defined(DEBUG) +#endif // FEATURE_MULTIREG_RET return WALK_CONTINUE; } @@ -22248,6 +22250,22 @@ _Done: compNeedsGSSecurityCookie |= InlineeCompiler->compNeedsGSSecurityCookie; compGSReorderStackLayout |= InlineeCompiler->compGSReorderStackLayout; + // Update optMethodFlags + +#ifdef DEBUG + unsigned optMethodFlagsBefore = optMethodFlags; +#endif + + optMethodFlags |= InlineeCompiler->optMethodFlags; + +#ifdef DEBUG + if (optMethodFlags != optMethodFlagsBefore) + { + JITDUMP("INLINER: Updating optMethodFlags -- root:%0x callee:%0x new:%0x\n", + optMethodFlagsBefore, InlineeCompiler->optMethodFlags, optMethodFlags); + } +#endif + // If there is non-NULL return, replace the GT_CALL with its return value expression, // so later it will be picked up by the GT_RET_EXPR node. if ((pInlineInfo->inlineCandidateInfo->fncRetType != TYP_VOID) || (iciCall->gtCall.gtReturnType == TYP_STRUCT)) diff --git a/src/jit/gcencode.cpp b/src/jit/gcencode.cpp index fb033ddfae..d515d78c4b 100644 --- a/src/jit/gcencode.cpp +++ b/src/jit/gcencode.cpp @@ -1785,7 +1785,7 @@ size_t GCInfo::gcMakeRegPtrTable(BYTE* dest, /* If this non-enregistered pointer arg is never * used, we don't need to report it */ - assert(varDsc->lvRefCnt == 0); + assert(varDsc->lvRefCnt == 0); // This assert is currently a known issue for X86-RyuJit continue; } else if (varDsc->lvIsRegArg && varDsc->lvTracked) diff --git a/src/jit/gentree.cpp b/src/jit/gentree.cpp index f9addbb490..8eafd68700 100644 --- a/src/jit/gentree.cpp +++ b/src/jit/gentree.cpp @@ -6013,22 +6013,8 @@ bool Compiler::gtArgIsThisPtr(fgArgTabEntryPtr argEntry) * Create a node that will assign 'src' to 'dst'. */ -GenTreePtr Compiler::gtNewAssignNode(GenTreePtr dst, GenTreePtr src DEBUGARG(bool isPhiDefn)) -{ - var_types type = dst->TypeGet(); - - // ARM has HFA struct return values, HFA return values are received in registers from GT_CALL, - // using struct assignment. -#ifdef FEATURE_HFA - assert(isPhiDefn || type != TYP_STRUCT || IsHfa(dst) || IsHfa(src)); -#elif defined(FEATURE_UNIX_AMD64_STRUCT_PASSING) - // You need to use GT_COPYBLK for assigning structs - // See impAssignStruct() - assert(isPhiDefn || type != TYP_STRUCT || IsRegisterPassable(dst) || IsRegisterPassable(src)); -#else // !FEATURE_UNIX_AMD64_STRUCT_PASSING - assert(isPhiDefn || type != TYP_STRUCT); -#endif - +GenTreePtr Compiler::gtNewAssignNode(GenTreePtr dst, GenTreePtr src) +{ /* Mark the target as being assigned */ if ((dst->gtOper == GT_LCL_VAR) || (dst->OperGet() == GT_LCL_FLD)) @@ -6044,7 +6030,7 @@ GenTreePtr Compiler::gtNewAssignNode(GenTreePtr dst, GenTreePtr src DEB /* Create the assignment node */ - GenTreePtr asg = gtNewOperNode(GT_ASG, type, dst, src); + GenTreePtr asg = gtNewOperNode(GT_ASG, dst->TypeGet(), dst, src); /* Mark the expression as containing an assignment */ @@ -14347,42 +14333,151 @@ bool GenTree::isCommutativeSIMDIntrinsic() } #endif //FEATURE_SIMD -//------------------------------------------------------------------------- -// Initialize: Return Type Descriptor given type handle. +//--------------------------------------------------------------------------------------- +// InitializeStructReturnType: +// Initialize the Return Type Descriptor for a method that returns a struct type // // Arguments // comp - Compiler Instance -// retClsHnd - VM handle to the type returned +// retClsHnd - VM handle to the struct type returned by the method // // Return Value // None // -void ReturnTypeDesc::InitializeReturnType(Compiler* comp, CORINFO_CLASS_HANDLE retClsHnd) +void ReturnTypeDesc::InitializeStructReturnType(Compiler* comp, CORINFO_CLASS_HANDLE retClsHnd) { assert(!m_inited); -#ifdef FEATURE_UNIX_AMD64_STRUCT_PASSING +#if FEATURE_MULTIREG_RET + assert(retClsHnd != NO_CLASS_HANDLE); + unsigned structSize = comp->info.compCompHnd->getClassSize(retClsHnd); - SYSTEMV_AMD64_CORINFO_STRUCT_REG_PASSING_DESCRIPTOR structDesc; - comp->eeGetSystemVAmd64PassStructInRegisterDescriptor(retClsHnd, &structDesc); + Compiler::structPassingKind howToReturnStruct; + var_types returnType = comp->getReturnTypeForStruct(retClsHnd, &howToReturnStruct, structSize); - if (structDesc.passedInRegisters) + switch (howToReturnStruct) { - for (int i=0; i<structDesc.eightByteCount; i++) + case Compiler::SPK_PrimitiveType: { - assert(i < MAX_RET_REG_COUNT); - m_regType[i] = comp->GetEightByteType(structDesc, i); + assert(returnType != TYP_UNKNOWN); + assert(returnType != TYP_STRUCT); + m_regType[0] = returnType; + break; } - } -#elif defined(_TARGET_X86_) - // TODO-X86: Assumes we are only using ReturnTypeDesc for longs on x86. - // Will need to be updated in the future to handle other return types - assert(MAX_RET_REG_COUNT == 2); + case Compiler::SPK_ByValueAsHfa: + { + assert(returnType == TYP_STRUCT); + var_types hfaType = comp->GetHfaType(retClsHnd); + + // We should have an hfa struct type + assert(varTypeIsFloating(hfaType)); + + // Note that the retail build issues a warning about a potential divsion by zero without this Max function + unsigned elemSize = Max((unsigned)1, EA_SIZE_IN_BYTES(emitActualTypeSize(hfaType))); + + // The size of this struct should be evenly divisible by elemSize + assert((structSize % elemSize) == 0); + + unsigned hfaCount = (structSize / elemSize); + for (unsigned i = 0; i < hfaCount; ++i) + { + m_regType[i] = hfaType; + } + + if (comp->compFloatingPointUsed == false) + { + comp->compFloatingPointUsed = true; + } + break; + } + + case Compiler::SPK_ByValue: + { + assert(returnType == TYP_STRUCT); + +#ifdef FEATURE_UNIX_AMD64_STRUCT_PASSING + + SYSTEMV_AMD64_CORINFO_STRUCT_REG_PASSING_DESCRIPTOR structDesc; + comp->eeGetSystemVAmd64PassStructInRegisterDescriptor(retClsHnd, &structDesc); + + assert(structDesc.passedInRegisters); + for (int i = 0; i < structDesc.eightByteCount; i++) + { + assert(i < MAX_RET_REG_COUNT); + m_regType[i] = comp->GetEightByteType(structDesc, i); + } + +#elif defined(_TARGET_ARM64_) + + // a non-HFA struct returned using two registers + // + assert((structSize > TARGET_POINTER_SIZE) && (structSize <= (2 * TARGET_POINTER_SIZE))); + + BYTE gcPtrs[2] = { TYPE_GC_NONE, TYPE_GC_NONE }; + comp->info.compCompHnd->getClassGClayout(retClsHnd, &gcPtrs[0]); + for (unsigned i = 0; i < 2; ++i) + { + m_regType[i] = comp->getJitGCType(gcPtrs[i]); + } + +#else // _TARGET_XXX_ + + // This target needs support here! + // + NYI("Unsupported TARGET returning a TYP_STRUCT in InitializeStructReturnType"); + + +#endif // FEATURE_UNIX_AMD64_STRUCT_PASSING + + break; // for case SPK_ByValue + } + + case Compiler::SPK_ByReference: + + // We are returning using the return buffer argument + // There are no return registers + break; + + default: + + unreached(); // By the contract of getReturnTypeForStruct we should never get here. + + } // end of switch (howToReturnStruct) + +#endif // FEATURE_MULTIREG_RET + +#ifdef DEBUG + m_inited = true; +#endif +} + +//--------------------------------------------------------------------------------------- +// InitializeLongReturnType: +// Initialize the Return Type Descriptor for a method that returns a TYP_LONG +// +// Arguments +// comp - Compiler Instance +// +// Return Value +// None +// +void ReturnTypeDesc::InitializeLongReturnType(Compiler* comp) +{ +#if defined(_TARGET_X86_) + + // Setups up a ReturnTypeDesc for returning a long using two registers + // + assert(MAX_RET_REG_COUNT >= 2); m_regType[0] = TYP_INT; m_regType[1] = TYP_INT; -#endif // FEATURE_UNIX_AMD64_STRUCT_PASSING + +#else // not _TARGET_X86_ + + m_regType[0] = TYP_LONG; + +#endif // _TARGET_X86_ #ifdef DEBUG m_inited = true; @@ -14405,7 +14500,6 @@ void ReturnTypeDesc::InitializeReturnType(Compiler* comp, CORINFO_CLASS_HANDLE r // targets (Arm64/Arm32/x86). // // TODO-ARM: Implement this routine to support HFA returns. -// TODO-ARM64: Implement this routine to support HFA returns. // TODO-X86: Implement this routine to support long returns. regNumber ReturnTypeDesc::GetABIReturnReg(unsigned idx) { @@ -14459,6 +14553,7 @@ regNumber ReturnTypeDesc::GetABIReturnReg(unsigned idx) } #elif defined(_TARGET_X86_) + if (idx == 0) { resultReg = REG_LNGRET_LO; @@ -14467,7 +14562,22 @@ regNumber ReturnTypeDesc::GetABIReturnReg(unsigned idx) { resultReg = REG_LNGRET_HI; } -#endif //FEATURE_UNIX_AMD64_STRUCT_PASSING + +#elif defined(_TARGET_ARM64_) + + var_types regType = GetReturnRegType(idx); + if (varTypeIsIntegralOrI(regType)) + { + noway_assert(idx < 2); // Up to 2 return registers for 16-byte structs + resultReg = (idx == 0) ? REG_INTRET : REG_INTRET_1; // X0 or X1 + } + else + { + noway_assert(idx < 4); // Up to 4 return registers for HFA's + resultReg = (regNumber)((unsigned)(REG_FLOATRET)+idx); // V0, V1, V2 or V3 + } + +#endif // TARGET_XXX assert(resultReg != REG_NA); return resultReg; diff --git a/src/jit/gentree.h b/src/jit/gentree.h index 48a04b6d9e..ef98214b54 100644 --- a/src/jit/gentree.h +++ b/src/jit/gentree.h @@ -2412,8 +2412,12 @@ public: Reset(); } - // Initialize the return type descriptor given its type handle - void InitializeReturnType(Compiler* comp, CORINFO_CLASS_HANDLE retClsHnd); + // Initialize the Return Type Descriptor for a method that returns a struct type + void InitializeStructReturnType(Compiler* comp, CORINFO_CLASS_HANDLE retClsHnd); + + // Initialize the Return Type Descriptor for a method that returns a TYP_LONG + // Only needed for X86 + void InitializeLongReturnType(Compiler* comp); // Reset type descriptor to defaults void Reset() @@ -2832,14 +2836,13 @@ struct GenTreeCall final : public GenTree // This is implemented only for x64 Unix and yet to be implemented for // other multi-reg return target arch (arm64/arm32/x86). // - // TODO-ARM: Implement this routine for Arm64 and Arm32 bool HasMultiRegRetVal() const - { -#ifdef FEATURE_UNIX_AMD64_STRUCT_PASSING - return varTypeIsStruct(gtType) && !HasRetBufArg(); -#elif defined(_TARGET_X86_) && !defined(LEGACY_BACKEND) + { +#if defined(_TARGET_X86_) && !defined(LEGACY_BACKEND) // LEGACY_BACKEND does not use multi reg returns for calls with long return types return varTypeIsLong(gtType); +#elif FEATURE_MULTIREG_RET + return varTypeIsStruct(gtType) && !HasRetBufArg(); #else return false; #endif diff --git a/src/jit/importer.cpp b/src/jit/importer.cpp index 8a07d9214c..98f8183b7a 100644 --- a/src/jit/importer.cpp +++ b/src/jit/importer.cpp @@ -1125,17 +1125,26 @@ GenTreePtr Compiler::impAssignStructPtr(GenTreePtr destAddr, var_types returnType = (var_types)src->gtCall.gtReturnType; - // We don't need a return buffer, so just change this to "(returnType)*dest = call" + // We won't use a return buffer, so change the type of src->gtType to 'returnType' src->gtType = genActualType(returnType); + // First we try to change this to "LclVar/LclFld = call" + // if ((destAddr->gtOper == GT_ADDR) && (destAddr->gtOp.gtOp1->gtOper == GT_LCL_VAR)) { // If it is a multi-reg struct return, don't change the oper to GT_LCL_FLD. - // That is, IR will be of the form lclVar = call for multi-reg return - + // That is, the IR will be of the form lclVar = call for multi-reg return + // GenTreePtr lcl = destAddr->gtOp.gtOp1; - if (!src->AsCall()->HasMultiRegRetVal()) + if (src->AsCall()->HasMultiRegRetVal()) { + // Mark the struct LclVar as used in a MultiReg return context + // which currently makes it non promotable. + lvaTable[lcl->gtLclVarCommon.gtLclNum].lvIsMultiRegRet = true; + } + else // The call result is not a multireg return + { + // We change this to a GT_LCL_FLD (from a GT_ADDR of a GT_LCL_VAR) lcl->ChangeOper(GT_LCL_FLD); fgLclFldAssign(lcl->gtLclVarCommon.gtLclNum); } @@ -1153,8 +1162,9 @@ GenTreePtr Compiler::impAssignStructPtr(GenTreePtr destAddr, lvaTable[lcl->gtLclVarCommon.gtLclNum].lvIsMultiRegRet = true; #endif } - else + else // we don't have a GT_ADDR of a GT_LCL_VAR { + // We change this to "(returnType)*destAddr = call" dest = gtNewOperNode(GT_IND, returnType, destAddr); // !!! The destination could be on stack. !!! @@ -1185,9 +1195,12 @@ GenTreePtr Compiler::impAssignStructPtr(GenTreePtr destAddr, } else { + // Case of inline method returning a struct in one or more registers. + // var_types returnType = (var_types)call->gtCall.gtReturnType; - src->gtType = genActualType(returnType); + // We won't need a return buffer + src->gtType = genActualType(returnType); call->gtType = src->gtType; dest = gtNewOperNode(GT_IND, returnType, destAddr); @@ -7180,7 +7193,7 @@ DONE_CALL: } else if (varTypeIsLong(callRetTyp)) { - call = impInitCallReturnTypeDesc(call, sig->retTypeClass); + call = impInitCallLongReturn(call); } if ((call->gtFlags & GTF_CALL_INLINE_CANDIDATE) != 0) @@ -7229,63 +7242,23 @@ DONE_CALL: bool Compiler::impMethodInfo_hasRetBuffArg(CORINFO_METHOD_INFO * methInfo) { - if (methInfo->args.retType != CORINFO_TYPE_VALUECLASS && methInfo->args.retType != CORINFO_TYPE_REFANY) + CorInfoType corType = methInfo->args.retType; + + if ((corType == CORINFO_TYPE_VALUECLASS) ||(corType == CORINFO_TYPE_REFANY)) { - return false; - } + // We have some kind of STRUCT being returned -#if defined(FEATURE_UNIX_AMD64_STRUCT_PASSING) - assert(!info.compIsVarArgs && "Varargs not supported in CoreCLR on Unix."); - if (IsRegisterPassable(methInfo->args.retTypeClass)) - { - return false; - } + structPassingKind howToReturnStruct = SPK_Unknown; - // The struct is not aligned properly or it is bigger than 16 bytes, - // or it is custom layout, or it is not passed in registers for any other reason. - return true; -#elif defined(_TARGET_XARCH_) - // On Amd64 and x86 only we don't need a return buffer if: - // - // i) TYP_STRUCT argument that can fit into a single register and - // ii) Power of two sized TYP_STRUCT. - // - unsigned size = info.compCompHnd->getClassSize(methInfo->args.retTypeClass); - if ((size <= TARGET_POINTER_SIZE) && isPow2(size)) - { - return false; - } -#else - // Generally we don't need a return buffer if: - // i) TYP_STRUCT argument that can fit into a single register - // The power of two size requirement only applies for Amd64 and x86. - // + var_types returnType = getReturnTypeForStruct(methInfo->args.retTypeClass, &howToReturnStruct); - unsigned size = info.compCompHnd->getClassSize(methInfo->args.retTypeClass); - if (size <= TARGET_POINTER_SIZE) - { - return false; - } -#endif - -#if FEATURE_MULTIREG_RET - - // Support for any additional cases that don't use a Return Buffer Argument - // on targets that support multi-reg return valuetypes. - // - #ifdef FEATURE_HFA - // On ARM HFAs are returned in registers. - if (!info.compIsVarArgs && IsHfa(methInfo->args.retTypeClass)) - { - return false; + if (howToReturnStruct == SPK_ByReference) + { + return true; + } } - #endif // FEATURE_HFA - -#endif // FEATURE_MULTIREG_RET - - // Otherwise we require that a RetBuffArg be used - return true; + return false; } #ifdef DEBUG @@ -7376,39 +7349,10 @@ GenTreePtr Compiler::impFixupCallStructReturn(GenTreePtr call #if FEATURE_MULTIREG_RET // Initialize Return type descriptor of call node ReturnTypeDesc* retTypeDesc = callNode->GetReturnTypeDesc(); - retTypeDesc->InitializeReturnType(this, retClsHnd); + retTypeDesc->InitializeStructReturnType(this, retClsHnd); #endif // FEATURE_MULTIREG_RET -#if FEATURE_MULTIREG_RET && defined(FEATURE_HFA) - // There is no fixup necessary if the return type is a HFA struct. - // HFA structs are returned in registers for ARM32 and ARM64 - // - if (!call->gtCall.IsVarargs() && IsHfa(retClsHnd)) - { - if (call->gtCall.CanTailCall()) - { - if (info.compIsVarArgs) - { - // We cannot tail call because control needs to return to fixup the calling - // convention for result return. - call->gtCall.gtCallMoreFlags &= ~GTF_CALL_M_EXPLICIT_TAILCALL; - } - else - { - // If we can tail call returning HFA, then don't assign it to - // a variable back and forth. - return call; - } - } - - if (call->gtFlags & GTF_CALL_INLINE_CANDIDATE) - { - return call; - } - - return impAssignMultiRegTypeToVar(call, retClsHnd); - } -#elif defined(FEATURE_UNIX_AMD64_STRUCT_PASSING) +#ifdef FEATURE_UNIX_AMD64_STRUCT_PASSING // Not allowed for FEATURE_CORCLR which is the only SKU available for System V OSs. assert(!callNode->IsVarargs() && "varargs not allowed for System V OSs."); @@ -7448,8 +7392,38 @@ GenTreePtr Compiler::impFixupCallStructReturn(GenTreePtr call callNode->gtCallMoreFlags |= GTF_CALL_M_RETBUFFARG; } - return call; -#endif // FEATURE_UNIX_AMD64_STRUCT_PASSING +#else // not FEATURE_UNIX_AMD64_STRUCT_PASSING + +#if FEATURE_MULTIREG_RET && defined(_TARGET_ARM_) + // There is no fixup necessary if the return type is a HFA struct. + // HFA structs are returned in registers for ARM32 and ARM64 + // + if (!call->gtCall.IsVarargs() && IsHfa(retClsHnd)) + { + if (call->gtCall.CanTailCall()) + { + if (info.compIsVarArgs) + { + // We cannot tail call because control needs to return to fixup the calling + // convention for result return. + call->gtCall.gtCallMoreFlags &= ~GTF_CALL_M_EXPLICIT_TAILCALL; + } + else + { + // If we can tail call returning HFA, then don't assign it to + // a variable back and forth. + return call; + } + } + + if (call->gtFlags & GTF_CALL_INLINE_CANDIDATE) + { + return call; + } + + return impAssignMultiRegTypeToVar(call, retClsHnd); + } +#endif // _TARGET_ARM_ // Check for TYP_STRUCT type that wraps a primitive type // Such structs are returned using a single register @@ -7473,48 +7447,65 @@ GenTreePtr Compiler::impFixupCallStructReturn(GenTreePtr call compLongUsed = true; else if (((returnType == TYP_FLOAT) || (returnType == TYP_DOUBLE)) && (compFloatingPointUsed == false)) compFloatingPointUsed = true; + +#if FEATURE_MULTIREG_RET + unsigned retRegCount = retTypeDesc->GetReturnRegCount(); + assert(retRegCount != 0); + + if (retRegCount >= 2) + { + if ((!callNode->CanTailCall()) && (!callNode->IsInlineCandidate())) + { + // Force a call returning multi-reg struct to be always of the IR form + // tmp = call + // + // No need to assign a multi-reg struct to a local var if: + // - It is a tail call or + // - The call is marked for in-lining later + return impAssignMultiRegTypeToVar(call, retClsHnd); + } + } +#endif // FEATURE_MULTIREG_RET + } +#endif // not FEATURE_UNIX_AMD64_STRUCT_PASSING + return call; } -//----------------------------------------------------------------------------------- -// impInitCallReturnTypeDesc: Initialize the ReturnTypDesc for a call node +//------------------------------------------------------------------------------------- +// impInitCallLongReturn: +// Initialize the ReturnTypDesc for a call that returns a TYP_LONG // // Arguments: // call - GT_CALL GenTree node -// retClsHnd - Class handle of return type of the call // // Return Value: // Returns new GenTree node after initializing the ReturnTypeDesc of call node // -GenTreePtr Compiler::impInitCallReturnTypeDesc(GenTreePtr call, - CORINFO_CLASS_HANDLE retClsHnd) +GenTreePtr Compiler::impInitCallLongReturn(GenTreePtr call) { + assert(call->gtOper == GT_CALL); + #if defined(_TARGET_X86_) && !defined(LEGACY_BACKEND) // LEGACY_BACKEND does not use multi reg returns for calls with long return types - assert(call->gtOper == GT_CALL); - if (!varTypeIsLong(call)) + if (varTypeIsLong(call)) { - return call; - } - - call->gtCall.gtRetClsHnd = retClsHnd; - - GenTreeCall* callNode = call->AsCall(); + GenTreeCall* callNode = call->AsCall(); - // The return type will remain as the incoming long type - callNode->gtReturnType = call->gtType; + // The return type will remain as the incoming long type + callNode->gtReturnType = call->gtType; - // Initialize Return type descriptor of call node - ReturnTypeDesc* retTypeDesc = callNode->GetReturnTypeDesc(); - retTypeDesc->InitializeReturnType(this, retClsHnd); + // Initialize Return type descriptor of call node + ReturnTypeDesc* retTypeDesc = callNode->GetReturnTypeDesc(); + retTypeDesc->InitializeLongReturnType(this); - unsigned retRegCount = retTypeDesc->GetReturnRegCount(); - // must be a long returned in two registers - assert(retRegCount == 2); + // must be a long returned in two registers + assert(retTypeDesc->GetReturnRegCount() == 2); + } #endif // _TARGET_X86_ && !LEGACY_BACKEND return call; @@ -7564,7 +7555,8 @@ GenTreePtr Compiler::impFixupStructReturnType(GenTreePtr op, CORINFO_CL assert(info.compRetNativeType != TYP_STRUCT); #endif // !FEATURE_UNIX_AMD64_STRUCT_PASSING -#elif FEATURE_MULTIREG_RET && defined(FEATURE_HFA) +#elif FEATURE_MULTIREG_RET && defined(_TARGET_ARM_) + if (!info.compIsVarArgs && IsHfa(retClsHnd)) { if (op->gtOper == GT_LCL_VAR) @@ -7592,6 +7584,39 @@ GenTreePtr Compiler::impFixupStructReturnType(GenTreePtr op, CORINFO_CL } return impAssignMultiRegTypeToVar(op, retClsHnd); } + +#elif FEATURE_MULTIREG_RET && defined(_TARGET_ARM64_) + + // Is method returning a multi-reg struct? + if (IsMultiRegReturnedType(retClsHnd)) + { + if (op->gtOper == GT_LCL_VAR) + { + // This LCL_VAR stays as a TYP_STRUCT + unsigned lclNum = op->gtLclVarCommon.gtLclNum; + + // Make sure this struct type is not struct promoted + lvaTable[lclNum].lvIsMultiRegRet = true; + return op; + } + + if (op->gtOper == GT_CALL) + { + if (op->gtCall.IsVarargs()) + { + // We cannot tail call because control needs to return to fixup the calling + // convention for result return. + op->gtCall.gtCallMoreFlags &= ~GTF_CALL_M_TAILCALL; + op->gtCall.gtCallMoreFlags &= ~GTF_CALL_M_EXPLICIT_TAILCALL; + } + else + { + return op; + } + } + return impAssignMultiRegTypeToVar(op, retClsHnd); + } + #endif // FEATURE_MULTIREG_RET && FEATURE_HFA REDO_RETURN_NODE: @@ -10053,10 +10078,11 @@ ARR_LD_POST_VERIFY: /* Mark the block as containing an index expression */ - if (op1->gtOper == GT_LCL_VAR) + if (op1->gtOper == GT_LCL_VAR) { - if (op2->gtOper == GT_LCL_VAR || - op2->gtOper == GT_ADD) + if (op2->gtOper == GT_LCL_VAR || + op2->gtOper == GT_CNS_INT || + op2->gtOper == GT_ADD) { block->bbFlags |= BBF_HAS_INDX; optMethodFlags |= OMF_HAS_ARRAYREF; @@ -10281,10 +10307,11 @@ ARR_LD_POST_VERIFY: // Mark the block as containing an index expression - if (op3->gtOper == GT_LCL_VAR) + if (op3->gtOper == GT_LCL_VAR) { - if (op1->gtOper == GT_LCL_VAR || - op1->gtOper == GT_ADD) + if (op1->gtOper == GT_LCL_VAR || + op1->gtOper == GT_CNS_INT || + op1->gtOper == GT_ADD) { block->bbFlags |= BBF_HAS_INDX; optMethodFlags |= OMF_HAS_ARRAYREF; @@ -11115,6 +11142,7 @@ _CONV: // Since we are throwing away the value, just normalize // it to its address. This is more efficient. + if (varTypeIsStruct(op1)) { #ifdef FEATURE_UNIX_AMD64_STRUCT_PASSING @@ -11122,10 +11150,12 @@ _CONV: // Calls with large struct return value have to go through this. // Helper calls with small struct return value also have to go // through this since they do not follow Unix calling convention. - if (op1->gtOper != GT_CALL || !IsRegisterPassable(clsHnd) + if (op1->gtOper != GT_CALL || !IsMultiRegReturnedType(clsHnd) || op1->AsCall()->gtCallType == CT_HELPER) #endif // FEATURE_UNIX_AMD64_STRUCT_PASSING - op1 = impGetStructAddr(op1, clsHnd, (unsigned)CHECK_SPILL_ALL, false); + { + op1 = impGetStructAddr(op1, clsHnd, (unsigned)CHECK_SPILL_ALL, false); + } } // If op1 is non-overflow cast, throw it away since it is useless. @@ -13165,56 +13195,48 @@ FIELD_DONE: assert(helper == CORINFO_HELP_UNBOX_NULLABLE && "Make sure the helper is nullable!"); -#ifdef FEATURE_UNIX_AMD64_STRUCT_PASSING - if (varTypeIsStruct(op1)) +#if FEATURE_MULTIREG_RET + + if (varTypeIsStruct(op1) && IsMultiRegReturnedType(resolvedToken.hClass)) { - if (IsMultiRegReturnedType(resolvedToken.hClass)) - { - // Unbox nullable helper returns a TYP_STRUCT. - // We need to spill it to a temp so than we can take the address of it. - // We need the temp so we can pass its address to the unbox_nullable jit helper function. - // This is needed for nullables returned in 2 registers. - // The ones returned in a single register are normalized. - // For the bigger than 16 bytes nullables there is retbuf already passed in - // rdi/rsi (depending whether there is a "this"). - - unsigned tmp = lvaGrabTemp(true DEBUGARG("UNBOXing a register returnable nullable")); - lvaTable[tmp].lvIsMultiRegArg = true; - lvaSetStruct(tmp, resolvedToken.hClass, true /* unsafe value cls check */); - - op2 = gtNewLclvNode(tmp, TYP_STRUCT); - op1 = impAssignStruct(op2, op1, resolvedToken.hClass, (unsigned)CHECK_SPILL_ALL); - assert(op1->gtType == TYP_VOID); // We must be assigning the return struct to the temp. - - op2 = gtNewLclvNode(tmp, TYP_STRUCT); - op2 = gtNewOperNode(GT_ADDR, TYP_BYREF, op2); - op1 = gtNewOperNode(GT_COMMA, TYP_BYREF, op1, op2); - - // In this case the return value of the unbox helper is TYP_BYREF. - // Make sure the right type is placed on the operand type stack. - impPushOnStack(op1, tiRetVal); + // Unbox nullable helper returns a TYP_STRUCT. + // For the multi-reg case we need to spill it to a temp so that + // we can pass the address to the unbox_nullable jit helper. - // Load the struct. - oper = GT_OBJ; + unsigned tmp = lvaGrabTemp(true DEBUGARG("UNBOXing a register returnable nullable")); + lvaTable[tmp].lvIsMultiRegArg = true; + lvaSetStruct(tmp, resolvedToken.hClass, true /* unsafe value cls check */); - assert(op1->gtType == TYP_BYREF); - assert(!tiVerificationNeeded || tiRetVal.IsByRef()); + op2 = gtNewLclvNode(tmp, TYP_STRUCT); + op1 = impAssignStruct(op2, op1, resolvedToken.hClass, (unsigned)CHECK_SPILL_ALL); + assert(op1->gtType == TYP_VOID); // We must be assigning the return struct to the temp. - goto OBJ; - } - else - { - // If non register passable struct we have it materialized in the RetBuf. - assert(op1->gtType == TYP_STRUCT); - tiRetVal = verMakeTypeInfo(resolvedToken.hClass); - assert(tiRetVal.IsValueClass()); - } - } -#else // !defined(FEATURE_UNIX_AMD64_STRUCT_PASSING) - assert(op1->gtType == TYP_STRUCT); - tiRetVal = verMakeTypeInfo(resolvedToken.hClass); - assert(tiRetVal.IsValueClass()); -#endif // !defined(FEATURE_UNIX_AMD64_STRUCT_PASSING) + op2 = gtNewLclvNode(tmp, TYP_STRUCT); + op2 = gtNewOperNode(GT_ADDR, TYP_BYREF, op2); + op1 = gtNewOperNode(GT_COMMA, TYP_BYREF, op1, op2); + + // In this case the return value of the unbox helper is TYP_BYREF. + // Make sure the right type is placed on the operand type stack. + impPushOnStack(op1, tiRetVal); + + // Load the struct. + oper = GT_OBJ; + + assert(op1->gtType == TYP_BYREF); + assert(!tiVerificationNeeded || tiRetVal.IsByRef()); + + goto OBJ; + } + else + +#endif // !FEATURE_MULTIREG_RET + + { + // If non register passable struct we have it materialized in the RetBuf. + assert(op1->gtType == TYP_STRUCT); + tiRetVal = verMakeTypeInfo(resolvedToken.hClass); + assert(tiRetVal.IsValueClass()); + } } impPushOnStack(op1, tiRetVal); @@ -14000,15 +14022,10 @@ GenTreePtr Compiler::impAssignMultiRegTypeToVar(GenTreePtr op, CORINFO_CLASS_HAN unsigned tmpNum = lvaGrabTemp(true DEBUGARG("Return value temp for multireg return.")); impAssignTempGen(tmpNum, op, hClass, (unsigned)CHECK_SPILL_NONE); GenTreePtr ret = gtNewLclvNode(tmpNum, op->gtType); - -#ifdef FEATURE_UNIX_AMD64_STRUCT_PASSING - // If single eightbyte, the return type would have been normalized and there won't be a temp var. - // This code will be called only if the struct return has not been normalized (i.e. 2 eightbytes - max allowed.) assert(IsMultiRegReturnedType(hClass)); // Mark the var so that fields are not promoted and stay together. lvaTable[tmpNum].lvIsMultiRegRet = true; -#endif // defined(FEATURE_UNIX_AMD64_STRUCT_PASSING) return ret; } @@ -14253,7 +14270,7 @@ bool Compiler::impReturnInstruction(BasicBlock *block, int prefixFlags, OPCODE & // Same as !IsHfa but just don't bother with impAssignStructPtr. #else // defined(FEATURE_UNIX_AMD64_STRUCT_PASSING) ReturnTypeDesc retTypeDesc; - retTypeDesc.InitializeReturnType(this, retClsHnd); + retTypeDesc.InitializeStructReturnType(this, retClsHnd); unsigned retRegCount = retTypeDesc.GetReturnRegCount(); if (retRegCount != 0) @@ -14283,13 +14300,20 @@ bool Compiler::impReturnInstruction(BasicBlock *block, int prefixFlags, OPCODE & } else #elif defined(_TARGET_ARM64_) - if (!iciCall->AsCall()->HasRetBufArg()) + ReturnTypeDesc retTypeDesc; + retTypeDesc.InitializeStructReturnType(this, retClsHnd); + unsigned retRegCount = retTypeDesc.GetReturnRegCount(); + + if (retRegCount != 0) { + assert(!iciCall->AsCall()->HasRetBufArg()); + assert(retRegCount >= 2); if (lvaInlineeReturnSpillTemp != BAD_VAR_NUM) { if (!impInlineInfo->retExpr) { - impInlineInfo->retExpr = gtNewLclvNode(lvaInlineeReturnSpillTemp, TYP_STRUCT); + // The inlinee compiler has figured out the type of the temp already. Use it here. + impInlineInfo->retExpr = gtNewLclvNode(lvaInlineeReturnSpillTemp, lvaTable[lvaInlineeReturnSpillTemp].lvType); } } else diff --git a/src/jit/instr.cpp b/src/jit/instr.cpp index 4fb23f662e..843b4022ba 100644 --- a/src/jit/instr.cpp +++ b/src/jit/instr.cpp @@ -1204,10 +1204,10 @@ void CodeGen::sched_AM(instruction ins, * Emit a "call [r/m]" instruction (the r/m operand given by a tree). */ -void CodeGen::instEmit_indCall(GenTreePtr call, - size_t argSize, - emitAttr retSize - FEATURE_UNIX_AMD64_STRUCT_PASSING_ONLY_ARG(emitAttr secondRetSize)) +void CodeGen::instEmit_indCall(GenTreePtr call, + size_t argSize, + emitAttr retSize + MULTIREG_HAS_SECOND_GC_RET_ONLY_ARG(emitAttr secondRetSize)) { GenTreePtr addr; @@ -1250,7 +1250,7 @@ void CodeGen::instEmit_indCall(GenTreePtr (void*) funcPtr, argSize, retSize - FEATURE_UNIX_AMD64_STRUCT_PASSING_ONLY_ARG(secondRetSize), + MULTIREG_HAS_SECOND_GC_RET_ONLY_ARG(secondRetSize), gcInfo.gcVarPtrSetCur, gcInfo.gcRegGCrefSetCur, gcInfo.gcRegByrefSetCur); @@ -1312,7 +1312,7 @@ void CodeGen::instEmit_indCall(GenTreePtr (void*) funcPtr, argSize, retSize - FEATURE_UNIX_AMD64_STRUCT_PASSING_ONLY_ARG(secondRetSize), + MULTIREG_HAS_SECOND_GC_RET_ONLY_ARG(secondRetSize), gcInfo.gcVarPtrSetCur, gcInfo.gcRegGCrefSetCur, gcInfo.gcRegByrefSetCur); @@ -1377,7 +1377,7 @@ void CodeGen::instEmit_indCall(GenTreePtr NULL, // addr argSize, retSize - FEATURE_UNIX_AMD64_STRUCT_PASSING_ONLY_ARG(secondRetSize), + MULTIREG_HAS_SECOND_GC_RET_ONLY_ARG(secondRetSize), gcInfo.gcVarPtrSetCur, gcInfo.gcRegGCrefSetCur, gcInfo.gcRegByrefSetCur, diff --git a/src/jit/jit.h b/src/jit/jit.h index d30336b936..fb5d0144af 100644 --- a/src/jit/jit.h +++ b/src/jit/jit.h @@ -256,6 +256,18 @@ struct CLRConfig #define UNIX_AMD64_ABI_ONLY(x) #endif // defined(UNIX_AMD64_ABI) +#if defined(UNIX_AMD64_ABI) || defined(_TARGET_ARM64_) +#define MULTIREG_HAS_SECOND_GC_RET 1 +#define MULTIREG_HAS_SECOND_GC_RET_ONLY_ARG(x) , x +#define MULTIREG_HAS_SECOND_GC_RET_ONLY(x) x +#else // !defined(FEATURE_UNIX_AMD64_STRUCT_PASSING) +#define MULTIREG_HAS_SECOND_GC_RET 0 +#define MULTIREG_HAS_SECOND_GC_RET_ONLY_ARG(x) +#define MULTIREG_HAS_SECOND_GC_RET_ONLY(x) +#endif // defined(FEATURE_UNIX_AMD64_STRUCT_PASSING) + + + // To get rid of warning 4701 : local variable may be used without being initialized #define DUMMY_INIT(x) (x) diff --git a/src/jit/lclvars.cpp b/src/jit/lclvars.cpp index e0c4f75599..f0015b2e8f 100644 --- a/src/jit/lclvars.cpp +++ b/src/jit/lclvars.cpp @@ -131,46 +131,28 @@ void Compiler::lvaInitTypeRef() // const bool hasRetBuffArg = impMethodInfo_hasRetBuffArg(info.compMethodInfo); - // Change the compRetNativeType if we are returning a struct by value in a register + // Possibly change the compRetNativeType from TYP_STRUCT to a "primitive" type + // when we are returning a struct by value and it fits in one register + // if (!hasRetBuffArg && varTypeIsStruct(info.compRetNativeType)) { -#if FEATURE_MULTIREG_RET && defined(FEATURE_HFA) - if (!info.compIsVarArgs && IsHfa(info.compMethodInfo->args.retTypeClass)) - { - info.compRetNativeType = TYP_STRUCT; - } - else -#endif // FEATURE_MULTIREG_RET && defined(FEATURE_HFA) + CORINFO_CLASS_HANDLE retClsHnd = info.compMethodInfo->args.retTypeClass; + + Compiler::structPassingKind howToReturnStruct; + var_types returnType = getReturnTypeForStruct(retClsHnd, &howToReturnStruct); + + if (howToReturnStruct == SPK_PrimitiveType) { -#ifdef FEATURE_UNIX_AMD64_STRUCT_PASSING - ReturnTypeDesc retTypeDesc; - retTypeDesc.InitializeReturnType(this, info.compMethodInfo->args.retTypeClass); + assert(returnType != TYP_UNKNOWN); + assert(returnType != TYP_STRUCT); - if (retTypeDesc.GetReturnRegCount() > 1) - { - info.compRetNativeType = TYP_STRUCT; - } - else - { - info.compRetNativeType = retTypeDesc.GetReturnRegType(0); - } -#else // !FEATURE_UNIX_AMD64_STRUCT_PASSING - // Check for TYP_STRUCT argument that can fit into a single register - structPassingKind howToReturnStruct; - var_types returnType = getReturnTypeForStruct(info.compMethodInfo->args.retTypeClass, &howToReturnStruct); - assert(howToReturnStruct != SPK_ByReference); // hasRetBuffArg is false, so we can't have this answer here info.compRetNativeType = returnType; - if (returnType == TYP_UNKNOWN) - { - assert(!"Unexpected size when returning struct by value"); - } // ToDo: Refactor this common code sequence into its own method as it is used 4+ times if ((returnType == TYP_LONG) && (compLongUsed == false)) compLongUsed = true; else if (((returnType == TYP_FLOAT) || (returnType == TYP_DOUBLE)) && (compFloatingPointUsed == false)) compFloatingPointUsed = true; -#endif // !FEATURE_UNIX_AMD64_STRUCT_PASSING } } @@ -548,8 +530,8 @@ void Compiler::lvaInitUserArgs(InitVarDscInfo * varDscInfo) CORINFO_CLASS_HANDLE typeHnd = NULL; CorInfoTypeWithMod corInfoType = info.compCompHnd->getArgType(&info.compMethodInfo->args, - argLst, - &typeHnd); + argLst, + &typeHnd); varDsc->lvIsParam = 1; #if ASSERTION_PROP varDsc->lvSingleDef = 1; @@ -917,13 +899,23 @@ void Compiler::lvaInitUserArgs(InitVarDscInfo * varDscInfo) } // end if (canPassArgInRegisters) else { -#ifdef _TARGET_ARM_ +#if defined(_TARGET_ARM_) + varDscInfo->setAllRegArgUsed(argType); if (varTypeIsFloating(argType)) { varDscInfo->setAnyFloatStackArgs(); } -#endif + +#elif defined(_TARGET_ARM64_) + + // If we needed to use the stack in order to pass this argument then + // record the fact that we have used up any remaining registers of this 'type' + // This prevents any 'backfilling' from occuring on ARM64 + // + varDscInfo->setAllRegArgUsed(argType); + +#endif // _TARGET_XXX_ } #ifdef FEATURE_UNIX_AMD64_STRUCT_PASSING @@ -1944,6 +1936,37 @@ void Compiler::lvaSetVarDoNotEnregister(unsigned varNum DEBUGARG(D #endif } +// Returns true if this local var is a multireg struct +bool Compiler::lvaIsMultiregStruct(LclVarDsc* varDsc) +{ + if (varDsc->TypeGet() == TYP_STRUCT) + { + CORINFO_CLASS_HANDLE clsHnd = varDsc->lvVerTypeInfo.GetClassHandleForValueClass(); + structPassingKind howToPassStruct; + + var_types type = getArgTypeForStruct(clsHnd, + &howToPassStruct, + varDsc->lvExactSize); + + if (howToPassStruct == SPK_ByValueAsHfa) + { + assert(type = TYP_STRUCT); + return true; + } + +#if defined(FEATURE_UNIX_AMD64_STRUCT_PASSING) || defined(_TARGET_ARM64_) + if (howToPassStruct == SPK_ByValue) + { + assert(type = TYP_STRUCT); + return true; + } +#endif + + } + return false; +} + + /***************************************************************************** * Set the lvClass for a local variable of a struct type */ @@ -5952,7 +5975,7 @@ void Compiler::lvaDumpEntry(unsigned lclNum, FrameLayoutState curState, size_t } else if (varDsc->lvOnFrame == 0) { - printf("multi-reg "); + printf("registers "); } else { @@ -5985,12 +6008,16 @@ void Compiler::lvaDumpEntry(unsigned lclNum, FrameLayoutState curState, size_t if (varDsc->lvLclFieldExpr) printf("F"); if (varDsc->lvLclBlockOpAddr) printf("B"); if (varDsc->lvLiveAcrossUCall) printf("U"); + if (varDsc->lvIsMultiRegArg) printf("A"); + if (varDsc->lvIsMultiRegRet) printf("R"); #ifdef JIT32_GCENCODER if (varDsc->lvPinned) printf("P"); #endif // JIT32_GCENCODER printf("]"); } + if (varDsc->lvIsMultiRegArg) printf(" multireg-arg"); + if (varDsc->lvIsMultiRegRet) printf(" multireg-ret"); if (varDsc->lvMustInit) printf(" must-init"); if (varDsc->lvAddrExposed) printf(" addr-exposed"); if (varDsc->lvHasLdAddrOp) printf(" ld-addr-op"); diff --git a/src/jit/lowerarm64.cpp b/src/jit/lowerarm64.cpp index 6cd1cb383b..faa5925027 100644 --- a/src/jit/lowerarm64.cpp +++ b/src/jit/lowerarm64.cpp @@ -34,15 +34,32 @@ void Lowering::LowerStoreLoc(GenTreeLclVarCommon* storeLoc) { TreeNodeInfo* info = &(storeLoc->gtLsraInfo); - CheckImmedAndMakeContained(storeLoc, storeLoc->gtOp1); + // Is this the case of var = call where call is returning + // a value in multiple return registers? + GenTree* op1 = storeLoc->gtGetOp1(); + if (op1->IsMultiRegCall()) + { + // backend expects to see this case only for store lclvar. + assert(storeLoc->OperGet() == GT_STORE_LCL_VAR); + + // srcCount = number of registers in which the value is returned by call + GenTreeCall* call = op1->AsCall(); + ReturnTypeDesc* retTypeDesc = call->GetReturnTypeDesc(); + info->srcCount = retTypeDesc->GetReturnRegCount(); + + // Call node srcCandidates = Bitwise-OR(allregs(GetReturnRegType(i))) for all i=0..RetRegCount-1 + regMaskTP srcCandidates = m_lsra->allMultiRegCallNodeRegs(call); + op1->gtLsraInfo.setSrcCandidates(m_lsra, srcCandidates); + return; + } + + CheckImmedAndMakeContained(storeLoc, op1); // Try to widen the ops if they are going into a local var. - if ((storeLoc->gtOper == GT_STORE_LCL_VAR) && - (storeLoc->gtOp1->gtOper == GT_CNS_INT)) + if ((storeLoc->gtOper == GT_STORE_LCL_VAR) && (op1->gtOper == GT_CNS_INT)) { - GenTreeIntCon* con = storeLoc->gtOp1->AsIntCon(); - ssize_t ival = con->gtIconVal; - + GenTreeIntCon* con = op1->AsIntCon(); + ssize_t ival = con->gtIconVal; unsigned varNum = storeLoc->gtLclNum; LclVarDsc* varDsc = comp->lvaTable + varNum; @@ -225,22 +242,7 @@ void Lowering::TreeNodeInfoInit(GenTree* stmt) break; case GT_RETURN: - info->srcCount = (tree->TypeGet() == TYP_VOID) ? 0 : 1; - info->dstCount = 0; - - regMaskTP useCandidates; - switch (tree->TypeGet()) - { - case TYP_VOID: useCandidates = RBM_NONE; break; - case TYP_FLOAT: useCandidates = RBM_FLOATRET; break; - case TYP_DOUBLE: useCandidates = RBM_DOUBLERET; break; - case TYP_LONG: useCandidates = RBM_LNGRET; break; - default: useCandidates = RBM_INTRET; break; - } - if (useCandidates != RBM_NONE) - { - tree->gtOp.gtOp1->gtLsraInfo.setSrcCandidates(l, useCandidates); - } + TreeNodeInfoInitReturn(tree); break; case GT_RETFILT: @@ -511,241 +513,7 @@ void Lowering::TreeNodeInfoInit(GenTree* stmt) break; case GT_CALL: - { - info->srcCount = 0; - info->dstCount = (tree->TypeGet() != TYP_VOID) ? 1 : 0; - - GenTree *ctrlExpr = tree->gtCall.gtControlExpr; - if (tree->gtCall.gtCallType == CT_INDIRECT) - { - // either gtControlExpr != null or gtCallAddr != null. - // Both cannot be non-null at the same time. - assert(ctrlExpr == nullptr); - assert(tree->gtCall.gtCallAddr != nullptr); - ctrlExpr = tree->gtCall.gtCallAddr; - } - - // set reg requirements on call target represented as control sequence. - if (ctrlExpr != nullptr) - { - // we should never see a gtControlExpr whose type is void. - assert(ctrlExpr->TypeGet() != TYP_VOID); - - info->srcCount++; - - // In case of fast tail implemented as jmp, make sure that gtControlExpr is - // computed into a register. - if (tree->gtCall.IsFastTailCall()) - { - // Fast tail call - make sure that call target is always computed in IP0 - // so that epilog sequence can generate "br xip0" to achieve fast tail call. - ctrlExpr->gtLsraInfo.setSrcCandidates(l, genRegMask(REG_IP0)); - } - } - - // Set destination candidates for return value of the call. - if (varTypeIsFloating(registerType)) - { - info->setDstCandidates(l, RBM_FLOATRET); - } - else if (registerType == TYP_LONG) - { - info->setDstCandidates(l, RBM_LNGRET); - } - else - { - info->setDstCandidates(l, RBM_INTRET); - } - - // If there is an explicit this pointer, we don't want that node to produce anything - // as it is redundant - if (tree->gtCall.gtCallObjp != nullptr) - { - GenTreePtr thisPtrNode = tree->gtCall.gtCallObjp; - - if (thisPtrNode->gtOper == GT_PUTARG_REG) - { - l->clearOperandCounts(thisPtrNode); - l->clearDstCount(thisPtrNode->gtOp.gtOp1); - } - else - { - l->clearDstCount(thisPtrNode); - } - } - - // First, count reg args - bool callHasFloatRegArgs = false; - - for (GenTreePtr list = tree->gtCall.gtCallLateArgs; list; list = list->MoveNext()) - { - assert(list->IsList()); - - GenTreePtr argNode = list->Current(); - - fgArgTabEntryPtr curArgTabEntry = compiler->gtArgEntryByNode(tree, argNode); - assert(curArgTabEntry); - - if (curArgTabEntry->regNum == REG_STK) - { - // late arg that is not passed in a register - assert(argNode->gtOper == GT_PUTARG_STK); - - TreeNodeInfoInitPutArgStk(argNode, curArgTabEntry); - continue; - } - - var_types argType = argNode->TypeGet(); - bool argIsFloat = varTypeIsFloating(argType); - callHasFloatRegArgs |= argIsFloat; - - regNumber argReg = curArgTabEntry->regNum; - // We will setup argMask to the set of all registers that compose this argument - regMaskTP argMask = 0; - - argNode = argNode->gtEffectiveVal(); - - // A GT_LIST has a TYP_VOID, but is used to represent a multireg struct - if (varTypeIsStruct(argNode) || (argNode->gtOper == GT_LIST)) - { - GenTreePtr actualArgNode = argNode; - unsigned originalSize = 0; - - if (argNode->gtOper == GT_LIST) - { - // There could be up to 2-4 PUTARG_REGs in the list (3 or 4 can only occur for HFAs) - GenTreeArgList* argListPtr = argNode->AsArgList(); - - // Initailize the first register and the first regmask in our list - regNumber targetReg = argReg; - regMaskTP targetMask = genRegMask(targetReg); - unsigned iterationNum = 0; - originalSize = 0; - - for (; argListPtr; argListPtr = argListPtr->Rest()) - { - GenTreePtr putArgRegNode = argListPtr->gtOp.gtOp1; - assert(putArgRegNode->gtOper == GT_PUTARG_REG); - GenTreePtr putArgChild = putArgRegNode->gtOp.gtOp1; - - originalSize += REGSIZE_BYTES; // 8 bytes - - // Record the register requirements for the GT_PUTARG_REG node - putArgRegNode->gtLsraInfo.setDstCandidates(l, targetMask); - putArgRegNode->gtLsraInfo.setSrcCandidates(l, targetMask); - - // To avoid redundant moves, request that the argument child tree be - // computed in the register in which the argument is passed to the call. - putArgChild->gtLsraInfo.setSrcCandidates(l, targetMask); - - // We consume one source for each item in this list - info->srcCount++; - iterationNum++; - - // Update targetReg and targetMask for the next putarg_reg (if any) - targetReg = REG_NEXT(targetReg); - targetMask = genRegMask(targetReg); - } - } - else - { -#ifdef DEBUG - compiler->gtDispTree(argNode); -#endif - noway_assert(!"Unsupported TYP_STRUCT arg kind"); - } - - unsigned slots = ((unsigned)(roundUp(originalSize, REGSIZE_BYTES))) / REGSIZE_BYTES; - regNumber curReg = argReg; - regNumber lastReg = argIsFloat ? REG_ARG_FP_LAST : REG_ARG_LAST; - unsigned remainingSlots = slots; - - while (remainingSlots > 0) - { - argMask |= genRegMask(curReg); - remainingSlots--; - - if (curReg == lastReg) - break; - - curReg = REG_NEXT(curReg); - } - - // Struct typed arguments must be fully passed in registers (Reg/Stk split not allowed) - noway_assert(remainingSlots == 0); - argNode->gtLsraInfo.internalIntCount = 0; - } - else // A scalar argument (not a struct) - { - // We consume one source - info->srcCount++; - - argMask |= genRegMask(argReg); - argNode->gtLsraInfo.setDstCandidates(l, argMask); - argNode->gtLsraInfo.setSrcCandidates(l, argMask); - - if (argNode->gtOper == GT_PUTARG_REG) - { - GenTreePtr putArgChild = argNode->gtOp.gtOp1; - - // To avoid redundant moves, request that the argument child tree be - // computed in the register in which the argument is passed to the call. - putArgChild ->gtLsraInfo.setSrcCandidates(l, argMask); - } - } - } - - // Now, count stack args - // Note that these need to be computed into a register, but then - // they're just stored to the stack - so the reg doesn't - // need to remain live until the call. In fact, it must not - // because the code generator doesn't actually consider it live, - // so it can't be spilled. - - GenTreePtr args = tree->gtCall.gtCallArgs; - while (args) - { - GenTreePtr arg = args->gtOp.gtOp1; - - // Skip arguments that have been moved to the Late Arg list - if (!(args->gtFlags & GTF_LATE_ARG)) - { - if (arg->gtOper == GT_PUTARG_STK) - { - fgArgTabEntryPtr curArgTabEntry = compiler->gtArgEntryByNode(tree, arg); - assert(curArgTabEntry); - - assert(curArgTabEntry->regNum == REG_STK); - - TreeNodeInfoInitPutArgStk(arg, curArgTabEntry); - } - else - { - TreeNodeInfo* argInfo = &(arg->gtLsraInfo); - if (argInfo->dstCount != 0) - { - argInfo->isLocalDefUse = true; - } - - argInfo->dstCount = 0; - } - } - args = args->gtOp.gtOp2; - } - - // If it is a fast tail call, it is already preferenced to use IP0. - // Therefore, no need set src candidates on call tgt again. - if (tree->gtCall.IsVarargs() && - callHasFloatRegArgs && - !tree->gtCall.IsFastTailCall() && - (ctrlExpr != nullptr)) - { - // Don't assign the call target to any of the argument registers because - // we will use them to also pass floating point arguments as required - // by Arm64 ABI. - ctrlExpr->gtLsraInfo.setSrcCandidates(l, l->allRegs(TYP_INT) & ~(RBM_ARG_REGS)); - } - } + TreeNodeInfoInitCall(tree->AsCall()); break; case GT_ADDR: @@ -1018,11 +786,352 @@ void Lowering::TreeNodeInfoInit(GenTree* stmt) info->internalIntCount = 1; break; } // end switch (tree->OperGet()) - - tree = next; // We need to be sure that we've set info->srcCount and info->dstCount appropriately - assert(info->dstCount < 2); + assert((info->dstCount < 2) || tree->IsMultiRegCall()); + + tree = next; + } +} +//------------------------------------------------------------------------ +// TreeNodeInfoInitReturn: Set the NodeInfo for a GT_RETURN. +// +// Arguments: +// tree - The node of interest +// +// Return Value: +// None. +// +void +Lowering::TreeNodeInfoInitReturn(GenTree* tree) +{ + TreeNodeInfo* info = &(tree->gtLsraInfo); + LinearScan* l = m_lsra; + Compiler* compiler = comp; + + GenTree* op1 = tree->gtGetOp1(); + regMaskTP useCandidates = RBM_NONE; + + info->srcCount = (tree->TypeGet() == TYP_VOID) ? 0 : 1; + info->dstCount = 0; + + if (varTypeIsStruct(tree)) + { + // op1 has to be either an lclvar or a multi-reg returning call + if ((op1->OperGet() == GT_LCL_VAR) || (op1->OperGet() == GT_LCL_FLD)) + { + GenTreeLclVarCommon* lclVarCommon = op1->AsLclVarCommon(); + LclVarDsc* varDsc = &(compiler->lvaTable[lclVarCommon->gtLclNum]); + assert(varDsc->lvIsMultiRegRet); + + // Mark var as contained if not enregistrable. + if (!varTypeIsEnregisterableStruct(op1)) + { + MakeSrcContained(tree, op1); + } + } + else + { + noway_assert(op1->IsMultiRegCall()); + + ReturnTypeDesc* retTypeDesc = op1->AsCall()->GetReturnTypeDesc(); + info->srcCount = retTypeDesc->GetReturnRegCount(); + useCandidates = retTypeDesc->GetABIReturnRegs(); + } + } + else + { + // Non-struct type return - determine useCandidates + switch (tree->TypeGet()) + { + case TYP_VOID: useCandidates = RBM_NONE; break; + case TYP_FLOAT: useCandidates = RBM_FLOATRET; break; + case TYP_DOUBLE: useCandidates = RBM_DOUBLERET; break; + case TYP_LONG: useCandidates = RBM_LNGRET; break; + default: useCandidates = RBM_INTRET; break; + } + } + + if (useCandidates != RBM_NONE) + { + tree->gtOp.gtOp1->gtLsraInfo.setSrcCandidates(l, useCandidates); + } +} + +//------------------------------------------------------------------------ +// TreeNodeInfoInitCall: Set the NodeInfo for a call. +// +// Arguments: +// call - The call node of interest +// +// Return Value: +// None. +// +void +Lowering::TreeNodeInfoInitCall(GenTreeCall* call) +{ + TreeNodeInfo* info = &(call->gtLsraInfo); + LinearScan* l = m_lsra; + Compiler* compiler = comp; + bool hasMultiRegRetVal = false; + ReturnTypeDesc* retTypeDesc = nullptr; + + info->srcCount = 0; + if (call->TypeGet() != TYP_VOID) + { + hasMultiRegRetVal = call->HasMultiRegRetVal(); + if (hasMultiRegRetVal) + { + // dst count = number of registers in which the value is returned by call + retTypeDesc = call->GetReturnTypeDesc(); + info->dstCount = retTypeDesc->GetReturnRegCount(); + } + else + { + info->dstCount = 1; + } + } + else + { + info->dstCount = 0; + } + + GenTree* ctrlExpr = call->gtControlExpr; + if (call->gtCallType == CT_INDIRECT) + { + // either gtControlExpr != null or gtCallAddr != null. + // Both cannot be non-null at the same time. + assert(ctrlExpr == nullptr); + assert(call->gtCallAddr != nullptr); + ctrlExpr = call->gtCallAddr; + } + + // set reg requirements on call target represented as control sequence. + if (ctrlExpr != nullptr) + { + // we should never see a gtControlExpr whose type is void. + assert(ctrlExpr->TypeGet() != TYP_VOID); + + info->srcCount++; + + // In case of fast tail implemented as jmp, make sure that gtControlExpr is + // computed into a register. + if (call->IsFastTailCall()) + { + // Fast tail call - make sure that call target is always computed in IP0 + // so that epilog sequence can generate "br xip0" to achieve fast tail call. + ctrlExpr->gtLsraInfo.setSrcCandidates(l, genRegMask(REG_IP0)); + } + } + + RegisterType registerType = call->TypeGet(); + + // Set destination candidates for return value of the call. + if (hasMultiRegRetVal) + { + assert(retTypeDesc != nullptr); + info->setDstCandidates(l, retTypeDesc->GetABIReturnRegs()); + } + else if (varTypeIsFloating(registerType)) + { + info->setDstCandidates(l, RBM_FLOATRET); + } + else if (registerType == TYP_LONG) + { + info->setDstCandidates(l, RBM_LNGRET); + } + else + { + info->setDstCandidates(l, RBM_INTRET); + } + + // If there is an explicit this pointer, we don't want that node to produce anything + // as it is redundant + if (call->gtCallObjp != nullptr) + { + GenTreePtr thisPtrNode = call->gtCallObjp; + + if (thisPtrNode->gtOper == GT_PUTARG_REG) + { + l->clearOperandCounts(thisPtrNode); + l->clearDstCount(thisPtrNode->gtOp.gtOp1); + } + else + { + l->clearDstCount(thisPtrNode); + } + } + + // First, count reg args + bool callHasFloatRegArgs = false; + + for (GenTreePtr list = call->gtCallLateArgs; list; list = list->MoveNext()) + { + assert(list->IsList()); + + GenTreePtr argNode = list->Current(); + + fgArgTabEntryPtr curArgTabEntry = compiler->gtArgEntryByNode(call, argNode); + assert(curArgTabEntry); + + if (curArgTabEntry->regNum == REG_STK) + { + // late arg that is not passed in a register + assert(argNode->gtOper == GT_PUTARG_STK); + + TreeNodeInfoInitPutArgStk(argNode, curArgTabEntry); + continue; + } + + var_types argType = argNode->TypeGet(); + bool argIsFloat = varTypeIsFloating(argType); + callHasFloatRegArgs |= argIsFloat; + + regNumber argReg = curArgTabEntry->regNum; + // We will setup argMask to the set of all registers that compose this argument + regMaskTP argMask = 0; + + argNode = argNode->gtEffectiveVal(); + + // A GT_LIST has a TYP_VOID, but is used to represent a multireg struct + if (varTypeIsStruct(argNode) || (argNode->gtOper == GT_LIST)) + { + GenTreePtr actualArgNode = argNode; + unsigned originalSize = 0; + + if (argNode->gtOper == GT_LIST) + { + // There could be up to 2-4 PUTARG_REGs in the list (3 or 4 can only occur for HFAs) + GenTreeArgList* argListPtr = argNode->AsArgList(); + + // Initailize the first register and the first regmask in our list + regNumber targetReg = argReg; + regMaskTP targetMask = genRegMask(targetReg); + unsigned iterationNum = 0; + originalSize = 0; + + for (; argListPtr; argListPtr = argListPtr->Rest()) + { + GenTreePtr putArgRegNode = argListPtr->gtOp.gtOp1; + assert(putArgRegNode->gtOper == GT_PUTARG_REG); + GenTreePtr putArgChild = putArgRegNode->gtOp.gtOp1; + + originalSize += REGSIZE_BYTES; // 8 bytes + + // Record the register requirements for the GT_PUTARG_REG node + putArgRegNode->gtLsraInfo.setDstCandidates(l, targetMask); + putArgRegNode->gtLsraInfo.setSrcCandidates(l, targetMask); + + // To avoid redundant moves, request that the argument child tree be + // computed in the register in which the argument is passed to the call. + putArgChild->gtLsraInfo.setSrcCandidates(l, targetMask); + + // We consume one source for each item in this list + info->srcCount++; + iterationNum++; + + // Update targetReg and targetMask for the next putarg_reg (if any) + targetReg = REG_NEXT(targetReg); + targetMask = genRegMask(targetReg); + } + } + else + { +#ifdef DEBUG + compiler->gtDispTree(argNode); +#endif + noway_assert(!"Unsupported TYP_STRUCT arg kind"); + } + + unsigned slots = ((unsigned)(roundUp(originalSize, REGSIZE_BYTES))) / REGSIZE_BYTES; + regNumber curReg = argReg; + regNumber lastReg = argIsFloat ? REG_ARG_FP_LAST : REG_ARG_LAST; + unsigned remainingSlots = slots; + + while (remainingSlots > 0) + { + argMask |= genRegMask(curReg); + remainingSlots--; + + if (curReg == lastReg) + break; + + curReg = REG_NEXT(curReg); + } + + // Struct typed arguments must be fully passed in registers (Reg/Stk split not allowed) + noway_assert(remainingSlots == 0); + argNode->gtLsraInfo.internalIntCount = 0; + } + else // A scalar argument (not a struct) + { + // We consume one source + info->srcCount++; + + argMask |= genRegMask(argReg); + argNode->gtLsraInfo.setDstCandidates(l, argMask); + argNode->gtLsraInfo.setSrcCandidates(l, argMask); + + if (argNode->gtOper == GT_PUTARG_REG) + { + GenTreePtr putArgChild = argNode->gtOp.gtOp1; + + // To avoid redundant moves, request that the argument child tree be + // computed in the register in which the argument is passed to the call. + putArgChild->gtLsraInfo.setSrcCandidates(l, argMask); + } + } + } + + // Now, count stack args + // Note that these need to be computed into a register, but then + // they're just stored to the stack - so the reg doesn't + // need to remain live until the call. In fact, it must not + // because the code generator doesn't actually consider it live, + // so it can't be spilled. + + GenTreePtr args = call->gtCallArgs; + while (args) + { + GenTreePtr arg = args->gtOp.gtOp1; + + // Skip arguments that have been moved to the Late Arg list + if (!(args->gtFlags & GTF_LATE_ARG)) + { + if (arg->gtOper == GT_PUTARG_STK) + { + fgArgTabEntryPtr curArgTabEntry = compiler->gtArgEntryByNode(call, arg); + assert(curArgTabEntry); + + assert(curArgTabEntry->regNum == REG_STK); + + TreeNodeInfoInitPutArgStk(arg, curArgTabEntry); + } + else + { + TreeNodeInfo* argInfo = &(arg->gtLsraInfo); + if (argInfo->dstCount != 0) + { + argInfo->isLocalDefUse = true; + } + + argInfo->dstCount = 0; + } + } + args = args->gtOp.gtOp2; + } + + // If it is a fast tail call, it is already preferenced to use IP0. + // Therefore, no need set src candidates on call tgt again. + if (call->IsVarargs() && + callHasFloatRegArgs && + !call->IsFastTailCall() && + (ctrlExpr != nullptr)) + { + // Don't assign the call target to any of the argument registers because + // we will use them to also pass floating point arguments as required + // by Arm64 ABI. + ctrlExpr->gtLsraInfo.setSrcCandidates(l, l->allRegs(TYP_INT) & ~(RBM_ARG_REGS)); } } @@ -1542,6 +1651,7 @@ void Lowering::LowerGCWriteBarrier(GenTree* tree) void Lowering::SetIndirAddrOpCounts(GenTreePtr indirTree) { assert(indirTree->OperIsIndir()); + assert(indirTree->TypeGet() != TYP_STRUCT); GenTreePtr addr = indirTree->gtGetOp1(); TreeNodeInfo* info = &(indirTree->gtLsraInfo); diff --git a/src/jit/morph.cpp b/src/jit/morph.cpp index f5c8d083ab..c8835ead82 100755 --- a/src/jit/morph.cpp +++ b/src/jit/morph.cpp @@ -89,7 +89,7 @@ GenTreePtr Compiler::fgMorphIntoHelperCall(GenTreePtr tree, GenTreeCall* callNode = tree->AsCall(); ReturnTypeDesc* retTypeDesc = callNode->GetReturnTypeDesc(); retTypeDesc->Reset(); - retTypeDesc->InitializeReturnType(this, callNode->gtRetClsHnd); + retTypeDesc->InitializeLongReturnType(this); callNode->ClearOtherRegs(); NYI("Helper with TYP_LONG return type"); @@ -1508,11 +1508,11 @@ void fgArgInfo::ArgsComplete() // so we skip this for ARM32 until it is ported to use RyuJIT backend // #if FEATURE_MULTIREG_ARGS - if ((argx->TypeGet() == TYP_STRUCT) && - (curArgTabEntry->numRegs > 1) && - (curArgTabEntry->needTmp == false)) + bool isMultiRegArg = (curArgTabEntry->numRegs > 1); + + if ((argx->TypeGet() == TYP_STRUCT) && (curArgTabEntry->needTmp == false)) { - if ((argx->gtFlags & GTF_PERSISTENT_SIDE_EFFECTS) != 0) + if (isMultiRegArg && ((argx->gtFlags & GTF_PERSISTENT_SIDE_EFFECTS) != 0)) { // Spill multireg struct arguments that have Assignments or Calls embedded in them curArgTabEntry->needTmp = true; @@ -1522,7 +1522,7 @@ void fgArgInfo::ArgsComplete() // We call gtPrepareCost to measure the cost of evaluating this tree compiler->gtPrepareCost(argx); - if (argx->gtCostEx > (6 * IND_COST_EX)) + if (isMultiRegArg && (argx->gtCostEx > (6 * IND_COST_EX))) { // Spill multireg struct arguments that are expensive to evaluate twice curArgTabEntry->needTmp = true; @@ -1534,6 +1534,21 @@ void fgArgInfo::ArgsComplete() unsigned structSize = compiler->info.compCompHnd->getClassSize(objClass); switch (structSize) { + case 3: + case 5: + case 6: + case 7: + // If we have a stack based LclVar we can perform a wider read of 4 or 8 bytes + // + if (argObj->gtObj.gtOp1->IsVarAddr() == false) // Is the source not a LclVar? + { + // If we don't have a LclVar we need to read exactly 3,5,6 or 7 bytes + // For now we use a a GT_CPBLK to copy the exact size into a GT_LCL_VAR temp. + // + curArgTabEntry->needTmp = true; + } + break; + case 11: case 13: case 14: @@ -2032,7 +2047,7 @@ GenTreePtr Compiler::fgMakeTmpArgNode(unsigned tmpVarNum #if FEATURE_MULTIREG_ARGS #ifdef _TARGET_ARM64_ assert(varTypeIsStruct(type)); - if (varDsc->lvIsMultiregStruct()) + if (lvaIsMultiregStruct(varDsc)) { // ToDo-ARM64: Consider using: arg->ChangeOper(GT_LCL_FLD); // as that is how FEATURE_UNIX_AMD64_STRUCT_PASSING works. @@ -2191,22 +2206,45 @@ void fgArgInfo::EvalArgsToTemps() { setupArg = compiler->gtNewTempAssign(tmpVarNum, argx); + LclVarDsc* varDsc = compiler->lvaTable + tmpVarNum; + #ifndef LEGACY_BACKEND if (compiler->fgOrder == Compiler::FGOrderLinear) { // We'll reference this temporary variable just once // when we perform the function call after // setting up this argument. - LclVarDsc* varDsc = compiler->lvaTable + tmpVarNum; varDsc->lvRefCnt = 1; } #endif // !LEGACY_BACKEND + var_types lclVarType = genActualType(argx->gtType); + var_types scalarType = TYP_UNKNOWN; + if (setupArg->OperIsCopyBlkOp()) + { setupArg = compiler->fgMorphCopyBlock(setupArg); +#ifdef _TARGET_ARM64_ + // This scalar LclVar widening step is only performed for ARM64 + // + CORINFO_CLASS_HANDLE clsHnd = compiler->lvaGetStruct(tmpVarNum); + unsigned structSize = varDsc->lvExactSize; - /* Create a copy of the temp to go to the late argument list */ - defArg = compiler->gtNewLclvNode(tmpVarNum, genActualType(argx->gtType)); + scalarType = compiler->getPrimitiveTypeForStruct(structSize, clsHnd); +#endif // _TARGET_ARM64_ + } + + // scalarType can be set to a wider type for ARM64: (3 => 4) or (5,6,7 => 8) + if ((scalarType != TYP_UNKNOWN) && (scalarType != lclVarType)) + { + // Create a GT_LCL_FLD using the wider type to go to the late argument list + defArg = compiler->gtNewLclFldNode(tmpVarNum, scalarType, 0); + } + else + { + // Create a copy of the temp to go to the late argument list + defArg = compiler->gtNewLclvNode(tmpVarNum, lclVarType); + } curArgTabEntry->isTmp = true; curArgTabEntry->tmpNum = tmpVarNum; @@ -2303,7 +2341,7 @@ void fgArgInfo::EvalArgsToTemps() { BADCODE("Unhandled struct argument tree in fgMorphArgs"); } - } + } #endif // !(defined(_TARGET_AMD64_) && !defined(FEATURE_UNIX_AMD64_STRUCT_PASSING)) @@ -2564,6 +2602,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* callNode) regMaskTP argSkippedRegMask = RBM_NONE; #if defined(_TARGET_ARM_) || defined(_TARGET_AMD64_) + // Note this in ONLY used by _TARGET_ARM_ regMaskTP fltArgSkippedRegMask = RBM_NONE; #endif @@ -2861,7 +2900,10 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* callNode) call->fgArgInfo = new (this, CMK_Unknown) fgArgInfo(this, call, numArgs); } - fgFixupStructReturn(call); + if (varTypeIsStruct(call)) + { + fgFixupStructReturn(call); + } /* First we morph the argument subtrees ('this' pointer, arguments, etc.). * During the first call to fgMorphArgs we also record the @@ -3323,7 +3365,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* callNode) } else // We must have a GT_OBJ with a struct type, but the GT_OBJ may be be a child of a GT_COMMA { - GenTreePtr argObj = argx; + GenTreePtr argObj = argx; GenTreePtr* parentOfArgObj = parentArgx; assert(args->IsList()); @@ -3333,7 +3375,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* callNode) while (argObj->gtOper == GT_COMMA) { parentOfArgObj = &argObj->gtOp.gtOp2; - argObj = argObj->gtOp.gtOp2; + argObj = argObj->gtOp.gtOp2; } if (argObj->gtOper != GT_OBJ) @@ -3346,10 +3388,26 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* callNode) unsigned originalSize = info.compCompHnd->getClassSize(objClass); originalSize = (originalSize == 0 ? TARGET_POINTER_SIZE : originalSize); - unsigned roundupSize = (unsigned)roundUp(originalSize, TARGET_POINTER_SIZE); + unsigned roundupSize = (unsigned)roundUp(originalSize, TARGET_POINTER_SIZE); structSize = originalSize; + structPassingKind howToPassStruct; + structBaseType = getArgTypeForStruct(objClass, &howToPassStruct, originalSize); + +#ifdef _TARGET_ARM64_ + if ((howToPassStruct == SPK_PrimitiveType) && // Passed in a single register + !isPow2(originalSize)) // size is 3,5,6 or 7 bytes + { + if (argObj->gtObj.gtOp1->IsVarAddr()) // Is the source a LclVar? + { + // For ARM64 we pass structs that are 3,5,6,7 bytes in size + // we can read 4 or 8 bytes from the LclVar to pass this arg + originalSize = genTypeSize(structBaseType); + } + } +#endif // _TARGET_ARM64_ + #ifdef FEATURE_UNIX_AMD64_STRUCT_PASSING // On System V OS-es a struct is never passed by reference. // It is either passed by value on the stack or in registers. @@ -3371,7 +3429,12 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* callNode) #ifndef FEATURE_UNIX_AMD64_STRUCT_PASSING // Check for struct argument with size 1, 2, 4 or 8 bytes // As we can optimize these by turning them into a GT_IND of the correct type - if ((originalSize > TARGET_POINTER_SIZE) || ((originalSize & (originalSize - 1)) != 0) || (isHfaArg && (hfaSlots != 1))) + // + // Check for cases that we cannot optimize: + // + if ((originalSize > TARGET_POINTER_SIZE) || // it is struct that is larger than a pointer + !isPow2(originalSize) || // it is not a power of two (1, 2, 4 or 8) + (isHfaArg && (hfaSlots != 1))) // it is a one element HFA struct #endif // FEATURE_UNIX_AMD64_STRUCT_PASSING { // Normalize 'size' to the number of pointer sized items @@ -3479,8 +3542,6 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* callNode) // change our GT_OBJ into a GT_IND of the correct type. // We've already ensured above that size is a power of 2, and less than or equal to pointer size. - structPassingKind howToPassStruct; - structBaseType = getArgTypeForStruct(objClass, &howToPassStruct, originalSize); assert(howToPassStruct == SPK_PrimitiveType); // ToDo: remove this block as getArgTypeForStruct properly handles turning one element HFAs into primitives @@ -3615,19 +3676,9 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* callNode) // There are a few special cases where we can omit using a CopyBlk // where we normally would need to use one. - GenTreePtr objAddr = argObj->gtObj.gtOp1; - if (objAddr->gtOper == GT_ADDR) + if (argObj->gtObj.gtOp1->IsVarAddr()) // Is the source a LclVar? { - // exception : no need to use CopyBlk if the valuetype is on the stack - if (objAddr->gtFlags & GTF_ADDR_ONSTACK) - { - copyBlkClass = NO_CLASS_HANDLE; - } - // exception : no need to use CopyBlk if the valuetype is already a struct local - else if (objAddr->gtOp.gtOp1->gtOper == GT_LCL_VAR) - { - copyBlkClass = NO_CLASS_HANDLE; - } + copyBlkClass = NO_CLASS_HANDLE; } } @@ -3701,12 +3752,30 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* callNode) // unsigned roundupSize = (unsigned)roundUp(structSize, TARGET_POINTER_SIZE); size = roundupSize / TARGET_POINTER_SIZE; + + // We also must update fltArgRegNum so that we no longer try to + // allocate any new floating point registers for args + // This prevents us from backfilling a subsequent arg into d7 + // + fltArgRegNum = MAX_FLOAT_REG_ARG; } } else { // Check if the last register needed is still in the int argument register range. isRegArg = (intArgRegNum + (size - 1)) < maxRegArgs; + + // Did we run out of registers when we had a 16-byte struct (size===2) ? + // (i.e we only have one register remaining but we needed two registers to pass this arg) + // This prevents us from backfilling a subsequent arg into x7 + // + if (!isRegArg && (size > 1)) + { + // We also must update intArgRegNum so that we no longer try to + // allocate any new general purpose registers for args + // + intArgRegNum = maxRegArgs; + } } #else // not _TARGET_ARM_ or _TARGET_ARM64_ @@ -5043,38 +5112,68 @@ void Compiler::fgAddSkippedRegsInPromotedStructArg(LclVarDsc* var #endif // _TARGET_ARM_ -/***************************************************************************** - * - * The companion to impFixupCallStructReturn. Now that the importer is done - * and we no longer care as much about the declared return type, change to - * precomputed native return type (at least for architectures that don't - * always use return buffers for structs). - * - */ +//**************************************************************************** +// fgFixupStructReturn: +// The companion to impFixupCallStructReturn. Now that the importer is done +// change the gtType to the precomputed native return type +// requires that callNode currently has a struct type +// void Compiler::fgFixupStructReturn(GenTreePtr callNode) { + assert(varTypeIsStruct(callNode)); + GenTreeCall* call = callNode->AsCall(); bool callHasRetBuffArg = call->HasRetBufArg(); + bool isHelperCall = call->IsHelperCall(); + + // Decide on the proper return type for this call that currently returns a struct + // + CORINFO_CLASS_HANDLE retClsHnd = call->gtRetClsHnd; + Compiler::structPassingKind howToReturnStruct; + var_types returnType; - if (!callHasRetBuffArg && varTypeIsStruct(call)) + // There are a couple of Helper Calls that say they return a TYP_STRUCT but they + // expect this method to re-type this to a TYP_REF (what is in call->gtReturnType) + // + // CORINFO_HELP_METHODDESC_TO_STUBRUNTIMEMETHOD + // CORINFO_HELP_FIELDDESC_TO_STUBRUNTIMEFIELD + // CORINFO_HELP_TYPEHANDLE_TO_RUNTIMETYPE_MAYBENULL + // + if (isHelperCall) { -#if FEATURE_MULTIREG_RET - if (call->gtCall.IsVarargs() || !IsHfa(call)) -#endif // FEATURE_MULTIREG_RET + assert(!callHasRetBuffArg); + assert(retClsHnd == NO_CLASS_HANDLE); + + // Now that we are past the importer, re-type this node + howToReturnStruct = SPK_PrimitiveType; + returnType = (var_types)call->gtReturnType; + } + else + { + returnType = getReturnTypeForStruct(retClsHnd, &howToReturnStruct); + } + + if (howToReturnStruct == SPK_ByReference) + { + assert(returnType == TYP_UNKNOWN); + assert(callHasRetBuffArg); + } + else + { + assert(returnType != TYP_UNKNOWN); + + if (returnType != TYP_STRUCT) { - // Now that we are past the importer, re-type this node so the register predictor does - // the right thing - call->gtType = genActualType((var_types)call->gtCall.gtReturnType); + // Widen the primitive type if necessary + returnType = genActualType(returnType); } + call->gtType = returnType; } -#ifdef FEATURE_HFA - // Either we don't have a struct now or if struct, then it is HFA returned in regs. - assert(!varTypeIsStruct(call) || (IsHfa(call) && !callHasRetBuffArg)); -#elif defined(FEATURE_UNIX_AMD64_STRUCT_PASSING) +#if FEATURE_MULTIREG_RET // Either we don't have a struct now or if struct, then it is a struct returned in regs or in return buffer. assert(!varTypeIsStruct(call) || call->HasMultiRegRetVal() || callHasRetBuffArg); -#else +#else // !FEATURE_MULTIREG_RET // No more struct returns assert(call->TypeGet() != TYP_STRUCT); #endif @@ -7309,7 +7408,10 @@ GenTreePtr Compiler::fgMorphCall(GenTreeCall* call) } #endif // FEATURE_TAILCALL_OPT - fgFixupStructReturn(call); + if (varTypeIsStruct(call)) + { + fgFixupStructReturn(call); + } var_types callType = call->TypeGet(); @@ -16002,11 +16104,11 @@ void Compiler::fgPromoteStructs() #if !FEATURE_MULTIREG_STRUCT_PROMOTE #if defined(_TARGET_ARM64_) // - // For now we currently don't promote structs that could be passed in registers + // For now we currently don't promote structs that are passed in registers // - else if (varDsc->lvIsMultiregStruct()) + else if (lvaIsMultiregStruct(varDsc)) { - JITDUMP("Not promoting promotable struct local V%02u (size==%d): ", + JITDUMP("Not promoting promotable multireg struct local V%02u (size==%d): ", lclNum, lvaLclExactSize(lclNum)); shouldPromote = false; } @@ -16015,8 +16117,8 @@ void Compiler::fgPromoteStructs() else if (varDsc->lvIsParam) { #if FEATURE_MULTIREG_STRUCT_PROMOTE - if (varDsc->lvIsMultiregStruct() && // Is this a variable holding a value that is passed in multiple registers? - (structPromotionInfo.fieldCnt != 2)) // Does it have exactly two fields + if (lvaIsMultiregStruct(varDsc) && // Is this a variable holding a value that is passed in multiple registers? + (structPromotionInfo.fieldCnt != 2)) // Does it have exactly two fields { JITDUMP("Not promoting multireg struct local V%02u, because lvIsParam is true and #fields != 2\n", lclNum); @@ -16316,8 +16418,7 @@ void Compiler::fgMarkImplicitByRefArgs() #if defined(_TARGET_AMD64_) if (size > REGSIZE_BYTES || (size & (size - 1)) != 0) #elif defined(_TARGET_ARM64_) - if ((size > TARGET_POINTER_SIZE) && !varDsc->lvIsMultiregStruct()) - + if ((size > TARGET_POINTER_SIZE) && !lvaIsMultiregStruct(varDsc)) #endif { // Previously nobody was ever setting lvIsParam and lvIsTemp on the same local diff --git a/src/jit/register_arg_convention.h b/src/jit/register_arg_convention.h index 40db9b8136..5114843c1c 100644 --- a/src/jit/register_arg_convention.h +++ b/src/jit/register_arg_convention.h @@ -74,13 +74,15 @@ public: // "numRegs" must be "2" to allocate an ARM double-precision floating-point register. bool canEnreg(var_types type, unsigned numRegs = 1); -#ifdef _TARGET_ARM_ - + // Set the fact that we have used up all remaining registers of 'type' + // void setAllRegArgUsed(var_types type) { regArgNum(type) = maxRegArgNum(type); } +#ifdef _TARGET_ARM_ + void setAnyFloatStackArgs() { anyFloatStackArgs = true; diff --git a/src/jit/ssabuilder.cpp b/src/jit/ssabuilder.cpp index 2dda5c5f61..d546a8160a 100644 --- a/src/jit/ssabuilder.cpp +++ b/src/jit/ssabuilder.cpp @@ -761,7 +761,7 @@ void SsaBuilder::InsertPhiFunctions(BasicBlock** postOrder, int count) // GenTreePtr phiRhs = m_pCompiler->gtNewOperNode(GT_PHI, m_pCompiler->lvaTable[lclNum].TypeGet(), nullptr); - GenTreePtr phiAsg = m_pCompiler->gtNewAssignNode(phiLhs, phiRhs DEBUGARG(/*isPhiDefn*/true)); + GenTreePtr phiAsg = m_pCompiler->gtNewAssignNode(phiLhs, phiRhs); GenTreePtr stmt = m_pCompiler->fgInsertStmtAtBeg(bbInDomFront, phiAsg); m_pCompiler->gtSetStmtInfo(stmt); diff --git a/src/jit/target.h b/src/jit/target.h index dcb18dbe32..984cf1b0ca 100644 --- a/src/jit/target.h +++ b/src/jit/target.h @@ -1167,7 +1167,7 @@ typedef unsigned short regPairNoSmall; // arm: need 12 bits #define FEATURE_SET_FLAGS 1 // Set to true to force the JIT to mark the trees with GTF_SET_FLAGS when the flags need to be set #define FEATURE_MULTIREG_ARGS_OR_RET 1 // Support for passing and/or returning single values in more than one register (including HFA support) #define FEATURE_MULTIREG_ARGS 1 // Support for passing a single argument in more than one register (including passing HFAs) - #define FEATURE_MULTIREG_RET 1 // Support for returning a single value in more than one register (including HFA returns) + #define FEATURE_MULTIREG_RET 0 // Support for returning a single value in more than one register (including HFA returns) #define FEATURE_STRUCT_CLASSIFIER 0 // Uses a classifier function to determine is structs are passed/returned in more than one register #define MAX_PASS_MULTIREG_BYTES 32 // Maximum size of a struct that could be passed in more than one register (Max is an HFA of 4 doubles) #define MAX_RET_MULTIREG_BYTES 32 // Maximum size of a struct that could be returned in more than one register (Max is an HFA of 4 doubles) @@ -1487,12 +1487,12 @@ typedef unsigned short regPairNoSmall; // arm: need 12 bits #define FEATURE_SET_FLAGS 1 // Set to true to force the JIT to mark the trees with GTF_SET_FLAGS when the flags need to be set #define FEATURE_MULTIREG_ARGS_OR_RET 1 // Support for passing and/or returning single values in more than one register #define FEATURE_MULTIREG_ARGS 1 // Support for passing a single argument in more than one register - #define FEATURE_MULTIREG_RET 0 // Support for returning a single value in more than one register + #define FEATURE_MULTIREG_RET 1 // Support for returning a single value in more than one register #define FEATURE_STRUCT_CLASSIFIER 0 // Uses a classifier function to determine is structs are passed/returned in more than one register #define MAX_PASS_MULTIREG_BYTES 32 // Maximum size of a struct that could be passed in more than one register (max is 4 doubles using an HFA) - #define MAX_RET_MULTIREG_BYTES 0 // Maximum size of a struct that could be returned in more than one register (Max is an HFA of 4 doubles) + #define MAX_RET_MULTIREG_BYTES 32 // Maximum size of a struct that could be returned in more than one register (Max is an HFA of 4 doubles) #define MAX_ARG_REG_COUNT 4 // Maximum registers used to pass a single argument in multiple registers. (max is 4 floats or doubles using an HFA) - #define MAX_RET_REG_COUNT 1 // Maximum registers used to return a value. + #define MAX_RET_REG_COUNT 4 // Maximum registers used to return a value. #ifdef FEATURE_USE_ASM_GC_WRITE_BARRIERS #define NOGC_WRITE_BARRIERS 1 // We have specialized WriteBarrier JIT Helpers that DO-NOT trash the RBM_CALLEE_TRASH registers @@ -1670,6 +1670,9 @@ typedef unsigned short regPairNoSmall; // arm: need 12 bits #define RBM_INTRET RBM_R0 #define REG_LNGRET REG_R0 #define RBM_LNGRET RBM_R0 + // second return register for 16-byte structs + #define REG_INTRET_1 REG_R1 + #define RBM_INTRET_1 RBM_R1 #define REG_FLOATRET REG_V0 #define RBM_FLOATRET RBM_V0 diff --git a/src/mscorlib/model.xml b/src/mscorlib/model.xml index 4208b3903f..65c5d39276 100644 --- a/src/mscorlib/model.xml +++ b/src/mscorlib/model.xml @@ -9583,6 +9583,7 @@ <Member Name="GetDecimal(System.String)" /> <Member Name="GetDateTime(System.String)" /> <Member Name="GetString(System.String)" /> + <Member Name="UpdateValue(System.String,System.Object,System.Type)" /> <Member MemberType="Property" Name="FullTypeName" /> <Member MemberType="Property" Name="AssemblyName" /> <Member MemberType="Property" Name="MemberCount" /> @@ -9599,6 +9600,9 @@ <Member MemberType="Property" Name="Value" /> <Member MemberType="Property" Name="ObjectType" /> </Type> + <Type Name="System.Runtime.Serialization.IObjectReference"> + <Member Name="GetRealObject(System.Runtime.Serialization.StreamingContext)" /> + </Type> <Type Name="System.Runtime.Serialization.ISerializable"> <Member Name="GetObjectData(System.Runtime.Serialization.SerializationInfo,System.Runtime.Serialization.StreamingContext)" /> </Type> diff --git a/src/mscorlib/ref/mscorlib.cs b/src/mscorlib/ref/mscorlib.cs index 769c497373..965e727ee9 100644 --- a/src/mscorlib/ref/mscorlib.cs +++ b/src/mscorlib/ref/mscorlib.cs @@ -2457,6 +2457,13 @@ namespace System public NotFiniteNumberException(string message) { } public NotFiniteNumberException(string message, System.Exception innerException) { } } + [AttributeUsage(AttributeTargets.Field, Inherited = false)] + public sealed class NonSerializedAttribute : Attribute + { + public NonSerializedAttribute() + { + } + } [System.Runtime.InteropServices.ComVisibleAttribute(true)] public partial class NotImplementedException : System.SystemException { @@ -2739,6 +2746,13 @@ namespace System [System.CLSCompliantAttribute(false)] public static bool TryParse(string s, out sbyte result) { result = default(sbyte); return default(bool); } } + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Delegate, Inherited = false)] + public sealed class SerializableAttribute : Attribute + { + public SerializableAttribute() + { + } + } [System.Runtime.InteropServices.ComVisibleAttribute(true)] [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] public partial struct Single : System.IComparable, System.IComparable<float>, System.IConvertible, System.IEquatable<float>, System.IFormattable @@ -11269,6 +11283,47 @@ namespace System.Runtime.Serialization [System.Security.SecurityCriticalAttribute] public static object GetUninitializedObject(System.Type type) { return default(object); } } + [System.Runtime.InteropServices.ComVisibleAttribute(true)] + public interface IDeserializationCallback + { + void OnDeserialization(object sender); + } + [System.Runtime.InteropServices.ComVisibleAttribute(true)] + [System.CLSCompliant(false)] + public interface IFormatterConverter + { + object Convert(object value, Type type); + object Convert(object value, TypeCode typeCode); + bool ToBoolean(object value); + char ToChar(object value); + [CLSCompliant(false)] + sbyte ToSByte(object value); + byte ToByte(object value); + short ToInt16(object value); + [CLSCompliant(false)] + ushort ToUInt16(object value); + int ToInt32(object value); + [CLSCompliant(false)] + uint ToUInt32(object value); + long ToInt64(object value); + [CLSCompliant(false)] + ulong ToUInt64(object value); + float ToSingle(object value); + double ToDouble(object value); + Decimal ToDecimal(object value); + DateTime ToDateTime(object value); + String ToString(object value); + } + [System.Runtime.InteropServices.ComVisible(true)] + public interface IObjectReference + { + object GetRealObject(StreamingContext context); + } + [System.Runtime.InteropServices.ComVisibleAttribute(true)] + public interface ISerializable + { + void GetObjectData(SerializationInfo info, StreamingContext context); + } [System.AttributeUsageAttribute((System.AttributeTargets)(64), Inherited=false)] [System.Runtime.InteropServices.ComVisibleAttribute(true)] public sealed partial class OnDeserializedAttribute : System.Attribute @@ -11293,6 +11348,21 @@ namespace System.Runtime.Serialization { public OnSerializingAttribute() { } } + [System.AttributeUsageAttribute(System.AttributeTargets.Field, Inherited = false)] + [System.Runtime.InteropServices.ComVisibleAttribute(true)] + public sealed partial class OptionalFieldAttribute : System.Attribute + { + public OptionalFieldAttribute() { } + public int VersionAdded { get { return default(int); } set { } } + } + [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] + [System.Runtime.InteropServices.ComVisibleAttribute(true)] + public struct SerializationEntry + { + public string Name { get { throw null; } } + public Type ObjectType { get { throw null; } } + public object Value { get { throw null; } } + } [System.Runtime.InteropServices.ComVisibleAttribute(true)] public partial class SerializationException : System.SystemException { @@ -11301,6 +11371,73 @@ namespace System.Runtime.Serialization public SerializationException(string message, System.Exception innerException) { } } [System.Runtime.InteropServices.ComVisibleAttribute(true)] + public sealed class SerializationInfo + { + [CLSCompliant(false)] + public SerializationInfo(Type type, IFormatterConverter converter) { } + public string AssemblyName { get { throw null; } set { } } + public string FullTypeName { get { throw null; } set { } } + public bool IsFullTypeNameSetExplicit { get { throw null; } } + public bool IsAssemblyNameSetExplicit { get { throw null; } } + public int MemberCount { get { throw null; } } + public Type ObjectType { get { throw null; } } + public void AddValue(string name, bool value) { } + public void AddValue(string name, byte value) { } + public void AddValue(string name, char value) { } + public void AddValue(string name, DateTime value) { } + public void AddValue(string name, decimal value) { } + public void AddValue(string name, double value) { } + public void AddValue(string name, short value) { } + public void AddValue(string name, int value) { } + public void AddValue(string name, long value) { } + public void AddValue(string name, object value) { } + public void AddValue(string name, object value, Type type) { } + [CLSCompliant(false)] + public void AddValue(string name, sbyte value) { } + public void AddValue(string name, float value) { } + [CLSCompliant(false)] + public void AddValue(string name, ushort value) { } + [CLSCompliant(false)] + public void AddValue(string name, uint value) { } + [CLSCompliant(false)] + public void AddValue(string name, ulong value) { } + public bool GetBoolean(string name) { throw null; } + public byte GetByte(string name) { throw null; } + public char GetChar(string name) { throw null; } + public DateTime GetDateTime(string name) { throw null; } + public decimal GetDecimal(string name) { throw null; } + public double GetDouble(string name) { throw null; } + public SerializationInfoEnumerator GetEnumerator() { throw null; } + public short GetInt16(string name) { throw null; } + public int GetInt32(string name) { throw null; } + public long GetInt64(string name) { throw null; } + [CLSCompliant(false)] + public sbyte GetSByte(string name) { throw null; } + public float GetSingle(string name) { throw null; } + public string GetString(string name) { throw null; } + [CLSCompliant(false)] + public ushort GetUInt16(string name) { throw null; } + [CLSCompliant(false)] + public uint GetUInt32(string name) { throw null; } + [CLSCompliant(false)] + public ulong GetUInt64(string name) { throw null; } + public object GetValue(string name, Type type) { throw null; } + public void SetType(Type type) { } + public void UpdateValue(string name, object value, Type type) { } + } + [System.Runtime.InteropServices.ComVisibleAttribute(true)] + public sealed class SerializationInfoEnumerator : System.Collections.IEnumerator + { + private SerializationInfoEnumerator() { } + public SerializationEntry Current { get { throw null; } } + public string Name { get { throw null; } } + public Type ObjectType { get { throw null; } } + object System.Collections.IEnumerator.Current { get { throw null; } } + public object Value { get { throw null; } } + public bool MoveNext() { throw null; } + public void Reset() { throw null; } + } + [System.Runtime.InteropServices.ComVisibleAttribute(true)] [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] public partial struct StreamingContext { diff --git a/src/mscorlib/src/System/Diagnostics/Eventing/ActivityTracker.cs b/src/mscorlib/src/System/Diagnostics/Eventing/ActivityTracker.cs index 1508025088..a7124a26ff 100644 --- a/src/mscorlib/src/System/Diagnostics/Eventing/ActivityTracker.cs +++ b/src/mscorlib/src/System/Diagnostics/Eventing/ActivityTracker.cs @@ -274,9 +274,9 @@ namespace System.Diagnostics.Tracing /// </summary> private string NormalizeActivityName(string providerName, string activityName, int task) { - if (activityName.EndsWith(EventSource.s_ActivityStartSuffix)) + if (activityName.EndsWith(EventSource.s_ActivityStartSuffix, StringComparison.Ordinal)) activityName = activityName.Substring(0, activityName.Length - EventSource.s_ActivityStartSuffix.Length); - else if (activityName.EndsWith(EventSource.s_ActivityStopSuffix)) + else if (activityName.EndsWith(EventSource.s_ActivityStopSuffix, StringComparison.Ordinal)) activityName = activityName.Substring(0, activityName.Length - EventSource.s_ActivityStopSuffix.Length); else if (task != 0) activityName = "task" + task.ToString(); @@ -328,10 +328,7 @@ namespace System.Diagnostics.Tracing public override string ToString() { - string dead = ""; - if (m_stopped != 0) - dead = ",DEAD"; - return m_name + "(" + Path(this) + dead + ")"; + return m_name + "(" + Path(this) + (m_stopped != 0 ? ",DEAD)" : ")"); } public static string LiveActivities(ActivityInfo list) diff --git a/src/mscorlib/src/System/Diagnostics/Eventing/EventProvider.cs b/src/mscorlib/src/System/Diagnostics/Eventing/EventProvider.cs index bc7aefcffe..2b0807f4ee 100644 --- a/src/mscorlib/src/System/Diagnostics/Eventing/EventProvider.cs +++ b/src/mscorlib/src/System/Diagnostics/Eventing/EventProvider.cs @@ -496,7 +496,7 @@ namespace System.Diagnostics.Tracing { foreach (string valueName in key.GetValueNames()) { - if (valueName.StartsWith("ControllerData_Session_")) + if (valueName.StartsWith("ControllerData_Session_", StringComparison.Ordinal)) { string strId = valueName.Substring(23); // strip of the ControllerData_Session_ int etwSessionId; @@ -508,7 +508,7 @@ namespace System.Diagnostics.Tracing if (data != null) { var dataAsString = System.Text.Encoding.UTF8.GetString(data); - int keywordIdx = dataAsString.IndexOf("EtwSessionKeyword"); + int keywordIdx = dataAsString.IndexOf("EtwSessionKeyword", StringComparison.Ordinal); if (0 <= keywordIdx) { int startIdx = keywordIdx + 18; diff --git a/src/mscorlib/src/System/Diagnostics/Eventing/EventSource.cs b/src/mscorlib/src/System/Diagnostics/Eventing/EventSource.cs index f2613e7224..5e025b0966 100644 --- a/src/mscorlib/src/System/Diagnostics/Eventing/EventSource.cs +++ b/src/mscorlib/src/System/Diagnostics/Eventing/EventSource.cs @@ -3259,10 +3259,10 @@ namespace System.Diagnostics.Tracing // are the typenames equal and the namespaces under "Diagnostics.Tracing" (typically // either Microsoft.Diagnostics.Tracing or System.Diagnostics.Tracing)? string.Equals(attributeType.Name, reflectedAttributeType.Name, StringComparison.Ordinal) && - attributeType.Namespace.EndsWith("Diagnostics.Tracing") && - (reflectedAttributeType.Namespace.EndsWith("Diagnostics.Tracing") + attributeType.Namespace.EndsWith("Diagnostics.Tracing", StringComparison.Ordinal) && + (reflectedAttributeType.Namespace.EndsWith("Diagnostics.Tracing", StringComparison.Ordinal) #if EVENT_SOURCE_LEGACY_NAMESPACE_SUPPORT - || reflectedAttributeType.Namespace.EndsWith("Diagnostics.Eventing") + || reflectedAttributeType.Namespace.EndsWith("Diagnostics.Eventing", StringComparison.Ordinal) #endif ); } @@ -6610,7 +6610,7 @@ namespace System.Diagnostics.Tracing if (localizedString != null) { value = localizedString; - if (etwFormat && key.StartsWith("event_")) + if (etwFormat && key.StartsWith("event_", StringComparison.Ordinal)) { var evtName = key.Substring("event_".Length); value = TranslateToManifestConvention(value, evtName); diff --git a/src/mscorlib/src/System/Diagnostics/Eventing/TraceLogging/TraceLoggingEventSource.cs b/src/mscorlib/src/System/Diagnostics/Eventing/TraceLogging/TraceLoggingEventSource.cs index 95cadbc906..963c492419 100644 --- a/src/mscorlib/src/System/Diagnostics/Eventing/TraceLogging/TraceLoggingEventSource.cs +++ b/src/mscorlib/src/System/Diagnostics/Eventing/TraceLogging/TraceLoggingEventSource.cs @@ -773,7 +773,7 @@ namespace System.Diagnostics.Tracing List<byte> traitMetaData = new List<byte>(100); for (int i = 0; i < m_traits.Length - 1; i += 2) { - if (m_traits[i].StartsWith("ETW_")) + if (m_traits[i].StartsWith("ETW_", StringComparison.Ordinal)) { string etwTrait = m_traits[i].Substring(4); byte traitNum; diff --git a/src/mscorlib/src/System/Runtime/Serialization/SerializationInfo.cs b/src/mscorlib/src/System/Runtime/Serialization/SerializationInfo.cs index a009033180..94e6825b51 100644 --- a/src/mscorlib/src/System/Runtime/Serialization/SerializationInfo.cs +++ b/src/mscorlib/src/System/Runtime/Serialization/SerializationInfo.cs @@ -408,16 +408,23 @@ namespace System.Runtime.Serialization /*=================================UpdateValue================================== **Action: Finds the value if it exists in the current data. If it does, we replace ** the values, if not, we append it to the end. This is useful to the - ** ObjectManager when it's performing fixups, but shouldn't be used by - ** clients. Exposing out this functionality would allow children to overwrite - ** their parent's values. + ** ObjectManager when it's performing fixups. **Returns: void **Arguments: name -- the name of the data to be updated. ** value -- the new value. ** type -- the type of the data being added. - **Exceptions: None. All error checking is done with asserts. + **Exceptions: None. All error checking is done with asserts. Although public in coreclr, + ** it's not exposed in a contract and is only meant to be used by corefx. ==============================================================================*/ - internal void UpdateValue(String name, Object value, Type type) +#if FEATURE_CORECLR + // This should not be used by clients: exposing out this functionality would allow children + // to overwrite their parent's values. It is public in order to give corefx access to it for + // its ObjectManager implementation, but it should not be exposed out of a contract. + public +#else + internal +#endif + void UpdateValue(String name, Object value, Type type) { Contract.Assert(null != name, "[SerializationInfo.UpdateValue]name!=null"); Contract.Assert(null != value, "[SerializationInfo.UpdateValue]value!=null"); diff --git a/src/mscorlib/src/System/String.cs b/src/mscorlib/src/System/String.cs index 067f88a605..aebc2e5cc4 100644 --- a/src/mscorlib/src/System/String.cs +++ b/src/mscorlib/src/System/String.cs @@ -100,31 +100,52 @@ namespace System { } [ComVisible(false)] - public static String Join<T>(String separator, IEnumerable<T> values) { + public static String Join<T>(String separator, IEnumerable<T> values) + { if (values == null) throw new ArgumentNullException("values"); Contract.Ensures(Contract.Result<String>() != null); Contract.EndContractBlock(); - using(IEnumerator<T> en = values.GetEnumerator()) { + using (IEnumerator<T> en = values.GetEnumerator()) + { if (!en.MoveNext()) - return String.Empty; - - StringBuilder result = StringBuilderCache.Acquire(); + return string.Empty; + + // We called MoveNext once, so this will be the first item T currentValue = en.Current; - if (currentValue != null) { - result.Append(currentValue.ToString()); + // Call ToString before calling MoveNext again, since + // we want to stay consistent with the below loop + // Everything should be called in the order + // MoveNext-Current-ToString, unless further optimizations + // can be made, to avoid breaking changes + string firstString = currentValue?.ToString(); + + // If there's only 1 item, simply call ToString on that + if (!en.MoveNext()) + { + // We have to handle the case of either currentValue + // or its ToString being null + return firstString ?? string.Empty; } - while (en.MoveNext()) { + StringBuilder result = StringBuilderCache.Acquire(); + + result.Append(firstString); + + do + { currentValue = en.Current; result.Append(separator); - if (currentValue != null) { + if (currentValue != null) + { result.Append(currentValue.ToString()); } - } + } + while (en.MoveNext()); + return StringBuilderCache.GetStringAndRelease(result); } } @@ -161,15 +182,6 @@ namespace System { } } - -#if BIT64 - private const int charPtrAlignConst = 3; - private const int alignConst = 7; -#else - private const int charPtrAlignConst = 1; - private const int alignConst = 3; -#endif - internal char FirstChar { get { return m_firstChar; } } // Joins an array of strings together as one string with a separator between each original string. diff --git a/src/pal/inc/pal_endian.h b/src/pal/inc/pal_endian.h index 8a9032301d..50b9e76765 100644 --- a/src/pal/inc/pal_endian.h +++ b/src/pal/inc/pal_endian.h @@ -98,8 +98,12 @@ inline void SwapGuid(GUID *pGuid) #define VALPTR(x) VAL32(x) #endif -#if defined(ALIGN_ACCESS) && !defined(_MSC_VER) +#ifdef _ARM_ +#define LOG2_PTRSIZE 2 +#define ALIGN_ACCESS ((1<<LOG2_PTRSIZE)-1) +#endif +#if defined(ALIGN_ACCESS) && !defined(_MSC_VER) #ifdef __cplusplus extern "C++" { // Get Unaligned values from a potentially unaligned object @@ -138,7 +142,7 @@ inline void SET_UNALIGNED_64(void *pObject, UINT64 Value) } #endif // __cplusplus -#else +#else // defined(ALIGN_ACCESS) && !defined(_MSC_VER) // Get Unaligned values from a potentially unaligned object #define GET_UNALIGNED_16(_pObject) (*(UINT16 UNALIGNED *)(_pObject)) @@ -150,7 +154,7 @@ inline void SET_UNALIGNED_64(void *pObject, UINT64 Value) #define SET_UNALIGNED_32(_pObject, _Value) (*(UNALIGNED UINT32 *)(_pObject)) = (UINT32)(_Value) #define SET_UNALIGNED_64(_pObject, _Value) (*(UNALIGNED UINT64 *)(_pObject)) = (UINT64)(_Value) -#endif +#endif // defined(ALIGN_ACCESS) && !defined(_MSC_VER) // Get Unaligned values from a potentially unaligned object and swap the value #define GET_UNALIGNED_VAL16(_pObject) VAL16(GET_UNALIGNED_16(_pObject)) diff --git a/src/pal/prebuilt/inc/cordebug.h b/src/pal/prebuilt/inc/cordebug.h index 46f6929778..a5a5cf2e8a 100644 --- a/src/pal/prebuilt/inc/cordebug.h +++ b/src/pal/prebuilt/inc/cordebug.h @@ -3,11 +3,11 @@ /* this ALWAYS GENERATED file contains the definitions for the interfaces */ - /* File created by MIDL compiler version 8.00.0613 */ -/* at Mon Jan 18 19:14:07 2038 + /* File created by MIDL compiler version 8.00.0603 */ +/* at Fri Jul 15 18:01:08 2016 */ -/* Compiler settings for C:/ssd/coreclr/src/inc/cordebug.idl: - Oicf, W1, Zp8, env=Win32 (32b run), target_arch=X86 8.00.0613 +/* Compiler settings for E:/git/coreclr/src/inc/cordebug.idl: + Oicf, W1, Zp8, env=Win32 (32b run), target_arch=X86 8.00.0603 protocol : dce , ms_ext, c_ext, robust error checks: allocation ref bounds_check enum stub_data VC __declspec() decoration level: @@ -29,7 +29,7 @@ #ifndef __RPCNDR_H_VERSION__ #error this stub requires an updated version of <rpcndr.h> -#endif /* __RPCNDR_H_VERSION__ */ +#endif // __RPCNDR_H_VERSION__ #ifndef COM_NO_WINDOWS_H #include "windows.h" @@ -8639,7 +8639,39 @@ enum CorDebugRegister REGISTER_ARM64_X26 = ( REGISTER_ARM64_X25 + 1 ) , REGISTER_ARM64_X27 = ( REGISTER_ARM64_X26 + 1 ) , REGISTER_ARM64_X28 = ( REGISTER_ARM64_X27 + 1 ) , - REGISTER_ARM64_LR = ( REGISTER_ARM64_X28 + 1 ) + REGISTER_ARM64_LR = ( REGISTER_ARM64_X28 + 1 ) , + REGISTER_ARM64_V0 = ( REGISTER_ARM64_LR + 1 ) , + REGISTER_ARM64_V1 = ( REGISTER_ARM64_V0 + 1 ) , + REGISTER_ARM64_V2 = ( REGISTER_ARM64_V1 + 1 ) , + REGISTER_ARM64_V3 = ( REGISTER_ARM64_V2 + 1 ) , + REGISTER_ARM64_V4 = ( REGISTER_ARM64_V3 + 1 ) , + REGISTER_ARM64_V5 = ( REGISTER_ARM64_V4 + 1 ) , + REGISTER_ARM64_V6 = ( REGISTER_ARM64_V5 + 1 ) , + REGISTER_ARM64_V7 = ( REGISTER_ARM64_V6 + 1 ) , + REGISTER_ARM64_V8 = ( REGISTER_ARM64_V7 + 1 ) , + REGISTER_ARM64_V9 = ( REGISTER_ARM64_V8 + 1 ) , + REGISTER_ARM64_V10 = ( REGISTER_ARM64_V9 + 1 ) , + REGISTER_ARM64_V11 = ( REGISTER_ARM64_V10 + 1 ) , + REGISTER_ARM64_V12 = ( REGISTER_ARM64_V11 + 1 ) , + REGISTER_ARM64_V13 = ( REGISTER_ARM64_V12 + 1 ) , + REGISTER_ARM64_V14 = ( REGISTER_ARM64_V13 + 1 ) , + REGISTER_ARM64_V15 = ( REGISTER_ARM64_V14 + 1 ) , + REGISTER_ARM64_V16 = ( REGISTER_ARM64_V15 + 1 ) , + REGISTER_ARM64_V17 = ( REGISTER_ARM64_V16 + 1 ) , + REGISTER_ARM64_V18 = ( REGISTER_ARM64_V17 + 1 ) , + REGISTER_ARM64_V19 = ( REGISTER_ARM64_V18 + 1 ) , + REGISTER_ARM64_V20 = ( REGISTER_ARM64_V19 + 1 ) , + REGISTER_ARM64_V21 = ( REGISTER_ARM64_V20 + 1 ) , + REGISTER_ARM64_V22 = ( REGISTER_ARM64_V21 + 1 ) , + REGISTER_ARM64_V23 = ( REGISTER_ARM64_V22 + 1 ) , + REGISTER_ARM64_V24 = ( REGISTER_ARM64_V23 + 1 ) , + REGISTER_ARM64_V25 = ( REGISTER_ARM64_V24 + 1 ) , + REGISTER_ARM64_V26 = ( REGISTER_ARM64_V25 + 1 ) , + REGISTER_ARM64_V27 = ( REGISTER_ARM64_V26 + 1 ) , + REGISTER_ARM64_V28 = ( REGISTER_ARM64_V27 + 1 ) , + REGISTER_ARM64_V29 = ( REGISTER_ARM64_V28 + 1 ) , + REGISTER_ARM64_V30 = ( REGISTER_ARM64_V29 + 1 ) , + REGISTER_ARM64_V31 = ( REGISTER_ARM64_V30 + 1 ) } CorDebugRegister; diff --git a/src/vm/arm64/CallDescrWorkerARM64.asm b/src/vm/arm64/CallDescrWorkerARM64.asm index 556983934e..464640157e 100644 --- a/src/vm/arm64/CallDescrWorkerARM64.asm +++ b/src/vm/arm64/CallDescrWorkerARM64.asm @@ -118,8 +118,8 @@ LNoDoubleHFAReturn EMIT_BREAKPOINT ; Unreachable LIntReturn - ;; Save return value into retbuf for int - str x0, [x19, #(CallDescrData__returnValue + 0)] + ;; Save return value(s) into retbuf for int + stp x0,x1, [x19, #(CallDescrData__returnValue + 0)] LReturnDone diff --git a/src/vm/arm64/asmhelpers.asm b/src/vm/arm64/asmhelpers.asm index 72794adef1..89e6710a96 100644 --- a/src/vm/arm64/asmhelpers.asm +++ b/src/vm/arm64/asmhelpers.asm @@ -779,10 +779,10 @@ UMThunkStub_WrongAppDomain ; remaining arguments are unused bl UM2MDoADCallBack - ; restore integral return value - ldr x0, [fp, #16] + ; restore any integral return value(s) + ldp x0, x1, [fp, #16] - ; restore FP or HFA return value + ; restore any FP or HFA return value(s) RESTORE_FLOAT_ARGUMENT_REGISTERS sp, 0 b UMThunkStub_PostCall @@ -857,9 +857,10 @@ UM2MThunk_WrapperHelper_RegArgumentsSetup blr x16 - ; save integral return value - str x0, [x19] - ; save FP/HFA return values + ; save any integral return value(s) + stp x0, x1, [x19] + + ; save any FP or HFA return value(s) SAVE_FLOAT_ARGUMENT_REGISTERS x19, -1 * (SIZEOF__FloatArgumentRegisters + 16) EPILOG_STACK_RESTORE @@ -931,13 +932,20 @@ UM2MThunk_WrapperHelper_RegArgumentsSetup PROLOG_SAVE_REG_PAIR x25, x26, #64 PROLOG_SAVE_REG_PAIR x27, x28, #80 - str x0, [sp, #96] - ; HFA return value can be in d0-d3 + ; save any integral return value(s) + stp x0, x1, [sp, #96] + + ; save any FP/HFA return value(s) stp d0, d1, [sp, #112] stp d2, d3, [sp, #128] + mov x0, sp bl OnHijackScalarWorker - ldr x0, [sp, #96] + + ; restore any integral return value(s) + ldp x0, x1, [sp, #96] + + ; restore any FP/HFA return value(s) ldp d0, d1, [sp, #112] ldp d2, d3, [sp, #128] diff --git a/src/vm/arm64/cgencpu.h b/src/vm/arm64/cgencpu.h index 4929cd1dc9..20b39c440d 100644 --- a/src/vm/arm64/cgencpu.h +++ b/src/vm/arm64/cgencpu.h @@ -54,8 +54,8 @@ class ComCallMethodDesc; #define CACHE_LINE_SIZE 32 // As per Intel Optimization Manual the cache line size is 32 bytes #define LOG2SLOT LOG2_PTRSIZE -#define ENREGISTERED_RETURNTYPE_MAXSIZE 64 // bytes (maximum HFA size is 8 doubles) -#define ENREGISTERED_RETURNTYPE_INTEGER_MAXSIZE 8 // bytes +#define ENREGISTERED_RETURNTYPE_MAXSIZE 32 // bytes (four FP registers: d0,d1,d2 and d3) +#define ENREGISTERED_RETURNTYPE_INTEGER_MAXSIZE 16 // bytes (two int registers: x0 and x1) #define ENREGISTERED_PARAMTYPE_MAXSIZE 16 // bytes (max value type size that can be passed by value) #define CALLDESCR_ARGREGS 1 // CallDescrWorker has ArgumentRegister parameter @@ -153,9 +153,34 @@ inline TADDR GetSP(const T_CONTEXT * context) { return TADDR(context->Sp); } -inline PCODE GetLR(const T_CONTEXT * context) { +inline TADDR GetLR(const T_CONTEXT * context) { LIMITED_METHOD_DAC_CONTRACT; - return PCODE(context->Lr); + return context->Lr; +} + +inline void SetLR( T_CONTEXT * context, TADDR eip) { + LIMITED_METHOD_DAC_CONTRACT; + context->Lr = eip; +} + +inline TADDR GetReg(T_CONTEXT * context, int Regnum) +{ + LIMITED_METHOD_DAC_CONTRACT; + _ASSERTE(Regnum >= 0 && Regnum < 32 ); + return context->X[Regnum]; +} + +inline void SetReg(T_CONTEXT * context, int Regnum, PCODE RegContent) +{ + LIMITED_METHOD_DAC_CONTRACT; + _ASSERTE(Regnum >= 0 && Regnum <=28 ); + context->X[Regnum] = RegContent; +} +inline void SetSimdReg(T_CONTEXT * context, int Regnum, NEON128 RegContent) +{ + LIMITED_METHOD_DAC_CONTRACT; + _ASSERTE(Regnum >= 0 && Regnum <= 28); + context->V[Regnum] = RegContent; } extern "C" LPVOID __stdcall GetCurrentSP(); @@ -176,6 +201,40 @@ inline TADDR GetFP(const T_CONTEXT * context) return (TADDR)(context->Fp); } +inline NEON128 GetSimdMem(PCODE ip) +{ + NEON128 mem; + LIMITED_METHOD_DAC_CONTRACT; + EX_TRY + { + mem.Low = dac_cast<PCODE>(ip); + mem.High = dac_cast<PCODE>(ip + sizeof(PCODE)); + } + EX_CATCH + { + _ASSERTE(!"Memory read within jitted Code Failed, this should not happen!!!!"); + } + EX_END_CATCH(SwallowAllExceptions); + + return mem; +} +inline TADDR GetMem(PCODE ip) +{ + TADDR mem; + LIMITED_METHOD_DAC_CONTRACT; + EX_TRY + { + mem = dac_cast<TADDR>(ip); + } + 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 *pCOMMethod, PCODE target); #endif // FEATURE_COMINTEROP @@ -456,8 +515,11 @@ struct HijackArgs DWORD64 X19, X20, X21, X22, X23, X24, X25, X26, X27, X28; union { - DWORD64 X0; - size_t ReturnValue; + struct { + DWORD64 X0; + DWORD64 X1; + }; + size_t ReturnValue[2]; }; }; diff --git a/src/vm/arm64/stubs.cpp b/src/vm/arm64/stubs.cpp index bb2a6cf256..7db8a31f20 100644 --- a/src/vm/arm64/stubs.cpp +++ b/src/vm/arm64/stubs.cpp @@ -1051,23 +1051,6 @@ AdjustContextForVirtualStub( } #endif // !(DACCESS_COMPILE && CROSSGEN_COMPILE) -extern "C" { - -void FuncEvalHijack(void) -{ - _ASSERTE(!"ARM64:NYI"); -} - -void ExceptionHijack(void) -{ - _ASSERTE(!"ARM64:NYI"); -} -void ExceptionHijackEnd(void) -{ - _ASSERTE(!"ARM64:NYI"); -} -}; - #ifdef FEATURE_COMINTEROP extern "C" void GenericComPlusCallStub(void) { diff --git a/src/vm/exceptionhandling.cpp b/src/vm/exceptionhandling.cpp index a99a20b312..e59f10e070 100644 --- a/src/vm/exceptionhandling.cpp +++ b/src/vm/exceptionhandling.cpp @@ -2457,7 +2457,7 @@ CLRUnwindStatus ExceptionTracker::ProcessManagedCallFrame( PTR_EXCEPTION_CLAUSE_TOKEN pLimitClauseToken = NULL; if (!fIgnoreThisFrame && !fIsFirstPass && !m_sfResumeStackFrame.IsNull() && (sf >= m_sfResumeStackFrame)) { - CONSISTENCY_CHECK_MSG(sf == m_sfResumeStackFrame, "Passed initial resume frame and fIgnoreThisFrame wasn't set!"); + EH_LOG((LL_INFO100, " RESUMEFRAME: sf is %p and m_sfResumeStackFrame: %p\n", sf.SP, m_sfResumeStackFrame.SP)); EH_LOG((LL_INFO100, " RESUMEFRAME: %s initial resume frame: %p\n", (sf == m_sfResumeStackFrame) ? "REACHED" : "PASSED" , m_sfResumeStackFrame.SP)); // process this frame to call handlers @@ -2469,6 +2469,7 @@ CLRUnwindStatus ExceptionTracker::ProcessManagedCallFrame( // as the last clause we process in the "inital resume frame". Anything further // down the list of clauses is skipped along with all call frames up to the actual // resume frame. + CONSISTENCY_CHECK_MSG(sf == m_sfResumeStackFrame, "Passed initial resume frame and fIgnoreThisFrame wasn't set!"); } // // END resume frame code diff --git a/src/vm/gccover.cpp b/src/vm/gccover.cpp index 3220cddd8e..41dc094e94 100644 --- a/src/vm/gccover.cpp +++ b/src/vm/gccover.cpp @@ -37,6 +37,7 @@ MethodDesc* AsMethodDesc(size_t addr); static SLOT getTargetOfCall(SLOT instrPtr, PCONTEXT regs, SLOT*nextInstr); +bool isCallToStopForGCJitHelper(SLOT instrPtr); #if defined(_TARGET_ARM_) || defined(_TARGET_ARM64_) static void replaceSafePointInstructionWithGcStressInstr(UINT32 safePointOffset, LPVOID codeStart); static bool replaceInterruptibleRangesWithGcStressInstr (UINT32 startOffset, UINT32 stopOffset, LPVOID codeStart); @@ -911,7 +912,11 @@ bool replaceInterruptibleRangesWithGcStressInstr (UINT32 startOffset, UINT32 sto if (instrLen == 2) *((WORD*)instrPtr) = INTERRUPT_INSTR; else - *((DWORD*)instrPtr) = INTERRUPT_INSTR_32; + { + // Do not replace with gcstress interrupt instruction at call to JIT_RareDisableHelper + if(!isCallToStopForGCJitHelper(instrPtr)) + *((DWORD*)instrPtr) = INTERRUPT_INSTR_32; + } instrPtr += instrLen; #elif defined(_TARGET_ARM64_) @@ -920,8 +925,10 @@ bool replaceInterruptibleRangesWithGcStressInstr (UINT32 startOffset, UINT32 sto *((DWORD*)instrPtr) != INTERRUPT_INSTR_CALL && *((DWORD*)instrPtr) != INTERRUPT_INSTR_PROTECT_RET); } - - *((DWORD*)instrPtr) = INTERRUPT_INSTR; + + // Do not replace with gcstress interrupt instruction at call to JIT_RareDisableHelper + if(!isCallToStopForGCJitHelper(instrPtr)) + *((DWORD*)instrPtr) = INTERRUPT_INSTR; instrPtr += 4; #endif @@ -940,6 +947,57 @@ bool replaceInterruptibleRangesWithGcStressInstr (UINT32 startOffset, UINT32 sto } #endif +// Is this a call instruction to JIT_RareDisableHelper() +// We cannot insert GCStress instruction at this call +// For arm64 & arm (R2R) call to jithelpers happens via a stub. +// For other architectures call does not happen via stub. +// For other architecures we can get the target directly by calling getTargetOfCall(). +// This is not the case for arm64/arm so need to decode the stub +// instruction to find the actual jithelper target. +// For other architecture we detect call to JIT_RareDisableHelper +// in function OnGcCoverageInterrupt() since getTargetOfCall() can +// get the actual jithelper target. +bool isCallToStopForGCJitHelper(SLOT instrPtr) +{ +#if defined(_TARGET_ARM64_) + if (((*reinterpret_cast<DWORD*>(instrPtr)) & 0xFC000000) == 0x94000000) // Do we have a BL instruction? + { + // call through immediate + int imm26 = ((*((DWORD*)instrPtr)) & 0x03FFFFFF)<<2; + // SignExtend the immediate value. + imm26 = (imm26 << 4) >> 4; + DWORD* target = (DWORD*) (instrPtr + imm26); + // Call to jithelpers happens via jumpstub + if(*target == 0x58000050 /* ldr xip0, PC+8*/ && *(target+1) == 0xd61f0200 /* br xip0 */) + { + // get the actual jithelper target + target = *(((DWORD**)target) + 1); + if((TADDR)target == GetEEFuncEntryPoint(JIT_RareDisableHelper)) + { + return true; + } + } + } +#elif defined(_TARGET_ARM_) + if((instrPtr[1] & 0xf8) == 0xf0 && (instrPtr[3] & 0xc0) == 0xc0) // call using imm + { + int imm32 = GetThumb2BlRel24((UINT16 *)instrPtr); + WORD* target = (WORD*) (instrPtr + 4 + imm32); + // Is target a stub + if(*target == 0xf8df && *(target+1) == 0xf000) // ldr pc, [pc+4] + { + //get actual target + target = *((WORD**)target + 1); + if((TADDR)target == GetEEFuncEntryPoint(JIT_RareDisableHelper)) + { + return true; + } + } + } +#endif + return false; +} + static size_t getRegVal(unsigned regNum, PCONTEXT regs) { return *getRegAddr(regNum, regs); diff --git a/src/vm/gcenv.os.cpp b/src/vm/gcenv.os.cpp index 1972b23ea7..73b21a7a0b 100644 --- a/src/vm/gcenv.os.cpp +++ b/src/vm/gcenv.os.cpp @@ -534,9 +534,14 @@ uint64_t GCToOSInterface::GetPhysicalMemoryLimit() return memStatus.ullTotalPhys; } -// Get global memory status +// Get memory status // Parameters: -// ms - pointer to the structure that will be filled in with the memory status +// memory_load - A number between 0 and 100 that specifies the approximate percentage of physical memory +// that is in use (0 indicates no memory use and 100 indicates full memory use). +// available_physical - The amount of physical memory currently available, in bytes. +// available_page_file - The maximum amount of memory the current process can commit, in bytes. +// Remarks: +// Any parameter can be null. void GCToOSInterface::GetMemoryStatus(uint32_t* memory_load, uint64_t* available_physical, uint64_t* available_page_file) { LIMITED_METHOD_CONTRACT; diff --git a/src/vm/stubmgr.cpp b/src/vm/stubmgr.cpp index d648262078..39f4c25a7d 100644 --- a/src/vm/stubmgr.cpp +++ b/src/vm/stubmgr.cpp @@ -2313,6 +2313,18 @@ BOOL DelegateInvokeStubManager::TraceManager(Thread *thread, TraceDestination *t else offsetOfNextDest = DelegateObject::GetOffsetOfMethodPtrAux(); destAddr = *(PCODE*)(pThis + offsetOfNextDest); +#elif defined(_TARGET_ARM64_) + (*pRetAddr) = (BYTE *)(size_t)(pContext->Lr); + pThis = (BYTE*)(size_t)(pContext->X0); + + // Could be in the singlecast invoke stub (in which case the next destination is in _methodPtr) or a + // shuffle thunk (destination in _methodPtrAux). + int offsetOfNextDest; + if (pc == GetEEFuncEntryPoint(SinglecastDelegateInvokeStub)) + offsetOfNextDest = DelegateObject::GetOffsetOfMethodPtr(); + else + offsetOfNextDest = DelegateObject::GetOffsetOfMethodPtrAux(); + destAddr = *(PCODE*)(pThis + offsetOfNextDest); #else PORTABILITY_ASSERT("DelegateInvokeStubManager::TraceManager"); destAddr = NULL; diff --git a/src/vm/stubmgr.h b/src/vm/stubmgr.h index eecd616b7a..f0098c9d55 100644 --- a/src/vm/stubmgr.h +++ b/src/vm/stubmgr.h @@ -933,7 +933,7 @@ public: #elif defined(_TARGET_ARM_) return pContext->R12; #elif defined(_TARGET_ARM64_) - return pContext->X15; + return pContext->X12; #else PORTABILITY_ASSERT("StubManagerHelpers::GetHiddenArg"); return NULL; diff --git a/src/vm/util.cpp b/src/vm/util.cpp index 7a05624113..f7185c744f 100644 --- a/src/vm/util.cpp +++ b/src/vm/util.cpp @@ -739,6 +739,46 @@ SIZE_T GetRegOffsInCONTEXT(ICorDebugInfo::RegNum regNum) case ICorDebugInfo::REGNUM_AMBIENT_SP: return offsetof(T_CONTEXT, Sp); default: _ASSERTE(!"Bad regNum"); return (SIZE_T)(-1); } +#elif defined(_TARGET_ARM64_) + + switch(regNum) + { + case ICorDebugInfo::REGNUM_X0: return offsetof(T_CONTEXT, X0); + case ICorDebugInfo::REGNUM_X1: return offsetof(T_CONTEXT, X1); + case ICorDebugInfo::REGNUM_X2: return offsetof(T_CONTEXT, X2); + case ICorDebugInfo::REGNUM_X3: return offsetof(T_CONTEXT, X3); + case ICorDebugInfo::REGNUM_X4: return offsetof(T_CONTEXT, X4); + case ICorDebugInfo::REGNUM_X5: return offsetof(T_CONTEXT, X5); + case ICorDebugInfo::REGNUM_X6: return offsetof(T_CONTEXT, X6); + case ICorDebugInfo::REGNUM_X7: return offsetof(T_CONTEXT, X7); + case ICorDebugInfo::REGNUM_X8: return offsetof(T_CONTEXT, X8); + case ICorDebugInfo::REGNUM_X9: return offsetof(T_CONTEXT, X9); + case ICorDebugInfo::REGNUM_X10: return offsetof(T_CONTEXT, X10); + case ICorDebugInfo::REGNUM_X11: return offsetof(T_CONTEXT, X11); + case ICorDebugInfo::REGNUM_X12: return offsetof(T_CONTEXT, X12); + case ICorDebugInfo::REGNUM_X13: return offsetof(T_CONTEXT, X13); + case ICorDebugInfo::REGNUM_X14: return offsetof(T_CONTEXT, X14); + case ICorDebugInfo::REGNUM_X15: return offsetof(T_CONTEXT, X15); + case ICorDebugInfo::REGNUM_X16: return offsetof(T_CONTEXT, X16); + case ICorDebugInfo::REGNUM_X17: return offsetof(T_CONTEXT, X17); + case ICorDebugInfo::REGNUM_X18: return offsetof(T_CONTEXT, X18); + case ICorDebugInfo::REGNUM_X19: return offsetof(T_CONTEXT, X19); + case ICorDebugInfo::REGNUM_X20: return offsetof(T_CONTEXT, X20); + case ICorDebugInfo::REGNUM_X21: return offsetof(T_CONTEXT, X21); + case ICorDebugInfo::REGNUM_X22: return offsetof(T_CONTEXT, X22); + case ICorDebugInfo::REGNUM_X23: return offsetof(T_CONTEXT, X23); + case ICorDebugInfo::REGNUM_X24: return offsetof(T_CONTEXT, X24); + case ICorDebugInfo::REGNUM_X25: return offsetof(T_CONTEXT, X25); + case ICorDebugInfo::REGNUM_X26: return offsetof(T_CONTEXT, X26); + case ICorDebugInfo::REGNUM_X27: return offsetof(T_CONTEXT, X27); + case ICorDebugInfo::REGNUM_X28: return offsetof(T_CONTEXT, X28); + case ICorDebugInfo::REGNUM_FP: return offsetof(T_CONTEXT, Fp); + case ICorDebugInfo::REGNUM_LR: return offsetof(T_CONTEXT, Lr); + case ICorDebugInfo::REGNUM_SP: return offsetof(T_CONTEXT, Sp); + case ICorDebugInfo::REGNUM_PC: return offsetof(T_CONTEXT, Pc); + case ICorDebugInfo::REGNUM_AMBIENT_SP: return offsetof(T_CONTEXT, Sp); + default: _ASSERTE(!"Bad regNum"); return (SIZE_T)(-1); + } #else PORTABILITY_ASSERT("GetRegOffsInCONTEXT is not implemented on this platform."); return (SIZE_T) -1; diff --git a/tests/arm64/Tests.lst b/tests/arm64/Tests.lst index d120adef45..7da5b574a2 100644 --- a/tests/arm64/Tests.lst +++ b/tests/arm64/Tests.lst @@ -2412,7 +2412,7 @@ RelativePath=baseservices\threading\interlocked\compareexchange\CompareExchangeT WorkingDir=baseservices\threading\interlocked\compareexchange\CompareExchangeTClass Expected=0 MaxAllowedDurationSeconds=600 -Categories=NEW;EXPECTED_FAIL;UNSTABLE +Categories=NEW;EXPECTED_PASS;UNSTABLE HostStyle=0 [CompareExchangeTClass_1.cmd_345] RelativePath=baseservices\threading\interlocked\compareexchange\CompareExchangeTClass_1\CompareExchangeTClass_1.cmd @@ -29306,7 +29306,7 @@ RelativePath=JIT\Directed\StructABI\StructABI\StructABI.cmd WorkingDir=JIT\Directed\StructABI\StructABI Expected=0 MaxAllowedDurationSeconds=600 -Categories=Pri0;JIT;EXPECTED_FAIL;NATIVE_INTEROP;NEED_TRIAGE +Categories=Pri0;JIT;EXPECTED_PASS;NATIVE_INTEROP;NEED_TRIAGE HostStyle=0 [SP1.cmd_4225] RelativePath=JIT\Directed\StructPromote\SP1\SP1.cmd @@ -35186,14 +35186,14 @@ RelativePath=JIT\jit64\hfa\main\testE\hfa_nd2E_d\hfa_nd2E_d.cmd WorkingDir=JIT\jit64\hfa\main\testE\hfa_nd2E_d Expected=0 MaxAllowedDurationSeconds=600 -Categories=Pri0;EXPECTED_FAIL;NATIVE_INTEROP +Categories=Pri0;EXPECTED_PASS;NATIVE_INTEROP HostStyle=0 [hfa_nd2E_r.cmd_5068] RelativePath=JIT\jit64\hfa\main\testE\hfa_nd2E_r\hfa_nd2E_r.cmd WorkingDir=JIT\jit64\hfa\main\testE\hfa_nd2E_r Expected=0 MaxAllowedDurationSeconds=600 -Categories=Pri0;EXPECTED_FAIL;NATIVE_INTEROP +Categories=Pri0;EXPECTED_PASS;NATIVE_INTEROP HostStyle=0 [hfa_nf0E_d.cmd_5069] RelativePath=JIT\jit64\hfa\main\testE\hfa_nf0E_d\hfa_nf0E_d.cmd @@ -35228,14 +35228,14 @@ RelativePath=JIT\jit64\hfa\main\testE\hfa_nf2E_d\hfa_nf2E_d.cmd WorkingDir=JIT\jit64\hfa\main\testE\hfa_nf2E_d Expected=0 MaxAllowedDurationSeconds=600 -Categories=Pri0;EXPECTED_FAIL;NATIVE_INTEROP +Categories=Pri0;EXPECTED_PASS;NATIVE_INTEROP HostStyle=0 [hfa_nf2E_r.cmd_5074] RelativePath=JIT\jit64\hfa\main\testE\hfa_nf2E_r\hfa_nf2E_r.cmd WorkingDir=JIT\jit64\hfa\main\testE\hfa_nf2E_r Expected=0 MaxAllowedDurationSeconds=600 -Categories=Pri0;EXPECTED_FAIL;NATIVE_INTEROP +Categories=Pri0;EXPECTED_PASS;NATIVE_INTEROP HostStyle=0 [hfa_sd0E_d.cmd_5075] RelativePath=JIT\jit64\hfa\main\testE\hfa_sd0E_d\hfa_sd0E_d.cmd @@ -35270,14 +35270,14 @@ RelativePath=JIT\jit64\hfa\main\testE\hfa_sd2E_d\hfa_sd2E_d.cmd WorkingDir=JIT\jit64\hfa\main\testE\hfa_sd2E_d Expected=0 MaxAllowedDurationSeconds=600 -Categories=Pri0;EXPECTED_FAIL;NATIVE_INTEROP +Categories=Pri0;EXPECTED_PASS;NATIVE_INTEROP HostStyle=0 [hfa_sd2E_r.cmd_5080] RelativePath=JIT\jit64\hfa\main\testE\hfa_sd2E_r\hfa_sd2E_r.cmd WorkingDir=JIT\jit64\hfa\main\testE\hfa_sd2E_r Expected=0 MaxAllowedDurationSeconds=600 -Categories=Pri0;EXPECTED_FAIL;NATIVE_INTEROP +Categories=Pri0;EXPECTED_PASS;NATIVE_INTEROP HostStyle=0 [hfa_sf0E_d.cmd_5081] RelativePath=JIT\jit64\hfa\main\testE\hfa_sf0E_d\hfa_sf0E_d.cmd @@ -35312,14 +35312,14 @@ RelativePath=JIT\jit64\hfa\main\testE\hfa_sf2E_d\hfa_sf2E_d.cmd WorkingDir=JIT\jit64\hfa\main\testE\hfa_sf2E_d Expected=0 MaxAllowedDurationSeconds=600 -Categories=Pri0;EXPECTED_FAIL;NATIVE_INTEROP +Categories=Pri0;EXPECTED_PASS;NATIVE_INTEROP HostStyle=0 [hfa_sf2E_r.cmd_5086] RelativePath=JIT\jit64\hfa\main\testE\hfa_sf2E_r\hfa_sf2E_r.cmd WorkingDir=JIT\jit64\hfa\main\testE\hfa_sf2E_r Expected=0 MaxAllowedDurationSeconds=600 -Categories=Pri0;EXPECTED_FAIL;NATIVE_INTEROP +Categories=Pri0;EXPECTED_PASS;NATIVE_INTEROP HostStyle=0 [hfa_nd0G_d.cmd_5087] RelativePath=JIT\jit64\hfa\main\testG\hfa_nd0G_d\hfa_nd0G_d.cmd @@ -35354,14 +35354,14 @@ RelativePath=JIT\jit64\hfa\main\testG\hfa_nd2G_d\hfa_nd2G_d.cmd WorkingDir=JIT\jit64\hfa\main\testG\hfa_nd2G_d Expected=0 MaxAllowedDurationSeconds=600 -Categories=Pri0;EXPECTED_FAIL +Categories=Pri0;EXPECTED_PASS HostStyle=0 [hfa_nd2G_r.cmd_5092] RelativePath=JIT\jit64\hfa\main\testG\hfa_nd2G_r\hfa_nd2G_r.cmd WorkingDir=JIT\jit64\hfa\main\testG\hfa_nd2G_r Expected=0 MaxAllowedDurationSeconds=600 -Categories=Pri0;EXPECTED_FAIL +Categories=Pri0;EXPECTED_PASS HostStyle=0 [hfa_nf0G_d.cmd_5093] RelativePath=JIT\jit64\hfa\main\testG\hfa_nf0G_d\hfa_nf0G_d.cmd @@ -35396,14 +35396,14 @@ RelativePath=JIT\jit64\hfa\main\testG\hfa_nf2G_d\hfa_nf2G_d.cmd WorkingDir=JIT\jit64\hfa\main\testG\hfa_nf2G_d Expected=0 MaxAllowedDurationSeconds=600 -Categories=Pri0;EXPECTED_FAIL +Categories=Pri0;EXPECTED_PASS HostStyle=0 [hfa_nf2G_r.cmd_5098] RelativePath=JIT\jit64\hfa\main\testG\hfa_nf2G_r\hfa_nf2G_r.cmd WorkingDir=JIT\jit64\hfa\main\testG\hfa_nf2G_r Expected=0 MaxAllowedDurationSeconds=600 -Categories=Pri0;EXPECTED_FAIL +Categories=Pri0;EXPECTED_PASS HostStyle=0 [hfa_sd0G_d.cmd_5099] RelativePath=JIT\jit64\hfa\main\testG\hfa_sd0G_d\hfa_sd0G_d.cmd @@ -35438,14 +35438,14 @@ RelativePath=JIT\jit64\hfa\main\testG\hfa_sd2G_d\hfa_sd2G_d.cmd WorkingDir=JIT\jit64\hfa\main\testG\hfa_sd2G_d Expected=0 MaxAllowedDurationSeconds=600 -Categories=Pri0;EXPECTED_FAIL +Categories=Pri0;EXPECTED_PASS HostStyle=0 [hfa_sd2G_r.cmd_5104] RelativePath=JIT\jit64\hfa\main\testG\hfa_sd2G_r\hfa_sd2G_r.cmd WorkingDir=JIT\jit64\hfa\main\testG\hfa_sd2G_r Expected=0 MaxAllowedDurationSeconds=600 -Categories=Pri0;EXPECTED_FAIL +Categories=Pri0;EXPECTED_PASS HostStyle=0 [hfa_sf0G_d.cmd_5105] RelativePath=JIT\jit64\hfa\main\testG\hfa_sf0G_d\hfa_sf0G_d.cmd @@ -35480,14 +35480,14 @@ RelativePath=JIT\jit64\hfa\main\testG\hfa_sf2G_d\hfa_sf2G_d.cmd WorkingDir=JIT\jit64\hfa\main\testG\hfa_sf2G_d Expected=0 MaxAllowedDurationSeconds=600 -Categories=Pri0;EXPECTED_FAIL +Categories=Pri0;EXPECTED_PASS HostStyle=0 [hfa_sf2G_r.cmd_5110] RelativePath=JIT\jit64\hfa\main\testG\hfa_sf2G_r\hfa_sf2G_r.cmd WorkingDir=JIT\jit64\hfa\main\testG\hfa_sf2G_r Expected=0 MaxAllowedDurationSeconds=600 -Categories=Pri0;EXPECTED_FAIL +Categories=Pri0;EXPECTED_PASS HostStyle=0 [call03_dynamic.cmd_5111] RelativePath=JIT\jit64\localloc\call\call03_dynamic\call03_dynamic.cmd @@ -47086,14 +47086,14 @@ RelativePath=JIT\Methodical\explicit\coverage\seq_byte_1_d\seq_byte_1_d.cmd WorkingDir=JIT\Methodical\explicit\coverage\seq_byte_1_d Expected=0 MaxAllowedDurationSeconds=600 -Categories=Pri0;EXPECTED_FAIL +Categories=Pri0;EXPECTED_PASS HostStyle=0 [seq_byte_1_r.cmd_6812] RelativePath=JIT\Methodical\explicit\coverage\seq_byte_1_r\seq_byte_1_r.cmd WorkingDir=JIT\Methodical\explicit\coverage\seq_byte_1_r Expected=0 MaxAllowedDurationSeconds=600 -Categories=Pri0;EXPECTED_FAIL +Categories=Pri0;EXPECTED_PASS HostStyle=0 [seq_double_1_d.cmd_6813] RelativePath=JIT\Methodical\explicit\coverage\seq_double_1_d\seq_double_1_d.cmd @@ -54604,7 +54604,7 @@ RelativePath=JIT\opt\ETW\TailCallCases\TailCallCases.cmd WorkingDir=JIT\opt\ETW\TailCallCases Expected=0 MaxAllowedDurationSeconds=600 -Categories=Pri0;JIT;EXPECTED_FAIL;REL_PASS +Categories=Pri0;JIT;EXPECTED_PASS;REL_PASS HostStyle=0 [badcallee.cmd_7894] RelativePath=JIT\opt\Inline\regression\badcallee\badcallee\badcallee.cmd @@ -63872,14 +63872,14 @@ RelativePath=JIT\SIMD\Vector3Interop_r\Vector3Interop_r.cmd WorkingDir=JIT\SIMD\Vector3Interop_r Expected=0 MaxAllowedDurationSeconds=600 -Categories=NEW;EXPECTED_FAIL +Categories=NEW;EXPECTED_PASS HostStyle=0 [Vector3Interop_ro.cmd_9247] RelativePath=JIT\SIMD\Vector3Interop_ro\Vector3Interop_ro.cmd WorkingDir=JIT\SIMD\Vector3Interop_ro Expected=0 MaxAllowedDurationSeconds=600 -Categories=NEW;EXPECTED_FAIL +Categories=NEW;EXPECTED_PASS HostStyle=0 [Vector3_r.cmd_9248] RelativePath=JIT\SIMD\Vector3_r\Vector3_r.cmd diff --git a/tests/runtest.sh b/tests/runtest.sh index d76328ae9f..e14fbfe7f4 100755 --- a/tests/runtest.sh +++ b/tests/runtest.sh @@ -1045,7 +1045,7 @@ fi if [ -z "$mscorlibDir" ]; then mscorlibDir=$coreClrBinDir fi -if [ -d $mscorlibDir/bin ]; then +if [ -d "$mscorlibDir" ] && [ -d "$mscorlibDir/bin" ]; then cp $mscorlibDir/bin/* $mscorlibDir fi |