summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--BuildToolsVersion.txt2
-rw-r--r--Documentation/building/crossgen.md1
-rw-r--r--dir.props2
-rw-r--r--functions.cmake6
-rw-r--r--src/ToolBox/SOS/Strike/disasmARM.cpp14
-rw-r--r--src/debug/daccess/dacdbiimpl.cpp5
-rw-r--r--src/debug/di/CMakeLists.txt22
-rw-r--r--src/debug/di/DI.props6
-rw-r--r--src/debug/di/arm64/cordbregisterset.cpp4
-rw-r--r--src/debug/di/arm64/floatconversion.asm22
-rw-r--r--src/debug/di/module.cpp4
-rw-r--r--src/debug/di/rspriv.h2
-rw-r--r--src/debug/di/rsthread.cpp34
-rw-r--r--src/debug/di/shimstackwalk.cpp2
-rw-r--r--src/debug/ee/arm64/arm64walker.cpp462
-rw-r--r--src/debug/ee/arm64/dbghelpers.asm54
-rw-r--r--src/debug/ee/controller.cpp60
-rw-r--r--src/debug/ee/controller.h5
-rw-r--r--src/debug/ee/debugger.cpp8
-rw-r--r--src/debug/ee/frameinfo.cpp2
-rw-r--r--src/debug/ee/funceval.cpp6
-rw-r--r--src/debug/ee/walker.h23
-rw-r--r--src/debug/ee/wks/CMakeLists.txt33
-rw-r--r--src/debug/ee/wks/wks.nativeproj1
-rw-r--r--src/debug/inc/arm64/primitives.h14
-rw-r--r--src/gc/env/gcenv.os.h4
-rw-r--r--src/gc/sample/gcenv.windows.cpp22
-rw-r--r--src/inc/cordebug.idl33
-rwxr-xr-xsrc/jit/codegen.h40
-rw-r--r--src/jit/codegenarm64.cpp728
-rwxr-xr-xsrc/jit/codegencommon.cpp84
-rwxr-xr-xsrc/jit/codegenxarch.cpp64
-rw-r--r--src/jit/compiler.cpp71
-rw-r--r--src/jit/compiler.h92
-rw-r--r--src/jit/compiler.hpp26
-rw-r--r--src/jit/earlyprop.cpp13
-rwxr-xr-xsrc/jit/ee_il_dll.cpp37
-rw-r--r--src/jit/emit.cpp42
-rw-r--r--src/jit/emit.h10
-rw-r--r--src/jit/emitarm64.cpp32
-rw-r--r--src/jit/emitarm64.h7
-rw-r--r--src/jit/emitxarch.cpp6
-rw-r--r--src/jit/emitxarch.h8
-rw-r--r--src/jit/flowgraph.cpp74
-rw-r--r--src/jit/gcencode.cpp2
-rw-r--r--src/jit/gentree.cpp182
-rw-r--r--src/jit/gentree.h17
-rw-r--r--src/jit/importer.cpp374
-rw-r--r--src/jit/instr.cpp14
-rw-r--r--src/jit/jit.h12
-rw-r--r--src/jit/lclvars.cpp95
-rw-r--r--src/jit/lowerarm64.cpp630
-rwxr-xr-xsrc/jit/morph.cpp217
-rw-r--r--src/jit/register_arg_convention.h6
-rw-r--r--src/jit/ssabuilder.cpp2
-rw-r--r--src/jit/target.h11
-rw-r--r--src/mscorlib/model.xml4
-rw-r--r--src/mscorlib/ref/mscorlib.cs137
-rw-r--r--src/mscorlib/src/System/Diagnostics/Eventing/ActivityTracker.cs9
-rw-r--r--src/mscorlib/src/System/Diagnostics/Eventing/EventProvider.cs4
-rw-r--r--src/mscorlib/src/System/Diagnostics/Eventing/EventSource.cs8
-rw-r--r--src/mscorlib/src/System/Diagnostics/Eventing/TraceLogging/TraceLoggingEventSource.cs2
-rw-r--r--src/mscorlib/src/System/Runtime/Serialization/SerializationInfo.cs17
-rw-r--r--src/mscorlib/src/System/String.cs50
-rw-r--r--src/pal/inc/pal_endian.h10
-rw-r--r--src/pal/prebuilt/inc/cordebug.h44
-rw-r--r--src/vm/arm64/CallDescrWorkerARM64.asm4
-rw-r--r--src/vm/arm64/asmhelpers.asm26
-rw-r--r--src/vm/arm64/cgencpu.h74
-rw-r--r--src/vm/arm64/stubs.cpp17
-rw-r--r--src/vm/exceptionhandling.cpp3
-rw-r--r--src/vm/gccover.cpp64
-rw-r--r--src/vm/gcenv.os.cpp9
-rw-r--r--src/vm/stubmgr.cpp12
-rw-r--r--src/vm/stubmgr.h2
-rw-r--r--src/vm/util.cpp40
-rw-r--r--tests/arm64/Tests.lst46
-rwxr-xr-xtests/runtest.sh2
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.
diff --git a/dir.props b/dir.props
index 9adde779fd..7cd3f5c374 100644
--- a/dir.props
+++ b/dir.props
@@ -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