summaryrefslogtreecommitdiff
path: root/src/jit
diff options
context:
space:
mode:
Diffstat (limited to 'src/jit')
-rw-r--r--src/jit/CMakeLists.txt27
-rw-r--r--src/jit/ICorJitInfo_API_names.h171
-rw-r--r--src/jit/ICorJitInfo_API_wrapper.hpp1666
-rw-r--r--src/jit/assertionprop.cpp119
-rw-r--r--src/jit/bitsetasuint64.h2
-rw-r--r--src/jit/block.cpp37
-rw-r--r--src/jit/block.h24
-rwxr-xr-xsrc/jit/codegen.h36
-rw-r--r--src/jit/codegenarm.cpp1078
-rw-r--r--src/jit/codegenarm64.cpp1441
-rw-r--r--src/jit/codegenclassic.h7
-rw-r--r--[-rwxr-xr-x]src/jit/codegencommon.cpp622
-rw-r--r--src/jit/codegeninterface.h6
-rw-r--r--src/jit/codegenlegacy.cpp448
-rw-r--r--src/jit/codegenlinear.cpp1773
-rw-r--r--src/jit/codegenlinear.h78
-rw-r--r--src/jit/codegenxarch.cpp3899
-rw-r--r--src/jit/compatjit/.gitmirror1
-rw-r--r--src/jit/compatjit/CMakeLists.txt66
-rw-r--r--src/jit/compiler.cpp1399
-rw-r--r--src/jit/compiler.h297
-rw-r--r--src/jit/compiler.hpp110
-rw-r--r--src/jit/compphases.h15
-rw-r--r--src/jit/crossgen/CMakeLists.txt4
-rw-r--r--src/jit/decomposelongs.cpp1056
-rw-r--r--src/jit/decomposelongs.h12
-rw-r--r--src/jit/dll/CMakeLists.txt10
-rw-r--r--src/jit/dll/jit.nativeproj6
-rw-r--r--src/jit/earlyprop.cpp42
-rw-r--r--[-rwxr-xr-x]src/jit/ee_il_dll.cpp53
-rw-r--r--src/jit/ee_il_dll.hpp4
-rw-r--r--src/jit/emit.cpp80
-rw-r--r--src/jit/emit.h27
-rw-r--r--src/jit/emitarm.cpp20
-rw-r--r--src/jit/emitarm64.cpp2
-rw-r--r--src/jit/emitxarch.cpp710
-rw-r--r--src/jit/emitxarch.h118
-rw-r--r--src/jit/error.cpp4
-rw-r--r--src/jit/error.h199
-rw-r--r--src/jit/flowgraph.cpp2347
-rw-r--r--src/jit/gcencode.cpp267
-rw-r--r--src/jit/gentree.cpp1311
-rw-r--r--src/jit/gentree.h563
-rw-r--r--src/jit/gschecks.cpp48
-rw-r--r--src/jit/gtlist.h333
-rw-r--r--src/jit/gtstructs.h4
-rw-r--r--src/jit/importer.cpp760
-rw-r--r--src/jit/inline.cpp138
-rw-r--r--src/jit/inline.def10
-rw-r--r--src/jit/inline.h17
-rw-r--r--src/jit/inlinepolicy.cpp232
-rw-r--r--src/jit/inlinepolicy.h98
-rw-r--r--src/jit/instr.cpp14
-rw-r--r--src/jit/instr.h10
-rw-r--r--src/jit/instrsxarch.h19
-rw-r--r--src/jit/jit.h49
-rw-r--r--src/jit/jit.settings.targets3
-rw-r--r--src/jit/jitconfig.h2
-rw-r--r--src/jit/jitconfigvalues.h50
-rw-r--r--src/jit/jitee.h264
-rw-r--r--src/jit/jiteh.cpp12
-rw-r--r--src/jit/jitgcinfo.h3
-rw-r--r--src/jit/lclvars.cpp97
-rw-r--r--src/jit/legacyjit/.gitmirror1
-rw-r--r--src/jit/legacyjit/CMakeLists.txt62
-rw-r--r--src/jit/lir.cpp49
-rw-r--r--src/jit/liveness.cpp37
-rw-r--r--src/jit/loopcloning.cpp2
-rw-r--r--src/jit/lower.cpp742
-rw-r--r--src/jit/lower.h18
-rw-r--r--src/jit/lowerarm.cpp138
-rw-r--r--src/jit/lowerarm64.cpp149
-rw-r--r--src/jit/lowerxarch.cpp1104
-rw-r--r--src/jit/lsra.cpp1051
-rw-r--r--src/jit/lsra.h87
-rw-r--r--src/jit/morph.cpp1656
-rw-r--r--src/jit/nodeinfo.h47
-rw-r--r--src/jit/optcse.cpp88
-rw-r--r--src/jit/optimizer.cpp688
-rw-r--r--src/jit/protojit/CMakeLists.txt8
-rw-r--r--src/jit/rangecheck.cpp5
-rw-r--r--src/jit/rationalize.cpp250
-rw-r--r--src/jit/regalloc.cpp208
-rw-r--r--src/jit/regalloc.h24
-rw-r--r--src/jit/registerfp.cpp6
-rw-r--r--src/jit/regset.cpp10
-rw-r--r--src/jit/scopeinfo.cpp18
-rw-r--r--src/jit/sideeffects.h6
-rw-r--r--src/jit/simd.cpp137
-rw-r--r--src/jit/simd.h15
-rw-r--r--src/jit/simdcodegenxarch.cpp698
-rw-r--r--src/jit/simdintrinsiclist.h9
-rw-r--r--src/jit/ssabuilder.cpp223
-rw-r--r--src/jit/stackfp.cpp23
-rw-r--r--src/jit/standalone/CMakeLists.txt21
-rw-r--r--src/jit/target.h47
-rw-r--r--src/jit/tinyarray.h2
-rw-r--r--src/jit/unwindamd64.cpp15
-rw-r--r--src/jit/utils.cpp24
-rw-r--r--src/jit/valuenum.cpp246
-rw-r--r--src/jit/valuenum.h59
-rw-r--r--src/jit/valuenumfuncs.h4
102 files changed, 18309 insertions, 11988 deletions
diff --git a/src/jit/CMakeLists.txt b/src/jit/CMakeLists.txt
index 6372e37852..96b8c496b9 100644
--- a/src/jit/CMakeLists.txt
+++ b/src/jit/CMakeLists.txt
@@ -7,9 +7,9 @@ include_directories("../inc")
# Enable the following for UNIX altjit on Windows
# add_definitions(-DALT_JIT)
-if (CLR_CMAKE_TARGET_ARCH_AMD64)
- add_definitions(-DFEATURE_SIMD)
- add_definitions(-DFEATURE_AVX_SUPPORT)
+if (CLR_CMAKE_TARGET_ARCH_AMD64 OR (CLR_CMAKE_TARGET_ARCH_I386 AND NOT CLR_CMAKE_PLATFORM_UNIX))
+ add_definitions(-DFEATURE_SIMD)
+ add_definitions(-DFEATURE_AVX_SUPPORT)
endif ()
@@ -23,6 +23,7 @@ set( JIT_SOURCES
bitset.cpp
block.cpp
codegencommon.cpp
+ codegenlinear.cpp
compiler.cpp
copyprop.cpp
disasm.cpp
@@ -194,19 +195,17 @@ endif()
add_custom_target(jit_exports DEPENDS ${JIT_EXPORTS_FILE})
-set(JIT_BASE_NAME clrjit)
-if (CLR_BUILD_JIT32)
- set(JIT_BASE_NAME ryujit)
-endif()
-
-if(WIN32)
- add_definitions(-DFX_VER_INTERNALNAME_STR=${JIT_BASE_NAME}.dll)
-endif(WIN32)
-
add_subdirectory(dll)
add_subdirectory(crossgen)
add_subdirectory(standalone)
-if (CLR_CMAKE_PLATFORM_ARCH_I386 OR CLR_CMAKE_PLATFORM_ARCH_ARM)
+if (CLR_CMAKE_PLATFORM_ARCH_ARM)
add_subdirectory(protojit)
-endif (CLR_CMAKE_PLATFORM_ARCH_I386 OR CLR_CMAKE_PLATFORM_ARCH_ARM)
+endif (CLR_CMAKE_PLATFORM_ARCH_ARM)
+
+if (CLR_CMAKE_PLATFORM_ARCH_I386)
+ add_subdirectory(legacyjit)
+ if (NOT CLR_BUILD_JIT32)
+ add_subdirectory(compatjit)
+ endif ()
+endif (CLR_CMAKE_PLATFORM_ARCH_I386)
diff --git a/src/jit/ICorJitInfo_API_names.h b/src/jit/ICorJitInfo_API_names.h
new file mode 100644
index 0000000000..601afbdfe1
--- /dev/null
+++ b/src/jit/ICorJitInfo_API_names.h
@@ -0,0 +1,171 @@
+// 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.
+
+DEF_CLR_API(getMethodAttribs)
+DEF_CLR_API(setMethodAttribs)
+DEF_CLR_API(getMethodSig)
+DEF_CLR_API(getMethodInfo)
+DEF_CLR_API(canInline)
+DEF_CLR_API(reportInliningDecision)
+DEF_CLR_API(canTailCall)
+DEF_CLR_API(reportTailCallDecision)
+DEF_CLR_API(getEHinfo)
+DEF_CLR_API(getMethodClass)
+DEF_CLR_API(getMethodModule)
+DEF_CLR_API(getMethodVTableOffset)
+DEF_CLR_API(getIntrinsicID)
+DEF_CLR_API(isInSIMDModule)
+DEF_CLR_API(getUnmanagedCallConv)
+DEF_CLR_API(pInvokeMarshalingRequired)
+DEF_CLR_API(satisfiesMethodConstraints)
+DEF_CLR_API(isCompatibleDelegate)
+DEF_CLR_API(isDelegateCreationAllowed)
+DEF_CLR_API(isInstantiationOfVerifiedGeneric)
+DEF_CLR_API(initConstraintsForVerification)
+DEF_CLR_API(canSkipMethodVerification)
+DEF_CLR_API(methodMustBeLoadedBeforeCodeIsRun)
+DEF_CLR_API(mapMethodDeclToMethodImpl)
+DEF_CLR_API(getGSCookie)
+DEF_CLR_API(resolveToken)
+DEF_CLR_API(tryResolveToken)
+DEF_CLR_API(findSig)
+DEF_CLR_API(findCallSiteSig)
+DEF_CLR_API(getTokenTypeAsHandle)
+DEF_CLR_API(canSkipVerification)
+DEF_CLR_API(isValidToken)
+DEF_CLR_API(isValidStringRef)
+DEF_CLR_API(shouldEnforceCallvirtRestriction)
+DEF_CLR_API(asCorInfoType)
+DEF_CLR_API(getClassName)
+DEF_CLR_API(appendClassName)
+DEF_CLR_API(isValueClass)
+DEF_CLR_API(canInlineTypeCheckWithObjectVTable)
+DEF_CLR_API(getClassAttribs)
+DEF_CLR_API(isStructRequiringStackAllocRetBuf)
+DEF_CLR_API(getClassModule)
+DEF_CLR_API(getModuleAssembly)
+DEF_CLR_API(getAssemblyName)
+DEF_CLR_API(LongLifetimeMalloc)
+DEF_CLR_API(LongLifetimeFree)
+DEF_CLR_API(getClassModuleIdForStatics)
+DEF_CLR_API(getClassSize)
+DEF_CLR_API(getClassAlignmentRequirement)
+DEF_CLR_API(getClassGClayout)
+DEF_CLR_API(getClassNumInstanceFields)
+DEF_CLR_API(getFieldInClass)
+DEF_CLR_API(checkMethodModifier)
+DEF_CLR_API(getNewHelper)
+DEF_CLR_API(getNewArrHelper)
+DEF_CLR_API(getCastingHelper)
+DEF_CLR_API(getSharedCCtorHelper)
+DEF_CLR_API(getSecurityPrologHelper)
+DEF_CLR_API(getTypeForBox)
+DEF_CLR_API(getBoxHelper)
+DEF_CLR_API(getUnBoxHelper)
+DEF_CLR_API(getReadyToRunHelper)
+DEF_CLR_API(getReadyToRunDelegateCtorHelper)
+DEF_CLR_API(getHelperName)
+DEF_CLR_API(initClass)
+DEF_CLR_API(classMustBeLoadedBeforeCodeIsRun)
+DEF_CLR_API(getBuiltinClass)
+DEF_CLR_API(getTypeForPrimitiveValueClass)
+DEF_CLR_API(canCast)
+DEF_CLR_API(areTypesEquivalent)
+DEF_CLR_API(mergeClasses)
+DEF_CLR_API(getParentType)
+DEF_CLR_API(getChildType)
+DEF_CLR_API(satisfiesClassConstraints)
+DEF_CLR_API(isSDArray)
+DEF_CLR_API(getArrayRank)
+DEF_CLR_API(getArrayInitializationData)
+DEF_CLR_API(canAccessClass)
+DEF_CLR_API(getFieldName)
+DEF_CLR_API(getFieldClass)
+DEF_CLR_API(getFieldType)
+DEF_CLR_API(getFieldOffset)
+DEF_CLR_API(isWriteBarrierHelperRequired)
+DEF_CLR_API(getFieldInfo)
+DEF_CLR_API(isFieldStatic)
+DEF_CLR_API(getBoundaries)
+DEF_CLR_API(setBoundaries)
+DEF_CLR_API(getVars)
+DEF_CLR_API(setVars)
+DEF_CLR_API(allocateArray)
+DEF_CLR_API(freeArray)
+DEF_CLR_API(getArgNext)
+DEF_CLR_API(getArgType)
+DEF_CLR_API(getArgClass)
+DEF_CLR_API(getHFAType)
+DEF_CLR_API(GetErrorHRESULT)
+DEF_CLR_API(GetErrorMessage)
+DEF_CLR_API(FilterException)
+DEF_CLR_API(HandleException)
+DEF_CLR_API(ThrowExceptionForJitResult)
+DEF_CLR_API(ThrowExceptionForHelper)
+DEF_CLR_API(getEEInfo)
+DEF_CLR_API(getJitTimeLogFilename)
+DEF_CLR_API(getMethodDefFromMethod)
+DEF_CLR_API(getMethodName)
+DEF_CLR_API(getMethodHash)
+DEF_CLR_API(findNameOfToken)
+DEF_CLR_API(getSystemVAmd64PassStructInRegisterDescriptor)
+DEF_CLR_API(getThreadTLSIndex)
+DEF_CLR_API(getInlinedCallFrameVptr)
+DEF_CLR_API(getAddrOfCaptureThreadGlobal)
+DEF_CLR_API(getAddrModuleDomainID)
+DEF_CLR_API(getHelperFtn)
+DEF_CLR_API(getFunctionEntryPoint)
+DEF_CLR_API(getFunctionFixedEntryPoint)
+DEF_CLR_API(getMethodSync)
+DEF_CLR_API(getLazyStringLiteralHelper)
+DEF_CLR_API(embedModuleHandle)
+DEF_CLR_API(embedClassHandle)
+DEF_CLR_API(embedMethodHandle)
+DEF_CLR_API(embedFieldHandle)
+DEF_CLR_API(embedGenericHandle)
+DEF_CLR_API(getLocationOfThisType)
+DEF_CLR_API(getPInvokeUnmanagedTarget)
+DEF_CLR_API(getAddressOfPInvokeFixup)
+DEF_CLR_API(getAddressOfPInvokeTarget)
+DEF_CLR_API(GetCookieForPInvokeCalliSig)
+DEF_CLR_API(canGetCookieForPInvokeCalliSig)
+DEF_CLR_API(getJustMyCodeHandle)
+DEF_CLR_API(GetProfilingHandle)
+DEF_CLR_API(getCallInfo)
+DEF_CLR_API(canAccessFamily)
+DEF_CLR_API(isRIDClassDomainID)
+DEF_CLR_API(getClassDomainID)
+DEF_CLR_API(getFieldAddress)
+DEF_CLR_API(getVarArgsHandle)
+DEF_CLR_API(canGetVarArgsHandle)
+DEF_CLR_API(constructStringLiteral)
+DEF_CLR_API(emptyStringLiteral)
+DEF_CLR_API(getFieldThreadLocalStoreID)
+DEF_CLR_API(setOverride)
+DEF_CLR_API(addActiveDependency)
+DEF_CLR_API(GetDelegateCtor)
+DEF_CLR_API(MethodCompileComplete)
+DEF_CLR_API(getTailCallCopyArgsThunk)
+DEF_CLR_API(getJitFlags)
+DEF_CLR_API(runWithErrorTrap)
+DEF_CLR_API(getMemoryManager)
+DEF_CLR_API(allocMem)
+DEF_CLR_API(reserveUnwindInfo)
+DEF_CLR_API(allocUnwindInfo)
+DEF_CLR_API(allocGCInfo)
+DEF_CLR_API(yieldExecution)
+DEF_CLR_API(setEHcount)
+DEF_CLR_API(setEHinfo)
+DEF_CLR_API(logMsg)
+DEF_CLR_API(doAssert)
+DEF_CLR_API(reportFatalError)
+DEF_CLR_API(allocBBProfileBuffer)
+DEF_CLR_API(getBBProfileData)
+DEF_CLR_API(recordCallSite)
+DEF_CLR_API(recordRelocation)
+DEF_CLR_API(getRelocTypeHint)
+DEF_CLR_API(getModuleNativeEntryPointRange)
+DEF_CLR_API(getExpectedTargetArchitecture)
+
+#undef DEF_CLR_API
diff --git a/src/jit/ICorJitInfo_API_wrapper.hpp b/src/jit/ICorJitInfo_API_wrapper.hpp
new file mode 100644
index 0000000000..4272b2755c
--- /dev/null
+++ b/src/jit/ICorJitInfo_API_wrapper.hpp
@@ -0,0 +1,1666 @@
+// 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.
+
+#define API_ENTER(name) wrapComp->CLR_API_Enter(API_##name);
+#define API_LEAVE(name) wrapComp->CLR_API_Leave(API_##name);
+
+/**********************************************************************************/
+// clang-format off
+/**********************************************************************************/
+//
+// ICorMethodInfo
+//
+
+DWORD WrapICorJitInfo::getMethodAttribs(CORINFO_METHOD_HANDLE ftn /* IN */)
+{
+ API_ENTER(getMethodAttribs)
+ DWORD temp = wrapHnd->getMethodAttribs(ftn);
+ API_LEAVE(getMethodAttribs)
+ return temp;
+}
+
+void WrapICorJitInfo::setMethodAttribs(CORINFO_METHOD_HANDLE ftn,/* IN */
+ CorInfoMethodRuntimeFlags attribs/* IN */)
+{
+ API_ENTER(setMethodAttribs);
+ wrapHnd->setMethodAttribs(ftn, attribs);
+ API_LEAVE(setMethodAttribs);
+}
+
+void WrapICorJitInfo::getMethodSig(CORINFO_METHOD_HANDLE ftn, /* IN */
+ CORINFO_SIG_INFO *sig, /* OUT */
+ CORINFO_CLASS_HANDLE memberParent/* IN */)
+{
+ API_ENTER(getMethodSig);
+ wrapHnd->getMethodSig(ftn, sig, memberParent);
+ API_LEAVE(getMethodSig);
+}
+
+bool WrapICorJitInfo::getMethodInfo(
+ CORINFO_METHOD_HANDLE ftn, /* IN */
+ CORINFO_METHOD_INFO* info /* OUT */)
+{
+ API_ENTER(getMethodInfo);
+ bool temp = wrapHnd->getMethodInfo(ftn, info);
+ API_LEAVE(getMethodInfo);
+ return temp;
+}
+
+CorInfoInline WrapICorJitInfo::canInline(
+ CORINFO_METHOD_HANDLE callerHnd, /* IN */
+ CORINFO_METHOD_HANDLE calleeHnd, /* IN */
+ DWORD* pRestrictions /* OUT */)
+{
+ API_ENTER(canInline);
+ CorInfoInline temp = wrapHnd->canInline(callerHnd, calleeHnd, pRestrictions);
+ API_LEAVE(canInline);
+ return temp;
+}
+
+void WrapICorJitInfo::reportInliningDecision(CORINFO_METHOD_HANDLE inlinerHnd,
+ CORINFO_METHOD_HANDLE inlineeHnd,
+ CorInfoInline inlineResult,
+ const char * reason)
+{
+ API_ENTER(reportInliningDecision);
+ wrapHnd->reportInliningDecision(inlinerHnd, inlineeHnd, inlineResult, reason);
+ API_LEAVE(reportInliningDecision);
+}
+
+bool WrapICorJitInfo::canTailCall(
+ CORINFO_METHOD_HANDLE callerHnd, /* IN */
+ CORINFO_METHOD_HANDLE declaredCalleeHnd, /* IN */
+ CORINFO_METHOD_HANDLE exactCalleeHnd, /* IN */
+ bool fIsTailPrefix /* IN */)
+{
+ API_ENTER(canTailCall);
+ bool temp = wrapHnd->canTailCall(callerHnd, declaredCalleeHnd, exactCalleeHnd, fIsTailPrefix);
+ API_LEAVE(canTailCall);
+ return temp;
+}
+
+void WrapICorJitInfo::reportTailCallDecision(CORINFO_METHOD_HANDLE callerHnd,
+ CORINFO_METHOD_HANDLE calleeHnd,
+ bool fIsTailPrefix,
+ CorInfoTailCall tailCallResult,
+ const char * reason)
+{
+ API_ENTER(reportTailCallDecision);
+ wrapHnd->reportTailCallDecision(callerHnd, calleeHnd, fIsTailPrefix, tailCallResult, reason);
+ API_LEAVE(reportTailCallDecision);
+}
+
+void WrapICorJitInfo::getEHinfo(
+ CORINFO_METHOD_HANDLE ftn, /* IN */
+ unsigned EHnumber, /* IN */
+ CORINFO_EH_CLAUSE* clause /* OUT */)
+{
+ API_ENTER(getEHinfo);
+ wrapHnd->getEHinfo(ftn, EHnumber, clause);
+ API_LEAVE(getEHinfo);
+}
+
+CORINFO_CLASS_HANDLE WrapICorJitInfo::getMethodClass(
+ CORINFO_METHOD_HANDLE method)
+{
+ API_ENTER(getMethodClass);
+ CORINFO_CLASS_HANDLE temp = wrapHnd->getMethodClass(method);
+ API_LEAVE(getMethodClass);
+ return temp;
+}
+
+CORINFO_MODULE_HANDLE WrapICorJitInfo::getMethodModule(
+ CORINFO_METHOD_HANDLE method)
+{
+ API_ENTER(getMethodModule);
+ CORINFO_MODULE_HANDLE temp = wrapHnd->getMethodModule(method);
+ API_LEAVE(getMethodModule);
+ return temp;
+}
+
+void WrapICorJitInfo::getMethodVTableOffset(
+ CORINFO_METHOD_HANDLE method, /* IN */
+ unsigned* offsetOfIndirection, /* OUT */
+ unsigned* offsetAfterIndirection /* OUT */)
+{
+ API_ENTER(getMethodVTableOffset);
+ wrapHnd->getMethodVTableOffset(method, offsetOfIndirection, offsetAfterIndirection);
+ API_LEAVE(getMethodVTableOffset);
+}
+
+#if COR_JIT_EE_VERSION > 460
+
+CorInfoIntrinsics WrapICorJitInfo::getIntrinsicID(
+ CORINFO_METHOD_HANDLE method,
+ bool* pMustExpand /* OUT */)
+{
+ API_ENTER(getIntrinsicID);
+ CorInfoIntrinsics temp = wrapHnd->getIntrinsicID(method, pMustExpand);
+ API_LEAVE(getIntrinsicID);
+ return temp;
+}
+
+#else
+
+CorInfoIntrinsics WrapICorJitInfo::getIntrinsicID(CORINFO_METHOD_HANDLE method)
+{
+ API_ENTER(getIntrinsicID);
+ CorInfoIntrinsics temp = wrapHnd->getIntrinsicID(method);
+ API_LEAVE(getIntrinsicID);
+ return temp;
+}
+
+#endif
+
+bool WrapICorJitInfo::isInSIMDModule(CORINFO_CLASS_HANDLE classHnd)
+{
+ API_ENTER(isInSIMDModule);
+ bool temp = wrapHnd->isInSIMDModule(classHnd);
+ API_LEAVE(isInSIMDModule);
+ return temp;
+}
+
+CorInfoUnmanagedCallConv WrapICorJitInfo::getUnmanagedCallConv(
+ CORINFO_METHOD_HANDLE method)
+{
+ API_ENTER(getUnmanagedCallConv);
+ CorInfoUnmanagedCallConv temp = wrapHnd->getUnmanagedCallConv(method);
+ API_LEAVE(getUnmanagedCallConv);
+ return temp;
+}
+
+BOOL WrapICorJitInfo::pInvokeMarshalingRequired(
+ CORINFO_METHOD_HANDLE method,
+ CORINFO_SIG_INFO* callSiteSig)
+{
+ API_ENTER(pInvokeMarshalingRequired);
+ BOOL temp = wrapHnd->pInvokeMarshalingRequired(method, callSiteSig);
+ API_LEAVE(pInvokeMarshalingRequired);
+ return temp;
+}
+
+BOOL WrapICorJitInfo::satisfiesMethodConstraints(
+ CORINFO_CLASS_HANDLE parent, // the exact parent of the method
+ CORINFO_METHOD_HANDLE method)
+{
+ API_ENTER(satisfiesMethodConstraints);
+ BOOL temp = wrapHnd->satisfiesMethodConstraints(parent, method);
+ API_LEAVE(satisfiesMethodConstraints);
+ return temp;
+}
+
+BOOL WrapICorJitInfo::isCompatibleDelegate(
+ CORINFO_CLASS_HANDLE objCls,
+ CORINFO_CLASS_HANDLE methodParentCls,
+ CORINFO_METHOD_HANDLE method,
+ CORINFO_CLASS_HANDLE delegateCls,
+ BOOL *pfIsOpenDelegate)
+{
+ API_ENTER(isCompatibleDelegate);
+ BOOL temp = wrapHnd->isCompatibleDelegate(objCls, methodParentCls, method, delegateCls, pfIsOpenDelegate);
+ API_LEAVE(isCompatibleDelegate);
+ return temp;
+}
+
+BOOL WrapICorJitInfo::isDelegateCreationAllowed(
+ CORINFO_CLASS_HANDLE delegateHnd,
+ CORINFO_METHOD_HANDLE calleeHnd)
+{
+ API_ENTER(isDelegateCreationAllowed);
+ BOOL temp = wrapHnd->isDelegateCreationAllowed(delegateHnd, calleeHnd);
+ API_LEAVE(isDelegateCreationAllowed);
+ return temp;
+}
+
+
+CorInfoInstantiationVerification WrapICorJitInfo::isInstantiationOfVerifiedGeneric(
+ CORINFO_METHOD_HANDLE method /* IN */)
+{
+ API_ENTER(isInstantiationOfVerifiedGeneric);
+ CorInfoInstantiationVerification temp = wrapHnd->isInstantiationOfVerifiedGeneric(method);
+ API_LEAVE(isInstantiationOfVerifiedGeneric);
+ return temp;
+}
+
+void WrapICorJitInfo::initConstraintsForVerification(
+ CORINFO_METHOD_HANDLE method, /* IN */
+ BOOL *pfHasCircularClassConstraints, /* OUT */
+ BOOL *pfHasCircularMethodConstraint /* OUT */)
+{
+ API_ENTER(initConstraintsForVerification);
+ wrapHnd->initConstraintsForVerification(method, pfHasCircularClassConstraints, pfHasCircularMethodConstraint);
+ API_LEAVE(initConstraintsForVerification);
+}
+
+CorInfoCanSkipVerificationResult WrapICorJitInfo::canSkipMethodVerification(
+ CORINFO_METHOD_HANDLE ftnHandle)
+{
+ API_ENTER(canSkipMethodVerification);
+ CorInfoCanSkipVerificationResult temp = wrapHnd->canSkipMethodVerification(ftnHandle);
+ API_LEAVE(canSkipMethodVerification);
+ return temp;
+}
+
+void WrapICorJitInfo::methodMustBeLoadedBeforeCodeIsRun(
+ CORINFO_METHOD_HANDLE method)
+{
+ API_ENTER(methodMustBeLoadedBeforeCodeIsRun);
+ wrapHnd->methodMustBeLoadedBeforeCodeIsRun(method);
+ API_LEAVE(methodMustBeLoadedBeforeCodeIsRun);
+}
+
+CORINFO_METHOD_HANDLE WrapICorJitInfo::mapMethodDeclToMethodImpl(
+ CORINFO_METHOD_HANDLE method)
+{
+ API_ENTER(mapMethodDeclToMethodImpl);
+ CORINFO_METHOD_HANDLE temp = wrapHnd->mapMethodDeclToMethodImpl(method);
+ API_LEAVE(mapMethodDeclToMethodImpl);
+ return temp;
+}
+
+void WrapICorJitInfo::getGSCookie(
+ GSCookie * pCookieVal,
+ GSCookie ** ppCookieVal )
+{
+ API_ENTER(getGSCookie);
+ wrapHnd->getGSCookie(pCookieVal, ppCookieVal);
+ API_LEAVE(getGSCookie);
+}
+
+/**********************************************************************************/
+//
+// ICorModuleInfo
+//
+/**********************************************************************************/
+
+void WrapICorJitInfo::resolveToken(/* IN, OUT */ CORINFO_RESOLVED_TOKEN * pResolvedToken)
+{
+ API_ENTER(resolveToken);
+ wrapHnd->resolveToken(pResolvedToken);
+ API_LEAVE(resolveToken);
+}
+
+#if COR_JIT_EE_VERSION > 460
+
+bool WrapICorJitInfo::tryResolveToken(/* IN, OUT */ CORINFO_RESOLVED_TOKEN * pResolvedToken)
+{
+ API_ENTER(tryResolveToken);
+ bool success = wrapHnd->tryResolveToken(pResolvedToken);
+ API_LEAVE(tryResolveToken);
+ return success;
+}
+
+#endif
+
+void WrapICorJitInfo::findSig(
+ CORINFO_MODULE_HANDLE module,
+ unsigned sigTOK,
+ CORINFO_CONTEXT_HANDLE context,
+ CORINFO_SIG_INFO *sig )
+{
+ API_ENTER(findSig);
+ wrapHnd->findSig(module, sigTOK, context, sig);
+ API_LEAVE(findSig);
+}
+
+void WrapICorJitInfo::findCallSiteSig(
+ CORINFO_MODULE_HANDLE module, /* IN */
+ unsigned methTOK, /* IN */
+ CORINFO_CONTEXT_HANDLE context, /* IN */
+ CORINFO_SIG_INFO *sig /* OUT */)
+{
+ API_ENTER(findCallSiteSig);
+ wrapHnd->findCallSiteSig(module, methTOK, context, sig);
+ API_LEAVE(findCallSiteSig);
+}
+
+CORINFO_CLASS_HANDLE WrapICorJitInfo::getTokenTypeAsHandle(
+ CORINFO_RESOLVED_TOKEN * pResolvedToken /* IN */)
+{
+ API_ENTER(getTokenTypeAsHandle);
+ CORINFO_CLASS_HANDLE temp = wrapHnd->getTokenTypeAsHandle(pResolvedToken);
+ API_LEAVE(getTokenTypeAsHandle);
+ return temp;
+}
+
+CorInfoCanSkipVerificationResult WrapICorJitInfo::canSkipVerification(
+ CORINFO_MODULE_HANDLE module /* IN */)
+{
+ API_ENTER(canSkipVerification);
+ CorInfoCanSkipVerificationResult temp = wrapHnd->canSkipVerification(module);
+ API_LEAVE(canSkipVerification);
+ return temp;
+}
+
+BOOL WrapICorJitInfo::isValidToken(
+ CORINFO_MODULE_HANDLE module, /* IN */
+ unsigned metaTOK /* IN */)
+{
+ API_ENTER(isValidToken);
+ BOOL result = wrapHnd->isValidToken(module, metaTOK);
+ API_LEAVE(isValidToken);
+ return result;
+}
+
+BOOL WrapICorJitInfo::isValidStringRef(
+ CORINFO_MODULE_HANDLE module, /* IN */
+ unsigned metaTOK /* IN */)
+{
+ API_ENTER(isValidStringRef);
+ BOOL temp = wrapHnd->isValidStringRef(module, metaTOK);
+ API_LEAVE(isValidStringRef);
+ return temp;
+}
+
+BOOL WrapICorJitInfo::shouldEnforceCallvirtRestriction(
+ CORINFO_MODULE_HANDLE scope)
+{
+ API_ENTER(shouldEnforceCallvirtRestriction);
+ BOOL temp = wrapHnd->shouldEnforceCallvirtRestriction(scope);
+ API_LEAVE(shouldEnforceCallvirtRestriction);
+ return temp;
+}
+
+/**********************************************************************************/
+//
+// ICorClassInfo
+//
+/**********************************************************************************/
+
+CorInfoType WrapICorJitInfo::asCorInfoType(CORINFO_CLASS_HANDLE cls)
+{
+ API_ENTER(asCorInfoType);
+ CorInfoType temp = wrapHnd->asCorInfoType(cls);
+ API_LEAVE(asCorInfoType);
+ return temp;
+}
+
+const char* WrapICorJitInfo::getClassName(CORINFO_CLASS_HANDLE cls)
+{
+ API_ENTER(getClassName);
+ const char* result = wrapHnd->getClassName(cls);
+ API_LEAVE(getClassName);
+ return result;
+}
+
+int WrapICorJitInfo::appendClassName(
+ __deref_inout_ecount(*pnBufLen) WCHAR** ppBuf,
+ int* pnBufLen,
+ CORINFO_CLASS_HANDLE cls,
+ BOOL fNamespace,
+ BOOL fFullInst,
+ BOOL fAssembly)
+{
+ API_ENTER(appendClassName);
+ WCHAR* pBuf = *ppBuf;
+ int nLen = wrapHnd->appendClassName(ppBuf, pnBufLen, cls, fNamespace, fFullInst, fAssembly);
+ API_LEAVE(appendClassName);
+ return nLen;
+}
+
+BOOL WrapICorJitInfo::isValueClass(CORINFO_CLASS_HANDLE cls)
+{
+ API_ENTER(isValueClass);
+ BOOL temp = wrapHnd->isValueClass(cls);
+ API_LEAVE(isValueClass);
+ return temp;
+}
+
+BOOL WrapICorJitInfo::canInlineTypeCheckWithObjectVTable(CORINFO_CLASS_HANDLE cls)
+{
+ API_ENTER(canInlineTypeCheckWithObjectVTable);
+ BOOL temp = wrapHnd->canInlineTypeCheckWithObjectVTable(cls);
+ API_LEAVE(canInlineTypeCheckWithObjectVTable);
+ return temp;
+}
+
+DWORD WrapICorJitInfo::getClassAttribs(
+ CORINFO_CLASS_HANDLE cls)
+{
+ API_ENTER(getClassAttribs);
+ DWORD temp = wrapHnd->getClassAttribs(cls);
+ API_LEAVE(getClassAttribs);
+ return temp;
+}
+
+BOOL WrapICorJitInfo::isStructRequiringStackAllocRetBuf(CORINFO_CLASS_HANDLE cls)
+{
+ API_ENTER(isStructRequiringStackAllocRetBuf);
+ BOOL temp = wrapHnd->isStructRequiringStackAllocRetBuf(cls);
+ API_LEAVE(isStructRequiringStackAllocRetBuf);
+ return temp;
+}
+
+CORINFO_MODULE_HANDLE WrapICorJitInfo::getClassModule(
+ CORINFO_CLASS_HANDLE cls)
+{
+ API_ENTER(getClassModule);
+ CORINFO_MODULE_HANDLE result = wrapHnd->getClassModule(cls);
+ API_LEAVE(getClassModule);
+ return result;
+}
+
+CORINFO_ASSEMBLY_HANDLE WrapICorJitInfo::getModuleAssembly(
+ CORINFO_MODULE_HANDLE mod)
+{
+ API_ENTER(getModuleAssembly);
+ CORINFO_ASSEMBLY_HANDLE result = wrapHnd->getModuleAssembly(mod);
+ API_LEAVE(getModuleAssembly);
+ return result;
+}
+
+const char* WrapICorJitInfo::getAssemblyName(
+ CORINFO_ASSEMBLY_HANDLE assem)
+{
+ API_ENTER(getAssemblyName);
+ const char* result = wrapHnd->getAssemblyName(assem);
+ API_LEAVE(getAssemblyName);
+ return result;
+}
+
+void* WrapICorJitInfo::LongLifetimeMalloc(size_t sz)
+{
+ API_ENTER(LongLifetimeMalloc);
+ void* result = wrapHnd->LongLifetimeMalloc(sz);
+ API_LEAVE(LongLifetimeMalloc);
+ return result;
+}
+
+void WrapICorJitInfo::LongLifetimeFree(void* obj)
+{
+ API_ENTER(LongLifetimeFree);
+ wrapHnd->LongLifetimeFree(obj);
+ API_LEAVE(LongLifetimeFree);
+}
+
+size_t WrapICorJitInfo::getClassModuleIdForStatics(
+ CORINFO_CLASS_HANDLE cls,
+ CORINFO_MODULE_HANDLE *pModule,
+ void **ppIndirection)
+{
+ API_ENTER(getClassModuleIdForStatics);
+ size_t temp = wrapHnd->getClassModuleIdForStatics(cls, pModule, ppIndirection);
+ API_LEAVE(getClassModuleIdForStatics);
+ return temp;
+}
+
+unsigned WrapICorJitInfo::getClassSize(CORINFO_CLASS_HANDLE cls)
+{
+ API_ENTER(getClassSize);
+ unsigned temp = wrapHnd->getClassSize(cls);
+ API_LEAVE(getClassSize);
+ return temp;
+}
+
+unsigned WrapICorJitInfo::getClassAlignmentRequirement(
+ CORINFO_CLASS_HANDLE cls,
+ BOOL fDoubleAlignHint)
+{
+ API_ENTER(getClassAlignmentRequirement);
+ unsigned temp = wrapHnd->getClassAlignmentRequirement(cls, fDoubleAlignHint);
+ API_LEAVE(getClassAlignmentRequirement);
+ return temp;
+}
+
+unsigned WrapICorJitInfo::getClassGClayout(
+ CORINFO_CLASS_HANDLE cls, /* IN */
+ BYTE *gcPtrs /* OUT */)
+{
+ API_ENTER(getClassGClayout);
+ unsigned temp = wrapHnd->getClassGClayout(cls, gcPtrs);
+ API_LEAVE(getClassGClayout);
+ return temp;
+}
+
+unsigned WrapICorJitInfo::getClassNumInstanceFields(
+ CORINFO_CLASS_HANDLE cls /* IN */)
+{
+ API_ENTER(getClassNumInstanceFields);
+ unsigned temp = wrapHnd->getClassNumInstanceFields(cls);
+ API_LEAVE(getClassNumInstanceFields);
+ return temp;
+}
+
+CORINFO_FIELD_HANDLE WrapICorJitInfo::getFieldInClass(
+ CORINFO_CLASS_HANDLE clsHnd,
+ INT num)
+{
+ API_ENTER(getFieldInClass);
+ CORINFO_FIELD_HANDLE temp = wrapHnd->getFieldInClass(clsHnd, num);
+ API_LEAVE(getFieldInClass);
+ return temp;
+}
+
+BOOL WrapICorJitInfo::checkMethodModifier(
+ CORINFO_METHOD_HANDLE hMethod,
+ LPCSTR modifier,
+ BOOL fOptional)
+{
+ API_ENTER(checkMethodModifier);
+ BOOL result = wrapHnd->checkMethodModifier(hMethod, modifier, fOptional);
+ API_LEAVE(checkMethodModifier);
+ return result;
+}
+
+CorInfoHelpFunc WrapICorJitInfo::getNewHelper(
+ CORINFO_RESOLVED_TOKEN * pResolvedToken,
+ CORINFO_METHOD_HANDLE callerHandle)
+{
+ API_ENTER(getNewHelper);
+ CorInfoHelpFunc temp = wrapHnd->getNewHelper(pResolvedToken, callerHandle);
+ API_LEAVE(getNewHelper);
+ return temp;
+}
+
+CorInfoHelpFunc WrapICorJitInfo::getNewArrHelper(
+ CORINFO_CLASS_HANDLE arrayCls)
+{
+ API_ENTER(getNewArrHelper);
+ CorInfoHelpFunc temp = wrapHnd->getNewArrHelper(arrayCls);
+ API_LEAVE(getNewArrHelper);
+ return temp;
+}
+
+CorInfoHelpFunc WrapICorJitInfo::getCastingHelper(
+ CORINFO_RESOLVED_TOKEN * pResolvedToken,
+ bool fThrowing)
+{
+ API_ENTER(getCastingHelper);
+ CorInfoHelpFunc temp = wrapHnd->getCastingHelper(pResolvedToken, fThrowing);
+ API_LEAVE(getCastingHelper);
+ return temp;
+}
+
+CorInfoHelpFunc WrapICorJitInfo::getSharedCCtorHelper(
+ CORINFO_CLASS_HANDLE clsHnd)
+{
+ API_ENTER(getSharedCCtorHelper);
+ CorInfoHelpFunc temp = wrapHnd->getSharedCCtorHelper(clsHnd);
+ API_LEAVE(getSharedCCtorHelper);
+ return temp;
+}
+
+CorInfoHelpFunc WrapICorJitInfo::getSecurityPrologHelper(
+ CORINFO_METHOD_HANDLE ftn)
+{
+ API_ENTER(getSecurityPrologHelper);
+ CorInfoHelpFunc temp = wrapHnd->getSecurityPrologHelper(ftn);
+ API_LEAVE(getSecurityPrologHelper);
+ return temp;
+}
+
+CORINFO_CLASS_HANDLE WrapICorJitInfo::getTypeForBox(
+ CORINFO_CLASS_HANDLE cls)
+{
+ API_ENTER(getTypeForBox);
+ CORINFO_CLASS_HANDLE temp = wrapHnd->getTypeForBox(cls);
+ API_LEAVE(getTypeForBox);
+ return temp;
+}
+
+CorInfoHelpFunc WrapICorJitInfo::getBoxHelper(
+ CORINFO_CLASS_HANDLE cls)
+{
+ API_ENTER(getBoxHelper);
+ CorInfoHelpFunc temp = wrapHnd->getBoxHelper(cls);
+ API_LEAVE(getBoxHelper);
+ return temp;
+}
+
+CorInfoHelpFunc WrapICorJitInfo::getUnBoxHelper(
+ CORINFO_CLASS_HANDLE cls)
+{
+ API_ENTER(getUnBoxHelper);
+ CorInfoHelpFunc temp = wrapHnd->getUnBoxHelper(cls);
+ API_LEAVE(getUnBoxHelper);
+ return temp;
+}
+
+#if COR_JIT_EE_VERSION > 460
+
+bool WrapICorJitInfo::getReadyToRunHelper(
+ CORINFO_RESOLVED_TOKEN * pResolvedToken,
+ CORINFO_LOOKUP_KIND * pGenericLookupKind,
+ CorInfoHelpFunc id,
+ CORINFO_CONST_LOOKUP * pLookup)
+{
+ API_ENTER(getReadyToRunHelper);
+ bool result = wrapHnd->getReadyToRunHelper(pResolvedToken, pGenericLookupKind, id, pLookup);
+ API_LEAVE(getReadyToRunHelper);
+ return result;
+}
+
+void WrapICorJitInfo::getReadyToRunDelegateCtorHelper(
+ CORINFO_RESOLVED_TOKEN * pTargetMethod,
+ CORINFO_CLASS_HANDLE delegateType,
+ CORINFO_CONST_LOOKUP * pLookup)
+{
+ API_ENTER(getReadyToRunDelegateCtorHelper);
+ wrapHnd->getReadyToRunDelegateCtorHelper(pTargetMethod, delegateType, pLookup);
+ API_LEAVE(getReadyToRunDelegateCtorHelper);
+}
+
+#else
+
+void WrapICorJitInfo::getReadyToRunHelper(
+ CORINFO_RESOLVED_TOKEN * pResolvedToken,
+ CorInfoHelpFunc id,
+ CORINFO_CONST_LOOKUP * pLookup)
+{
+ API_ENTER(getReadyToRunHelper);
+ wrapHnd->getReadyToRunHelper(pResolvedToken, id, pLookup);
+ API_LEAVE(getReadyToRunHelper);
+}
+
+#endif
+
+const char* WrapICorJitInfo::getHelperName(
+ CorInfoHelpFunc funcNum)
+{
+ API_ENTER(getHelperName);
+ const char* temp = wrapHnd->getHelperName(funcNum);
+ API_LEAVE(getHelperName);
+ return temp;
+}
+
+CorInfoInitClassResult WrapICorJitInfo::initClass(
+ CORINFO_FIELD_HANDLE field,
+
+ CORINFO_METHOD_HANDLE method,
+ CORINFO_CONTEXT_HANDLE context,
+ BOOL speculative)
+{
+ API_ENTER(initClass);
+ CorInfoInitClassResult temp = wrapHnd->initClass(field, method, context, speculative);
+ API_LEAVE(initClass);
+ return temp;
+}
+
+void WrapICorJitInfo::classMustBeLoadedBeforeCodeIsRun(
+ CORINFO_CLASS_HANDLE cls)
+{
+ API_ENTER(classMustBeLoadedBeforeCodeIsRun);
+ wrapHnd->classMustBeLoadedBeforeCodeIsRun(cls);
+ API_LEAVE(classMustBeLoadedBeforeCodeIsRun);
+}
+
+CORINFO_CLASS_HANDLE WrapICorJitInfo::getBuiltinClass(
+ CorInfoClassId classId)
+{
+ API_ENTER(getBuiltinClass);
+ CORINFO_CLASS_HANDLE temp = wrapHnd->getBuiltinClass(classId);
+ API_LEAVE(getBuiltinClass);
+ return temp;
+}
+
+CorInfoType WrapICorJitInfo::getTypeForPrimitiveValueClass(
+ CORINFO_CLASS_HANDLE cls)
+{
+ API_ENTER(getTypeForPrimitiveValueClass);
+ CorInfoType temp = wrapHnd->getTypeForPrimitiveValueClass(cls);
+ API_LEAVE(getTypeForPrimitiveValueClass);
+ return temp;
+}
+
+BOOL WrapICorJitInfo::canCast(
+ CORINFO_CLASS_HANDLE child,
+ CORINFO_CLASS_HANDLE parent )
+{
+ API_ENTER(canCast);
+ BOOL temp = wrapHnd->canCast(child, parent);
+ API_LEAVE(canCast);
+ return temp;
+}
+
+BOOL WrapICorJitInfo::areTypesEquivalent(
+ CORINFO_CLASS_HANDLE cls1,
+ CORINFO_CLASS_HANDLE cls2)
+{
+ API_ENTER(areTypesEquivalent);
+ BOOL temp = wrapHnd->areTypesEquivalent(cls1, cls2);
+ API_LEAVE(areTypesEquivalent);
+ return temp;
+}
+
+CORINFO_CLASS_HANDLE WrapICorJitInfo::mergeClasses(
+ CORINFO_CLASS_HANDLE cls1,
+ CORINFO_CLASS_HANDLE cls2)
+{
+ API_ENTER(mergeClasses);
+ CORINFO_CLASS_HANDLE temp = wrapHnd->mergeClasses(cls1, cls2);
+ API_LEAVE(mergeClasses);
+ return temp;
+}
+
+CORINFO_CLASS_HANDLE WrapICorJitInfo::getParentType(
+ CORINFO_CLASS_HANDLE cls)
+{
+ API_ENTER(getParentType);
+ CORINFO_CLASS_HANDLE temp = wrapHnd->getParentType(cls);
+ API_LEAVE(getParentType);
+ return temp;
+}
+
+CorInfoType WrapICorJitInfo::getChildType(
+ CORINFO_CLASS_HANDLE clsHnd,
+ CORINFO_CLASS_HANDLE *clsRet)
+{
+ API_ENTER(getChildType);
+ CorInfoType temp = wrapHnd->getChildType(clsHnd, clsRet);
+ API_LEAVE(getChildType);
+ return temp;
+}
+
+BOOL WrapICorJitInfo::satisfiesClassConstraints(
+ CORINFO_CLASS_HANDLE cls)
+{
+ API_ENTER(satisfiesClassConstraints);
+ BOOL temp = wrapHnd->satisfiesClassConstraints(cls);
+ API_LEAVE(satisfiesClassConstraints);
+ return temp;
+
+}
+
+BOOL WrapICorJitInfo::isSDArray(
+ CORINFO_CLASS_HANDLE cls)
+{
+ API_ENTER(isSDArray);
+ BOOL temp = wrapHnd->isSDArray(cls);
+ API_LEAVE(isSDArray);
+ return temp;
+}
+
+unsigned WrapICorJitInfo::getArrayRank(
+ CORINFO_CLASS_HANDLE cls)
+{
+ API_ENTER(getArrayRank);
+ unsigned result = wrapHnd->getArrayRank(cls);
+ API_LEAVE(getArrayRank);
+ return result;
+}
+
+void * WrapICorJitInfo::getArrayInitializationData(
+ CORINFO_FIELD_HANDLE field,
+ DWORD size)
+{
+ API_ENTER(getArrayInitializationData);
+ void *temp = wrapHnd->getArrayInitializationData(field, size);
+ API_LEAVE(getArrayInitializationData);
+ return temp;
+}
+
+CorInfoIsAccessAllowedResult WrapICorJitInfo::canAccessClass(
+ CORINFO_RESOLVED_TOKEN * pResolvedToken,
+ CORINFO_METHOD_HANDLE callerHandle,
+ CORINFO_HELPER_DESC *pAccessHelper)
+{
+ API_ENTER(canAccessClass);
+ CorInfoIsAccessAllowedResult temp = wrapHnd->canAccessClass(pResolvedToken, callerHandle, pAccessHelper);
+ API_LEAVE(canAccessClass);
+ return temp;
+}
+
+/**********************************************************************************/
+//
+// ICorFieldInfo
+//
+/**********************************************************************************/
+
+const char* WrapICorJitInfo::getFieldName(
+ CORINFO_FIELD_HANDLE ftn, /* IN */
+ const char **moduleName /* OUT */)
+{
+ API_ENTER(getFieldName);
+ const char* temp = wrapHnd->getFieldName(ftn, moduleName);
+ API_LEAVE(getFieldName);
+ return temp;
+}
+
+CORINFO_CLASS_HANDLE WrapICorJitInfo::getFieldClass(
+ CORINFO_FIELD_HANDLE field)
+{
+ API_ENTER(getFieldClass);
+ CORINFO_CLASS_HANDLE temp = wrapHnd->getFieldClass(field);
+ API_LEAVE(getFieldClass);
+ return temp;
+}
+
+CorInfoType WrapICorJitInfo::getFieldType(
+ CORINFO_FIELD_HANDLE field,
+ CORINFO_CLASS_HANDLE *structType,
+ CORINFO_CLASS_HANDLE memberParent/* IN */)
+{
+ API_ENTER(getFieldType);
+ CorInfoType temp = wrapHnd->getFieldType(field, structType, memberParent);
+ API_LEAVE(getFieldType);
+ return temp;
+}
+
+unsigned WrapICorJitInfo::getFieldOffset(
+ CORINFO_FIELD_HANDLE field)
+{
+ API_ENTER(getFieldOffset);
+ unsigned temp = wrapHnd->getFieldOffset(field);
+ API_LEAVE(getFieldOffset);
+ return temp;
+}
+
+bool WrapICorJitInfo::isWriteBarrierHelperRequired(
+ CORINFO_FIELD_HANDLE field)
+{
+ API_ENTER(isWriteBarrierHelperRequired);
+ bool result = wrapHnd->isWriteBarrierHelperRequired(field);
+ API_LEAVE(isWriteBarrierHelperRequired);
+ return result;
+}
+
+void WrapICorJitInfo::getFieldInfo(CORINFO_RESOLVED_TOKEN * pResolvedToken,
+ CORINFO_METHOD_HANDLE callerHandle,
+ CORINFO_ACCESS_FLAGS flags,
+ CORINFO_FIELD_INFO *pResult)
+{
+ API_ENTER(getFieldInfo);
+ wrapHnd->getFieldInfo(pResolvedToken, callerHandle, flags, pResult);
+ API_LEAVE(getFieldInfo);
+}
+
+bool WrapICorJitInfo::isFieldStatic(CORINFO_FIELD_HANDLE fldHnd)
+{
+ API_ENTER(isFieldStatic);
+ bool result = wrapHnd->isFieldStatic(fldHnd);
+ API_LEAVE(isFieldStatic);
+ return result;
+}
+
+/*********************************************************************************/
+//
+// ICorDebugInfo
+//
+/*********************************************************************************/
+
+void WrapICorJitInfo::getBoundaries(
+ CORINFO_METHOD_HANDLE ftn,
+ unsigned int *cILOffsets,
+ DWORD **pILOffsets,
+
+ ICorDebugInfo::BoundaryTypes *implictBoundaries)
+{
+ API_ENTER(getBoundaries);
+ wrapHnd->getBoundaries(ftn, cILOffsets, pILOffsets, implictBoundaries);
+ API_LEAVE(getBoundaries);
+}
+
+void WrapICorJitInfo::setBoundaries(
+ CORINFO_METHOD_HANDLE ftn,
+ ULONG32 cMap,
+ ICorDebugInfo::OffsetMapping *pMap)
+{
+ API_ENTER(setBoundaries);
+ wrapHnd->setBoundaries(ftn, cMap, pMap);
+ API_LEAVE(setBoundaries);
+}
+
+void WrapICorJitInfo::getVars(
+ CORINFO_METHOD_HANDLE ftn,
+ ULONG32 *cVars,
+ ICorDebugInfo::ILVarInfo **vars,
+ bool *extendOthers)
+
+{
+ API_ENTER(getVars);
+ wrapHnd->getVars(ftn, cVars, vars, extendOthers);
+ API_LEAVE(getVars);
+}
+
+void WrapICorJitInfo::setVars(
+ CORINFO_METHOD_HANDLE ftn,
+ ULONG32 cVars,
+ ICorDebugInfo::NativeVarInfo *vars)
+
+{
+ API_ENTER(setVars);
+ wrapHnd->setVars(ftn, cVars, vars);
+ API_LEAVE(setVars);
+}
+
+void * WrapICorJitInfo::allocateArray(
+ ULONG cBytes)
+{
+ API_ENTER(allocateArray);
+ void *temp = wrapHnd->allocateArray(cBytes);
+ API_LEAVE(allocateArray);
+ return temp;
+}
+
+void WrapICorJitInfo::freeArray(
+ void *array)
+{
+ API_ENTER(freeArray);
+ wrapHnd->freeArray(array);
+ API_LEAVE(freeArray);
+}
+
+/*********************************************************************************/
+//
+// ICorArgInfo
+//
+/*********************************************************************************/
+
+CORINFO_ARG_LIST_HANDLE WrapICorJitInfo::getArgNext(
+ CORINFO_ARG_LIST_HANDLE args /* IN */)
+{
+ API_ENTER(getArgNext);
+ CORINFO_ARG_LIST_HANDLE temp = wrapHnd->getArgNext(args);
+ API_LEAVE(getArgNext);
+ return temp;
+}
+
+CorInfoTypeWithMod WrapICorJitInfo::getArgType(
+ CORINFO_SIG_INFO* sig, /* IN */
+ CORINFO_ARG_LIST_HANDLE args, /* IN */
+ CORINFO_CLASS_HANDLE *vcTypeRet /* OUT */)
+{
+ API_ENTER(getArgType);
+ CorInfoTypeWithMod temp = wrapHnd->getArgType(sig, args, vcTypeRet);
+ API_LEAVE(getArgType);
+ return temp;
+}
+
+CORINFO_CLASS_HANDLE WrapICorJitInfo::getArgClass(
+ CORINFO_SIG_INFO* sig, /* IN */
+ CORINFO_ARG_LIST_HANDLE args /* IN */)
+{
+ API_ENTER(getArgClass);
+ CORINFO_CLASS_HANDLE temp = wrapHnd->getArgClass(sig, args);
+ API_LEAVE(getArgClass);
+ return temp;
+}
+
+CorInfoType WrapICorJitInfo::getHFAType(
+ CORINFO_CLASS_HANDLE hClass)
+{
+ API_ENTER(getHFAType);
+ CorInfoType temp = wrapHnd->getHFAType(hClass);
+ API_LEAVE(getHFAType);
+ return temp;
+}
+
+HRESULT WrapICorJitInfo::GetErrorHRESULT(
+ struct _EXCEPTION_POINTERS *pExceptionPointers)
+{
+ API_ENTER(GetErrorHRESULT);
+ HRESULT temp = wrapHnd->GetErrorHRESULT(pExceptionPointers);
+ API_LEAVE(GetErrorHRESULT);
+ return temp;
+}
+
+ULONG WrapICorJitInfo::GetErrorMessage(
+ __inout_ecount(bufferLength) LPWSTR buffer,
+ ULONG bufferLength)
+{
+ API_ENTER(GetErrorMessage);
+ ULONG temp = wrapHnd->GetErrorMessage(buffer, bufferLength);
+ API_LEAVE(GetErrorMessage);
+ return temp;
+}
+
+int WrapICorJitInfo::FilterException(
+ struct _EXCEPTION_POINTERS *pExceptionPointers)
+{
+ API_ENTER(FilterException);
+ int temp = wrapHnd->FilterException(pExceptionPointers);
+ API_LEAVE(FilterException);
+ return temp;
+}
+
+void WrapICorJitInfo::HandleException(
+ struct _EXCEPTION_POINTERS *pExceptionPointers)
+{
+ API_ENTER(HandleException);
+ wrapHnd->HandleException(pExceptionPointers);
+ API_LEAVE(HandleException);
+}
+
+void WrapICorJitInfo::ThrowExceptionForJitResult(
+ HRESULT result)
+{
+ API_ENTER(ThrowExceptionForJitResult);
+ wrapHnd->ThrowExceptionForJitResult(result);
+ API_LEAVE(ThrowExceptionForJitResult);
+}
+
+void WrapICorJitInfo::ThrowExceptionForHelper(
+ const CORINFO_HELPER_DESC * throwHelper)
+{
+ API_ENTER(ThrowExceptionForHelper);
+ wrapHnd->ThrowExceptionForHelper(throwHelper);
+ API_LEAVE(ThrowExceptionForHelper);
+}
+
+void WrapICorJitInfo::getEEInfo(
+ CORINFO_EE_INFO *pEEInfoOut)
+{
+ API_ENTER(getEEInfo);
+ wrapHnd->getEEInfo(pEEInfoOut);
+ API_LEAVE(getEEInfo);
+}
+
+LPCWSTR WrapICorJitInfo::getJitTimeLogFilename()
+{
+ API_ENTER(getJitTimeLogFilename);
+ LPCWSTR temp = wrapHnd->getJitTimeLogFilename();
+ API_LEAVE(getJitTimeLogFilename);
+ return temp;
+}
+
+mdMethodDef WrapICorJitInfo::getMethodDefFromMethod(
+ CORINFO_METHOD_HANDLE hMethod)
+{
+ API_ENTER(getMethodDefFromMethod);
+ mdMethodDef result = wrapHnd->getMethodDefFromMethod(hMethod);
+ API_LEAVE(getMethodDefFromMethod);
+ return result;
+}
+
+const char* WrapICorJitInfo::getMethodName(
+ CORINFO_METHOD_HANDLE ftn, /* IN */
+ const char **moduleName /* OUT */)
+{
+ API_ENTER(getMethodName);
+ const char* temp = wrapHnd->getMethodName(ftn, moduleName);
+ API_LEAVE(getMethodName);
+ return temp;
+}
+
+unsigned WrapICorJitInfo::getMethodHash(
+ CORINFO_METHOD_HANDLE ftn /* IN */)
+{
+ API_ENTER(getMethodHash);
+ unsigned temp = wrapHnd->getMethodHash(ftn);
+ API_LEAVE(getMethodHash);
+ return temp;
+}
+
+size_t WrapICorJitInfo::findNameOfToken(
+ CORINFO_MODULE_HANDLE module, /* IN */
+ mdToken metaTOK, /* IN */
+ __out_ecount(FQNameCapacity) char * szFQName, /* OUT */
+ size_t FQNameCapacity /* IN */)
+{
+ API_ENTER(findNameOfToken);
+ size_t result = wrapHnd->findNameOfToken(module, metaTOK, szFQName, FQNameCapacity);
+ API_LEAVE(findNameOfToken);
+ return result;
+}
+
+#if COR_JIT_EE_VERSION > 460
+
+bool WrapICorJitInfo::getSystemVAmd64PassStructInRegisterDescriptor(
+ /* IN */ CORINFO_CLASS_HANDLE structHnd,
+ /* OUT */ SYSTEMV_AMD64_CORINFO_STRUCT_REG_PASSING_DESCRIPTOR* structPassInRegDescPtr)
+{
+ API_ENTER(getSystemVAmd64PassStructInRegisterDescriptor);
+ bool result = wrapHnd->getSystemVAmd64PassStructInRegisterDescriptor(structHnd, structPassInRegDescPtr);
+ API_LEAVE(getSystemVAmd64PassStructInRegisterDescriptor);
+ return result;
+}
+
+#endif
+
+DWORD WrapICorJitInfo::getThreadTLSIndex(
+ void **ppIndirection)
+{
+ API_ENTER(getThreadTLSIndex);
+ DWORD temp = wrapHnd->getThreadTLSIndex(ppIndirection);
+ API_LEAVE(getThreadTLSIndex);
+ return temp;
+}
+
+const void * WrapICorJitInfo::getInlinedCallFrameVptr(
+ void **ppIndirection)
+{
+ API_ENTER(getInlinedCallFrameVptr);
+ const void* temp = wrapHnd->getInlinedCallFrameVptr(ppIndirection);
+ API_LEAVE(getInlinedCallFrameVptr);
+ return temp;
+}
+
+LONG * WrapICorJitInfo::getAddrOfCaptureThreadGlobal(
+ void **ppIndirection)
+{
+ API_ENTER(getAddrOfCaptureThreadGlobal);
+ LONG * temp = wrapHnd->getAddrOfCaptureThreadGlobal(ppIndirection);
+ API_LEAVE(getAddrOfCaptureThreadGlobal);
+ return temp;
+}
+
+SIZE_T* WrapICorJitInfo::getAddrModuleDomainID(CORINFO_MODULE_HANDLE module)
+{
+ API_ENTER(getAddrModuleDomainID);
+ SIZE_T* result = wrapHnd->getAddrModuleDomainID(module);
+ API_LEAVE(getAddrModuleDomainID);
+ return result;
+}
+
+void* WrapICorJitInfo::getHelperFtn(
+ CorInfoHelpFunc ftnNum,
+ void **ppIndirection)
+{
+ API_ENTER(getHelperFtn);
+ void *temp = wrapHnd->getHelperFtn(ftnNum, ppIndirection);
+ API_LEAVE(getHelperFtn);
+ return temp;
+}
+
+void WrapICorJitInfo::getFunctionEntryPoint(
+ CORINFO_METHOD_HANDLE ftn, /* IN */
+ CORINFO_CONST_LOOKUP * pResult, /* OUT */
+ CORINFO_ACCESS_FLAGS accessFlags)
+{
+ API_ENTER(getFunctionEntryPoint);
+ wrapHnd->getFunctionEntryPoint(ftn, pResult, accessFlags);
+ API_LEAVE(getFunctionEntryPoint);
+}
+
+void WrapICorJitInfo::getFunctionFixedEntryPoint(
+ CORINFO_METHOD_HANDLE ftn,
+ CORINFO_CONST_LOOKUP * pResult)
+{
+ API_ENTER(getFunctionFixedEntryPoint);
+ wrapHnd->getFunctionFixedEntryPoint(ftn, pResult);
+ API_LEAVE(getFunctionFixedEntryPoint);
+}
+
+void* WrapICorJitInfo::getMethodSync(
+ CORINFO_METHOD_HANDLE ftn,
+ void **ppIndirection)
+{
+ API_ENTER(getMethodSync);
+ void *temp = wrapHnd->getMethodSync(ftn, ppIndirection);
+ API_LEAVE(getMethodSync);
+ return temp;
+}
+
+
+CorInfoHelpFunc WrapICorJitInfo::getLazyStringLiteralHelper(
+ CORINFO_MODULE_HANDLE handle)
+{
+ API_ENTER(getLazyStringLiteralHelper);
+ CorInfoHelpFunc temp = wrapHnd->getLazyStringLiteralHelper(handle);
+ API_LEAVE(getLazyStringLiteralHelper);
+ return temp;
+}
+
+CORINFO_MODULE_HANDLE WrapICorJitInfo::embedModuleHandle(
+ CORINFO_MODULE_HANDLE handle,
+ void **ppIndirection)
+{
+ API_ENTER(embedModuleHandle);
+ CORINFO_MODULE_HANDLE temp = wrapHnd->embedModuleHandle(handle, ppIndirection);
+ API_LEAVE(embedModuleHandle);
+ return temp;
+}
+
+CORINFO_CLASS_HANDLE WrapICorJitInfo::embedClassHandle(
+ CORINFO_CLASS_HANDLE handle,
+ void **ppIndirection)
+{
+ API_ENTER(embedClassHandle);
+ CORINFO_CLASS_HANDLE temp = wrapHnd->embedClassHandle(handle, ppIndirection);
+ API_LEAVE(embedClassHandle);
+ return temp;
+}
+
+CORINFO_METHOD_HANDLE WrapICorJitInfo::embedMethodHandle(
+ CORINFO_METHOD_HANDLE handle,
+ void **ppIndirection)
+{
+ API_ENTER(embedMethodHandle);
+ CORINFO_METHOD_HANDLE temp = wrapHnd->embedMethodHandle(handle, ppIndirection);
+ API_LEAVE(embedMethodHandle);
+ return temp;
+}
+
+CORINFO_FIELD_HANDLE WrapICorJitInfo::embedFieldHandle(
+ CORINFO_FIELD_HANDLE handle,
+ void **ppIndirection)
+{
+ API_ENTER(embedFieldHandle);
+ CORINFO_FIELD_HANDLE temp = wrapHnd->embedFieldHandle(handle, ppIndirection);
+ API_LEAVE(embedFieldHandle);
+ return temp;
+}
+
+void WrapICorJitInfo::embedGenericHandle(
+ CORINFO_RESOLVED_TOKEN * pResolvedToken,
+ BOOL fEmbedParent,
+ CORINFO_GENERICHANDLE_RESULT * pResult)
+{
+ API_ENTER(embedGenericHandle);
+ wrapHnd->embedGenericHandle(pResolvedToken, fEmbedParent, pResult);
+ API_LEAVE(embedGenericHandle);
+}
+
+CORINFO_LOOKUP_KIND WrapICorJitInfo::getLocationOfThisType(
+ CORINFO_METHOD_HANDLE context)
+{
+ API_ENTER(getLocationOfThisType);
+ CORINFO_LOOKUP_KIND temp = wrapHnd->getLocationOfThisType(context);
+ API_LEAVE(getLocationOfThisType);
+ return temp;
+}
+
+void* WrapICorJitInfo::getPInvokeUnmanagedTarget(
+ CORINFO_METHOD_HANDLE method,
+ void **ppIndirection)
+{
+ API_ENTER(getPInvokeUnmanagedTarget);
+ void *result = wrapHnd->getPInvokeUnmanagedTarget(method, ppIndirection);
+ API_LEAVE(getPInvokeUnmanagedTarget);
+ return result;
+}
+
+void* WrapICorJitInfo::getAddressOfPInvokeFixup(
+ CORINFO_METHOD_HANDLE method,
+ void **ppIndirection)
+{
+ API_ENTER(getAddressOfPInvokeFixup);
+ void *temp = wrapHnd->getAddressOfPInvokeFixup(method, ppIndirection);
+ API_LEAVE(getAddressOfPInvokeFixup);
+ return temp;
+}
+
+#if COR_JIT_EE_VERSION > 460
+
+void WrapICorJitInfo::getAddressOfPInvokeTarget(
+ CORINFO_METHOD_HANDLE method,
+ CORINFO_CONST_LOOKUP *pLookup)
+{
+ API_ENTER(getAddressOfPInvokeTarget);
+ wrapHnd->getAddressOfPInvokeTarget(method, pLookup);
+ API_LEAVE(getAddressOfPInvokeTarget);
+}
+
+#endif
+
+LPVOID WrapICorJitInfo::GetCookieForPInvokeCalliSig(
+ CORINFO_SIG_INFO* szMetaSig,
+ void ** ppIndirection)
+{
+ API_ENTER(GetCookieForPInvokeCalliSig);
+ LPVOID temp = wrapHnd->GetCookieForPInvokeCalliSig(szMetaSig, ppIndirection);
+ API_LEAVE(GetCookieForPInvokeCalliSig);
+ return temp;
+}
+
+bool WrapICorJitInfo::canGetCookieForPInvokeCalliSig(
+ CORINFO_SIG_INFO* szMetaSig)
+{
+ API_ENTER(canGetCookieForPInvokeCalliSig);
+ bool temp = wrapHnd->canGetCookieForPInvokeCalliSig(szMetaSig);
+ API_LEAVE(canGetCookieForPInvokeCalliSig);
+ return temp;
+}
+
+CORINFO_JUST_MY_CODE_HANDLE WrapICorJitInfo::getJustMyCodeHandle(
+ CORINFO_METHOD_HANDLE method,
+ CORINFO_JUST_MY_CODE_HANDLE**ppIndirection)
+{
+ API_ENTER(getJustMyCodeHandle);
+ CORINFO_JUST_MY_CODE_HANDLE temp = wrapHnd->getJustMyCodeHandle(method, ppIndirection);
+ API_LEAVE(getJustMyCodeHandle);
+ return temp;
+}
+
+void WrapICorJitInfo::GetProfilingHandle(
+ BOOL *pbHookFunction,
+ void **pProfilerHandle,
+ BOOL *pbIndirectedHandles)
+{
+ API_ENTER(GetProfilingHandle);
+ wrapHnd->GetProfilingHandle(pbHookFunction, pProfilerHandle, pbIndirectedHandles);
+ API_LEAVE(GetProfilingHandle);
+}
+
+void WrapICorJitInfo::getCallInfo(
+ CORINFO_RESOLVED_TOKEN * pResolvedToken,
+ CORINFO_RESOLVED_TOKEN * pConstrainedResolvedToken,
+ CORINFO_METHOD_HANDLE callerHandle,
+ CORINFO_CALLINFO_FLAGS flags,
+ CORINFO_CALL_INFO *pResult)
+{
+ API_ENTER(getCallInfo);
+ wrapHnd->getCallInfo(pResolvedToken, pConstrainedResolvedToken, callerHandle, flags, pResult);
+ API_LEAVE(getCallInfo);
+}
+
+BOOL WrapICorJitInfo::canAccessFamily(CORINFO_METHOD_HANDLE hCaller,
+ CORINFO_CLASS_HANDLE hInstanceType)
+{
+ API_ENTER(canAccessFamily);
+ BOOL temp = wrapHnd->canAccessFamily(hCaller, hInstanceType);
+ API_LEAVE(canAccessFamily);
+ return temp;
+}
+
+BOOL WrapICorJitInfo::isRIDClassDomainID(CORINFO_CLASS_HANDLE cls)
+{
+ API_ENTER(isRIDClassDomainID);
+ BOOL result = wrapHnd->isRIDClassDomainID(cls);
+ API_LEAVE(isRIDClassDomainID);
+ return result;
+}
+
+unsigned WrapICorJitInfo::getClassDomainID(
+ CORINFO_CLASS_HANDLE cls,
+ void **ppIndirection)
+{
+ API_ENTER(getClassDomainID);
+ unsigned temp = wrapHnd->getClassDomainID(cls, ppIndirection);
+ API_LEAVE(getClassDomainID);
+ return temp;
+}
+
+void* WrapICorJitInfo::getFieldAddress(
+ CORINFO_FIELD_HANDLE field,
+ void **ppIndirection)
+{
+ API_ENTER(getFieldAddress);
+ void *temp = wrapHnd->getFieldAddress(field, ppIndirection);
+ API_LEAVE(getFieldAddress);
+ return temp;
+}
+
+CORINFO_VARARGS_HANDLE WrapICorJitInfo::getVarArgsHandle(
+ CORINFO_SIG_INFO *pSig,
+ void **ppIndirection)
+{
+ API_ENTER(getVarArgsHandle);
+ CORINFO_VARARGS_HANDLE temp = wrapHnd->getVarArgsHandle(pSig, ppIndirection);
+ API_LEAVE(getVarArgsHandle);
+ return temp;
+}
+
+bool WrapICorJitInfo::canGetVarArgsHandle(
+ CORINFO_SIG_INFO *pSig)
+{
+ API_ENTER(canGetVarArgsHandle);
+ bool temp = wrapHnd->canGetVarArgsHandle(pSig);
+ API_LEAVE(canGetVarArgsHandle);
+ return temp;
+}
+
+InfoAccessType WrapICorJitInfo::constructStringLiteral(
+ CORINFO_MODULE_HANDLE module,
+ mdToken metaTok,
+ void **ppValue)
+{
+ API_ENTER(constructStringLiteral);
+ InfoAccessType temp = wrapHnd->constructStringLiteral(module, metaTok, ppValue);
+ API_LEAVE(constructStringLiteral);
+ return temp;
+}
+
+InfoAccessType WrapICorJitInfo::emptyStringLiteral(void **ppValue)
+{
+ API_ENTER(emptyStringLiteral);
+ InfoAccessType temp = wrapHnd->emptyStringLiteral(ppValue);
+ API_LEAVE(emptyStringLiteral);
+ return temp;
+}
+
+DWORD WrapICorJitInfo::getFieldThreadLocalStoreID(
+ CORINFO_FIELD_HANDLE field,
+ void **ppIndirection)
+{
+ API_ENTER(getFieldThreadLocalStoreID);
+ DWORD temp = wrapHnd->getFieldThreadLocalStoreID(field, ppIndirection);
+ API_LEAVE(getFieldThreadLocalStoreID);
+ return temp;
+}
+
+void WrapICorJitInfo::setOverride(
+ ICorDynamicInfo *pOverride,
+ CORINFO_METHOD_HANDLE currentMethod)
+{
+ API_ENTER(setOverride);
+ wrapHnd->setOverride(pOverride, currentMethod);
+ API_LEAVE(setOverride);
+}
+
+void WrapICorJitInfo::addActiveDependency(
+ CORINFO_MODULE_HANDLE moduleFrom,
+ CORINFO_MODULE_HANDLE moduleTo)
+{
+ API_ENTER(addActiveDependency);
+ wrapHnd->addActiveDependency(moduleFrom, moduleTo);
+ API_LEAVE(addActiveDependency);
+}
+
+CORINFO_METHOD_HANDLE WrapICorJitInfo::GetDelegateCtor(
+ CORINFO_METHOD_HANDLE methHnd,
+ CORINFO_CLASS_HANDLE clsHnd,
+ CORINFO_METHOD_HANDLE targetMethodHnd,
+ DelegateCtorArgs * pCtorData)
+{
+ API_ENTER(GetDelegateCtor);
+ CORINFO_METHOD_HANDLE temp = wrapHnd->GetDelegateCtor(methHnd, clsHnd, targetMethodHnd, pCtorData);
+ API_LEAVE(GetDelegateCtor);
+ return temp;
+}
+
+void WrapICorJitInfo::MethodCompileComplete(
+ CORINFO_METHOD_HANDLE methHnd)
+{
+ API_ENTER(MethodCompileComplete);
+ wrapHnd->MethodCompileComplete(methHnd);
+ API_LEAVE(MethodCompileComplete);
+}
+
+void* WrapICorJitInfo::getTailCallCopyArgsThunk(
+ CORINFO_SIG_INFO *pSig,
+ CorInfoHelperTailCallSpecialHandling flags)
+{
+ API_ENTER(getTailCallCopyArgsThunk);
+ void *result = wrapHnd->getTailCallCopyArgsThunk(pSig, flags);
+ API_LEAVE(getTailCallCopyArgsThunk);
+ return result;
+}
+
+/*********************************************************************************/
+//
+// ICorJitInfo
+//
+/*********************************************************************************/
+
+#if COR_JIT_EE_VERSION > 460
+
+DWORD WrapICorJitInfo::getJitFlags(CORJIT_FLAGS *jitFlags, DWORD sizeInBytes)
+{
+ API_ENTER(getJitFlags);
+ DWORD result = wrapHnd->getJitFlags(jitFlags, sizeInBytes);
+ API_LEAVE(getJitFlags);
+ return result;
+}
+
+bool WrapICorJitInfo::runWithErrorTrap(void(*function)(void*), void *param)
+{
+ return wrapHnd->runWithErrorTrap(function, param);
+}
+
+#endif
+
+IEEMemoryManager* WrapICorJitInfo::getMemoryManager()
+{
+ API_ENTER(getMemoryManager);
+ IEEMemoryManager * temp = wrapHnd->getMemoryManager();
+ API_LEAVE(getMemoryManager);
+ return temp;
+}
+
+void WrapICorJitInfo::allocMem(
+ ULONG hotCodeSize, /* IN */
+ ULONG coldCodeSize, /* IN */
+ ULONG roDataSize, /* IN */
+ ULONG xcptnsCount, /* IN */
+ CorJitAllocMemFlag flag, /* IN */
+ void ** hotCodeBlock, /* OUT */
+ void ** coldCodeBlock, /* OUT */
+ void ** roDataBlock /* OUT */)
+{
+ API_ENTER(allocMem);
+ wrapHnd->allocMem(hotCodeSize, coldCodeSize, roDataSize, xcptnsCount, flag, hotCodeBlock, coldCodeBlock, roDataBlock);
+ API_LEAVE(allocMem);
+}
+
+void WrapICorJitInfo::reserveUnwindInfo(
+ BOOL isFunclet, /* IN */
+ BOOL isColdCode, /* IN */
+ ULONG unwindSize /* IN */)
+{
+ API_ENTER(reserveUnwindInfo);
+ wrapHnd->reserveUnwindInfo(isFunclet, isColdCode, unwindSize);
+ API_LEAVE(reserveUnwindInfo);
+}
+
+void WrapICorJitInfo::allocUnwindInfo(
+ BYTE * pHotCode, /* IN */
+ BYTE * pColdCode, /* IN */
+ ULONG startOffset, /* IN */
+ ULONG endOffset, /* IN */
+ ULONG unwindSize, /* IN */
+ BYTE * pUnwindBlock, /* IN */
+ CorJitFuncKind funcKind /* IN */)
+{
+ API_ENTER(allocUnwindInfo);
+ wrapHnd->allocUnwindInfo(pHotCode, pColdCode, startOffset, endOffset, unwindSize, pUnwindBlock, funcKind);
+ API_LEAVE(allocUnwindInfo);
+}
+
+void *WrapICorJitInfo::allocGCInfo(size_t size /* IN */)
+{
+ API_ENTER(allocGCInfo);
+ void *temp = wrapHnd->allocGCInfo(size);
+ API_LEAVE(allocGCInfo);
+ return temp;
+}
+
+void WrapICorJitInfo::yieldExecution()
+{
+ API_ENTER(yieldExecution); //Nothing to record
+ wrapHnd->yieldExecution();
+ API_LEAVE(yieldExecution); //Nothing to recor)
+}
+
+void WrapICorJitInfo::setEHcount(unsigned cEH /* IN */)
+{
+ API_ENTER(setEHcount);
+ wrapHnd->setEHcount(cEH);
+ API_LEAVE(setEHcount);
+}
+
+void WrapICorJitInfo::setEHinfo(
+ unsigned EHnumber, /* IN */
+ const CORINFO_EH_CLAUSE *clause /* IN */)
+{
+ API_ENTER(setEHinfo);
+ wrapHnd->setEHinfo(EHnumber, clause);
+ API_LEAVE(setEHinfo);
+}
+
+BOOL WrapICorJitInfo::logMsg(unsigned level, const char* fmt, va_list args)
+{
+ API_ENTER(logMsg);
+ BOOL result = wrapHnd->logMsg(level, fmt, args);
+ API_LEAVE(logMsg);
+ return result;
+}
+
+int WrapICorJitInfo::doAssert(const char* szFile, int iLine, const char* szExpr)
+{
+ API_ENTER(doAssert);
+ int result = wrapHnd->doAssert(szFile, iLine, szExpr);
+ API_LEAVE(doAssert);
+ return result;
+}
+
+void WrapICorJitInfo::reportFatalError(CorJitResult result)
+{
+ API_ENTER(reportFatalError);
+ wrapHnd->reportFatalError(result);
+ API_LEAVE(reportFatalError);
+}
+
+HRESULT WrapICorJitInfo::allocBBProfileBuffer(
+ ULONG count,
+ ProfileBuffer **profileBuffer)
+{
+ API_ENTER(allocBBProfileBuffer);
+ HRESULT result = wrapHnd->allocBBProfileBuffer(count, profileBuffer);
+ API_LEAVE(allocBBProfileBuffer);
+ return result;
+}
+
+HRESULT WrapICorJitInfo::getBBProfileData(
+ CORINFO_METHOD_HANDLE ftnHnd,
+ ULONG *count,
+ ProfileBuffer **profileBuffer,
+ ULONG *numRuns)
+{
+ API_ENTER(getBBProfileData);
+ HRESULT temp = wrapHnd->getBBProfileData(ftnHnd, count, profileBuffer, numRuns);
+ API_LEAVE(getBBProfileData);
+ return temp;
+}
+
+void WrapICorJitInfo::recordCallSite(
+ ULONG instrOffset, /* IN */
+ CORINFO_SIG_INFO * callSig, /* IN */
+ CORINFO_METHOD_HANDLE methodHandle /* IN */)
+{
+ API_ENTER(recordCallSite);
+ wrapHnd->recordCallSite(instrOffset, callSig, methodHandle);
+ API_LEAVE(recordCallSite);
+}
+
+void WrapICorJitInfo::recordRelocation(
+ void *location, /* IN */
+ void *target, /* IN */
+ WORD fRelocType, /* IN */
+ WORD slotNum, /* IN */
+ INT32 addlDelta /* IN */)
+{
+ API_ENTER(recordRelocation);
+ wrapHnd->recordRelocation(location, target, fRelocType, slotNum, addlDelta);
+ API_LEAVE(recordRelocation);
+}
+
+WORD WrapICorJitInfo::getRelocTypeHint(void *target)
+{
+ API_ENTER(getRelocTypeHint);
+ WORD result = wrapHnd->getRelocTypeHint(target);
+ API_LEAVE(getRelocTypeHint);
+ return result;
+}
+
+void WrapICorJitInfo::getModuleNativeEntryPointRange(
+ void **pStart, /* OUT */
+ void **pEnd /* OUT */)
+{
+ API_ENTER(getModuleNativeEntryPointRange);
+ wrapHnd->getModuleNativeEntryPointRange(pStart, pEnd);
+ API_LEAVE(getModuleNativeEntryPointRange);
+}
+
+DWORD WrapICorJitInfo::getExpectedTargetArchitecture()
+{
+ API_ENTER(getExpectedTargetArchitecture);
+ DWORD result = wrapHnd->getExpectedTargetArchitecture();
+ API_LEAVE(getExpectedTargetArchitecture);
+ return result;
+}
+
+/**********************************************************************************/
+// clang-format on
+/**********************************************************************************/
diff --git a/src/jit/assertionprop.cpp b/src/jit/assertionprop.cpp
index fe35c3b780..cb0832fe47 100644
--- a/src/jit/assertionprop.cpp
+++ b/src/jit/assertionprop.cpp
@@ -1100,11 +1100,6 @@ Compiler::AssertionIndex Compiler::optCreateAssertion(GenTreePtr op1,
CNS_COMMON:
{
- // TODO-1stClassStructs: handle constant propagation to struct types.
- if (varTypeIsStruct(lclVar))
- {
- goto DONE_ASSERTION;
- }
//
// Must either be an OAK_EQUAL or an OAK_NOT_EQUAL assertion
//
@@ -2034,12 +2029,7 @@ void Compiler::optAssertionGen(GenTreePtr tree)
{
case GT_ASG:
// VN takes care of non local assertions for assignments and data flow.
- // TODO-1stClassStructs: Enable assertion prop for struct types.
- if (varTypeIsStruct(tree))
- {
- // Do nothing.
- }
- else if (optLocalAssertionProp)
+ if (optLocalAssertionProp)
{
assertionIndex = optCreateAssertion(tree->gtOp.gtOp1, tree->gtOp.gtOp2, OAK_EQUAL);
}
@@ -2052,26 +2042,15 @@ void Compiler::optAssertionGen(GenTreePtr tree)
case GT_OBJ:
case GT_BLK:
case GT_DYN_BLK:
- // TODO-1stClassStructs: These should always be considered to create a non-null
- // assertion, but previously, when these indirections were implicit due to a block
- // copy or init, they were not being considered to do so.
- break;
case GT_IND:
- // TODO-1stClassStructs: All indirections should be considered to create a non-null
- // assertion, but previously, when these indirections were implicit due to a block
- // copy or init, they were not being considered to do so.
- if (tree->gtType == TYP_STRUCT)
- {
- GenTree* parent = tree->gtGetParent(nullptr);
- if ((parent != nullptr) && (parent->gtOper == GT_ASG))
- {
- break;
- }
- }
case GT_NULLCHECK:
+ // All indirections create non-null assertions
+ assertionIndex = optCreateAssertion(tree->AsIndir()->Addr(), nullptr, OAK_NOT_EQUAL);
+ break;
+
case GT_ARR_LENGTH:
- // An array length can create a non-null assertion
- assertionIndex = optCreateAssertion(tree->gtOp.gtOp1, nullptr, OAK_NOT_EQUAL);
+ // An array length is an indirection (but doesn't derive from GenTreeIndir).
+ assertionIndex = optCreateAssertion(tree->AsArrLen()->ArrRef(), nullptr, OAK_NOT_EQUAL);
break;
case GT_ARR_BOUNDS_CHECK:
@@ -2629,9 +2608,29 @@ GenTreePtr Compiler::optConstantAssertionProp(AssertionDsc* curAssertion,
else
{
bool isArrIndex = ((tree->gtFlags & GTF_VAR_ARR_INDEX) != 0);
- newTree->ChangeOperConst(GT_CNS_INT);
- newTree->gtIntCon.gtIconVal = curAssertion->op2.u1.iconVal;
- newTree->ClearIconHandleMask();
+ // If we have done constant propagation of a struct type, it is only valid for zero-init,
+ // and we have to ensure that we have the right zero for the type.
+ if (varTypeIsStruct(tree))
+ {
+ assert(curAssertion->op2.u1.iconVal == 0);
+ }
+#ifdef FEATURE_SIMD
+ if (varTypeIsSIMD(tree))
+ {
+ var_types simdType = tree->TypeGet();
+ tree->ChangeOperConst(GT_CNS_DBL);
+ GenTree* initVal = tree;
+ initVal->gtType = TYP_FLOAT;
+ newTree =
+ gtNewSIMDNode(simdType, initVal, nullptr, SIMDIntrinsicInit, TYP_FLOAT, genTypeSize(simdType));
+ }
+ else
+#endif // FEATURE_SIMD
+ {
+ newTree->ChangeOperConst(GT_CNS_INT);
+ newTree->gtIntCon.gtIconVal = curAssertion->op2.u1.iconVal;
+ newTree->ClearIconHandleMask();
+ }
// If we're doing an array index address, assume any constant propagated contributes to the index.
if (isArrIndex)
{
@@ -3421,32 +3420,13 @@ GenTreePtr Compiler::optAssertionProp_Ind(ASSERT_VALARG_TP assertions, const Gen
{
assert(tree->OperIsIndir());
- // TODO-1stClassStructs: All indirections should be handled here, but
- // previously, when these indirections were GT_OBJ, or implicit due to a block
- // copy or init, they were not being handled.
- if (tree->TypeGet() == TYP_STRUCT)
- {
- if (tree->OperIsBlk())
- {
- return nullptr;
- }
- else
- {
- GenTree* parent = tree->gtGetParent(nullptr);
- if ((parent != nullptr) && parent->OperIsBlkOp())
- {
- return nullptr;
- }
- }
- }
-
if (!(tree->gtFlags & GTF_EXCEPT))
{
return nullptr;
}
// Check for add of a constant.
- GenTreePtr op1 = tree->gtOp.gtOp1;
+ GenTreePtr op1 = tree->AsIndir()->Addr();
if ((op1->gtOper == GT_ADD) && (op1->gtOp.gtOp2->gtOper == GT_CNS_INT))
{
op1 = op1->gtOp.gtOp1;
@@ -3700,6 +3680,21 @@ GenTreePtr Compiler::optAssertionProp_BndsChk(ASSERT_VALARG_TP assertions, const
assert(tree->gtOper == GT_ARR_BOUNDS_CHECK);
+#ifdef FEATURE_ENABLE_NO_RANGE_CHECKS
+ if (JitConfig.JitNoRangeChks())
+ {
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("\nFlagging check redundant due to JitNoRangeChks in BB%02u:\n", compCurBB->bbNum);
+ gtDispTree(tree, nullptr, nullptr, true);
+ }
+#endif // DEBUG
+ tree->gtFlags |= GTF_ARR_BOUND_INBND;
+ return nullptr;
+ }
+#endif // FEATURE_ENABLE_NO_RANGE_CHECKS
+
BitVecOps::Iter iter(apTraits, assertions);
unsigned index = 0;
while (iter.NextElem(apTraits, &index))
@@ -4688,9 +4683,8 @@ GenTreePtr Compiler::optVNConstantPropOnJTrue(BasicBlock* block, GenTreePtr stmt
newStmt = fgInsertStmtNearEnd(block, sideEffList);
sideEffList = nullptr;
}
- fgMorphBlockStmt(block, newStmt DEBUGARG(__FUNCTION__));
- gtSetStmtInfo(newStmt);
- fgSetStmtSeq(newStmt);
+
+ fgMorphBlockStmt(block, newStmt->AsStmt() DEBUGARG(__FUNCTION__));
}
// Transform the relop's operands to be both zeroes.
@@ -4748,7 +4742,6 @@ Compiler::fgWalkResult Compiler::optVNConstantPropCurStmt(BasicBlock* block, Gen
case GT_MOD:
case GT_UDIV:
case GT_UMOD:
- case GT_MULHI:
case GT_EQ:
case GT_NE:
case GT_LT:
@@ -4767,6 +4760,10 @@ Compiler::fgWalkResult Compiler::optVNConstantPropCurStmt(BasicBlock* block, Gen
case GT_INTRINSIC:
break;
+ case GT_MULHI:
+ assert(false && "Unexpected GT_MULHI node encountered before lowering");
+ break;
+
case GT_JTRUE:
break;
@@ -4911,9 +4908,7 @@ GenTreePtr Compiler::optVNAssertionPropCurStmt(BasicBlock* block, GenTreePtr stm
if (optAssertionPropagatedCurrentStmt)
{
- fgMorphBlockStmt(block, stmt DEBUGARG("optVNAssertionPropCurStmt"));
- gtSetStmtInfo(stmt);
- fgSetStmtSeq(stmt);
+ fgMorphBlockStmt(block, stmt->AsStmt() DEBUGARG("optVNAssertionPropCurStmt"));
}
// Check if propagation removed statements starting from current stmt.
@@ -5110,13 +5105,7 @@ void Compiler::optAssertionPropMain()
}
#endif
// Re-morph the statement.
- fgMorphBlockStmt(block, stmt DEBUGARG("optAssertionPropMain"));
-
- // Recalculate the gtCostSz, etc...
- gtSetStmtInfo(stmt);
-
- // Re-thread the nodes
- fgSetStmtSeq(stmt);
+ fgMorphBlockStmt(block, stmt->AsStmt() DEBUGARG("optAssertionPropMain"));
}
// Check if propagation removed statements starting from current stmt.
diff --git a/src/jit/bitsetasuint64.h b/src/jit/bitsetasuint64.h
index 150f7e9d61..243e9e33b4 100644
--- a/src/jit/bitsetasuint64.h
+++ b/src/jit/bitsetasuint64.h
@@ -167,7 +167,7 @@ public:
{
IAllocator* alloc = BitSetTraits::GetDebugOnlyAllocator(env);
const int CharsForUINT64 = sizeof(UINT64) * 2;
- char* res = NULL;
+ char* res = nullptr;
const int AllocSize = CharsForUINT64 + 4;
res = (char*)alloc->Alloc(AllocSize);
UINT64 bits = bs;
diff --git a/src/jit/block.cpp b/src/jit/block.cpp
index 2d37754ec5..47f1052cc8 100644
--- a/src/jit/block.cpp
+++ b/src/jit/block.cpp
@@ -554,7 +554,9 @@ void BasicBlock::dspBlockHeader(Compiler* compiler,
}
if (showFlags)
{
- printf(" flags=0x%08x: ", bbFlags);
+ const unsigned lowFlags = (unsigned)bbFlags;
+ const unsigned highFlags = (unsigned)(bbFlags >> 32);
+ printf(" flags=0x%08x.%08x: ", highFlags, lowFlags);
dspFlags();
}
printf("\n");
@@ -568,7 +570,25 @@ void* BasicBlock::HeapPhiArg::operator new(size_t sz, Compiler* comp)
return comp->compGetMem(sz, CMK_HeapPhiArg);
}
-void BasicBlock::CloneBlockState(Compiler* compiler, BasicBlock* to, const BasicBlock* from)
+//------------------------------------------------------------------------
+// CloneBlockState: Try to populate `to` block with a copy of `from` block's statements, replacing
+// uses of local `varNum` with IntCns `varVal`.
+//
+// Arguments:
+// compiler - Jit compiler instance
+// to - New/empty block to copy statements into
+// from - Block to copy statements from
+// varNum - lclVar uses with lclNum `varNum` will be replaced; can be ~0 to indicate no replacement.
+// varVal - If replacing uses of `varNum`, replace them with int constants with value `varVal`.
+//
+// Return Value:
+// Cloning may fail because this routine uses `gtCloneExpr` for cloning and it can't handle all
+// IR nodes. If cloning of any statement fails, `false` will be returned and block `to` may be
+// partially populated. If cloning of all statements succeeds, `true` will be returned and
+// block `to` will be fully populated.
+
+bool BasicBlock::CloneBlockState(
+ Compiler* compiler, BasicBlock* to, const BasicBlock* from, unsigned varNum, int varVal)
{
assert(to->bbTreeList == nullptr);
@@ -595,9 +615,17 @@ void BasicBlock::CloneBlockState(Compiler* compiler, BasicBlock* to, const Basic
for (GenTreePtr fromStmt = from->bbTreeList; fromStmt != nullptr; fromStmt = fromStmt->gtNext)
{
- compiler->fgInsertStmtAtEnd(to,
- compiler->fgNewStmtFromTree(compiler->gtCloneExpr(fromStmt->gtStmt.gtStmtExpr)));
+ auto newExpr = compiler->gtCloneExpr(fromStmt->gtStmt.gtStmtExpr, 0, varNum, varVal);
+ if (!newExpr)
+ {
+ // gtCloneExpr doesn't handle all opcodes, so may fail to clone a statement.
+ // When that happens, it returns nullptr; abandon the rest of this block and
+ // return `false` to the caller to indicate that cloning was unsuccessful.
+ return false;
+ }
+ compiler->fgInsertStmtAtEnd(to, compiler->fgNewStmtFromTree(newExpr));
}
+ return true;
}
// LIR helpers
@@ -667,7 +695,6 @@ GenTreeStmt* BasicBlock::lastStmt()
return result->AsStmt();
}
-
//------------------------------------------------------------------------
// BasicBlock::firstNode: Returns the first node in the block.
//
diff --git a/src/jit/block.h b/src/jit/block.h
index ecfbb620a1..99c0efc1a7 100644
--- a/src/jit/block.h
+++ b/src/jit/block.h
@@ -30,17 +30,13 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
#include "simplerhash.h"
/*****************************************************************************/
-
+typedef BitVec EXPSET_TP;
#if LARGE_EXPSET
-typedef unsigned __int64 EXPSET_TP;
#define EXPSET_SZ 64
#else
-typedef unsigned int EXPSET_TP;
#define EXPSET_SZ 32
#endif
-#define EXPSET_ALL ((EXPSET_TP)0 - 1)
-
typedef BitVec ASSERT_TP;
typedef BitVec_ValArg_T ASSERT_VALARG_TP;
typedef BitVec_ValRet_T ASSERT_VALRET_TP;
@@ -291,14 +287,14 @@ struct BasicBlock : private LIR::Range
}
}
+ unsigned __int64 bbFlags; // see BBF_xxxx below
+
unsigned bbNum; // the block's number
unsigned bbPostOrderNum; // the block's post order number in the graph.
unsigned bbRefs; // number of blocks that can reach here, either by fall-through or a branch. If this falls to zero,
// the block is unreachable.
- unsigned bbFlags; // see BBF_xxxx below
-
#define BBF_VISITED 0x00000001 // BB visited during optimizations
#define BBF_MARKED 0x00000002 // BB marked during optimizations
#define BBF_CHANGED 0x00000004 // input/output of this block has changed
@@ -357,6 +353,10 @@ struct BasicBlock : private LIR::Range
// BBJ_CALLFINALLY block, as well as, on x86, the final step block out of a
// finally.
+// Flags that relate blocks to loop structure.
+
+#define BBF_LOOP_FLAGS (BBF_LOOP_PREHEADER | BBF_LOOP_HEAD | BBF_LOOP_CALL0 | BBF_LOOP_CALL1)
+
bool isRunRarely()
{
return ((bbFlags & BBF_RUN_RARELY) != 0);
@@ -860,9 +860,7 @@ struct BasicBlock : private LIR::Range
unsigned bbHeapSsaNumIn; // The SSA # of "Heap" on entry to the block.
unsigned bbHeapSsaNumOut; // The SSA # of "Heap" on exit from the block.
-#ifdef DEBUGGING_SUPPORT
VARSET_TP bbScope; // variables in scope over the block
-#endif
void InitVarSets(class Compiler* comp);
@@ -1094,9 +1092,11 @@ public:
return AllSuccs(comp, this);
}
- // Clone block state and statements from 'from' block to 'to' block.
- // Assumes that "to" is an empty block.
- static void CloneBlockState(Compiler* compiler, BasicBlock* to, const BasicBlock* from);
+ // Try to clone block state and statements from `from` block to `to` block (which must be new/empty),
+ // optionally replacing uses of local `varNum` with IntCns `varVal`. Return true if all statements
+ // in the block are cloned successfully, false (with partially-populated `to` block) if one fails.
+ static bool CloneBlockState(
+ Compiler* compiler, BasicBlock* to, const BasicBlock* from, unsigned varNum = (unsigned)-1, int varVal = 0);
void MakeLIR(GenTree* firstNode, GenTree* lastNode);
bool IsLIR();
diff --git a/src/jit/codegen.h b/src/jit/codegen.h
index 0c4a311186..c6e38ab6af 100755
--- a/src/jit/codegen.h
+++ b/src/jit/codegen.h
@@ -48,7 +48,6 @@ public:
unsigned* cnsPtr,
bool nogen = false);
-
private:
#if defined(_TARGET_XARCH_) && !FEATURE_STACK_FP_X87
// Bit masks used in negating a float or double number.
@@ -123,7 +122,7 @@ private:
void genRangeCheck(GenTree* node);
- void genLockedInstructions(GenTree* node);
+ void genLockedInstructions(GenTreeOp* node);
//-------------------------------------------------------------------------
// Register-related methods
@@ -251,6 +250,8 @@ protected:
void genAdjustSP(ssize_t delta);
+ void genAdjustStackLevel(BasicBlock* block);
+
void genExitCode(BasicBlock* block);
//-------------------------------------------------------------------------
@@ -488,15 +489,26 @@ protected:
void genAmd64EmitterUnitTests();
#endif
-//-------------------------------------------------------------------------
-//
-// End prolog/epilog generation
-//
-//-------------------------------------------------------------------------
+ //-------------------------------------------------------------------------
+ //
+ // End prolog/epilog generation
+ //
+ //-------------------------------------------------------------------------
-/*****************************************************************************/
-#ifdef DEBUGGING_SUPPORT
-/*****************************************************************************/
+ void genSinglePush();
+ void genSinglePop();
+ regMaskTP genPushRegs(regMaskTP regs, regMaskTP* byrefRegs, regMaskTP* noRefRegs);
+ void genPopRegs(regMaskTP regs, regMaskTP byrefRegs, regMaskTP noRefRegs);
+
+/*
+XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+XX XX
+XX Debugging Support XX
+XX XX
+XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+*/
#ifdef DEBUG
void genIPmappingDisp(unsigned mappingNum, Compiler::IPmappingDsc* ipMapping);
@@ -730,10 +742,6 @@ protected:
unsigned genTrnslLocalVarCount;
#endif
-/*****************************************************************************/
-#endif // DEBUGGING_SUPPORT
-/*****************************************************************************/
-
#ifndef LEGACY_BACKEND
#include "codegenlinear.h"
#else // LEGACY_BACKEND
diff --git a/src/jit/codegenarm.cpp b/src/jit/codegenarm.cpp
index 4ce82307f9..73e51f2ef7 100644
--- a/src/jit/codegenarm.cpp
+++ b/src/jit/codegenarm.cpp
@@ -27,102 +27,6 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
#include "gcinfoencoder.h"
#endif
-// Get the register assigned to the given node
-
-regNumber CodeGenInterface::genGetAssignedReg(GenTreePtr tree)
-{
- return tree->gtRegNum;
-}
-
-//------------------------------------------------------------------------
-// genSpillVar: Spill a local variable
-//
-// Arguments:
-// tree - the lclVar node for the variable being spilled
-//
-// Return Value:
-// None.
-//
-// Assumptions:
-// The lclVar must be a register candidate (lvRegCandidate)
-
-void CodeGen::genSpillVar(GenTreePtr tree)
-{
- regMaskTP regMask;
- unsigned varNum = tree->gtLclVarCommon.gtLclNum;
- LclVarDsc* varDsc = &(compiler->lvaTable[varNum]);
-
- // We don't actually need to spill if it is already living in memory
- bool needsSpill = ((tree->gtFlags & GTF_VAR_DEF) == 0 && varDsc->lvIsInReg());
- if (needsSpill)
- {
- bool restoreRegVar = false;
- if (tree->gtOper == GT_REG_VAR)
- {
- tree->SetOper(GT_LCL_VAR);
- restoreRegVar = true;
- }
-
- // mask off the flag to generate the right spill code, then bring it back
- tree->gtFlags &= ~GTF_REG_VAL;
-
- instruction storeIns = ins_Store(tree->TypeGet());
-
- if (varTypeIsMultiReg(tree))
- {
- assert(varDsc->lvRegNum == genRegPairLo(tree->gtRegPair));
- assert(varDsc->lvOtherReg == genRegPairHi(tree->gtRegPair));
- regNumber regLo = genRegPairLo(tree->gtRegPair);
- regNumber regHi = genRegPairHi(tree->gtRegPair);
- inst_TT_RV(storeIns, tree, regLo);
- inst_TT_RV(storeIns, tree, regHi, 4);
- }
- else
- {
- assert(varDsc->lvRegNum == tree->gtRegNum);
- inst_TT_RV(storeIns, tree, tree->gtRegNum);
- }
- tree->gtFlags |= GTF_REG_VAL;
-
- if (restoreRegVar)
- {
- tree->SetOper(GT_REG_VAR);
- }
-
- genUpdateRegLife(varDsc, /*isBorn*/ false, /*isDying*/ true DEBUGARG(tree));
- gcInfo.gcMarkRegSetNpt(varDsc->lvRegMask());
-
- if (VarSetOps::IsMember(compiler, gcInfo.gcTrkStkPtrLcls, varDsc->lvVarIndex))
- {
-#ifdef DEBUG
- if (!VarSetOps::IsMember(compiler, gcInfo.gcVarPtrSetCur, varDsc->lvVarIndex))
- {
- JITDUMP("\t\t\t\t\t\t\tVar V%02u becoming live\n", varNum);
- }
- else
- {
- JITDUMP("\t\t\t\t\t\t\tVar V%02u continuing live\n", varNum);
- }
-#endif
- VarSetOps::AddElemD(compiler, gcInfo.gcVarPtrSetCur, varDsc->lvVarIndex);
- }
- }
-
- tree->gtFlags &= ~GTF_SPILL;
- varDsc->lvRegNum = REG_STK;
- if (varTypeIsMultiReg(tree))
- {
- varDsc->lvOtherReg = REG_STK;
- }
-}
-
-// inline
-void CodeGenInterface::genUpdateVarReg(LclVarDsc* varDsc, GenTreePtr tree)
-{
- assert(tree->OperIsScalarLocal() || (tree->gtOper == GT_COPY));
- varDsc->lvRegNum = tree->gtRegNum;
-}
-
/*****************************************************************************
*
* Generate code that will set the given register to the integer constant.
@@ -157,735 +61,22 @@ void CodeGen::genSetRegToIcon(regNumber reg, ssize_t val, var_types type, insFla
*/
void CodeGen::genEmitGSCookieCheck(bool pushReg)
{
- NYI("ARM genEmitGSCookieCheck is not yet implemented for protojit");
+ NYI("ARM genEmitGSCookieCheck");
}
-/*****************************************************************************
- *
- * Generate code for all the basic blocks in the function.
- */
-
-void CodeGen::genCodeForBBlist()
+BasicBlock* CodeGen::genCallFinally(BasicBlock* block, BasicBlock* lblk)
{
- unsigned varNum;
- LclVarDsc* varDsc;
-
- unsigned savedStkLvl;
-
-#ifdef DEBUG
- genInterruptibleUsed = true;
-
- // You have to be careful if you create basic blocks from now on
- compiler->fgSafeBasicBlockCreation = false;
-
- // This stress mode is not comptible with fully interruptible GC
- if (genInterruptible && compiler->opts.compStackCheckOnCall)
- {
- compiler->opts.compStackCheckOnCall = false;
- }
-
- // This stress mode is not comptible with fully interruptible GC
- if (genInterruptible && compiler->opts.compStackCheckOnRet)
- {
- compiler->opts.compStackCheckOnRet = false;
- }
-#endif
-
- // Prepare the blocks for exception handling codegen: mark the blocks that needs labels.
- genPrepForEHCodegen();
-
- assert(!compiler->fgFirstBBScratch ||
- compiler->fgFirstBB == compiler->fgFirstBBScratch); // compiler->fgFirstBBScratch has to be first.
-
- /* Initialize the spill tracking logic */
-
- regSet.rsSpillBeg();
-
-#ifdef DEBUGGING_SUPPORT
- /* Initialize the line# tracking logic */
-
- if (compiler->opts.compScopeInfo)
- {
- siInit();
- }
-#endif
-
- if (compiler->opts.compDbgEnC)
- {
- noway_assert(isFramePointerUsed());
- regSet.rsSetRegsModified(RBM_INT_CALLEE_SAVED & ~RBM_FPBASE);
- }
-
- /* If we have any pinvoke calls, we might potentially trash everything */
- if (compiler->info.compCallUnmanaged)
- {
- noway_assert(isFramePointerUsed()); // Setup of Pinvoke frame currently requires an EBP style frame
- regSet.rsSetRegsModified(RBM_INT_CALLEE_SAVED & ~RBM_FPBASE);
- }
-
- genPendingCallLabel = nullptr;
-
- /* Initialize the pointer tracking code */
-
- gcInfo.gcRegPtrSetInit();
- gcInfo.gcVarPtrSetInit();
-
- /* If any arguments live in registers, mark those regs as such */
-
- for (varNum = 0, varDsc = compiler->lvaTable; varNum < compiler->lvaCount; varNum++, varDsc++)
- {
- /* Is this variable a parameter assigned to a register? */
-
- if (!varDsc->lvIsParam || !varDsc->lvRegister)
- continue;
-
- /* Is the argument live on entry to the method? */
-
- if (!VarSetOps::IsMember(compiler, compiler->fgFirstBB->bbLiveIn, varDsc->lvVarIndex))
- continue;
-
- /* Is this a floating-point argument? */
-
- if (varDsc->IsFloatRegType())
- continue;
-
- noway_assert(!varTypeIsFloating(varDsc->TypeGet()));
-
- /* Mark the register as holding the variable */
-
- regTracker.rsTrackRegLclVar(varDsc->lvRegNum, varNum);
- }
-
- unsigned finallyNesting = 0;
-
- // Make sure a set is allocated for compiler->compCurLife (in the long case), so we can set it to empty without
- // allocation at the start of each basic block.
- VarSetOps::AssignNoCopy(compiler, compiler->compCurLife, VarSetOps::MakeEmpty(compiler));
-
- /*-------------------------------------------------------------------------
- *
- * Walk the basic blocks and generate code for each one
- *
- */
-
- BasicBlock* block;
- BasicBlock* lblk; /* previous block */
-
- for (lblk = NULL, block = compiler->fgFirstBB; block != NULL; lblk = block, block = block->bbNext)
- {
-#ifdef DEBUG
- if (compiler->verbose)
- {
- printf("\n=============== Generating ");
- block->dspBlockHeader(compiler, true, true);
- compiler->fgDispBBLiveness(block);
- }
-#endif // DEBUG
-
- /* Figure out which registers hold variables on entry to this block */
-
- regSet.ClearMaskVars();
- gcInfo.gcRegGCrefSetCur = RBM_NONE;
- gcInfo.gcRegByrefSetCur = RBM_NONE;
-
- compiler->m_pLinearScan->recordVarLocationsAtStartOfBB(block);
-
- genUpdateLife(block->bbLiveIn);
-
- // Even if liveness didn't change, we need to update the registers containing GC references.
- // genUpdateLife will update the registers live due to liveness changes. But what about registers that didn't
- // change? We cleared them out above. Maybe we should just not clear them out, but update the ones that change
- // here. That would require handling the changes in recordVarLocationsAtStartOfBB().
-
- regMaskTP newLiveRegSet = RBM_NONE;
- regMaskTP newRegGCrefSet = RBM_NONE;
- regMaskTP newRegByrefSet = RBM_NONE;
- VARSET_ITER_INIT(compiler, iter, block->bbLiveIn, varIndex);
- while (iter.NextElem(compiler, &varIndex))
- {
- unsigned varNum = compiler->lvaTrackedToVarNum[varIndex];
- LclVarDsc* varDsc = &(compiler->lvaTable[varNum]);
-
- if (varDsc->lvIsInReg())
- {
- newLiveRegSet |= varDsc->lvRegMask();
- if (varDsc->lvType == TYP_REF)
- {
- newRegGCrefSet |= varDsc->lvRegMask();
- }
- else if (varDsc->lvType == TYP_BYREF)
- {
- newRegByrefSet |= varDsc->lvRegMask();
- }
- }
- else if (varDsc->lvType == TYP_REF || varDsc->lvType == TYP_BYREF)
- {
- VarSetOps::AddElemD(compiler, gcInfo.gcVarPtrSetCur, varIndex);
- }
- }
-
- regSet.rsMaskVars = newLiveRegSet;
- gcInfo.gcMarkRegSetGCref(newRegGCrefSet DEBUGARG(true));
- gcInfo.gcMarkRegSetByref(newRegByrefSet DEBUGARG(true));
-
- /* Blocks with handlerGetsXcptnObj()==true use GT_CATCH_ARG to
- represent the exception object (TYP_REF).
- We mark REG_EXCEPTION_OBJECT as holding a GC object on entry
- to the block, it will be the first thing evaluated
- (thanks to GTF_ORDER_SIDEEFF).
- */
-
- if (handlerGetsXcptnObj(block->bbCatchTyp))
- {
- for (GenTree* node : LIR::AsRange(block))
- {
- if (node->OperGet() == GT_CATCH_ARG)
- {
- gcInfo.gcMarkRegSetGCref(RBM_EXCEPTION_OBJECT);
- break;
- }
- }
- }
-
- /* Start a new code output block */
- CLANG_FORMAT_COMMENT_ANCHOR;
-
-#if FEATURE_EH_FUNCLETS
-#if defined(_TARGET_ARM_)
- // If this block is the target of a finally return, we need to add a preceding NOP, in the same EH region,
- // so the unwinder doesn't get confused by our "movw lr, xxx; movt lr, xxx; b Lyyy" calling convention that
- // calls the funclet during non-exceptional control flow.
- if (block->bbFlags & BBF_FINALLY_TARGET)
- {
- assert(block->bbFlags & BBF_JMP_TARGET);
-
-#ifdef DEBUG
- if (compiler->verbose)
- {
- printf("\nEmitting finally target NOP predecessor for BB%02u\n", block->bbNum);
- }
-#endif
- // Create a label that we'll use for computing the start of an EH region, if this block is
- // at the beginning of such a region. If we used the existing bbEmitCookie as is for
- // determining the EH regions, then this NOP would end up outside of the region, if this
- // block starts an EH region. If we pointed the existing bbEmitCookie here, then the NOP
- // would be executed, which we would prefer not to do.
-
- block->bbUnwindNopEmitCookie =
- getEmitter()->emitAddLabel(gcInfo.gcVarPtrSetCur, gcInfo.gcRegGCrefSetCur, gcInfo.gcRegByrefSetCur);
-
- instGen(INS_nop);
- }
-#endif // defined(_TARGET_ARM_)
-
- genUpdateCurrentFunclet(block);
-#endif // FEATURE_EH_FUNCLETS
-
-#ifdef _TARGET_XARCH_
- if (genAlignLoops && block->bbFlags & BBF_LOOP_HEAD)
- {
- getEmitter()->emitLoopAlign();
- }
-#endif
-
-#ifdef DEBUG
- if (compiler->opts.dspCode)
- printf("\n L_M%03u_BB%02u:\n", Compiler::s_compMethodsCount, block->bbNum);
-#endif
-
- block->bbEmitCookie = NULL;
-
- if (block->bbFlags & (BBF_JMP_TARGET | BBF_HAS_LABEL))
- {
- /* Mark a label and update the current set of live GC refs */
-
- block->bbEmitCookie =
- getEmitter()->emitAddLabel(gcInfo.gcVarPtrSetCur, gcInfo.gcRegGCrefSetCur, gcInfo.gcRegByrefSetCur,
- /*isFinally*/ block->bbFlags & BBF_FINALLY_TARGET);
- }
-
- if (block == compiler->fgFirstColdBlock)
- {
-#ifdef DEBUG
- if (compiler->verbose)
- {
- printf("\nThis is the start of the cold region of the method\n");
- }
-#endif
- // We should never have a block that falls through into the Cold section
- noway_assert(!lblk->bbFallsThrough());
-
- // We require the block that starts the Cold section to have a label
- noway_assert(block->bbEmitCookie);
- getEmitter()->emitSetFirstColdIGCookie(block->bbEmitCookie);
- }
-
- /* Both stacks are always empty on entry to a basic block */
-
- genStackLevel = 0;
-
-#if !FEATURE_FIXED_OUT_ARGS
- /* Check for inserted throw blocks and adjust genStackLevel */
-
- if (!isFramePointerUsed() && compiler->fgIsThrowHlpBlk(block))
- {
- noway_assert(block->bbFlags & BBF_JMP_TARGET);
-
- genStackLevel = compiler->fgThrowHlpBlkStkLevel(block) * sizeof(int);
-
- if (genStackLevel)
- {
- NYI("Need emitMarkStackLvl()");
- }
- }
-#endif // !FEATURE_FIXED_OUT_ARGS
-
- savedStkLvl = genStackLevel;
-
- /* Tell everyone which basic block we're working on */
-
- compiler->compCurBB = block;
-
-#ifdef DEBUGGING_SUPPORT
- siBeginBlock(block);
-
- // BBF_INTERNAL blocks don't correspond to any single IL instruction.
- if (compiler->opts.compDbgInfo && (block->bbFlags & BBF_INTERNAL) && block != compiler->fgFirstBB)
- genIPmappingAdd((IL_OFFSETX)ICorDebugInfo::NO_MAPPING, true);
-
- bool firstMapping = true;
-#endif // DEBUGGING_SUPPORT
-
- /*---------------------------------------------------------------------
- *
- * Generate code for each statement-tree in the block
- *
- */
- CLANG_FORMAT_COMMENT_ANCHOR;
-
-#if FEATURE_EH_FUNCLETS
- if (block->bbFlags & BBF_FUNCLET_BEG)
- {
- genReserveFuncletProlog(block);
- }
-#endif // FEATURE_EH_FUNCLETS
-
- // Clear compCurStmt and compCurLifeTree.
- compiler->compCurStmt = nullptr;
- compiler->compCurLifeTree = nullptr;
-
-#ifdef DEBUG
- bool pastProfileUpdate = false;
-#endif
-
-// Traverse the block in linear order, generating code for each node as we
-// as we encounter it.
-#ifdef DEBUGGING_SUPPORT
- IL_OFFSETX currentILOffset = BAD_IL_OFFSET;
-#endif
- for (GenTree* node : LIR::AsRange(block))
- {
-#ifdef DEBUGGING_SUPPORT
- // Do we have a new IL offset?
- if (node->OperGet() == GT_IL_OFFSET)
- {
- genEnsureCodeEmitted(currentILOffset);
-
- currentILOffset = node->gtStmt.gtStmtILoffsx;
-
- genIPmappingAdd(currentILOffset, firstMapping);
- firstMapping = false;
- }
-#endif // DEBUGGING_SUPPORT
-
-#ifdef DEBUG
- if (node->OperGet() == GT_IL_OFFSET)
- {
- noway_assert(node->gtStmt.gtStmtLastILoffs <= compiler->info.compILCodeSize ||
- node->gtStmt.gtStmtLastILoffs == BAD_IL_OFFSET);
-
- if (compiler->opts.dspCode && compiler->opts.dspInstrs &&
- node->gtStmt.gtStmtLastILoffs != BAD_IL_OFFSET)
- {
- while (genCurDispOffset <= node->gtStmt.gtStmtLastILoffs)
- {
- genCurDispOffset += dumpSingleInstr(compiler->info.compCode, genCurDispOffset, "> ");
- }
- }
- }
-#endif // DEBUG
-
- genCodeForTreeNode(node);
- if (node->gtHasReg() && node->gtLsraInfo.isLocalDefUse)
- {
- genConsumeReg(node);
- }
-
-#ifdef DEBUG
- regSet.rsSpillChk();
-
- assert((node->gtFlags & GTF_SPILL) == 0);
-
- /* Make sure we didn't bungle pointer register tracking */
-
- regMaskTP ptrRegs = (gcInfo.gcRegGCrefSetCur | gcInfo.gcRegByrefSetCur);
- regMaskTP nonVarPtrRegs = ptrRegs & ~regSet.rsMaskVars;
-
- // If return is a GC-type, clear it. Note that if a common
- // epilog is generated (genReturnBB) it has a void return
- // even though we might return a ref. We can't use the compRetType
- // as the determiner because something we are tracking as a byref
- // might be used as a return value of a int function (which is legal)
- if (node->gtOper == GT_RETURN && (varTypeIsGC(compiler->info.compRetType) ||
- (node->gtOp.gtOp1 != 0 && varTypeIsGC(node->gtOp.gtOp1->TypeGet()))))
- {
- nonVarPtrRegs &= ~RBM_INTRET;
- }
-
- // When profiling, the first few nodes in a catch block will be an update of
- // the profile count (does not interfere with the exception object).
- if (((compiler->opts.eeFlags & CORJIT_FLG_BBINSTR) != 0) && handlerGetsXcptnObj(block->bbCatchTyp))
- {
- pastProfileUpdate = pastProfileUpdate || node->OperGet() == GT_CATCH_ARG;
- if (!pastProfileUpdate)
- {
- nonVarPtrRegs &= ~RBM_EXCEPTION_OBJECT;
- }
- }
-
- if (nonVarPtrRegs)
- {
- printf("Regset after node=");
- Compiler::printTreeID(node);
- printf(" BB%02u gcr=", block->bbNum);
- printRegMaskInt(gcInfo.gcRegGCrefSetCur & ~regSet.rsMaskVars);
- compiler->getEmitter()->emitDispRegSet(gcInfo.gcRegGCrefSetCur & ~regSet.rsMaskVars);
- printf(", byr=");
- printRegMaskInt(gcInfo.gcRegByrefSetCur & ~regSet.rsMaskVars);
- compiler->getEmitter()->emitDispRegSet(gcInfo.gcRegByrefSetCur & ~regSet.rsMaskVars);
- printf(", regVars=");
- printRegMaskInt(regSet.rsMaskVars);
- compiler->getEmitter()->emitDispRegSet(regSet.rsMaskVars);
- printf("\n");
- }
-
- noway_assert(nonVarPtrRegs == 0);
-#endif // DEBUG
- }
-
-#ifdef DEBUGGING_SUPPORT
- // It is possible to reach the end of the block without generating code for the current IL offset.
- // For example, if the following IR ends the current block, no code will have been generated for
- // offset 21:
- //
- // ( 0, 0) [000040] ------------ il_offset void IL offset: 21
- //
- // N001 ( 0, 0) [000039] ------------ nop void
- //
- // This can lead to problems when debugging the generated code. To prevent these issues, make sure
- // we've generated code for the last IL offset we saw in the block.
- genEnsureCodeEmitted(currentILOffset);
-
- if (compiler->opts.compScopeInfo && (compiler->info.compVarScopesCount > 0))
- {
- siEndBlock(block);
-
- /* Is this the last block, and are there any open scopes left ? */
-
- bool isLastBlockProcessed = (block->bbNext == NULL);
- if (block->isBBCallAlwaysPair())
- {
- isLastBlockProcessed = (block->bbNext->bbNext == NULL);
- }
-
- if (isLastBlockProcessed && siOpenScopeList.scNext)
- {
- /* This assert no longer holds, because we may insert a throw
- block to demarcate the end of a try or finally region when they
- are at the end of the method. It would be nice if we could fix
- our code so that this throw block will no longer be necessary. */
-
- // noway_assert(block->bbCodeOffsEnd != compiler->info.compILCodeSize);
-
- siCloseAllOpenScopes();
- }
- }
-
-#endif // DEBUGGING_SUPPORT
-
- genStackLevel -= savedStkLvl;
-
-#ifdef DEBUG
- // compCurLife should be equal to the liveOut set, except that we don't keep
- // it up to date for vars that are not register candidates
- // (it would be nice to have a xor set function)
-
- VARSET_TP VARSET_INIT_NOCOPY(extraLiveVars, VarSetOps::Diff(compiler, block->bbLiveOut, compiler->compCurLife));
- VarSetOps::UnionD(compiler, extraLiveVars, VarSetOps::Diff(compiler, compiler->compCurLife, block->bbLiveOut));
- VARSET_ITER_INIT(compiler, extraLiveVarIter, extraLiveVars, extraLiveVarIndex);
- while (extraLiveVarIter.NextElem(compiler, &extraLiveVarIndex))
- {
- unsigned varNum = compiler->lvaTrackedToVarNum[extraLiveVarIndex];
- LclVarDsc* varDsc = compiler->lvaTable + varNum;
- assert(!varDsc->lvIsRegCandidate());
- }
-#endif
-
- /* Both stacks should always be empty on exit from a basic block */
-
- noway_assert(genStackLevel == 0);
-
-#ifdef _TARGET_AMD64_
- // On AMD64, we need to generate a NOP after a call that is the last instruction of the block, in several
- // situations, to support proper exception handling semantics. This is mostly to ensure that when the stack
- // walker computes an instruction pointer for a frame, that instruction pointer is in the correct EH region.
- // The document "X64 and ARM ABIs.docx" has more details. The situations:
- // 1. If the call instruction is in a different EH region as the instruction that follows it.
- // 2. If the call immediately precedes an OS epilog. (Note that what the JIT or VM consider an epilog might
- // be slightly different from what the OS considers an epilog, and it is the OS-reported epilog that matters
- // here.)
- // We handle case #1 here, and case #2 in the emitter.
- if (getEmitter()->emitIsLastInsCall())
- {
- // Ok, the last instruction generated is a call instruction. Do any of the other conditions hold?
- // Note: we may be generating a few too many NOPs for the case of call preceding an epilog. Technically,
- // if the next block is a BBJ_RETURN, an epilog will be generated, but there may be some instructions
- // generated before the OS epilog starts, such as a GS cookie check.
- if ((block->bbNext == nullptr) || !BasicBlock::sameEHRegion(block, block->bbNext))
- {
- // We only need the NOP if we're not going to generate any more code as part of the block end.
-
- switch (block->bbJumpKind)
- {
- case BBJ_ALWAYS:
- case BBJ_THROW:
- case BBJ_CALLFINALLY:
- case BBJ_EHCATCHRET:
- // We're going to generate more code below anyway, so no need for the NOP.
-
- case BBJ_RETURN:
- case BBJ_EHFINALLYRET:
- case BBJ_EHFILTERRET:
- // These are the "epilog follows" case, handled in the emitter.
-
- break;
-
- case BBJ_NONE:
- if (block->bbNext == nullptr)
- {
- // Call immediately before the end of the code; we should never get here .
- instGen(INS_BREAKPOINT); // This should never get executed
- }
- else
- {
- // We need the NOP
- instGen(INS_nop);
- }
- break;
-
- case BBJ_COND:
- case BBJ_SWITCH:
- // These can't have a call as the last instruction!
-
- default:
- noway_assert(!"Unexpected bbJumpKind");
- break;
- }
- }
- }
-#endif //_TARGET_AMD64_
-
- /* Do we need to generate a jump or return? */
-
- switch (block->bbJumpKind)
- {
- case BBJ_ALWAYS:
- inst_JMP(EJ_jmp, block->bbJumpDest);
- break;
-
- case BBJ_RETURN:
- genExitCode(block);
- break;
-
- case BBJ_THROW:
- // If we have a throw at the end of a function or funclet, we need to emit another instruction
- // afterwards to help the OS unwinder determine the correct context during unwind.
- // We insert an unexecuted breakpoint instruction in several situations
- // following a throw instruction:
- // 1. If the throw is the last instruction of the function or funclet. This helps
- // the OS unwinder determine the correct context during an unwind from the
- // thrown exception.
- // 2. If this is this is the last block of the hot section.
- // 3. If the subsequent block is a special throw block.
- // 4. On AMD64, if the next block is in a different EH region.
- if ((block->bbNext == NULL)
-#if FEATURE_EH_FUNCLETS
- || (block->bbNext->bbFlags & BBF_FUNCLET_BEG)
-#endif // FEATURE_EH_FUNCLETS
-#ifdef _TARGET_AMD64_
- || !BasicBlock::sameEHRegion(block, block->bbNext)
-#endif // _TARGET_AMD64_
- || (!isFramePointerUsed() && compiler->fgIsThrowHlpBlk(block->bbNext)) ||
- block->bbNext == compiler->fgFirstColdBlock)
- {
- instGen(INS_BREAKPOINT); // This should never get executed
- }
-
- break;
-
- case BBJ_CALLFINALLY:
-
- // Now set REG_LR to the address of where the finally funclet should
- // return to directly.
-
- BasicBlock* bbFinallyRet;
- bbFinallyRet = NULL;
-
- // We don't have retless calls, since we use the BBJ_ALWAYS to point at a NOP pad where
- // we would have otherwise created retless calls.
- assert(block->isBBCallAlwaysPair());
-
- assert(block->bbNext != NULL);
- assert(block->bbNext->bbJumpKind == BBJ_ALWAYS);
- assert(block->bbNext->bbJumpDest != NULL);
- assert(block->bbNext->bbJumpDest->bbFlags & BBF_FINALLY_TARGET);
-
- bbFinallyRet = block->bbNext->bbJumpDest;
- bbFinallyRet->bbFlags |= BBF_JMP_TARGET;
-
-#if 0
- // TODO-ARM-CQ:
- // We don't know the address of finally funclet yet. But adr requires the offset
- // to finally funclet from current IP is within 4095 bytes. So this code is disabled
- // for now.
- getEmitter()->emitIns_J_R (INS_adr,
- EA_4BYTE,
- bbFinallyRet,
- REG_LR);
-#else // !0
- // Load the address where the finally funclet should return into LR.
- // The funclet prolog/epilog will do "push {lr}" / "pop {pc}" to do
- // the return.
- getEmitter()->emitIns_R_L(INS_movw, EA_4BYTE_DSP_RELOC, bbFinallyRet, REG_LR);
- getEmitter()->emitIns_R_L(INS_movt, EA_4BYTE_DSP_RELOC, bbFinallyRet, REG_LR);
-#endif // !0
-
- // Jump to the finally BB
- inst_JMP(EJ_jmp, block->bbJumpDest);
-
- // The BBJ_ALWAYS is used because the BBJ_CALLFINALLY can't point to the
- // jump target using bbJumpDest - that is already used to point
- // to the finally block. So just skip past the BBJ_ALWAYS unless the
- // block is RETLESS.
- if (!(block->bbFlags & BBF_RETLESS_CALL))
- {
- assert(block->isBBCallAlwaysPair());
-
- lblk = block;
- block = block->bbNext;
- }
- break;
-
-#ifdef _TARGET_ARM_
-
- case BBJ_EHCATCHRET:
- // set r0 to the address the VM should return to after the catch
- getEmitter()->emitIns_R_L(INS_movw, EA_4BYTE_DSP_RELOC, block->bbJumpDest, REG_R0);
- getEmitter()->emitIns_R_L(INS_movt, EA_4BYTE_DSP_RELOC, block->bbJumpDest, REG_R0);
-
- __fallthrough;
-
- case BBJ_EHFINALLYRET:
- case BBJ_EHFILTERRET:
- genReserveFuncletEpilog(block);
- break;
-
-#elif defined(_TARGET_AMD64_)
-
- case BBJ_EHCATCHRET:
- // Set EAX to the address the VM should return to after the catch.
- // Generate a RIP-relative
- // lea reg, [rip + disp32] ; the RIP is implicit
- // which will be position-indepenent.
- // TODO-ARM-Bug?: For ngen, we need to generate a reloc for the displacement (maybe EA_PTR_DSP_RELOC).
- getEmitter()->emitIns_R_L(INS_lea, EA_PTRSIZE, block->bbJumpDest, REG_INTRET);
- __fallthrough;
-
- case BBJ_EHFINALLYRET:
- case BBJ_EHFILTERRET:
- genReserveFuncletEpilog(block);
- break;
-
-#endif // _TARGET_AMD64_
-
- case BBJ_NONE:
- case BBJ_COND:
- case BBJ_SWITCH:
- break;
-
- default:
- noway_assert(!"Unexpected bbJumpKind");
- break;
- }
-
-#ifdef DEBUG
- compiler->compCurBB = 0;
-#endif
-
- } //------------------ END-FOR each block of the method -------------------
-
- /* Nothing is live at this point */
- genUpdateLife(VarSetOps::MakeEmpty(compiler));
-
- /* Finalize the spill tracking logic */
-
- regSet.rsSpillEnd();
-
- /* Finalize the temp tracking logic */
-
- compiler->tmpEnd();
-
-#ifdef DEBUG
- if (compiler->verbose)
- {
- printf("\n# ");
- printf("compCycleEstimate = %6d, compSizeEstimate = %5d ", compiler->compCycleEstimate, compiler->compSizeEstimate);
- printf("%s\n", compiler->info.compFullName);
- }
-#endif
+ NYI("ARM genCallFinally");
+ return block;
}
-// return the child that has the same reg as the dst (if any)
-// other child returned (out param) in 'other'
-GenTree* sameRegAsDst(GenTree* tree, GenTree*& other /*out*/)
-{
- if (tree->gtRegNum == REG_NA)
- {
- other = nullptr;
- return NULL;
- }
+// move an immediate value into an integer register
- GenTreePtr op1 = tree->gtOp.gtOp1->gtEffectiveVal();
- GenTreePtr op2 = tree->gtOp.gtOp2->gtEffectiveVal();
- if (op1->gtRegNum == tree->gtRegNum)
- {
- other = op2;
- return op1;
- }
- if (op2->gtRegNum == tree->gtRegNum)
- {
- other = op1;
- return op2;
- }
- else
- {
- other = nullptr;
- return NULL;
- }
+void CodeGen::genEHCatchRet(BasicBlock* block)
+{
+ NYI("ARM genEHCatchRet");
}
-// move an immediate value into an integer register
-
void CodeGen::instGen_Set_Reg_To_Imm(emitAttr size, regNumber reg, ssize_t imm, insFlags flags)
{
// reg cannot be a FP register
@@ -902,16 +93,7 @@ void CodeGen::instGen_Set_Reg_To_Imm(emitAttr size, regNumber reg, ssize_t imm,
}
else
{
-#ifdef _TARGET_AMD64_
- if (AddrShouldUsePCRel(imm))
- {
- getEmitter()->emitIns_R_AI(INS_lea, EA_PTR_DSP_RELOC, reg, imm);
- }
- else
-#endif // _TARGET_AMD64_
- {
- getEmitter()->emitIns_R_I(INS_mov, size, reg, imm);
- }
+ getEmitter()->emitIns_R_I(INS_mov, size, reg, imm);
}
regTracker.rsTrackRegIntCns(reg, imm);
}
@@ -1423,6 +605,7 @@ void CodeGen::genCodeForTreeNode(GenTreePtr treeNode)
break;
case GT_LIST:
+ case GT_FIELD_LIST:
case GT_ARGPLACE:
// Nothing to do
break;
@@ -1479,7 +662,7 @@ void CodeGen::genCodeForTreeNode(GenTreePtr treeNode)
case GT_LOCKADD:
case GT_XCHG:
case GT_XADD:
- genLockedInstructions(treeNode);
+ genLockedInstructions(treeNode->AsOp());
break;
case GT_CMPXCHG:
@@ -1554,7 +737,8 @@ void CodeGen::genCodeForTreeNode(GenTreePtr treeNode)
{
#ifdef DEBUG
char message[256];
- sprintf(message, "NYI: Unimplemented node type %s\n", GenTree::NodeName(treeNode->OperGet()));
+ _snprintf_s(message, _countof(message), _TRUNCATE, "NYI: Unimplemented node type %s\n",
+ GenTree::NodeName(treeNode->OperGet()));
notYetImplemented(message, __FILE__, __LINE__);
#else
NYI("unimplemented node");
@@ -1566,7 +750,7 @@ void CodeGen::genCodeForTreeNode(GenTreePtr treeNode)
// generate code for the locked operations:
// GT_LOCKADD, GT_XCHG, GT_XADD
-void CodeGen::genLockedInstructions(GenTree* treeNode)
+void CodeGen::genLockedInstructions(GenTreeOp* treeNode)
{
NYI("genLockedInstructions");
}
@@ -1697,188 +881,9 @@ void CodeGen::genCodeForShift(GenTreePtr tree)
NYI("genCodeForShift");
}
-void CodeGen::genUnspillRegIfNeeded(GenTree* tree)
-{
- regNumber dstReg = tree->gtRegNum;
-
- GenTree* unspillTree = tree;
- if (tree->gtOper == GT_RELOAD)
- {
- unspillTree = tree->gtOp.gtOp1;
- }
- if (unspillTree->gtFlags & GTF_SPILLED)
- {
- if (genIsRegCandidateLocal(unspillTree))
- {
- // Reset spilled flag, since we are going to load a local variable from its home location.
- unspillTree->gtFlags &= ~GTF_SPILLED;
-
- // Load local variable from its home location.
- inst_RV_TT(ins_Load(unspillTree->gtType), dstReg, unspillTree);
-
- unspillTree->SetInReg();
-
- GenTreeLclVarCommon* lcl = unspillTree->AsLclVarCommon();
- LclVarDsc* varDsc = &compiler->lvaTable[lcl->gtLclNum];
-
- // TODO-Review: We would like to call:
- // genUpdateRegLife(varDsc, /*isBorn*/ true, /*isDying*/ false DEBUGARG(tree));
- // instead of the following code, but this ends up hitting this assert:
- // assert((regSet.rsMaskVars & regMask) == 0);
- // due to issues with LSRA resolution moves.
- // So, just force it for now. This probably indicates a condition that creates a GC hole!
- //
- // Extra note: I think we really want to call something like gcInfo.gcUpdateForRegVarMove,
- // because the variable is not really going live or dead, but that method is somewhat poorly
- // factored because it, in turn, updates rsMaskVars which is part of RegSet not GCInfo.
- // TODO-Cleanup: This code exists in other CodeGen*.cpp files, and should be moved to CodeGenCommon.cpp.
-
- genUpdateVarReg(varDsc, tree);
-#ifdef DEBUG
- if (VarSetOps::IsMember(compiler, gcInfo.gcVarPtrSetCur, varDsc->lvVarIndex))
- {
- JITDUMP("\t\t\t\t\t\t\tRemoving V%02u from gcVarPtrSetCur\n", lcl->gtLclNum);
- }
-#endif // DEBUG
- VarSetOps::RemoveElemD(compiler, gcInfo.gcVarPtrSetCur, varDsc->lvVarIndex);
-
-#ifdef DEBUG
- if (compiler->verbose)
- {
- printf("\t\t\t\t\t\t\tV%02u in reg ", lcl->gtLclNum);
- varDsc->PrintVarReg();
- printf(" is becoming live ");
- Compiler::printTreeID(unspillTree);
- printf("\n");
- }
-#endif // DEBUG
-
- regSet.AddMaskVars(genGetRegMask(varDsc));
- }
- else
- {
- TempDsc* t = regSet.rsUnspillInPlace(unspillTree, unspillTree->gtRegNum);
- compiler->tmpRlsTemp(t);
- getEmitter()->emitIns_R_S(ins_Load(unspillTree->gtType), emitActualTypeSize(unspillTree->gtType), dstReg,
- t->tdTempNum(), 0);
-
- unspillTree->SetInReg();
- }
-
- gcInfo.gcMarkRegPtrVal(dstReg, unspillTree->TypeGet());
- }
-}
-
-// do liveness update for a subnode that is being consumed by codegen
-regNumber CodeGen::genConsumeReg(GenTree* tree)
-{
- genUnspillRegIfNeeded(tree);
-
- // genUpdateLife() will also spill local var if marked as GTF_SPILL by calling CodeGen::genSpillVar
- genUpdateLife(tree);
- assert(tree->gtRegNum != REG_NA);
-
- // there are three cases where consuming a reg means clearing the bit in the live mask
- // 1. it was not produced by a local
- // 2. it was produced by a local that is going dead
- // 3. it was produced by a local that does not live in that reg (like one allocated on the stack)
-
- if (genIsRegCandidateLocal(tree))
- {
- GenTreeLclVarCommon* lcl = tree->AsLclVarCommon();
- LclVarDsc* varDsc = &compiler->lvaTable[lcl->GetLclNum()];
-
- if (varDsc->lvRegNum == tree->gtRegNum && ((tree->gtFlags & GTF_VAR_DEATH) != 0))
- {
- gcInfo.gcMarkRegSetNpt(genRegMask(tree->gtRegNum));
- }
- else if (!varDsc->lvLRACandidate)
- {
- gcInfo.gcMarkRegSetNpt(genRegMask(tree->gtRegNum));
- }
- }
- else
- {
- gcInfo.gcMarkRegSetNpt(genRegMask(tree->gtRegNum));
- }
-
- return tree->gtRegNum;
-}
-
-// Do liveness update for an address tree: one of GT_LEA, GT_LCL_VAR, or GT_CNS_INT (for call indirect).
-void CodeGen::genConsumeAddress(GenTree* addr)
-{
- if (addr->OperGet() == GT_LEA)
- {
- genConsumeAddrMode(addr->AsAddrMode());
- }
- else
- {
- assert(!addr->isContained());
- genConsumeReg(addr);
- }
-}
-
-// do liveness update for a subnode that is being consumed by codegen
-void CodeGen::genConsumeAddrMode(GenTreeAddrMode* addr)
+void CodeGen::genRegCopy(GenTree* treeNode)
{
- if (addr->Base())
- genConsumeReg(addr->Base());
- if (addr->Index())
- genConsumeReg(addr->Index());
-}
-
-// do liveness update for register produced by the current node in codegen
-void CodeGen::genProduceReg(GenTree* tree)
-{
- if (tree->gtFlags & GTF_SPILL)
- {
- if (genIsRegCandidateLocal(tree))
- {
- // Store local variable to its home location.
- tree->gtFlags &= ~GTF_REG_VAL;
- inst_TT_RV(ins_Store(tree->gtType), tree, tree->gtRegNum);
- }
- else
- {
- tree->SetInReg();
- regSet.rsSpillTree(tree->gtRegNum, tree);
- tree->gtFlags |= GTF_SPILLED;
- tree->gtFlags &= ~GTF_SPILL;
- gcInfo.gcMarkRegSetNpt(genRegMask(tree->gtRegNum));
- return;
- }
- }
-
- genUpdateLife(tree);
-
- // If we've produced a register, mark it as a pointer, as needed.
- // Except in the case of a dead definition of a lclVar.
- if (tree->gtHasReg() && (!tree->IsLocal() || (tree->gtFlags & GTF_VAR_DEATH) == 0))
- {
- gcInfo.gcMarkRegPtrVal(tree->gtRegNum, tree->TypeGet());
- }
- tree->SetInReg();
-}
-
-// transfer gc/byref status of src reg to dst reg
-void CodeGen::genTransferRegGCState(regNumber dst, regNumber src)
-{
- regMaskTP srcMask = genRegMask(src);
- regMaskTP dstMask = genRegMask(dst);
-
- if (gcInfo.gcRegGCrefSetCur & srcMask)
- {
- gcInfo.gcMarkRegSetGCref(dstMask);
- }
- else if (gcInfo.gcRegByrefSetCur & srcMask)
- {
- gcInfo.gcMarkRegSetByref(dstMask);
- }
- else
- {
- gcInfo.gcMarkRegSetNpt(dstMask);
- }
+ NYI("genRegCopy");
}
// Produce code for a GT_CALL node
@@ -2050,57 +1055,6 @@ void CodeGen::genEmitHelperCall(unsigned helper,
NYI("Helper call");
}
-/*****************************************************************************/
-#ifdef DEBUGGING_SUPPORT
-/*****************************************************************************
- * genSetScopeInfo
- *
- * Called for every scope info piece to record by the main genSetScopeInfo()
- */
-
-void CodeGen::genSetScopeInfo(unsigned which,
- UNATIVE_OFFSET startOffs,
- UNATIVE_OFFSET length,
- unsigned varNum,
- unsigned LVnum,
- bool avail,
- Compiler::siVarLoc& varLoc)
-{
- /* We need to do some mapping while reporting back these variables */
-
- unsigned ilVarNum = compiler->compMap2ILvarNum(varNum);
- noway_assert((int)ilVarNum != ICorDebugInfo::UNKNOWN_ILNUM);
-
- VarName name = nullptr;
-
-#ifdef DEBUG
-
- for (unsigned scopeNum = 0; scopeNum < compiler->info.compVarScopesCount; scopeNum++)
- {
- if (LVnum == compiler->info.compVarScopes[scopeNum].vsdLVnum)
- {
- name = compiler->info.compVarScopes[scopeNum].vsdName;
- }
- }
-
- // Hang on to this compiler->info.
-
- TrnslLocalVarInfo& tlvi = genTrnslLocalVarInfo[which];
-
- tlvi.tlviVarNum = ilVarNum;
- tlvi.tlviLVnum = LVnum;
- tlvi.tlviName = name;
- tlvi.tlviStartPC = startOffs;
- tlvi.tlviLength = length;
- tlvi.tlviAvailable = avail;
- tlvi.tlviVarLoc = varLoc;
-
-#endif // DEBUG
-
- compiler->eeSetLVinfo(which, startOffs, length, ilVarNum, LVnum, name, avail, varLoc);
-}
-#endif // DEBUGGING_SUPPORT
-
#endif // _TARGET_ARM_
#endif // !LEGACY_BACKEND
diff --git a/src/jit/codegenarm64.cpp b/src/jit/codegenarm64.cpp
index ca0df53a34..cc7c5dc524 100644
--- a/src/jit/codegenarm64.cpp
+++ b/src/jit/codegenarm64.cpp
@@ -747,7 +747,7 @@ void CodeGen::genRestoreCalleeSavedRegistersHelp(regMaskTP regsToRestoreMask, in
* +=======================+ <---- Caller's SP
* |Callee saved registers | // multiple of 8 bytes
* |-----------------------|
- * | PSP slot | // 8 bytes
+ * | PSP slot | // 8 bytes (omitted in CoreRT ABI)
* |-----------------------|
* ~ alignment padding ~ // To make the whole frame 16 byte aligned.
* |-----------------------|
@@ -773,7 +773,7 @@ void CodeGen::genRestoreCalleeSavedRegistersHelp(regMaskTP regsToRestoreMask, in
* +=======================+ <---- Caller's SP
* |Callee saved registers | // multiple of 8 bytes
* |-----------------------|
- * | PSP slot | // 8 bytes
+ * | PSP slot | // 8 bytes (omitted in CoreRT ABI)
* |-----------------------|
* ~ alignment padding ~ // To make the whole frame 16 byte aligned.
* |-----------------------|
@@ -801,7 +801,7 @@ void CodeGen::genRestoreCalleeSavedRegistersHelp(regMaskTP regsToRestoreMask, in
* +=======================+ <---- Caller's SP
* |Callee saved registers | // multiple of 8 bytes
* |-----------------------|
- * | PSP slot | // 8 bytes
+ * | PSP slot | // 8 bytes (omitted in CoreRT ABI)
* |-----------------------|
* ~ alignment padding ~ // To make the first SP subtraction 16 byte aligned
* |-----------------------|
@@ -883,7 +883,7 @@ void CodeGen::genRestoreCalleeSavedRegistersHelp(regMaskTP regsToRestoreMask, in
* +=======================+ <---- Caller's SP
* |Callee saved registers | // multiple of 8 bytes
* |-----------------------|
- * | PSP slot | // 8 bytes
+ * | PSP slot | // 8 bytes (omitted in CoreRT ABI)
* |-----------------------|
* | Saved FP, LR | // 16 bytes
* |-----------------------|
@@ -988,6 +988,12 @@ void CodeGen::genFuncletProlog(BasicBlock* block)
// This is the end of the OS-reported prolog for purposes of unwinding
compiler->unwindEndProlog();
+ // If there is no PSPSym (CoreRT ABI), we are done.
+ if (compiler->lvaPSPSym == BAD_VAR_NUM)
+ {
+ return;
+ }
+
if (isFilter)
{
// This is the first block of a filter
@@ -1134,8 +1140,10 @@ void CodeGen::genCaptureFuncletPrologEpilogInfo()
assert((rsMaskSaveRegs & RBM_LR) != 0);
assert((rsMaskSaveRegs & RBM_FP) != 0);
+ unsigned PSPSize = (compiler->lvaPSPSym != BAD_VAR_NUM) ? REGSIZE_BYTES : 0;
+
unsigned saveRegsCount = genCountBits(rsMaskSaveRegs);
- unsigned saveRegsPlusPSPSize = saveRegsCount * REGSIZE_BYTES + /* PSPSym */ REGSIZE_BYTES;
+ unsigned saveRegsPlusPSPSize = saveRegsCount * REGSIZE_BYTES + PSPSize;
if (compiler->info.compIsVarArgs)
{
// For varargs we always save all of the integer register arguments
@@ -1222,22 +1230,29 @@ void CodeGen::genCaptureFuncletPrologEpilogInfo()
printf(" SP delta 1: %d\n", genFuncletInfo.fiSpDelta1);
printf(" SP delta 2: %d\n", genFuncletInfo.fiSpDelta2);
- if (CallerSP_to_PSP_slot_delta != compiler->lvaGetCallerSPRelativeOffset(compiler->lvaPSPSym)) // for debugging
+ if (compiler->lvaPSPSym != BAD_VAR_NUM)
{
- printf("lvaGetCallerSPRelativeOffset(lvaPSPSym): %d\n",
- compiler->lvaGetCallerSPRelativeOffset(compiler->lvaPSPSym));
+ if (CallerSP_to_PSP_slot_delta !=
+ compiler->lvaGetCallerSPRelativeOffset(compiler->lvaPSPSym)) // for debugging
+ {
+ printf("lvaGetCallerSPRelativeOffset(lvaPSPSym): %d\n",
+ compiler->lvaGetCallerSPRelativeOffset(compiler->lvaPSPSym));
+ }
}
}
-#endif // DEBUG
assert(genFuncletInfo.fiSP_to_FPLR_save_delta >= 0);
assert(genFuncletInfo.fiSP_to_PSP_slot_delta >= 0);
assert(genFuncletInfo.fiSP_to_CalleeSave_delta >= 0);
assert(genFuncletInfo.fiCallerSP_to_PSP_slot_delta <= 0);
- assert(compiler->lvaPSPSym != BAD_VAR_NUM);
- assert(genFuncletInfo.fiCallerSP_to_PSP_slot_delta ==
- compiler->lvaGetCallerSPRelativeOffset(compiler->lvaPSPSym)); // same offset used in main function and
- // funclet!
+
+ if (compiler->lvaPSPSym != BAD_VAR_NUM)
+ {
+ assert(genFuncletInfo.fiCallerSP_to_PSP_slot_delta ==
+ compiler->lvaGetCallerSPRelativeOffset(compiler->lvaPSPSym)); // same offset used in main function and
+ // funclet!
+ }
+#endif // DEBUG
}
/*
@@ -1250,100 +1265,6 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
*/
-// Get the register assigned to the given node
-
-regNumber CodeGenInterface::genGetAssignedReg(GenTreePtr tree)
-{
- return tree->gtRegNum;
-}
-
-//------------------------------------------------------------------------
-// genSpillVar: Spill a local variable
-//
-// Arguments:
-// tree - the lclVar node for the variable being spilled
-//
-// Return Value:
-// None.
-//
-// Assumptions:
-// The lclVar must be a register candidate (lvRegCandidate)
-
-void CodeGen::genSpillVar(GenTreePtr tree)
-{
- unsigned varNum = tree->gtLclVarCommon.gtLclNum;
- LclVarDsc* varDsc = &(compiler->lvaTable[varNum]);
-
- assert(varDsc->lvIsRegCandidate());
-
- // We don't actually need to spill if it is already living in memory
- bool needsSpill = ((tree->gtFlags & GTF_VAR_DEF) == 0 && varDsc->lvIsInReg());
- if (needsSpill)
- {
- var_types lclTyp = varDsc->TypeGet();
- if (varDsc->lvNormalizeOnStore())
- lclTyp = genActualType(lclTyp);
- emitAttr size = emitTypeSize(lclTyp);
-
- bool restoreRegVar = false;
- if (tree->gtOper == GT_REG_VAR)
- {
- tree->SetOper(GT_LCL_VAR);
- restoreRegVar = true;
- }
-
- // mask off the flag to generate the right spill code, then bring it back
- tree->gtFlags &= ~GTF_REG_VAL;
-
- instruction storeIns = ins_Store(tree->TypeGet(), compiler->isSIMDTypeLocalAligned(varNum));
-
- assert(varDsc->lvRegNum == tree->gtRegNum);
- inst_TT_RV(storeIns, tree, tree->gtRegNum, 0, size);
-
- tree->gtFlags |= GTF_REG_VAL;
-
- if (restoreRegVar)
- {
- tree->SetOper(GT_REG_VAR);
- }
-
- genUpdateRegLife(varDsc, /*isBorn*/ false, /*isDying*/ true DEBUGARG(tree));
- gcInfo.gcMarkRegSetNpt(varDsc->lvRegMask());
-
- if (VarSetOps::IsMember(compiler, gcInfo.gcTrkStkPtrLcls, varDsc->lvVarIndex))
- {
-#ifdef DEBUG
- if (!VarSetOps::IsMember(compiler, gcInfo.gcVarPtrSetCur, varDsc->lvVarIndex))
- {
- JITDUMP("\t\t\t\t\t\t\tVar V%02u becoming live\n", varNum);
- }
- else
- {
- JITDUMP("\t\t\t\t\t\t\tVar V%02u continuing live\n", varNum);
- }
-#endif
- VarSetOps::AddElemD(compiler, gcInfo.gcVarPtrSetCur, varDsc->lvVarIndex);
- }
- }
-
- tree->gtFlags &= ~GTF_SPILL;
- varDsc->lvRegNum = REG_STK;
- if (varTypeIsMultiReg(tree))
- {
- varDsc->lvOtherReg = REG_STK;
- }
-}
-
-// inline
-void CodeGenInterface::genUpdateVarReg(LclVarDsc* varDsc, GenTreePtr tree)
-{
- assert(tree->OperIsScalarLocal() || (tree->gtOper == GT_COPY));
- varDsc->lvRegNum = tree->gtRegNum;
-}
-
-/*****************************************************************************/
-/*****************************************************************************/
-
/*****************************************************************************
*
* Generate code that will set the given register to the integer constant.
@@ -1405,702 +1326,79 @@ void CodeGen::genEmitGSCookieCheck(bool pushReg)
genDefineTempLabel(gsCheckBlk);
}
-/*****************************************************************************
- *
- * Generate code for all the basic blocks in the function.
- */
-
-void CodeGen::genCodeForBBlist()
+BasicBlock* CodeGen::genCallFinally(BasicBlock* block, BasicBlock* lblk)
{
- unsigned varNum;
- LclVarDsc* varDsc;
-
- unsigned savedStkLvl;
-
-#ifdef DEBUG
- genInterruptibleUsed = true;
-
- // You have to be careful if you create basic blocks from now on
- compiler->fgSafeBasicBlockCreation = false;
-
- // This stress mode is not comptible with fully interruptible GC
- if (genInterruptible && compiler->opts.compStackCheckOnCall)
- {
- compiler->opts.compStackCheckOnCall = false;
- }
-
- // This stress mode is not comptible with fully interruptible GC
- if (genInterruptible && compiler->opts.compStackCheckOnRet)
- {
- compiler->opts.compStackCheckOnRet = false;
- }
-#endif // DEBUG
-
- // Prepare the blocks for exception handling codegen: mark the blocks that needs labels.
- genPrepForEHCodegen();
-
- assert(!compiler->fgFirstBBScratch ||
- compiler->fgFirstBB == compiler->fgFirstBBScratch); // compiler->fgFirstBBScratch has to be first.
-
- /* Initialize the spill tracking logic */
-
- regSet.rsSpillBeg();
+ // Generate a call to the finally, like this:
+ // mov x0,qword ptr [fp + 10H] / sp // Load x0 with PSPSym, or sp if PSPSym is not used
+ // bl finally-funclet
+ // b finally-return // Only for non-retless finally calls
+ // The 'b' can be a NOP if we're going to the next block.
-#ifdef DEBUGGING_SUPPORT
- /* Initialize the line# tracking logic */
-
- if (compiler->opts.compScopeInfo)
- {
- siInit();
- }
-#endif
-
- // The current implementation of switch tables requires the first block to have a label so it
- // can generate offsets to the switch label targets.
- // TODO-ARM64-CQ: remove this when switches have been re-implemented to not use this.
- if (compiler->fgHasSwitch)
+ if (compiler->lvaPSPSym != BAD_VAR_NUM)
{
- compiler->fgFirstBB->bbFlags |= BBF_JMP_TARGET;
+ getEmitter()->emitIns_R_S(ins_Load(TYP_I_IMPL), EA_PTRSIZE, REG_R0, compiler->lvaPSPSym, 0);
}
-
- genPendingCallLabel = nullptr;
-
- /* Initialize the pointer tracking code */
-
- gcInfo.gcRegPtrSetInit();
- gcInfo.gcVarPtrSetInit();
-
- /* If any arguments live in registers, mark those regs as such */
-
- for (varNum = 0, varDsc = compiler->lvaTable; varNum < compiler->lvaCount; varNum++, varDsc++)
+ else
{
- /* Is this variable a parameter assigned to a register? */
-
- if (!varDsc->lvIsParam || !varDsc->lvRegister)
- continue;
-
- /* Is the argument live on entry to the method? */
-
- if (!VarSetOps::IsMember(compiler, compiler->fgFirstBB->bbLiveIn, varDsc->lvVarIndex))
- continue;
-
- /* Is this a floating-point argument? */
-
- if (varDsc->IsFloatRegType())
- continue;
-
- noway_assert(!varTypeIsFloating(varDsc->TypeGet()));
-
- /* Mark the register as holding the variable */
-
- regTracker.rsTrackRegLclVar(varDsc->lvRegNum, varNum);
+ getEmitter()->emitIns_R_R(INS_mov, EA_PTRSIZE, REG_R0, REG_SPBASE);
}
+ getEmitter()->emitIns_J(INS_bl_local, block->bbJumpDest);
- unsigned finallyNesting = 0;
-
- // Make sure a set is allocated for compiler->compCurLife (in the long case), so we can set it to empty without
- // allocation at the start of each basic block.
- VarSetOps::AssignNoCopy(compiler, compiler->compCurLife, VarSetOps::MakeEmpty(compiler));
-
- /*-------------------------------------------------------------------------
- *
- * Walk the basic blocks and generate code for each one
- *
- */
-
- BasicBlock* block;
- BasicBlock* lblk; /* previous block */
-
- for (lblk = NULL, block = compiler->fgFirstBB; block != NULL; lblk = block, block = block->bbNext)
+ if (block->bbFlags & BBF_RETLESS_CALL)
{
-#ifdef DEBUG
- if (compiler->verbose)
- {
- printf("\n=============== Generating ");
- block->dspBlockHeader(compiler, true, true);
- compiler->fgDispBBLiveness(block);
- }
-#endif // DEBUG
-
- /* Figure out which registers hold variables on entry to this block */
-
- regSet.ClearMaskVars();
- gcInfo.gcRegGCrefSetCur = RBM_NONE;
- gcInfo.gcRegByrefSetCur = RBM_NONE;
-
- compiler->m_pLinearScan->recordVarLocationsAtStartOfBB(block);
-
- genUpdateLife(block->bbLiveIn);
-
- // Even if liveness didn't change, we need to update the registers containing GC references.
- // genUpdateLife will update the registers live due to liveness changes. But what about registers that didn't
- // change? We cleared them out above. Maybe we should just not clear them out, but update the ones that change
- // here. That would require handling the changes in recordVarLocationsAtStartOfBB().
-
- regMaskTP newLiveRegSet = RBM_NONE;
- regMaskTP newRegGCrefSet = RBM_NONE;
- regMaskTP newRegByrefSet = RBM_NONE;
-#ifdef DEBUG
- VARSET_TP VARSET_INIT_NOCOPY(removedGCVars, VarSetOps::MakeEmpty(compiler));
- VARSET_TP VARSET_INIT_NOCOPY(addedGCVars, VarSetOps::MakeEmpty(compiler));
-#endif
- VARSET_ITER_INIT(compiler, iter, block->bbLiveIn, varIndex);
- while (iter.NextElem(compiler, &varIndex))
- {
- unsigned varNum = compiler->lvaTrackedToVarNum[varIndex];
- LclVarDsc* varDsc = &(compiler->lvaTable[varNum]);
-
- if (varDsc->lvIsInReg())
- {
- newLiveRegSet |= varDsc->lvRegMask();
- if (varDsc->lvType == TYP_REF)
- {
- newRegGCrefSet |= varDsc->lvRegMask();
- }
- else if (varDsc->lvType == TYP_BYREF)
- {
- newRegByrefSet |= varDsc->lvRegMask();
- }
-#ifdef DEBUG
- if (verbose && VarSetOps::IsMember(compiler, gcInfo.gcVarPtrSetCur, varIndex))
- {
- VarSetOps::AddElemD(compiler, removedGCVars, varIndex);
- }
-#endif // DEBUG
- VarSetOps::RemoveElemD(compiler, gcInfo.gcVarPtrSetCur, varIndex);
- }
- else if (compiler->lvaIsGCTracked(varDsc))
- {
-#ifdef DEBUG
- if (verbose && !VarSetOps::IsMember(compiler, gcInfo.gcVarPtrSetCur, varIndex))
- {
- VarSetOps::AddElemD(compiler, addedGCVars, varIndex);
- }
-#endif // DEBUG
- VarSetOps::AddElemD(compiler, gcInfo.gcVarPtrSetCur, varIndex);
- }
- }
-
- regSet.rsMaskVars = newLiveRegSet;
-
-#ifdef DEBUG
- if (compiler->verbose)
- {
- if (!VarSetOps::IsEmpty(compiler, addedGCVars))
- {
- printf("\t\t\t\t\t\t\tAdded GCVars: ");
- dumpConvertedVarSet(compiler, addedGCVars);
- printf("\n");
- }
- if (!VarSetOps::IsEmpty(compiler, removedGCVars))
- {
- printf("\t\t\t\t\t\t\tRemoved GCVars: ");
- dumpConvertedVarSet(compiler, removedGCVars);
- printf("\n");
- }
- }
-#endif // DEBUG
-
- gcInfo.gcMarkRegSetGCref(newRegGCrefSet DEBUGARG(true));
- gcInfo.gcMarkRegSetByref(newRegByrefSet DEBUGARG(true));
-
- /* Blocks with handlerGetsXcptnObj()==true use GT_CATCH_ARG to
- represent the exception object (TYP_REF).
- We mark REG_EXCEPTION_OBJECT as holding a GC object on entry
- to the block, it will be the first thing evaluated
- (thanks to GTF_ORDER_SIDEEFF).
- */
-
- if (handlerGetsXcptnObj(block->bbCatchTyp))
- {
- for (GenTree* node : LIR::AsRange(block))
- {
- if (node->OperGet() == GT_CATCH_ARG)
- {
- gcInfo.gcMarkRegSetGCref(RBM_EXCEPTION_OBJECT);
- break;
- }
- }
- }
-
- /* Start a new code output block */
-
- genUpdateCurrentFunclet(block);
-
-#ifdef _TARGET_XARCH_
- if (genAlignLoops && block->bbFlags & BBF_LOOP_HEAD)
- {
- getEmitter()->emitLoopAlign();
- }
-#endif
-
-#ifdef DEBUG
- if (compiler->opts.dspCode)
- printf("\n L_M%03u_BB%02u:\n", Compiler::s_compMethodsCount, block->bbNum);
-#endif
-
- block->bbEmitCookie = NULL;
-
- if (block->bbFlags & (BBF_JMP_TARGET | BBF_HAS_LABEL))
- {
- /* Mark a label and update the current set of live GC refs */
-
- block->bbEmitCookie = getEmitter()->emitAddLabel(gcInfo.gcVarPtrSetCur, gcInfo.gcRegGCrefSetCur,
- gcInfo.gcRegByrefSetCur, FALSE);
- }
-
- if (block == compiler->fgFirstColdBlock)
- {
-#ifdef DEBUG
- if (compiler->verbose)
- {
- printf("\nThis is the start of the cold region of the method\n");
- }
-#endif
- // We should never have a block that falls through into the Cold section
- noway_assert(!lblk->bbFallsThrough());
-
- // We require the block that starts the Cold section to have a label
- noway_assert(block->bbEmitCookie);
- getEmitter()->emitSetFirstColdIGCookie(block->bbEmitCookie);
- }
-
- /* Both stacks are always empty on entry to a basic block */
-
- genStackLevel = 0;
-
- savedStkLvl = genStackLevel;
-
- /* Tell everyone which basic block we're working on */
-
- compiler->compCurBB = block;
-
-#ifdef DEBUGGING_SUPPORT
- siBeginBlock(block);
-
- // BBF_INTERNAL blocks don't correspond to any single IL instruction.
- if (compiler->opts.compDbgInfo && (block->bbFlags & BBF_INTERNAL) &&
- !compiler->fgBBisScratch(block)) // If the block is the distinguished first scratch block, then no need to
- // emit a NO_MAPPING entry, immediately after the prolog.
- {
- genIPmappingAdd((IL_OFFSETX)ICorDebugInfo::NO_MAPPING, true);
- }
-
- bool firstMapping = true;
-#endif // DEBUGGING_SUPPORT
-
- /*---------------------------------------------------------------------
- *
- * Generate code for each statement-tree in the block
- *
- */
-
- if (block->bbFlags & BBF_FUNCLET_BEG)
- {
- genReserveFuncletProlog(block);
- }
-
- // Clear compCurStmt and compCurLifeTree.
- compiler->compCurStmt = nullptr;
- compiler->compCurLifeTree = nullptr;
-
- // Traverse the block in linear order, generating code for each node as we
- // as we encounter it.
- CLANG_FORMAT_COMMENT_ANCHOR;
-
-#ifdef DEBUGGING_SUPPORT
- IL_OFFSETX currentILOffset = BAD_IL_OFFSET;
-#endif
- for (GenTree* node : LIR::AsRange(block).NonPhiNodes())
- {
-#ifdef DEBUGGING_SUPPORT
- // Do we have a new IL offset?
- if (node->OperGet() == GT_IL_OFFSET)
- {
- genEnsureCodeEmitted(currentILOffset);
- currentILOffset = node->gtStmt.gtStmtILoffsx;
- genIPmappingAdd(currentILOffset, firstMapping);
- firstMapping = false;
- }
-#endif // DEBUGGING_SUPPORT
-
-#ifdef DEBUG
- if (node->OperGet() == GT_IL_OFFSET)
- {
- noway_assert(node->gtStmt.gtStmtLastILoffs <= compiler->info.compILCodeSize ||
- node->gtStmt.gtStmtLastILoffs == BAD_IL_OFFSET);
-
- if (compiler->opts.dspCode && compiler->opts.dspInstrs &&
- node->gtStmt.gtStmtLastILoffs != BAD_IL_OFFSET)
- {
- while (genCurDispOffset <= node->gtStmt.gtStmtLastILoffs)
- {
- genCurDispOffset += dumpSingleInstr(compiler->info.compCode, genCurDispOffset, "> ");
- }
- }
- }
-#endif // DEBUG
-
- genCodeForTreeNode(node);
- if (node->gtHasReg() && node->gtLsraInfo.isLocalDefUse)
- {
- genConsumeReg(node);
- }
- } // end for each node in block
-
-#ifdef DEBUG
- // The following set of register spill checks and GC pointer tracking checks used to be
- // performed at statement boundaries. Now, with LIR, there are no statements, so they are
- // performed at the end of each block.
- // TODO: could these checks be performed more frequently? E.g., at each location where
- // the register allocator says there are no live non-variable registers. Perhaps this could
- // be done by (a) keeping a running count of live non-variable registers by using
- // gtLsraInfo.srcCount and gtLsraInfo.dstCount to decrement and increment the count, respectively,
- // and running the checks when the count is zero. Or, (b) use the map maintained by LSRA
- // (operandToLocationInfoMap) to mark a node somehow when, after the execution of that node,
- // there will be no live non-variable registers.
-
- regSet.rsSpillChk();
-
- /* Make sure we didn't bungle pointer register tracking */
-
- regMaskTP ptrRegs = gcInfo.gcRegGCrefSetCur | gcInfo.gcRegByrefSetCur;
- regMaskTP nonVarPtrRegs = ptrRegs & ~regSet.rsMaskVars;
-
- // If return is a GC-type, clear it. Note that if a common
- // epilog is generated (genReturnBB) it has a void return
- // even though we might return a ref. We can't use the compRetType
- // as the determiner because something we are tracking as a byref
- // might be used as a return value of a int function (which is legal)
- GenTree* blockLastNode = block->lastNode();
- if ((blockLastNode != nullptr) && (blockLastNode->gtOper == GT_RETURN) &&
- (varTypeIsGC(compiler->info.compRetType) ||
- (blockLastNode->gtOp.gtOp1 != nullptr && varTypeIsGC(blockLastNode->gtOp.gtOp1->TypeGet()))))
- {
- nonVarPtrRegs &= ~RBM_INTRET;
- }
-
- if (nonVarPtrRegs)
- {
- printf("Regset after BB%02u gcr=", block->bbNum);
- printRegMaskInt(gcInfo.gcRegGCrefSetCur & ~regSet.rsMaskVars);
- compiler->getEmitter()->emitDispRegSet(gcInfo.gcRegGCrefSetCur & ~regSet.rsMaskVars);
- printf(", byr=");
- printRegMaskInt(gcInfo.gcRegByrefSetCur & ~regSet.rsMaskVars);
- compiler->getEmitter()->emitDispRegSet(gcInfo.gcRegByrefSetCur & ~regSet.rsMaskVars);
- printf(", regVars=");
- printRegMaskInt(regSet.rsMaskVars);
- compiler->getEmitter()->emitDispRegSet(regSet.rsMaskVars);
- printf("\n");
- }
-
- noway_assert(nonVarPtrRegs == RBM_NONE);
-#endif // DEBUG
-
-#if defined(DEBUG) && defined(_TARGET_ARM64_)
- if (block->bbNext == nullptr)
- {
- // Unit testing of the ARM64 emitter: generate a bunch of instructions into the last block
- // (it's as good as any, but better than the prolog, which can only be a single instruction
- // group) then use COMPlus_JitLateDisasm=* to see if the late disassembler
- // thinks the instructions are the same as we do.
- genArm64EmitterUnitTests();
- }
-#endif // defined(DEBUG) && defined(_TARGET_ARM64_)
-
-#ifdef DEBUGGING_SUPPORT
- // It is possible to reach the end of the block without generating code for the current IL offset.
- // For example, if the following IR ends the current block, no code will have been generated for
- // offset 21:
- //
- // ( 0, 0) [000040] ------------ il_offset void IL offset: 21
- //
- // N001 ( 0, 0) [000039] ------------ nop void
- //
- // This can lead to problems when debugging the generated code. To prevent these issues, make sure
- // we've generated code for the last IL offset we saw in the block.
- genEnsureCodeEmitted(currentILOffset);
-
- if (compiler->opts.compScopeInfo && (compiler->info.compVarScopesCount > 0))
- {
- siEndBlock(block);
-
- /* Is this the last block, and are there any open scopes left ? */
+ // We have a retless call, and the last instruction generated was a call.
+ // If the next block is in a different EH region (or is the end of the code
+ // block), then we need to generate a breakpoint here (since it will never
+ // get executed) to get proper unwind behavior.
- bool isLastBlockProcessed = (block->bbNext == NULL);
- if (block->isBBCallAlwaysPair())
- {
- isLastBlockProcessed = (block->bbNext->bbNext == NULL);
- }
-
- if (isLastBlockProcessed && siOpenScopeList.scNext)
- {
- /* This assert no longer holds, because we may insert a throw
- block to demarcate the end of a try or finally region when they
- are at the end of the method. It would be nice if we could fix
- our code so that this throw block will no longer be necessary. */
-
- // noway_assert(block->bbCodeOffsEnd != compiler->info.compILCodeSize);
-
- siCloseAllOpenScopes();
- }
- }
-
-#endif // DEBUGGING_SUPPORT
-
- genStackLevel -= savedStkLvl;
-
-#ifdef DEBUG
- // compCurLife should be equal to the liveOut set, except that we don't keep
- // it up to date for vars that are not register candidates
- // (it would be nice to have a xor set function)
-
- VARSET_TP VARSET_INIT_NOCOPY(extraLiveVars, VarSetOps::Diff(compiler, block->bbLiveOut, compiler->compCurLife));
- VarSetOps::UnionD(compiler, extraLiveVars, VarSetOps::Diff(compiler, compiler->compCurLife, block->bbLiveOut));
- VARSET_ITER_INIT(compiler, extraLiveVarIter, extraLiveVars, extraLiveVarIndex);
- while (extraLiveVarIter.NextElem(compiler, &extraLiveVarIndex))
+ if ((block->bbNext == nullptr) || !BasicBlock::sameEHRegion(block, block->bbNext))
{
- unsigned varNum = compiler->lvaTrackedToVarNum[extraLiveVarIndex];
- LclVarDsc* varDsc = compiler->lvaTable + varNum;
- assert(!varDsc->lvIsRegCandidate());
+ instGen(INS_BREAKPOINT); // This should never get executed
}
-#endif
-
- /* Both stacks should always be empty on exit from a basic block */
-
- noway_assert(genStackLevel == 0);
+ }
+ else
+ {
+ // Because of the way the flowgraph is connected, the liveness info for this one instruction
+ // after the call is not (can not be) correct in cases where a variable has a last use in the
+ // handler. So turn off GC reporting for this single instruction.
+ getEmitter()->emitDisableGC();
-#if 0
- // On AMD64, we need to generate a NOP after a call that is the last instruction of the block, in several
- // situations, to support proper exception handling semantics. This is mostly to ensure that when the stack
- // walker computes an instruction pointer for a frame, that instruction pointer is in the correct EH region.
- // The document "X64 and ARM ABIs.docx" has more details. The situations:
- // 1. If the call instruction is in a different EH region as the instruction that follows it.
- // 2. If the call immediately precedes an OS epilog. (Note that what the JIT or VM consider an epilog might
- // be slightly different from what the OS considers an epilog, and it is the OS-reported epilog that matters here.)
- // We handle case #1 here, and case #2 in the emitter.
- if (getEmitter()->emitIsLastInsCall())
+ // Now go to where the finally funclet needs to return to.
+ if (block->bbNext->bbJumpDest == block->bbNext->bbNext)
{
- // Ok, the last instruction generated is a call instruction. Do any of the other conditions hold?
- // Note: we may be generating a few too many NOPs for the case of call preceding an epilog. Technically,
- // if the next block is a BBJ_RETURN, an epilog will be generated, but there may be some instructions
- // generated before the OS epilog starts, such as a GS cookie check.
- if ((block->bbNext == nullptr) ||
- !BasicBlock::sameEHRegion(block, block->bbNext))
- {
- // We only need the NOP if we're not going to generate any more code as part of the block end.
-
- switch (block->bbJumpKind)
- {
- case BBJ_ALWAYS:
- case BBJ_THROW:
- case BBJ_CALLFINALLY:
- case BBJ_EHCATCHRET:
- // We're going to generate more code below anyway, so no need for the NOP.
-
- case BBJ_RETURN:
- case BBJ_EHFINALLYRET:
- case BBJ_EHFILTERRET:
- // These are the "epilog follows" case, handled in the emitter.
-
- break;
-
- case BBJ_NONE:
- if (block->bbNext == nullptr)
- {
- // Call immediately before the end of the code; we should never get here .
- instGen(INS_BREAKPOINT); // This should never get executed
- }
- else
- {
- // We need the NOP
- instGen(INS_nop);
- }
- break;
-
- case BBJ_COND:
- case BBJ_SWITCH:
- // These can't have a call as the last instruction!
-
- default:
- noway_assert(!"Unexpected bbJumpKind");
- break;
- }
- }
+ // Fall-through.
+ // TODO-ARM64-CQ: Can we get rid of this instruction, and just have the call return directly
+ // to the next instruction? This would depend on stack walking from within the finally
+ // handler working without this instruction being in this special EH region.
+ instGen(INS_nop);
}
-#endif // 0
-
- /* Do we need to generate a jump or return? */
-
- switch (block->bbJumpKind)
+ else
{
- case BBJ_ALWAYS:
- inst_JMP(EJ_jmp, block->bbJumpDest);
- break;
-
- case BBJ_RETURN:
- genExitCode(block);
- break;
-
- case BBJ_THROW:
- // If we have a throw at the end of a function or funclet, we need to emit another instruction
- // afterwards to help the OS unwinder determine the correct context during unwind.
- // We insert an unexecuted breakpoint instruction in several situations
- // following a throw instruction:
- // 1. If the throw is the last instruction of the function or funclet. This helps
- // the OS unwinder determine the correct context during an unwind from the
- // thrown exception.
- // 2. If this is this is the last block of the hot section.
- // 3. If the subsequent block is a special throw block.
- // 4. On AMD64, if the next block is in a different EH region.
- if ((block->bbNext == NULL) || (block->bbNext->bbFlags & BBF_FUNCLET_BEG) ||
- !BasicBlock::sameEHRegion(block, block->bbNext) ||
- (!isFramePointerUsed() && compiler->fgIsThrowHlpBlk(block->bbNext)) ||
- block->bbNext == compiler->fgFirstColdBlock)
- {
- instGen(INS_BREAKPOINT); // This should never get executed
- }
-
- break;
-
- case BBJ_CALLFINALLY:
-
- // Generate a call to the finally, like this:
- // mov x0,qword ptr [fp + 10H] // Load x0 with PSPSym
- // bl finally-funclet
- // b finally-return // Only for non-retless finally calls
- // The 'b' can be a NOP if we're going to the next block.
-
- getEmitter()->emitIns_R_S(ins_Load(TYP_I_IMPL), EA_PTRSIZE, REG_R0, compiler->lvaPSPSym, 0);
- getEmitter()->emitIns_J(INS_bl_local, block->bbJumpDest);
-
- if (block->bbFlags & BBF_RETLESS_CALL)
- {
- // We have a retless call, and the last instruction generated was a call.
- // If the next block is in a different EH region (or is the end of the code
- // block), then we need to generate a breakpoint here (since it will never
- // get executed) to get proper unwind behavior.
-
- if ((block->bbNext == nullptr) || !BasicBlock::sameEHRegion(block, block->bbNext))
- {
- instGen(INS_BREAKPOINT); // This should never get executed
- }
- }
- else
- {
- // Because of the way the flowgraph is connected, the liveness info for this one instruction
- // after the call is not (can not be) correct in cases where a variable has a last use in the
- // handler. So turn off GC reporting for this single instruction.
- getEmitter()->emitDisableGC();
-
- // Now go to where the finally funclet needs to return to.
- if (block->bbNext->bbJumpDest == block->bbNext->bbNext)
- {
- // Fall-through.
- // TODO-ARM64-CQ: Can we get rid of this instruction, and just have the call return directly
- // to the next instruction? This would depend on stack walking from within the finally
- // handler working without this instruction being in this special EH region.
- instGen(INS_nop);
- }
- else
- {
- inst_JMP(EJ_jmp, block->bbNext->bbJumpDest);
- }
-
- getEmitter()->emitEnableGC();
- }
-
- // The BBJ_ALWAYS is used because the BBJ_CALLFINALLY can't point to the
- // jump target using bbJumpDest - that is already used to point
- // to the finally block. So just skip past the BBJ_ALWAYS unless the
- // block is RETLESS.
- if (!(block->bbFlags & BBF_RETLESS_CALL))
- {
- assert(block->isBBCallAlwaysPair());
-
- lblk = block;
- block = block->bbNext;
- }
- break;
-
- case BBJ_EHCATCHRET:
- // For long address (default): `adrp + add` will be emitted.
- // For short address (proven later): `adr` will be emitted.
- getEmitter()->emitIns_R_L(INS_adr, EA_PTRSIZE, block->bbJumpDest, REG_INTRET);
-
- __fallthrough;
-
- case BBJ_EHFINALLYRET:
- case BBJ_EHFILTERRET:
- genReserveFuncletEpilog(block);
- break;
-
- case BBJ_NONE:
- case BBJ_COND:
- case BBJ_SWITCH:
- break;
-
- default:
- noway_assert(!"Unexpected bbJumpKind");
- break;
+ inst_JMP(EJ_jmp, block->bbNext->bbJumpDest);
}
-#ifdef DEBUG
- compiler->compCurBB = 0;
-#endif
-
- } //------------------ END-FOR each block of the method -------------------
-
- /* Nothing is live at this point */
- genUpdateLife(VarSetOps::MakeEmpty(compiler));
-
- /* Finalize the spill tracking logic */
-
- regSet.rsSpillEnd();
-
- /* Finalize the temp tracking logic */
-
- compiler->tmpEnd();
+ getEmitter()->emitEnableGC();
+ }
-#ifdef DEBUG
- if (compiler->verbose)
+ // The BBJ_ALWAYS is used because the BBJ_CALLFINALLY can't point to the
+ // jump target using bbJumpDest - that is already used to point
+ // to the finally block. So just skip past the BBJ_ALWAYS unless the
+ // block is RETLESS.
+ if (!(block->bbFlags & BBF_RETLESS_CALL))
{
- printf("\n# ");
- printf("compCycleEstimate = %6d, compSizeEstimate = %5d ", compiler->compCycleEstimate,
- compiler->compSizeEstimate);
- printf("%s\n", compiler->info.compFullName);
+ assert(block->isBBCallAlwaysPair());
+
+ lblk = block;
+ block = block->bbNext;
}
-#endif
+ return block;
}
-// return the child that has the same reg as the dst (if any)
-// other child returned (out param) in 'other'
-// TODO-Cleanup: move to CodeGenCommon.cpp
-GenTree* sameRegAsDst(GenTree* tree, GenTree*& other /*out*/)
+void CodeGen::genEHCatchRet(BasicBlock* block)
{
- if (tree->gtRegNum == REG_NA)
- {
- other = nullptr;
- return NULL;
- }
-
- GenTreePtr op1 = tree->gtOp.gtOp1;
- GenTreePtr op2 = tree->gtOp.gtOp2;
- if (op1->gtRegNum == tree->gtRegNum)
- {
- other = op2;
- return op1;
- }
- if (op2->gtRegNum == tree->gtRegNum)
- {
- other = op1;
- return op2;
- }
- else
- {
- other = nullptr;
- return NULL;
- }
+ // For long address (default): `adrp + add` will be emitted.
+ // For short address (proven later): `adr` will be emitted.
+ getEmitter()->emitIns_R_L(INS_adr, EA_PTRSIZE, block->bbJumpDest, REG_INTRET);
}
// move an immediate value into an integer register
@@ -3397,12 +2695,13 @@ void CodeGen::genCodeForTreeNode(GenTreePtr treeNode)
break;
case GT_LIST:
+ case GT_FIELD_LIST:
case GT_ARGPLACE:
// Nothing to do
break;
case GT_PUTARG_STK:
- genPutArgStk(treeNode);
+ genPutArgStk(treeNode->AsPutArgStk());
break;
case GT_PUTARG_REG:
@@ -3432,7 +2731,7 @@ void CodeGen::genCodeForTreeNode(GenTreePtr treeNode)
case GT_LOCKADD:
case GT_XCHG:
case GT_XADD:
- genLockedInstructions(treeNode);
+ genLockedInstructions(treeNode->AsOp());
break;
case GT_MEMORYBARRIER:
@@ -3597,7 +2896,8 @@ void CodeGen::genCodeForTreeNode(GenTreePtr treeNode)
{
#ifdef DEBUG
char message[256];
- sprintf(message, "Unimplemented node type %s\n", GenTree::NodeName(treeNode->OperGet()));
+ _snprintf_s(message, _countof(message), _TRUNCATE, "Unimplemented node type %s\n",
+ GenTree::NodeName(treeNode->OperGet()));
#endif
assert(!"Unknown node in codegen");
}
@@ -3998,9 +3298,11 @@ BAILOUT:
if (endLabel != nullptr)
genDefineTempLabel(endLabel);
- // Write the lvaShadowSPfirst stack frame slot
- noway_assert(compiler->lvaLocAllocSPvar != BAD_VAR_NUM);
- getEmitter()->emitIns_S_R(ins_Store(TYP_I_IMPL), EA_PTRSIZE, targetReg, compiler->lvaLocAllocSPvar, 0);
+ // Write the lvaLocAllocSPvar stack frame slot
+ if (compiler->lvaLocAllocSPvar != BAD_VAR_NUM)
+ {
+ getEmitter()->emitIns_S_R(ins_Store(TYP_I_IMPL), EA_PTRSIZE, targetReg, compiler->lvaLocAllocSPvar, 0);
+ }
#if STACK_PROBES
if (compiler->opts.compNeedStackProbes)
@@ -4034,6 +3336,10 @@ void CodeGen::genCodeForInitBlkUnroll(GenTreeBlk* initBlkNode)
unsigned size = initBlkNode->Size();
GenTreePtr dstAddr = initBlkNode->Addr();
GenTreePtr initVal = initBlkNode->Data();
+ if (initVal->OperIsInitVal())
+ {
+ initVal = initVal->gtGetOp1();
+ }
assert(!dstAddr->isContained());
assert(!initVal->isContained());
@@ -4043,8 +3349,7 @@ void CodeGen::genCodeForInitBlkUnroll(GenTreeBlk* initBlkNode)
emitter *emit = getEmitter();
- genConsumeReg(initVal);
- genConsumeReg(dstAddr);
+ genConsumeOperands(initBlkNode);
// If the initVal was moved, or spilled and reloaded to a different register,
// get the original initVal from below the GT_RELOAD, but only after capturing the valReg,
@@ -4066,27 +3371,25 @@ void CodeGen::genCodeForInitBlk(GenTreeBlk* initBlkNode)
unsigned size = initBlkNode->Size();
GenTreePtr dstAddr = initBlkNode->Addr();
GenTreePtr initVal = initBlkNode->Data();
+ if (initVal->OperIsInitVal())
+ {
+ initVal = initVal->gtGetOp1();
+ }
assert(!dstAddr->isContained());
assert(!initVal->isContained());
assert(initBlkNode->gtRsvdRegs == RBM_ARG_2);
- if (size == 0)
- {
- noway_assert(initBlkNode->gtOper == GT_DYN_BLK);
- genConsumeRegAndCopy(initBlkNode->AsDynBlk()->gtDynamicSize, REG_ARG_2);
- }
- else
- {
// TODO-ARM64-CQ: When initblk loop unrolling is implemented
// put this assert back on.
#if 0
- assert(size >= INITBLK_UNROLL_LIMIT);
-#endif // 0
- genSetRegToIcon(REG_ARG_2, size);
+ if (size != 0)
+ {
+ assert(blockSize >= INITBLK_UNROLL_LIMIT);
}
- genConsumeRegAndCopy(initVal, REG_ARG_1);
- genConsumeRegAndCopy(dstAddr, REG_ARG_0);
+#endif // 0
+
+ genConsumeBlockOp(initBlkNode, REG_ARG_0, REG_ARG_1, REG_ARG_2);
genEmitHelperCall(CORINFO_HELP_MEMSET, 0, EA_UNKNOWN);
}
@@ -4238,29 +3541,38 @@ void CodeGen::genCodeForCpBlkUnroll(GenTreeBlk* cpBlkNode)
// str tempReg, [R14, #8]
void CodeGen::genCodeForCpObj(GenTreeObj* cpObjNode)
{
- // Make sure we got the arguments of the cpobj operation in the right registers
- GenTreePtr dstAddr = cpObjNode->Addr();
- GenTreePtr source = cpObjNode->Data();
- noway_assert(source->gtOper == GT_IND);
- GenTreePtr srcAddr = source->gtGetOp1();
+ GenTreePtr dstAddr = cpObjNode->Addr();
+ GenTreePtr source = cpObjNode->Data();
+ var_types srcAddrType = TYP_BYREF;
+ bool sourceIsLocal = false;
+
+ assert(source->isContained());
+ if (source->gtOper == GT_IND)
+ {
+ GenTree* srcAddr = source->gtGetOp1();
+ assert(!srcAddr->isContained());
+ srcAddrType = srcAddr->TypeGet();
+ }
+ else
+ {
+ noway_assert(source->IsLocal());
+ sourceIsLocal = true;
+ }
bool dstOnStack = dstAddr->OperIsLocalAddr();
#ifdef DEBUG
assert(!dstAddr->isContained());
- assert(!srcAddr->isContained());
// This GenTree node has data about GC pointers, this means we're dealing
// with CpObj.
assert(cpObjNode->gtGcPtrCount > 0);
#endif // DEBUG
- // Consume these registers.
+ // Consume the operands and get them into the right registers.
// They may now contain gc pointers (depending on their type; gcMarkRegPtrVal will "do the right thing").
- genConsumeRegAndCopy(srcAddr, REG_WRITE_BARRIER_SRC_BYREF);
- gcInfo.gcMarkRegPtrVal(REG_WRITE_BARRIER_SRC_BYREF, srcAddr->TypeGet());
-
- genConsumeRegAndCopy(dstAddr, REG_WRITE_BARRIER_DST_BYREF);
+ genConsumeBlockOp(cpObjNode, REG_WRITE_BARRIER_DST_BYREF, REG_WRITE_BARRIER_SRC_BYREF, REG_NA);
+ gcInfo.gcMarkRegPtrVal(REG_WRITE_BARRIER_SRC_BYREF, srcAddrType);
gcInfo.gcMarkRegPtrVal(REG_WRITE_BARRIER_DST_BYREF, dstAddr->TypeGet());
// Temp register used to perform the sequence of loads and stores.
@@ -4332,31 +3644,17 @@ void CodeGen::genCodeForCpBlk(GenTreeBlk* cpBlkNode)
// Make sure we got the arguments of the cpblk operation in the right registers
unsigned blockSize = cpBlkNode->Size();
GenTreePtr dstAddr = cpBlkNode->Addr();
- GenTreePtr source = cpBlkNode->Data();
- noway_assert(source->gtOper == GT_IND);
- GenTreePtr srcAddr = source->gtGetOp1();
-
assert(!dstAddr->isContained());
- assert(!srcAddr->isContained());
- assert(cpBlkNode->gtRsvdRegs == RBM_ARG_2);
- if (blockSize != 0)
- {
+ genConsumeBlockOp(cpBlkNode, REG_ARG_0, REG_ARG_1, REG_ARG_2);
+
#if 0
// Enable this when we support cpblk loop unrolling.
-
- assert(blockSize->gtIntCon.gtIconVal >= CPBLK_UNROLL_LIMIT);
-
-#endif // 0
- genSetRegToIcon(REG_ARG_2, blockSize);
- }
- else
+ if (blockSize != 0)
{
- noway_assert(cpBlkNode->gtOper == GT_DYN_BLK);
- genConsumeRegAndCopy(cpBlkNode->AsDynBlk()->gtDynamicSize, REG_ARG_2);
+ assert(blockSize->gtIntCon.gtIconVal >= CPBLK_UNROLL_LIMIT);
}
- genConsumeRegAndCopy(srcAddr, REG_ARG_1);
- genConsumeRegAndCopy(dstAddr, REG_ARG_0);
+#endif // 0
genEmitHelperCall(CORINFO_HELP_MEMCPY, 0, EA_UNKNOWN);
}
@@ -4421,7 +3719,7 @@ void CodeGen::genJumpTable(GenTree* treeNode)
// generate code for the locked operations:
// GT_LOCKADD, GT_XCHG, GT_XADD
-void CodeGen::genLockedInstructions(GenTree* treeNode)
+void CodeGen::genLockedInstructions(GenTreeOp* treeNode)
{
#if 0
GenTree* data = treeNode->gtOp.gtOp2;
@@ -4839,154 +4137,6 @@ void CodeGen::genCodeForShift(GenTreePtr tree)
genProduceReg(tree);
}
-// TODO-Cleanup: move to CodeGenCommon.cpp
-void CodeGen::genUnspillRegIfNeeded(GenTree* tree)
-{
- regNumber dstReg = tree->gtRegNum;
-
- GenTree* unspillTree = tree;
- if (tree->gtOper == GT_RELOAD)
- {
- unspillTree = tree->gtOp.gtOp1;
- }
-
- if (unspillTree->gtFlags & GTF_SPILLED)
- {
- if (genIsRegCandidateLocal(unspillTree))
- {
- // Reset spilled flag, since we are going to load a local variable from its home location.
- unspillTree->gtFlags &= ~GTF_SPILLED;
-
- GenTreeLclVarCommon* lcl = unspillTree->AsLclVarCommon();
- LclVarDsc* varDsc = &compiler->lvaTable[lcl->gtLclNum];
-
- var_types targetType = unspillTree->gtType;
- instruction ins = ins_Load(targetType, compiler->isSIMDTypeLocalAligned(lcl->gtLclNum));
- emitAttr attr = emitTypeSize(targetType);
- emitter* emit = getEmitter();
-
- // Fixes Issue #3326
- attr = emit->emitInsAdjustLoadStoreAttr(ins, attr);
-
- // Load local variable from its home location.
- inst_RV_TT(ins, dstReg, unspillTree, 0, attr);
-
- unspillTree->SetInReg();
-
- // TODO-Review: We would like to call:
- // genUpdateRegLife(varDsc, /*isBorn*/ true, /*isDying*/ false DEBUGARG(tree));
- // instead of the following code, but this ends up hitting this assert:
- // assert((regSet.rsMaskVars & regMask) == 0);
- // due to issues with LSRA resolution moves.
- // So, just force it for now. This probably indicates a condition that creates a GC hole!
- //
- // Extra note: I think we really want to call something like gcInfo.gcUpdateForRegVarMove,
- // because the variable is not really going live or dead, but that method is somewhat poorly
- // factored because it, in turn, updates rsMaskVars which is part of RegSet not GCInfo.
- // This code exists in other CodeGen*.cpp files.
-
- // Don't update the variable's location if we are just re-spilling it again.
-
- if ((unspillTree->gtFlags & GTF_SPILL) == 0)
- {
- genUpdateVarReg(varDsc, tree);
-#ifdef DEBUG
- if (VarSetOps::IsMember(compiler, gcInfo.gcVarPtrSetCur, varDsc->lvVarIndex))
- {
- JITDUMP("\t\t\t\t\t\t\tRemoving V%02u from gcVarPtrSetCur\n", lcl->gtLclNum);
- }
-#endif // DEBUG
- VarSetOps::RemoveElemD(compiler, gcInfo.gcVarPtrSetCur, varDsc->lvVarIndex);
-
-#ifdef DEBUG
- if (compiler->verbose)
- {
- printf("\t\t\t\t\t\t\tV%02u in reg ", lcl->gtLclNum);
- varDsc->PrintVarReg();
- printf(" is becoming live ");
- compiler->printTreeID(unspillTree);
- printf("\n");
- }
-#endif // DEBUG
-
- 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->TypeGet()), dstReg,
- t->tdTempNum(), 0);
- compiler->tmpRlsTemp(t);
-
- unspillTree->gtFlags &= ~GTF_SPILLED;
- unspillTree->SetInReg();
- gcInfo.gcMarkRegPtrVal(dstReg, unspillTree->TypeGet());
- }
- }
-}
-
-// Do Liveness update for a subnodes that is being consumed by codegen
-// including the logic for reload in case is needed and also takes care
-// of locating the value on the desired register.
-void CodeGen::genConsumeRegAndCopy(GenTree* tree, regNumber needReg)
-{
- regNumber treeReg = genConsumeReg(tree);
- if (treeReg != needReg)
- {
- var_types targetType = tree->TypeGet();
- inst_RV_RV(ins_Copy(targetType), needReg, treeReg, targetType);
- }
-}
-
void CodeGen::genRegCopy(GenTree* treeNode)
{
assert(treeNode->OperGet() == GT_COPY);
@@ -5049,261 +4199,6 @@ void CodeGen::genRegCopy(GenTree* treeNode)
genProduceReg(treeNode);
}
-// Do liveness update for a subnode that is being consumed by codegen.
-// TODO-Cleanup: move to CodeGenCommon.cpp
-regNumber CodeGen::genConsumeReg(GenTree* tree)
-{
- if (tree->OperGet() == GT_COPY)
- {
- genRegCopy(tree);
- }
- // Handle the case where we have a lclVar that needs to be copied before use (i.e. because it
- // interferes with one of the other sources (or the target, if it's a "delayed use" register)).
- // TODO-Cleanup: This is a special copyReg case in LSRA - consider eliminating these and
- // always using GT_COPY to make the lclVar location explicit.
- // Note that we have to do this before calling genUpdateLife because otherwise if we spill it
- // the lvRegNum will be set to REG_STK and we will lose track of what register currently holds
- // the lclVar (normally when a lclVar is spilled it is then used from its former register
- // location, which matches the gtRegNum on the node).
- // (Note that it doesn't matter if we call this before or after genUnspillRegIfNeeded
- // because if it's on the stack it will always get reloaded into tree->gtRegNum).
- if (genIsRegCandidateLocal(tree))
- {
- GenTreeLclVarCommon* lcl = tree->AsLclVarCommon();
- LclVarDsc* varDsc = &compiler->lvaTable[lcl->GetLclNum()];
- if ((varDsc->lvRegNum != REG_STK) && (varDsc->lvRegNum != tree->gtRegNum))
- {
- inst_RV_RV(ins_Copy(tree->TypeGet()), tree->gtRegNum, varDsc->lvRegNum);
- }
- }
-
- genUnspillRegIfNeeded(tree);
-
- // genUpdateLife() will also spill local var if marked as GTF_SPILL by calling CodeGen::genSpillVar
- genUpdateLife(tree);
- assert(tree->gtRegNum != REG_NA);
-
- // there are three cases where consuming a reg means clearing the bit in the live mask
- // 1. it was not produced by a local
- // 2. it was produced by a local that is going dead
- // 3. it was produced by a local that does not live in that reg (like one allocated on the stack)
-
- if (genIsRegCandidateLocal(tree))
- {
- GenTreeLclVarCommon* lcl = tree->AsLclVarCommon();
- LclVarDsc* varDsc = &compiler->lvaTable[lcl->GetLclNum()];
- assert(varDsc->lvLRACandidate);
-
- if ((tree->gtFlags & GTF_VAR_DEATH) != 0)
- {
- gcInfo.gcMarkRegSetNpt(genRegMask(varDsc->lvRegNum));
- }
- else if (varDsc->lvRegNum == REG_STK)
- {
- // We have loaded this into a register only temporarily
- gcInfo.gcMarkRegSetNpt(genRegMask(tree->gtRegNum));
- }
- }
- else
- {
- gcInfo.gcMarkRegSetNpt(genRegMask(tree->gtRegNum));
- }
-
- return tree->gtRegNum;
-}
-
-// Do liveness update for an address tree: one of GT_LEA, GT_LCL_VAR, or GT_CNS_INT (for call indirect).
-// TODO-Cleanup: move to CodeGenCommon.cpp
-void CodeGen::genConsumeAddress(GenTree* addr)
-{
- if (addr->OperGet() == GT_LEA)
- {
- genConsumeAddrMode(addr->AsAddrMode());
- }
- else if (!addr->isContained())
- {
- genConsumeReg(addr);
- }
-}
-
-// do liveness update for a subnode that is being consumed by codegen
-// TODO-Cleanup: move to CodeGenCommon.cpp
-void CodeGen::genConsumeAddrMode(GenTreeAddrMode* addr)
-{
- if (addr->Base())
- genConsumeReg(addr->Base());
- if (addr->Index())
- genConsumeReg(addr->Index());
-}
-
-// TODO-Cleanup: move to CodeGenCommon.cpp
-void CodeGen::genConsumeRegs(GenTree* tree)
-{
- if (tree->isContained())
- {
- if (tree->isIndir())
- {
- genConsumeAddress(tree->AsIndir()->Addr());
- }
- else if (tree->OperGet() == GT_AND)
- {
- // This is the special contained GT_AND that we created in Lowering::LowerCmp()
- // Now we need to consume the operands of the GT_AND node.
- genConsumeOperands(tree->AsOp());
- }
- else
- {
- assert(tree->OperIsLeaf());
- }
- }
- else
- {
- genConsumeReg(tree);
- }
-}
-
-//------------------------------------------------------------------------
-// genConsumeOperands: Do liveness update for the operands of a unary or binary tree
-//
-// Arguments:
-// tree - the GenTreeOp whose operands will have their liveness updated.
-//
-// Return Value:
-// None.
-//
-// Notes:
-// Note that this logic is localized here because we must do the liveness update in
-// the correct execution order. This is important because we may have two operands
-// that involve the same lclVar, and if one is marked "lastUse" we must handle it
-// after the first.
-// TODO-Cleanup: move to CodeGenCommon.cpp
-
-void CodeGen::genConsumeOperands(GenTreeOp* tree)
-{
- GenTree* firstOp = tree->gtOp1;
- GenTree* secondOp = tree->gtOp2;
- if ((tree->gtFlags & GTF_REVERSE_OPS) != 0)
- {
- assert(secondOp != nullptr);
- firstOp = secondOp;
- secondOp = tree->gtOp1;
- }
- if (firstOp != nullptr)
- {
- genConsumeRegs(firstOp);
- }
- if (secondOp != nullptr)
- {
- genConsumeRegs(secondOp);
- }
-}
-
-// do liveness update for register produced by the current node in codegen
-// TODO-Cleanup: move to CodeGenCommon.cpp
-void CodeGen::genProduceReg(GenTree* tree)
-{
- if (tree->gtFlags & GTF_SPILL)
- {
- if (genIsRegCandidateLocal(tree))
- {
- // Store local variable to its home location.
- tree->gtFlags &= ~GTF_REG_VAL;
- inst_TT_RV(ins_Store(tree->gtType, compiler->isSIMDTypeLocalAligned(tree->gtLclVarCommon.gtLclNum)), tree,
- tree->gtRegNum);
- }
- else
- {
- tree->SetInReg();
- regSet.rsSpillTree(tree->gtRegNum, tree);
- tree->gtFlags |= GTF_SPILLED;
- tree->gtFlags &= ~GTF_SPILL;
- gcInfo.gcMarkRegSetNpt(genRegMask(tree->gtRegNum));
- return;
- }
- }
-
- genUpdateLife(tree);
-
- // If we've produced a register, mark it as a pointer, as needed.
- if (tree->gtHasReg())
- {
- // We only mark the register in the following cases:
- // 1. It is not a register candidate local. In this case, we're producing a
- // register from a local, but the local is not a register candidate. Thus,
- // we must be loading it as a temp register, and any "last use" flag on
- // the register wouldn't be relevant.
- // 2. The register candidate local is going dead. There's no point to mark
- // the register as live, with a GC pointer, if the variable is dead.
- if (!genIsRegCandidateLocal(tree) || ((tree->gtFlags & GTF_VAR_DEATH) == 0))
- {
- gcInfo.gcMarkRegPtrVal(tree->gtRegNum, tree->TypeGet());
- }
- }
- tree->SetInReg();
-}
-
-// transfer gc/byref status of src reg to dst reg
-// TODO-Cleanup: move to CodeGenCommon.cpp
-void CodeGen::genTransferRegGCState(regNumber dst, regNumber src)
-{
- regMaskTP srcMask = genRegMask(src);
- regMaskTP dstMask = genRegMask(dst);
-
- if (gcInfo.gcRegGCrefSetCur & srcMask)
- {
- gcInfo.gcMarkRegSetGCref(dstMask);
- }
- else if (gcInfo.gcRegByrefSetCur & srcMask)
- {
- gcInfo.gcMarkRegSetByref(dstMask);
- }
- else
- {
- gcInfo.gcMarkRegSetNpt(dstMask);
- }
-}
-
-// generates an ip-relative call or indirect call via reg ('call reg')
-// 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)
-// TODO-Cleanup: move to CodeGenCommon.cpp
-void CodeGen::genEmitCall(int callType,
- CORINFO_METHOD_HANDLE methHnd,
- INDEBUG_LDISASM_COMMA(CORINFO_SIG_INFO* sigInfo) void* addr,
- emitAttr retSize,
- emitAttr secondRetSize,
- IL_OFFSETX ilOffset,
- regNumber base,
- bool isJump,
- bool isNoGC)
-{
-
- getEmitter()->emitIns_Call(emitter::EmitCallType(callType), methHnd, INDEBUG_LDISASM_COMMA(sigInfo) addr, 0,
- retSize, secondRetSize, gcInfo.gcVarPtrSetCur, gcInfo.gcRegGCrefSetCur,
- gcInfo.gcRegByrefSetCur, ilOffset, base, REG_NA, 0, 0, isJump,
- emitter::emitNoGChelper(compiler->eeGetHelperNum(methHnd)));
-}
-
-// 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)
-// TODO-Cleanup: move to CodeGenCommon.cpp
-void CodeGen::genEmitCall(int callType,
- CORINFO_METHOD_HANDLE methHnd,
- INDEBUG_LDISASM_COMMA(CORINFO_SIG_INFO* sigInfo) GenTreeIndir* indir,
- emitAttr retSize,
- emitAttr secondRetSize,
- IL_OFFSETX ilOffset)
-{
- genConsumeAddress(indir->Addr());
-
- getEmitter()->emitIns_Call(emitter::EmitCallType(callType), methHnd, INDEBUG_LDISASM_COMMA(sigInfo) nullptr, 0,
- retSize, secondRetSize, gcInfo.gcVarPtrSetCur, gcInfo.gcRegGCrefSetCur,
- gcInfo.gcRegByrefSetCur, ilOffset, indir->Base() ? indir->Base()->gtRegNum : REG_NA,
- indir->Index() ? indir->Index()->gtRegNum : REG_NA, indir->Scale(), indir->Offset());
-}
-
// Produce code for a GT_CALL node
void CodeGen::genCallInstruction(GenTreePtr node)
{
@@ -5321,7 +4216,7 @@ void CodeGen::genCallInstruction(GenTreePtr node)
// Consume all the arg regs
for (GenTreePtr list = call->gtCallLateArgs; list; list = list->MoveNext())
{
- assert(list->IsList());
+ assert(list->OperIsList());
GenTreePtr argNode = list->Current();
@@ -5332,7 +4227,7 @@ void CodeGen::genCallInstruction(GenTreePtr node)
continue;
// Deal with multi register passed struct args.
- if (argNode->OperGet() == GT_LIST)
+ if (argNode->OperGet() == GT_FIELD_LIST)
{
GenTreeArgList* argListPtr = argNode->AsArgList();
unsigned iterationNum = 0;
@@ -5457,7 +4352,6 @@ void CodeGen::genCallInstruction(GenTreePtr node)
}
}
-#ifdef DEBUGGING_SUPPORT
// We need to propagate the IL offset information to the call instruction, so we can emit
// an IL to native mapping record for the call, to support managed return value debugging.
// We don't want tail call helper calls that were converted from normal calls to get a record,
@@ -5466,7 +4360,6 @@ void CodeGen::genCallInstruction(GenTreePtr node)
{
(void)compiler->genCallSite2ILOffsetMap->Lookup(call, &ilOffset);
}
-#endif // DEBUGGING_SUPPORT
if (target != nullptr)
{
@@ -6703,7 +5596,7 @@ void CodeGen::genIntrinsic(GenTreePtr treeNode)
// Return value:
// None
//
-void CodeGen::genPutArgStk(GenTreePtr treeNode)
+void CodeGen::genPutArgStk(GenTreePutArgStk* treeNode)
{
assert(treeNode->OperGet() == GT_PUTARG_STK);
var_types targetType = treeNode->TypeGet();
@@ -6759,7 +5652,7 @@ void CodeGen::genPutArgStk(GenTreePtr treeNode)
varNumOut = compiler->lvaOutgoingArgSpaceVar;
argOffsetMax = compiler->lvaOutgoingArgSpaceSize;
}
- bool isStruct = (targetType == TYP_STRUCT) || (source->OperGet() == GT_LIST);
+ bool isStruct = (targetType == TYP_STRUCT) || (source->OperGet() == GT_FIELD_LIST);
if (!isStruct) // a normal non-Struct argument
{
@@ -6785,24 +5678,24 @@ void CodeGen::genPutArgStk(GenTreePtr treeNode)
{
assert(source->isContained()); // We expect that this node was marked as contained in LowerArm64
- if (source->OperGet() == GT_LIST)
+ if (source->OperGet() == GT_FIELD_LIST)
{
// Deal with the multi register passed struct args.
- GenTreeArgList* argListPtr = source->AsArgList();
+ GenTreeFieldList* fieldListPtr = source->AsFieldList();
- // Evaluate each of the GT_LIST items into their register
+ // Evaluate each of the GT_FIELD_LIST items into their register
// and store their register into the outgoing argument area
- for (; argListPtr != nullptr; argListPtr = argListPtr->Rest())
+ for (; fieldListPtr != nullptr; fieldListPtr = fieldListPtr->Rest())
{
- GenTreePtr nextArgNode = argListPtr->gtOp.gtOp1;
+ GenTreePtr nextArgNode = fieldListPtr->gtOp.gtOp1;
genConsumeReg(nextArgNode);
regNumber reg = nextArgNode->gtRegNum;
var_types type = nextArgNode->TypeGet();
emitAttr attr = emitTypeSize(type);
- // Emit store instructions to store the registers produced by the GT_LIST into the outgoing argument
- // area
+ // Emit store instructions to store the registers produced by the GT_FIELD_LIST into the outgoing
+ // argument area
emit->emitIns_S_R(ins_Store(type), attr, reg, varNumOut, argOffsetOut);
argOffsetOut += EA_SIZE_IN_BYTES(attr);
assert(argOffsetOut <= argOffsetMax); // We can't write beyound the outgoing area area
@@ -7159,7 +6052,6 @@ void CodeGen::genCreateAndStoreGCInfoX64(unsigned codeSize, unsigned prologSize
// Now we can actually use those slot ID's to declare live ranges.
gcInfo.gcMakeRegPtrTable(gcInfoEncoder, codeSize, prologSize, GCInfo::MAKE_REG_PTR_MODE_DO_WORK);
-#if defined(DEBUGGING_SUPPORT)
if (compiler->opts.compDbgEnC)
{
// what we have to preserve is called the "frame header" (see comments in VM\eetwain.cpp)
@@ -7183,7 +6075,6 @@ void CodeGen::genCreateAndStoreGCInfoX64(unsigned codeSize, unsigned prologSize
// frame
gcInfoEncoder->SetSizeOfEditAndContinuePreservedArea(preservedAreaSize);
}
-#endif
gcInfoEncoder->Build();
@@ -7249,58 +6140,6 @@ void CodeGen::genEmitHelperCall(unsigned helper, int argSize, emitAttr retSize,
regTracker.rsTrashRegsForGCInterruptability();
}
-/*****************************************************************************/
-#ifdef DEBUGGING_SUPPORT
-/*****************************************************************************
- * genSetScopeInfo
- *
- * Called for every scope info piece to record by the main genSetScopeInfo()
- */
-
-// TODO-Cleanup: move to CodeGenCommon.cpp
-void CodeGen::genSetScopeInfo(unsigned which,
- UNATIVE_OFFSET startOffs,
- UNATIVE_OFFSET length,
- unsigned varNum,
- unsigned LVnum,
- bool avail,
- Compiler::siVarLoc& varLoc)
-{
- /* We need to do some mapping while reporting back these variables */
-
- unsigned ilVarNum = compiler->compMap2ILvarNum(varNum);
- noway_assert((int)ilVarNum != ICorDebugInfo::UNKNOWN_ILNUM);
-
- VarName name = nullptr;
-
-#ifdef DEBUG
-
- for (unsigned scopeNum = 0; scopeNum < compiler->info.compVarScopesCount; scopeNum++)
- {
- if (LVnum == compiler->info.compVarScopes[scopeNum].vsdLVnum)
- {
- name = compiler->info.compVarScopes[scopeNum].vsdName;
- }
- }
-
- // Hang on to this compiler->info.
-
- TrnslLocalVarInfo& tlvi = genTrnslLocalVarInfo[which];
-
- tlvi.tlviVarNum = ilVarNum;
- tlvi.tlviLVnum = LVnum;
- tlvi.tlviName = name;
- tlvi.tlviStartPC = startOffs;
- tlvi.tlviLength = length;
- tlvi.tlviAvailable = avail;
- tlvi.tlviVarLoc = varLoc;
-
-#endif // DEBUG
-
- compiler->eeSetLVinfo(which, startOffs, length, ilVarNum, LVnum, name, avail, varLoc);
-}
-#endif // DEBUGGING_SUPPORT
-
/*****************************************************************************
* Unit testing of the ARM64 emitter: generate a bunch of instructions into the prolog
* (it's as good a place as any), then use COMPlus_JitLateDisasm=* to see if the late
diff --git a/src/jit/codegenclassic.h b/src/jit/codegenclassic.h
index 81b7b34194..3a88c83915 100644
--- a/src/jit/codegenclassic.h
+++ b/src/jit/codegenclassic.h
@@ -63,10 +63,6 @@ void genPInvokeCallEpilog(LclVarDsc* varDsc, regMaskTP retVal);
regNumber genLclHeap(GenTreePtr size);
-void genSinglePush();
-
-void genSinglePop();
-
void genDyingVars(VARSET_VALARG_TP beforeSet, VARSET_VALARG_TP afterSet);
bool genContainsVarDeath(GenTreePtr from, GenTreePtr to, unsigned varNum);
@@ -287,9 +283,6 @@ void genCodeForJumpTable(GenTreePtr tree);
void genCodeForSwitchTable(GenTreePtr tree);
void genCodeForSwitch(GenTreePtr tree);
-regMaskTP genPushRegs(regMaskTP regs, regMaskTP* byrefRegs, regMaskTP* noRefRegs);
-void genPopRegs(regMaskTP regs, regMaskTP byrefRegs, regMaskTP noRefRegs);
-
size_t genPushArgList(GenTreePtr call);
#ifdef _TARGET_ARM_
diff --git a/src/jit/codegencommon.cpp b/src/jit/codegencommon.cpp
index 2710447ade..240911523f 100755..100644
--- a/src/jit/codegencommon.cpp
+++ b/src/jit/codegencommon.cpp
@@ -103,6 +103,10 @@ CodeGen::CodeGen(Compiler* theCompiler) : CodeGenInterface(theCompiler)
u8ToDblBitmask = nullptr;
#endif // defined(_TARGET_XARCH_) && !FEATURE_STACK_FP_X87
+#if defined(FEATURE_PUT_STRUCT_ARG_STK) && !defined(_TARGET_X86_)
+ m_stkArgVarNum = BAD_VAR_NUM;
+#endif
+
regTracker.rsTrackInit(compiler, &regSet);
gcInfo.regSet = &regSet;
m_cgEmitter = new (compiler->getAllocator()) emitter();
@@ -163,12 +167,10 @@ CodeGen::CodeGen(Compiler* theCompiler) : CodeGenInterface(theCompiler)
genFlagsEqualToNone();
#endif // LEGACY_BACKEND
-#ifdef DEBUGGING_SUPPORT
// Initialize the IP-mapping logic.
compiler->genIPmappingList = nullptr;
compiler->genIPmappingLast = nullptr;
compiler->genCallSite2ILOffsetMap = nullptr;
-#endif
/* Assume that we not fully interruptible */
@@ -359,7 +361,7 @@ void CodeGen::genPrepForCompiler()
{
VarSetOps::AddElemD(compiler, compiler->raRegVarsMask, varDsc->lvVarIndex);
}
- else if (compiler->lvaIsGCTracked(varDsc) && (!varDsc->lvIsParam || varDsc->lvIsRegArg))
+ else if (compiler->lvaIsGCTracked(varDsc))
{
VarSetOps::AddElemD(compiler, gcInfo.gcTrkStkPtrLcls, varDsc->lvVarIndex);
}
@@ -646,23 +648,32 @@ regMaskTP Compiler::compHelperCallKillSet(CorInfoHelpFunc helper)
return RBM_RSI | RBM_RDI | RBM_CALLEE_TRASH;
#elif defined(_TARGET_ARM64_)
return RBM_CALLEE_TRASH_NOGC;
+#elif defined(_TARGET_X86_)
+ return RBM_ESI | RBM_EDI | RBM_ECX;
#else
NYI("Model kill set for CORINFO_HELP_ASSIGN_BYREF on target arch");
return RBM_CALLEE_TRASH;
#endif
case CORINFO_HELP_PROF_FCN_ENTER:
-#ifdef _TARGET_AMD64_
+#ifdef RBM_PROFILER_ENTER_TRASH
return RBM_PROFILER_ENTER_TRASH;
#else
- unreached();
+ NYI("Model kill set for CORINFO_HELP_PROF_FCN_ENTER on target arch");
#endif
+
case CORINFO_HELP_PROF_FCN_LEAVE:
- case CORINFO_HELP_PROF_FCN_TAILCALL:
-#ifdef _TARGET_AMD64_
+#ifdef RBM_PROFILER_LEAVE_TRASH
return RBM_PROFILER_LEAVE_TRASH;
#else
- unreached();
+ NYI("Model kill set for CORINFO_HELP_PROF_FCN_LEAVE on target arch");
+#endif
+
+ case CORINFO_HELP_PROF_FCN_TAILCALL:
+#ifdef RBM_PROFILER_TAILCALL_TRASH
+ return RBM_PROFILER_TAILCALL_TRASH;
+#else
+ NYI("Model kill set for CORINFO_HELP_PROF_FCN_TAILCALL on target arch");
#endif
case CORINFO_HELP_STOP_FOR_GC:
@@ -685,26 +696,34 @@ regMaskTP Compiler::compHelperCallKillSet(CorInfoHelpFunc helper)
regMaskTP Compiler::compNoGCHelperCallKillSet(CorInfoHelpFunc helper)
{
assert(emitter::emitNoGChelper(helper));
-#ifdef _TARGET_AMD64_
+
switch (helper)
{
+#if defined(_TARGET_AMD64_) || defined(_TARGET_X86_)
case CORINFO_HELP_PROF_FCN_ENTER:
return RBM_PROFILER_ENTER_TRASH;
case CORINFO_HELP_PROF_FCN_LEAVE:
- case CORINFO_HELP_PROF_FCN_TAILCALL:
return RBM_PROFILER_LEAVE_TRASH;
+ case CORINFO_HELP_PROF_FCN_TAILCALL:
+ return RBM_PROFILER_TAILCALL_TRASH;
+#endif // defined(_TARGET_AMD64_) || defined(_TARGET_X86_)
+
case CORINFO_HELP_ASSIGN_BYREF:
+#if defined(_TARGET_AMD64_)
// this helper doesn't trash RSI and RDI
return RBM_CALLEE_TRASH_NOGC & ~(RBM_RSI | RBM_RDI);
+#elif defined(_TARGET_X86_)
+ // This helper only trashes ECX.
+ return RBM_ECX;
+#else
+ return RBM_CALLEE_TRASH_NOGC;
+#endif // defined(_TARGET_AMD64_)
default:
return RBM_CALLEE_TRASH_NOGC;
}
-#else
- return RBM_CALLEE_TRASH_NOGC;
-#endif
}
// Update liveness (always var liveness, i.e., compCurLife, and also, if "ForCodeGen" is true, reg liveness, i.e.,
@@ -1004,9 +1023,7 @@ void Compiler::compUpdateLifeVar(GenTreePtr tree, VARSET_TP* pLastUseVars)
#endif // LEGACY_BACKEND
-#ifdef DEBUGGING_SUPPORT
codeGen->siUpdate();
-#endif
}
}
@@ -1172,9 +1189,7 @@ void Compiler::compChangeLife(VARSET_VALARG_TP newLife DEBUGARG(GenTreePtr tree)
#endif // !LEGACY_BACKEND
}
-#ifdef DEBUGGING_SUPPORT
codeGen->siUpdate();
-#endif
}
// Need an explicit instantiation.
@@ -1626,6 +1641,44 @@ void CodeGen::genAdjustSP(ssize_t delta)
inst_RV_IV(INS_add, REG_SPBASE, delta, EA_PTRSIZE);
}
+//------------------------------------------------------------------------
+// genAdjustStackLevel: Adjust the stack level, if required, for a throw helper block
+//
+// Arguments:
+// block - The BasicBlock for which we are about to generate code.
+//
+// Assumptions:
+// Must be called just prior to generating code for 'block'.
+//
+// Notes:
+// This only makes an adjustment if !FEATURE_FIXED_OUT_ARGS, if there is no frame pointer,
+// and if 'block' is a throw helper block with a non-zero stack level.
+
+void CodeGen::genAdjustStackLevel(BasicBlock* block)
+{
+#if !FEATURE_FIXED_OUT_ARGS
+ // Check for inserted throw blocks and adjust genStackLevel.
+
+ if (!isFramePointerUsed() && compiler->fgIsThrowHlpBlk(block))
+ {
+ noway_assert(block->bbFlags & BBF_JMP_TARGET);
+
+ genStackLevel = compiler->fgThrowHlpBlkStkLevel(block) * sizeof(int);
+
+ if (genStackLevel != 0)
+ {
+#ifdef _TARGET_X86_
+ getEmitter()->emitMarkStackLvl(genStackLevel);
+ inst_RV_IV(INS_add, REG_SPBASE, genStackLevel, EA_PTRSIZE);
+ genStackLevel = 0;
+#else // _TARGET_X86_
+ NYI("Need emitMarkStackLvl()");
+#endif // _TARGET_X86_
+ }
+ }
+#endif // !FEATURE_FIXED_OUT_ARGS
+}
+
#ifdef _TARGET_ARM_
// return size
// alignmentWB is out param
@@ -2539,14 +2592,12 @@ emitJumpKind CodeGen::genJumpKindForOper(genTreeOps cmp, CompareKind compareKind
void CodeGen::genExitCode(BasicBlock* block)
{
-#ifdef DEBUGGING_SUPPORT
/* Just wrote the first instruction of the epilog - inform debugger
Note that this may result in a duplicate IPmapping entry, and
that this is ok */
// For non-optimized debuggable code, there is only one epilog.
genIPmappingAdd((IL_OFFSETX)ICorDebugInfo::EPILOG, true);
-#endif // DEBUGGING_SUPPORT
bool jmpEpilog = ((block->bbFlags & BBF_HAS_JMP) != 0);
if (compiler->getNeedsGSSecurityCookie())
@@ -2968,7 +3019,7 @@ void CodeGen::genGenerateCode(void** codePtr, ULONG* nativeSizeOfCode)
#if defined(DEBUG)
,
(compiler->compCodeOpt() != Compiler::SMALL_CODE) &&
- !(compiler->opts.eeFlags & CORJIT_FLG_PREJIT)
+ !compiler->opts.jitFlags->IsSet(JitFlags::JIT_FLAG_PREJIT)
#endif
#ifdef LEGACY_BACKEND
,
@@ -3095,7 +3146,8 @@ void CodeGen::genGenerateCode(void** codePtr, ULONG* nativeSizeOfCode)
We need to relax the assert as our estimation won't include code-gen
stack changes (which we know don't affect fgAddCodeRef()) */
noway_assert(getEmitter()->emitMaxStackDepth <=
- (compiler->fgPtrArgCntMax + compiler->compHndBBtabCount + // Return address for locally-called finallys
+ (compiler->fgPtrArgCntMax + // Max number of pointer-sized stack arguments.
+ compiler->compHndBBtabCount + // Return address for locally-called finallys
genTypeStSz(TYP_LONG) + // longs/doubles may be transferred via stack, etc
(compiler->compTailCallUsed ? 4 : 0))); // CORINFO_HELP_TAILCALL args
#endif
@@ -3116,8 +3168,6 @@ void CodeGen::genGenerateCode(void** codePtr, ULONG* nativeSizeOfCode)
compiler->unwindEmit(*codePtr, coldCodePtr);
-#ifdef DEBUGGING_SUPPORT
-
/* Finalize the line # tracking logic after we know the exact block sizes/offsets */
genIPmappingGen();
@@ -3126,8 +3176,6 @@ void CodeGen::genGenerateCode(void** codePtr, ULONG* nativeSizeOfCode)
genSetScopeInfo();
-#endif // DEBUGGING_SUPPORT
-
#ifdef LATE_DISASM
unsigned finalHotCodeSize;
unsigned finalColdCodeSize;
@@ -3272,6 +3320,8 @@ void CodeGen::genReportEH()
EHblkDsc* HBtab;
EHblkDsc* HBtabEnd;
+ bool isCoreRTABI = compiler->IsTargetAbi(CORINFO_CORERT_ABI);
+
unsigned EHCount = compiler->compHndBBtabCount;
#if FEATURE_EH_FUNCLETS
@@ -3279,46 +3329,55 @@ void CodeGen::genReportEH()
// VM.
unsigned duplicateClauseCount = 0;
unsigned enclosingTryIndex;
- for (XTnum = 0; XTnum < compiler->compHndBBtabCount; XTnum++)
+
+ // Duplicate clauses are not used by CoreRT ABI
+ if (!isCoreRTABI)
{
- for (enclosingTryIndex = compiler->ehTrueEnclosingTryIndexIL(XTnum); // find the true enclosing try index,
- // ignoring 'mutual protect' trys
- enclosingTryIndex != EHblkDsc::NO_ENCLOSING_INDEX;
- enclosingTryIndex = compiler->ehGetEnclosingTryIndex(enclosingTryIndex))
+ for (XTnum = 0; XTnum < compiler->compHndBBtabCount; XTnum++)
{
- ++duplicateClauseCount;
+ for (enclosingTryIndex = compiler->ehTrueEnclosingTryIndexIL(XTnum); // find the true enclosing try index,
+ // ignoring 'mutual protect' trys
+ enclosingTryIndex != EHblkDsc::NO_ENCLOSING_INDEX;
+ enclosingTryIndex = compiler->ehGetEnclosingTryIndex(enclosingTryIndex))
+ {
+ ++duplicateClauseCount;
+ }
}
+ EHCount += duplicateClauseCount;
}
- EHCount += duplicateClauseCount;
#if FEATURE_EH_CALLFINALLY_THUNKS
unsigned clonedFinallyCount = 0;
- // We don't keep track of how many cloned finally there are. So, go through and count.
- // We do a quick pass first through the EH table to see if there are any try/finally
- // clauses. If there aren't, we don't need to look for BBJ_CALLFINALLY.
-
- bool anyFinallys = false;
- for (HBtab = compiler->compHndBBtab, HBtabEnd = compiler->compHndBBtab + compiler->compHndBBtabCount;
- HBtab < HBtabEnd; HBtab++)
+ // Duplicate clauses are not used by CoreRT ABI
+ if (!isCoreRTABI)
{
- if (HBtab->HasFinallyHandler())
+ // We don't keep track of how many cloned finally there are. So, go through and count.
+ // We do a quick pass first through the EH table to see if there are any try/finally
+ // clauses. If there aren't, we don't need to look for BBJ_CALLFINALLY.
+
+ bool anyFinallys = false;
+ for (HBtab = compiler->compHndBBtab, HBtabEnd = compiler->compHndBBtab + compiler->compHndBBtabCount;
+ HBtab < HBtabEnd; HBtab++)
{
- anyFinallys = true;
- break;
+ if (HBtab->HasFinallyHandler())
+ {
+ anyFinallys = true;
+ break;
+ }
}
- }
- if (anyFinallys)
- {
- for (BasicBlock* block = compiler->fgFirstBB; block != nullptr; block = block->bbNext)
+ if (anyFinallys)
{
- if (block->bbJumpKind == BBJ_CALLFINALLY)
+ for (BasicBlock* block = compiler->fgFirstBB; block != nullptr; block = block->bbNext)
{
- ++clonedFinallyCount;
+ if (block->bbJumpKind == BBJ_CALLFINALLY)
+ {
+ ++clonedFinallyCount;
+ }
}
- }
- EHCount += clonedFinallyCount;
+ EHCount += clonedFinallyCount;
+ }
}
#endif // FEATURE_EH_CALLFINALLY_THUNKS
@@ -3373,6 +3432,23 @@ void CodeGen::genReportEH()
CORINFO_EH_CLAUSE_FLAGS flags = ToCORINFO_EH_CLAUSE_FLAGS(HBtab->ebdHandlerType);
+ if (isCoreRTABI && (XTnum > 0))
+ {
+ // For CoreRT, CORINFO_EH_CLAUSE_SAMETRY flag means that the current clause covers same
+ // try block as the previous one. The runtime cannot reliably infer this information from
+ // native code offsets because of different try blocks can have same offsets. Alternative
+ // solution to this problem would be inserting extra nops to ensure that different try
+ // blocks have different offsets.
+ if (EHblkDsc::ebdIsSameTry(HBtab, HBtab - 1))
+ {
+ // The SAMETRY bit should only be set on catch clauses. This is ensured in IL, where only 'catch' is
+ // allowed to be mutually-protect. E.g., the C# "try {} catch {} catch {} finally {}" actually exists in
+ // IL as "try { try {} catch {} catch {} } finally {}".
+ assert(HBtab->HasCatchHandler());
+ flags = (CORINFO_EH_CLAUSE_FLAGS)(flags | CORINFO_EH_CLAUSE_SAMETRY);
+ }
+ }
+
// Note that we reuse the CORINFO_EH_CLAUSE type, even though the names of
// the fields aren't accurate.
@@ -3578,9 +3654,7 @@ void CodeGen::genReportEH()
CORINFO_EH_CLAUSE_FLAGS flags = ToCORINFO_EH_CLAUSE_FLAGS(encTab->ebdHandlerType);
// Tell the VM this is an extra clause caused by moving funclets out of line.
- // It seems weird this is from the CorExceptionFlag enum in corhdr.h,
- // not the CORINFO_EH_CLAUSE_FLAGS enum in corinfo.h.
- flags = (CORINFO_EH_CLAUSE_FLAGS)(flags | COR_ILEXCEPTION_CLAUSE_DUPLICATED);
+ flags = (CORINFO_EH_CLAUSE_FLAGS)(flags | CORINFO_EH_CLAUSE_DUPLICATE);
// Note that the JIT-EE interface reuses the CORINFO_EH_CLAUSE type, even though the names of
// the fields aren't really accurate. For example, we set "TryLength" to the offset of the
@@ -3617,7 +3691,7 @@ void CodeGen::genReportEH()
} // if (duplicateClauseCount > 0)
#if FEATURE_EH_CALLFINALLY_THUNKS
- if (anyFinallys)
+ if (clonedFinallyCount > 0)
{
unsigned reportedClonedFinallyCount = 0;
for (BasicBlock* block = compiler->fgFirstBB; block != nullptr; block = block->bbNext)
@@ -3647,9 +3721,9 @@ void CodeGen::genReportEH()
CORINFO_EH_CLAUSE clause;
clause.ClassToken = 0; // unused
- clause.Flags = (CORINFO_EH_CLAUSE_FLAGS)(CORINFO_EH_CLAUSE_FINALLY | COR_ILEXCEPTION_CLAUSE_DUPLICATED);
- clause.TryOffset = hndBeg;
- clause.TryLength = hndBeg;
+ clause.Flags = (CORINFO_EH_CLAUSE_FLAGS)(CORINFO_EH_CLAUSE_FINALLY | CORINFO_EH_CLAUSE_DUPLICATE);
+ clause.TryOffset = hndBeg;
+ clause.TryLength = hndBeg;
clause.HandlerOffset = hndBeg;
clause.HandlerLength = hndEnd;
@@ -3671,7 +3745,7 @@ void CodeGen::genReportEH()
} // for each block
assert(clonedFinallyCount == reportedClonedFinallyCount);
- } // if (anyFinallys)
+ } // if (clonedFinallyCount > 0)
#endif // FEATURE_EH_CALLFINALLY_THUNKS
#endif // FEATURE_EH_FUNCLETS
@@ -6995,12 +7069,12 @@ void CodeGen::genZeroInitFrame(int untrLclHi, int untrLclLo, regNumber initReg,
noway_assert(varTypeIsGC(varDsc->TypeGet()) || (varDsc->TypeGet() == TYP_STRUCT) ||
compiler->info.compInitMem || compiler->opts.compDbgCode);
-#ifdef _TARGET_64BIT_
+#ifndef LEGACY_BACKEND
if (!varDsc->lvOnFrame)
{
continue;
}
-#else // !_TARGET_64BIT_
+#else // LEGACY_BACKEND
if (varDsc->lvRegister)
{
if (varDsc->lvOnFrame)
@@ -7016,7 +7090,7 @@ void CodeGen::genZeroInitFrame(int untrLclHi, int untrLclLo, regNumber initReg,
}
continue;
}
-#endif // !_TARGET_64BIT_
+#endif // LEGACY_BACKEND
if ((varDsc->TypeGet() == TYP_STRUCT) && !compiler->info.compInitMem &&
(varDsc->lvExactSize >= TARGET_POINTER_SIZE))
@@ -7221,11 +7295,31 @@ void CodeGen::genSetGSSecurityCookie(regNumber initReg, bool* pInitRegZeroed)
#ifdef PROFILING_SUPPORTED
-/*-----------------------------------------------------------------------------
- *
- * Generate the profiling function enter callback.
- */
-
+//-----------------------------------------------------------------------------------
+// genProfilingEnterCallback: Generate the profiling function enter callback.
+//
+// Arguments:
+// initReg - register to use as scratch register
+// pInitRegZeroed - OUT parameter. *pInitRegZeroed set to 'false' if 'initReg' is
+// not zero after this call.
+//
+// Return Value:
+// None
+//
+// Notes:
+// The x86 profile enter helper has the following requirements (see ProfileEnterNaked in
+// VM\i386\asmhelpers.asm for details):
+// 1. The calling sequence for calling the helper is:
+// push FunctionIDOrClientID
+// call ProfileEnterHelper
+// 2. The calling function has an EBP frame.
+// 3. EBP points to the saved ESP which is the first thing saved in the function. Thus,
+// the following prolog is assumed:
+// push ESP
+// mov EBP, ESP
+// 4. All registers are preserved.
+// 5. The helper pops the FunctionIDOrClientID argument from the stack.
+//
void CodeGen::genProfilingEnterCallback(regNumber initReg, bool* pInitRegZeroed)
{
assert(compiler->compGeneratingProlog);
@@ -7236,7 +7330,6 @@ void CodeGen::genProfilingEnterCallback(regNumber initReg, bool* pInitRegZeroed)
return;
}
-#ifndef LEGACY_BACKEND
#if defined(_TARGET_AMD64_) && !defined(UNIX_AMD64_ABI) // No profiling for System V systems yet.
unsigned varNum;
LclVarDsc* varDsc;
@@ -7280,7 +7373,7 @@ void CodeGen::genProfilingEnterCallback(regNumber initReg, bool* pInitRegZeroed)
else
{
// No need to record relocations, if we are generating ELT hooks under the influence
- // of complus_JitELtHookEnabled=1
+ // of COMPlus_JitELTHookEnabled=1
if (compiler->opts.compJitELTHookEnabled)
{
genSetRegToIcon(REG_ARG_0, (ssize_t)compiler->compProfilerMethHnd, TYP_I_IMPL);
@@ -7346,11 +7439,7 @@ void CodeGen::genProfilingEnterCallback(regNumber initReg, bool* pInitRegZeroed)
*pInitRegZeroed = false;
}
-#else //!_TARGET_AMD64_
- NYI("RyuJIT: Emit Profiler Enter callback");
-#endif
-
-#else // LEGACY_BACKEND
+#elif defined(_TARGET_X86_) || (defined(_TARGET_ARM_) && defined(LEGACY_BACKEND))
unsigned saveStackLvl2 = genStackLevel;
@@ -7423,17 +7512,41 @@ void CodeGen::genProfilingEnterCallback(regNumber initReg, bool* pInitRegZeroed)
/* Restore the stack level */
genStackLevel = saveStackLvl2;
-#endif // LEGACY_BACKEND
-}
-/*****************************************************************************
- *
- * Generates Leave profiler hook.
- * Technically, this is not part of the epilog; it is called when we are generating code for a GT_RETURN node.
- */
+#else // target
+ NYI("Emit Profiler Enter callback");
+#endif // target
+}
+//-----------------------------------------------------------------------------------
+// genProfilingLeaveCallback: Generate the profiling function leave or tailcall callback.
+// Technically, this is not part of the epilog; it is called when we are generating code for a GT_RETURN node.
+//
+// Arguments:
+// helper - which helper to call. Either CORINFO_HELP_PROF_FCN_LEAVE or CORINFO_HELP_PROF_FCN_TAILCALL
+//
+// Return Value:
+// None
+//
+// Notes:
+// The x86 profile leave/tailcall helper has the following requirements (see ProfileLeaveNaked and
+// ProfileTailcallNaked in VM\i386\asmhelpers.asm for details):
+// 1. The calling sequence for calling the helper is:
+// push FunctionIDOrClientID
+// call ProfileLeaveHelper or ProfileTailcallHelper
+// 2. The calling function has an EBP frame.
+// 3. EBP points to the saved ESP which is the first thing saved in the function. Thus,
+// the following prolog is assumed:
+// push ESP
+// mov EBP, ESP
+// 4. helper == CORINFO_HELP_PROF_FCN_LEAVE: All registers are preserved.
+// helper == CORINFO_HELP_PROF_FCN_TAILCALL: Only argument registers are preserved.
+// 5. The helper pops the FunctionIDOrClientID argument from the stack.
+//
void CodeGen::genProfilingLeaveCallback(unsigned helper /*= CORINFO_HELP_PROF_FCN_LEAVE*/)
{
+ assert((helper == CORINFO_HELP_PROF_FCN_LEAVE) || (helper == CORINFO_HELP_PROF_FCN_TAILCALL));
+
// Only hook if profiler says it's okay.
if (!compiler->compIsProfilerHookNeeded())
{
@@ -7442,12 +7555,11 @@ void CodeGen::genProfilingLeaveCallback(unsigned helper /*= CORINFO_HELP_PROF_FC
compiler->info.compProfilerCallback = true;
- // Need to save on to the stack level, since the callee will pop the argument
+ // Need to save on to the stack level, since the helper call will pop the argument
unsigned saveStackLvl2 = genStackLevel;
-#ifndef LEGACY_BACKEND
-
#if defined(_TARGET_AMD64_) && !defined(UNIX_AMD64_ABI) // No profiling for System V systems yet.
+
// Since the method needs to make a profiler callback, it should have out-going arg space allocated.
noway_assert(compiler->lvaOutgoingArgSpaceVar != BAD_VAR_NUM);
noway_assert(compiler->lvaOutgoingArgSpaceSize >= (4 * REGSIZE_BYTES));
@@ -7477,7 +7589,7 @@ void CodeGen::genProfilingLeaveCallback(unsigned helper /*= CORINFO_HELP_PROF_FC
else
{
// Don't record relocations, if we are generating ELT hooks under the influence
- // of complus_JitELtHookEnabled=1
+ // of COMPlus_JitELTHookEnabled=1
if (compiler->opts.compJitELTHookEnabled)
{
genSetRegToIcon(REG_ARG_0, (ssize_t)compiler->compProfilerMethHnd, TYP_I_IMPL);
@@ -7517,13 +7629,8 @@ void CodeGen::genProfilingLeaveCallback(unsigned helper /*= CORINFO_HELP_PROF_FC
// "mov r8, helper addr; call r8"
genEmitHelperCall(helper, 0, EA_UNKNOWN, REG_ARG_2);
-#else //!_TARGET_AMD64_
- NYI("RyuJIT: Emit Profiler Leave callback");
-#endif // _TARGET_*
-
-#else // LEGACY_BACKEND
+#elif defined(_TARGET_X86_)
-#if defined(_TARGET_X86_)
//
// Push the profilerHandle
//
@@ -7538,7 +7645,7 @@ void CodeGen::genProfilingLeaveCallback(unsigned helper /*= CORINFO_HELP_PROF_FC
}
genSinglePush();
- genEmitHelperCall(CORINFO_HELP_PROF_FCN_LEAVE,
+ genEmitHelperCall(helper,
sizeof(int) * 1, // argSize
EA_UNKNOWN); // retSize
@@ -7549,7 +7656,9 @@ void CodeGen::genProfilingLeaveCallback(unsigned helper /*= CORINFO_HELP_PROF_FC
{
compiler->fgPtrArgCntMax = 1;
}
-#elif defined(_TARGET_ARM_)
+
+#elif defined(LEGACY_BACKEND) && defined(_TARGET_ARM_)
+
//
// Push the profilerHandle
//
@@ -7571,9 +7680,9 @@ void CodeGen::genProfilingLeaveCallback(unsigned helper /*= CORINFO_HELP_PROF_FC
bool r0Trashed;
emitAttr attr = EA_UNKNOWN;
- if (compiler->info.compRetType == TYP_VOID ||
- (!compiler->info.compIsVarArgs && !compiler->opts.compUseSoftFP && (varTypeIsFloating(compiler->info.compRetType) ||
- compiler->IsHfa(compiler->info.compMethodInfo->args.retTypeClass))))
+ if (compiler->info.compRetType == TYP_VOID || (!compiler->info.compIsVarArgs && !compiler->opts.compUseSoftFP &&
+ (varTypeIsFloating(compiler->info.compRetType) ||
+ compiler->IsHfa(compiler->info.compMethodInfo->args.retTypeClass))))
{
r0Trashed = false;
}
@@ -7625,11 +7734,10 @@ void CodeGen::genProfilingLeaveCallback(unsigned helper /*= CORINFO_HELP_PROF_FC
}
regSet.rsUnlockReg(RBM_PROFILER_RET_USED);
-#else // _TARGET_*
- NYI("Pushing the profilerHandle & caller's sp for the profiler callout and locking them");
-#endif // _TARGET_*
-#endif // LEGACY_BACKEND
+#else // target
+ NYI("Emit Profiler Leave callback");
+#endif // target
/* Restore the stack level */
genStackLevel = saveStackLvl2;
@@ -7741,7 +7849,7 @@ void CodeGen::genPrologPadForReJit()
assert(compiler->compGeneratingProlog);
#ifdef _TARGET_XARCH_
- if (!(compiler->opts.eeFlags & CORJIT_FLG_PROF_REJIT_NOPS))
+ if (!compiler->opts.jitFlags->IsSet(JitFlags::JIT_FLAG_PROF_REJIT_NOPS))
{
return;
}
@@ -8165,11 +8273,9 @@ void CodeGen::genFnProlog()
getEmitter()->emitBegProlog();
compiler->unwindBegProlog();
-#ifdef DEBUGGING_SUPPORT
// Do this so we can put the prolog instruction group ahead of
// other instruction groups
genIPmappingAddToFront((IL_OFFSETX)ICorDebugInfo::PROLOG);
-#endif // DEBUGGING_SUPPORT
#ifdef DEBUG
if (compiler->opts.dspCode)
@@ -8178,13 +8284,11 @@ void CodeGen::genFnProlog()
}
#endif
-#ifdef DEBUGGING_SUPPORT
if (compiler->opts.compScopeInfo && (compiler->info.compVarScopesCount > 0))
{
// Create new scopes for the method-parameters for the prolog-block.
psiBegProlog();
}
-#endif
#ifdef DEBUG
@@ -8664,12 +8768,6 @@ void CodeGen::genFnProlog()
// when compInitMem is true the genZeroInitFrame will zero out the shadow SP slots
if (compiler->ehNeedsShadowSPslots() && !compiler->info.compInitMem)
{
- /*
- // size/speed option?
- getEmitter()->emitIns_I_ARR(INS_mov, EA_PTRSIZE, 0,
- REG_EBP, REG_NA, -compiler->lvaShadowSPfirstOffs);
- */
-
// The last slot is reserved for ICodeManager::FixContext(ppEndRegion)
unsigned filterEndOffsetSlotOffs = compiler->lvaLclSize(compiler->lvaShadowSPslotsVar) - (sizeof(void*));
@@ -8707,9 +8805,8 @@ void CodeGen::genFnProlog()
// Initialize any "hidden" slots/locals
- if (compiler->compLocallocUsed)
+ if (compiler->lvaLocAllocSPvar != BAD_VAR_NUM)
{
- noway_assert(compiler->lvaLocAllocSPvar != BAD_VAR_NUM);
#ifdef _TARGET_ARM64_
getEmitter()->emitIns_S_R(ins_Store(TYP_I_IMPL), EA_PTRSIZE, REG_FPBASE, compiler->lvaLocAllocSPvar, 0);
#else
@@ -8870,12 +8967,10 @@ void CodeGen::genFnProlog()
getEmitter()->emitMarkPrologEnd();
}
-#ifdef DEBUGGING_SUPPORT
if (compiler->opts.compScopeInfo && (compiler->info.compVarScopesCount > 0))
{
psiEndProlog();
}
-#endif
if (hasGCRef)
{
@@ -8927,7 +9022,7 @@ void CodeGen::genFnProlog()
// LEA EAX, &<VARARGS HANDLE> + EAX
getEmitter()->emitIns_R_ARR(INS_lea, EA_PTRSIZE, REG_EAX, genFramePointerReg(), REG_EAX, offset);
- if (varDsc->lvRegister)
+ if (varDsc->lvIsInReg())
{
if (varDsc->lvRegNum != REG_EAX)
{
@@ -9637,7 +9732,7 @@ void CodeGen::genFnEpilog(BasicBlock* block)
* |Pre-spill regs space | // This is only necessary to keep the PSP slot at the same offset
* | | // in function and funclet
* |-----------------------|
- * | PSP slot |
+ * | PSP slot | // Omitted in CoreRT ABI
* |-----------------------|
* ~ possible 4 byte pad ~
* ~ for alignment ~
@@ -9936,7 +10031,7 @@ void CodeGen::genCaptureFuncletPrologEpilogInfo()
* ~ possible 8 byte pad ~
* ~ for alignment ~
* |-----------------------|
- * | PSP slot |
+ * | PSP slot | // Omitted in CoreRT ABI
* |-----------------------|
* | Outgoing arg space | // this only exists if the function makes a call
* |-----------------------| <---- Initial SP
@@ -10007,6 +10102,12 @@ void CodeGen::genFuncletProlog(BasicBlock* block)
// This is the end of the OS-reported prolog for purposes of unwinding
compiler->unwindEndProlog();
+ // If there is no PSPSym (CoreRT ABI), we are done.
+ if (compiler->lvaPSPSym == BAD_VAR_NUM)
+ {
+ return;
+ }
+
getEmitter()->emitIns_R_AR(INS_mov, EA_PTRSIZE, REG_FPBASE, REG_ARG_0, genFuncletInfo.fiPSP_slot_InitialSP_offset);
regTracker.rsTrackRegTrash(REG_FPBASE);
@@ -10100,10 +10201,12 @@ void CodeGen::genCaptureFuncletPrologEpilogInfo()
unsigned calleeFPRegsSavedSize = genCountBits(compiler->compCalleeFPRegsSavedMask) * XMM_REGSIZE_BYTES;
unsigned FPRegsPad = (calleeFPRegsSavedSize > 0) ? AlignmentPad(totalFrameSize, XMM_REGSIZE_BYTES) : 0;
+ unsigned PSPSymSize = (compiler->lvaPSPSym != BAD_VAR_NUM) ? REGSIZE_BYTES : 0;
+
totalFrameSize += FPRegsPad // Padding before pushing entire xmm regs
+ calleeFPRegsSavedSize // pushed callee-saved float regs
// below calculated 'pad' will go here
- + REGSIZE_BYTES // PSPSym
+ + PSPSymSize // PSPSym
+ compiler->lvaOutgoingArgSpaceSize // outgoing arg space
;
@@ -10111,7 +10214,7 @@ void CodeGen::genCaptureFuncletPrologEpilogInfo()
genFuncletInfo.fiSpDelta = FPRegsPad // Padding to align SP on XMM_REGSIZE_BYTES boundary
+ calleeFPRegsSavedSize // Callee saved xmm regs
- + pad + REGSIZE_BYTES // PSPSym
+ + pad + PSPSymSize // PSPSym
+ compiler->lvaOutgoingArgSpaceSize // outgoing arg space
;
@@ -10124,12 +10227,14 @@ void CodeGen::genCaptureFuncletPrologEpilogInfo()
printf(" SP delta: %d\n", genFuncletInfo.fiSpDelta);
printf(" PSP slot Initial SP offset: %d\n", genFuncletInfo.fiPSP_slot_InitialSP_offset);
}
-#endif // DEBUG
- assert(compiler->lvaPSPSym != BAD_VAR_NUM);
- assert(genFuncletInfo.fiPSP_slot_InitialSP_offset ==
- compiler->lvaGetInitialSPRelativeOffset(compiler->lvaPSPSym)); // same offset used in main function and
- // funclet!
+ if (compiler->lvaPSPSym != BAD_VAR_NUM)
+ {
+ assert(genFuncletInfo.fiPSP_slot_InitialSP_offset ==
+ compiler->lvaGetInitialSPRelativeOffset(compiler->lvaPSPSym)); // same offset used in main function and
+ // funclet!
+ }
+#endif // DEBUG
}
#elif defined(_TARGET_ARM64_)
@@ -10249,13 +10354,12 @@ void CodeGen::genSetPSPSym(regNumber initReg, bool* pInitRegZeroed)
{
assert(compiler->compGeneratingProlog);
- if (!compiler->ehNeedsPSPSym())
+ if (compiler->lvaPSPSym == BAD_VAR_NUM)
{
return;
}
- noway_assert(isFramePointerUsed()); // We need an explicit frame pointer
- assert(compiler->lvaPSPSym != BAD_VAR_NUM); // We should have created the PSPSym variable
+ noway_assert(isFramePointerUsed()); // We need an explicit frame pointer
#if defined(_TARGET_ARM_)
@@ -10851,8 +10955,162 @@ unsigned CodeGen::getFirstArgWithStackSlot()
#endif // !LEGACY_BACKEND && (_TARGET_XARCH_ || _TARGET_ARM64_)
-/*****************************************************************************/
-#ifdef DEBUGGING_SUPPORT
+//------------------------------------------------------------------------
+// genSinglePush: Report a change in stack level caused by a single word-sized push instruction
+//
+void CodeGen::genSinglePush()
+{
+ genStackLevel += sizeof(void*);
+}
+
+//------------------------------------------------------------------------
+// genSinglePop: Report a change in stack level caused by a single word-sized pop instruction
+//
+void CodeGen::genSinglePop()
+{
+ genStackLevel -= sizeof(void*);
+}
+
+//------------------------------------------------------------------------
+// genPushRegs: Push the given registers.
+//
+// Arguments:
+// regs - mask or registers to push
+// byrefRegs - OUT arg. Set to byref registers that were pushed.
+// noRefRegs - OUT arg. Set to non-GC ref registers that were pushed.
+//
+// Return Value:
+// Mask of registers pushed.
+//
+// Notes:
+// This function does not check if the register is marked as used, etc.
+//
+regMaskTP CodeGen::genPushRegs(regMaskTP regs, regMaskTP* byrefRegs, regMaskTP* noRefRegs)
+{
+ *byrefRegs = RBM_NONE;
+ *noRefRegs = RBM_NONE;
+
+ if (regs == RBM_NONE)
+ {
+ return RBM_NONE;
+ }
+
+#if FEATURE_FIXED_OUT_ARGS
+
+ NYI("Don't call genPushRegs with real regs!");
+ return RBM_NONE;
+
+#else // FEATURE_FIXED_OUT_ARGS
+
+ noway_assert(genTypeStSz(TYP_REF) == genTypeStSz(TYP_I_IMPL));
+ noway_assert(genTypeStSz(TYP_BYREF) == genTypeStSz(TYP_I_IMPL));
+
+ regMaskTP pushedRegs = regs;
+
+ for (regNumber reg = REG_INT_FIRST; regs != RBM_NONE; reg = REG_NEXT(reg))
+ {
+ regMaskTP regBit = regMaskTP(1) << reg;
+
+ if ((regBit & regs) == RBM_NONE)
+ continue;
+
+ var_types type;
+ if (regBit & gcInfo.gcRegGCrefSetCur)
+ {
+ type = TYP_REF;
+ }
+ else if (regBit & gcInfo.gcRegByrefSetCur)
+ {
+ *byrefRegs |= regBit;
+ type = TYP_BYREF;
+ }
+ else if (noRefRegs != NULL)
+ {
+ *noRefRegs |= regBit;
+ type = TYP_I_IMPL;
+ }
+ else
+ {
+ continue;
+ }
+
+ inst_RV(INS_push, reg, type);
+
+ genSinglePush();
+ gcInfo.gcMarkRegSetNpt(regBit);
+
+ regs &= ~regBit;
+ }
+
+ return pushedRegs;
+
+#endif // FEATURE_FIXED_OUT_ARGS
+}
+
+//------------------------------------------------------------------------
+// genPopRegs: Pop the registers that were pushed by genPushRegs().
+//
+// Arguments:
+// regs - mask of registers to pop
+// byrefRegs - The byref registers that were pushed by genPushRegs().
+// noRefRegs - The non-GC ref registers that were pushed by genPushRegs().
+//
+// Return Value:
+// None
+//
+void CodeGen::genPopRegs(regMaskTP regs, regMaskTP byrefRegs, regMaskTP noRefRegs)
+{
+ if (regs == RBM_NONE)
+ {
+ return;
+ }
+
+#if FEATURE_FIXED_OUT_ARGS
+
+ NYI("Don't call genPopRegs with real regs!");
+
+#else // FEATURE_FIXED_OUT_ARGS
+
+ noway_assert((regs & byrefRegs) == byrefRegs);
+ noway_assert((regs & noRefRegs) == noRefRegs);
+ noway_assert((regs & (gcInfo.gcRegGCrefSetCur | gcInfo.gcRegByrefSetCur)) == RBM_NONE);
+
+ noway_assert(genTypeStSz(TYP_REF) == genTypeStSz(TYP_INT));
+ noway_assert(genTypeStSz(TYP_BYREF) == genTypeStSz(TYP_INT));
+
+ // Walk the registers in the reverse order as genPushRegs()
+ for (regNumber reg = REG_INT_LAST; regs != RBM_NONE; reg = REG_PREV(reg))
+ {
+ regMaskTP regBit = regMaskTP(1) << reg;
+
+ if ((regBit & regs) == RBM_NONE)
+ continue;
+
+ var_types type;
+ if (regBit & byrefRegs)
+ {
+ type = TYP_BYREF;
+ }
+ else if (regBit & noRefRegs)
+ {
+ type = TYP_INT;
+ }
+ else
+ {
+ type = TYP_REF;
+ }
+
+ inst_RV(INS_pop, reg, type);
+ genSinglePop();
+
+ if (type != TYP_INT)
+ gcInfo.gcMarkRegPtrVal(reg, type);
+
+ regs &= ~regBit;
+ }
+
+#endif // FEATURE_FIXED_OUT_ARGS
+}
/*****************************************************************************
* genSetScopeInfo
@@ -11151,6 +11409,103 @@ void CodeGen::genSetScopeInfo()
compiler->eeSetLVdone();
}
+//------------------------------------------------------------------------
+// genSetScopeInfo: Record scope information for debug info
+//
+// Arguments:
+// which
+// startOffs - the starting offset for this scope
+// length - the length of this scope
+// varNum - the lclVar for this scope info
+// LVnum
+// avail
+// varLoc
+//
+// Notes:
+// Called for every scope info piece to record by the main genSetScopeInfo()
+
+void CodeGen::genSetScopeInfo(unsigned which,
+ UNATIVE_OFFSET startOffs,
+ UNATIVE_OFFSET length,
+ unsigned varNum,
+ unsigned LVnum,
+ bool avail,
+ Compiler::siVarLoc& varLoc)
+{
+ // We need to do some mapping while reporting back these variables.
+
+ unsigned ilVarNum = compiler->compMap2ILvarNum(varNum);
+ noway_assert((int)ilVarNum != ICorDebugInfo::UNKNOWN_ILNUM);
+
+#ifdef _TARGET_X86_
+ // Non-x86 platforms are allowed to access all arguments directly
+ // so we don't need this code.
+
+ // Is this a varargs function?
+
+ if (compiler->info.compIsVarArgs && varNum != compiler->lvaVarargsHandleArg &&
+ varNum < compiler->info.compArgsCount && !compiler->lvaTable[varNum].lvIsRegArg)
+ {
+ noway_assert(varLoc.vlType == Compiler::VLT_STK || varLoc.vlType == Compiler::VLT_STK2);
+
+ // All stack arguments (except the varargs handle) have to be
+ // accessed via the varargs cookie. Discard generated info,
+ // and just find its position relative to the varargs handle
+
+ PREFIX_ASSUME(compiler->lvaVarargsHandleArg < compiler->info.compArgsCount);
+ if (!compiler->lvaTable[compiler->lvaVarargsHandleArg].lvOnFrame)
+ {
+ noway_assert(!compiler->opts.compDbgCode);
+ return;
+ }
+
+ // Can't check compiler->lvaTable[varNum].lvOnFrame as we don't set it for
+ // arguments of vararg functions to avoid reporting them to GC.
+ noway_assert(!compiler->lvaTable[varNum].lvRegister);
+ unsigned cookieOffset = compiler->lvaTable[compiler->lvaVarargsHandleArg].lvStkOffs;
+ unsigned varOffset = compiler->lvaTable[varNum].lvStkOffs;
+
+ noway_assert(cookieOffset < varOffset);
+ unsigned offset = varOffset - cookieOffset;
+ unsigned stkArgSize = compiler->compArgSize - intRegState.rsCalleeRegArgCount * sizeof(void*);
+ noway_assert(offset < stkArgSize);
+ offset = stkArgSize - offset;
+
+ varLoc.vlType = Compiler::VLT_FIXED_VA;
+ varLoc.vlFixedVarArg.vlfvOffset = offset;
+ }
+
+#endif // _TARGET_X86_
+
+ VarName name = nullptr;
+
+#ifdef DEBUG
+
+ for (unsigned scopeNum = 0; scopeNum < compiler->info.compVarScopesCount; scopeNum++)
+ {
+ if (LVnum == compiler->info.compVarScopes[scopeNum].vsdLVnum)
+ {
+ name = compiler->info.compVarScopes[scopeNum].vsdName;
+ }
+ }
+
+ // Hang on to this compiler->info.
+
+ TrnslLocalVarInfo& tlvi = genTrnslLocalVarInfo[which];
+
+ tlvi.tlviVarNum = ilVarNum;
+ tlvi.tlviLVnum = LVnum;
+ tlvi.tlviName = name;
+ tlvi.tlviStartPC = startOffs;
+ tlvi.tlviLength = length;
+ tlvi.tlviAvailable = avail;
+ tlvi.tlviVarLoc = varLoc;
+
+#endif // DEBUG
+
+ compiler->eeSetLVinfo(which, startOffs, length, ilVarNum, LVnum, name, avail, varLoc);
+}
+
/*****************************************************************************/
#ifdef LATE_DISASM
#if defined(DEBUG)
@@ -11747,19 +12102,16 @@ void CodeGen::genIPmappingGen()
compiler->eeSetLIdone();
}
-#endif // DEBUGGING_SUPPORT
-
/*============================================================================
*
* These are empty stubs to help the late dis-assembler to compile
- * if DEBUGGING_SUPPORT is not enabled, or the late disassembler is being
- * built into a non-DEBUG build.
+ * if the late disassembler is being built into a non-DEBUG build.
*
*============================================================================
*/
#if defined(LATE_DISASM)
-#if !defined(DEBUGGING_SUPPORT) || !defined(DEBUG)
+#if !defined(DEBUG)
/* virtual */
const char* CodeGen::siRegVarName(size_t offs, size_t size, unsigned reg)
@@ -11774,6 +12126,6 @@ const char* CodeGen::siStackVarName(size_t offs, size_t size, unsigned reg, unsi
}
/*****************************************************************************/
-#endif // !defined(DEBUGGING_SUPPORT) || !defined(DEBUG)
+#endif // !defined(DEBUG)
#endif // defined(LATE_DISASM)
/*****************************************************************************/
diff --git a/src/jit/codegeninterface.h b/src/jit/codegeninterface.h
index e9abbe6b3c..3950673e3a 100644
--- a/src/jit/codegeninterface.h
+++ b/src/jit/codegeninterface.h
@@ -253,12 +253,14 @@ public:
private:
bool m_cgDoubleAlign;
-#else // !DOUBLE_ALIGN
+#else // !DOUBLE_ALIGN
+
public:
bool doubleAlignOrFramePointerUsed() const
{
return isFramePointerUsed();
}
+
#endif // !DOUBLE_ALIGN
#ifdef DEBUG
@@ -424,10 +426,8 @@ public:
private:
bool m_cgFullPtrRegMap;
-#ifdef DEBUGGING_SUPPORT
public:
virtual void siUpdate() = 0;
-#endif // DEBUGGING_SUPPORT
#ifdef LATE_DISASM
public:
diff --git a/src/jit/codegenlegacy.cpp b/src/jit/codegenlegacy.cpp
index ea40eb2aff..667b9d4af8 100644
--- a/src/jit/codegenlegacy.cpp
+++ b/src/jit/codegenlegacy.cpp
@@ -243,18 +243,6 @@ GenTreePtr CodeGen::genGetAddrModeBase(GenTreePtr tree)
return NULL;
}
-// inline
-void CodeGen::genSinglePush()
-{
- genStackLevel += sizeof(void*);
-}
-
-// inline
-void CodeGen::genSinglePop()
-{
- genStackLevel -= sizeof(void*);
-}
-
#if FEATURE_STACK_FP_X87
// inline
void CodeGenInterface::genResetFPstkLevel(unsigned newValue /* = 0 */)
@@ -497,9 +485,10 @@ void CodeGen::genIncRegBy(regNumber reg, ssize_t ival, GenTreePtr tree, var_type
}
}
#endif
-
- insFlags flags = setFlags ? INS_FLAGS_SET : INS_FLAGS_DONT_CARE;
- inst_RV_IV(INS_add, reg, ival, emitActualTypeSize(dstType), flags);
+ {
+ insFlags flags = setFlags ? INS_FLAGS_SET : INS_FLAGS_DONT_CARE;
+ inst_RV_IV(INS_add, reg, ival, emitActualTypeSize(dstType), flags);
+ }
#ifdef _TARGET_XARCH_
UPDATE_LIVENESS:
@@ -4328,8 +4317,6 @@ emitJumpKind CodeGen::genCondSetFlags(GenTreePtr cond)
addrReg1 = genMakeRvalueAddressable(op1, RBM_NONE, RegSet::KEEP_REG, false, smallOk);
}
- // #if defined(DEBUGGING_SUPPORT)
-
/* Special case: comparison of two constants */
// Needed if Importer doesn't call gtFoldExpr()
@@ -4347,8 +4334,6 @@ emitJumpKind CodeGen::genCondSetFlags(GenTreePtr cond)
addrReg1 = genRegMask(op1->gtRegNum);
}
- // #endif
-
/* Compare the operand against the constant */
if (op2->IsIconHandle())
@@ -7087,84 +7072,87 @@ void CodeGen::genCodeForTreeSmpBinArithLogOp(GenTreePtr tree, regMaskTP destReg,
regTracker.rsTrackRegTrash(reg);
- bool op2Released = false;
+ {
+ bool op2Released = false;
- // For overflow instructions, tree->gtType is the accurate type,
- // and gives us the size for the operands.
+ // For overflow instructions, tree->gtType is the accurate type,
+ // and gives us the size for the operands.
- emitAttr opSize = emitTypeSize(treeType);
+ emitAttr opSize = emitTypeSize(treeType);
- /* Compute the new value */
+ /* Compute the new value */
- if (isArith && !op2->InReg() && (op2->OperKind() & GTK_CONST)
+ if (isArith && !op2->InReg() && (op2->OperKind() & GTK_CONST)
#if !CPU_HAS_FP_SUPPORT
- && (treeType == TYP_INT || treeType == TYP_I_IMPL)
+ && (treeType == TYP_INT || treeType == TYP_I_IMPL)
#endif
- )
- {
- ssize_t ival = op2->gtIntCon.gtIconVal;
-
- if (oper == GT_ADD)
- {
- genIncRegBy(reg, ival, tree, treeType, ovfl);
- }
- else if (oper == GT_SUB)
- {
- if (ovfl && ((tree->gtFlags & GTF_UNSIGNED) ||
- (ival == ((treeType == TYP_INT) ? INT32_MIN : SSIZE_T_MIN))) // -0x80000000 == 0x80000000.
- // Therefore we can't use -ival.
)
- {
- /* For unsigned overflow, we have to use INS_sub to set
- the flags correctly */
+ {
+ ssize_t ival = op2->gtIntCon.gtIconVal;
- genDecRegBy(reg, ival, tree);
+ if (oper == GT_ADD)
+ {
+ genIncRegBy(reg, ival, tree, treeType, ovfl);
}
- else
+ else if (oper == GT_SUB)
{
- /* Else, we simply add the negative of the value */
+ if (ovfl && ((tree->gtFlags & GTF_UNSIGNED) ||
+ (ival == ((treeType == TYP_INT) ? INT32_MIN : SSIZE_T_MIN))) // -0x80000000 == 0x80000000.
+ // Therefore we can't use -ival.
+ )
+ {
+ /* For unsigned overflow, we have to use INS_sub to set
+ the flags correctly */
- genIncRegBy(reg, -ival, tree, treeType, ovfl);
+ genDecRegBy(reg, ival, tree);
+ }
+ else
+ {
+ /* Else, we simply add the negative of the value */
+
+ genIncRegBy(reg, -ival, tree, treeType, ovfl);
+ }
+ }
+ else if (oper == GT_MUL)
+ {
+ genMulRegBy(reg, ival, tree, treeType, ovfl);
}
}
- else if (oper == GT_MUL)
- {
- genMulRegBy(reg, ival, tree, treeType, ovfl);
- }
- }
- else
- {
- // op2 could be a GT_COMMA (i.e. an assignment for a CSE def)
- op2 = op2->gtEffectiveVal();
- if (varTypeIsByte(treeType) && op2->InReg())
+ else
{
- noway_assert(genRegMask(reg) & RBM_BYTE_REGS);
+ // op2 could be a GT_COMMA (i.e. an assignment for a CSE def)
+ op2 = op2->gtEffectiveVal();
+ if (varTypeIsByte(treeType) && op2->InReg())
+ {
+ noway_assert(genRegMask(reg) & RBM_BYTE_REGS);
- regNumber op2reg = op2->gtRegNum;
- regMaskTP op2regMask = genRegMask(op2reg);
+ regNumber op2reg = op2->gtRegNum;
+ regMaskTP op2regMask = genRegMask(op2reg);
- if (!(op2regMask & RBM_BYTE_REGS))
- {
- regNumber byteReg = regSet.rsGrabReg(RBM_BYTE_REGS);
+ if (!(op2regMask & RBM_BYTE_REGS))
+ {
+ regNumber byteReg = regSet.rsGrabReg(RBM_BYTE_REGS);
- inst_RV_RV(INS_mov, byteReg, op2reg);
- regTracker.rsTrackRegTrash(byteReg);
+ inst_RV_RV(INS_mov, byteReg, op2reg);
+ regTracker.rsTrackRegTrash(byteReg);
- genDoneAddressable(op2, addrReg, RegSet::KEEP_REG);
- op2Released = true;
+ genDoneAddressable(op2, addrReg, RegSet::KEEP_REG);
+ op2Released = true;
- op2->gtRegNum = byteReg;
+ op2->gtRegNum = byteReg;
+ }
}
- }
- inst_RV_TT(ins, reg, op2, 0, opSize, flags);
- }
+ inst_RV_TT(ins, reg, op2, 0, opSize, flags);
+ }
- /* Free up anything that was tied up by the operand */
-
- if (!op2Released)
- genDoneAddressable(op2, addrReg, RegSet::KEEP_REG);
+ /* Free up anything that was tied up by the operand */
+ if (!op2Released)
+ {
+ genDoneAddressable(op2, addrReg, RegSet::KEEP_REG);
+ }
+ }
/* The result will be where the first operand is sitting */
/* We must use RegSet::KEEP_REG since op1 can have a GC pointer here */
@@ -9721,7 +9709,7 @@ void CodeGen::genCodeForTreeSmpOp(GenTreePtr tree, regMaskTP destReg, regMaskTP
switch (oper)
{
case GT_ASG:
- if (tree->OperIsBlkOp())
+ if (tree->OperIsBlkOp() && op1->gtOper != GT_LCL_VAR)
{
genCodeForBlkOp(tree, destReg);
}
@@ -10184,6 +10172,9 @@ void CodeGen::genCodeForTreeSmpOp(GenTreePtr tree, regMaskTP destReg, regMaskTP
if (op1 == NULL)
return;
#endif
+ __fallthrough;
+
+ case GT_INIT_VAL:
/* Generate the operand into some register */
@@ -11293,10 +11284,8 @@ void CodeGen::genCodeForTreeSmpOpAsg(GenTreePtr tree)
bool volat = false; // Is this a volatile store
regMaskTP regGC;
instruction ins;
-#ifdef DEBUGGING_SUPPORT
- unsigned lclVarNum = compiler->lvaCount;
- unsigned lclILoffs = DUMMY_INIT(0);
-#endif
+ unsigned lclVarNum = compiler->lvaCount;
+ unsigned lclILoffs = DUMMY_INIT(0);
#ifdef _TARGET_ARM_
if (tree->gtType == TYP_STRUCT)
@@ -11335,7 +11324,6 @@ void CodeGen::genCodeForTreeSmpOpAsg(GenTreePtr tree)
noway_assert(varNum < compiler->lvaCount);
varDsc = compiler->lvaTable + varNum;
-#ifdef DEBUGGING_SUPPORT
/* For non-debuggable code, every definition of a lcl-var has
* to be checked to see if we need to open a new scope for it.
* Remember the local var info to call siCheckVarScope
@@ -11346,7 +11334,6 @@ void CodeGen::genCodeForTreeSmpOpAsg(GenTreePtr tree)
lclVarNum = varNum;
lclILoffs = op1->gtLclVar.gtLclILoffs;
}
-#endif
/* Check against dead store ? (with min opts we may have dead stores) */
@@ -11999,13 +11986,11 @@ void CodeGen::genCodeForTreeSmpOpAsg(GenTreePtr tree)
genCodeForTreeSmpOpAsg_DONE_ASSG(tree, addrReg, REG_NA, ovfl);
LExit:
-#ifdef DEBUGGING_SUPPORT
/* For non-debuggable code, every definition of a lcl-var has
* to be checked to see if we need to open a new scope for it.
*/
if (lclVarNum < compiler->lvaCount)
siCheckVarScope(lclVarNum, lclILoffs);
-#endif
}
#ifdef _PREFAST_
#pragma warning(pop)
@@ -12436,14 +12421,12 @@ void CodeGen::genCodeForBBlist()
regSet.rsSpillBeg();
-#ifdef DEBUGGING_SUPPORT
/* Initialize the line# tracking logic */
if (compiler->opts.compScopeInfo)
{
siInit();
}
-#endif
#ifdef _TARGET_X86_
if (compiler->compTailCallUsed)
@@ -12774,27 +12757,7 @@ void CodeGen::genCodeForBBlist()
genResetFPstkLevel();
#endif // FEATURE_STACK_FP_X87
-#if !FEATURE_FIXED_OUT_ARGS
- /* Check for inserted throw blocks and adjust genStackLevel */
-
- if (!isFramePointerUsed() && compiler->fgIsThrowHlpBlk(block))
- {
- noway_assert(block->bbFlags & BBF_JMP_TARGET);
-
- genStackLevel = compiler->fgThrowHlpBlkStkLevel(block) * sizeof(int);
-
- if (genStackLevel)
- {
-#ifdef _TARGET_X86_
- getEmitter()->emitMarkStackLvl(genStackLevel);
- inst_RV_IV(INS_add, REG_SPBASE, genStackLevel, EA_PTRSIZE);
- genStackLevel = 0;
-#else // _TARGET_X86_
- NYI("Need emitMarkStackLvl()");
-#endif // _TARGET_X86_
- }
- }
-#endif // !FEATURE_FIXED_OUT_ARGS
+ genAdjustStackLevel(block);
savedStkLvl = genStackLevel;
@@ -12802,7 +12765,6 @@ void CodeGen::genCodeForBBlist()
compiler->compCurBB = block;
-#ifdef DEBUGGING_SUPPORT
siBeginBlock(block);
// BBF_INTERNAL blocks don't correspond to any single IL instruction.
@@ -12810,7 +12772,6 @@ void CodeGen::genCodeForBBlist()
genIPmappingAdd((IL_OFFSETX)ICorDebugInfo::NO_MAPPING, true);
bool firstMapping = true;
-#endif // DEBUGGING_SUPPORT
/*---------------------------------------------------------------------
*
@@ -12830,8 +12791,6 @@ void CodeGen::genCodeForBBlist()
{
noway_assert(stmt->gtOper == GT_STMT);
-#if defined(DEBUGGING_SUPPORT)
-
/* Do we have a new IL-offset ? */
if (stmt->gtStmt.gtStmtILoffsx != BAD_IL_OFFSET)
@@ -12841,8 +12800,6 @@ void CodeGen::genCodeForBBlist()
firstMapping = false;
}
-#endif // DEBUGGING_SUPPORT
-
#ifdef DEBUG
if (stmt->gtStmt.gtStmtLastILoffs != BAD_IL_OFFSET)
{
@@ -12945,7 +12902,7 @@ void CodeGen::genCodeForBBlist()
// harmless "inc" instruction (does not interfere with the exception
// object).
- if ((compiler->opts.eeFlags & CORJIT_FLG_BBINSTR) && (stmt == block->bbTreeList) &&
+ if (compiler->opts.jitFlags->IsSet(JitFlags::JIT_FLAG_BBINSTR) && (stmt == block->bbTreeList) &&
(block->bbCatchTyp && handlerGetsXcptnObj(block->bbCatchTyp)))
{
nonVarPtrRegs &= ~RBM_EXCEPTION_OBJECT;
@@ -12972,14 +12929,10 @@ void CodeGen::genCodeForBBlist()
noway_assert(stmt->gtOper == GT_STMT);
-#ifdef DEBUGGING_SUPPORT
genEnsureCodeEmitted(stmt->gtStmt.gtStmtILoffsx);
-#endif
} //-------- END-FOR each statement-tree of the current block ---------
-#ifdef DEBUGGING_SUPPORT
-
if (compiler->opts.compScopeInfo && (compiler->info.compVarScopesCount > 0))
{
siEndBlock(block);
@@ -13005,8 +12958,6 @@ void CodeGen::genCodeForBBlist()
}
}
-#endif // DEBUGGING_SUPPORT
-
genStackLevel -= savedStkLvl;
gcInfo.gcMarkRegSetNpt(gcrefRegs | byrefRegs);
@@ -13449,10 +13400,8 @@ void CodeGen::genCodeForTreeLng(GenTreePtr tree, regMaskTP needReg, regMaskTP av
{
case GT_ASG:
{
-#ifdef DEBUGGING_SUPPORT
unsigned lclVarNum = compiler->lvaCount;
unsigned lclVarILoffs = DUMMY_INIT(0);
-#endif
/* Is the target a local ? */
@@ -13467,7 +13416,6 @@ void CodeGen::genCodeForTreeLng(GenTreePtr tree, regMaskTP needReg, regMaskTP av
// No dead stores, (with min opts we may have dead stores)
noway_assert(!varDsc->lvTracked || compiler->opts.MinOpts() || !(op1->gtFlags & GTF_VAR_DEATH));
-#ifdef DEBUGGING_SUPPORT
/* For non-debuggable code, every definition of a lcl-var has
* to be checked to see if we need to open a new scope for it.
* Remember the local var info to call siCheckVarScope
@@ -13479,7 +13427,6 @@ void CodeGen::genCodeForTreeLng(GenTreePtr tree, regMaskTP needReg, regMaskTP av
lclVarNum = varNum;
lclVarILoffs = op1->gtLclVar.gtLclILoffs;
}
-#endif
/* Has the variable been assigned to a register (pair) ? */
@@ -13767,13 +13714,11 @@ void CodeGen::genCodeForTreeLng(GenTreePtr tree, regMaskTP needReg, regMaskTP av
genUpdateLife(op1);
genUpdateLife(tree);
-#ifdef DEBUGGING_SUPPORT
/* For non-debuggable code, every definition of a lcl-var has
* to be checked to see if we need to open a new scope for it.
*/
if (lclVarNum < compiler->lvaCount)
siCheckVarScope(lclVarNum, lclVarILoffs);
-#endif
}
return;
@@ -15792,132 +15737,6 @@ void CodeGen::genEmitHelperCall(unsigned helper, int argSize, emitAttr retSize)
/*****************************************************************************
*
- * Push the given registers.
- * This function does not check if the register is marked as used, etc.
- */
-
-regMaskTP CodeGen::genPushRegs(regMaskTP regs, regMaskTP* byrefRegs, regMaskTP* noRefRegs)
-{
- *byrefRegs = RBM_NONE;
- *noRefRegs = RBM_NONE;
-
- // noway_assert((regs & regSet.rsRegMaskFree()) == regs); // Don't care. Caller is responsible for all this
-
- if (regs == RBM_NONE)
- return RBM_NONE;
-
-#if FEATURE_FIXED_OUT_ARGS
-
- NYI("Don't call genPushRegs with real regs!");
- return RBM_NONE;
-
-#else // FEATURE_FIXED_OUT_ARGS
-
- noway_assert(genTypeStSz(TYP_REF) == genTypeStSz(TYP_I_IMPL));
- noway_assert(genTypeStSz(TYP_BYREF) == genTypeStSz(TYP_I_IMPL));
-
- regMaskTP pushedRegs = regs;
-
- for (regNumber reg = REG_INT_FIRST; regs != RBM_NONE; reg = REG_NEXT(reg))
- {
- regMaskTP regBit = regMaskTP(1) << reg;
-
- if ((regBit & regs) == RBM_NONE)
- continue;
-
- var_types type;
- if (regBit & gcInfo.gcRegGCrefSetCur)
- {
- type = TYP_REF;
- }
- else if (regBit & gcInfo.gcRegByrefSetCur)
- {
- *byrefRegs |= regBit;
- type = TYP_BYREF;
- }
- else if (noRefRegs != NULL)
- {
- *noRefRegs |= regBit;
- type = TYP_I_IMPL;
- }
- else
- {
- continue;
- }
-
- inst_RV(INS_push, reg, type);
-
- genSinglePush();
- gcInfo.gcMarkRegSetNpt(regBit);
-
- regs &= ~regBit;
- }
-
- return pushedRegs;
-
-#endif // FEATURE_FIXED_OUT_ARGS
-}
-
-/*****************************************************************************
- *
- * Pop the registers pushed by genPushRegs()
- */
-
-void CodeGen::genPopRegs(regMaskTP regs, regMaskTP byrefRegs, regMaskTP noRefRegs)
-{
- if (regs == RBM_NONE)
- return;
-
-#if FEATURE_FIXED_OUT_ARGS
-
- NYI("Don't call genPopRegs with real regs!");
-
-#else // FEATURE_FIXED_OUT_ARGS
-
- noway_assert((regs & byrefRegs) == byrefRegs);
- noway_assert((regs & noRefRegs) == noRefRegs);
- // noway_assert((regs & regSet.rsRegMaskFree()) == regs); // Don't care. Caller is responsible for all this
- noway_assert((regs & (gcInfo.gcRegGCrefSetCur | gcInfo.gcRegByrefSetCur)) == RBM_NONE);
-
- noway_assert(genTypeStSz(TYP_REF) == genTypeStSz(TYP_INT));
- noway_assert(genTypeStSz(TYP_BYREF) == genTypeStSz(TYP_INT));
-
- // Walk the registers in the reverse order as genPushRegs()
- for (regNumber reg = REG_INT_LAST; regs != RBM_NONE; reg = REG_PREV(reg))
- {
- regMaskTP regBit = regMaskTP(1) << reg;
-
- if ((regBit & regs) == RBM_NONE)
- continue;
-
- var_types type;
- if (regBit & byrefRegs)
- {
- type = TYP_BYREF;
- }
- else if (regBit & noRefRegs)
- {
- type = TYP_INT;
- }
- else
- {
- type = TYP_REF;
- }
-
- inst_RV(INS_pop, reg, type);
- genSinglePop();
-
- if (type != TYP_INT)
- gcInfo.gcMarkRegPtrVal(reg, type);
-
- regs &= ~regBit;
- }
-
-#endif // FEATURE_FIXED_OUT_ARGS
-}
-
-/*****************************************************************************
- *
* Push the given argument list, right to left; returns the total amount of
* stuff pushed.
*/
@@ -18519,12 +18338,10 @@ regMaskTP CodeGen::genCodeForCall(GenTreePtr call, bool valUsed)
CORINFO_SIG_INFO* sigInfo = nullptr;
-#ifdef DEBUGGING_SUPPORT
if (compiler->opts.compDbgInfo && compiler->genCallSite2ILOffsetMap != NULL)
{
(void)compiler->genCallSite2ILOffsetMap->Lookup(call, &ilOffset);
}
-#endif
/* Make some sanity checks on the call node */
@@ -19600,6 +19417,7 @@ regMaskTP CodeGen::genCodeForCall(GenTreePtr call, bool valUsed)
regNumber indCallReg;
case IAT_VALUE:
+ {
//------------------------------------------------------
// Non-virtual direct calls to known addressess
//
@@ -19607,7 +19425,24 @@ regMaskTP CodeGen::genCodeForCall(GenTreePtr call, bool valUsed)
// it be nice if they all did!
CLANG_FORMAT_COMMENT_ANCHOR;
#ifdef _TARGET_ARM_
- if (!arm_Valid_Imm_For_BL((ssize_t)addr))
+ // We may use direct call for some of recursive calls
+ // as we can safely estimate the distance from the call site to the top of the method
+ const int codeOffset = MAX_PROLOG_SIZE_BYTES + // prolog size
+ getEmitter()->emitCurCodeOffset + // offset of the current IG
+ getEmitter()->emitCurIGsize + // size of the current IG
+ 4; // size of the jump instruction
+ // that we are now emitting
+ if (compiler->gtIsRecursiveCall(call->AsCall()) && codeOffset <= -CALL_DIST_MAX_NEG)
+ {
+ getEmitter()->emitIns_Call(emitter::EC_FUNC_TOKEN, methHnd,
+ INDEBUG_LDISASM_COMMA(sigInfo) NULL, // addr
+ args, retSize, gcInfo.gcVarPtrSetCur,
+ gcInfo.gcRegGCrefSetCur, gcInfo.gcRegByrefSetCur, ilOffset,
+ REG_NA, REG_NA, 0, 0, // ireg, xreg, xmul, disp
+ false, // isJump
+ emitter::emitNoGChelper(helperNum));
+ }
+ else if (!arm_Valid_Imm_For_BL((ssize_t)addr))
{
// Load the address into a register and call through a register
indCallReg = regSet.rsGrabReg(RBM_ALLINT); // Grab an available register to use for the
@@ -19634,7 +19469,8 @@ regMaskTP CodeGen::genCodeForCall(GenTreePtr call, bool valUsed)
false, /* isJump */
emitter::emitNoGChelper(helperNum));
}
- break;
+ }
+ break;
case IAT_PVALUE:
//------------------------------------------------------
@@ -20046,7 +19882,7 @@ regMaskTP CodeGen::genCodeForCall(GenTreePtr call, bool valUsed)
#if defined(_TARGET_X86_)
if (call->gtFlags & GTF_CALL_UNMANAGED)
{
- if ((compiler->opts.eeFlags & CORJIT_FLG_PINVOKE_RESTORE_ESP) ||
+ if (compiler->opts.jitFlags->IsSet(JitFlags::JIT_FLAG_PINVOKE_RESTORE_ESP) ||
compiler->compStressCompile(Compiler::STRESS_PINVOKE_RESTORE_ESP, 50))
{
// P/Invoke signature mismatch resilience - restore ESP to pre-call value. We would ideally
@@ -20756,9 +20592,11 @@ DONE:
}
#endif
- /* Write the lvaShadowSPfirst stack frame slot */
- noway_assert(compiler->lvaLocAllocSPvar != BAD_VAR_NUM);
- getEmitter()->emitIns_S_R(ins_Store(TYP_I_IMPL), EA_PTRSIZE, REG_SPBASE, compiler->lvaLocAllocSPvar, 0);
+ /* Write the lvaLocAllocSPvar stack frame slot */
+ if (compiler->lvaLocAllocSPvar != BAD_VAR_NUM)
+ {
+ getEmitter()->emitIns_S_R(ins_Store(TYP_I_IMPL), EA_PTRSIZE, REG_SPBASE, compiler->lvaLocAllocSPvar, 0);
+ }
#if STACK_PROBES
// Don't think it is worth it the codegen complexity to embed this
@@ -20783,98 +20621,6 @@ DONE:
return regCnt;
}
-/*****************************************************************************/
-#ifdef DEBUGGING_SUPPORT
-/*****************************************************************************
- * genSetScopeInfo
- *
- * Called for every scope info piece to record by the main genSetScopeInfo()
- */
-
-void CodeGen::genSetScopeInfo(unsigned which,
- UNATIVE_OFFSET startOffs,
- UNATIVE_OFFSET length,
- unsigned varNum,
- unsigned LVnum,
- bool avail,
- Compiler::siVarLoc& varLoc)
-{
- /* We need to do some mapping while reporting back these variables */
-
- unsigned ilVarNum = compiler->compMap2ILvarNum(varNum);
- noway_assert((int)ilVarNum != ICorDebugInfo::UNKNOWN_ILNUM);
-
-#ifdef _TARGET_X86_
- // Non-x86 platforms are allowed to access all arguments directly
- // so we don't need this code.
-
- // Is this a varargs function?
-
- if (compiler->info.compIsVarArgs && varNum != compiler->lvaVarargsHandleArg &&
- varNum < compiler->info.compArgsCount && !compiler->lvaTable[varNum].lvIsRegArg)
- {
- noway_assert(varLoc.vlType == Compiler::VLT_STK || varLoc.vlType == Compiler::VLT_STK2);
-
- // All stack arguments (except the varargs handle) have to be
- // accessed via the varargs cookie. Discard generated info,
- // and just find its position relative to the varargs handle
-
- PREFIX_ASSUME(compiler->lvaVarargsHandleArg < compiler->info.compArgsCount);
- if (!compiler->lvaTable[compiler->lvaVarargsHandleArg].lvOnFrame)
- {
- noway_assert(!compiler->opts.compDbgCode);
- return;
- }
-
- // Can't check compiler->lvaTable[varNum].lvOnFrame as we don't set it for
- // arguments of vararg functions to avoid reporting them to GC.
- noway_assert(!compiler->lvaTable[varNum].lvRegister);
- unsigned cookieOffset = compiler->lvaTable[compiler->lvaVarargsHandleArg].lvStkOffs;
- unsigned varOffset = compiler->lvaTable[varNum].lvStkOffs;
-
- noway_assert(cookieOffset < varOffset);
- unsigned offset = varOffset - cookieOffset;
- unsigned stkArgSize = compiler->compArgSize - intRegState.rsCalleeRegArgCount * sizeof(void*);
- noway_assert(offset < stkArgSize);
- offset = stkArgSize - offset;
-
- varLoc.vlType = Compiler::VLT_FIXED_VA;
- varLoc.vlFixedVarArg.vlfvOffset = offset;
- }
-
-#endif // _TARGET_X86_
-
- VarName name = NULL;
-
-#ifdef DEBUG
-
- for (unsigned scopeNum = 0; scopeNum < compiler->info.compVarScopesCount; scopeNum++)
- {
- if (LVnum == compiler->info.compVarScopes[scopeNum].vsdLVnum)
- {
- name = compiler->info.compVarScopes[scopeNum].vsdName;
- }
- }
-
- // Hang on to this compiler->info.
-
- TrnslLocalVarInfo& tlvi = genTrnslLocalVarInfo[which];
-
- tlvi.tlviVarNum = ilVarNum;
- tlvi.tlviLVnum = LVnum;
- tlvi.tlviName = name;
- tlvi.tlviStartPC = startOffs;
- tlvi.tlviLength = length;
- tlvi.tlviAvailable = avail;
- tlvi.tlviVarLoc = varLoc;
-
-#endif // DEBUG
-
- compiler->eeSetLVinfo(which, startOffs, length, ilVarNum, LVnum, name, avail, varLoc);
-}
-
-#endif // DEBUGGING_SUPPORT
-
/*****************************************************************************
*
* Return non-zero if the given register is free after the given tree is
diff --git a/src/jit/codegenlinear.cpp b/src/jit/codegenlinear.cpp
new file mode 100644
index 0000000000..9713288e08
--- /dev/null
+++ b/src/jit/codegenlinear.cpp
@@ -0,0 +1,1773 @@
+// 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.
+
+/*XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+XX XX
+XX Code Generation Support Methods for Linear Codegen XX
+XX XX
+XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+*/
+#include "jitpch.h"
+#ifdef _MSC_VER
+#pragma hdrstop
+#endif
+
+#ifndef LEGACY_BACKEND // This file is ONLY used for the RyuJIT backend that uses the linear scan register allocator.
+#include "emit.h"
+#include "codegen.h"
+
+//------------------------------------------------------------------------
+// genCodeForBBlist: Generate code for all the blocks in a method
+//
+// Arguments:
+// None
+//
+// Notes:
+// This is the main method for linear codegen. It calls genCodeForTreeNode
+// to generate the code for each node in each BasicBlock, and handles BasicBlock
+// boundaries and branches.
+//
+void CodeGen::genCodeForBBlist()
+{
+ unsigned varNum;
+ LclVarDsc* varDsc;
+
+ unsigned savedStkLvl;
+
+#ifdef DEBUG
+ genInterruptibleUsed = true;
+
+ // You have to be careful if you create basic blocks from now on
+ compiler->fgSafeBasicBlockCreation = false;
+
+ // This stress mode is not comptible with fully interruptible GC
+ if (genInterruptible && compiler->opts.compStackCheckOnCall)
+ {
+ compiler->opts.compStackCheckOnCall = false;
+ }
+
+ // This stress mode is not comptible with fully interruptible GC
+ if (genInterruptible && compiler->opts.compStackCheckOnRet)
+ {
+ compiler->opts.compStackCheckOnRet = false;
+ }
+#endif // DEBUG
+
+ // Prepare the blocks for exception handling codegen: mark the blocks that needs labels.
+ genPrepForEHCodegen();
+
+ assert(!compiler->fgFirstBBScratch ||
+ compiler->fgFirstBB == compiler->fgFirstBBScratch); // compiler->fgFirstBBScratch has to be first.
+
+ /* Initialize the spill tracking logic */
+
+ regSet.rsSpillBeg();
+
+ /* Initialize the line# tracking logic */
+
+ if (compiler->opts.compScopeInfo)
+ {
+ siInit();
+ }
+
+ // The current implementation of switch tables requires the first block to have a label so it
+ // can generate offsets to the switch label targets.
+ // TODO-CQ: remove this when switches have been re-implemented to not use this.
+ if (compiler->fgHasSwitch)
+ {
+ compiler->fgFirstBB->bbFlags |= BBF_JMP_TARGET;
+ }
+
+ genPendingCallLabel = nullptr;
+
+ /* Initialize the pointer tracking code */
+
+ gcInfo.gcRegPtrSetInit();
+ gcInfo.gcVarPtrSetInit();
+
+ /* If any arguments live in registers, mark those regs as such */
+
+ for (varNum = 0, varDsc = compiler->lvaTable; varNum < compiler->lvaCount; varNum++, varDsc++)
+ {
+ /* Is this variable a parameter assigned to a register? */
+
+ if (!varDsc->lvIsParam || !varDsc->lvRegister)
+ {
+ continue;
+ }
+
+ /* Is the argument live on entry to the method? */
+
+ if (!VarSetOps::IsMember(compiler, compiler->fgFirstBB->bbLiveIn, varDsc->lvVarIndex))
+ {
+ continue;
+ }
+
+ /* Is this a floating-point argument? */
+
+ if (varDsc->IsFloatRegType())
+ {
+ continue;
+ }
+
+ noway_assert(!varTypeIsFloating(varDsc->TypeGet()));
+
+ /* Mark the register as holding the variable */
+
+ regTracker.rsTrackRegLclVar(varDsc->lvRegNum, varNum);
+ }
+
+ unsigned finallyNesting = 0;
+
+ // Make sure a set is allocated for compiler->compCurLife (in the long case), so we can set it to empty without
+ // allocation at the start of each basic block.
+ VarSetOps::AssignNoCopy(compiler, compiler->compCurLife, VarSetOps::MakeEmpty(compiler));
+
+ /*-------------------------------------------------------------------------
+ *
+ * Walk the basic blocks and generate code for each one
+ *
+ */
+
+ BasicBlock* block;
+ BasicBlock* lblk; /* previous block */
+
+ for (lblk = nullptr, block = compiler->fgFirstBB; block != nullptr; lblk = block, block = block->bbNext)
+ {
+#ifdef DEBUG
+ if (compiler->verbose)
+ {
+ printf("\n=============== Generating ");
+ block->dspBlockHeader(compiler, true, true);
+ compiler->fgDispBBLiveness(block);
+ }
+#endif // DEBUG
+
+ // Figure out which registers hold variables on entry to this block
+
+ regSet.ClearMaskVars();
+ gcInfo.gcRegGCrefSetCur = RBM_NONE;
+ gcInfo.gcRegByrefSetCur = RBM_NONE;
+
+ compiler->m_pLinearScan->recordVarLocationsAtStartOfBB(block);
+
+ genUpdateLife(block->bbLiveIn);
+
+ // Even if liveness didn't change, we need to update the registers containing GC references.
+ // genUpdateLife will update the registers live due to liveness changes. But what about registers that didn't
+ // change? We cleared them out above. Maybe we should just not clear them out, but update the ones that change
+ // here. That would require handling the changes in recordVarLocationsAtStartOfBB().
+
+ regMaskTP newLiveRegSet = RBM_NONE;
+ regMaskTP newRegGCrefSet = RBM_NONE;
+ regMaskTP newRegByrefSet = RBM_NONE;
+#ifdef DEBUG
+ VARSET_TP VARSET_INIT_NOCOPY(removedGCVars, VarSetOps::MakeEmpty(compiler));
+ VARSET_TP VARSET_INIT_NOCOPY(addedGCVars, VarSetOps::MakeEmpty(compiler));
+#endif
+ VARSET_ITER_INIT(compiler, iter, block->bbLiveIn, varIndex);
+ while (iter.NextElem(compiler, &varIndex))
+ {
+ unsigned varNum = compiler->lvaTrackedToVarNum[varIndex];
+ LclVarDsc* varDsc = &(compiler->lvaTable[varNum]);
+
+ if (varDsc->lvIsInReg())
+ {
+ newLiveRegSet |= varDsc->lvRegMask();
+ if (varDsc->lvType == TYP_REF)
+ {
+ newRegGCrefSet |= varDsc->lvRegMask();
+ }
+ else if (varDsc->lvType == TYP_BYREF)
+ {
+ newRegByrefSet |= varDsc->lvRegMask();
+ }
+#ifdef DEBUG
+ if (verbose && VarSetOps::IsMember(compiler, gcInfo.gcVarPtrSetCur, varIndex))
+ {
+ VarSetOps::AddElemD(compiler, removedGCVars, varIndex);
+ }
+#endif // DEBUG
+ VarSetOps::RemoveElemD(compiler, gcInfo.gcVarPtrSetCur, varIndex);
+ }
+ else if (compiler->lvaIsGCTracked(varDsc))
+ {
+#ifdef DEBUG
+ if (verbose && !VarSetOps::IsMember(compiler, gcInfo.gcVarPtrSetCur, varIndex))
+ {
+ VarSetOps::AddElemD(compiler, addedGCVars, varIndex);
+ }
+#endif // DEBUG
+ VarSetOps::AddElemD(compiler, gcInfo.gcVarPtrSetCur, varIndex);
+ }
+ }
+
+ regSet.rsMaskVars = newLiveRegSet;
+
+#ifdef DEBUG
+ if (compiler->verbose)
+ {
+ if (!VarSetOps::IsEmpty(compiler, addedGCVars))
+ {
+ printf("\t\t\t\t\t\t\tAdded GCVars: ");
+ dumpConvertedVarSet(compiler, addedGCVars);
+ printf("\n");
+ }
+ if (!VarSetOps::IsEmpty(compiler, removedGCVars))
+ {
+ printf("\t\t\t\t\t\t\tRemoved GCVars: ");
+ dumpConvertedVarSet(compiler, removedGCVars);
+ printf("\n");
+ }
+ }
+#endif // DEBUG
+
+ gcInfo.gcMarkRegSetGCref(newRegGCrefSet DEBUGARG(true));
+ gcInfo.gcMarkRegSetByref(newRegByrefSet DEBUGARG(true));
+
+ /* Blocks with handlerGetsXcptnObj()==true use GT_CATCH_ARG to
+ represent the exception object (TYP_REF).
+ We mark REG_EXCEPTION_OBJECT as holding a GC object on entry
+ to the block, it will be the first thing evaluated
+ (thanks to GTF_ORDER_SIDEEFF).
+ */
+
+ if (handlerGetsXcptnObj(block->bbCatchTyp))
+ {
+ for (GenTree* node : LIR::AsRange(block))
+ {
+ if (node->OperGet() == GT_CATCH_ARG)
+ {
+ gcInfo.gcMarkRegSetGCref(RBM_EXCEPTION_OBJECT);
+ break;
+ }
+ }
+ }
+
+ /* Start a new code output block */
+
+ genUpdateCurrentFunclet(block);
+
+#ifdef _TARGET_XARCH_
+ if (genAlignLoops && block->bbFlags & BBF_LOOP_HEAD)
+ {
+ getEmitter()->emitLoopAlign();
+ }
+#endif
+
+#ifdef DEBUG
+ if (compiler->opts.dspCode)
+ {
+ printf("\n L_M%03u_BB%02u:\n", Compiler::s_compMethodsCount, block->bbNum);
+ }
+#endif
+
+ block->bbEmitCookie = nullptr;
+
+ if (block->bbFlags & (BBF_JMP_TARGET | BBF_HAS_LABEL))
+ {
+ /* Mark a label and update the current set of live GC refs */
+
+ block->bbEmitCookie = getEmitter()->emitAddLabel(gcInfo.gcVarPtrSetCur, gcInfo.gcRegGCrefSetCur,
+ gcInfo.gcRegByrefSetCur, FALSE);
+ }
+
+ if (block == compiler->fgFirstColdBlock)
+ {
+#ifdef DEBUG
+ if (compiler->verbose)
+ {
+ printf("\nThis is the start of the cold region of the method\n");
+ }
+#endif
+ // We should never have a block that falls through into the Cold section
+ noway_assert(!lblk->bbFallsThrough());
+
+ // We require the block that starts the Cold section to have a label
+ noway_assert(block->bbEmitCookie);
+ getEmitter()->emitSetFirstColdIGCookie(block->bbEmitCookie);
+ }
+
+ /* Both stacks are always empty on entry to a basic block */
+
+ genStackLevel = 0;
+ genAdjustStackLevel(block);
+ savedStkLvl = genStackLevel;
+
+ /* Tell everyone which basic block we're working on */
+
+ compiler->compCurBB = block;
+
+ siBeginBlock(block);
+
+ // BBF_INTERNAL blocks don't correspond to any single IL instruction.
+ if (compiler->opts.compDbgInfo && (block->bbFlags & BBF_INTERNAL) &&
+ !compiler->fgBBisScratch(block)) // If the block is the distinguished first scratch block, then no need to
+ // emit a NO_MAPPING entry, immediately after the prolog.
+ {
+ genIPmappingAdd((IL_OFFSETX)ICorDebugInfo::NO_MAPPING, true);
+ }
+
+ bool firstMapping = true;
+
+#if FEATURE_EH_FUNCLETS
+ if (block->bbFlags & BBF_FUNCLET_BEG)
+ {
+ genReserveFuncletProlog(block);
+ }
+#endif // FEATURE_EH_FUNCLETS
+
+ // Clear compCurStmt and compCurLifeTree.
+ compiler->compCurStmt = nullptr;
+ compiler->compCurLifeTree = nullptr;
+
+ // Traverse the block in linear order, generating code for each node as we
+ // as we encounter it.
+ CLANG_FORMAT_COMMENT_ANCHOR;
+
+#ifdef DEBUG
+ // Set the use-order numbers for each node.
+ {
+ int useNum = 0;
+ for (GenTree* node : LIR::AsRange(block).NonPhiNodes())
+ {
+ assert((node->gtDebugFlags & GTF_DEBUG_NODE_CG_CONSUMED) == 0);
+
+ node->gtUseNum = -1;
+ if (node->isContained() || node->IsCopyOrReload())
+ {
+ continue;
+ }
+
+ for (GenTree* operand : node->Operands())
+ {
+ genNumberOperandUse(operand, useNum);
+ }
+ }
+ }
+#endif // DEBUG
+
+ IL_OFFSETX currentILOffset = BAD_IL_OFFSET;
+ for (GenTree* node : LIR::AsRange(block).NonPhiNodes())
+ {
+ // Do we have a new IL offset?
+ if (node->OperGet() == GT_IL_OFFSET)
+ {
+ genEnsureCodeEmitted(currentILOffset);
+ currentILOffset = node->gtStmt.gtStmtILoffsx;
+ genIPmappingAdd(currentILOffset, firstMapping);
+ firstMapping = false;
+ }
+
+#ifdef DEBUG
+ if (node->OperGet() == GT_IL_OFFSET)
+ {
+ noway_assert(node->gtStmt.gtStmtLastILoffs <= compiler->info.compILCodeSize ||
+ node->gtStmt.gtStmtLastILoffs == BAD_IL_OFFSET);
+
+ if (compiler->opts.dspCode && compiler->opts.dspInstrs &&
+ node->gtStmt.gtStmtLastILoffs != BAD_IL_OFFSET)
+ {
+ while (genCurDispOffset <= node->gtStmt.gtStmtLastILoffs)
+ {
+ genCurDispOffset += dumpSingleInstr(compiler->info.compCode, genCurDispOffset, "> ");
+ }
+ }
+ }
+#endif // DEBUG
+
+ genCodeForTreeNode(node);
+ if (node->gtHasReg() && node->gtLsraInfo.isLocalDefUse)
+ {
+ genConsumeReg(node);
+ }
+ } // end for each node in block
+
+#ifdef DEBUG
+ // The following set of register spill checks and GC pointer tracking checks used to be
+ // performed at statement boundaries. Now, with LIR, there are no statements, so they are
+ // performed at the end of each block.
+ // TODO: could these checks be performed more frequently? E.g., at each location where
+ // the register allocator says there are no live non-variable registers. Perhaps this could
+ // be done by (a) keeping a running count of live non-variable registers by using
+ // gtLsraInfo.srcCount and gtLsraInfo.dstCount to decrement and increment the count, respectively,
+ // and running the checks when the count is zero. Or, (b) use the map maintained by LSRA
+ // (operandToLocationInfoMap) to mark a node somehow when, after the execution of that node,
+ // there will be no live non-variable registers.
+
+ regSet.rsSpillChk();
+
+ /* Make sure we didn't bungle pointer register tracking */
+
+ regMaskTP ptrRegs = gcInfo.gcRegGCrefSetCur | gcInfo.gcRegByrefSetCur;
+ regMaskTP nonVarPtrRegs = ptrRegs & ~regSet.rsMaskVars;
+
+ // If return is a GC-type, clear it. Note that if a common
+ // epilog is generated (genReturnBB) it has a void return
+ // even though we might return a ref. We can't use the compRetType
+ // as the determiner because something we are tracking as a byref
+ // might be used as a return value of a int function (which is legal)
+ GenTree* blockLastNode = block->lastNode();
+ if ((blockLastNode != nullptr) && (blockLastNode->gtOper == GT_RETURN) &&
+ (varTypeIsGC(compiler->info.compRetType) ||
+ (blockLastNode->gtOp.gtOp1 != nullptr && varTypeIsGC(blockLastNode->gtOp.gtOp1->TypeGet()))))
+ {
+ nonVarPtrRegs &= ~RBM_INTRET;
+ }
+
+ if (nonVarPtrRegs)
+ {
+ printf("Regset after BB%02u gcr=", block->bbNum);
+ printRegMaskInt(gcInfo.gcRegGCrefSetCur & ~regSet.rsMaskVars);
+ compiler->getEmitter()->emitDispRegSet(gcInfo.gcRegGCrefSetCur & ~regSet.rsMaskVars);
+ printf(", byr=");
+ printRegMaskInt(gcInfo.gcRegByrefSetCur & ~regSet.rsMaskVars);
+ compiler->getEmitter()->emitDispRegSet(gcInfo.gcRegByrefSetCur & ~regSet.rsMaskVars);
+ printf(", regVars=");
+ printRegMaskInt(regSet.rsMaskVars);
+ compiler->getEmitter()->emitDispRegSet(regSet.rsMaskVars);
+ printf("\n");
+ }
+
+ noway_assert(nonVarPtrRegs == RBM_NONE);
+#endif // DEBUG
+
+#if defined(DEBUG)
+ if (block->bbNext == nullptr)
+ {
+// Unit testing of the emitter: generate a bunch of instructions into the last block
+// (it's as good as any, but better than the prolog, which can only be a single instruction
+// group) then use COMPlus_JitLateDisasm=* to see if the late disassembler
+// thinks the instructions are the same as we do.
+#if defined(_TARGET_AMD64_) && defined(LATE_DISASM)
+ genAmd64EmitterUnitTests();
+#elif defined(_TARGET_ARM64_)
+ genArm64EmitterUnitTests();
+#endif // _TARGET_ARM64_
+ }
+#endif // defined(DEBUG)
+
+ // It is possible to reach the end of the block without generating code for the current IL offset.
+ // For example, if the following IR ends the current block, no code will have been generated for
+ // offset 21:
+ //
+ // ( 0, 0) [000040] ------------ il_offset void IL offset: 21
+ //
+ // N001 ( 0, 0) [000039] ------------ nop void
+ //
+ // This can lead to problems when debugging the generated code. To prevent these issues, make sure
+ // we've generated code for the last IL offset we saw in the block.
+ genEnsureCodeEmitted(currentILOffset);
+
+ if (compiler->opts.compScopeInfo && (compiler->info.compVarScopesCount > 0))
+ {
+ siEndBlock(block);
+
+ /* Is this the last block, and are there any open scopes left ? */
+
+ bool isLastBlockProcessed = (block->bbNext == nullptr);
+ if (block->isBBCallAlwaysPair())
+ {
+ isLastBlockProcessed = (block->bbNext->bbNext == nullptr);
+ }
+
+ if (isLastBlockProcessed && siOpenScopeList.scNext)
+ {
+ /* This assert no longer holds, because we may insert a throw
+ block to demarcate the end of a try or finally region when they
+ are at the end of the method. It would be nice if we could fix
+ our code so that this throw block will no longer be necessary. */
+
+ // noway_assert(block->bbCodeOffsEnd != compiler->info.compILCodeSize);
+
+ siCloseAllOpenScopes();
+ }
+ }
+
+ genStackLevel -= savedStkLvl;
+
+#ifdef DEBUG
+ // compCurLife should be equal to the liveOut set, except that we don't keep
+ // it up to date for vars that are not register candidates
+ // (it would be nice to have a xor set function)
+
+ VARSET_TP VARSET_INIT_NOCOPY(extraLiveVars, VarSetOps::Diff(compiler, block->bbLiveOut, compiler->compCurLife));
+ VarSetOps::UnionD(compiler, extraLiveVars, VarSetOps::Diff(compiler, compiler->compCurLife, block->bbLiveOut));
+ VARSET_ITER_INIT(compiler, extraLiveVarIter, extraLiveVars, extraLiveVarIndex);
+ while (extraLiveVarIter.NextElem(compiler, &extraLiveVarIndex))
+ {
+ unsigned varNum = compiler->lvaTrackedToVarNum[extraLiveVarIndex];
+ LclVarDsc* varDsc = compiler->lvaTable + varNum;
+ assert(!varDsc->lvIsRegCandidate());
+ }
+#endif
+
+ /* Both stacks should always be empty on exit from a basic block */
+ noway_assert(genStackLevel == 0);
+
+#ifdef _TARGET_AMD64_
+ // On AMD64, we need to generate a NOP after a call that is the last instruction of the block, in several
+ // situations, to support proper exception handling semantics. This is mostly to ensure that when the stack
+ // walker computes an instruction pointer for a frame, that instruction pointer is in the correct EH region.
+ // The document "X64 and ARM ABIs.docx" has more details. The situations:
+ // 1. If the call instruction is in a different EH region as the instruction that follows it.
+ // 2. If the call immediately precedes an OS epilog. (Note that what the JIT or VM consider an epilog might
+ // be slightly different from what the OS considers an epilog, and it is the OS-reported epilog that matters
+ // here.)
+ // We handle case #1 here, and case #2 in the emitter.
+ if (getEmitter()->emitIsLastInsCall())
+ {
+ // Ok, the last instruction generated is a call instruction. Do any of the other conditions hold?
+ // Note: we may be generating a few too many NOPs for the case of call preceding an epilog. Technically,
+ // if the next block is a BBJ_RETURN, an epilog will be generated, but there may be some instructions
+ // generated before the OS epilog starts, such as a GS cookie check.
+ if ((block->bbNext == nullptr) || !BasicBlock::sameEHRegion(block, block->bbNext))
+ {
+ // We only need the NOP if we're not going to generate any more code as part of the block end.
+
+ switch (block->bbJumpKind)
+ {
+ case BBJ_ALWAYS:
+ case BBJ_THROW:
+ case BBJ_CALLFINALLY:
+ case BBJ_EHCATCHRET:
+ // We're going to generate more code below anyway, so no need for the NOP.
+
+ case BBJ_RETURN:
+ case BBJ_EHFINALLYRET:
+ case BBJ_EHFILTERRET:
+ // These are the "epilog follows" case, handled in the emitter.
+
+ break;
+
+ case BBJ_NONE:
+ if (block->bbNext == nullptr)
+ {
+ // Call immediately before the end of the code; we should never get here .
+ instGen(INS_BREAKPOINT); // This should never get executed
+ }
+ else
+ {
+ // We need the NOP
+ instGen(INS_nop);
+ }
+ break;
+
+ case BBJ_COND:
+ case BBJ_SWITCH:
+ // These can't have a call as the last instruction!
+
+ default:
+ noway_assert(!"Unexpected bbJumpKind");
+ break;
+ }
+ }
+ }
+#endif // _TARGET_AMD64_
+
+ /* Do we need to generate a jump or return? */
+
+ switch (block->bbJumpKind)
+ {
+ case BBJ_ALWAYS:
+ inst_JMP(EJ_jmp, block->bbJumpDest);
+ break;
+
+ case BBJ_RETURN:
+ genExitCode(block);
+ break;
+
+ case BBJ_THROW:
+ // If we have a throw at the end of a function or funclet, we need to emit another instruction
+ // afterwards to help the OS unwinder determine the correct context during unwind.
+ // We insert an unexecuted breakpoint instruction in several situations
+ // following a throw instruction:
+ // 1. If the throw is the last instruction of the function or funclet. This helps
+ // the OS unwinder determine the correct context during an unwind from the
+ // thrown exception.
+ // 2. If this is this is the last block of the hot section.
+ // 3. If the subsequent block is a special throw block.
+ // 4. On AMD64, if the next block is in a different EH region.
+ if ((block->bbNext == nullptr) || (block->bbNext->bbFlags & BBF_FUNCLET_BEG) ||
+ !BasicBlock::sameEHRegion(block, block->bbNext) ||
+ (!isFramePointerUsed() && compiler->fgIsThrowHlpBlk(block->bbNext)) ||
+ block->bbNext == compiler->fgFirstColdBlock)
+ {
+ instGen(INS_BREAKPOINT); // This should never get executed
+ }
+
+ break;
+
+ case BBJ_CALLFINALLY:
+ block = genCallFinally(block, lblk);
+ break;
+
+#if FEATURE_EH_FUNCLETS
+
+ case BBJ_EHCATCHRET:
+ genEHCatchRet(block);
+ __fallthrough;
+
+ case BBJ_EHFINALLYRET:
+ case BBJ_EHFILTERRET:
+ genReserveFuncletEpilog(block);
+ break;
+
+#else // !FEATURE_EH_FUNCLETS
+
+ case BBJ_EHCATCHRET:
+ noway_assert(!"Unexpected BBJ_EHCATCHRET"); // not used on x86
+
+ case BBJ_EHFINALLYRET:
+ case BBJ_EHFILTERRET:
+ genEHFinallyOrFilterRet(block);
+ break;
+
+#endif // !FEATURE_EH_FUNCLETS
+
+ case BBJ_NONE:
+ case BBJ_COND:
+ case BBJ_SWITCH:
+ break;
+
+ default:
+ noway_assert(!"Unexpected bbJumpKind");
+ break;
+ }
+
+#ifdef DEBUG
+ compiler->compCurBB = nullptr;
+#endif
+
+ } //------------------ END-FOR each block of the method -------------------
+
+ /* Nothing is live at this point */
+ genUpdateLife(VarSetOps::MakeEmpty(compiler));
+
+ /* Finalize the spill tracking logic */
+
+ regSet.rsSpillEnd();
+
+ /* Finalize the temp tracking logic */
+
+ compiler->tmpEnd();
+
+#ifdef DEBUG
+ if (compiler->verbose)
+ {
+ printf("\n# ");
+ printf("compCycleEstimate = %6d, compSizeEstimate = %5d ", compiler->compCycleEstimate,
+ compiler->compSizeEstimate);
+ printf("%s\n", compiler->info.compFullName);
+ }
+#endif
+}
+
+/*
+XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+XX XX
+XX Register Management XX
+XX XX
+XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+*/
+//
+
+//------------------------------------------------------------------------
+// genGetAssignedReg: Get the register assigned to the given node
+//
+// Arguments:
+// tree - the lclVar node whose assigned register we want
+//
+// Return Value:
+// The assigned regNumber
+//
+regNumber CodeGenInterface::genGetAssignedReg(GenTreePtr tree)
+{
+ return tree->gtRegNum;
+}
+
+//------------------------------------------------------------------------
+// genSpillVar: Spill a local variable
+//
+// Arguments:
+// tree - the lclVar node for the variable being spilled
+//
+// Return Value:
+// None.
+//
+// Assumptions:
+// The lclVar must be a register candidate (lvRegCandidate)
+
+void CodeGen::genSpillVar(GenTreePtr tree)
+{
+ unsigned varNum = tree->gtLclVarCommon.gtLclNum;
+ LclVarDsc* varDsc = &(compiler->lvaTable[varNum]);
+
+ assert(varDsc->lvIsRegCandidate());
+
+ // We don't actually need to spill if it is already living in memory
+ bool needsSpill = ((tree->gtFlags & GTF_VAR_DEF) == 0 && varDsc->lvIsInReg());
+ if (needsSpill)
+ {
+ // In order for a lclVar to have been allocated to a register, it must not have been aliasable, and can
+ // therefore be store-normalized (rather than load-normalized). In fact, not performing store normalization
+ // can lead to problems on architectures where a lclVar may be allocated to a register that is not
+ // addressable at the granularity of the lclVar's defined type (e.g. x86).
+ var_types lclTyp = genActualType(varDsc->TypeGet());
+ emitAttr size = emitTypeSize(lclTyp);
+
+ bool restoreRegVar = false;
+ if (tree->gtOper == GT_REG_VAR)
+ {
+ tree->SetOper(GT_LCL_VAR);
+ restoreRegVar = true;
+ }
+
+ // mask off the flag to generate the right spill code, then bring it back
+ tree->gtFlags &= ~GTF_REG_VAL;
+
+ instruction storeIns = ins_Store(tree->TypeGet(), compiler->isSIMDTypeLocalAligned(varNum));
+#if CPU_LONG_USES_REGPAIR
+ if (varTypeIsMultiReg(tree))
+ {
+ assert(varDsc->lvRegNum == genRegPairLo(tree->gtRegPair));
+ assert(varDsc->lvOtherReg == genRegPairHi(tree->gtRegPair));
+ regNumber regLo = genRegPairLo(tree->gtRegPair);
+ regNumber regHi = genRegPairHi(tree->gtRegPair);
+ inst_TT_RV(storeIns, tree, regLo);
+ inst_TT_RV(storeIns, tree, regHi, 4);
+ }
+ else
+#endif
+ {
+ assert(varDsc->lvRegNum == tree->gtRegNum);
+ inst_TT_RV(storeIns, tree, tree->gtRegNum, 0, size);
+ }
+ tree->gtFlags |= GTF_REG_VAL;
+
+ if (restoreRegVar)
+ {
+ tree->SetOper(GT_REG_VAR);
+ }
+
+ genUpdateRegLife(varDsc, /*isBorn*/ false, /*isDying*/ true DEBUGARG(tree));
+ gcInfo.gcMarkRegSetNpt(varDsc->lvRegMask());
+
+ if (VarSetOps::IsMember(compiler, gcInfo.gcTrkStkPtrLcls, varDsc->lvVarIndex))
+ {
+#ifdef DEBUG
+ if (!VarSetOps::IsMember(compiler, gcInfo.gcVarPtrSetCur, varDsc->lvVarIndex))
+ {
+ JITDUMP("\t\t\t\t\t\t\tVar V%02u becoming live\n", varNum);
+ }
+ else
+ {
+ JITDUMP("\t\t\t\t\t\t\tVar V%02u continuing live\n", varNum);
+ }
+#endif
+ VarSetOps::AddElemD(compiler, gcInfo.gcVarPtrSetCur, varDsc->lvVarIndex);
+ }
+ }
+
+ tree->gtFlags &= ~GTF_SPILL;
+ varDsc->lvRegNum = REG_STK;
+ if (varTypeIsMultiReg(tree))
+ {
+ varDsc->lvOtherReg = REG_STK;
+ }
+}
+
+//------------------------------------------------------------------------
+// genUpdateVarReg: Update the current register location for a lclVar
+//
+// Arguments:
+// varDsc - the LclVarDsc for the lclVar
+// tree - the lclVar node
+//
+// inline
+void CodeGenInterface::genUpdateVarReg(LclVarDsc* varDsc, GenTreePtr tree)
+{
+ assert(tree->OperIsScalarLocal() || (tree->gtOper == GT_COPY));
+ varDsc->lvRegNum = tree->gtRegNum;
+}
+
+//------------------------------------------------------------------------
+// sameRegAsDst: Return the child that has the same reg as the dst (if any)
+//
+// Arguments:
+// tree - the node of interest
+// other - an out parameter to return the other child
+//
+// Notes:
+// If 'tree' has a child with the same assigned register as its target reg,
+// that child will be returned, and 'other' will contain the non-matching child.
+// Otherwise, both other and the return value will be nullptr.
+//
+GenTree* sameRegAsDst(GenTree* tree, GenTree*& other /*out*/)
+{
+ if (tree->gtRegNum == REG_NA)
+ {
+ other = nullptr;
+ return nullptr;
+ }
+
+ GenTreePtr op1 = tree->gtOp.gtOp1;
+ GenTreePtr op2 = tree->gtOp.gtOp2;
+ if (op1->gtRegNum == tree->gtRegNum)
+ {
+ other = op2;
+ return op1;
+ }
+ if (op2->gtRegNum == tree->gtRegNum)
+ {
+ other = op1;
+ return op2;
+ }
+ else
+ {
+ other = nullptr;
+ return nullptr;
+ }
+}
+
+//------------------------------------------------------------------------
+// genUnspillRegIfNeeded: Reload the value into a register, if needed
+//
+// Arguments:
+// tree - the node of interest.
+//
+// Notes:
+// In the normal case, the value will be reloaded into the register it
+// was originally computed into. However, if that register is not available,
+// the register allocator will have allocated a different register, and
+// inserted a GT_RELOAD to indicate the register into which it should be
+// reloaded.
+//
+void CodeGen::genUnspillRegIfNeeded(GenTree* tree)
+{
+ regNumber dstReg = tree->gtRegNum;
+ GenTree* unspillTree = tree;
+
+ if (tree->gtOper == GT_RELOAD)
+ {
+ unspillTree = tree->gtOp.gtOp1;
+ }
+
+ if ((unspillTree->gtFlags & GTF_SPILLED) != 0)
+ {
+ if (genIsRegCandidateLocal(unspillTree))
+ {
+ // Reset spilled flag, since we are going to load a local variable from its home location.
+ unspillTree->gtFlags &= ~GTF_SPILLED;
+
+ GenTreeLclVarCommon* lcl = unspillTree->AsLclVarCommon();
+ LclVarDsc* varDsc = &compiler->lvaTable[lcl->gtLclNum];
+
+// TODO-Cleanup: The following code could probably be further merged and cleaned up.
+#ifdef _TARGET_XARCH_
+ // Load local variable from its home location.
+ // In most cases the tree type will indicate the correct type to use for the load.
+ // However, if it is NOT a normalizeOnLoad lclVar (i.e. NOT a small int that always gets
+ // widened when loaded into a register), and its size is not the same as genActualType of
+ // the type of the lclVar, then we need to change the type of the tree node when loading.
+ // This situation happens due to "optimizations" that avoid a cast and
+ // simply retype the node when using long type lclVar as an int.
+ // While loading the int in that case would work for this use of the lclVar, if it is
+ // later used as a long, we will have incorrectly truncated the long.
+ // In the normalizeOnLoad case ins_Load will return an appropriate sign- or zero-
+ // extending load.
+
+ var_types treeType = unspillTree->TypeGet();
+ if (treeType != genActualType(varDsc->lvType) && !varTypeIsGC(treeType) && !varDsc->lvNormalizeOnLoad())
+ {
+ assert(!varTypeIsGC(varDsc));
+ var_types spillType = genActualType(varDsc->lvType);
+ unspillTree->gtType = spillType;
+ inst_RV_TT(ins_Load(spillType, compiler->isSIMDTypeLocalAligned(lcl->gtLclNum)), dstReg, unspillTree);
+ unspillTree->gtType = treeType;
+ }
+ else
+ {
+ inst_RV_TT(ins_Load(treeType, compiler->isSIMDTypeLocalAligned(lcl->gtLclNum)), dstReg, unspillTree);
+ }
+#elif defined(_TARGET_ARM64_)
+ var_types targetType = unspillTree->gtType;
+ instruction ins = ins_Load(targetType, compiler->isSIMDTypeLocalAligned(lcl->gtLclNum));
+ emitAttr attr = emitTypeSize(targetType);
+ emitter* emit = getEmitter();
+
+ // Fixes Issue #3326
+ attr = emit->emitInsAdjustLoadStoreAttr(ins, attr);
+
+ // Load local variable from its home location.
+ inst_RV_TT(ins, dstReg, unspillTree, 0, attr);
+#else
+ NYI("Unspilling not implemented for this target architecture.");
+#endif
+ unspillTree->SetInReg();
+
+ // TODO-Review: We would like to call:
+ // genUpdateRegLife(varDsc, /*isBorn*/ true, /*isDying*/ false DEBUGARG(tree));
+ // instead of the following code, but this ends up hitting this assert:
+ // assert((regSet.rsMaskVars & regMask) == 0);
+ // due to issues with LSRA resolution moves.
+ // So, just force it for now. This probably indicates a condition that creates a GC hole!
+ //
+ // Extra note: I think we really want to call something like gcInfo.gcUpdateForRegVarMove,
+ // because the variable is not really going live or dead, but that method is somewhat poorly
+ // factored because it, in turn, updates rsMaskVars which is part of RegSet not GCInfo.
+ // TODO-Cleanup: This code exists in other CodeGen*.cpp files, and should be moved to CodeGenCommon.cpp.
+
+ // Don't update the variable's location if we are just re-spilling it again.
+
+ if ((unspillTree->gtFlags & GTF_SPILL) == 0)
+ {
+ genUpdateVarReg(varDsc, tree);
+#ifdef DEBUG
+ if (VarSetOps::IsMember(compiler, gcInfo.gcVarPtrSetCur, varDsc->lvVarIndex))
+ {
+ JITDUMP("\t\t\t\t\t\t\tRemoving V%02u from gcVarPtrSetCur\n", lcl->gtLclNum);
+ }
+#endif // DEBUG
+ VarSetOps::RemoveElemD(compiler, gcInfo.gcVarPtrSetCur, varDsc->lvVarIndex);
+
+#ifdef DEBUG
+ if (compiler->verbose)
+ {
+ printf("\t\t\t\t\t\t\tV%02u in reg ", lcl->gtLclNum);
+ varDsc->PrintVarReg();
+ printf(" is becoming live ");
+ compiler->printTreeID(unspillTree);
+ printf("\n");
+ }
+#endif // DEBUG
+
+ regSet.AddMaskVars(genGetRegMask(varDsc));
+ }
+
+ gcInfo.gcMarkRegPtrVal(dstReg, unspillTree->TypeGet());
+ }
+ else if (unspillTree->IsMultiRegCall())
+ {
+ GenTreeCall* call = unspillTree->AsCall();
+ ReturnTypeDesc* retTypeDesc = call->GetReturnTypeDesc();
+ unsigned regCount = retTypeDesc->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 = retTypeDesc->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->TypeGet()), dstReg,
+ t->tdTempNum(), 0);
+ compiler->tmpRlsTemp(t);
+
+ unspillTree->gtFlags &= ~GTF_SPILLED;
+ unspillTree->SetInReg();
+ gcInfo.gcMarkRegPtrVal(dstReg, unspillTree->TypeGet());
+ }
+ }
+}
+
+//------------------------------------------------------------------------
+// genCopyRegIfNeeded: Copy the given node into the specified register
+//
+// Arguments:
+// node - The node that has been evaluated (consumed).
+// needReg - The register in which its value is needed.
+//
+// Notes:
+// This must be a node that has a register.
+//
+void CodeGen::genCopyRegIfNeeded(GenTree* node, regNumber needReg)
+{
+ assert((node->gtRegNum != REG_NA) && (needReg != REG_NA));
+ if (node->gtRegNum != needReg)
+ {
+ inst_RV_RV(INS_mov, needReg, node->gtRegNum, node->TypeGet());
+ }
+}
+
+// Do Liveness update for a subnodes that is being consumed by codegen
+// including the logic for reload in case is needed and also takes care
+// of locating the value on the desired register.
+void CodeGen::genConsumeRegAndCopy(GenTree* node, regNumber needReg)
+{
+ if (needReg == REG_NA)
+ {
+ return;
+ }
+ regNumber treeReg = genConsumeReg(node);
+ genCopyRegIfNeeded(node, needReg);
+}
+
+// Check that registers are consumed in the right order for the current node being generated.
+#ifdef DEBUG
+void CodeGen::genNumberOperandUse(GenTree* const operand, int& useNum) const
+{
+ assert(operand != nullptr);
+ assert(operand->gtUseNum == -1);
+
+ // Ignore argument placeholders.
+ if (operand->OperGet() == GT_ARGPLACE)
+ {
+ return;
+ }
+
+ if (!operand->isContained() && !operand->IsCopyOrReload())
+ {
+ operand->gtUseNum = useNum;
+ useNum++;
+ }
+ else
+ {
+ for (GenTree* operand : operand->Operands())
+ {
+ genNumberOperandUse(operand, useNum);
+ }
+ }
+}
+
+void CodeGen::genCheckConsumeNode(GenTree* const node)
+{
+ assert(node != nullptr);
+
+ if (verbose)
+ {
+ if ((node->gtDebugFlags & GTF_DEBUG_NODE_CG_CONSUMED) != 0)
+ {
+ printf("Node was consumed twice:\n");
+ compiler->gtDispTree(node, nullptr, nullptr, true);
+ }
+ else if ((lastConsumedNode != nullptr) && (node->gtUseNum < lastConsumedNode->gtUseNum))
+ {
+ printf("Nodes were consumed out-of-order:\n");
+ compiler->gtDispTree(lastConsumedNode, nullptr, nullptr, true);
+ compiler->gtDispTree(node, nullptr, nullptr, true);
+ }
+ }
+
+ assert((node->OperGet() == GT_CATCH_ARG) || ((node->gtDebugFlags & GTF_DEBUG_NODE_CG_CONSUMED) == 0));
+ assert((lastConsumedNode == nullptr) || (node->gtUseNum == -1) || (node->gtUseNum > lastConsumedNode->gtUseNum));
+
+ node->gtDebugFlags |= GTF_DEBUG_NODE_CG_CONSUMED;
+ lastConsumedNode = node;
+}
+#endif // DEBUG
+
+//--------------------------------------------------------------------
+// genConsumeReg: Do liveness update for a subnode that is being
+// consumed by codegen.
+//
+// Arguments:
+// tree - GenTree node
+//
+// Return Value:
+// Returns the reg number of tree.
+// In case of multi-reg call node returns the first reg number
+// of the multi-reg return.
+regNumber CodeGen::genConsumeReg(GenTree* tree)
+{
+ if (tree->OperGet() == GT_COPY)
+ {
+ genRegCopy(tree);
+ }
+
+ // Handle the case where we have a lclVar that needs to be copied before use (i.e. because it
+ // interferes with one of the other sources (or the target, if it's a "delayed use" register)).
+ // TODO-Cleanup: This is a special copyReg case in LSRA - consider eliminating these and
+ // always using GT_COPY to make the lclVar location explicit.
+ // Note that we have to do this before calling genUpdateLife because otherwise if we spill it
+ // the lvRegNum will be set to REG_STK and we will lose track of what register currently holds
+ // the lclVar (normally when a lclVar is spilled it is then used from its former register
+ // location, which matches the gtRegNum on the node).
+ // (Note that it doesn't matter if we call this before or after genUnspillRegIfNeeded
+ // because if it's on the stack it will always get reloaded into tree->gtRegNum).
+ if (genIsRegCandidateLocal(tree))
+ {
+ GenTreeLclVarCommon* lcl = tree->AsLclVarCommon();
+ LclVarDsc* varDsc = &compiler->lvaTable[lcl->GetLclNum()];
+ if (varDsc->lvRegNum != REG_STK && varDsc->lvRegNum != tree->gtRegNum)
+ {
+ inst_RV_RV(ins_Copy(tree->TypeGet()), tree->gtRegNum, varDsc->lvRegNum);
+ }
+ }
+
+ genUnspillRegIfNeeded(tree);
+
+ // genUpdateLife() will also spill local var if marked as GTF_SPILL by calling CodeGen::genSpillVar
+ genUpdateLife(tree);
+
+ assert(tree->gtHasReg());
+
+ // there are three cases where consuming a reg means clearing the bit in the live mask
+ // 1. it was not produced by a local
+ // 2. it was produced by a local that is going dead
+ // 3. it was produced by a local that does not live in that reg (like one allocated on the stack)
+
+ if (genIsRegCandidateLocal(tree))
+ {
+ GenTreeLclVarCommon* lcl = tree->AsLclVarCommon();
+ LclVarDsc* varDsc = &compiler->lvaTable[lcl->GetLclNum()];
+ assert(varDsc->lvLRACandidate);
+
+ if ((tree->gtFlags & GTF_VAR_DEATH) != 0)
+ {
+ gcInfo.gcMarkRegSetNpt(genRegMask(varDsc->lvRegNum));
+ }
+ else if (varDsc->lvRegNum == REG_STK)
+ {
+ // We have loaded this into a register only temporarily
+ gcInfo.gcMarkRegSetNpt(genRegMask(tree->gtRegNum));
+ }
+ }
+ else
+ {
+ gcInfo.gcMarkRegSetNpt(tree->gtGetRegMask());
+ }
+
+ genCheckConsumeNode(tree);
+ return tree->gtRegNum;
+}
+
+// Do liveness update for an address tree: one of GT_LEA, GT_LCL_VAR, or GT_CNS_INT (for call indirect).
+void CodeGen::genConsumeAddress(GenTree* addr)
+{
+ if (!addr->isContained())
+ {
+ genConsumeReg(addr);
+ }
+ else if (addr->OperGet() == GT_LEA)
+ {
+ genConsumeAddrMode(addr->AsAddrMode());
+ }
+}
+
+// do liveness update for a subnode that is being consumed by codegen
+void CodeGen::genConsumeAddrMode(GenTreeAddrMode* addr)
+{
+ genConsumeOperands(addr);
+}
+
+void CodeGen::genConsumeRegs(GenTree* tree)
+{
+#if !defined(_TARGET_64BIT_)
+ if (tree->OperGet() == GT_LONG)
+ {
+ genConsumeRegs(tree->gtGetOp1());
+ genConsumeRegs(tree->gtGetOp2());
+ return;
+ }
+#endif // !defined(_TARGET_64BIT_)
+
+ if (tree->isContained())
+ {
+ if (tree->isContainedSpillTemp())
+ {
+ // spill temps are un-tracked and hence no need to update life
+ }
+ else if (tree->isIndir())
+ {
+ genConsumeAddress(tree->AsIndir()->Addr());
+ }
+ else if (tree->OperGet() == GT_AND)
+ {
+ // This is the special contained GT_AND that we created in Lowering::TreeNodeInfoInitCmp()
+ // Now we need to consume the operands of the GT_AND node.
+ genConsumeOperands(tree->AsOp());
+ }
+#ifdef _TARGET_XARCH_
+ else if (tree->OperGet() == GT_LCL_VAR)
+ {
+ // A contained lcl var must be living on stack and marked as reg optional, or not be a
+ // register candidate.
+ unsigned varNum = tree->AsLclVarCommon()->GetLclNum();
+ LclVarDsc* varDsc = compiler->lvaTable + varNum;
+
+ noway_assert(varDsc->lvRegNum == REG_STK);
+ noway_assert(tree->IsRegOptional() || !varDsc->lvLRACandidate);
+
+ // Update the life of the lcl var.
+ genUpdateLife(tree);
+ }
+#endif // _TARGET_XARCH_
+ else if (tree->OperIsInitVal())
+ {
+ genConsumeReg(tree->gtGetOp1());
+ }
+ else
+ {
+#ifdef FEATURE_SIMD
+ // (In)Equality operation that produces bool result, when compared
+ // against Vector zero, marks its Vector Zero operand as contained.
+ assert(tree->OperIsLeaf() || tree->IsIntegralConstVector(0));
+#else
+ assert(tree->OperIsLeaf());
+#endif
+ }
+ }
+ else
+ {
+ genConsumeReg(tree);
+ }
+}
+
+//------------------------------------------------------------------------
+// genConsumeOperands: Do liveness update for the operands of a unary or binary tree
+//
+// Arguments:
+// tree - the GenTreeOp whose operands will have their liveness updated.
+//
+// Return Value:
+// None.
+//
+// Notes:
+// Note that this logic is localized here because we must do the liveness update in
+// the correct execution order. This is important because we may have two operands
+// that involve the same lclVar, and if one is marked "lastUse" we must handle it
+// after the first.
+
+void CodeGen::genConsumeOperands(GenTreeOp* tree)
+{
+ GenTree* firstOp = tree->gtOp1;
+ GenTree* secondOp = tree->gtOp2;
+ if ((tree->gtFlags & GTF_REVERSE_OPS) != 0)
+ {
+ assert(secondOp != nullptr);
+ firstOp = secondOp;
+ secondOp = tree->gtOp1;
+ }
+ if (firstOp != nullptr)
+ {
+ genConsumeRegs(firstOp);
+ }
+ if (secondOp != nullptr)
+ {
+ genConsumeRegs(secondOp);
+ }
+}
+
+#if FEATURE_PUT_STRUCT_ARG_STK
+//------------------------------------------------------------------------
+// genConsumePutStructArgStk: Do liveness update for the operands of a PutArgStk node.
+// Also loads in the right register the addresses of the
+// src/dst for rep mov operation.
+//
+// Arguments:
+// putArgNode - the PUTARG_STK tree.
+// dstReg - the dstReg for the rep move operation.
+// srcReg - the srcReg for the rep move operation.
+// sizeReg - the sizeReg for the rep move operation.
+//
+// Return Value:
+// None.
+//
+// Notes:
+// sizeReg can be REG_NA when this function is used to consume the dstReg and srcReg
+// for copying on the stack a struct with references.
+// The source address/offset is determined from the address on the GT_OBJ node, while
+// the destination address is the address contained in 'm_stkArgVarNum' plus the offset
+// provided in the 'putArgNode'.
+// m_stkArgVarNum must be set to the varnum for the local used for placing the "by-value" args on the stack.
+
+void CodeGen::genConsumePutStructArgStk(GenTreePutArgStk* putArgNode,
+ regNumber dstReg,
+ regNumber srcReg,
+ regNumber sizeReg)
+{
+ assert(varTypeIsStruct(putArgNode));
+
+ // The putArgNode children are always contained. We should not consume any registers.
+ assert(putArgNode->gtGetOp1()->isContained());
+
+ GenTree* dstAddr = putArgNode;
+
+ // Get the source address.
+ GenTree* src = putArgNode->gtGetOp1();
+ assert((src->gtOper == GT_OBJ) || ((src->gtOper == GT_IND && varTypeIsSIMD(src))));
+ GenTree* srcAddr = src->gtGetOp1();
+
+ size_t size = putArgNode->getArgSize();
+
+ assert(dstReg != REG_NA);
+ assert(srcReg != REG_NA);
+
+ // Consume the registers only if they are not contained or set to REG_NA.
+ if (srcAddr->gtRegNum != REG_NA)
+ {
+ genConsumeReg(srcAddr);
+ }
+
+ // If the op1 is already in the dstReg - nothing to do.
+ // Otherwise load the op1 (GT_ADDR) into the dstReg to copy the struct on the stack by value.
+ CLANG_FORMAT_COMMENT_ANCHOR;
+
+#ifdef _TARGET_X86_
+ assert(dstReg != REG_SPBASE);
+ inst_RV_RV(INS_mov, dstReg, REG_SPBASE);
+#else // !_TARGET_X86_
+ if (dstAddr->gtRegNum != dstReg)
+ {
+ // Generate LEA instruction to load the stack of the outgoing var + SlotNum offset (or the incoming arg area
+ // for tail calls) in RDI.
+ // Destination is always local (on the stack) - use EA_PTRSIZE.
+ assert(m_stkArgVarNum != BAD_VAR_NUM);
+ getEmitter()->emitIns_R_S(INS_lea, EA_PTRSIZE, dstReg, m_stkArgVarNum, putArgNode->getArgOffset());
+ }
+#endif // !_TARGET_X86_
+
+ if (srcAddr->gtRegNum != srcReg)
+ {
+ if (srcAddr->OperIsLocalAddr())
+ {
+ // The OperLocalAddr is always contained.
+ assert(srcAddr->isContained());
+ GenTreeLclVarCommon* lclNode = srcAddr->AsLclVarCommon();
+
+ // Generate LEA instruction to load the LclVar address in RSI.
+ // Source is known to be on the stack. Use EA_PTRSIZE.
+ unsigned int offset = 0;
+ if (srcAddr->OperGet() == GT_LCL_FLD_ADDR)
+ {
+ offset = srcAddr->AsLclFld()->gtLclOffs;
+ }
+ getEmitter()->emitIns_R_S(INS_lea, EA_PTRSIZE, srcReg, lclNode->gtLclNum, offset);
+ }
+ else
+ {
+ assert(srcAddr->gtRegNum != REG_NA);
+ // Source is not known to be on the stack. Use EA_BYREF.
+ getEmitter()->emitIns_R_R(INS_mov, EA_BYREF, srcReg, srcAddr->gtRegNum);
+ }
+ }
+
+ if (sizeReg != REG_NA)
+ {
+ inst_RV_IV(INS_mov, sizeReg, size, EA_PTRSIZE);
+ }
+}
+#endif // FEATURE_PUT_STRUCT_ARG_STK
+
+//------------------------------------------------------------------------
+// genSetBlockSize: Ensure that the block size is in the given register
+//
+// Arguments:
+// blkNode - The block node
+// sizeReg - The register into which the block's size should go
+//
+
+void CodeGen::genSetBlockSize(GenTreeBlk* blkNode, regNumber sizeReg)
+{
+ if (sizeReg != REG_NA)
+ {
+ unsigned blockSize = blkNode->Size();
+ if (blockSize != 0)
+ {
+ assert((blkNode->gtRsvdRegs & genRegMask(sizeReg)) != 0);
+ genSetRegToIcon(sizeReg, blockSize);
+ }
+ else
+ {
+ noway_assert(blkNode->gtOper == GT_STORE_DYN_BLK);
+ GenTree* sizeNode = blkNode->AsDynBlk()->gtDynamicSize;
+ if (sizeNode->gtRegNum != sizeReg)
+ {
+ inst_RV_RV(INS_mov, sizeReg, sizeNode->gtRegNum, sizeNode->TypeGet());
+ }
+ }
+ }
+}
+
+//------------------------------------------------------------------------
+// genConsumeBlockSrc: Consume the source address register of a block node, if any.
+//
+// Arguments:
+// blkNode - The block node
+
+void CodeGen::genConsumeBlockSrc(GenTreeBlk* blkNode)
+{
+ GenTree* src = blkNode->Data();
+ if (blkNode->OperIsCopyBlkOp())
+ {
+ // For a CopyBlk we need the address of the source.
+ if (src->OperGet() == GT_IND)
+ {
+ src = src->gtOp.gtOp1;
+ }
+ else
+ {
+ // This must be a local.
+ // For this case, there is no source address register, as it is a
+ // stack-based address.
+ assert(src->OperIsLocal());
+ return;
+ }
+ }
+ else
+ {
+ if (src->OperIsInitVal())
+ {
+ src = src->gtGetOp1();
+ }
+ }
+ genConsumeReg(src);
+}
+
+//------------------------------------------------------------------------
+// genSetBlockSrc: Ensure that the block source is in its allocated register.
+//
+// Arguments:
+// blkNode - The block node
+// srcReg - The register in which to set the source (address or init val).
+//
+void CodeGen::genSetBlockSrc(GenTreeBlk* blkNode, regNumber srcReg)
+{
+ GenTree* src = blkNode->Data();
+ if (blkNode->OperIsCopyBlkOp())
+ {
+ // For a CopyBlk we need the address of the source.
+ if (src->OperGet() == GT_IND)
+ {
+ src = src->gtOp.gtOp1;
+ }
+ else
+ {
+ // This must be a local struct.
+ // Load its address into srcReg.
+ inst_RV_TT(INS_lea, srcReg, src, 0, EA_BYREF);
+ return;
+ }
+ }
+ else
+ {
+ if (src->OperIsInitVal())
+ {
+ src = src->gtGetOp1();
+ }
+ }
+ genCopyRegIfNeeded(src, srcReg);
+}
+
+//------------------------------------------------------------------------
+// genConsumeBlockOp: Ensure that the block's operands are enregistered
+// as needed.
+// Arguments:
+// blkNode - The block node
+//
+// Notes:
+// This ensures that the operands are consumed in the proper order to
+// obey liveness modeling.
+
+void CodeGen::genConsumeBlockOp(GenTreeBlk* blkNode, regNumber dstReg, regNumber srcReg, regNumber sizeReg)
+{
+ // We have to consume the registers, and perform any copies, in the actual execution order.
+ // The nominal order is: dst, src, size. However this may have been changed
+ // with reverse flags on the blkNode and the setting of gtEvalSizeFirst in the case of a dynamic
+ // block size.
+ // Note that the register allocator ensures that the registers ON THE NODES will not interfere
+ // with one another if consumed (i.e. reloaded or moved to their ASSIGNED reg) in execution order.
+ // Further, it ensures that they will not interfere with one another if they are then copied
+ // to the REQUIRED register (if a fixed register requirement) in execution order. This requires,
+ // then, that we first consume all the operands, then do any necessary moves.
+
+ GenTree* dstAddr = blkNode->Addr();
+ GenTree* src = nullptr;
+ unsigned blockSize = blkNode->Size();
+ GenTree* size = nullptr;
+ bool evalSizeFirst = true;
+
+ // First, consume all the sources in order
+ if (blkNode->OperGet() == GT_STORE_DYN_BLK)
+ {
+ size = blkNode->AsDynBlk()->gtDynamicSize;
+ if (blkNode->AsDynBlk()->gtEvalSizeFirst)
+ {
+ genConsumeReg(size);
+ }
+ else
+ {
+ evalSizeFirst = false;
+ }
+ }
+ if (blkNode->IsReverseOp())
+ {
+
+ genConsumeBlockSrc(blkNode);
+ genConsumeReg(dstAddr);
+ }
+ else
+ {
+ genConsumeReg(dstAddr);
+ genConsumeBlockSrc(blkNode);
+ }
+ if (!evalSizeFirst)
+ {
+ noway_assert(size != nullptr);
+ genConsumeReg(size);
+ }
+
+ // Next, perform any necessary moves.
+ if (evalSizeFirst)
+ {
+ genSetBlockSize(blkNode, sizeReg);
+ }
+ if (blkNode->IsReverseOp())
+ {
+ genSetBlockSrc(blkNode, srcReg);
+ genCopyRegIfNeeded(dstAddr, dstReg);
+ }
+ else
+ {
+ genCopyRegIfNeeded(dstAddr, dstReg);
+ genSetBlockSrc(blkNode, srcReg);
+ }
+ if (!evalSizeFirst)
+ {
+ genSetBlockSize(blkNode, sizeReg);
+ }
+}
+
+//-------------------------------------------------------------------------
+// genProduceReg: do liveness update for register produced by the current
+// node in codegen.
+//
+// Arguments:
+// tree - Gentree node
+//
+// Return Value:
+// None.
+void CodeGen::genProduceReg(GenTree* tree)
+{
+#ifdef DEBUG
+ assert((tree->gtDebugFlags & GTF_DEBUG_NODE_CG_PRODUCED) == 0);
+ tree->gtDebugFlags |= GTF_DEBUG_NODE_CG_PRODUCED;
+#endif
+
+ if (tree->gtFlags & GTF_SPILL)
+ {
+ // Code for GT_COPY node gets generated as part of consuming regs by its parent.
+ // A GT_COPY node in turn produces reg result and it should never be marked to
+ // spill.
+ //
+ // Similarly GT_RELOAD node gets generated as part of consuming regs by its
+ // parent and should never be marked for spilling.
+ noway_assert(!tree->IsCopyOrReload());
+
+ if (genIsRegCandidateLocal(tree))
+ {
+ // Store local variable to its home location.
+ tree->gtFlags &= ~GTF_REG_VAL;
+ // Ensure that lclVar stores are typed correctly.
+ unsigned varNum = tree->gtLclVarCommon.gtLclNum;
+ assert(!compiler->lvaTable[varNum].lvNormalizeOnStore() ||
+ (tree->TypeGet() == genActualType(compiler->lvaTable[varNum].TypeGet())));
+ inst_TT_RV(ins_Store(tree->gtType, compiler->isSIMDTypeLocalAligned(varNum)), tree, tree->gtRegNum);
+ }
+ else
+ {
+ // In case of multi-reg call node, spill flag on call node
+ // indicates that one or more of its allocated regs need to
+ // be spilled. Call node needs to be further queried to
+ // know which of its result regs needs to be spilled.
+ if (tree->IsMultiRegCall())
+ {
+ GenTreeCall* call = tree->AsCall();
+ ReturnTypeDesc* retTypeDesc = call->GetReturnTypeDesc();
+ unsigned regCount = retTypeDesc->GetReturnRegCount();
+
+ for (unsigned i = 0; i < regCount; ++i)
+ {
+ unsigned flags = call->GetRegSpillFlagByIdx(i);
+ if ((flags & GTF_SPILL) != 0)
+ {
+ regNumber reg = call->GetRegNumByIdx(i);
+ call->SetInReg();
+ regSet.rsSpillTree(reg, call, i);
+ gcInfo.gcMarkRegSetNpt(genRegMask(reg));
+ }
+ }
+ }
+ else
+ {
+ tree->SetInReg();
+ regSet.rsSpillTree(tree->gtRegNum, tree);
+ gcInfo.gcMarkRegSetNpt(genRegMask(tree->gtRegNum));
+ }
+
+ tree->gtFlags |= GTF_SPILLED;
+ tree->gtFlags &= ~GTF_SPILL;
+
+ return;
+ }
+ }
+
+ genUpdateLife(tree);
+
+ // If we've produced a register, mark it as a pointer, as needed.
+ if (tree->gtHasReg())
+ {
+ // We only mark the register in the following cases:
+ // 1. It is not a register candidate local. In this case, we're producing a
+ // register from a local, but the local is not a register candidate. Thus,
+ // we must be loading it as a temp register, and any "last use" flag on
+ // the register wouldn't be relevant.
+ // 2. The register candidate local is going dead. There's no point to mark
+ // the register as live, with a GC pointer, if the variable is dead.
+ if (!genIsRegCandidateLocal(tree) || ((tree->gtFlags & GTF_VAR_DEATH) == 0))
+ {
+ // Multi-reg call node will produce more than one register result.
+ // Mark all the regs produced by call node.
+ if (tree->IsMultiRegCall())
+ {
+ GenTreeCall* call = tree->AsCall();
+ ReturnTypeDesc* retTypeDesc = call->GetReturnTypeDesc();
+ unsigned regCount = retTypeDesc->GetReturnRegCount();
+
+ for (unsigned i = 0; i < regCount; ++i)
+ {
+ regNumber reg = call->GetRegNumByIdx(i);
+ var_types type = retTypeDesc->GetReturnRegType(i);
+ gcInfo.gcMarkRegPtrVal(reg, type);
+ }
+ }
+ else if (tree->IsCopyOrReloadOfMultiRegCall())
+ {
+ // we should never see reload of multi-reg call here
+ // because GT_RELOAD gets generated in reg consuming path.
+ noway_assert(tree->OperGet() == GT_COPY);
+
+ // A multi-reg GT_COPY node produces those regs to which
+ // copy has taken place.
+ GenTreeCopyOrReload* copy = tree->AsCopyOrReload();
+ GenTreeCall* call = copy->gtGetOp1()->AsCall();
+ ReturnTypeDesc* retTypeDesc = call->GetReturnTypeDesc();
+ unsigned regCount = retTypeDesc->GetReturnRegCount();
+
+ for (unsigned i = 0; i < regCount; ++i)
+ {
+ var_types type = retTypeDesc->GetReturnRegType(i);
+ regNumber fromReg = call->GetRegNumByIdx(i);
+ regNumber toReg = copy->GetRegNumByIdx(i);
+
+ if (toReg != REG_NA)
+ {
+ gcInfo.gcMarkRegPtrVal(toReg, type);
+ }
+ }
+ }
+ else
+ {
+ gcInfo.gcMarkRegPtrVal(tree->gtRegNum, tree->TypeGet());
+ }
+ }
+ }
+ tree->SetInReg();
+}
+
+// transfer gc/byref status of src reg to dst reg
+void CodeGen::genTransferRegGCState(regNumber dst, regNumber src)
+{
+ regMaskTP srcMask = genRegMask(src);
+ regMaskTP dstMask = genRegMask(dst);
+
+ if (gcInfo.gcRegGCrefSetCur & srcMask)
+ {
+ gcInfo.gcMarkRegSetGCref(dstMask);
+ }
+ else if (gcInfo.gcRegByrefSetCur & srcMask)
+ {
+ gcInfo.gcMarkRegSetByref(dstMask);
+ }
+ else
+ {
+ gcInfo.gcMarkRegSetNpt(dstMask);
+ }
+}
+
+// generates an ip-relative call or indirect call via reg ('call reg')
+// 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 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;
+#endif // !defined(_TARGET_X86_)
+ getEmitter()->emitIns_Call(emitter::EmitCallType(callType), methHnd, INDEBUG_LDISASM_COMMA(sigInfo) addr, argSize,
+ retSize MULTIREG_HAS_SECOND_GC_RET_ONLY_ARG(secondRetSize), gcInfo.gcVarPtrSetCur,
+ gcInfo.gcRegGCrefSetCur, gcInfo.gcRegByrefSetCur, ilOffset, base, REG_NA, 0, 0, isJump,
+ emitter::emitNoGChelper(compiler->eeGetHelperNum(methHnd)));
+}
+
+// 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 MULTIREG_HAS_SECOND_GC_RET_ONLY_ARG(emitAttr secondRetSize),
+ IL_OFFSETX ilOffset)
+{
+#if !defined(_TARGET_X86_)
+ ssize_t argSize = 0;
+#endif // !defined(_TARGET_X86_)
+ genConsumeAddress(indir->Addr());
+
+ getEmitter()->emitIns_Call(emitter::EmitCallType(callType), methHnd, INDEBUG_LDISASM_COMMA(sigInfo) nullptr,
+ argSize, retSize MULTIREG_HAS_SECOND_GC_RET_ONLY_ARG(secondRetSize),
+ gcInfo.gcVarPtrSetCur, gcInfo.gcRegGCrefSetCur, gcInfo.gcRegByrefSetCur, ilOffset,
+ indir->Base() ? indir->Base()->gtRegNum : REG_NA,
+ indir->Index() ? indir->Index()->gtRegNum : REG_NA, indir->Scale(), indir->Offset());
+}
+
+#endif // !LEGACY_BACKEND
diff --git a/src/jit/codegenlinear.h b/src/jit/codegenlinear.h
index fb0d6ea165..406ab779f1 100644
--- a/src/jit/codegenlinear.h
+++ b/src/jit/codegenlinear.h
@@ -16,6 +16,10 @@ void genCodeForTreeNode(GenTreePtr treeNode);
void genCodeForBinary(GenTreePtr treeNode);
+#if defined(_TARGET_X86_)
+void genCodeForLongUMod(GenTreeOp* node);
+#endif // _TARGET_X86_
+
void genCodeForDivMod(GenTreeOp* treeNode);
void genCodeForMulHi(GenTreeOp* treeNode);
@@ -24,6 +28,10 @@ void genLeaInstruction(GenTreeAddrMode* lea);
void genSetRegToCond(regNumber dstReg, GenTreePtr tree);
+#if !defined(_TARGET_64BIT_)
+void genLongToIntCast(GenTreePtr treeNode);
+#endif
+
void genIntToIntCast(GenTreePtr treeNode);
void genFloatToFloatCast(GenTreePtr treeNode);
@@ -36,7 +44,7 @@ void genCkfinite(GenTreePtr treeNode);
void genIntrinsic(GenTreePtr treeNode);
-void genPutArgStk(GenTreePtr treeNode);
+void genPutArgStk(GenTreePutArgStk* treeNode);
unsigned getBaseVarForPutArgStk(GenTreePtr treeNode);
#if defined(_TARGET_XARCH_) || defined(_TARGET_ARM64_)
@@ -49,7 +57,6 @@ void genCompareInt(GenTreePtr treeNode);
#if !defined(_TARGET_64BIT_)
void genCompareLong(GenTreePtr treeNode);
-void genJTrueLong(GenTreePtr treeNode);
#endif
#ifdef FEATURE_SIMD
@@ -61,7 +68,8 @@ enum SIMDScalarMoveType
};
instruction getOpForSIMDIntrinsic(SIMDIntrinsicID intrinsicId, var_types baseType, unsigned* ival = nullptr);
-void genSIMDScalarMove(var_types type, regNumber target, regNumber src, SIMDScalarMoveType moveType);
+void genSIMDScalarMove(
+ var_types targetType, var_types type, regNumber target, regNumber src, SIMDScalarMoveType moveType);
void genSIMDZero(var_types targetType, var_types baseType, regNumber targetReg);
void genSIMDIntrinsicInit(GenTreeSIMD* simdNode);
void genSIMDIntrinsicInitN(GenTreeSIMD* simdNode);
@@ -87,7 +95,10 @@ void genSIMDCheck(GenTree* treeNode);
void genStoreIndTypeSIMD12(GenTree* treeNode);
void genStoreLclFldTypeSIMD12(GenTree* treeNode);
void genLoadIndTypeSIMD12(GenTree* treeNode);
-void genLoadLclFldTypeSIMD12(GenTree* treeNode);
+void genLoadLclTypeSIMD12(GenTree* treeNode);
+#ifdef _TARGET_X86_
+void genPutArgStkSIMD12(GenTree* treeNode);
+#endif // _TARGET_X86_
#endif // FEATURE_SIMD
#if !defined(_TARGET_64BIT_)
@@ -104,6 +115,7 @@ void genUnspillRegIfNeeded(GenTree* tree);
regNumber genConsumeReg(GenTree* tree);
+void genCopyRegIfNeeded(GenTree* tree, regNumber needReg);
void genConsumeRegAndCopy(GenTree* tree, regNumber needReg);
void genConsumeIfReg(GenTreePtr tree)
@@ -122,15 +134,14 @@ void genConsumeAddress(GenTree* addr);
void genConsumeAddrMode(GenTreeAddrMode* mode);
-void genConsumeBlockSize(GenTreeBlk* blkNode, regNumber sizeReg);
-void genConsumeBlockDst(GenTreeBlk* blkNode);
-GenTree* genConsumeBlockSrc(GenTreeBlk* blkNode);
+void genSetBlockSize(GenTreeBlk* blkNode, regNumber sizeReg);
+void genConsumeBlockSrc(GenTreeBlk* blkNode);
+void genSetBlockSrc(GenTreeBlk* blkNode, regNumber srcReg);
void genConsumeBlockOp(GenTreeBlk* blkNode, regNumber dstReg, regNumber srcReg, regNumber sizeReg);
-#ifdef FEATURE_UNIX_AMD64_STRUCT_PASSING
-void genConsumePutStructArgStk(
- GenTreePutArgStk* putArgStkNode, regNumber dstReg, regNumber srcReg, regNumber sizeReg, unsigned baseVarNum);
-#endif // FEATURE_UNIX_AMD64_STRUCT_PASSING
+#ifdef FEATURE_PUT_STRUCT_ARG_STK
+void genConsumePutStructArgStk(GenTreePutArgStk* putArgStkNode, regNumber dstReg, regNumber srcReg, regNumber sizeReg);
+#endif // FEATURE_PUT_STRUCT_ARG_STK
void genConsumeRegs(GenTree* tree);
@@ -142,6 +153,10 @@ void genSetRegToIcon(regNumber reg, ssize_t val, var_types type = TYP_INT, insFl
void genCodeForShift(GenTreePtr tree);
+#if defined(_TARGET_X86_)
+void genCodeForShiftLong(GenTreePtr tree);
+#endif
+
#ifdef _TARGET_XARCH_
void genCodeForShiftRMW(GenTreeStoreInd* storeInd);
#endif // _TARGET_XARCH_
@@ -154,12 +169,23 @@ void genCodeForCpBlkRepMovs(GenTreeBlk* cpBlkNode);
void genCodeForCpBlkUnroll(GenTreeBlk* cpBlkNode);
-#ifdef FEATURE_UNIX_AMD64_STRUCT_PASSING
-void genPutStructArgStk(GenTreePtr treeNode, unsigned baseVarNum);
+#ifdef FEATURE_PUT_STRUCT_ARG_STK
+#ifdef _TARGET_X86_
+bool genAdjustStackForPutArgStk(GenTreePutArgStk* putArgStk);
+void genPushReg(var_types type, regNumber srcReg);
+void genPutArgStkFieldList(GenTreePutArgStk* putArgStk);
+#endif // _TARGET_X86_
+
+void genPutStructArgStk(GenTreePutArgStk* treeNode);
-void genStructPutArgRepMovs(GenTreePutArgStk* putArgStkNode, unsigned baseVarNum);
-void genStructPutArgUnroll(GenTreePutArgStk* putArgStkNode, unsigned baseVarNum);
-#endif // FEATURE_UNIX_AMD64_STRUCT_PASSING
+int genMove8IfNeeded(unsigned size, regNumber tmpReg, GenTree* srcAddr, unsigned offset);
+int genMove4IfNeeded(unsigned size, regNumber tmpReg, GenTree* srcAddr, unsigned offset);
+int genMove2IfNeeded(unsigned size, regNumber tmpReg, GenTree* srcAddr, unsigned offset);
+int genMove1IfNeeded(unsigned size, regNumber tmpReg, GenTree* srcAddr, unsigned offset);
+void genStructPutArgRepMovs(GenTreePutArgStk* putArgStkNode);
+void genStructPutArgUnroll(GenTreePutArgStk* putArgStkNode);
+void genStoreRegToStackArg(var_types type, regNumber reg, int offset);
+#endif // FEATURE_PUT_STRUCT_ARG_STK
void genCodeForLoadOffset(instruction ins, emitAttr size, regNumber dst, GenTree* base, unsigned offset);
@@ -191,6 +217,14 @@ void genCallInstruction(GenTreePtr call);
void genJmpMethod(GenTreePtr jmp);
+BasicBlock* genCallFinally(BasicBlock* block, BasicBlock* lblk);
+
+#if FEATURE_EH_FUNCLETS
+void genEHCatchRet(BasicBlock* block);
+#else // !FEATURE_EH_FUNCLETS
+void genEHFinallyOrFilterRet(BasicBlock* block);
+#endif // !FEATURE_EH_FUNCLETS
+
void genMultiRegCallStoreToLocal(GenTreePtr treeNode);
// Deals with codegen for muti-register struct returns.
@@ -212,9 +246,19 @@ bool genIsRegCandidateLocal(GenTreePtr tree)
return (varDsc->lvIsRegCandidate());
}
+#ifdef FEATURE_PUT_STRUCT_ARG_STK
+#ifdef _TARGET_X86_
+bool m_pushStkArg;
+#else // !_TARGET_X86_
+unsigned m_stkArgVarNum;
+unsigned m_stkArgOffset;
+#endif // !_TARGET_X86_
+#endif // !FEATURE_PUT_STRUCT_ARG_STK
+
#ifdef DEBUG
GenTree* lastConsumedNode;
-void genCheckConsumeNode(GenTree* treeNode);
+void genNumberOperandUse(GenTree* const operand, int& useNum) const;
+void genCheckConsumeNode(GenTree* const node);
#else // !DEBUG
inline void genCheckConsumeNode(GenTree* treeNode)
{
diff --git a/src/jit/codegenxarch.cpp b/src/jit/codegenxarch.cpp
index a41c28695b..8e0af48799 100644
--- a/src/jit/codegenxarch.cpp
+++ b/src/jit/codegenxarch.cpp
@@ -24,114 +24,6 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
#include "gcinfo.h"
#include "gcinfoencoder.h"
-// Get the register assigned to the given node
-
-regNumber CodeGenInterface::genGetAssignedReg(GenTreePtr tree)
-{
- return tree->gtRegNum;
-}
-
-//------------------------------------------------------------------------
-// genSpillVar: Spill a local variable
-//
-// Arguments:
-// tree - the lclVar node for the variable being spilled
-//
-// Return Value:
-// None.
-//
-// Assumptions:
-// The lclVar must be a register candidate (lvRegCandidate)
-
-void CodeGen::genSpillVar(GenTreePtr tree)
-{
- unsigned varNum = tree->gtLclVarCommon.gtLclNum;
- LclVarDsc* varDsc = &(compiler->lvaTable[varNum]);
-
- assert(varDsc->lvIsRegCandidate());
-
- // We don't actually need to spill if it is already living in memory
- bool needsSpill = ((tree->gtFlags & GTF_VAR_DEF) == 0 && varDsc->lvIsInReg());
- if (needsSpill)
- {
- var_types lclTyp = varDsc->TypeGet();
- if (varDsc->lvNormalizeOnStore())
- {
- lclTyp = genActualType(lclTyp);
- }
- emitAttr size = emitTypeSize(lclTyp);
-
- bool restoreRegVar = false;
- if (tree->gtOper == GT_REG_VAR)
- {
- tree->SetOper(GT_LCL_VAR);
- restoreRegVar = true;
- }
-
- // mask off the flag to generate the right spill code, then bring it back
- tree->gtFlags &= ~GTF_REG_VAL;
-
- instruction storeIns = ins_Store(tree->TypeGet(), compiler->isSIMDTypeLocalAligned(varNum));
-#if CPU_LONG_USES_REGPAIR
- if (varTypeIsMultiReg(tree))
- {
- assert(varDsc->lvRegNum == genRegPairLo(tree->gtRegPair));
- assert(varDsc->lvOtherReg == genRegPairHi(tree->gtRegPair));
- regNumber regLo = genRegPairLo(tree->gtRegPair);
- regNumber regHi = genRegPairHi(tree->gtRegPair);
- inst_TT_RV(storeIns, tree, regLo);
- inst_TT_RV(storeIns, tree, regHi, 4);
- }
- else
-#endif
- {
- assert(varDsc->lvRegNum == tree->gtRegNum);
- inst_TT_RV(storeIns, tree, tree->gtRegNum, 0, size);
- }
- tree->gtFlags |= GTF_REG_VAL;
-
- if (restoreRegVar)
- {
- tree->SetOper(GT_REG_VAR);
- }
-
- genUpdateRegLife(varDsc, /*isBorn*/ false, /*isDying*/ true DEBUGARG(tree));
- gcInfo.gcMarkRegSetNpt(varDsc->lvRegMask());
-
- if (VarSetOps::IsMember(compiler, gcInfo.gcTrkStkPtrLcls, varDsc->lvVarIndex))
- {
-#ifdef DEBUG
- if (!VarSetOps::IsMember(compiler, gcInfo.gcVarPtrSetCur, varDsc->lvVarIndex))
- {
- JITDUMP("\t\t\t\t\t\t\tVar V%02u becoming live\n", varNum);
- }
- else
- {
- JITDUMP("\t\t\t\t\t\t\tVar V%02u continuing live\n", varNum);
- }
-#endif
- VarSetOps::AddElemD(compiler, gcInfo.gcVarPtrSetCur, varDsc->lvVarIndex);
- }
- }
-
- tree->gtFlags &= ~GTF_SPILL;
- varDsc->lvRegNum = REG_STK;
- if (varTypeIsMultiReg(tree))
- {
- varDsc->lvOtherReg = REG_STK;
- }
-}
-
-// inline
-void CodeGenInterface::genUpdateVarReg(LclVarDsc* varDsc, GenTreePtr tree)
-{
- assert(tree->OperIsScalarLocal() || (tree->gtOper == GT_COPY));
- varDsc->lvRegNum = tree->gtRegNum;
-}
-
-/*****************************************************************************/
-/*****************************************************************************/
-
/*****************************************************************************
*
* Generate code that will set the given register to the integer constant.
@@ -231,6 +123,8 @@ void CodeGen::genEmitGSCookieCheck(bool pushReg)
}
regNumber regGSCheck;
+ regMaskTP regMaskGSCheck = RBM_NONE;
+
if (!pushReg)
{
// Non-tail call: we can use any callee trash register that is not
@@ -251,8 +145,11 @@ void CodeGen::genEmitGSCookieCheck(bool pushReg)
else
{
#ifdef _TARGET_X86_
- NYI_X86("Tail calls from methods that need GS check");
- regGSCheck = REG_NA;
+ // It doesn't matter which register we pick, since we're going to save and restore it
+ // around the check.
+ // TODO-CQ: Can we optimize the choice of register to avoid doing the push/pop sometimes?
+ regGSCheck = REG_EAX;
+ regMaskGSCheck = RBM_EAX;
#else // !_TARGET_X86_
// Tail calls from methods that need GS check: We need to preserve registers while
// emitting GS cookie check for a tail prefixed call or a jmp. To emit GS cookie
@@ -287,8 +184,13 @@ void CodeGen::genEmitGSCookieCheck(bool pushReg)
#endif // !_TARGET_X86_
}
+ regMaskTP byrefPushedRegs = RBM_NONE;
+ regMaskTP norefPushedRegs = RBM_NONE;
+ regMaskTP pushedRegs = RBM_NONE;
+
if (compiler->gsGlobalSecurityCookieAddr == nullptr)
{
+#if defined(_TARGET_AMD64_)
// If GS cookie value fits within 32-bits we can use 'cmp mem64, imm32'.
// Otherwise, load the value into a reg and use 'cmp mem64, reg64'.
if ((int)compiler->gsGlobalSecurityCookieVal != (ssize_t)compiler->gsGlobalSecurityCookieVal)
@@ -297,7 +199,9 @@ void CodeGen::genEmitGSCookieCheck(bool pushReg)
getEmitter()->emitIns_S_R(INS_cmp, EA_PTRSIZE, regGSCheck, compiler->lvaGSSecurityCookie, 0);
}
else
+#endif // defined(_TARGET_AMD64_)
{
+ assert((int)compiler->gsGlobalSecurityCookieVal == (ssize_t)compiler->gsGlobalSecurityCookieVal);
getEmitter()->emitIns_S_I(INS_cmp, EA_PTRSIZE, compiler->lvaGSSecurityCookie, 0,
(int)compiler->gsGlobalSecurityCookieVal);
}
@@ -305,6 +209,9 @@ void CodeGen::genEmitGSCookieCheck(bool pushReg)
else
{
// Ngen case - GS cookie value needs to be accessed through an indirection.
+
+ pushedRegs = genPushRegs(regMaskGSCheck, &byrefPushedRegs, &norefPushedRegs);
+
instGen_Set_Reg_To_Imm(EA_HANDLE_CNS_RELOC, regGSCheck, (ssize_t)compiler->gsGlobalSecurityCookieAddr);
getEmitter()->emitIns_R_AR(ins_Load(TYP_I_IMPL), EA_PTRSIZE, regGSCheck, regGSCheck, 0);
getEmitter()->emitIns_S_R(INS_cmp, EA_PTRSIZE, regGSCheck, compiler->lvaGSSecurityCookie, 0);
@@ -315,821 +222,180 @@ void CodeGen::genEmitGSCookieCheck(bool pushReg)
inst_JMP(jmpEqual, gsCheckBlk);
genEmitHelperCall(CORINFO_HELP_FAIL_FAST, 0, EA_UNKNOWN);
genDefineTempLabel(gsCheckBlk);
-}
-/*****************************************************************************
- *
- * Generate code for all the basic blocks in the function.
- */
+ genPopRegs(pushedRegs, byrefPushedRegs, norefPushedRegs);
+}
-void CodeGen::genCodeForBBlist()
+BasicBlock* CodeGen::genCallFinally(BasicBlock* block, BasicBlock* lblk)
{
- unsigned varNum;
- LclVarDsc* varDsc;
-
- unsigned savedStkLvl;
-
-#ifdef DEBUG
- genInterruptibleUsed = true;
-
- // You have to be careful if you create basic blocks from now on
- compiler->fgSafeBasicBlockCreation = false;
-
- // This stress mode is not comptible with fully interruptible GC
- if (genInterruptible && compiler->opts.compStackCheckOnCall)
- {
- compiler->opts.compStackCheckOnCall = false;
- }
-
- // This stress mode is not comptible with fully interruptible GC
- if (genInterruptible && compiler->opts.compStackCheckOnRet)
- {
- compiler->opts.compStackCheckOnRet = false;
- }
-#endif // DEBUG
-
- // Prepare the blocks for exception handling codegen: mark the blocks that needs labels.
- genPrepForEHCodegen();
-
- assert(!compiler->fgFirstBBScratch ||
- compiler->fgFirstBB == compiler->fgFirstBBScratch); // compiler->fgFirstBBScratch has to be first.
-
- /* Initialize the spill tracking logic */
-
- regSet.rsSpillBeg();
-
-#ifdef DEBUGGING_SUPPORT
- /* Initialize the line# tracking logic */
+#if FEATURE_EH_FUNCLETS
+ // Generate a call to the finally, like this:
+ // mov rcx,qword ptr [rbp + 20H] // Load rcx with PSPSym
+ // call finally-funclet
+ // jmp finally-return // Only for non-retless finally calls
+ // The jmp can be a NOP if we're going to the next block.
+ // If we're generating code for the main function (not a funclet), and there is no localloc,
+ // then RSP at this point is the same value as that stored in the PSPSym. So just copy RSP
+ // instead of loading the PSPSym in this case, or if PSPSym is not used (CoreRT ABI).
- if (compiler->opts.compScopeInfo)
+ if ((compiler->lvaPSPSym == BAD_VAR_NUM) ||
+ (!compiler->compLocallocUsed && (compiler->funCurrentFunc()->funKind == FUNC_ROOT)))
{
- siInit();
+ inst_RV_RV(INS_mov, REG_ARG_0, REG_SPBASE, TYP_I_IMPL);
}
-#endif
-
- // The current implementation of switch tables requires the first block to have a label so it
- // can generate offsets to the switch label targets.
- // TODO-XArch-CQ: remove this when switches have been re-implemented to not use this.
- if (compiler->fgHasSwitch)
+ else
{
- compiler->fgFirstBB->bbFlags |= BBF_JMP_TARGET;
+ getEmitter()->emitIns_R_S(ins_Load(TYP_I_IMPL), EA_PTRSIZE, REG_ARG_0, compiler->lvaPSPSym, 0);
}
+ getEmitter()->emitIns_J(INS_call, block->bbJumpDest);
- genPendingCallLabel = nullptr;
-
- /* Initialize the pointer tracking code */
-
- gcInfo.gcRegPtrSetInit();
- gcInfo.gcVarPtrSetInit();
-
- /* If any arguments live in registers, mark those regs as such */
-
- for (varNum = 0, varDsc = compiler->lvaTable; varNum < compiler->lvaCount; varNum++, varDsc++)
+ if (block->bbFlags & BBF_RETLESS_CALL)
{
- /* Is this variable a parameter assigned to a register? */
-
- if (!varDsc->lvIsParam || !varDsc->lvRegister)
- {
- continue;
- }
+ // We have a retless call, and the last instruction generated was a call.
+ // If the next block is in a different EH region (or is the end of the code
+ // block), then we need to generate a breakpoint here (since it will never
+ // get executed) to get proper unwind behavior.
- /* Is the argument live on entry to the method? */
-
- if (!VarSetOps::IsMember(compiler, compiler->fgFirstBB->bbLiveIn, varDsc->lvVarIndex))
+ if ((block->bbNext == nullptr) || !BasicBlock::sameEHRegion(block, block->bbNext))
{
- continue;
- }
-
- /* Is this a floating-point argument? */
-
- if (varDsc->IsFloatRegType())
- {
- continue;
+ instGen(INS_BREAKPOINT); // This should never get executed
}
-
- noway_assert(!varTypeIsFloating(varDsc->TypeGet()));
-
- /* Mark the register as holding the variable */
-
- regTracker.rsTrackRegLclVar(varDsc->lvRegNum, varNum);
}
-
- unsigned finallyNesting = 0;
-
- // Make sure a set is allocated for compiler->compCurLife (in the long case), so we can set it to empty without
- // allocation at the start of each basic block.
- VarSetOps::AssignNoCopy(compiler, compiler->compCurLife, VarSetOps::MakeEmpty(compiler));
-
- /*-------------------------------------------------------------------------
- *
- * Walk the basic blocks and generate code for each one
- *
- */
-
- BasicBlock* block;
- BasicBlock* lblk; /* previous block */
-
- for (lblk = nullptr, block = compiler->fgFirstBB; block != nullptr; lblk = block, block = block->bbNext)
+ else
{
-#ifdef DEBUG
- if (compiler->verbose)
- {
- printf("\n=============== Generating ");
- block->dspBlockHeader(compiler, true, true);
- compiler->fgDispBBLiveness(block);
- }
-#endif // DEBUG
-
- // Figure out which registers hold variables on entry to this block
-
- regSet.ClearMaskVars();
- gcInfo.gcRegGCrefSetCur = RBM_NONE;
- gcInfo.gcRegByrefSetCur = RBM_NONE;
-
- compiler->m_pLinearScan->recordVarLocationsAtStartOfBB(block);
-
- genUpdateLife(block->bbLiveIn);
-
- // Even if liveness didn't change, we need to update the registers containing GC references.
- // genUpdateLife will update the registers live due to liveness changes. But what about registers that didn't
- // change? We cleared them out above. Maybe we should just not clear them out, but update the ones that change
- // here. That would require handling the changes in recordVarLocationsAtStartOfBB().
-
- regMaskTP newLiveRegSet = RBM_NONE;
- regMaskTP newRegGCrefSet = RBM_NONE;
- regMaskTP newRegByrefSet = RBM_NONE;
-#ifdef DEBUG
- VARSET_TP VARSET_INIT_NOCOPY(removedGCVars, VarSetOps::MakeEmpty(compiler));
- VARSET_TP VARSET_INIT_NOCOPY(addedGCVars, VarSetOps::MakeEmpty(compiler));
-#endif
- VARSET_ITER_INIT(compiler, iter, block->bbLiveIn, varIndex);
- while (iter.NextElem(compiler, &varIndex))
- {
- unsigned varNum = compiler->lvaTrackedToVarNum[varIndex];
- LclVarDsc* varDsc = &(compiler->lvaTable[varNum]);
-
- if (varDsc->lvIsInReg())
- {
- newLiveRegSet |= varDsc->lvRegMask();
- if (varDsc->lvType == TYP_REF)
- {
- newRegGCrefSet |= varDsc->lvRegMask();
- }
- else if (varDsc->lvType == TYP_BYREF)
- {
- newRegByrefSet |= varDsc->lvRegMask();
- }
-#ifdef DEBUG
- if (verbose && VarSetOps::IsMember(compiler, gcInfo.gcVarPtrSetCur, varIndex))
- {
- VarSetOps::AddElemD(compiler, removedGCVars, varIndex);
- }
-#endif // DEBUG
- VarSetOps::RemoveElemD(compiler, gcInfo.gcVarPtrSetCur, varIndex);
- }
- else if (compiler->lvaIsGCTracked(varDsc))
- {
-#ifdef DEBUG
- if (verbose && !VarSetOps::IsMember(compiler, gcInfo.gcVarPtrSetCur, varIndex))
- {
- VarSetOps::AddElemD(compiler, addedGCVars, varIndex);
- }
-#endif // DEBUG
- VarSetOps::AddElemD(compiler, gcInfo.gcVarPtrSetCur, varIndex);
- }
- }
-
- regSet.rsMaskVars = newLiveRegSet;
-
-#ifdef DEBUG
- if (compiler->verbose)
- {
- if (!VarSetOps::IsEmpty(compiler, addedGCVars))
- {
- printf("\t\t\t\t\t\t\tAdded GCVars: ");
- dumpConvertedVarSet(compiler, addedGCVars);
- printf("\n");
- }
- if (!VarSetOps::IsEmpty(compiler, removedGCVars))
- {
- printf("\t\t\t\t\t\t\tRemoved GCVars: ");
- dumpConvertedVarSet(compiler, removedGCVars);
- printf("\n");
- }
- }
-#endif // DEBUG
-
- gcInfo.gcMarkRegSetGCref(newRegGCrefSet DEBUGARG(true));
- gcInfo.gcMarkRegSetByref(newRegByrefSet DEBUGARG(true));
-
- /* Blocks with handlerGetsXcptnObj()==true use GT_CATCH_ARG to
- represent the exception object (TYP_REF).
- We mark REG_EXCEPTION_OBJECT as holding a GC object on entry
- to the block, it will be the first thing evaluated
- (thanks to GTF_ORDER_SIDEEFF).
- */
-
- if (handlerGetsXcptnObj(block->bbCatchTyp))
- {
- for (GenTree* node : LIR::AsRange(block))
- {
- if (node->OperGet() == GT_CATCH_ARG)
- {
- gcInfo.gcMarkRegSetGCref(RBM_EXCEPTION_OBJECT);
- break;
- }
- }
- }
-
- /* Start a new code output block */
-
- genUpdateCurrentFunclet(block);
-
- if (genAlignLoops && block->bbFlags & BBF_LOOP_HEAD)
- {
- getEmitter()->emitLoopAlign();
- }
-
-#ifdef DEBUG
- if (compiler->opts.dspCode)
- {
- printf("\n L_M%03u_BB%02u:\n", Compiler::s_compMethodsCount, block->bbNum);
- }
-#endif
-
- block->bbEmitCookie = nullptr;
-
- if (block->bbFlags & (BBF_JMP_TARGET | BBF_HAS_LABEL))
- {
- /* Mark a label and update the current set of live GC refs */
-
- block->bbEmitCookie = getEmitter()->emitAddLabel(gcInfo.gcVarPtrSetCur, gcInfo.gcRegGCrefSetCur,
- gcInfo.gcRegByrefSetCur, FALSE);
- }
-
- if (block == compiler->fgFirstColdBlock)
- {
-#ifdef DEBUG
- if (compiler->verbose)
- {
- printf("\nThis is the start of the cold region of the method\n");
- }
-#endif
- // We should never have a block that falls through into the Cold section
- noway_assert(!lblk->bbFallsThrough());
-
- // We require the block that starts the Cold section to have a label
- noway_assert(block->bbEmitCookie);
- getEmitter()->emitSetFirstColdIGCookie(block->bbEmitCookie);
- }
-
- /* Both stacks are always empty on entry to a basic block */
-
- genStackLevel = 0;
-
- savedStkLvl = genStackLevel;
-
- /* Tell everyone which basic block we're working on */
-
- compiler->compCurBB = block;
-
-#ifdef DEBUGGING_SUPPORT
- siBeginBlock(block);
-
- // BBF_INTERNAL blocks don't correspond to any single IL instruction.
- if (compiler->opts.compDbgInfo && (block->bbFlags & BBF_INTERNAL) &&
- !compiler->fgBBisScratch(block)) // If the block is the distinguished first scratch block, then no need to
- // emit a NO_MAPPING entry, immediately after the prolog.
- {
- genIPmappingAdd((IL_OFFSETX)ICorDebugInfo::NO_MAPPING, true);
- }
-
- bool firstMapping = true;
-#endif // DEBUGGING_SUPPORT
-
- /*---------------------------------------------------------------------
- *
- * Generate code for each statement-tree in the block
- *
- */
- CLANG_FORMAT_COMMENT_ANCHOR;
-
-#if FEATURE_EH_FUNCLETS
- if (block->bbFlags & BBF_FUNCLET_BEG)
- {
- genReserveFuncletProlog(block);
- }
-#endif // FEATURE_EH_FUNCLETS
-
- // Clear compCurStmt and compCurLifeTree.
- compiler->compCurStmt = nullptr;
- compiler->compCurLifeTree = nullptr;
-
- // Traverse the block in linear order, generating code for each node as we
- // as we encounter it.
- CLANG_FORMAT_COMMENT_ANCHOR;
-
-#ifdef DEBUGGING_SUPPORT
- IL_OFFSETX currentILOffset = BAD_IL_OFFSET;
-#endif
- for (GenTree* node : LIR::AsRange(block).NonPhiNodes())
- {
-#ifdef DEBUGGING_SUPPORT
- // Do we have a new IL offset?
- if (node->OperGet() == GT_IL_OFFSET)
- {
- genEnsureCodeEmitted(currentILOffset);
- currentILOffset = node->gtStmt.gtStmtILoffsx;
- genIPmappingAdd(currentILOffset, firstMapping);
- firstMapping = false;
- }
-#endif // DEBUGGING_SUPPORT
-
-#ifdef DEBUG
- if (node->OperGet() == GT_IL_OFFSET)
- {
- noway_assert(node->gtStmt.gtStmtLastILoffs <= compiler->info.compILCodeSize ||
- node->gtStmt.gtStmtLastILoffs == BAD_IL_OFFSET);
-
- if (compiler->opts.dspCode && compiler->opts.dspInstrs &&
- node->gtStmt.gtStmtLastILoffs != BAD_IL_OFFSET)
- {
- while (genCurDispOffset <= node->gtStmt.gtStmtLastILoffs)
- {
- genCurDispOffset += dumpSingleInstr(compiler->info.compCode, genCurDispOffset, "> ");
- }
- }
- }
-#endif // DEBUG
-
- genCodeForTreeNode(node);
- if (node->gtHasReg() && node->gtLsraInfo.isLocalDefUse)
- {
- genConsumeReg(node);
- }
- } // end for each node in block
-
-#ifdef DEBUG
- // The following set of register spill checks and GC pointer tracking checks used to be
- // performed at statement boundaries. Now, with LIR, there are no statements, so they are
- // performed at the end of each block.
- // TODO: could these checks be performed more frequently? E.g., at each location where
- // the register allocator says there are no live non-variable registers. Perhaps this could
- // be done by (a) keeping a running count of live non-variable registers by using
- // gtLsraInfo.srcCount and gtLsraInfo.dstCount to decrement and increment the count, respectively,
- // and running the checks when the count is zero. Or, (b) use the map maintained by LSRA
- // (operandToLocationInfoMap) to mark a node somehow when, after the execution of that node,
- // there will be no live non-variable registers.
-
- regSet.rsSpillChk();
-
- /* Make sure we didn't bungle pointer register tracking */
-
- regMaskTP ptrRegs = gcInfo.gcRegGCrefSetCur | gcInfo.gcRegByrefSetCur;
- regMaskTP nonVarPtrRegs = ptrRegs & ~regSet.rsMaskVars;
-
- // If return is a GC-type, clear it. Note that if a common
- // epilog is generated (genReturnBB) it has a void return
- // even though we might return a ref. We can't use the compRetType
- // as the determiner because something we are tracking as a byref
- // might be used as a return value of a int function (which is legal)
- GenTree* blockLastNode = block->lastNode();
- if ((blockLastNode != nullptr) && (blockLastNode->gtOper == GT_RETURN) &&
- (varTypeIsGC(compiler->info.compRetType) ||
- (blockLastNode->gtOp.gtOp1 != nullptr && varTypeIsGC(blockLastNode->gtOp.gtOp1->TypeGet()))))
- {
- nonVarPtrRegs &= ~RBM_INTRET;
- }
-
- if (nonVarPtrRegs)
- {
- printf("Regset after BB%02u gcr=", block->bbNum);
- printRegMaskInt(gcInfo.gcRegGCrefSetCur & ~regSet.rsMaskVars);
- compiler->getEmitter()->emitDispRegSet(gcInfo.gcRegGCrefSetCur & ~regSet.rsMaskVars);
- printf(", byr=");
- printRegMaskInt(gcInfo.gcRegByrefSetCur & ~regSet.rsMaskVars);
- compiler->getEmitter()->emitDispRegSet(gcInfo.gcRegByrefSetCur & ~regSet.rsMaskVars);
- printf(", regVars=");
- printRegMaskInt(regSet.rsMaskVars);
- compiler->getEmitter()->emitDispRegSet(regSet.rsMaskVars);
- printf("\n");
- }
-
- noway_assert(nonVarPtrRegs == RBM_NONE);
-#endif // DEBUG
-
-#if defined(DEBUG) && defined(LATE_DISASM) && defined(_TARGET_AMD64_)
- if (block->bbNext == nullptr)
- {
- // Unit testing of the AMD64 emitter: generate a bunch of instructions into the last block
- // (it's as good as any, but better than the prolog, which can only be a single instruction
- // group) then use COMPlus_JitLateDisasm=* to see if the late disassembler
- // thinks the instructions are the same as we do.
- genAmd64EmitterUnitTests();
- }
-#endif // defined(DEBUG) && defined(LATE_DISASM) && defined(_TARGET_ARM64_)
-
-#ifdef DEBUGGING_SUPPORT
- // It is possible to reach the end of the block without generating code for the current IL offset.
- // For example, if the following IR ends the current block, no code will have been generated for
- // offset 21:
- //
- // ( 0, 0) [000040] ------------ il_offset void IL offset: 21
- //
- // N001 ( 0, 0) [000039] ------------ nop void
- //
- // This can lead to problems when debugging the generated code. To prevent these issues, make sure
- // we've generated code for the last IL offset we saw in the block.
- genEnsureCodeEmitted(currentILOffset);
+ // Because of the way the flowgraph is connected, the liveness info for this one instruction
+ // after the call is not (can not be) correct in cases where a variable has a last use in the
+ // handler. So turn off GC reporting for this single instruction.
+ getEmitter()->emitDisableGC();
- if (compiler->opts.compScopeInfo && (compiler->info.compVarScopesCount > 0))
+ // Now go to where the finally funclet needs to return to.
+ if (block->bbNext->bbJumpDest == block->bbNext->bbNext)
{
- siEndBlock(block);
-
- /* Is this the last block, and are there any open scopes left ? */
-
- bool isLastBlockProcessed = (block->bbNext == nullptr);
- if (block->isBBCallAlwaysPair())
- {
- isLastBlockProcessed = (block->bbNext->bbNext == nullptr);
- }
-
- if (isLastBlockProcessed && siOpenScopeList.scNext)
- {
- /* This assert no longer holds, because we may insert a throw
- block to demarcate the end of a try or finally region when they
- are at the end of the method. It would be nice if we could fix
- our code so that this throw block will no longer be necessary. */
-
- // noway_assert(block->bbCodeOffsEnd != compiler->info.compILCodeSize);
-
- siCloseAllOpenScopes();
- }
+ // Fall-through.
+ // TODO-XArch-CQ: Can we get rid of this instruction, and just have the call return directly
+ // to the next instruction? This would depend on stack walking from within the finally
+ // handler working without this instruction being in this special EH region.
+ instGen(INS_nop);
}
-
-#endif // DEBUGGING_SUPPORT
-
- genStackLevel -= savedStkLvl;
-
-#ifdef DEBUG
- // compCurLife should be equal to the liveOut set, except that we don't keep
- // it up to date for vars that are not register candidates
- // (it would be nice to have a xor set function)
-
- VARSET_TP VARSET_INIT_NOCOPY(extraLiveVars, VarSetOps::Diff(compiler, block->bbLiveOut, compiler->compCurLife));
- VarSetOps::UnionD(compiler, extraLiveVars, VarSetOps::Diff(compiler, compiler->compCurLife, block->bbLiveOut));
- VARSET_ITER_INIT(compiler, extraLiveVarIter, extraLiveVars, extraLiveVarIndex);
- while (extraLiveVarIter.NextElem(compiler, &extraLiveVarIndex))
+ else
{
- unsigned varNum = compiler->lvaTrackedToVarNum[extraLiveVarIndex];
- LclVarDsc* varDsc = compiler->lvaTable + varNum;
- assert(!varDsc->lvIsRegCandidate());
+ inst_JMP(EJ_jmp, block->bbNext->bbJumpDest);
}
-#endif
-
- /* Both stacks should always be empty on exit from a basic block */
- noway_assert(genStackLevel == 0);
-
-#ifdef _TARGET_AMD64_
- // On AMD64, we need to generate a NOP after a call that is the last instruction of the block, in several
- // situations, to support proper exception handling semantics. This is mostly to ensure that when the stack
- // walker computes an instruction pointer for a frame, that instruction pointer is in the correct EH region.
- // The document "X64 and ARM ABIs.docx" has more details. The situations:
- // 1. If the call instruction is in a different EH region as the instruction that follows it.
- // 2. If the call immediately precedes an OS epilog. (Note that what the JIT or VM consider an epilog might
- // be slightly different from what the OS considers an epilog, and it is the OS-reported epilog that matters
- // here.)
- // We handle case #1 here, and case #2 in the emitter.
- if (getEmitter()->emitIsLastInsCall())
- {
- // Ok, the last instruction generated is a call instruction. Do any of the other conditions hold?
- // Note: we may be generating a few too many NOPs for the case of call preceding an epilog. Technically,
- // if the next block is a BBJ_RETURN, an epilog will be generated, but there may be some instructions
- // generated before the OS epilog starts, such as a GS cookie check.
- if ((block->bbNext == nullptr) || !BasicBlock::sameEHRegion(block, block->bbNext))
- {
- // We only need the NOP if we're not going to generate any more code as part of the block end.
-
- switch (block->bbJumpKind)
- {
- case BBJ_ALWAYS:
- case BBJ_THROW:
- case BBJ_CALLFINALLY:
- case BBJ_EHCATCHRET:
- // We're going to generate more code below anyway, so no need for the NOP.
-
- case BBJ_RETURN:
- case BBJ_EHFINALLYRET:
- case BBJ_EHFILTERRET:
- // These are the "epilog follows" case, handled in the emitter.
-
- break;
-
- case BBJ_NONE:
- if (block->bbNext == nullptr)
- {
- // Call immediately before the end of the code; we should never get here .
- instGen(INS_BREAKPOINT); // This should never get executed
- }
- else
- {
- // We need the NOP
- instGen(INS_nop);
- }
- break;
-
- case BBJ_COND:
- case BBJ_SWITCH:
- // These can't have a call as the last instruction!
-
- default:
- noway_assert(!"Unexpected bbJumpKind");
- break;
- }
- }
- }
-#endif // _TARGET_AMD64_
-
- /* Do we need to generate a jump or return? */
-
- switch (block->bbJumpKind)
- {
- case BBJ_ALWAYS:
- inst_JMP(EJ_jmp, block->bbJumpDest);
- break;
-
- case BBJ_RETURN:
- genExitCode(block);
- break;
-
- case BBJ_THROW:
- // If we have a throw at the end of a function or funclet, we need to emit another instruction
- // afterwards to help the OS unwinder determine the correct context during unwind.
- // We insert an unexecuted breakpoint instruction in several situations
- // following a throw instruction:
- // 1. If the throw is the last instruction of the function or funclet. This helps
- // the OS unwinder determine the correct context during an unwind from the
- // thrown exception.
- // 2. If this is this is the last block of the hot section.
- // 3. If the subsequent block is a special throw block.
- // 4. On AMD64, if the next block is in a different EH region.
- if ((block->bbNext == nullptr) || (block->bbNext->bbFlags & BBF_FUNCLET_BEG) ||
- !BasicBlock::sameEHRegion(block, block->bbNext) ||
- (!isFramePointerUsed() && compiler->fgIsThrowHlpBlk(block->bbNext)) ||
- block->bbNext == compiler->fgFirstColdBlock)
- {
- instGen(INS_BREAKPOINT); // This should never get executed
- }
-
- break;
-
- case BBJ_CALLFINALLY:
-
-#if FEATURE_EH_FUNCLETS
-
- // Generate a call to the finally, like this:
- // mov rcx,qword ptr [rbp + 20H] // Load rcx with PSPSym
- // call finally-funclet
- // jmp finally-return // Only for non-retless finally calls
- // The jmp can be a NOP if we're going to the next block.
- // If we're generating code for the main function (not a funclet), and there is no localloc,
- // then RSP at this point is the same value as that stored in the PSPsym. So just copy RSP
- // instead of loading the PSPSym in this case.
- if (!compiler->compLocallocUsed && (compiler->funCurrentFunc()->funKind == FUNC_ROOT))
- {
- inst_RV_RV(INS_mov, REG_ARG_0, REG_SPBASE, TYP_I_IMPL);
- }
- else
- {
- getEmitter()->emitIns_R_S(ins_Load(TYP_I_IMPL), EA_PTRSIZE, REG_ARG_0, compiler->lvaPSPSym, 0);
- }
- getEmitter()->emitIns_J(INS_call, block->bbJumpDest);
+ getEmitter()->emitEnableGC();
+ }
- if (block->bbFlags & BBF_RETLESS_CALL)
- {
- // We have a retless call, and the last instruction generated was a call.
- // If the next block is in a different EH region (or is the end of the code
- // block), then we need to generate a breakpoint here (since it will never
- // get executed) to get proper unwind behavior.
+#else // !FEATURE_EH_FUNCLETS
- if ((block->bbNext == nullptr) || !BasicBlock::sameEHRegion(block, block->bbNext))
- {
- instGen(INS_BREAKPOINT); // This should never get executed
- }
- }
- else
- {
- // Because of the way the flowgraph is connected, the liveness info for this one instruction
- // after the call is not (can not be) correct in cases where a variable has a last use in the
- // handler. So turn off GC reporting for this single instruction.
- getEmitter()->emitDisableGC();
+ // If we are about to invoke a finally locally from a try block, we have to set the ShadowSP slot
+ // corresponding to the finally's nesting level. When invoked in response to an exception, the
+ // EE does this.
+ //
+ // We have a BBJ_CALLFINALLY followed by a BBJ_ALWAYS.
+ //
+ // We will emit :
+ // mov [ebp - (n + 1)], 0
+ // mov [ebp - n ], 0xFC
+ // push &step
+ // jmp finallyBlock
+ // ...
+ // step:
+ // mov [ebp - n ], 0
+ // jmp leaveTarget
+ // ...
+ // leaveTarget:
+
+ noway_assert(isFramePointerUsed());
+
+ // Get the nesting level which contains the finally
+ unsigned finallyNesting = 0;
+ compiler->fgGetNestingLevel(block, &finallyNesting);
- // Now go to where the finally funclet needs to return to.
- if (block->bbNext->bbJumpDest == block->bbNext->bbNext)
- {
- // Fall-through.
- // TODO-XArch-CQ: Can we get rid of this instruction, and just have the call return directly
- // to the next instruction? This would depend on stack walking from within the finally
- // handler working without this instruction being in this special EH region.
- instGen(INS_nop);
- }
- else
- {
- inst_JMP(EJ_jmp, block->bbNext->bbJumpDest);
- }
+ // The last slot is reserved for ICodeManager::FixContext(ppEndRegion)
+ unsigned filterEndOffsetSlotOffs;
+ filterEndOffsetSlotOffs = (unsigned)(compiler->lvaLclSize(compiler->lvaShadowSPslotsVar) - TARGET_POINTER_SIZE);
- getEmitter()->emitEnableGC();
- }
+ unsigned curNestingSlotOffs;
+ curNestingSlotOffs = (unsigned)(filterEndOffsetSlotOffs - ((finallyNesting + 1) * TARGET_POINTER_SIZE));
-#else // !FEATURE_EH_FUNCLETS
+ // Zero out the slot for the next nesting level
+ instGen_Store_Imm_Into_Lcl(TYP_I_IMPL, EA_PTRSIZE, 0, compiler->lvaShadowSPslotsVar,
+ curNestingSlotOffs - TARGET_POINTER_SIZE);
+ instGen_Store_Imm_Into_Lcl(TYP_I_IMPL, EA_PTRSIZE, LCL_FINALLY_MARK, compiler->lvaShadowSPslotsVar,
+ curNestingSlotOffs);
- // If we are about to invoke a finally locally from a try block, we have to set the ShadowSP slot
- // corresponding to the finally's nesting level. When invoked in response to an exception, the
- // EE does this.
- //
- // We have a BBJ_CALLFINALLY followed by a BBJ_ALWAYS.
- //
- // We will emit :
- // mov [ebp - (n + 1)], 0
- // mov [ebp - n ], 0xFC
- // push &step
- // jmp finallyBlock
- // ...
- // step:
- // mov [ebp - n ], 0
- // jmp leaveTarget
- // ...
- // leaveTarget:
-
- noway_assert(isFramePointerUsed());
-
- // Get the nesting level which contains the finally
- compiler->fgGetNestingLevel(block, &finallyNesting);
-
- // The last slot is reserved for ICodeManager::FixContext(ppEndRegion)
- unsigned filterEndOffsetSlotOffs;
- filterEndOffsetSlotOffs =
- (unsigned)(compiler->lvaLclSize(compiler->lvaShadowSPslotsVar) - TARGET_POINTER_SIZE);
-
- unsigned curNestingSlotOffs;
- curNestingSlotOffs = (unsigned)(filterEndOffsetSlotOffs - ((finallyNesting + 1) * TARGET_POINTER_SIZE));
-
- // Zero out the slot for the next nesting level
- instGen_Store_Imm_Into_Lcl(TYP_I_IMPL, EA_PTRSIZE, 0, compiler->lvaShadowSPslotsVar,
- curNestingSlotOffs - TARGET_POINTER_SIZE);
- instGen_Store_Imm_Into_Lcl(TYP_I_IMPL, EA_PTRSIZE, LCL_FINALLY_MARK, compiler->lvaShadowSPslotsVar,
- curNestingSlotOffs);
-
- // Now push the address where the finally funclet should return to directly.
- if (!(block->bbFlags & BBF_RETLESS_CALL))
- {
- assert(block->isBBCallAlwaysPair());
- getEmitter()->emitIns_J(INS_push_hide, block->bbNext->bbJumpDest);
- }
- else
- {
- // EE expects a DWORD, so we give him 0
- inst_IV(INS_push_hide, 0);
- }
+ // Now push the address where the finally funclet should return to directly.
+ if (!(block->bbFlags & BBF_RETLESS_CALL))
+ {
+ assert(block->isBBCallAlwaysPair());
+ getEmitter()->emitIns_J(INS_push_hide, block->bbNext->bbJumpDest);
+ }
+ else
+ {
+ // EE expects a DWORD, so we give him 0
+ inst_IV(INS_push_hide, 0);
+ }
- // Jump to the finally BB
- inst_JMP(EJ_jmp, block->bbJumpDest);
+ // Jump to the finally BB
+ inst_JMP(EJ_jmp, block->bbJumpDest);
#endif // !FEATURE_EH_FUNCLETS
- // The BBJ_ALWAYS is used because the BBJ_CALLFINALLY can't point to the
- // jump target using bbJumpDest - that is already used to point
- // to the finally block. So just skip past the BBJ_ALWAYS unless the
- // block is RETLESS.
- if (!(block->bbFlags & BBF_RETLESS_CALL))
- {
- assert(block->isBBCallAlwaysPair());
-
- lblk = block;
- block = block->bbNext;
- }
+ // The BBJ_ALWAYS is used because the BBJ_CALLFINALLY can't point to the
+ // jump target using bbJumpDest - that is already used to point
+ // to the finally block. So just skip past the BBJ_ALWAYS unless the
+ // block is RETLESS.
+ if (!(block->bbFlags & BBF_RETLESS_CALL))
+ {
+ assert(block->isBBCallAlwaysPair());
- break;
+ lblk = block;
+ block = block->bbNext;
+ }
+ return block;
+}
#if FEATURE_EH_FUNCLETS
-
- case BBJ_EHCATCHRET:
- // Set RAX to the address the VM should return to after the catch.
- // Generate a RIP-relative
- // lea reg, [rip + disp32] ; the RIP is implicit
- // which will be position-indepenent.
- getEmitter()->emitIns_R_L(INS_lea, EA_PTR_DSP_RELOC, block->bbJumpDest, REG_INTRET);
- __fallthrough;
-
- case BBJ_EHFINALLYRET:
- case BBJ_EHFILTERRET:
- genReserveFuncletEpilog(block);
- break;
+void CodeGen::genEHCatchRet(BasicBlock* block)
+{
+ // Set RAX to the address the VM should return to after the catch.
+ // Generate a RIP-relative
+ // lea reg, [rip + disp32] ; the RIP is implicit
+ // which will be position-indepenent.
+ getEmitter()->emitIns_R_L(INS_lea, EA_PTR_DSP_RELOC, block->bbJumpDest, REG_INTRET);
+}
#else // !FEATURE_EH_FUNCLETS
- case BBJ_EHCATCHRET:
- noway_assert(!"Unexpected BBJ_EHCATCHRET"); // not used on x86
-
- case BBJ_EHFINALLYRET:
- case BBJ_EHFILTERRET:
- {
- // The last statement of the block must be a GT_RETFILT, which has already been generated.
- assert(block->lastNode() != nullptr);
- assert(block->lastNode()->OperGet() == GT_RETFILT);
-
- if (block->bbJumpKind == BBJ_EHFINALLYRET)
- {
- assert(block->lastNode()->gtOp.gtOp1 == nullptr); // op1 == nullptr means endfinally
-
- // Return using a pop-jmp sequence. As the "try" block calls
- // the finally with a jmp, this leaves the x86 call-ret stack
- // balanced in the normal flow of path.
-
- noway_assert(isFramePointerRequired());
- inst_RV(INS_pop_hide, REG_EAX, TYP_I_IMPL);
- inst_RV(INS_i_jmp, REG_EAX, TYP_I_IMPL);
- }
- else
- {
- assert(block->bbJumpKind == BBJ_EHFILTERRET);
-
- // The return value has already been computed.
- instGen_Return(0);
- }
- }
- break;
-
-#endif // !FEATURE_EH_FUNCLETS
-
- case BBJ_NONE:
- case BBJ_COND:
- case BBJ_SWITCH:
- break;
-
- default:
- noway_assert(!"Unexpected bbJumpKind");
- break;
- }
-
-#ifdef DEBUG
- compiler->compCurBB = nullptr;
-#endif
-
- } //------------------ END-FOR each block of the method -------------------
-
- /* Nothing is live at this point */
- genUpdateLife(VarSetOps::MakeEmpty(compiler));
-
- /* Finalize the spill tracking logic */
-
- regSet.rsSpillEnd();
-
- /* Finalize the temp tracking logic */
-
- compiler->tmpEnd();
+void CodeGen::genEHFinallyOrFilterRet(BasicBlock* block)
+{
+ // The last statement of the block must be a GT_RETFILT, which has already been generated.
+ assert(block->lastNode() != nullptr);
+ assert(block->lastNode()->OperGet() == GT_RETFILT);
-#ifdef DEBUG
- if (compiler->verbose)
+ if (block->bbJumpKind == BBJ_EHFINALLYRET)
{
- printf("\n# ");
- printf("compCycleEstimate = %6d, compSizeEstimate = %5d ", compiler->compCycleEstimate,
- compiler->compSizeEstimate);
- printf("%s\n", compiler->info.compFullName);
- }
-#endif
-}
+ assert(block->lastNode()->gtOp.gtOp1 == nullptr); // op1 == nullptr means endfinally
-// return the child that has the same reg as the dst (if any)
-// other child returned (out param) in 'other'
-GenTree* sameRegAsDst(GenTree* tree, GenTree*& other /*out*/)
-{
- if (tree->gtRegNum == REG_NA)
- {
- other = nullptr;
- return nullptr;
- }
+ // Return using a pop-jmp sequence. As the "try" block calls
+ // the finally with a jmp, this leaves the x86 call-ret stack
+ // balanced in the normal flow of path.
- GenTreePtr op1 = tree->gtOp.gtOp1;
- GenTreePtr op2 = tree->gtOp.gtOp2;
- if (op1->gtRegNum == tree->gtRegNum)
- {
- other = op2;
- return op1;
- }
- if (op2->gtRegNum == tree->gtRegNum)
- {
- other = op1;
- return op2;
+ noway_assert(isFramePointerRequired());
+ inst_RV(INS_pop_hide, REG_EAX, TYP_I_IMPL);
+ inst_RV(INS_i_jmp, REG_EAX, TYP_I_IMPL);
}
else
{
- other = nullptr;
- return nullptr;
+ assert(block->bbJumpKind == BBJ_EHFILTERRET);
+
+ // The return value has already been computed.
+ instGen_Return(0);
}
}
+#endif // !FEATURE_EH_FUNCLETS
+
// Move an immediate value into an integer register
void CodeGen::instGen_Set_Reg_To_Imm(emitAttr size, regNumber reg, ssize_t imm, insFlags flags)
@@ -1227,7 +493,10 @@ void CodeGen::genSetRegToConst(regNumber targetReg, var_types targetType, GenTre
// Generate code to get the high N bits of a N*N=2N bit multiplication result
void CodeGen::genCodeForMulHi(GenTreeOp* treeNode)
{
- assert(!(treeNode->gtFlags & GTF_UNSIGNED));
+ if (treeNode->OperGet() == GT_MULHI)
+ {
+ assert(!(treeNode->gtFlags & GTF_UNSIGNED));
+ }
assert(!treeNode->gtOverflowEx());
regNumber targetReg = treeNode->gtRegNum;
@@ -1247,8 +516,7 @@ void CodeGen::genCodeForMulHi(GenTreeOp* treeNode)
GenTree* rmOp = op2;
// Set rmOp to the contained memory operand (if any)
- //
- if (op1->isContained() || (!op2->isContained() && (op2->gtRegNum == targetReg)))
+ if (op1->isContained() || (!op2->isContained() && (op2->gtRegNum == REG_RAX)))
{
regOp = op2;
rmOp = op1;
@@ -1256,25 +524,131 @@ void CodeGen::genCodeForMulHi(GenTreeOp* treeNode)
assert(!regOp->isContained());
// Setup targetReg when neither of the source operands was a matching register
- if (regOp->gtRegNum != targetReg)
+ if (regOp->gtRegNum != REG_RAX)
{
- inst_RV_RV(ins_Copy(targetType), targetReg, regOp->gtRegNum, targetType);
+ inst_RV_RV(ins_Copy(targetType), REG_RAX, regOp->gtRegNum, targetType);
}
- emit->emitInsBinary(INS_imulEAX, size, treeNode, rmOp);
+ instruction ins;
+ if ((treeNode->gtFlags & GTF_UNSIGNED) == 0)
+ {
+ ins = INS_imulEAX;
+ }
+ else
+ {
+ ins = INS_mulEAX;
+ }
+ emit->emitInsBinary(ins, size, treeNode, rmOp);
// Move the result to the desired register, if necessary
- if (targetReg != REG_RDX)
+ if (treeNode->OperGet() == GT_MULHI && targetReg != REG_RDX)
{
inst_RV_RV(INS_mov, targetReg, REG_RDX, targetType);
}
}
-// generate code for a DIV or MOD operation
+#ifdef _TARGET_X86_
+//------------------------------------------------------------------------
+// genCodeForLongUMod: Generate code for a tree of the form
+// `(umod (gt_long x y) (const int))`
+//
+// Arguments:
+// node - the node for which to generate code
+//
+void CodeGen::genCodeForLongUMod(GenTreeOp* node)
+{
+ assert(node != nullptr);
+ assert(node->OperGet() == GT_UMOD);
+ assert(node->TypeGet() == TYP_INT);
+
+ GenTreeOp* const dividend = node->gtOp1->AsOp();
+ assert(dividend->OperGet() == GT_LONG);
+ assert(varTypeIsLong(dividend));
+
+ genConsumeOperands(node);
+
+ GenTree* const dividendLo = dividend->gtOp1;
+ GenTree* const dividendHi = dividend->gtOp2;
+ assert(!dividendLo->isContained());
+ assert(!dividendHi->isContained());
+
+ GenTree* const divisor = node->gtOp2;
+ assert(divisor->gtSkipReloadOrCopy()->OperGet() == GT_CNS_INT);
+ assert(!divisor->gtSkipReloadOrCopy()->isContained());
+ assert(divisor->gtSkipReloadOrCopy()->AsIntCon()->gtIconVal >= 2);
+ assert(divisor->gtSkipReloadOrCopy()->AsIntCon()->gtIconVal <= 0x3fffffff);
+
+ // dividendLo must be in RAX; dividendHi must be in RDX
+ genCopyRegIfNeeded(dividendLo, REG_EAX);
+ genCopyRegIfNeeded(dividendHi, REG_EDX);
+
+ // At this point, EAX:EDX contains the 64bit dividend and op2->gtRegNum
+ // contains the 32bit divisor. We want to generate the following code:
+ //
+ // cmp edx, divisor->gtRegNum
+ // jb noOverflow
+ //
+ // mov temp, eax
+ // mov eax, edx
+ // xor edx, edx
+ // div divisor->gtRegNum
+ // mov eax, temp
+ //
+ // noOverflow:
+ // div divisor->gtRegNum
+ //
+ // This works because (a * 2^32 + b) % c = ((a % c) * 2^32 + b) % c.
+
+ BasicBlock* const noOverflow = genCreateTempLabel();
+
+ // cmp edx, divisor->gtRegNum
+ // jb noOverflow
+ inst_RV_RV(INS_cmp, REG_EDX, divisor->gtRegNum);
+ inst_JMP(EJ_jb, noOverflow);
+
+ // mov temp, eax
+ // mov eax, edx
+ // xor edx, edx
+ // div divisor->gtRegNum
+ // mov eax, temp
+ const regNumber tempReg = genRegNumFromMask(node->gtRsvdRegs);
+ inst_RV_RV(INS_mov, tempReg, REG_EAX, TYP_INT);
+ inst_RV_RV(INS_mov, REG_EAX, REG_EDX, TYP_INT);
+ instGen_Set_Reg_To_Zero(EA_PTRSIZE, REG_EDX);
+ inst_RV(INS_div, divisor->gtRegNum, TYP_INT);
+ inst_RV_RV(INS_mov, REG_EAX, tempReg, TYP_INT);
+
+ // noOverflow:
+ // div divisor->gtRegNum
+ genDefineTempLabel(noOverflow);
+ inst_RV(INS_div, divisor->gtRegNum, TYP_INT);
+
+ const regNumber targetReg = node->gtRegNum;
+ if (targetReg != REG_EDX)
+ {
+ inst_RV_RV(INS_mov, targetReg, REG_RDX, TYP_INT);
+ }
+ genProduceReg(node);
+}
+#endif // _TARGET_X86_
+
+//------------------------------------------------------------------------
+// genCodeForDivMod: Generate code for a DIV or MOD operation.
+//
+// Arguments:
+// treeNode - the node to generate the code for
//
void CodeGen::genCodeForDivMod(GenTreeOp* treeNode)
{
- GenTree* dividend = treeNode->gtOp1;
+ GenTree* dividend = treeNode->gtOp1;
+#ifdef _TARGET_X86_
+ if (varTypeIsLong(dividend->TypeGet()))
+ {
+ genCodeForLongUMod(treeNode);
+ return;
+ }
+#endif // _TARGET_X86_
+
GenTree* divisor = treeNode->gtOp2;
genTreeOps oper = treeNode->OperGet();
emitAttr size = emitTypeSize(treeNode);
@@ -1319,10 +693,7 @@ void CodeGen::genCodeForDivMod(GenTreeOp* treeNode)
else
{
// dividend must be in RAX
- if (dividend->gtRegNum != REG_RAX)
- {
- inst_RV_RV(INS_mov, REG_RAX, dividend->gtRegNum, targetType);
- }
+ genCopyRegIfNeeded(dividend, REG_RAX);
// zero or sign extend rax to rdx
if (oper == GT_UMOD || oper == GT_UDIV)
@@ -1395,7 +766,7 @@ void CodeGen::genCodeForBinary(GenTree* treeNode)
assert(oper == GT_OR || oper == GT_XOR || oper == GT_AND || oper == GT_ADD || oper == GT_SUB);
#else // !defined(_TARGET_64BIT_)
assert(oper == GT_OR || oper == GT_XOR || oper == GT_AND || oper == GT_ADD_LO || oper == GT_ADD_HI ||
- oper == GT_SUB_LO || oper == GT_SUB_HI || oper == GT_MUL_HI || oper == GT_DIV_HI || oper == GT_MOD_HI ||
+ oper == GT_SUB_LO || oper == GT_SUB_HI || oper == GT_MUL_LONG || oper == GT_DIV_HI || oper == GT_MOD_HI ||
oper == GT_ADD || oper == GT_SUB);
#endif // !defined(_TARGET_64BIT_)
@@ -1443,7 +814,7 @@ void CodeGen::genCodeForBinary(GenTree* treeNode)
}
// now we know there are 3 different operands so attempt to use LEA
else if (oper == GT_ADD && !varTypeIsFloating(treeNode) && !treeNode->gtOverflowEx() // LEA does not set flags
- && (op2->isContainedIntOrIImmed() || !op2->isContained()))
+ && (op2->isContainedIntOrIImmed() || !op2->isContained()) && !treeNode->gtSetFlags())
{
if (op2->isContainedIntOrIImmed())
{
@@ -1833,7 +1204,7 @@ void CodeGen::genReturn(GenTreePtr treeNode)
//
// 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
+ // BBJ_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)
@@ -1913,9 +1284,11 @@ void CodeGen::genCodeForTreeNode(GenTreePtr treeNode)
switch (treeNode->gtOper)
{
+#ifndef JIT32_GCENCODER
case GT_START_NONGC:
getEmitter()->emitDisableGC();
break;
+#endif // !defined(JIT32_GCENCODER)
case GT_PROF_HOOK:
#ifdef PROFILING_SUPPORTED
@@ -1996,14 +1369,18 @@ void CodeGen::genCodeForTreeNode(GenTreePtr treeNode)
// genCodeForShift() calls genProduceReg()
break;
- case GT_CAST:
#if !defined(_TARGET_64BIT_)
- // We will NYI in DecomposeNode() if we are cast TO a long type, but we do not
- // yet support casting FROM a long type either, and that's simpler to catch
- // here.
- NYI_IF(varTypeIsLong(treeNode->gtOp.gtOp1), "Casts from TYP_LONG");
-#endif // !defined(_TARGET_64BIT_)
+ case GT_LSH_HI:
+ case GT_RSH_LO:
+ // TODO-X86-CQ: This only handles the case where the operand being shifted is in a register. We don't
+ // need sourceHi to be always in reg in case of GT_LSH_HI (because it could be moved from memory to
+ // targetReg if sourceHi is a contained mem-op). Similarly for GT_RSH_LO, sourceLo could be marked as
+ // contained memory-op. Even if not a memory-op, we could mark it as reg-optional.
+ genCodeForShiftLong(treeNode);
+ break;
+#endif
+ case GT_CAST:
if (varTypeIsFloating(targetType) && varTypeIsFloating(treeNode->gtOp.gtOp1))
{
// Casts float/double <--> double/float
@@ -2037,7 +1414,7 @@ void CodeGen::genCodeForTreeNode(GenTreePtr treeNode)
if (isRegCandidate && !(treeNode->gtFlags & GTF_VAR_DEATH))
{
- assert((treeNode->InReg()) || (treeNode->gtFlags & GTF_SPILLED));
+ assert(treeNode->InReg() || (treeNode->gtFlags & GTF_SPILLED));
}
// If this is a register candidate that has been spilled, genConsumeReg() will
@@ -2047,6 +1424,15 @@ void CodeGen::genCodeForTreeNode(GenTreePtr treeNode)
{
assert(!isRegCandidate);
+#if defined(FEATURE_SIMD) && defined(_TARGET_X86_)
+ // Loading of TYP_SIMD12 (i.e. Vector3) variable
+ if (treeNode->TypeGet() == TYP_SIMD12)
+ {
+ genLoadLclTypeSIMD12(treeNode);
+ break;
+ }
+#endif // defined(FEATURE_SIMD) && defined(_TARGET_X86_)
+
emit->emitIns_R_S(ins_Load(treeNode->TypeGet(), compiler->isSIMDTypeLocalAligned(lcl->gtLclNum)),
emitTypeSize(treeNode), treeNode->gtRegNum, lcl->gtLclNum, 0);
genProduceReg(treeNode);
@@ -2075,7 +1461,7 @@ void CodeGen::genCodeForTreeNode(GenTreePtr treeNode)
// Loading of TYP_SIMD12 (i.e. Vector3) field
if (treeNode->TypeGet() == TYP_SIMD12)
{
- genLoadLclFldTypeSIMD12(treeNode);
+ genLoadLclTypeSIMD12(treeNode);
break;
}
#endif
@@ -2243,6 +1629,9 @@ void CodeGen::genCodeForTreeNode(GenTreePtr treeNode)
break;
case GT_MULHI:
+#ifdef _TARGET_X86_
+ case GT_MUL_LONG:
+#endif
genCodeForMulHi(treeNode->AsOp());
genProduceReg(treeNode);
break;
@@ -2408,18 +1797,18 @@ void CodeGen::genCodeForTreeNode(GenTreePtr treeNode)
// X86 Long comparison
else if (varTypeIsLong(op1Type))
{
- // When not materializing the result in a register, the compare logic is generated
- // when we generate the GT_JTRUE.
- if (treeNode->gtRegNum != REG_NA)
- {
- genCompareLong(treeNode);
- }
- else
- {
- // We generate the compare when we generate the GT_JTRUE, but we need to consume
- // the operands now.
- genConsumeOperands(treeNode->AsOp());
- }
+#ifdef DEBUG
+ // The result of an unlowered long compare on a 32-bit target must either be
+ // a) materialized into a register, or
+ // b) unused.
+ //
+ // A long compare that has a result that is used but not materialized into a register should
+ // have been handled by Lowering::LowerCompare.
+
+ LIR::Use use;
+ assert((treeNode->gtRegNum != REG_NA) || !LIR::AsRange(compiler->compCurBB).TryGetUse(treeNode, &use));
+#endif
+ genCompareLong(treeNode);
}
#endif // !defined(_TARGET_64BIT_)
else
@@ -2437,52 +1826,60 @@ void CodeGen::genCodeForTreeNode(GenTreePtr treeNode)
assert(compiler->compCurBB->bbJumpKind == BBJ_COND);
#if !defined(_TARGET_64BIT_)
- // For long compares, we emit special logic
- if (varTypeIsLong(cmp->gtGetOp1()))
- {
- genJTrueLong(cmp);
- }
- else
+ // Long-typed compares should have been handled by Lowering::LowerCompare.
+ assert(!varTypeIsLong(cmp->gtGetOp1()));
#endif
- {
- // Get the "kind" and type of the comparison. Note that whether it is an unsigned cmp
- // is governed by a flag NOT by the inherent type of the node
- // TODO-XArch-CQ: Check if we can use the currently set flags.
- emitJumpKind jumpKind[2];
- bool branchToTrueLabel[2];
- genJumpKindsForTree(cmp, jumpKind, branchToTrueLabel);
- BasicBlock* skipLabel = nullptr;
- if (jumpKind[0] != EJ_NONE)
- {
- BasicBlock* jmpTarget;
- if (branchToTrueLabel[0])
- {
- jmpTarget = compiler->compCurBB->bbJumpDest;
- }
- else
- {
- // This case arises only for ordered GT_EQ right now
- assert((cmp->gtOper == GT_EQ) && ((cmp->gtFlags & GTF_RELOP_NAN_UN) == 0));
- skipLabel = genCreateTempLabel();
- jmpTarget = skipLabel;
- }
-
- inst_JMP(jumpKind[0], jmpTarget);
- }
+ // Get the "kind" and type of the comparison. Note that whether it is an unsigned cmp
+ // is governed by a flag NOT by the inherent type of the node
+ // TODO-XArch-CQ: Check if we can use the currently set flags.
+ emitJumpKind jumpKind[2];
+ bool branchToTrueLabel[2];
+ genJumpKindsForTree(cmp, jumpKind, branchToTrueLabel);
- if (jumpKind[1] != EJ_NONE)
+ BasicBlock* skipLabel = nullptr;
+ if (jumpKind[0] != EJ_NONE)
+ {
+ BasicBlock* jmpTarget;
+ if (branchToTrueLabel[0])
{
- // the second conditional branch always has to be to the true label
- assert(branchToTrueLabel[1]);
- inst_JMP(jumpKind[1], compiler->compCurBB->bbJumpDest);
+ jmpTarget = compiler->compCurBB->bbJumpDest;
}
-
- if (skipLabel != nullptr)
+ else
{
- genDefineTempLabel(skipLabel);
+ // This case arises only for ordered GT_EQ right now
+ assert((cmp->gtOper == GT_EQ) && ((cmp->gtFlags & GTF_RELOP_NAN_UN) == 0));
+ skipLabel = genCreateTempLabel();
+ jmpTarget = skipLabel;
}
+
+ inst_JMP(jumpKind[0], jmpTarget);
+ }
+
+ if (jumpKind[1] != EJ_NONE)
+ {
+ // the second conditional branch always has to be to the true label
+ assert(branchToTrueLabel[1]);
+ inst_JMP(jumpKind[1], compiler->compCurBB->bbJumpDest);
}
+
+ if (skipLabel != nullptr)
+ {
+ genDefineTempLabel(skipLabel);
+ }
+ }
+ break;
+
+ case GT_JCC:
+ {
+ GenTreeJumpCC* jcc = treeNode->AsJumpCC();
+
+ assert(compiler->compCurBB->bbJumpKind == BBJ_COND);
+
+ CompareKind compareKind = ((jcc->gtFlags & GTF_UNSIGNED) != 0) ? CK_UNSIGNED : CK_SIGNED;
+ emitJumpKind jumpKind = genJumpKindForOper(jcc->gtCondition, compareKind);
+
+ inst_JMP(jumpKind, compiler->compCurBB->bbJumpDest);
}
break;
@@ -2572,12 +1969,13 @@ void CodeGen::genCodeForTreeNode(GenTreePtr treeNode)
break;
case GT_LIST:
+ case GT_FIELD_LIST:
case GT_ARGPLACE:
// Nothing to do
break;
case GT_PUTARG_STK:
- genPutArgStk(treeNode);
+ genPutArgStk(treeNode->AsPutArgStk());
break;
case GT_PUTARG_REG:
@@ -2608,7 +2006,7 @@ void CodeGen::genCodeForTreeNode(GenTreePtr treeNode)
case GT_LOCKADD:
case GT_XCHG:
case GT_XADD:
- genLockedInstructions(treeNode);
+ genLockedInstructions(treeNode->AsOp());
break;
case GT_MEMORYBARRIER:
@@ -2795,7 +2193,8 @@ void CodeGen::genCodeForTreeNode(GenTreePtr treeNode)
{
#ifdef DEBUG
char message[256];
- sprintf(message, "Unimplemented node type %s\n", GenTree::NodeName(treeNode->OperGet()));
+ _snprintf_s(message, _countof(message), _TRUNCATE, "Unimplemented node type %s\n",
+ GenTree::NodeName(treeNode->OperGet()));
#endif
assert(!"Unknown node in codegen");
}
@@ -3330,8 +2729,10 @@ ALLOC_DONE:
BAILOUT:
// Write the lvaLocAllocSPvar stack frame slot
- noway_assert(compiler->lvaLocAllocSPvar != BAD_VAR_NUM);
- getEmitter()->emitIns_S_R(ins_Store(TYP_I_IMPL), EA_PTRSIZE, REG_SPBASE, compiler->lvaLocAllocSPvar, 0);
+ if (compiler->lvaLocAllocSPvar != BAD_VAR_NUM)
+ {
+ getEmitter()->emitIns_S_R(ins_Store(TYP_I_IMPL), EA_PTRSIZE, REG_SPBASE, compiler->lvaLocAllocSPvar, 0);
+ }
#if STACK_PROBES
if (compiler->opts.compNeedStackProbes)
@@ -3356,10 +2757,15 @@ BAILOUT:
void CodeGen::genCodeForStoreBlk(GenTreeBlk* storeBlkNode)
{
+#ifdef JIT32_GCENCODER
+ assert(!storeBlkNode->gtBlkOpGcUnsafe);
+#else
if (storeBlkNode->gtBlkOpGcUnsafe)
{
getEmitter()->emitDisableGC();
}
+#endif // JIT32_GCENCODER
+
bool isCopyBlk = storeBlkNode->OperIsCopyBlkOp();
switch (storeBlkNode->gtBlkOpKind)
@@ -3399,23 +2805,40 @@ void CodeGen::genCodeForStoreBlk(GenTreeBlk* storeBlkNode)
default:
unreached();
}
+
+#ifndef JIT32_GCENCODER
if (storeBlkNode->gtBlkOpGcUnsafe)
{
getEmitter()->emitEnableGC();
}
+#endif // !defined(JIT32_GCENCODER)
}
-// Generate code for InitBlk using rep stos.
+//
+//------------------------------------------------------------------------
+// genCodeForInitBlkRepStos: Generate code for InitBlk using rep stos.
+//
+// Arguments:
+// initBlkNode - The Block store for which we are generating code.
+//
// Preconditions:
-// The size of the buffers must be a constant and also less than INITBLK_STOS_LIMIT bytes.
-// Any value larger than that, we'll use the helper even if both the
-// fill byte and the size are integer constants.
+// On x64:
+// The size of the buffers must be a constant and also less than INITBLK_STOS_LIMIT bytes.
+// Any value larger than that, we'll use the helper even if both the fill byte and the
+// size are integer constants.
+// On x86:
+// The size must either be a non-constant or less than INITBLK_STOS_LIMIT bytes.
+//
void CodeGen::genCodeForInitBlkRepStos(GenTreeBlk* initBlkNode)
{
- // Make sure we got the arguments of the initblk/initobj operation in the right registers
+ // Make sure we got the arguments of the initblk/initobj operation in the right registers.
unsigned size = initBlkNode->Size();
GenTreePtr dstAddr = initBlkNode->Addr();
GenTreePtr initVal = initBlkNode->Data();
+ if (initVal->OperIsInitVal())
+ {
+ initVal = initVal->gtGetOp1();
+ }
#ifdef DEBUG
assert(!dstAddr->isContained());
@@ -3428,7 +2851,8 @@ void CodeGen::genCodeForInitBlkRepStos(GenTreeBlk* initBlkNode)
#ifdef _TARGET_AMD64_
assert(size > CPBLK_UNROLL_LIMIT && size < CPBLK_MOVS_LIMIT);
#else
- assert(size > CPBLK_UNROLL_LIMIT);
+ // Note that a size of zero means a non-constant size.
+ assert((size == 0) || (size > CPBLK_UNROLL_LIMIT));
#endif
}
@@ -3449,9 +2873,13 @@ void CodeGen::genCodeForInitBlkUnroll(GenTreeBlk* initBlkNode)
unsigned size = initBlkNode->Size();
GenTreePtr dstAddr = initBlkNode->Addr();
GenTreePtr initVal = initBlkNode->Data();
+ if (initVal->OperIsInitVal())
+ {
+ initVal = initVal->gtGetOp1();
+ }
assert(!dstAddr->isContained());
- assert(!initVal->isContained());
+ assert(!initVal->isContained() || (initVal->IsIntegralConst(0) && ((size & 0xf) == 0)));
assert(size != 0);
assert(size <= INITBLK_UNROLL_LIMIT);
assert(initVal->gtSkipReloadOrCopy()->IsCnsIntOrI());
@@ -3512,9 +2940,11 @@ void CodeGen::genCodeForInitBlkUnroll(GenTreeBlk* initBlkNode)
offset += 4;
emit->emitIns_AR_R(INS_mov, EA_4BYTE, valReg, dstAddr->gtRegNum, offset);
offset += 4;
-#else // !_TARGET_X86_
+#else // !_TARGET_X86_
+
emit->emitIns_AR_R(INS_mov, EA_8BYTE, valReg, dstAddr->gtRegNum, offset);
offset += 8;
+
#endif // !_TARGET_X86_
}
if ((size & 4) != 0)
@@ -3544,6 +2974,10 @@ void CodeGen::genCodeForInitBlk(GenTreeBlk* initBlkNode)
unsigned blockSize = initBlkNode->Size();
GenTreePtr dstAddr = initBlkNode->Addr();
GenTreePtr initVal = initBlkNode->Data();
+ if (initVal->OperIsInitVal())
+ {
+ initVal = initVal->gtGetOp1();
+ }
assert(!dstAddr->isContained());
assert(!initVal->isContained());
@@ -3760,21 +3194,145 @@ void CodeGen::genCodeForCpBlkRepMovs(GenTreeBlk* cpBlkNode)
instGen(INS_r_movsb);
}
-#ifdef FEATURE_UNIX_AMD64_STRUCT_PASSING
+#ifdef FEATURE_PUT_STRUCT_ARG_STK
+//------------------------------------------------------------------------
+// CodeGen::genMove8IfNeeded: Conditionally move 8 bytes of a struct to the argument area
+//
+// Arguments:
+// size - The size of bytes remaining to be moved
+// longTmpReg - The tmp register to be used for the long value
+// srcAddr - The address of the source struct
+// offset - The current offset being copied
+//
+// Return Value:
+// Returns the number of bytes moved (8 or 0).
+//
+// Notes:
+// This is used in the PutArgStkKindUnroll case, to move any bytes that are
+// not an even multiple of 16.
+// On x86, longTmpReg must be an xmm reg; on x64 it must be an integer register.
+// This is checked by genStoreRegToStackArg.
+//
+int CodeGen::genMove8IfNeeded(unsigned size, regNumber longTmpReg, GenTree* srcAddr, unsigned offset)
+{
+#ifdef _TARGET_X86_
+ instruction longMovIns = INS_movq;
+#else // !_TARGET_X86_
+ instruction longMovIns = INS_mov;
+#endif // !_TARGET_X86_
+ if ((size & 8) != 0)
+ {
+ genCodeForLoadOffset(longMovIns, EA_8BYTE, longTmpReg, srcAddr, offset);
+ genStoreRegToStackArg(TYP_LONG, longTmpReg, offset);
+ return 8;
+ }
+ return 0;
+}
+
+//------------------------------------------------------------------------
+// CodeGen::genMove4IfNeeded: Conditionally move 4 bytes of a struct to the argument area
+//
+// Arguments:
+// size - The size of bytes remaining to be moved
+// intTmpReg - The tmp register to be used for the long value
+// srcAddr - The address of the source struct
+// offset - The current offset being copied
+//
+// Return Value:
+// Returns the number of bytes moved (4 or 0).
+//
+// Notes:
+// This is used in the PutArgStkKindUnroll case, to move any bytes that are
+// not an even multiple of 16.
+// intTmpReg must be an integer register.
+// This is checked by genStoreRegToStackArg.
+//
+int CodeGen::genMove4IfNeeded(unsigned size, regNumber intTmpReg, GenTree* srcAddr, unsigned offset)
+{
+ if ((size & 4) != 0)
+ {
+ genCodeForLoadOffset(INS_mov, EA_4BYTE, intTmpReg, srcAddr, offset);
+ genStoreRegToStackArg(TYP_INT, intTmpReg, offset);
+ return 4;
+ }
+ return 0;
+}
+
+//------------------------------------------------------------------------
+// CodeGen::genMove2IfNeeded: Conditionally move 2 bytes of a struct to the argument area
+//
+// Arguments:
+// size - The size of bytes remaining to be moved
+// intTmpReg - The tmp register to be used for the long value
+// srcAddr - The address of the source struct
+// offset - The current offset being copied
+//
+// Return Value:
+// Returns the number of bytes moved (2 or 0).
+//
+// Notes:
+// This is used in the PutArgStkKindUnroll case, to move any bytes that are
+// not an even multiple of 16.
+// intTmpReg must be an integer register.
+// This is checked by genStoreRegToStackArg.
+//
+int CodeGen::genMove2IfNeeded(unsigned size, regNumber intTmpReg, GenTree* srcAddr, unsigned offset)
+{
+ if ((size & 2) != 0)
+ {
+ genCodeForLoadOffset(INS_mov, EA_2BYTE, intTmpReg, srcAddr, offset);
+ genStoreRegToStackArg(TYP_SHORT, intTmpReg, offset);
+ return 2;
+ }
+ return 0;
+}
+
+//------------------------------------------------------------------------
+// CodeGen::genMove1IfNeeded: Conditionally move 1 byte of a struct to the argument area
+//
+// Arguments:
+// size - The size of bytes remaining to be moved
+// intTmpReg - The tmp register to be used for the long value
+// srcAddr - The address of the source struct
+// offset - The current offset being copied
+//
+// Return Value:
+// Returns the number of bytes moved (1 or 0).
+//
+// Notes:
+// This is used in the PutArgStkKindUnroll case, to move any bytes that are
+// not an even multiple of 16.
+// intTmpReg must be an integer register.
+// This is checked by genStoreRegToStackArg.
+//
+int CodeGen::genMove1IfNeeded(unsigned size, regNumber intTmpReg, GenTree* srcAddr, unsigned offset)
+{
+
+ if ((size & 1) != 0)
+ {
+ genCodeForLoadOffset(INS_mov, EA_1BYTE, intTmpReg, srcAddr, offset);
+ genStoreRegToStackArg(TYP_BYTE, intTmpReg, offset);
+ return 1;
+ }
+ return 0;
+}
//---------------------------------------------------------------------------------------------------------------//
// genStructPutArgUnroll: Generates code for passing a struct arg on stack by value using loop unrolling.
//
// Arguments:
// putArgNode - the PutArgStk tree.
-// baseVarNum - the base var number, relative to which the by-val struct will be copied on the stack.
+//
+// Notes:
+// m_stkArgVarNum must be set to the base var number, relative to which the by-val struct will be copied to the
+// stack.
//
// TODO-Amd64-Unix: Try to share code with copyblk.
// Need refactoring of copyblk before it could be used for putarg_stk.
// The difference for now is that a putarg_stk contains its children, while cpyblk does not.
// This creates differences in code. After some significant refactoring it could be reused.
//
-void CodeGen::genStructPutArgUnroll(GenTreePutArgStk* putArgNode, unsigned baseVarNum)
+void CodeGen::genStructPutArgUnroll(GenTreePutArgStk* putArgNode)
{
// We will never call this method for SIMD types, which are stored directly
// in genPutStructArgStk().
@@ -3801,14 +3359,43 @@ void CodeGen::genStructPutArgUnroll(GenTreePutArgStk* putArgNode, unsigned baseV
unsigned offset = 0;
+ regNumber xmmTmpReg = REG_NA;
+ regNumber intTmpReg = REG_NA;
+ regNumber longTmpReg = REG_NA;
+#ifdef _TARGET_X86_
+ // On x86 we use an XMM register for both 16 and 8-byte chunks, but if it's
+ // less than 16 bytes, we will just be using pushes
+ if (size >= 8)
+ {
+ xmmTmpReg = genRegNumFromMask(putArgNode->gtRsvdRegs & RBM_ALLFLOAT);
+ longTmpReg = xmmTmpReg;
+ }
+ if ((size & 0x7) != 0)
+ {
+ intTmpReg = genRegNumFromMask(putArgNode->gtRsvdRegs & RBM_ALLINT);
+ }
+#else // !_TARGET_X86_
+ // On x64 we use an XMM register only for 16-byte chunks.
+ if (size >= XMM_REGSIZE_BYTES)
+ {
+ xmmTmpReg = genRegNumFromMask(putArgNode->gtRsvdRegs & RBM_ALLFLOAT);
+ }
+ if ((size & 0xf) != 0)
+ {
+ intTmpReg = genRegNumFromMask(putArgNode->gtRsvdRegs & RBM_ALLINT);
+ longTmpReg = intTmpReg;
+ }
+#endif // !_TARGET_X86_
+
// If the size of this struct is larger than 16 bytes
// let's use SSE2 to be able to do 16 byte at a time
// loads and stores.
if (size >= XMM_REGSIZE_BYTES)
{
+#ifdef _TARGET_X86_
+ assert(!m_pushStkArg);
+#endif // _TARGET_X86_
assert(putArgNode->gtRsvdRegs != RBM_NONE);
- regNumber xmmReg = genRegNumFromMask(putArgNode->gtRsvdRegs & RBM_ALLFLOAT);
- assert(genIsValidFloatReg(xmmReg));
size_t slots = size / XMM_REGSIZE_BYTES;
assert(putArgNode->gtGetOp1()->isContained());
@@ -3820,11 +3407,10 @@ void CodeGen::genStructPutArgUnroll(GenTreePutArgStk* putArgNode, unsigned baseV
while (slots-- > 0)
{
// Load
- genCodeForLoadOffset(INS_movdqu, EA_8BYTE, xmmReg, src->gtGetOp1(),
- offset); // Load the address of the child of the Obj node.
+ genCodeForLoadOffset(INS_movdqu, EA_8BYTE, xmmTmpReg, src->gtGetOp1(), offset);
// Store
- emit->emitIns_S_R(INS_movdqu, EA_8BYTE, xmmReg, baseVarNum, putArgOffset + offset);
+ genStoreRegToStackArg(TYP_STRUCT, xmmTmpReg, offset);
offset += XMM_REGSIZE_BYTES;
}
@@ -3833,41 +3419,29 @@ void CodeGen::genStructPutArgUnroll(GenTreePutArgStk* putArgNode, unsigned baseV
// Fill the remainder (15 bytes or less) if there's one.
if ((size & 0xf) != 0)
{
- // Grab the integer temp register to emit the remaining loads and stores.
- regNumber tmpReg = genRegNumFromMask(putArgNode->gtRsvdRegs & RBM_ALLINT);
- assert(genIsValidIntReg(tmpReg));
-
- if ((size & 8) != 0)
- {
- genCodeForLoadOffset(INS_mov, EA_8BYTE, tmpReg, src->gtOp.gtOp1, offset);
-
- emit->emitIns_S_R(INS_mov, EA_8BYTE, tmpReg, baseVarNum, putArgOffset + offset);
-
- offset += 8;
- }
-
- if ((size & 4) != 0)
- {
- genCodeForLoadOffset(INS_mov, EA_4BYTE, tmpReg, src->gtOp.gtOp1, offset);
-
- emit->emitIns_S_R(INS_mov, EA_4BYTE, tmpReg, baseVarNum, putArgOffset + offset);
-
- offset += 4;
- }
-
- if ((size & 2) != 0)
- {
- genCodeForLoadOffset(INS_mov, EA_2BYTE, tmpReg, src->gtOp.gtOp1, offset);
-
- emit->emitIns_S_R(INS_mov, EA_2BYTE, tmpReg, baseVarNum, putArgOffset + offset);
-
- offset += 2;
+#ifdef _TARGET_X86_
+ if (m_pushStkArg)
+ {
+ // This case is currently supported only for the case where the total size is
+ // less than XMM_REGSIZE_BYTES. We need to push the remaining chunks in reverse
+ // order. However, morph has ensured that we have a struct that is an even
+ // multiple of TARGET_POINTER_SIZE, so we don't need to worry about alignment.
+ assert(((size & 0xc) == size) && (offset == 0));
+ // If we have a 4 byte chunk, load it from either offset 0 or 8, depending on
+ // whether we've got an 8 byte chunk, and then push it on the stack.
+ unsigned pushedBytes = genMove4IfNeeded(size, intTmpReg, src->gtOp.gtOp1, size & 0x8);
+ // Now if we have an 8 byte chunk, load it from offset 0 (it's the first chunk)
+ // and push it on the stack.
+ pushedBytes += genMove8IfNeeded(size, longTmpReg, src->gtOp.gtOp1, 0);
}
-
- if ((size & 1) != 0)
+ else
+#endif // _TARGET_X86_
{
- genCodeForLoadOffset(INS_mov, EA_1BYTE, tmpReg, src->gtOp.gtOp1, offset);
- emit->emitIns_S_R(INS_mov, EA_1BYTE, tmpReg, baseVarNum, putArgOffset + offset);
+ offset += genMove8IfNeeded(size, longTmpReg, src->gtOp.gtOp1, offset);
+ offset += genMove4IfNeeded(size, intTmpReg, src->gtOp.gtOp1, offset);
+ offset += genMove2IfNeeded(size, intTmpReg, src->gtOp.gtOp1, offset);
+ offset += genMove1IfNeeded(size, intTmpReg, src->gtOp.gtOp1, offset);
+ assert(offset == size);
}
}
}
@@ -3877,17 +3451,16 @@ void CodeGen::genStructPutArgUnroll(GenTreePutArgStk* putArgNode, unsigned baseV
//
// Arguments:
// putArgNode - the PutArgStk tree.
-// baseVarNum - the base var number, relative to which the by-val struct bits will go.
//
// Preconditions:
// The size argument of the PutArgStk (for structs) is a constant and is between
// CPBLK_UNROLL_LIMIT and CPBLK_MOVS_LIMIT bytes.
+// m_stkArgVarNum must be set to the base var number, relative to which the by-val struct bits will go.
//
-void CodeGen::genStructPutArgRepMovs(GenTreePutArgStk* putArgNode, unsigned baseVarNum)
+void CodeGen::genStructPutArgRepMovs(GenTreePutArgStk* putArgNode)
{
assert(putArgNode->TypeGet() == TYP_STRUCT);
assert(putArgNode->getArgSize() > CPBLK_UNROLL_LIMIT);
- assert(baseVarNum != BAD_VAR_NUM);
// Make sure we got the arguments of the cpblk operation in the right registers
GenTreePtr dstAddr = putArgNode;
@@ -3897,7 +3470,7 @@ void CodeGen::genStructPutArgRepMovs(GenTreePutArgStk* putArgNode, unsigned base
assert(putArgNode->gtRsvdRegs == (RBM_RDI | RBM_RCX | RBM_RSI));
assert(srcAddr->isContained());
- genConsumePutStructArgStk(putArgNode, REG_RDI, REG_RSI, REG_RCX, baseVarNum);
+ genConsumePutStructArgStk(putArgNode, REG_RDI, REG_RSI, REG_RCX);
instGen(INS_r_movsb);
}
@@ -3906,12 +3479,14 @@ void CodeGen::genStructPutArgRepMovs(GenTreePutArgStk* putArgNode, unsigned base
// must be cleared to zeroes. The native compiler doesn't clear the upper bits
// and there is no way to know if the caller is native or not. So, the upper
// 32 bits of Vector argument on stack are always cleared to zero.
-#ifdef FEATURE_SIMD
+#if defined(FEATURE_UNIX_AMD64_STRUCT_PASSING) && defined(FEATURE_SIMD)
void CodeGen::genClearStackVec3ArgUpperBits()
{
#ifdef DEBUG
if (verbose)
+ {
printf("*************** In genClearStackVec3ArgUpperBits()\n");
+ }
#endif
assert(compiler->compGeneratingProlog);
@@ -3948,12 +3523,13 @@ void CodeGen::genClearStackVec3ArgUpperBits()
}
}
}
-#endif // FEATURE_SIMD
-#endif // FEATURE_UNIX_AMD64_STRUCT_PASSING
+#endif // defined(FEATURE_UNIX_AMD64_STRUCT_PASSING) && defined(FEATURE_SIMD)
+#endif // FEATURE_PUT_STRUCT_ARG_STK
// Generate code for CpObj nodes wich copy structs that have interleaved
// GC pointers.
-// This will generate a sequence of movsq instructions for the cases of non-gc members
+// This will generate a sequence of movsp instructions for the cases of non-gc members.
+// Note that movsp is an alias for movsd on x86 and movsq on x64.
// and calls to the BY_REF_ASSIGN helper otherwise.
void CodeGen::genCodeForCpObj(GenTreeObj* cpObjNode)
{
@@ -3961,6 +3537,7 @@ void CodeGen::genCodeForCpObj(GenTreeObj* cpObjNode)
GenTreePtr dstAddr = cpObjNode->Addr();
GenTreePtr source = cpObjNode->Data();
GenTreePtr srcAddr = nullptr;
+ var_types srcAddrType = TYP_BYREF;
bool sourceIsLocal = false;
assert(source->isContained());
@@ -3973,24 +3550,12 @@ void CodeGen::genCodeForCpObj(GenTreeObj* cpObjNode)
{
noway_assert(source->IsLocal());
sourceIsLocal = true;
- // TODO: Consider making the addrForm() method in Rationalize public, e.g. in GenTree.
- // OR: transform source to GT_IND(GT_LCL_VAR_ADDR)
- if (source->OperGet() == GT_LCL_VAR)
- {
- source->SetOper(GT_LCL_VAR_ADDR);
- }
- else
- {
- assert(source->OperGet() == GT_LCL_FLD);
- source->SetOper(GT_LCL_FLD_ADDR);
- }
- srcAddr = source;
}
bool dstOnStack = dstAddr->OperIsLocalAddr();
#ifdef DEBUG
- bool isRepMovsqUsed = false;
+ bool isRepMovspUsed = false;
assert(!dstAddr->isContained());
@@ -3998,44 +3563,40 @@ void CodeGen::genCodeForCpObj(GenTreeObj* cpObjNode)
// with CpObj, so this requires special logic.
assert(cpObjNode->gtGcPtrCount > 0);
- // MovSq instruction is used for copying non-gcref fields and it needs
- // src = RSI and dst = RDI.
+ // MovSp (alias for movsq on x64 and movsd on x86) instruction is used for copying non-gcref fields
+ // and it needs src = RSI and dst = RDI.
// Either these registers must not contain lclVars, or they must be dying or marked for spill.
// This is because these registers are incremented as we go through the struct.
- GenTree* actualSrcAddr = srcAddr->gtSkipReloadOrCopy();
- GenTree* actualDstAddr = dstAddr->gtSkipReloadOrCopy();
- unsigned srcLclVarNum = BAD_VAR_NUM;
- unsigned dstLclVarNum = BAD_VAR_NUM;
- bool isSrcAddrLiveOut = false;
- bool isDstAddrLiveOut = false;
- if (genIsRegCandidateLocal(actualSrcAddr))
- {
- srcLclVarNum = actualSrcAddr->AsLclVarCommon()->gtLclNum;
- isSrcAddrLiveOut = ((actualSrcAddr->gtFlags & (GTF_VAR_DEATH | GTF_SPILL)) == 0);
- }
- if (genIsRegCandidateLocal(actualDstAddr))
- {
- dstLclVarNum = actualDstAddr->AsLclVarCommon()->gtLclNum;
- isDstAddrLiveOut = ((actualDstAddr->gtFlags & (GTF_VAR_DEATH | GTF_SPILL)) == 0);
- }
- assert((actualSrcAddr->gtRegNum != REG_RSI) || !isSrcAddrLiveOut ||
- ((srcLclVarNum == dstLclVarNum) && !isDstAddrLiveOut));
- assert((actualDstAddr->gtRegNum != REG_RDI) || !isDstAddrLiveOut ||
- ((srcLclVarNum == dstLclVarNum) && !isSrcAddrLiveOut));
+ if (!sourceIsLocal)
+ {
+ GenTree* actualSrcAddr = srcAddr->gtSkipReloadOrCopy();
+ GenTree* actualDstAddr = dstAddr->gtSkipReloadOrCopy();
+ unsigned srcLclVarNum = BAD_VAR_NUM;
+ unsigned dstLclVarNum = BAD_VAR_NUM;
+ bool isSrcAddrLiveOut = false;
+ bool isDstAddrLiveOut = false;
+ if (genIsRegCandidateLocal(actualSrcAddr))
+ {
+ srcLclVarNum = actualSrcAddr->AsLclVarCommon()->gtLclNum;
+ isSrcAddrLiveOut = ((actualSrcAddr->gtFlags & (GTF_VAR_DEATH | GTF_SPILL)) == 0);
+ }
+ if (genIsRegCandidateLocal(actualDstAddr))
+ {
+ dstLclVarNum = actualDstAddr->AsLclVarCommon()->gtLclNum;
+ isDstAddrLiveOut = ((actualDstAddr->gtFlags & (GTF_VAR_DEATH | GTF_SPILL)) == 0);
+ }
+ assert((actualSrcAddr->gtRegNum != REG_RSI) || !isSrcAddrLiveOut ||
+ ((srcLclVarNum == dstLclVarNum) && !isDstAddrLiveOut));
+ assert((actualDstAddr->gtRegNum != REG_RDI) || !isDstAddrLiveOut ||
+ ((srcLclVarNum == dstLclVarNum) && !isSrcAddrLiveOut));
+ srcAddrType = srcAddr->TypeGet();
+ }
#endif // DEBUG
- // Consume these registers.
+ // Consume the operands and get them into the right registers.
// They may now contain gc pointers (depending on their type; gcMarkRegPtrVal will "do the right thing").
- if (sourceIsLocal)
- {
- inst_RV_TT(INS_lea, REG_RSI, source, 0, EA_BYREF);
- genConsumeBlockOp(cpObjNode, REG_RDI, REG_NA, REG_NA);
- }
- else
- {
- genConsumeBlockOp(cpObjNode, REG_RDI, REG_RSI, REG_NA);
- }
- gcInfo.gcMarkRegPtrVal(REG_RSI, srcAddr->TypeGet());
+ genConsumeBlockOp(cpObjNode, REG_RDI, REG_RSI, REG_NA);
+ gcInfo.gcMarkRegPtrVal(REG_RSI, srcAddrType);
gcInfo.gcMarkRegPtrVal(REG_RDI, dstAddr->TypeGet());
unsigned slots = cpObjNode->gtSlots;
@@ -4046,23 +3607,23 @@ void CodeGen::genCodeForCpObj(GenTreeObj* cpObjNode)
if (slots >= CPOBJ_NONGC_SLOTS_LIMIT)
{
#ifdef DEBUG
- // If the destination of the CpObj is on the stack
- // make sure we allocated RCX to emit rep movsq.
- regNumber tmpReg = genRegNumFromMask(cpObjNode->gtRsvdRegs & RBM_ALLINT);
- assert(tmpReg == REG_RCX);
- isRepMovsqUsed = true;
+ // If the destination of the CpObj is on the stack, make sure we allocated
+ // RCX to emit the movsp (alias for movsd or movsq for 32 and 64 bits respectively).
+ assert((cpObjNode->gtRsvdRegs & RBM_RCX) != 0);
+ regNumber tmpReg = REG_RCX;
+ isRepMovspUsed = true;
#endif // DEBUG
getEmitter()->emitIns_R_I(INS_mov, EA_4BYTE, REG_RCX, slots);
- instGen(INS_r_movsq);
+ instGen(INS_r_movsp);
}
else
{
- // For small structs, it's better to emit a sequence of movsq than to
- // emit a rep movsq instruction.
+ // For small structs, it's better to emit a sequence of movsp than to
+ // emit a rep movsp instruction.
while (slots > 0)
{
- instGen(INS_movsq);
+ instGen(INS_movsp);
slots--;
}
}
@@ -4078,7 +3639,7 @@ void CodeGen::genCodeForCpObj(GenTreeObj* cpObjNode)
switch (gcPtrs[i])
{
case TYPE_GC_NONE:
- // Let's see if we can use rep movsq instead of a sequence of movsq instructions
+ // Let's see if we can use rep movsp instead of a sequence of movsp instructions
// to save cycles and code size.
{
unsigned nonGcSlotCount = 0;
@@ -4090,12 +3651,12 @@ void CodeGen::genCodeForCpObj(GenTreeObj* cpObjNode)
} while (i < slots && gcPtrs[i] == TYPE_GC_NONE);
// If we have a very small contiguous non-gc region, it's better just to
- // emit a sequence of movsq instructions
+ // emit a sequence of movsp instructions
if (nonGcSlotCount < CPOBJ_NONGC_SLOTS_LIMIT)
{
while (nonGcSlotCount > 0)
{
- instGen(INS_movsq);
+ instGen(INS_movsp);
nonGcSlotCount--;
}
}
@@ -4103,13 +3664,13 @@ void CodeGen::genCodeForCpObj(GenTreeObj* cpObjNode)
{
#ifdef DEBUG
// Otherwise, we can save code-size and improve CQ by emitting
- // rep movsq
- regNumber tmpReg = genRegNumFromMask(cpObjNode->gtRsvdRegs & RBM_ALLINT);
- assert(tmpReg == REG_RCX);
- isRepMovsqUsed = true;
+ // rep movsp (alias for movsd/movsq for x86/x64)
+ assert((cpObjNode->gtRsvdRegs & RBM_RCX) != 0);
+ regNumber tmpReg = REG_RCX;
+ isRepMovspUsed = true;
#endif // DEBUG
getEmitter()->emitIns_R_I(INS_mov, EA_4BYTE, REG_RCX, nonGcSlotCount);
- instGen(INS_r_movsq);
+ instGen(INS_r_movsp);
}
}
break;
@@ -4235,7 +3796,7 @@ void CodeGen::genJumpTable(GenTree* treeNode)
// generate code for the locked operations:
// GT_LOCKADD, GT_XCHG, GT_XADD
-void CodeGen::genLockedInstructions(GenTree* treeNode)
+void CodeGen::genLockedInstructions(GenTreeOp* treeNode)
{
GenTree* data = treeNode->gtOp.gtOp2;
GenTree* addr = treeNode->gtOp.gtOp1;
@@ -4244,11 +3805,6 @@ void CodeGen::genLockedInstructions(GenTree* treeNode)
regNumber addrReg = addr->gtRegNum;
instruction ins;
- // all of these nodes implicitly do an indirection on op1
- // so create a temporary node to feed into the pattern matching
- GenTreeIndir i = indirForm(data->TypeGet(), addr);
- genConsumeReg(addr);
-
// The register allocator should have extended the lifetime of the address
// so that it is not used as the target.
noway_assert(addrReg != targetReg);
@@ -4258,7 +3814,7 @@ void CodeGen::genLockedInstructions(GenTree* treeNode)
assert(targetReg != REG_NA || treeNode->OperGet() == GT_LOCKADD || !genIsRegCandidateLocal(data) ||
(data->gtFlags & GTF_VAR_DEATH) != 0);
- genConsumeIfReg(data);
+ genConsumeOperands(treeNode);
if (targetReg != REG_NA && dataReg != REG_NA && dataReg != targetReg)
{
inst_RV_RV(ins_Copy(data->TypeGet()), targetReg, dataReg);
@@ -4284,6 +3840,10 @@ void CodeGen::genLockedInstructions(GenTree* treeNode)
default:
unreached();
}
+
+ // all of these nodes implicitly do an indirection on op1
+ // so create a temporary node to feed into the pattern matching
+ GenTreeIndir i = indirForm(data->TypeGet(), addr);
getEmitter()->emitInsBinary(ins, emitTypeSize(data), &i, data);
if (treeNode->gtRegNum != REG_NA)
@@ -4459,22 +4019,22 @@ void CodeGen::genCodeForArrOffset(GenTreeArrOffs* arrOffset)
GenTreePtr arrObj = arrOffset->gtArrObj;
regNumber tgtReg = arrOffset->gtRegNum;
-
- noway_assert(tgtReg != REG_NA);
+ assert(tgtReg != REG_NA);
unsigned dim = arrOffset->gtCurrDim;
unsigned rank = arrOffset->gtArrRank;
var_types elemType = arrOffset->gtArrElemType;
- // We will use a temp register for the offset*scale+effectiveIndex computation.
- regMaskTP tmpRegMask = arrOffset->gtRsvdRegs;
- regNumber tmpReg = genRegNumFromMask(tmpRegMask);
-
// First, consume the operands in the correct order.
regNumber offsetReg = REG_NA;
+ regNumber tmpReg = REG_NA;
if (!offsetNode->IsIntegralConst(0))
{
offsetReg = genConsumeReg(offsetNode);
+
+ // We will use a temp register for the offset*scale+effectiveIndex computation.
+ regMaskTP tmpRegMask = arrOffset->gtRsvdRegs;
+ tmpReg = genRegNumFromMask(tmpRegMask);
}
else
{
@@ -4495,6 +4055,9 @@ void CodeGen::genCodeForArrOffset(GenTreeArrOffs* arrOffset)
if (!offsetNode->IsIntegralConst(0))
{
+ assert(tmpReg != REG_NA);
+ assert(arrReg != REG_NA);
+
// Evaluate tgtReg = offsetReg*dim_size + indexReg.
// tmpReg is used to load dim_size and the result of the multiplication.
// Note that dim_size will never be negative.
@@ -4617,6 +4180,12 @@ instruction CodeGen::genGetInsForOper(genTreeOps oper, var_types type)
case GT_SUB_HI:
ins = INS_sbb;
break;
+ case GT_LSH_HI:
+ ins = INS_shld;
+ break;
+ case GT_RSH_LO:
+ ins = INS_shrd;
+ break;
#endif // !defined(_TARGET_64BIT_)
default:
unreached();
@@ -4654,6 +4223,7 @@ void CodeGen::genCodeForShift(GenTreePtr tree)
regNumber operandReg = operand->gtRegNum;
GenTreePtr shiftBy = tree->gtGetOp2();
+
if (shiftBy->isContainedIntOrIImmed())
{
// First, move the operand to the destination register and
@@ -4672,12 +4242,7 @@ void CodeGen::genCodeForShift(GenTreePtr tree)
// We must have the number of bits to shift stored in ECX, since we constrained this node to
// sit in ECX. In case this didn't happen, LSRA expects the code generator to move it since it's a single
// register destination requirement.
- regNumber shiftReg = shiftBy->gtRegNum;
- if (shiftReg != REG_RCX)
- {
- // Issue the mov to RCX:
- inst_RV_RV(INS_mov, REG_RCX, shiftReg, shiftBy->TypeGet());
- }
+ genCopyRegIfNeeded(shiftBy, REG_RCX);
// The operand to be shifted must not be in ECX
noway_assert(operandReg != REG_RCX);
@@ -4692,6 +4257,67 @@ void CodeGen::genCodeForShift(GenTreePtr tree)
genProduceReg(tree);
}
+#ifdef _TARGET_X86_
+//------------------------------------------------------------------------
+// genCodeForShiftLong: Generates the code sequence for a GenTree node that
+// represents a three operand bit shift or rotate operation (<<Hi, >>Lo).
+//
+// Arguments:
+// tree - the bit shift node (that specifies the type of bit shift to perform).
+//
+// Assumptions:
+// a) All GenTrees are register allocated.
+// b) The shift-by-amount in tree->gtOp.gtOp2 is a contained constant
+//
+void CodeGen::genCodeForShiftLong(GenTreePtr tree)
+{
+ // Only the non-RMW case here.
+ genTreeOps oper = tree->OperGet();
+ assert(oper == GT_LSH_HI || oper == GT_RSH_LO);
+
+ GenTree* operand = tree->gtOp.gtOp1;
+ assert(operand->OperGet() == GT_LONG);
+ assert(!operand->gtOp.gtOp1->isContained());
+ assert(!operand->gtOp.gtOp2->isContained());
+
+ GenTree* operandLo = operand->gtGetOp1();
+ GenTree* operandHi = operand->gtGetOp2();
+
+ regNumber regLo = operandLo->gtRegNum;
+ regNumber regHi = operandHi->gtRegNum;
+
+ genConsumeOperands(tree->AsOp());
+
+ var_types targetType = tree->TypeGet();
+ instruction ins = genGetInsForOper(oper, targetType);
+
+ GenTreePtr shiftBy = tree->gtGetOp2();
+
+ assert(shiftBy->isContainedIntOrIImmed());
+
+ unsigned int count = shiftBy->AsIntConCommon()->IconValue();
+
+ regNumber regResult = (oper == GT_LSH_HI) ? regHi : regLo;
+
+ if (regResult != tree->gtRegNum)
+ {
+ inst_RV_RV(INS_mov, tree->gtRegNum, regResult, targetType);
+ }
+
+ if (oper == GT_LSH_HI)
+ {
+ inst_RV_RV_IV(ins, emitTypeSize(targetType), tree->gtRegNum, regLo, count);
+ }
+ else
+ {
+ assert(oper == GT_RSH_LO);
+ inst_RV_RV_IV(ins, emitTypeSize(targetType), tree->gtRegNum, regHi, count);
+ }
+
+ genProduceReg(tree);
+}
+#endif
+
//------------------------------------------------------------------------
// genCodeForShiftRMW: Generates the code sequence for a GT_STOREIND GenTree node that
// represents a RMW bit shift or rotate operation (<<, >>, >>>, rol, ror), for example:
@@ -4739,182 +4365,13 @@ void CodeGen::genCodeForShiftRMW(GenTreeStoreInd* storeInd)
// sit in ECX. In case this didn't happen, LSRA expects the code generator to move it since it's a single
// register destination requirement.
regNumber shiftReg = shiftBy->gtRegNum;
- if (shiftReg != REG_RCX)
- {
- // Issue the mov to RCX:
- inst_RV_RV(INS_mov, REG_RCX, shiftReg, shiftBy->TypeGet());
- }
+ genCopyRegIfNeeded(shiftBy, REG_RCX);
// The shiftBy operand is implicit, so call the unary version of emitInsRMW.
getEmitter()->emitInsRMW(ins, attr, storeInd);
}
}
-void CodeGen::genUnspillRegIfNeeded(GenTree* tree)
-{
- regNumber dstReg = tree->gtRegNum;
- GenTree* unspillTree = tree;
-
- if (tree->gtOper == GT_RELOAD)
- {
- unspillTree = tree->gtOp.gtOp1;
- }
-
- if ((unspillTree->gtFlags & GTF_SPILLED) != 0)
- {
- if (genIsRegCandidateLocal(unspillTree))
- {
- // Reset spilled flag, since we are going to load a local variable from its home location.
- unspillTree->gtFlags &= ~GTF_SPILLED;
-
- GenTreeLclVarCommon* lcl = unspillTree->AsLclVarCommon();
- LclVarDsc* varDsc = &compiler->lvaTable[lcl->gtLclNum];
-
- // Load local variable from its home location.
- // In most cases the tree type will indicate the correct type to use for the load.
- // However, if it is NOT a normalizeOnLoad lclVar (i.e. NOT a small int that always gets
- // widened when loaded into a register), and its size is not the same as genActualType of
- // the type of the lclVar, then we need to change the type of the tree node when loading.
- // This situation happens due to "optimizations" that avoid a cast and
- // simply retype the node when using long type lclVar as an int.
- // While loading the int in that case would work for this use of the lclVar, if it is
- // later used as a long, we will have incorrectly truncated the long.
- // In the normalizeOnLoad case ins_Load will return an appropriate sign- or zero-
- // extending load.
-
- var_types treeType = unspillTree->TypeGet();
- if (treeType != genActualType(varDsc->lvType) && !varTypeIsGC(treeType) && !varDsc->lvNormalizeOnLoad())
- {
- assert(!varTypeIsGC(varDsc));
- var_types spillType = genActualType(varDsc->lvType);
- unspillTree->gtType = spillType;
- inst_RV_TT(ins_Load(spillType, compiler->isSIMDTypeLocalAligned(lcl->gtLclNum)), dstReg, unspillTree);
- unspillTree->gtType = treeType;
- }
- else
- {
- inst_RV_TT(ins_Load(treeType, compiler->isSIMDTypeLocalAligned(lcl->gtLclNum)), dstReg, unspillTree);
- }
-
- unspillTree->SetInReg();
-
- // TODO-Review: We would like to call:
- // genUpdateRegLife(varDsc, /*isBorn*/ true, /*isDying*/ false DEBUGARG(tree));
- // instead of the following code, but this ends up hitting this assert:
- // assert((regSet.rsMaskVars & regMask) == 0);
- // due to issues with LSRA resolution moves.
- // So, just force it for now. This probably indicates a condition that creates a GC hole!
- //
- // Extra note: I think we really want to call something like gcInfo.gcUpdateForRegVarMove,
- // because the variable is not really going live or dead, but that method is somewhat poorly
- // factored because it, in turn, updates rsMaskVars which is part of RegSet not GCInfo.
- // TODO-Cleanup: This code exists in other CodeGen*.cpp files, and should be moved to CodeGenCommon.cpp.
-
- // Don't update the variable's location if we are just re-spilling it again.
-
- if ((unspillTree->gtFlags & GTF_SPILL) == 0)
- {
- genUpdateVarReg(varDsc, tree);
-#ifdef DEBUG
- if (VarSetOps::IsMember(compiler, gcInfo.gcVarPtrSetCur, varDsc->lvVarIndex))
- {
- JITDUMP("\t\t\t\t\t\t\tRemoving V%02u from gcVarPtrSetCur\n", lcl->gtLclNum);
- }
-#endif // DEBUG
- VarSetOps::RemoveElemD(compiler, gcInfo.gcVarPtrSetCur, varDsc->lvVarIndex);
-
-#ifdef DEBUG
- if (compiler->verbose)
- {
- printf("\t\t\t\t\t\t\tV%02u in reg ", lcl->gtLclNum);
- varDsc->PrintVarReg();
- printf(" is becoming live ");
- compiler->printTreeID(unspillTree);
- printf("\n");
- }
-#endif // DEBUG
-
- regSet.AddMaskVars(genGetRegMask(varDsc));
- }
-
- gcInfo.gcMarkRegPtrVal(dstReg, unspillTree->TypeGet());
- }
- else if (unspillTree->IsMultiRegCall())
- {
- GenTreeCall* call = unspillTree->AsCall();
- ReturnTypeDesc* retTypeDesc = call->GetReturnTypeDesc();
- unsigned regCount = retTypeDesc->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 = retTypeDesc->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->TypeGet()), dstReg,
- t->tdTempNum(), 0);
- compiler->tmpRlsTemp(t);
-
- unspillTree->gtFlags &= ~GTF_SPILLED;
- unspillTree->SetInReg();
- gcInfo.gcMarkRegPtrVal(dstReg, unspillTree->TypeGet());
- }
- }
-}
-
-// Do Liveness update for a subnodes that is being consumed by codegen
-// including the logic for reload in case is needed and also takes care
-// of locating the value on the desired register.
-void CodeGen::genConsumeRegAndCopy(GenTree* tree, regNumber needReg)
-{
- if (needReg == REG_NA)
- {
- return;
- }
- regNumber treeReg = genConsumeReg(tree);
- if (treeReg != needReg)
- {
- inst_RV_RV(INS_mov, needReg, treeReg, tree->TypeGet());
- }
-}
-
void CodeGen::genRegCopy(GenTree* treeNode)
{
assert(treeNode->OperGet() == GT_COPY);
@@ -5022,662 +4479,6 @@ void CodeGen::genRegCopy(GenTree* treeNode)
genProduceReg(treeNode);
}
-// Check that registers are consumed in the right order for the current node being generated.
-#ifdef DEBUG
-void CodeGen::genCheckConsumeNode(GenTree* treeNode)
-{
- // GT_PUTARG_REG is consumed out of order.
- if (treeNode->gtSeqNum != 0 && treeNode->OperGet() != GT_PUTARG_REG)
- {
- if (lastConsumedNode != nullptr)
- {
- if (treeNode == lastConsumedNode)
- {
- if (verbose)
- {
- printf("Node was consumed twice:\n ");
- compiler->gtDispTree(treeNode, nullptr, nullptr, true);
- }
- }
- else
- {
- if (verbose && (lastConsumedNode->gtSeqNum > treeNode->gtSeqNum))
- {
- printf("Nodes were consumed out-of-order:\n");
- compiler->gtDispTree(lastConsumedNode, nullptr, nullptr, true);
- compiler->gtDispTree(treeNode, nullptr, nullptr, true);
- }
- // assert(lastConsumedNode->gtSeqNum < treeNode->gtSeqNum);
- }
- }
- lastConsumedNode = treeNode;
- }
-}
-#endif // DEBUG
-
-//--------------------------------------------------------------------
-// genConsumeReg: Do liveness update for a subnode that is being
-// consumed by codegen.
-//
-// Arguments:
-// tree - GenTree node
-//
-// Return Value:
-// Returns the reg number of tree.
-// In case of multi-reg call node returns the first reg number
-// of the multi-reg return.
-regNumber CodeGen::genConsumeReg(GenTree* tree)
-{
- if (tree->OperGet() == GT_COPY)
- {
- genRegCopy(tree);
- }
-
- // Handle the case where we have a lclVar that needs to be copied before use (i.e. because it
- // interferes with one of the other sources (or the target, if it's a "delayed use" register)).
- // TODO-Cleanup: This is a special copyReg case in LSRA - consider eliminating these and
- // always using GT_COPY to make the lclVar location explicit.
- // Note that we have to do this before calling genUpdateLife because otherwise if we spill it
- // the lvRegNum will be set to REG_STK and we will lose track of what register currently holds
- // the lclVar (normally when a lclVar is spilled it is then used from its former register
- // location, which matches the gtRegNum on the node).
- // (Note that it doesn't matter if we call this before or after genUnspillRegIfNeeded
- // because if it's on the stack it will always get reloaded into tree->gtRegNum).
- if (genIsRegCandidateLocal(tree))
- {
- GenTreeLclVarCommon* lcl = tree->AsLclVarCommon();
- LclVarDsc* varDsc = &compiler->lvaTable[lcl->GetLclNum()];
- if (varDsc->lvRegNum != REG_STK && varDsc->lvRegNum != tree->gtRegNum)
- {
- inst_RV_RV(INS_mov, tree->gtRegNum, varDsc->lvRegNum);
- }
- }
-
- genUnspillRegIfNeeded(tree);
-
- // genUpdateLife() will also spill local var if marked as GTF_SPILL by calling CodeGen::genSpillVar
- genUpdateLife(tree);
-
- assert(tree->gtHasReg());
-
- // there are three cases where consuming a reg means clearing the bit in the live mask
- // 1. it was not produced by a local
- // 2. it was produced by a local that is going dead
- // 3. it was produced by a local that does not live in that reg (like one allocated on the stack)
-
- if (genIsRegCandidateLocal(tree))
- {
- GenTreeLclVarCommon* lcl = tree->AsLclVarCommon();
- LclVarDsc* varDsc = &compiler->lvaTable[lcl->GetLclNum()];
- assert(varDsc->lvLRACandidate);
-
- if ((tree->gtFlags & GTF_VAR_DEATH) != 0)
- {
- gcInfo.gcMarkRegSetNpt(genRegMask(varDsc->lvRegNum));
- }
- else if (varDsc->lvRegNum == REG_STK)
- {
- // We have loaded this into a register only temporarily
- gcInfo.gcMarkRegSetNpt(genRegMask(tree->gtRegNum));
- }
- }
- else
- {
- gcInfo.gcMarkRegSetNpt(tree->gtGetRegMask());
- }
-
- genCheckConsumeNode(tree);
- return tree->gtRegNum;
-}
-
-// Do liveness update for an address tree: one of GT_LEA, GT_LCL_VAR, or GT_CNS_INT (for call indirect).
-void CodeGen::genConsumeAddress(GenTree* addr)
-{
- if (!addr->isContained())
- {
- genConsumeReg(addr);
- }
- else if (addr->OperGet() == GT_LEA)
- {
- genConsumeAddrMode(addr->AsAddrMode());
- }
-}
-
-// do liveness update for a subnode that is being consumed by codegen
-void CodeGen::genConsumeAddrMode(GenTreeAddrMode* addr)
-{
- genConsumeOperands(addr);
-}
-
-void CodeGen::genConsumeRegs(GenTree* tree)
-{
-#if !defined(_TARGET_64BIT_)
- if (tree->OperGet() == GT_LONG)
- {
- genConsumeRegs(tree->gtGetOp1());
- genConsumeRegs(tree->gtGetOp2());
- return;
- }
-#endif // !defined(_TARGET_64BIT_)
-
- if (tree->isContained())
- {
- if (tree->isContainedSpillTemp())
- {
- // spill temps are un-tracked and hence no need to update life
- }
- else if (tree->isIndir())
- {
- genConsumeAddress(tree->AsIndir()->Addr());
- }
- else if (tree->OperGet() == GT_AND)
- {
- // This is the special contained GT_AND that we created in Lowering::LowerCmp()
- // Now we need to consume the operands of the GT_AND node.
- genConsumeOperands(tree->AsOp());
- }
- else if (tree->OperGet() == GT_LCL_VAR)
- {
- // A contained lcl var must be living on stack and marked as reg optional.
- unsigned varNum = tree->AsLclVarCommon()->GetLclNum();
- LclVarDsc* varDsc = compiler->lvaTable + varNum;
-
- noway_assert(varDsc->lvRegNum == REG_STK);
- noway_assert(tree->IsRegOptional());
-
- // Update the life of reg optional lcl var.
- genUpdateLife(tree);
- }
- else
- {
- assert(tree->OperIsLeaf());
- }
- }
- else
- {
- genConsumeReg(tree);
- }
-}
-
-//------------------------------------------------------------------------
-// genConsumeOperands: Do liveness update for the operands of a unary or binary tree
-//
-// Arguments:
-// tree - the GenTreeOp whose operands will have their liveness updated.
-//
-// Return Value:
-// None.
-//
-// Notes:
-// Note that this logic is localized here because we must do the liveness update in
-// the correct execution order. This is important because we may have two operands
-// that involve the same lclVar, and if one is marked "lastUse" we must handle it
-// after the first.
-
-void CodeGen::genConsumeOperands(GenTreeOp* tree)
-{
- GenTree* firstOp = tree->gtOp1;
- GenTree* secondOp = tree->gtOp2;
- if ((tree->gtFlags & GTF_REVERSE_OPS) != 0)
- {
- assert(secondOp != nullptr);
- firstOp = secondOp;
- secondOp = tree->gtOp1;
- }
- if (firstOp != nullptr)
- {
- genConsumeRegs(firstOp);
- }
- if (secondOp != nullptr)
- {
- genConsumeRegs(secondOp);
- }
-}
-
-#ifdef FEATURE_UNIX_AMD64_STRUCT_PASSING
-//------------------------------------------------------------------------
-// genConsumePutStructArgStk: Do liveness update for the operands of a PutArgStk node.
-// Also loads in the right register the addresses of the
-// src/dst for rep mov operation.
-//
-// Arguments:
-// putArgNode - the PUTARG_STK tree.
-// dstReg - the dstReg for the rep move operation.
-// srcReg - the srcReg for the rep move operation.
-// sizeReg - the sizeReg for the rep move operation.
-// baseVarNum - the varnum for the local used for placing the "by-value" args on the stack.
-//
-// Return Value:
-// None.
-//
-// Note: sizeReg can be REG_NA when this function is used to consume the dstReg and srcReg
-// for copying on the stack a struct with references.
-// The source address/offset is determined from the address on the GT_OBJ node, while
-// the destination address is the address contained in 'baseVarNum' plus the offset
-// provided in the 'putArgNode'.
-
-void CodeGen::genConsumePutStructArgStk(
- GenTreePutArgStk* putArgNode, regNumber dstReg, regNumber srcReg, regNumber sizeReg, unsigned baseVarNum)
-{
- assert(varTypeIsStruct(putArgNode));
- assert(baseVarNum != BAD_VAR_NUM);
-
- // The putArgNode children are always contained. We should not consume any registers.
- assert(putArgNode->gtGetOp1()->isContained());
-
- GenTree* dstAddr = putArgNode;
-
- // Get the source address.
- GenTree* src = putArgNode->gtGetOp1();
- assert((src->gtOper == GT_OBJ) || ((src->gtOper == GT_IND && varTypeIsSIMD(src))));
- GenTree* srcAddr = src->gtGetOp1();
-
- size_t size = putArgNode->getArgSize();
-
- assert(dstReg != REG_NA);
- assert(srcReg != REG_NA);
-
- // Consume the registers only if they are not contained or set to REG_NA.
- if (srcAddr->gtRegNum != REG_NA)
- {
- genConsumeReg(srcAddr);
- }
-
- // If the op1 is already in the dstReg - nothing to do.
- // Otherwise load the op1 (GT_ADDR) into the dstReg to copy the struct on the stack by value.
- if (dstAddr->gtRegNum != dstReg)
- {
- // Generate LEA instruction to load the stack of the outgoing var + SlotNum offset (or the incoming arg area
- // for tail calls) in RDI.
- // Destination is always local (on the stack) - use EA_PTRSIZE.
- getEmitter()->emitIns_R_S(INS_lea, EA_PTRSIZE, dstReg, baseVarNum, putArgNode->getArgOffset());
- }
-
- if (srcAddr->gtRegNum != srcReg)
- {
- if (srcAddr->OperIsLocalAddr())
- {
- // The OperLocalAddr is always contained.
- assert(srcAddr->isContained());
- GenTreeLclVarCommon* lclNode = srcAddr->AsLclVarCommon();
-
- // Generate LEA instruction to load the LclVar address in RSI.
- // Source is known to be on the stack. Use EA_PTRSIZE.
- unsigned int offset = 0;
- if (srcAddr->OperGet() == GT_LCL_FLD_ADDR)
- {
- offset = srcAddr->AsLclFld()->gtLclOffs;
- }
- getEmitter()->emitIns_R_S(INS_lea, EA_PTRSIZE, srcReg, lclNode->gtLclNum, offset);
- }
- else
- {
- assert(srcAddr->gtRegNum != REG_NA);
- // Source is not known to be on the stack. Use EA_BYREF.
- getEmitter()->emitIns_R_R(INS_mov, EA_BYREF, srcReg, srcAddr->gtRegNum);
- }
- }
-
- if (sizeReg != REG_NA)
- {
- inst_RV_IV(INS_mov, sizeReg, size, EA_8BYTE);
- }
-}
-#endif // FEATURE_UNIX_AMD64_STRUCT_PASSING
-
-//------------------------------------------------------------------------
-// genConsumeBlockSize: Ensure that the block size is in the given register
-//
-// Arguments:
-// blkNode - The block node
-// sizeReg - The register into which the block's size should go
-//
-
-void CodeGen::genConsumeBlockSize(GenTreeBlk* blkNode, regNumber sizeReg)
-{
- if (sizeReg != REG_NA)
- {
- unsigned blockSize = blkNode->Size();
- if (blockSize != 0)
- {
- assert(blkNode->gtRsvdRegs == genRegMask(sizeReg));
- genSetRegToIcon(sizeReg, blockSize);
- }
- else
- {
- noway_assert(blkNode->gtOper == GT_STORE_DYN_BLK);
- genConsumeReg(blkNode->AsDynBlk()->gtDynamicSize);
- }
- }
-}
-
-//------------------------------------------------------------------------
-// genConsumeBlockDst: Ensure that the block destination address is in its
-// allocated register.
-// Arguments:
-// blkNode - The block node
-//
-
-void CodeGen::genConsumeBlockDst(GenTreeBlk* blkNode)
-{
- GenTree* dstAddr = blkNode->Addr();
- genConsumeReg(dstAddr);
-}
-
-//------------------------------------------------------------------------
-// genConsumeBlockSrc: Ensure that the block source address is in its
-// allocated register if it is non-local.
-// Arguments:
-// blkNode - The block node
-//
-// Return Value:
-// Returns the source address node, if it is non-local,
-// and nullptr otherwise.
-
-GenTree* CodeGen::genConsumeBlockSrc(GenTreeBlk* blkNode)
-{
- GenTree* src = blkNode->Data();
- if (blkNode->OperIsCopyBlkOp())
- {
- // For a CopyBlk we need the address of the source.
- if (src->OperGet() == GT_IND)
- {
- src = src->gtOp.gtOp1;
- }
- else
- {
- // This must be a local.
- // For this case, there is no source address register, as it is a
- // stack-based address.
- assert(src->OperIsLocal());
- return nullptr;
- }
- }
- genConsumeReg(src);
- return src;
-}
-
-//------------------------------------------------------------------------
-// genConsumeBlockOp: Ensure that the block's operands are enregistered
-// as needed.
-// Arguments:
-// blkNode - The block node
-//
-// Notes:
-// This ensures that the operands are consumed in the proper order to
-// obey liveness modeling.
-
-void CodeGen::genConsumeBlockOp(GenTreeBlk* blkNode, regNumber dstReg, regNumber srcReg, regNumber sizeReg)
-{
- // We have to consume the registers, and perform any copies, in the actual execution order.
- // The nominal order is: dst, src, size. However this may have been changed
- // with reverse flags on the blkNode and the setting of gtEvalSizeFirst in the case of a dynamic
- // block size.
- // Note that the register allocator ensures that the registers ON THE NODES will not interfere
- // with one another if consumed (i.e. reloaded or moved to their ASSIGNED reg) in execution order.
- // Further, it ensures that they will not interfere with one another if they are then copied
- // to the REQUIRED register (if a fixed register requirement) in execution order. This requires,
- // then, that we first consume all the operands, then do any necessary moves.
-
- GenTree* dstAddr = blkNode->Addr();
- GenTree* src = nullptr;
- unsigned blockSize = blkNode->Size();
- GenTree* size = nullptr;
- bool evalSizeFirst = true;
-
- if (blkNode->OperGet() == GT_STORE_DYN_BLK)
- {
- evalSizeFirst = blkNode->AsDynBlk()->gtEvalSizeFirst;
- size = blkNode->AsDynBlk()->gtDynamicSize;
- }
-
- // First, consusme all the sources in order
- if (evalSizeFirst)
- {
- genConsumeBlockSize(blkNode, sizeReg);
- }
- if (blkNode->IsReverseOp())
- {
- src = genConsumeBlockSrc(blkNode);
- genConsumeBlockDst(blkNode);
- }
- else
- {
- genConsumeBlockDst(blkNode);
- src = genConsumeBlockSrc(blkNode);
- }
- if (!evalSizeFirst)
- {
- genConsumeBlockSize(blkNode, sizeReg);
- }
- // Next, perform any necessary moves.
- if (evalSizeFirst && (size != nullptr) && (size->gtRegNum != sizeReg))
- {
- inst_RV_RV(INS_mov, sizeReg, size->gtRegNum, size->TypeGet());
- }
- if (blkNode->IsReverseOp())
- {
- if ((src != nullptr) && (src->gtRegNum != srcReg))
- {
- inst_RV_RV(INS_mov, srcReg, src->gtRegNum, src->TypeGet());
- }
- if (dstAddr->gtRegNum != dstReg)
- {
- inst_RV_RV(INS_mov, dstReg, dstAddr->gtRegNum, dstAddr->TypeGet());
- }
- }
- else
- {
- if (dstAddr->gtRegNum != dstReg)
- {
- inst_RV_RV(INS_mov, dstReg, dstAddr->gtRegNum, dstAddr->TypeGet());
- }
- if ((src != nullptr) && (src->gtRegNum != srcReg))
- {
- inst_RV_RV(INS_mov, srcReg, src->gtRegNum, src->TypeGet());
- }
- }
- if (!evalSizeFirst && size != nullptr && (size->gtRegNum != sizeReg))
- {
- inst_RV_RV(INS_mov, sizeReg, size->gtRegNum, size->TypeGet());
- }
-}
-
-//-------------------------------------------------------------------------
-// genProduceReg: do liveness update for register produced by the current
-// node in codegen.
-//
-// Arguments:
-// tree - Gentree node
-//
-// Return Value:
-// None.
-void CodeGen::genProduceReg(GenTree* tree)
-{
- if (tree->gtFlags & GTF_SPILL)
- {
- // Code for GT_COPY node gets generated as part of consuming regs by its parent.
- // A GT_COPY node in turn produces reg result and it should never be marked to
- // spill.
- //
- // Similarly GT_RELOAD node gets generated as part of consuming regs by its
- // parent and should never be marked for spilling.
- noway_assert(!tree->IsCopyOrReload());
-
- if (genIsRegCandidateLocal(tree))
- {
- // Store local variable to its home location.
- tree->gtFlags &= ~GTF_REG_VAL;
- // Ensure that lclVar stores are typed correctly.
- unsigned varNum = tree->gtLclVarCommon.gtLclNum;
- assert(!compiler->lvaTable[varNum].lvNormalizeOnStore() ||
- (tree->TypeGet() == genActualType(compiler->lvaTable[varNum].TypeGet())));
- inst_TT_RV(ins_Store(tree->gtType, compiler->isSIMDTypeLocalAligned(varNum)), tree, tree->gtRegNum);
- }
- else
- {
- // In case of multi-reg call node, spill flag on call node
- // indicates that one or more of its allocated regs need to
- // be spilled. Call node needs to be further queried to
- // know which of its result regs needs to be spilled.
- if (tree->IsMultiRegCall())
- {
- GenTreeCall* call = tree->AsCall();
- ReturnTypeDesc* retTypeDesc = call->GetReturnTypeDesc();
- unsigned regCount = retTypeDesc->GetReturnRegCount();
-
- for (unsigned i = 0; i < regCount; ++i)
- {
- unsigned flags = call->GetRegSpillFlagByIdx(i);
- if ((flags & GTF_SPILL) != 0)
- {
- regNumber reg = call->GetRegNumByIdx(i);
- call->SetInReg();
- regSet.rsSpillTree(reg, call, i);
- gcInfo.gcMarkRegSetNpt(genRegMask(reg));
- }
- }
- }
- else
- {
- tree->SetInReg();
- regSet.rsSpillTree(tree->gtRegNum, tree);
- gcInfo.gcMarkRegSetNpt(genRegMask(tree->gtRegNum));
- }
-
- tree->gtFlags |= GTF_SPILLED;
- tree->gtFlags &= ~GTF_SPILL;
-
- return;
- }
- }
-
- genUpdateLife(tree);
-
- // If we've produced a register, mark it as a pointer, as needed.
- if (tree->gtHasReg())
- {
- // We only mark the register in the following cases:
- // 1. It is not a register candidate local. In this case, we're producing a
- // register from a local, but the local is not a register candidate. Thus,
- // we must be loading it as a temp register, and any "last use" flag on
- // the register wouldn't be relevant.
- // 2. The register candidate local is going dead. There's no point to mark
- // the register as live, with a GC pointer, if the variable is dead.
- if (!genIsRegCandidateLocal(tree) || ((tree->gtFlags & GTF_VAR_DEATH) == 0))
- {
- // Multi-reg call node will produce more than one register result.
- // Mark all the regs produced by call node.
- if (tree->IsMultiRegCall())
- {
- GenTreeCall* call = tree->AsCall();
- ReturnTypeDesc* retTypeDesc = call->GetReturnTypeDesc();
- unsigned regCount = retTypeDesc->GetReturnRegCount();
-
- for (unsigned i = 0; i < regCount; ++i)
- {
- regNumber reg = call->GetRegNumByIdx(i);
- var_types type = retTypeDesc->GetReturnRegType(i);
- gcInfo.gcMarkRegPtrVal(reg, type);
- }
- }
- else if (tree->IsCopyOrReloadOfMultiRegCall())
- {
- // we should never see reload of multi-reg call here
- // because GT_RELOAD gets generated in reg consuming path.
- noway_assert(tree->OperGet() == GT_COPY);
-
- // A multi-reg GT_COPY node produces those regs to which
- // copy has taken place.
- GenTreeCopyOrReload* copy = tree->AsCopyOrReload();
- GenTreeCall* call = copy->gtGetOp1()->AsCall();
- ReturnTypeDesc* retTypeDesc = call->GetReturnTypeDesc();
- unsigned regCount = retTypeDesc->GetReturnRegCount();
-
- for (unsigned i = 0; i < regCount; ++i)
- {
- var_types type = retTypeDesc->GetReturnRegType(i);
- regNumber fromReg = call->GetRegNumByIdx(i);
- regNumber toReg = copy->GetRegNumByIdx(i);
-
- if (toReg != REG_NA)
- {
- gcInfo.gcMarkRegPtrVal(toReg, type);
- }
- }
- }
- else
- {
- gcInfo.gcMarkRegPtrVal(tree->gtRegNum, tree->TypeGet());
- }
- }
- }
- tree->SetInReg();
-}
-
-// transfer gc/byref status of src reg to dst reg
-void CodeGen::genTransferRegGCState(regNumber dst, regNumber src)
-{
- regMaskTP srcMask = genRegMask(src);
- regMaskTP dstMask = genRegMask(dst);
-
- if (gcInfo.gcRegGCrefSetCur & srcMask)
- {
- gcInfo.gcMarkRegSetGCref(dstMask);
- }
- else if (gcInfo.gcRegByrefSetCur & srcMask)
- {
- gcInfo.gcMarkRegSetByref(dstMask);
- }
- else
- {
- gcInfo.gcMarkRegSetNpt(dstMask);
- }
-}
-
-// generates an ip-relative call or indirect call via reg ('call reg')
-// 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 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;
-#endif // !defined(_TARGET_X86_)
- getEmitter()->emitIns_Call(emitter::EmitCallType(callType), methHnd, INDEBUG_LDISASM_COMMA(sigInfo) addr, argSize,
- retSize MULTIREG_HAS_SECOND_GC_RET_ONLY_ARG(secondRetSize), gcInfo.gcVarPtrSetCur,
- gcInfo.gcRegGCrefSetCur, gcInfo.gcRegByrefSetCur, ilOffset, base, REG_NA, 0, 0, isJump,
- emitter::emitNoGChelper(compiler->eeGetHelperNum(methHnd)));
-}
-
-// 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 MULTIREG_HAS_SECOND_GC_RET_ONLY_ARG(emitAttr secondRetSize),
- IL_OFFSETX ilOffset)
-{
-#if !defined(_TARGET_X86_)
- ssize_t argSize = 0;
-#endif // !defined(_TARGET_X86_)
- genConsumeAddress(indir->Addr());
-
- getEmitter()->emitIns_Call(emitter::EmitCallType(callType), methHnd, INDEBUG_LDISASM_COMMA(sigInfo) nullptr,
- argSize, retSize MULTIREG_HAS_SECOND_GC_RET_ONLY_ARG(secondRetSize),
- gcInfo.gcVarPtrSetCur, gcInfo.gcRegGCrefSetCur, gcInfo.gcRegByrefSetCur, ilOffset,
- indir->Base() ? indir->Base()->gtRegNum : REG_NA,
- indir->Index() ? indir->Index()->gtRegNum : REG_NA, indir->Scale(), indir->Offset());
-}
-
//------------------------------------------------------------------------
// genStoreInd: Generate code for a GT_STOREIND node.
//
@@ -5724,16 +4525,10 @@ void CodeGen::genStoreInd(GenTreePtr node)
noway_assert(data->gtRegNum != REG_ARG_0);
// addr goes in REG_ARG_0
- if (addr->gtRegNum != REG_ARG_0)
- {
- inst_RV_RV(INS_mov, REG_ARG_0, addr->gtRegNum, addr->TypeGet());
- }
+ genCopyRegIfNeeded(addr, REG_ARG_0);
// data goes in REG_ARG_1
- if (data->gtRegNum != REG_ARG_1)
- {
- inst_RV_RV(INS_mov, REG_ARG_1, data->gtRegNum, data->TypeGet());
- }
+ genCopyRegIfNeeded(data, REG_ARG_1);
genGCWriteBarrier(storeInd, writeBarrierForm);
}
@@ -5821,6 +4616,23 @@ void CodeGen::genStoreInd(GenTreePtr node)
assert(rmwSrc == data->gtGetOp2());
genCodeForShiftRMW(storeInd);
}
+ else if (!compiler->opts.compDbgCode && data->OperGet() == GT_ADD &&
+ (rmwSrc->IsIntegralConst(1) || rmwSrc->IsIntegralConst(-1)))
+ {
+ // Generate "inc/dec [mem]" instead of "add/sub [mem], 1".
+ //
+ // Notes:
+ // 1) Global morph transforms GT_SUB(x, +/-1) into GT_ADD(x, -/+1).
+ // 2) TODO-AMD64: Debugger routine NativeWalker::Decode() runs into
+ // an assert while decoding ModR/M byte of "inc dword ptr [rax]".
+ // It is not clear whether Decode() can handle all possible
+ // addr modes with inc/dec. For this reason, inc/dec [mem]
+ // is not generated while generating debuggable code. Update
+ // the above if condition once Decode() routine is fixed.
+ assert(rmwSrc->isContainedIntOrIImmed());
+ instruction ins = rmwSrc->IsIntegralConst(1) ? INS_inc : INS_dec;
+ getEmitter()->emitInsRMW(ins, emitTypeSize(storeInd), storeInd);
+ }
else
{
// generate code for remaining binary RMW memory ops like add/sub/and/or/xor
@@ -5905,10 +4717,7 @@ bool CodeGen::genEmitOptimizedGCWriteBarrier(GCInfo::WriteBarrierForm writeBarri
// call write_barrier_helper_reg
// addr goes in REG_ARG_0
- if (addr->gtRegNum != REG_WRITE_BARRIER) // REVIEW: can it ever not already by in this register?
- {
- inst_RV_RV(INS_mov, REG_WRITE_BARRIER, addr->gtRegNum, addr->TypeGet());
- }
+ genCopyRegIfNeeded(addr, REG_WRITE_BARRIER);
unsigned tgtAnywhere = 0;
if (writeBarrierForm != GCInfo::WBF_BarrierUnchecked)
@@ -5943,10 +4752,28 @@ void CodeGen::genCallInstruction(GenTreePtr node)
// all virtuals should have been expanded into a control expression
assert(!call->IsVirtual() || call->gtControlExpr || call->gtCallAddr);
+ // Insert a GS check if necessary
+ if (call->IsTailCallViaHelper())
+ {
+ if (compiler->getNeedsGSSecurityCookie())
+ {
+#if FEATURE_FIXED_OUT_ARGS
+ // If either of the conditions below is true, we will need a temporary register in order to perform the GS
+ // cookie check. When FEATURE_FIXED_OUT_ARGS is disabled, we save and restore the temporary register using
+ // push/pop. When FEATURE_FIXED_OUT_ARGS is enabled, however, we need an alternative solution. For now,
+ // though, the tail prefix is ignored on all platforms that use fixed out args, so we should never hit this
+ // case.
+ assert(compiler->gsGlobalSecurityCookieAddr == nullptr);
+ assert((int)compiler->gsGlobalSecurityCookieVal == (ssize_t)compiler->gsGlobalSecurityCookieVal);
+#endif
+ genEmitGSCookieCheck(true);
+ }
+ }
+
// Consume all the arg regs
for (GenTreePtr list = call->gtCallLateArgs; list; list = list->MoveNext())
{
- assert(list->IsList());
+ assert(list->OperIsList());
GenTreePtr argNode = list->Current();
@@ -5960,13 +4787,13 @@ void CodeGen::genCallInstruction(GenTreePtr node)
#ifdef FEATURE_UNIX_AMD64_STRUCT_PASSING
// Deal with multi register passed struct args.
- if (argNode->OperGet() == GT_LIST)
+ if (argNode->OperGet() == GT_FIELD_LIST)
{
- GenTreeArgList* argListPtr = argNode->AsArgList();
- unsigned iterationNum = 0;
- for (; argListPtr != nullptr; argListPtr = argListPtr->Rest(), iterationNum++)
+ GenTreeFieldList* fieldListPtr = argNode->AsFieldList();
+ unsigned iterationNum = 0;
+ for (; fieldListPtr != nullptr; fieldListPtr = fieldListPtr->Rest(), iterationNum++)
{
- GenTreePtr putArgRegNode = argListPtr->gtOp.gtOp1;
+ GenTreePtr putArgRegNode = fieldListPtr->gtOp.gtOp1;
assert(putArgRegNode->gtOper == GT_PUTARG_REG);
regNumber argReg = REG_NA;
@@ -6036,20 +4863,34 @@ void CodeGen::genCallInstruction(GenTreePtr node)
{
assert((arg->gtGetOp1()->OperGet() == GT_PUTARG_STK) && (arg->gtGetOp2()->OperGet() == GT_PUTARG_STK));
}
+ if ((arg->OperGet() == GT_PUTARG_STK) && (arg->gtGetOp1()->OperGet() == GT_FIELD_LIST))
+ {
+ fgArgTabEntryPtr curArgTabEntry = compiler->gtArgEntryByNode(call, arg);
+ assert(curArgTabEntry);
+ stackArgBytes += curArgTabEntry->numSlots * TARGET_POINTER_SIZE;
+ }
+ else
#endif // defined(_TARGET_X86_)
-#ifdef FEATURE_UNIX_AMD64_STRUCT_PASSING
- if (genActualType(arg->TypeGet()) == TYP_STRUCT)
+#ifdef FEATURE_PUT_STRUCT_ARG_STK
+ if (genActualType(arg->TypeGet()) == TYP_STRUCT)
{
assert(arg->OperGet() == GT_PUTARG_STK);
- GenTreeObj* obj = arg->gtGetOp1()->AsObj();
- stackArgBytes = compiler->info.compCompHnd->getClassSize(obj->gtClass);
+ GenTreeObj* obj = arg->gtGetOp1()->AsObj();
+ unsigned argBytes = (unsigned)roundUp(obj->gtBlkSize, TARGET_POINTER_SIZE);
+#ifdef DEBUG
+ fgArgTabEntryPtr curArgTabEntry = compiler->gtArgEntryByNode(call, arg);
+ assert((curArgTabEntry->numSlots * TARGET_POINTER_SIZE) == argBytes);
+#endif // DEBUG
+ stackArgBytes += argBytes;
}
else
-#endif // FEATURE_UNIX_AMD64_STRUCT_PASSING
+ {
+#endif // FEATURE_PUT_STRUCT_ARG_STK
stackArgBytes += genTypeSize(genActualType(arg->TypeGet()));
+ }
}
args = args->gtOp.gtOp2;
}
@@ -6098,10 +4939,7 @@ void CodeGen::genCallInstruction(GenTreePtr node)
assert(target != nullptr);
genConsumeReg(target);
- if (target->gtRegNum != REG_RAX)
- {
- inst_RV_RV(INS_mov, REG_RAX, target->gtRegNum);
- }
+ genCopyRegIfNeeded(target, REG_RAX);
return;
}
@@ -6141,7 +4979,6 @@ void CodeGen::genCallInstruction(GenTreePtr node)
bool fPossibleSyncHelperCall = false;
CorInfoHelpFunc helperNum = CORINFO_HELP_UNDEF;
-#ifdef DEBUGGING_SUPPORT
// We need to propagate the IL offset information to the call instruction, so we can emit
// an IL to native mapping record for the call, to support managed return value debugging.
// We don't want tail call helper calls that were converted from normal calls to get a record,
@@ -6150,7 +4987,6 @@ void CodeGen::genCallInstruction(GenTreePtr node)
{
(void)compiler->genCallSite2ILOffsetMap->Lookup(call, &ilOffset);
}
-#endif // DEBUGGING_SUPPORT
#if defined(_TARGET_X86_)
// If the callee pops the arguments, we pass a positive value as the argSize, and the emitter will
@@ -6167,7 +5003,38 @@ void CodeGen::genCallInstruction(GenTreePtr node)
if (target != nullptr)
{
- if (target->isContainedIndir())
+#ifdef _TARGET_X86_
+ if (call->IsVirtualStub() && (call->gtCallType == CT_INDIRECT))
+ {
+ // On x86, we need to generate a very specific pattern for indirect VSD calls:
+ //
+ // 3-byte nop
+ // call dword ptr [eax]
+ //
+ // Where EAX is also used as an argument to the stub dispatch helper. Make
+ // sure that the call target address is computed into EAX in this case.
+
+ assert(REG_VIRTUAL_STUB_PARAM == REG_VIRTUAL_STUB_TARGET);
+
+ assert(target->isContainedIndir());
+ assert(target->OperGet() == GT_IND);
+
+ GenTree* addr = target->AsIndir()->Addr();
+ assert(!addr->isContained());
+
+ genConsumeReg(addr);
+ genCopyRegIfNeeded(addr, REG_VIRTUAL_STUB_TARGET);
+
+ getEmitter()->emitIns_Nop(3);
+ getEmitter()->emitIns_Call(emitter::EmitCallType(emitter::EC_INDIR_ARD), methHnd,
+ INDEBUG_LDISASM_COMMA(sigInfo) nullptr, argSizeForEmitter,
+ retSize MULTIREG_HAS_SECOND_GC_RET_ONLY_ARG(secondRetSize),
+ gcInfo.gcVarPtrSetCur, gcInfo.gcRegGCrefSetCur, gcInfo.gcRegByrefSetCur,
+ ilOffset, REG_VIRTUAL_STUB_TARGET, REG_NA, 1, 0);
+ }
+ else
+#endif
+ if (target->isContainedIndir())
{
if (target->AsIndir()->HasBase() && target->AsIndir()->Base()->isContainedIntOrIImmed())
{
@@ -6977,8 +5844,6 @@ void CodeGen::genCompareLong(GenTreePtr treeNode)
genConsumeOperands(tree);
- assert(targetReg != REG_NA);
-
GenTreePtr loOp1 = op1->gtGetOp1();
GenTreePtr hiOp1 = op1->gtGetOp2();
GenTreePtr loOp2 = op2->gtGetOp1();
@@ -6992,6 +5857,12 @@ void CodeGen::genCompareLong(GenTreePtr treeNode)
// Emit the compare instruction
getEmitter()->emitInsBinary(ins, cmpAttr, hiOp1, hiOp2);
+ // If the result is not being materialized in a register, we're done.
+ if (targetReg == REG_NA)
+ {
+ return;
+ }
+
// Generate the first jump for the high compare
CompareKind compareKind = ((tree->gtFlags & GTF_UNSIGNED) != 0) ? CK_UNSIGNED : CK_SIGNED;
@@ -7015,10 +5886,6 @@ void CodeGen::genCompareLong(GenTreePtr treeNode)
emitJumpKind jumpKindLo = genJumpKindForOper(tree->gtOper, CK_UNSIGNED);
inst_SET(jumpKindLo, targetReg);
- // Set the higher bytes to 0
- inst_RV_RV(ins_Move_Extend(TYP_UBYTE, true), targetReg, targetReg, TYP_UBYTE, emitTypeSize(TYP_UBYTE));
- genProduceReg(tree);
-
inst_JMP(EJ_jmp, labelFinal);
// Define the label for hi jump target here. If we have jumped here, we want to set
@@ -7027,11 +5894,10 @@ void CodeGen::genCompareLong(GenTreePtr treeNode)
genDefineTempLabel(labelHi);
inst_SET(genJumpKindForOper(tree->gtOper, compareKind), targetReg);
+ genDefineTempLabel(labelFinal);
// Set the higher bytes to 0
inst_RV_RV(ins_Move_Extend(TYP_UBYTE, true), targetReg, targetReg, TYP_UBYTE, emitTypeSize(TYP_UBYTE));
genProduceReg(tree);
-
- genDefineTempLabel(labelFinal);
}
else
{
@@ -7062,152 +5928,6 @@ void CodeGen::genCompareLong(GenTreePtr treeNode)
genProduceReg(tree);
}
}
-
-//------------------------------------------------------------------------
-// genJTrueLong: Generate code for comparing two longs on x86 for the case where the result
-// is not manifested in a register.
-//
-// Arguments:
-// treeNode - the compare tree
-//
-// Return Value:
-// None.
-// Comments:
-// For long compares, we need to compare the high parts of operands first, then the low parts.
-// We only have to do the low compare if the high parts of the operands are equal.
-//
-// In the case where the result of a rel-op is not realized in a register, we generate:
-//
-// Opcode x86 equivalent Comment
-// ------ -------------- -------
-//
-// GT_LT; unsigned cmp hiOp1,hiOp2
-// jb trueLabel
-// ja falseLabel
-// cmp loOp1,loOp2
-// jb trueLabel
-// falseLabel:
-//
-// GT_LE; unsigned cmp hiOp1,hiOp2
-// jb trueLabel
-// ja falseLabel
-// cmp loOp1,loOp2
-// jbe trueLabel
-// falseLabel:
-//
-// GT_GT; unsigned cmp hiOp1,hiOp2
-// ja trueLabel
-// jb falseLabel
-// cmp loOp1,loOp2
-// ja trueLabel
-// falseLabel:
-//
-// GT_GE; unsigned cmp hiOp1,hiOp2
-// ja trueLabel
-// jb falseLabel
-// cmp loOp1,loOp2
-// jae trueLabel
-// falseLabel:
-//
-// GT_LT; signed cmp hiOp1,hiOp2
-// jl trueLabel
-// jg falseLabel
-// cmp loOp1,loOp2
-// jb trueLabel
-// falseLabel:
-//
-// GT_LE; signed cmp hiOp1,hiOp2
-// jl trueLabel
-// jg falseLabel
-// cmp loOp1,loOp2
-// jbe trueLabel
-// falseLabel:
-//
-// GT_GT; signed cmp hiOp1,hiOp2
-// jg trueLabel
-// jl falseLabel
-// cmp loOp1,loOp2
-// ja trueLabel
-// falseLabel:
-//
-// GT_GE; signed cmp hiOp1,hiOp2
-// jg trueLabel
-// jl falseLabel
-// cmp loOp1,loOp2
-// jae trueLabel
-// falseLabel:
-//
-// GT_EQ; cmp hiOp1,hiOp2
-// jne falseLabel
-// cmp loOp1,loOp2
-// je trueLabel
-// falseLabel:
-//
-// GT_NE; cmp hiOp1,hiOp2
-// jne labelTrue
-// cmp loOp1,loOp2
-// jne trueLabel
-// falseLabel:
-//
-// TODO-X86-CQ: Check if hi or lo parts of op2 are 0 and change the compare to a test.
-void CodeGen::genJTrueLong(GenTreePtr treeNode)
-{
- assert(treeNode->OperIsCompare());
-
- GenTreeOp* tree = treeNode->AsOp();
- GenTreePtr op1 = tree->gtOp1;
- GenTreePtr op2 = tree->gtOp2;
-
- assert(varTypeIsLong(op1->TypeGet()));
- assert(varTypeIsLong(op2->TypeGet()));
-
- regNumber targetReg = treeNode->gtRegNum;
-
- assert(targetReg == REG_NA);
-
- GenTreePtr loOp1 = op1->gtGetOp1();
- GenTreePtr hiOp1 = op1->gtGetOp2();
- GenTreePtr loOp2 = op2->gtGetOp1();
- GenTreePtr hiOp2 = op2->gtGetOp2();
-
- // Emit the compare instruction
- getEmitter()->emitInsBinary(INS_cmp, EA_4BYTE, hiOp1, hiOp2);
-
- // Generate the first jump for the high compare
- CompareKind compareKind = ((tree->gtFlags & GTF_UNSIGNED) != 0) ? CK_UNSIGNED : CK_SIGNED;
-
- // TODO-X86-CQ: If the next block is a BBJ_ALWAYS, we can set falseLabel = compiler->compCurBB->bbNext->bbJumpDest.
- BasicBlock* falseLabel = genCreateTempLabel();
-
- emitJumpKind jumpKindHi[2];
-
- // Generate the jumps for the high compare
- genJumpKindsForTreeLongHi(tree, jumpKindHi);
-
- BasicBlock* trueLabel = compiler->compCurBB->bbJumpDest;
-
- if (jumpKindHi[0] != EJ_NONE)
- {
- inst_JMP(jumpKindHi[0], trueLabel);
- }
-
- if (jumpKindHi[1] != EJ_NONE)
- {
- inst_JMP(jumpKindHi[1], falseLabel);
- }
-
- // The low jump must be unsigned
- emitJumpKind jumpKindLo = genJumpKindForOper(tree->gtOper, CK_UNSIGNED);
-
- // Emit the comparison and the jump to the trueLabel
- getEmitter()->emitInsBinary(INS_cmp, EA_4BYTE, loOp1, loOp2);
-
- inst_JMP(jumpKindLo, trueLabel);
-
- // Generate falseLabel, which is the false path. We will jump here if the high compare is false
- // or fall through if the low compare is false.
- genDefineTempLabel(falseLabel);
-}
#endif //! defined(_TARGET_64BIT_)
//------------------------------------------------------------------------
@@ -7339,19 +6059,77 @@ void CodeGen::genCompareInt(GenTreePtr treeNode)
{
assert(treeNode->OperIsCompare());
- GenTreeOp* tree = treeNode->AsOp();
- GenTreePtr op1 = tree->gtOp1;
- GenTreePtr op2 = tree->gtOp2;
- var_types op1Type = op1->TypeGet();
- var_types op2Type = op2->TypeGet();
+ GenTreeOp* tree = treeNode->AsOp();
+ GenTreePtr op1 = tree->gtOp1;
+ GenTreePtr op2 = tree->gtOp2;
+ var_types op1Type = op1->TypeGet();
+ var_types op2Type = op2->TypeGet();
+ regNumber targetReg = treeNode->gtRegNum;
+
+ // Case of op1 == 0 or op1 != 0:
+ // Optimize generation of 'test' instruction if op1 sets flags.
+ //
+ // Note that if LSRA has inserted any GT_RELOAD/GT_COPY before
+ // op1, it will not modify the flags set by codegen of op1.
+ // Similarly op1 could also be reg-optional at its use and
+ // it was spilled after producing its result in a register.
+ // Spill code too will not modify the flags set by op1.
+ GenTree* realOp1 = op1->gtSkipReloadOrCopy();
+ if (realOp1->gtSetFlags())
+ {
+ // op1 must set ZF and SF flags
+ assert(realOp1->gtSetZSFlags());
+
+ // Must be (in)equality against zero.
+ assert(tree->OperGet() == GT_EQ || tree->OperGet() == GT_NE);
+ assert(op2->IsIntegralConst(0));
+ assert(op2->isContained());
+
+ // Just consume the operands
+ genConsumeOperands(tree);
+
+ // No need to generate test instruction since
+ // op1 sets flags
+
+ // Are we evaluating this into a register?
+ if (targetReg != REG_NA)
+ {
+ genSetRegToCond(targetReg, tree);
+ genProduceReg(tree);
+ }
+
+ return;
+ }
+
+#ifdef FEATURE_SIMD
+ // If we have GT_JTRUE(GT_EQ/NE(GT_SIMD((in)Equality, v1, v2), true/false)),
+ // then we don't need to generate code for GT_EQ/GT_NE, since SIMD (in)Equality intrinsic
+ // would set or clear Zero flag.
+ if ((targetReg == REG_NA) && (tree->OperGet() == GT_EQ || tree->OperGet() == GT_NE))
+ {
+ // Is it a SIMD (in)Equality that doesn't need to materialize result into a register?
+ if ((op1->gtRegNum == REG_NA) && op1->IsSIMDEqualityOrInequality())
+ {
+ // Must be comparing against true or false.
+ assert(op2->IsIntegralConst(0) || op2->IsIntegralConst(1));
+ assert(op2->isContainedIntOrIImmed());
+
+ // In this case SIMD (in)Equality will set or clear
+ // Zero flag, based on which GT_JTRUE would generate
+ // the right conditional jump.
+ return;
+ }
+ }
+#endif // FEATURE_SIMD
genConsumeOperands(tree);
instruction ins;
emitAttr cmpAttr;
- regNumber targetReg = treeNode->gtRegNum;
- assert(!op1->isContainedIntOrIImmed()); // We no longer support swapping op1 and op2 to generate cmp reg, imm
+ // TODO-CQ: We should be able to support swapping op1 and op2 to generate cmp reg, imm.
+ // https://github.com/dotnet/coreclr/issues/7270
+ assert(!op1->isContainedIntOrIImmed()); // We no longer support
assert(!varTypeIsFloating(op2Type));
#ifdef _TARGET_X86_
@@ -7387,7 +6165,7 @@ void CodeGen::genCompareInt(GenTreePtr treeNode)
{
// Do we have a short compare against a constant in op2?
//
- // We checked for this case in LowerCmp() and if we can perform a small
+ // We checked for this case in TreeNodeInfoInitCmp() and if we can perform a small
// compare immediate we labeled this compare with a GTF_RELOP_SMALL
// and for unsigned small non-equality compares the GTF_UNSIGNED flag.
//
@@ -7442,12 +6220,11 @@ void CodeGen::genCompareInt(GenTreePtr treeNode)
if (op1->isContained())
{
// op1 can be a contained memory op
- // or the special contained GT_AND that we created in Lowering::LowerCmp()
+ // or the special contained GT_AND that we created in Lowering::TreeNodeInfoInitCmp()
//
- if ((op1->OperGet() == GT_AND))
+ if ((op1->OperGet() == GT_AND) && op1->gtGetOp2()->isContainedIntOrIImmed() &&
+ ((tree->OperGet() == GT_EQ) || (tree->OperGet() == GT_NE)))
{
- noway_assert(op1->gtOp.gtOp2->isContainedIntOrIImmed());
-
ins = INS_test; // we will generate "test andOp1, andOp2CnsVal"
op2 = op1->gtOp.gtOp2; // must assign op2 before we overwrite op1
op1 = op1->gtOp.gtOp1; // overwrite op1
@@ -7561,6 +6338,93 @@ void CodeGen::genSetRegToCond(regNumber dstReg, GenTreePtr tree)
}
}
+#if !defined(_TARGET_64BIT_)
+//------------------------------------------------------------------------
+// genIntToIntCast: Generate code for long to int casts on x86.
+//
+// Arguments:
+// cast - The GT_CAST node
+//
+// Return Value:
+// None.
+//
+// Assumptions:
+// The cast node and its sources (via GT_LONG) must have been assigned registers.
+// The destination cannot be a floating point type or a small integer type.
+//
+void CodeGen::genLongToIntCast(GenTree* cast)
+{
+ assert(cast->OperGet() == GT_CAST);
+
+ GenTree* src = cast->gtGetOp1();
+ noway_assert(src->OperGet() == GT_LONG);
+
+ genConsumeRegs(src);
+
+ var_types srcType = ((cast->gtFlags & GTF_UNSIGNED) != 0) ? TYP_ULONG : TYP_LONG;
+ var_types dstType = cast->CastToType();
+ regNumber loSrcReg = src->gtGetOp1()->gtRegNum;
+ regNumber hiSrcReg = src->gtGetOp2()->gtRegNum;
+ regNumber dstReg = cast->gtRegNum;
+
+ assert((dstType == TYP_INT) || (dstType == TYP_UINT));
+ assert(genIsValidIntReg(loSrcReg));
+ assert(genIsValidIntReg(hiSrcReg));
+ assert(genIsValidIntReg(dstReg));
+
+ if (cast->gtOverflow())
+ {
+ //
+ // Generate an overflow check for [u]long to [u]int casts:
+ //
+ // long -> int - check if the upper 33 bits are all 0 or all 1
+ //
+ // ulong -> int - check if the upper 33 bits are all 0
+ //
+ // long -> uint - check if the upper 32 bits are all 0
+ // ulong -> uint - check if the upper 32 bits are all 0
+ //
+
+ if ((srcType == TYP_LONG) && (dstType == TYP_INT))
+ {
+ BasicBlock* allOne = genCreateTempLabel();
+ BasicBlock* success = genCreateTempLabel();
+
+ inst_RV_RV(INS_test, loSrcReg, loSrcReg, TYP_INT, EA_4BYTE);
+ inst_JMP(EJ_js, allOne);
+
+ inst_RV_RV(INS_test, hiSrcReg, hiSrcReg, TYP_INT, EA_4BYTE);
+ genJumpToThrowHlpBlk(EJ_jne, SCK_OVERFLOW);
+ inst_JMP(EJ_jmp, success);
+
+ genDefineTempLabel(allOne);
+ inst_RV_IV(INS_cmp, hiSrcReg, -1, EA_4BYTE);
+ genJumpToThrowHlpBlk(EJ_jne, SCK_OVERFLOW);
+
+ genDefineTempLabel(success);
+ }
+ else
+ {
+ if ((srcType == TYP_ULONG) && (dstType == TYP_INT))
+ {
+ inst_RV_RV(INS_test, loSrcReg, loSrcReg, TYP_INT, EA_4BYTE);
+ genJumpToThrowHlpBlk(EJ_js, SCK_OVERFLOW);
+ }
+
+ inst_RV_RV(INS_test, hiSrcReg, hiSrcReg, TYP_INT, EA_4BYTE);
+ genJumpToThrowHlpBlk(EJ_jne, SCK_OVERFLOW);
+ }
+ }
+
+ if (dstReg != loSrcReg)
+ {
+ inst_RV_RV(INS_mov, dstReg, loSrcReg, TYP_INT, EA_4BYTE);
+ }
+
+ genProduceReg(cast);
+}
+#endif
+
//------------------------------------------------------------------------
// genIntToIntCast: Generate code for an integer cast
// This method handles integer overflow checking casts
@@ -7584,13 +6448,22 @@ void CodeGen::genIntToIntCast(GenTreePtr treeNode)
{
assert(treeNode->OperGet() == GT_CAST);
- GenTreePtr castOp = treeNode->gtCast.CastOp();
- regNumber targetReg = treeNode->gtRegNum;
- regNumber sourceReg = castOp->gtRegNum;
- var_types dstType = treeNode->CastToType();
- bool isUnsignedDst = varTypeIsUnsigned(dstType);
- var_types srcType = genActualType(castOp->TypeGet());
- bool isUnsignedSrc = varTypeIsUnsigned(srcType);
+ GenTreePtr castOp = treeNode->gtCast.CastOp();
+ var_types srcType = genActualType(castOp->TypeGet());
+
+#if !defined(_TARGET_64BIT_)
+ if (varTypeIsLong(srcType))
+ {
+ genLongToIntCast(treeNode);
+ return;
+ }
+#endif // !defined(_TARGET_64BIT_)
+
+ regNumber targetReg = treeNode->gtRegNum;
+ regNumber sourceReg = castOp->gtRegNum;
+ var_types dstType = treeNode->CastToType();
+ bool isUnsignedDst = varTypeIsUnsigned(dstType);
+ bool isUnsignedSrc = varTypeIsUnsigned(srcType);
// if necessary, force the srcType to unsigned when the GT_UNSIGNED flag is set
if (!isUnsignedSrc && (treeNode->gtFlags & GTF_UNSIGNED) != 0)
@@ -7948,7 +6821,7 @@ void CodeGen::genFloatToFloatCast(GenTreePtr treeNode)
assert(varTypeIsFloating(srcType) && varTypeIsFloating(dstType));
genConsumeOperands(treeNode->AsOp());
- if (srcType == dstType && targetReg == op1->gtRegNum)
+ if (srcType == dstType && (!op1->isContained() && (targetReg == op1->gtRegNum)))
{
// source and destinations types are the same and also reside in the same register.
// we just need to consume and produce the reg in this case.
@@ -7999,7 +6872,8 @@ void CodeGen::genIntToFloatCast(GenTreePtr treeNode)
assert(!varTypeIsFloating(srcType) && varTypeIsFloating(dstType));
#if !defined(_TARGET_64BIT_)
- NYI_IF(varTypeIsLong(srcType), "Conversion from long to float");
+ // We expect morph to replace long to float/double casts with helper calls
+ noway_assert(!varTypeIsLong(srcType));
#endif // !defined(_TARGET_64BIT_)
// Since xarch emitter doesn't handle reporting gc-info correctly while casting away gc-ness we
@@ -8225,27 +7099,27 @@ void CodeGen::genCkfinite(GenTreePtr treeNode)
//
// For TYP_DOUBLE, we'll generate (for targetReg != op1->gtRegNum):
// movaps targetReg, op1->gtRegNum
- // shufps targetReg, targetReg, 0xB1 // WZYX => ZWXY
- // mov_xmm2i tmpReg, targetReg // tmpReg <= Y
+ // shufps targetReg, targetReg, 0xB1 // WZYX => ZWXY
+ // mov_xmm2i tmpReg, targetReg // tmpReg <= Y
// and tmpReg, <mask>
// cmp tmpReg, <mask>
// je <throw block>
// movaps targetReg, op1->gtRegNum // copy the value again, instead of un-shuffling it
//
// For TYP_DOUBLE with (targetReg == op1->gtRegNum):
- // shufps targetReg, targetReg, 0xB1 // WZYX => ZWXY
- // mov_xmm2i tmpReg, targetReg // tmpReg <= Y
+ // shufps targetReg, targetReg, 0xB1 // WZYX => ZWXY
+ // mov_xmm2i tmpReg, targetReg // tmpReg <= Y
// and tmpReg, <mask>
// cmp tmpReg, <mask>
// je <throw block>
- // shufps targetReg, targetReg, 0xB1 // ZWXY => WZYX
+ // shufps targetReg, targetReg, 0xB1 // ZWXY => WZYX
//
// For TYP_FLOAT, it's the same as _TARGET_64BIT_:
- // mov_xmm2i tmpReg, targetReg // tmpReg <= low 32 bits
+ // mov_xmm2i tmpReg, targetReg // tmpReg <= low 32 bits
// and tmpReg, <mask>
// cmp tmpReg, <mask>
// je <throw block>
- // movaps targetReg, op1->gtRegNum // only if targetReg != op1->gtRegNum
+ // movaps targetReg, op1->gtRegNum // only if targetReg != op1->gtRegNum
regNumber copyToTmpSrcReg; // The register we'll copy to the integer temp.
@@ -8613,7 +7487,7 @@ unsigned CodeGen::getBaseVarForPutArgStk(GenTreePtr treeNode)
#if FEATURE_FIXED_OUT_ARGS
baseVarNum = compiler->lvaOutgoingArgSpaceVar;
#else // !FEATURE_FIXED_OUT_ARGS
- NYI_X86("Stack args for x86/RyuJIT");
+ assert(!"No BaseVarForPutArgStk on x86");
baseVarNum = BAD_VAR_NUM;
#endif // !FEATURE_FIXED_OUT_ARGS
}
@@ -8621,8 +7495,74 @@ unsigned CodeGen::getBaseVarForPutArgStk(GenTreePtr treeNode)
return baseVarNum;
}
-//--------------------------------------------------------------------- //
-// genPutStructArgStk - generate code for passing an arg on the stack.
+#ifdef _TARGET_X86_
+//---------------------------------------------------------------------
+// adjustStackForPutArgStk:
+// adjust the stack pointer for a putArgStk node if necessary.
+//
+// Arguments:
+// putArgStk - the putArgStk node.
+//
+// Returns: true if the stack pointer was adjusted; false otherwise.
+//
+bool CodeGen::genAdjustStackForPutArgStk(GenTreePutArgStk* putArgStk)
+{
+#ifdef FEATURE_SIMD
+ if (varTypeIsSIMD(putArgStk))
+ {
+ const unsigned argSize = genTypeSize(putArgStk);
+ inst_RV_IV(INS_sub, REG_SPBASE, argSize, EA_PTRSIZE);
+ genStackLevel += argSize;
+ m_pushStkArg = false;
+ return true;
+ }
+#endif // FEATURE_SIMD
+
+ const unsigned argSize = putArgStk->getArgSize();
+
+ // If the gtPutArgStkKind is one of the push types, we do not pre-adjust the stack.
+ // This is set in Lowering, and is true if and only if:
+ // - This argument contains any GC pointers OR
+ // - It is a GT_FIELD_LIST OR
+ // - It is less than 16 bytes in size.
+ CLANG_FORMAT_COMMENT_ANCHOR;
+
+#ifdef DEBUG
+ switch (putArgStk->gtPutArgStkKind)
+ {
+ case GenTreePutArgStk::Kind::RepInstr:
+ case GenTreePutArgStk::Kind::Unroll:
+ assert((putArgStk->gtNumberReferenceSlots == 0) && (putArgStk->gtGetOp1()->OperGet() != GT_FIELD_LIST) &&
+ (argSize >= 16));
+ break;
+ case GenTreePutArgStk::Kind::Push:
+ case GenTreePutArgStk::Kind::PushAllSlots:
+ assert((putArgStk->gtNumberReferenceSlots != 0) || (putArgStk->gtGetOp1()->OperGet() == GT_FIELD_LIST) ||
+ (argSize < 16));
+ break;
+ case GenTreePutArgStk::Kind::Invalid:
+ default:
+ assert(!"Uninitialized GenTreePutArgStk::Kind");
+ break;
+ }
+#endif // DEBUG
+
+ if (putArgStk->isPushKind())
+ {
+ m_pushStkArg = true;
+ return false;
+ }
+ else
+ {
+ m_pushStkArg = false;
+ inst_RV_IV(INS_sub, REG_SPBASE, argSize, EA_PTRSIZE);
+ genStackLevel += argSize;
+ return true;
+ }
+}
+
+//---------------------------------------------------------------------
+// genPutArgStkFieldList - generate code for passing an arg on the stack.
//
// Arguments
// treeNode - the GT_PUTARG_STK node
@@ -8631,25 +7571,224 @@ unsigned CodeGen::getBaseVarForPutArgStk(GenTreePtr treeNode)
// Return value:
// None
//
-void CodeGen::genPutArgStk(GenTreePtr treeNode)
+void CodeGen::genPutArgStkFieldList(GenTreePutArgStk* putArgStk)
{
- var_types targetType = treeNode->TypeGet();
+ GenTreeFieldList* const fieldList = putArgStk->gtOp1->AsFieldList();
+ assert(fieldList != nullptr);
+
+ // Set m_pushStkArg and pre-adjust the stack if necessary.
+ const bool preAdjustedStack = genAdjustStackForPutArgStk(putArgStk);
+ // For now, we only support the "push" case; we will push a full slot for the first field of each slot
+ // within the struct.
+ assert((putArgStk->isPushKind()) && !preAdjustedStack && m_pushStkArg);
+
+ // If we have pre-adjusted the stack and are simply storing the fields in order) set the offset to 0.
+ // (Note that this mode is not currently being used.)
+ // If we are pushing the arguments (i.e. we have not pre-adjusted the stack), then we are pushing them
+ // in reverse order, so we start with the current field offset at the size of the struct arg (which must be
+ // a multiple of the target pointer size).
+ unsigned currentOffset = (preAdjustedStack) ? 0 : putArgStk->getArgSize();
+ unsigned prevFieldOffset = currentOffset;
+ regNumber tmpReg = REG_NA;
+ if (putArgStk->gtRsvdRegs != RBM_NONE)
+ {
+ assert(genCountBits(putArgStk->gtRsvdRegs) == 1);
+ tmpReg = genRegNumFromMask(putArgStk->gtRsvdRegs);
+ assert(genIsValidIntReg(tmpReg));
+ }
+ for (GenTreeFieldList* current = fieldList; current != nullptr; current = current->Rest())
+ {
+ GenTree* const fieldNode = current->Current();
+ const unsigned fieldOffset = current->gtFieldOffset;
+ var_types fieldType = current->gtFieldType;
+
+ // Long-typed nodes should have been handled by the decomposition pass, and lowering should have sorted the
+ // field list in descending order by offset.
+ assert(!varTypeIsLong(fieldType));
+ assert(fieldOffset <= prevFieldOffset);
+
+ // Consume the register, if any, for this field. Note that genConsumeRegs() will appropriately
+ // update the liveness info for a lclVar that has been marked RegOptional, which hasn't been
+ // assigned a register, and which is therefore contained.
+ // Unlike genConsumeReg(), it handles the case where no registers are being consumed.
+ genConsumeRegs(fieldNode);
+ regNumber argReg = fieldNode->isContainedSpillTemp() ? REG_NA : fieldNode->gtRegNum;
+
+ // If the field is slot-like, we can use a push instruction to store the entire register no matter the type.
+ //
+ // The GC encoder requires that the stack remain 4-byte aligned at all times. Round the adjustment up
+ // to the next multiple of 4. If we are going to generate a `push` instruction, the adjustment must
+ // not require rounding.
+ // NOTE: if the field is of GC type, we must use a push instruction, since the emitter is not otherwise
+ // able to detect stores into the outgoing argument area of the stack on x86.
+ const bool fieldIsSlot = ((fieldOffset % 4) == 0) && ((prevFieldOffset - fieldOffset) >= 4);
+ int adjustment = roundUp(currentOffset - fieldOffset, 4);
+ if (fieldIsSlot)
+ {
+ fieldType = genActualType(fieldType);
+ unsigned pushSize = genTypeSize(fieldType);
+ assert((pushSize % 4) == 0);
+ adjustment -= pushSize;
+ while (adjustment != 0)
+ {
+ inst_IV(INS_push, 0);
+ currentOffset -= pushSize;
+ genStackLevel += pushSize;
+ adjustment -= pushSize;
+ }
+ m_pushStkArg = true;
+ }
+ else
+ {
+ m_pushStkArg = false;
+ // We always "push" floating point fields (i.e. they are full slot values that don't
+ // require special handling).
+ assert(varTypeIsIntegralOrI(fieldNode));
+ // If we can't push this field, it needs to be in a register so that we can store
+ // it to the stack location.
+ assert(tmpReg != REG_NA);
+ if (adjustment != 0)
+ {
+ // This moves the stack pointer to fieldOffset.
+ // For this case, we must adjust the stack and generate stack-relative stores rather than pushes.
+ // Adjust the stack pointer to the next slot boundary.
+ inst_RV_IV(INS_sub, REG_SPBASE, adjustment, EA_PTRSIZE);
+ currentOffset -= adjustment;
+ genStackLevel += adjustment;
+ }
+
+ // Does it need to be in a byte register?
+ // If so, we'll use tmpReg, which must have been allocated as a byte register.
+ // If it's already in a register, but not a byteable one, then move it.
+ if (varTypeIsByte(fieldType) && ((argReg == REG_NA) || ((genRegMask(argReg) & RBM_BYTE_REGS) == 0)))
+ {
+ noway_assert((genRegMask(tmpReg) & RBM_BYTE_REGS) != 0);
+ if (argReg != REG_NA)
+ {
+ inst_RV_RV(INS_mov, tmpReg, argReg, fieldType);
+ argReg = tmpReg;
+ }
+ }
+ }
+
+ if (argReg == REG_NA)
+ {
+ if (m_pushStkArg)
+ {
+ if (fieldNode->isContainedSpillTemp())
+ {
+ assert(fieldNode->IsRegOptional());
+ TempDsc* tmp = getSpillTempDsc(fieldNode);
+ getEmitter()->emitIns_S(INS_push, emitActualTypeSize(fieldNode->TypeGet()), tmp->tdTempNum(), 0);
+ compiler->tmpRlsTemp(tmp);
+ }
+ else
+ {
+ assert(varTypeIsIntegralOrI(fieldNode));
+ switch (fieldNode->OperGet())
+ {
+ case GT_LCL_VAR:
+ inst_TT(INS_push, fieldNode, 0, 0, emitActualTypeSize(fieldNode->TypeGet()));
+ break;
+ case GT_CNS_INT:
+ if (fieldNode->IsIconHandle())
+ {
+ inst_IV_handle(INS_push, fieldNode->gtIntCon.gtIconVal);
+ }
+ else
+ {
+ inst_IV(INS_push, fieldNode->gtIntCon.gtIconVal);
+ }
+ break;
+ default:
+ unreached();
+ }
+ }
+ currentOffset -= TARGET_POINTER_SIZE;
+ genStackLevel += TARGET_POINTER_SIZE;
+ }
+ else
+ {
+ // The stack has been adjusted and we will load the field to tmpReg and then store it on the stack.
+ assert(varTypeIsIntegralOrI(fieldNode));
+ switch (fieldNode->OperGet())
+ {
+ case GT_LCL_VAR:
+ inst_RV_TT(INS_mov, tmpReg, fieldNode);
+ break;
+ case GT_CNS_INT:
+ genSetRegToConst(tmpReg, fieldNode->TypeGet(), fieldNode);
+ break;
+ default:
+ unreached();
+ }
+ genStoreRegToStackArg(fieldType, tmpReg, fieldOffset - currentOffset);
+ }
+ }
+ else
+ {
+ genStoreRegToStackArg(fieldType, argReg, fieldOffset - currentOffset);
+ if (m_pushStkArg)
+ {
+ // We always push a slot-rounded size
+ currentOffset -= genTypeSize(fieldType);
+ }
+ }
+
+ prevFieldOffset = fieldOffset;
+ }
+ if (currentOffset != 0)
+ {
+ // We don't expect padding at the beginning of a struct, but it could happen with explicit layout.
+ inst_RV_IV(INS_sub, REG_SPBASE, currentOffset, EA_PTRSIZE);
+ genStackLevel += currentOffset;
+ }
+}
+#endif // _TARGET_X86_
+
+//---------------------------------------------------------------------
+// genPutArgStk - generate code for passing an arg on the stack.
+//
+// Arguments
+// treeNode - the GT_PUTARG_STK node
+// targetType - the type of the treeNode
+//
+// Return value:
+// None
+//
+void CodeGen::genPutArgStk(GenTreePutArgStk* putArgStk)
+{
+ var_types targetType = putArgStk->TypeGet();
+
#ifdef _TARGET_X86_
- noway_assert(targetType != TYP_STRUCT);
+
+#ifdef FEATURE_SIMD
+ if (targetType == TYP_SIMD12)
+ {
+ genPutArgStkSIMD12(putArgStk);
+ return;
+ }
+#endif // FEATURE_SIMD
+
+ if (varTypeIsStruct(targetType))
+ {
+ (void)genAdjustStackForPutArgStk(putArgStk);
+ genPutStructArgStk(putArgStk);
+ return;
+ }
// The following logic is applicable for x86 arch.
- assert(!varTypeIsFloating(targetType) || (targetType == treeNode->gtGetOp1()->TypeGet()));
+ assert(!varTypeIsFloating(targetType) || (targetType == putArgStk->gtOp1->TypeGet()));
- GenTreePtr data = treeNode->gtOp.gtOp1;
+ GenTreePtr data = putArgStk->gtOp1;
// On a 32-bit target, all of the long arguments have been decomposed into
// a separate putarg_stk for each of the upper and lower halves.
noway_assert(targetType != TYP_LONG);
- int argSize = genTypeSize(genActualType(targetType));
- genStackLevel += argSize;
+ const unsigned argSize = putArgStk->getArgSize();
+ assert((argSize % TARGET_POINTER_SIZE) == 0);
- // TODO-Cleanup: Handle this in emitInsMov() in emitXArch.cpp?
if (data->isContainedIntOrIImmed())
{
if (data->IsIconHandle())
@@ -8660,53 +7799,50 @@ void CodeGen::genPutArgStk(GenTreePtr treeNode)
{
inst_IV(INS_push, data->gtIntCon.gtIconVal);
}
+ genStackLevel += argSize;
}
- else if (data->isContained())
+ else if (data->OperGet() == GT_FIELD_LIST)
{
- NYI_X86("Contained putarg_stk of non-constant");
+ genPutArgStkFieldList(putArgStk);
}
else
{
+ // We should not see any contained nodes that are not immediates.
+ assert(!data->isContained());
genConsumeReg(data);
- if (varTypeIsIntegralOrI(targetType))
- {
- inst_RV(INS_push, data->gtRegNum, targetType);
- }
- else
- {
- // Decrement SP.
- inst_RV_IV(INS_sub, REG_SPBASE, argSize, emitActualTypeSize(TYP_I_IMPL));
- getEmitter()->emitIns_AR_R(ins_Store(targetType), emitTypeSize(targetType), data->gtRegNum, REG_SPBASE, 0);
- }
+ genPushReg(targetType, data->gtRegNum);
}
#else // !_TARGET_X86_
{
- unsigned baseVarNum = getBaseVarForPutArgStk(treeNode);
+ unsigned baseVarNum = getBaseVarForPutArgStk(putArgStk);
#ifdef FEATURE_UNIX_AMD64_STRUCT_PASSING
if (varTypeIsStruct(targetType))
{
- genPutStructArgStk(treeNode, baseVarNum);
+ m_stkArgVarNum = baseVarNum;
+ m_stkArgOffset = putArgStk->getArgOffset();
+ genPutStructArgStk(putArgStk);
+ m_stkArgVarNum = BAD_VAR_NUM;
return;
}
#endif // FEATURE_UNIX_AMD64_STRUCT_PASSING
noway_assert(targetType != TYP_STRUCT);
- assert(!varTypeIsFloating(targetType) || (targetType == treeNode->gtGetOp1()->TypeGet()));
+ assert(!varTypeIsFloating(targetType) || (targetType == putArgStk->gtOp1->TypeGet()));
// Get argument offset on stack.
// Here we cross check that argument offset hasn't changed from lowering to codegen since
// we are storing arg slot number in GT_PUTARG_STK node in lowering phase.
- int argOffset = treeNode->AsPutArgStk()->getArgOffset();
+ int argOffset = putArgStk->getArgOffset();
#ifdef DEBUG
- fgArgTabEntryPtr curArgTabEntry = compiler->gtArgEntryByNode(treeNode->AsPutArgStk()->gtCall, treeNode);
+ fgArgTabEntryPtr curArgTabEntry = compiler->gtArgEntryByNode(putArgStk->gtCall, putArgStk);
assert(curArgTabEntry);
assert(argOffset == (int)curArgTabEntry->slotNum * TARGET_POINTER_SIZE);
#endif
- GenTreePtr data = treeNode->gtGetOp1();
+ GenTreePtr data = putArgStk->gtOp1;
if (data->isContained())
{
@@ -8723,7 +7859,125 @@ void CodeGen::genPutArgStk(GenTreePtr treeNode)
#endif // !_TARGET_X86_
}
-#if defined(FEATURE_UNIX_AMD64_STRUCT_PASSING)
+#ifdef _TARGET_X86_
+// genPushReg: Push a register value onto the stack and adjust the stack level
+//
+// Arguments:
+// type - the type of value to be stored
+// reg - the register containing the value
+//
+// Notes:
+// For TYP_LONG, the srcReg must be a floating point register.
+// Otherwise, the register type must be consistent with the given type.
+//
+void CodeGen::genPushReg(var_types type, regNumber srcReg)
+{
+ unsigned size = genTypeSize(type);
+ if (varTypeIsIntegralOrI(type) && type != TYP_LONG)
+ {
+ assert(genIsValidIntReg(srcReg));
+ inst_RV(INS_push, srcReg, type);
+ }
+ else
+ {
+ instruction ins;
+ emitAttr attr = emitTypeSize(type);
+ if (type == TYP_LONG)
+ {
+ // On x86, the only way we can push a TYP_LONG from a register is if it is in an xmm reg.
+ // This is only used when we are pushing a struct from memory to memory, and basically is
+ // handling an 8-byte "chunk", as opposed to strictly a long type.
+ ins = INS_movq;
+ }
+ else
+ {
+ ins = ins_Store(type);
+ }
+ assert(genIsValidFloatReg(srcReg));
+ inst_RV_IV(INS_sub, REG_SPBASE, size, EA_PTRSIZE);
+ getEmitter()->emitIns_AR_R(ins, attr, srcReg, REG_SPBASE, 0);
+ }
+ genStackLevel += size;
+}
+#endif // _TARGET_X86_
+
+#if defined(FEATURE_PUT_STRUCT_ARG_STK)
+// genStoreRegToStackArg: Store a register value into the stack argument area
+//
+// Arguments:
+// type - the type of value to be stored
+// reg - the register containing the value
+// offset - the offset from the base (see Assumptions below)
+//
+// Notes:
+// A type of TYP_STRUCT instructs this method to store a 16-byte chunk
+// at the given offset (i.e. not the full struct).
+//
+// Assumptions:
+// The caller must set the context appropriately before calling this method:
+// - On x64, m_stkArgVarNum must be set according to whether this is a regular or tail call.
+// - On x86, the caller must set m_pushStkArg if this method should push the argument.
+// Otherwise, the argument is stored at the given offset from sp.
+//
+// TODO: In the below code the load and store instructions are for 16 bytes, but the
+// type is EA_8BYTE. The movdqa/u are 16 byte instructions, so it works, but
+// this probably needs to be changed.
+//
+void CodeGen::genStoreRegToStackArg(var_types type, regNumber srcReg, int offset)
+{
+ assert(srcReg != REG_NA);
+ instruction ins;
+ emitAttr attr;
+ unsigned size;
+
+ if (type == TYP_STRUCT)
+ {
+ ins = INS_movdqu;
+ // This should be changed!
+ attr = EA_8BYTE;
+ size = 16;
+ }
+ else
+ {
+#ifdef FEATURE_SIMD
+ if (varTypeIsSIMD(type))
+ {
+ assert(genIsValidFloatReg(srcReg));
+ ins = ins_Store(type); // TODO-CQ: pass 'aligned' correctly
+ }
+ else
+#endif // FEATURE_SIMD
+#ifdef _TARGET_X86_
+ if (type == TYP_LONG)
+ {
+ assert(genIsValidFloatReg(srcReg));
+ ins = INS_movq;
+ }
+ else
+#endif // _TARGET_X86_
+ {
+ assert((varTypeIsFloating(type) && genIsValidFloatReg(srcReg)) ||
+ (varTypeIsIntegralOrI(type) && genIsValidIntReg(srcReg)));
+ ins = ins_Store(type);
+ }
+ attr = emitTypeSize(type);
+ size = genTypeSize(type);
+ }
+
+#ifdef _TARGET_X86_
+ if (m_pushStkArg)
+ {
+ genPushReg(type, srcReg);
+ }
+ else
+ {
+ getEmitter()->emitIns_AR_R(ins, attr, srcReg, REG_SPBASE, offset);
+ }
+#else // !_TARGET_X86_
+ assert(m_stkArgVarNum != BAD_VAR_NUM);
+ getEmitter()->emitIns_S_R(ins, attr, srcReg, m_stkArgVarNum, m_stkArgOffset + offset);
+#endif // !_TARGET_X86_
+}
//---------------------------------------------------------------------
// genPutStructArgStk - generate code for copying a struct arg on the stack by value.
@@ -8731,42 +7985,39 @@ void CodeGen::genPutArgStk(GenTreePtr treeNode)
// it generates the gcinfo as well.
//
// Arguments
-// treeNode - the GT_PUTARG_STK node
-// baseVarNum - the variable number relative to which to put the argument on the stack.
-// For tail calls this is the baseVarNum = 0.
-// For non tail calls this is the outgoingArgSpace.
-//
-// Return value:
-// None
+// putArgStk - the GT_PUTARG_STK node
//
-void CodeGen::genPutStructArgStk(GenTreePtr treeNode, unsigned baseVarNum)
+// Notes:
+// In the case of fixed out args, the caller must have set m_stkArgVarNum to the variable number
+// corresponding to the argument area (where we will put the argument on the stack).
+// For tail calls this is the baseVarNum = 0.
+// For non tail calls this is the outgoingArgSpace.
+void CodeGen::genPutStructArgStk(GenTreePutArgStk* putArgStk)
{
- assert(treeNode->OperGet() == GT_PUTARG_STK);
- assert(baseVarNum != BAD_VAR_NUM);
-
- var_types targetType = treeNode->TypeGet();
+ var_types targetType = putArgStk->TypeGet();
if (varTypeIsSIMD(targetType))
{
- regNumber srcReg = genConsumeReg(treeNode->gtGetOp1());
+ regNumber srcReg = genConsumeReg(putArgStk->gtGetOp1());
assert((srcReg != REG_NA) && (genIsValidFloatReg(srcReg)));
- getEmitter()->emitIns_S_R(ins_Store(targetType), emitTypeSize(targetType), srcReg, baseVarNum,
- treeNode->AsPutArgStk()->getArgOffset());
+ genStoreRegToStackArg(targetType, srcReg, 0);
return;
}
assert(targetType == TYP_STRUCT);
- GenTreePutArgStk* putArgStk = treeNode->AsPutArgStk();
if (putArgStk->gtNumberReferenceSlots == 0)
{
switch (putArgStk->gtPutArgStkKind)
{
- case GenTreePutArgStk::PutArgStkKindRepInstr:
- genStructPutArgRepMovs(putArgStk, baseVarNum);
+ case GenTreePutArgStk::Kind::RepInstr:
+ genStructPutArgRepMovs(putArgStk);
break;
- case GenTreePutArgStk::PutArgStkKindUnroll:
- genStructPutArgUnroll(putArgStk, baseVarNum);
+ case GenTreePutArgStk::Kind::Unroll:
+ genStructPutArgUnroll(putArgStk);
+ break;
+ case GenTreePutArgStk::Kind::Push:
+ genStructPutArgUnroll(putArgStk);
break;
default:
unreached();
@@ -8775,108 +8026,150 @@ void CodeGen::genPutStructArgStk(GenTreePtr treeNode, unsigned baseVarNum)
else
{
// No need to disable GC the way COPYOBJ does. Here the refs are copied in atomic operations always.
+ CLANG_FORMAT_COMMENT_ANCHOR;
- // Consume these registers.
- // They may now contain gc pointers (depending on their type; gcMarkRegPtrVal will "do the right thing").
- genConsumePutStructArgStk(putArgStk, REG_RDI, REG_RSI, REG_NA, baseVarNum);
- GenTreePtr dstAddr = putArgStk;
- GenTreePtr src = putArgStk->gtOp.gtOp1;
- assert(src->OperGet() == GT_OBJ);
- GenTreePtr srcAddr = src->gtGetOp1();
+#ifdef _TARGET_X86_
+ // On x86, any struct that has contains GC references must be stored to the stack using `push` instructions so
+ // that the emitter properly detects the need to update the method's GC information.
+ //
+ // Strictly speaking, it is only necessary to use `push` to store the GC references themselves, so for structs
+ // with large numbers of consecutive non-GC-ref-typed fields, we may be able to improve the code size in the
+ // future.
+ assert(m_pushStkArg);
- unsigned slots = putArgStk->gtNumSlots;
+ GenTree* srcAddr = putArgStk->gtGetOp1()->gtGetOp1();
+ BYTE* gcPtrs = putArgStk->gtGcPtrs;
+ const unsigned numSlots = putArgStk->gtNumSlots;
- // We are always on the stack we don't need to use the write barrier.
- BYTE* gcPtrs = putArgStk->gtGcPtrs;
- unsigned gcPtrCount = putArgStk->gtNumberReferenceSlots;
+ regNumber srcRegNum = srcAddr->gtRegNum;
+ const bool srcAddrInReg = srcRegNum != REG_NA;
- unsigned i = 0;
- unsigned copiedSlots = 0;
- while (i < slots)
+ unsigned srcLclNum = 0;
+ unsigned srcLclOffset = 0;
+ if (srcAddrInReg)
{
- switch (gcPtrs[i])
+ genConsumeReg(srcAddr);
+ }
+ else
+ {
+ assert(srcAddr->OperIsLocalAddr());
+
+ srcLclNum = srcAddr->AsLclVarCommon()->gtLclNum;
+ if (srcAddr->OperGet() == GT_LCL_FLD_ADDR)
{
- case TYPE_GC_NONE:
- // Let's see if we can use rep movsq instead of a sequence of movsq instructions
- // to save cycles and code size.
- {
- unsigned nonGcSlotCount = 0;
+ srcLclOffset = srcAddr->AsLclFld()->gtLclOffs;
+ }
+ }
- do
- {
- nonGcSlotCount++;
- i++;
- } while (i < slots && gcPtrs[i] == TYPE_GC_NONE);
+ for (int i = numSlots - 1; i >= 0; --i)
+ {
+ emitAttr slotAttr;
+ if (gcPtrs[i] == TYPE_GC_NONE)
+ {
+ slotAttr = EA_4BYTE;
+ }
+ else if (gcPtrs[i] == TYPE_GC_REF)
+ {
+ slotAttr = EA_GCREF;
+ }
+ else
+ {
+ assert(gcPtrs[i] == TYPE_GC_BYREF);
+ slotAttr = EA_BYREF;
+ }
- // If we have a very small contiguous non-gc region, it's better just to
- // emit a sequence of movsq instructions
- if (nonGcSlotCount < CPOBJ_NONGC_SLOTS_LIMIT)
- {
- copiedSlots += nonGcSlotCount;
- while (nonGcSlotCount > 0)
- {
- instGen(INS_movsq);
- nonGcSlotCount--;
- }
- }
- else
- {
- getEmitter()->emitIns_R_I(INS_mov, EA_4BYTE, REG_RCX, nonGcSlotCount);
- copiedSlots += nonGcSlotCount;
- instGen(INS_r_movsq);
- }
- }
- break;
+ const unsigned offset = i * 4;
+ if (srcAddrInReg)
+ {
+ getEmitter()->emitIns_AR_R(INS_push, slotAttr, REG_NA, srcRegNum, offset);
+ }
+ else
+ {
+ getEmitter()->emitIns_S(INS_push, slotAttr, srcLclNum, srcLclOffset + offset);
+ }
+ genStackLevel += 4;
+ }
+#else // !defined(_TARGET_X86_)
+
+ // Consume these registers.
+ // They may now contain gc pointers (depending on their type; gcMarkRegPtrVal will "do the right thing").
+ genConsumePutStructArgStk(putArgStk, REG_RDI, REG_RSI, REG_NA);
+
+ const bool srcIsLocal = putArgStk->gtOp1->AsObj()->gtOp1->OperIsLocalAddr();
+ const emitAttr srcAddrAttr = srcIsLocal ? EA_PTRSIZE : EA_BYREF;
+
+#if DEBUG
+ unsigned numGCSlotsCopied = 0;
+#endif // DEBUG
+
+ BYTE* gcPtrs = putArgStk->gtGcPtrs;
+ const unsigned numSlots = putArgStk->gtNumSlots;
+ for (unsigned i = 0; i < numSlots;)
+ {
+ if (gcPtrs[i] == TYPE_GC_NONE)
+ {
+ // Let's see if we can use rep movsp (alias for movsd or movsq for 32 and 64 bits respectively)
+ // instead of a sequence of movsp instructions to save cycles and code size.
+ unsigned adjacentNonGCSlotCount = 0;
+ do
+ {
+ adjacentNonGCSlotCount++;
+ i++;
+ } while ((i < numSlots) && (gcPtrs[i] == TYPE_GC_NONE));
- case TYPE_GC_REF: // Is an object ref
- case TYPE_GC_BYREF: // Is an interior pointer - promote it but don't scan it
+ // If we have a very small contiguous non-ref region, it's better just to
+ // emit a sequence of movsp instructions
+ if (adjacentNonGCSlotCount < CPOBJ_NONGC_SLOTS_LIMIT)
{
- // We have a GC (byref or ref) pointer
- // TODO-Amd64-Unix: Here a better solution (for code size and CQ) would be to use movsq instruction,
- // but the logic for emitting a GC info record is not available (it is internal for the emitter
- // only.) See emitGCVarLiveUpd function. If we could call it separately, we could do
- // instGen(INS_movsq); and emission of gc info.
-
- var_types memType;
- if (gcPtrs[i] == TYPE_GC_REF)
- {
- memType = TYP_REF;
- }
- else
+ for (; adjacentNonGCSlotCount > 0; adjacentNonGCSlotCount--)
{
- assert(gcPtrs[i] == TYPE_GC_BYREF);
- memType = TYP_BYREF;
+ instGen(INS_movsp);
}
+ }
+ else
+ {
+ getEmitter()->emitIns_R_I(INS_mov, EA_4BYTE, REG_RCX, adjacentNonGCSlotCount);
+ instGen(INS_r_movsp);
+ }
+ }
+ else
+ {
+ assert((gcPtrs[i] == TYPE_GC_REF) || (gcPtrs[i] == TYPE_GC_BYREF));
+
+ // We have a GC (byref or ref) pointer
+ // TODO-Amd64-Unix: Here a better solution (for code size and CQ) would be to use movsp instruction,
+ // but the logic for emitting a GC info record is not available (it is internal for the emitter
+ // only.) See emitGCVarLiveUpd function. If we could call it separately, we could do
+ // instGen(INS_movsp); and emission of gc info.
- getEmitter()->emitIns_R_AR(ins_Load(memType), emitTypeSize(memType), REG_RCX, REG_RSI, 0);
- getEmitter()->emitIns_S_R(ins_Store(memType), emitTypeSize(memType), REG_RCX, baseVarNum,
- ((copiedSlots + putArgStk->gtSlotNum) * TARGET_POINTER_SIZE));
+ var_types memType = (gcPtrs[i] == TYPE_GC_REF) ? TYP_REF : TYP_BYREF;
+ getEmitter()->emitIns_R_AR(ins_Load(memType), emitTypeSize(memType), REG_RCX, REG_RSI, 0);
+ genStoreRegToStackArg(memType, REG_RCX, i * TARGET_POINTER_SIZE);
+
+#ifdef DEBUG
+ numGCSlotsCopied++;
+#endif // DEBUG
+ i++;
+ if (i < numSlots)
+ {
// Source for the copy operation.
// If a LocalAddr, use EA_PTRSIZE - copy from stack.
// If not a LocalAddr, use EA_BYREF - the source location is not on the stack.
- getEmitter()->emitIns_R_I(INS_add, ((src->OperIsLocalAddr()) ? EA_PTRSIZE : EA_BYREF), REG_RSI,
- TARGET_POINTER_SIZE);
+ getEmitter()->emitIns_R_I(INS_add, srcAddrAttr, REG_RSI, TARGET_POINTER_SIZE);
// Always copying to the stack - outgoing arg area
// (or the outgoing arg area of the caller for a tail call) - use EA_PTRSIZE.
getEmitter()->emitIns_R_I(INS_add, EA_PTRSIZE, REG_RDI, TARGET_POINTER_SIZE);
- copiedSlots++;
- gcPtrCount--;
- i++;
}
- break;
-
- default:
- unreached();
- break;
}
}
- assert(gcPtrCount == 0);
+ assert(numGCSlotsCopied == putArgStk->gtNumberReferenceSlots);
+#endif // _TARGET_X86_
}
}
-#endif // defined(FEATURE_UNIX_AMD64_STRUCT_PASSING)
+#endif // defined(FEATURE_PUT_STRUCT_ARG_STK)
/*****************************************************************************
*
@@ -9043,7 +8336,7 @@ void* CodeGen::genCreateAndStoreGCInfoJIT32(unsigned codeSize,
return infoPtr;
}
-#else // !JIT32_GCENCODER
+#else // !JIT32_GCENCODER
void CodeGen::genCreateAndStoreGCInfoX64(unsigned codeSize, unsigned prologSize DEBUGARG(void* codePtr))
{
IAllocator* allowZeroAlloc = new (compiler, CMK_GC) AllowZeroAllocator(compiler->getAllocatorGC());
@@ -9061,7 +8354,6 @@ void CodeGen::genCreateAndStoreGCInfoX64(unsigned codeSize, unsigned prologSize
// Now we can actually use those slot ID's to declare live ranges.
gcInfo.gcMakeRegPtrTable(gcInfoEncoder, codeSize, prologSize, GCInfo::MAKE_REG_PTR_MODE_DO_WORK);
-#if defined(DEBUGGING_SUPPORT)
if (compiler->opts.compDbgEnC)
{
// what we have to preserve is called the "frame header" (see comments in VM\eetwain.cpp)
@@ -9088,7 +8380,6 @@ void CodeGen::genCreateAndStoreGCInfoX64(unsigned codeSize, unsigned prologSize
// frame
gcInfoEncoder->SetSizeOfEditAndContinuePreservedArea(preservedAreaSize);
}
-#endif
gcInfoEncoder->Build();
@@ -9203,18 +8494,33 @@ void CodeGen::genStoreLongLclVar(GenTree* treeNode)
assert(varDsc->TypeGet() == TYP_LONG);
assert(!varDsc->lvPromoted);
GenTreePtr op1 = treeNode->gtOp.gtOp1;
- noway_assert(op1->OperGet() == GT_LONG);
+ noway_assert(op1->OperGet() == GT_LONG || op1->OperGet() == GT_MUL_LONG);
genConsumeRegs(op1);
- // Definitions of register candidates will have been lowered to 2 int lclVars.
- assert(!treeNode->InReg());
+ if (op1->OperGet() == GT_LONG)
+ {
+ // Definitions of register candidates will have been lowered to 2 int lclVars.
+ assert(!treeNode->InReg());
+
+ GenTreePtr loVal = op1->gtGetOp1();
+ GenTreePtr hiVal = op1->gtGetOp2();
+
+ // NYI: Contained immediates.
+ NYI_IF((loVal->gtRegNum == REG_NA) || (hiVal->gtRegNum == REG_NA),
+ "Store of long lclVar with contained immediate");
+
+ emit->emitIns_S_R(ins_Store(TYP_INT), EA_4BYTE, loVal->gtRegNum, lclNum, 0);
+ emit->emitIns_S_R(ins_Store(TYP_INT), EA_4BYTE, hiVal->gtRegNum, lclNum, genTypeSize(TYP_INT));
+ }
+ else if (op1->OperGet() == GT_MUL_LONG)
+ {
+ assert((op1->gtFlags & GTF_MUL_64RSLT) != 0);
- GenTreePtr loVal = op1->gtGetOp1();
- GenTreePtr hiVal = op1->gtGetOp2();
- // NYI: Contained immediates.
- NYI_IF((loVal->gtRegNum == REG_NA) || (hiVal->gtRegNum == REG_NA), "Store of long lclVar with contained immediate");
- emit->emitIns_R_S(ins_Store(TYP_INT), EA_4BYTE, loVal->gtRegNum, lclNum, 0);
- emit->emitIns_R_S(ins_Store(TYP_INT), EA_4BYTE, hiVal->gtRegNum, lclNum, genTypeSize(TYP_INT));
+ // Stack store
+ getEmitter()->emitIns_S_R(ins_Store(TYP_INT), emitTypeSize(TYP_INT), REG_LNGRET_LO, lclNum, 0);
+ getEmitter()->emitIns_S_R(ins_Store(TYP_INT), emitTypeSize(TYP_INT), REG_LNGRET_HI, lclNum,
+ genTypeSize(TYP_INT));
+ }
}
#endif // !defined(_TARGET_64BIT_)
@@ -9332,57 +8638,6 @@ void CodeGen::genAmd64EmitterUnitTests()
#endif // defined(DEBUG) && defined(LATE_DISASM) && defined(_TARGET_AMD64_)
-/*****************************************************************************/
-#ifdef DEBUGGING_SUPPORT
-/*****************************************************************************
- * genSetScopeInfo
- *
- * Called for every scope info piece to record by the main genSetScopeInfo()
- */
-
-void CodeGen::genSetScopeInfo(unsigned which,
- UNATIVE_OFFSET startOffs,
- UNATIVE_OFFSET length,
- unsigned varNum,
- unsigned LVnum,
- bool avail,
- Compiler::siVarLoc& varLoc)
-{
- /* We need to do some mapping while reporting back these variables */
-
- unsigned ilVarNum = compiler->compMap2ILvarNum(varNum);
- noway_assert((int)ilVarNum != ICorDebugInfo::UNKNOWN_ILNUM);
-
- VarName name = nullptr;
-
-#ifdef DEBUG
-
- for (unsigned scopeNum = 0; scopeNum < compiler->info.compVarScopesCount; scopeNum++)
- {
- if (LVnum == compiler->info.compVarScopes[scopeNum].vsdLVnum)
- {
- name = compiler->info.compVarScopes[scopeNum].vsdName;
- }
- }
-
- // Hang on to this compiler->info.
-
- TrnslLocalVarInfo& tlvi = genTrnslLocalVarInfo[which];
-
- tlvi.tlviVarNum = ilVarNum;
- tlvi.tlviLVnum = LVnum;
- tlvi.tlviName = name;
- tlvi.tlviStartPC = startOffs;
- tlvi.tlviLength = length;
- tlvi.tlviAvailable = avail;
- tlvi.tlviVarLoc = varLoc;
-
-#endif // DEBUG
-
- compiler->eeSetLVinfo(which, startOffs, length, ilVarNum, LVnum, name, avail, varLoc);
-}
-#endif // DEBUGGING_SUPPORT
-
#endif // _TARGET_AMD64_
#endif // !LEGACY_BACKEND
diff --git a/src/jit/compatjit/.gitmirror b/src/jit/compatjit/.gitmirror
new file mode 100644
index 0000000000..f507630f94
--- /dev/null
+++ b/src/jit/compatjit/.gitmirror
@@ -0,0 +1 @@
+Only contents of this folder, excluding subfolders, will be mirrored by the Git-TFS Mirror. \ No newline at end of file
diff --git a/src/jit/compatjit/CMakeLists.txt b/src/jit/compatjit/CMakeLists.txt
new file mode 100644
index 0000000000..1e0615e431
--- /dev/null
+++ b/src/jit/compatjit/CMakeLists.txt
@@ -0,0 +1,66 @@
+project(compatjit)
+
+# This compatjit.dll is only built if we are not building JIT32 as compatjit.dll.
+# It is the same build as legacyjit.dll, just with a different name, and not
+# built as an altjit.
+
+add_definitions(-DLEGACY_BACKEND)
+
+add_definitions(-DFEATURE_NO_HOST)
+add_definitions(-DSELF_NO_HOST)
+add_definitions(-DFEATURE_READYTORUN_COMPILER)
+remove_definitions(-DFEATURE_MERGE_JIT_AND_ENGINE)
+
+# No SIMD in legacy back-end.
+remove_definitions(-DFEATURE_SIMD)
+remove_definitions(-DFEATURE_AVX_SUPPORT)
+
+if(WIN32)
+ add_definitions(-DFX_VER_INTERNALNAME_STR=compatjit.dll)
+endif(WIN32)
+
+add_library_clr(compatjit
+ SHARED
+ ${SHARED_LIB_SOURCES}
+)
+
+add_dependencies(compatjit jit_exports)
+
+set_property(TARGET compatjit APPEND_STRING PROPERTY LINK_FLAGS ${JIT_EXPORTS_LINKER_OPTION})
+set_property(TARGET compatjit APPEND_STRING PROPERTY LINK_DEPENDS ${JIT_EXPORTS_FILE})
+
+set(RYUJIT_LINK_LIBRARIES
+ utilcodestaticnohost
+ gcinfo
+)
+
+if(CLR_CMAKE_PLATFORM_UNIX)
+ list(APPEND RYUJIT_LINK_LIBRARIES
+ mscorrc_debug
+ coreclrpal
+ palrt
+ )
+else()
+ list(APPEND RYUJIT_LINK_LIBRARIES
+ ${STATIC_MT_CRT_LIB}
+ ${STATIC_MT_VCRT_LIB}
+ kernel32.lib
+ advapi32.lib
+ ole32.lib
+ oleaut32.lib
+ uuid.lib
+ user32.lib
+ version.lib
+ shlwapi.lib
+ bcrypt.lib
+ crypt32.lib
+ RuntimeObject.lib
+ )
+endif(CLR_CMAKE_PLATFORM_UNIX)
+
+target_link_libraries(compatjit
+ ${RYUJIT_LINK_LIBRARIES}
+)
+
+# add the install targets
+install_clr(compatjit)
diff --git a/src/jit/compiler.cpp b/src/jit/compiler.cpp
index afbecdfc60..114847c0d0 100644
--- a/src/jit/compiler.cpp
+++ b/src/jit/compiler.cpp
@@ -48,6 +48,60 @@ bool Compiler::s_pAltJitExcludeAssembliesListInitialized = false;
AssemblyNamesList2* Compiler::s_pAltJitExcludeAssembliesList = nullptr;
#endif // ALT_JIT
+/*****************************************************************************
+ *
+ * Little helpers to grab the current cycle counter value; this is done
+ * differently based on target architecture, host toolchain, etc. The
+ * main thing is to keep the overhead absolutely minimal; in fact, on
+ * x86/x64 we use RDTSC even though it's not thread-safe; GetThreadCycles
+ * (which is monotonous) is just too expensive.
+ */
+#ifdef FEATURE_JIT_METHOD_PERF
+
+#if defined(_HOST_X86_) || defined(_HOST_AMD64_)
+
+#if defined(_MSC_VER)
+
+#include <intrin.h>
+inline bool _our_GetThreadCycles(unsigned __int64* cycleOut)
+{
+ *cycleOut = __rdtsc();
+ return true;
+}
+
+#elif defined(__clang__)
+
+inline bool _our_GetThreadCycles(unsigned __int64* cycleOut)
+{
+ uint64_t cycles;
+ asm volatile("rdtsc" : "=A"(cycles));
+ *cycleOut = cycles;
+ return true;
+}
+
+#else // neither _MSC_VER nor __clang__
+
+// The following *might* work - might as well try.
+#define _our_GetThreadCycles(cp) GetThreadCycles(cp)
+
+#endif
+
+#elif defined(_HOST_ARM_) || defined(_HOST_ARM64_)
+
+// If this doesn't work please see ../gc/gc.cpp for additional ARM
+// info (and possible solutions).
+#define _our_GetThreadCycles(cp) GetThreadCycles(cp)
+
+#else // not x86/x64 and not ARM
+
+// Don't know what this target is, but let's give it a try; if
+// someone really wants to make this work, please add the right
+// code here.
+#define _our_GetThreadCycles(cp) GetThreadCycles(cp)
+
+#endif // which host OS
+
+#endif // FEATURE_JIT_METHOD_PERF
/*****************************************************************************/
inline unsigned getCurTime()
{
@@ -147,8 +201,6 @@ void Compiler::compDspSrcLinesByLineNum(unsigned line, bool seek)
void Compiler::compDspSrcLinesByNativeIP(UNATIVE_OFFSET curIP)
{
-#ifdef DEBUGGING_SUPPORT
-
static IPmappingDsc* nextMappingDsc;
static unsigned lastLine;
@@ -203,8 +255,6 @@ void Compiler::compDspSrcLinesByNativeIP(UNATIVE_OFFSET curIP)
nextMappingDsc = nextMappingDsc->ipmdNext;
}
}
-
-#endif
}
/*****************************************************************************/
@@ -232,6 +282,15 @@ unsigned genTreeNsizHistBuckets[] = {1000, 5000, 10000, 50000, 100000, 500000,
Histogram genTreeNsizHist(HostAllocator::getHostAllocator(), genTreeNsizHistBuckets);
#endif // MEASURE_NODE_SIZE
+/*****************************************************************************/
+#if MEASURE_MEM_ALLOC
+
+unsigned memSizeHistBuckets[] = {20, 50, 75, 100, 150, 250, 500, 1000, 5000, 0};
+Histogram memAllocHist(HostAllocator::getHostAllocator(), memSizeHistBuckets);
+Histogram memUsedHist(HostAllocator::getHostAllocator(), memSizeHistBuckets);
+
+#endif // MEASURE_MEM_ALLOC
+
/*****************************************************************************
*
* Variables to keep track of total code amounts.
@@ -475,7 +534,7 @@ bool Compiler::isSingleFloat32Struct(CORINFO_CLASS_HANDLE clsHnd)
for (;;)
{
// all of class chain must be of value type and must have only one field
- if (!info.compCompHnd->isValueClass(clsHnd) && info.compCompHnd->getClassNumInstanceFields(clsHnd) != 1)
+ if (!info.compCompHnd->isValueClass(clsHnd) || info.compCompHnd->getClassNumInstanceFields(clsHnd) != 1)
{
return false;
}
@@ -1101,14 +1160,11 @@ size_t genFlowNodeCnt;
#ifdef DEBUG
/* static */
unsigned Compiler::s_compMethodsCount = 0; // to produce unique label names
-
-/* static */
-bool Compiler::s_dspMemStats = false;
#endif
-#ifndef DEBUGGING_SUPPORT
+#if MEASURE_MEM_ALLOC
/* static */
-const bool Compiler::Options::compDbgCode = false;
+bool Compiler::s_dspMemStats = false;
#endif
#ifndef PROFILING_SUPPORTED
@@ -1184,18 +1240,22 @@ void Compiler::compShutdown()
}
#endif
+#if NODEBASH_STATS
+ GenTree::ReportOperBashing(jitstdout);
+#endif
+
// Where should we write our statistics output?
FILE* fout = jitstdout;
#ifdef FEATURE_JIT_METHOD_PERF
- if (compJitTimeLogFilename != NULL)
+ if (compJitTimeLogFilename != nullptr)
{
- // I assume that this will return NULL if it fails for some reason, and
- // that...
FILE* jitTimeLogFile = _wfopen(compJitTimeLogFilename, W("a"));
- // ...Print will return silently with a NULL argument.
- CompTimeSummaryInfo::s_compTimeSummary.Print(jitTimeLogFile);
- fclose(jitTimeLogFile);
+ if (jitTimeLogFile != nullptr)
+ {
+ CompTimeSummaryInfo::s_compTimeSummary.Print(jitTimeLogFile);
+ fclose(jitTimeLogFile);
+ }
}
#endif // FEATURE_JIT_METHOD_PERF
@@ -1214,6 +1274,63 @@ void Compiler::compShutdown()
}
#endif // COUNT_RANGECHECKS
+#if COUNT_AST_OPERS
+
+ // Add up all the counts so that we can show percentages of total
+ unsigned gtc = 0;
+ for (unsigned op = 0; op < GT_COUNT; op++)
+ gtc += GenTree::s_gtNodeCounts[op];
+
+ if (gtc > 0)
+ {
+ unsigned rem_total = gtc;
+ unsigned rem_large = 0;
+ unsigned rem_small = 0;
+
+ unsigned tot_large = 0;
+ unsigned tot_small = 0;
+
+ fprintf(fout, "\nGenTree operator counts (approximate):\n\n");
+
+ for (unsigned op = 0; op < GT_COUNT; op++)
+ {
+ unsigned siz = GenTree::s_gtTrueSizes[op];
+ unsigned cnt = GenTree::s_gtNodeCounts[op];
+ double pct = 100.0 * cnt / gtc;
+
+ if (siz > TREE_NODE_SZ_SMALL)
+ tot_large += cnt;
+ else
+ tot_small += cnt;
+
+ // Let's not show anything below a threshold
+ if (pct >= 0.5)
+ {
+ fprintf(fout, " GT_%-17s %7u (%4.1lf%%) %3u bytes each\n", GenTree::OpName((genTreeOps)op), cnt,
+ pct, siz);
+ rem_total -= cnt;
+ }
+ else
+ {
+ if (siz > TREE_NODE_SZ_SMALL)
+ rem_large += cnt;
+ else
+ rem_small += cnt;
+ }
+ }
+ if (rem_total > 0)
+ {
+ fprintf(fout, " All other GT_xxx ... %7u (%4.1lf%%) ... %4.1lf%% small + %4.1lf%% large\n", rem_total,
+ 100.0 * rem_total / gtc, 100.0 * rem_small / gtc, 100.0 * rem_large / gtc);
+ }
+ fprintf(fout, " -----------------------------------------------------\n");
+ fprintf(fout, " Total ....... %11u --ALL-- ... %4.1lf%% small + %4.1lf%% large\n", gtc,
+ 100.0 * tot_small / gtc, 100.0 * tot_large / gtc);
+ fprintf(fout, "\n");
+ }
+
+#endif // COUNT_AST_OPERS
+
#if DISPLAY_SIZES
if (grossVMsize && grossNCsize)
@@ -1367,17 +1484,23 @@ void Compiler::compShutdown()
#if MEASURE_MEM_ALLOC
-#ifdef DEBUG
- // Under debug, we only dump memory stats when the COMPlus_* variable is defined.
- // Under non-debug, we don't have the COMPlus_* variable, and we always dump it.
if (s_dspMemStats)
-#endif
{
fprintf(fout, "\nAll allocations:\n");
s_aggMemStats.Print(jitstdout);
fprintf(fout, "\nLargest method:\n");
s_maxCompMemStats.Print(jitstdout);
+
+ fprintf(fout, "\n");
+ fprintf(fout, "---------------------------------------------------\n");
+ fprintf(fout, "Distribution of total memory allocated per method (in KB):\n");
+ memAllocHist.dump(fout);
+
+ fprintf(fout, "\n");
+ fprintf(fout, "---------------------------------------------------\n");
+ fprintf(fout, "Distribution of total memory used per method (in KB):\n");
+ memUsedHist.dump(fout);
}
#endif // MEASURE_MEM_ALLOC
@@ -1452,100 +1575,8 @@ void Compiler::compDisplayStaticSizes(FILE* fout)
{
#if MEASURE_NODE_SIZE
- /*
- IMPORTANT: Use the following code to check the alignment of
- GenTree members (in a retail build, of course).
- */
-
- GenTree* gtDummy = nullptr;
-
- fprintf(fout, "\n");
- fprintf(fout, "Offset / size of gtOper = %2u / %2u\n", offsetof(GenTree, gtOper), sizeof(gtDummy->gtOper));
- fprintf(fout, "Offset / size of gtType = %2u / %2u\n", offsetof(GenTree, gtType), sizeof(gtDummy->gtType));
-#if FEATURE_ANYCSE
- fprintf(fout, "Offset / size of gtCSEnum = %2u / %2u\n", offsetof(GenTree, gtCSEnum),
- sizeof(gtDummy->gtCSEnum));
-#endif // FEATURE_ANYCSE
-#if ASSERTION_PROP
- fprintf(fout, "Offset / size of gtAssertionNum = %2u / %2u\n", offsetof(GenTree, gtAssertionNum),
- sizeof(gtDummy->gtAssertionNum));
-#endif // ASSERTION_PROP
-#if FEATURE_STACK_FP_X87
- fprintf(fout, "Offset / size of gtFPlvl = %2u / %2u\n", offsetof(GenTree, gtFPlvl),
- sizeof(gtDummy->gtFPlvl));
-#endif // FEATURE_STACK_FP_X87
- // TODO: The section that report GenTree sizes should be made into a public static member function of the GenTree
- // class (see https://github.com/dotnet/coreclr/pull/493)
- // fprintf(fout, "Offset / size of gtCostEx = %2u / %2u\n", offsetof(GenTree, _gtCostEx ),
- // sizeof(gtDummy->_gtCostEx ));
- // fprintf(fout, "Offset / size of gtCostSz = %2u / %2u\n", offsetof(GenTree, _gtCostSz ),
- // sizeof(gtDummy->_gtCostSz ));
- fprintf(fout, "Offset / size of gtFlags = %2u / %2u\n", offsetof(GenTree, gtFlags),
- sizeof(gtDummy->gtFlags));
- fprintf(fout, "Offset / size of gtVNPair = %2u / %2u\n", offsetof(GenTree, gtVNPair),
- sizeof(gtDummy->gtVNPair));
- fprintf(fout, "Offset / size of gtRsvdRegs = %2u / %2u\n", offsetof(GenTree, gtRsvdRegs),
- sizeof(gtDummy->gtRsvdRegs));
-#ifdef LEGACY_BACKEND
- fprintf(fout, "Offset / size of gtUsedRegs = %2u / %2u\n", offsetof(GenTree, gtUsedRegs),
- sizeof(gtDummy->gtUsedRegs));
-#endif // LEGACY_BACKEND
-#ifndef LEGACY_BACKEND
- fprintf(fout, "Offset / size of gtLsraInfo = %2u / %2u\n", offsetof(GenTree, gtLsraInfo),
- sizeof(gtDummy->gtLsraInfo));
-#endif // !LEGACY_BACKEND
- fprintf(fout, "Offset / size of gtNext = %2u / %2u\n", offsetof(GenTree, gtNext), sizeof(gtDummy->gtNext));
- fprintf(fout, "Offset / size of gtPrev = %2u / %2u\n", offsetof(GenTree, gtPrev), sizeof(gtDummy->gtPrev));
- fprintf(fout, "\n");
-
-#if SMALL_TREE_NODES
- fprintf(fout, "Small tree node size = %3u\n", TREE_NODE_SZ_SMALL);
-#endif // SMALL_TREE_NODES
- fprintf(fout, "Large tree node size = %3u\n", TREE_NODE_SZ_LARGE);
- fprintf(fout, "Size of GenTree = %3u\n", sizeof(GenTree));
- fprintf(fout, "Size of GenTreeUnOp = %3u\n", sizeof(GenTreeUnOp));
- fprintf(fout, "Size of GenTreeOp = %3u\n", sizeof(GenTreeOp));
- fprintf(fout, "Size of GenTreeVal = %3u\n", sizeof(GenTreeVal));
- fprintf(fout, "Size of GenTreeIntConCommon = %3u\n", sizeof(GenTreeIntConCommon));
- fprintf(fout, "Size of GenTreePhysReg = %3u\n", sizeof(GenTreePhysReg));
-#ifndef LEGACY_BACKEND
- fprintf(fout, "Size of GenTreeJumpTable = %3u\n", sizeof(GenTreeJumpTable));
-#endif // !LEGACY_BACKEND
- fprintf(fout, "Size of GenTreeIntCon = %3u\n", sizeof(GenTreeIntCon));
- fprintf(fout, "Size of GenTreeLngCon = %3u\n", sizeof(GenTreeLngCon));
- fprintf(fout, "Size of GenTreeDblCon = %3u\n", sizeof(GenTreeDblCon));
- fprintf(fout, "Size of GenTreeStrCon = %3u\n", sizeof(GenTreeStrCon));
- fprintf(fout, "Size of GenTreeLclVarCommon = %3u\n", sizeof(GenTreeLclVarCommon));
- fprintf(fout, "Size of GenTreeLclVar = %3u\n", sizeof(GenTreeLclVar));
- fprintf(fout, "Size of GenTreeLclFld = %3u\n", sizeof(GenTreeLclFld));
- fprintf(fout, "Size of GenTreeRegVar = %3u\n", sizeof(GenTreeRegVar));
- fprintf(fout, "Size of GenTreeCast = %3u\n", sizeof(GenTreeCast));
- fprintf(fout, "Size of GenTreeBox = %3u\n", sizeof(GenTreeBox));
- fprintf(fout, "Size of GenTreeField = %3u\n", sizeof(GenTreeField));
- fprintf(fout, "Size of GenTreeArgList = %3u\n", sizeof(GenTreeArgList));
- fprintf(fout, "Size of GenTreeColon = %3u\n", sizeof(GenTreeColon));
- fprintf(fout, "Size of GenTreeCall = %3u\n", sizeof(GenTreeCall));
- fprintf(fout, "Size of GenTreeCmpXchg = %3u\n", sizeof(GenTreeCmpXchg));
- fprintf(fout, "Size of GenTreeFptrVal = %3u\n", sizeof(GenTreeFptrVal));
- fprintf(fout, "Size of GenTreeQmark = %3u\n", sizeof(GenTreeQmark));
- fprintf(fout, "Size of GenTreeIntrinsic = %3u\n", sizeof(GenTreeIntrinsic));
- fprintf(fout, "Size of GenTreeIndex = %3u\n", sizeof(GenTreeIndex));
- fprintf(fout, "Size of GenTreeArrLen = %3u\n", sizeof(GenTreeArrLen));
- fprintf(fout, "Size of GenTreeBoundsChk = %3u\n", sizeof(GenTreeBoundsChk));
- fprintf(fout, "Size of GenTreeArrElem = %3u\n", sizeof(GenTreeArrElem));
- fprintf(fout, "Size of GenTreeAddrMode = %3u\n", sizeof(GenTreeAddrMode));
- fprintf(fout, "Size of GenTreeIndir = %3u\n", sizeof(GenTreeIndir));
- fprintf(fout, "Size of GenTreeStoreInd = %3u\n", sizeof(GenTreeStoreInd));
- fprintf(fout, "Size of GenTreeRetExpr = %3u\n", sizeof(GenTreeRetExpr));
- fprintf(fout, "Size of GenTreeStmt = %3u\n", sizeof(GenTreeStmt));
- fprintf(fout, "Size of GenTreeObj = %3u\n", sizeof(GenTreeObj));
- fprintf(fout, "Size of GenTreeClsVar = %3u\n", sizeof(GenTreeClsVar));
- fprintf(fout, "Size of GenTreeArgPlace = %3u\n", sizeof(GenTreeArgPlace));
- fprintf(fout, "Size of GenTreeLabel = %3u\n", sizeof(GenTreeLabel));
- fprintf(fout, "Size of GenTreePhiArg = %3u\n", sizeof(GenTreePhiArg));
- fprintf(fout, "Size of GenTreePutArgStk = %3u\n", sizeof(GenTreePutArgStk));
- fprintf(fout, "\n");
-#endif // MEASURE_NODE_SIZE
+ GenTree::DumpNodeSizes(fout);
+#endif
#if MEASURE_BLOCK_SIZE
@@ -1572,8 +1603,6 @@ void Compiler::compDisplayStaticSizes(FILE* fout)
sizeof(bbDummy->bbJumpDest));
fprintf(fout, "Offset / size of bbJumpSwt = %3u / %3u\n", offsetof(BasicBlock, bbJumpSwt),
sizeof(bbDummy->bbJumpSwt));
- fprintf(fout, "Offset / size of bbTreeList = %3u / %3u\n", offsetof(BasicBlock, bbTreeList),
- sizeof(bbDummy->bbTreeList));
fprintf(fout, "Offset / size of bbEntryState = %3u / %3u\n", offsetof(BasicBlock, bbEntryState),
sizeof(bbDummy->bbEntryState));
fprintf(fout, "Offset / size of bbStkTempsIn = %3u / %3u\n", offsetof(BasicBlock, bbStkTempsIn),
@@ -1618,12 +1647,8 @@ void Compiler::compDisplayStaticSizes(FILE* fout)
sizeof(bbDummy->bbHeapSsaNumIn));
fprintf(fout, "Offset / size of bbHeapSsaNumOut = %3u / %3u\n", offsetof(BasicBlock, bbHeapSsaNumOut),
sizeof(bbDummy->bbHeapSsaNumOut));
-
-#ifdef DEBUGGING_SUPPORT
fprintf(fout, "Offset / size of bbScope = %3u / %3u\n", offsetof(BasicBlock, bbScope),
sizeof(bbDummy->bbScope));
-#endif // DEBUGGING_SUPPORT
-
fprintf(fout, "Offset / size of bbCseGen = %3u / %3u\n", offsetof(BasicBlock, bbCseGen),
sizeof(bbDummy->bbCseGen));
fprintf(fout, "Offset / size of bbCseIn = %3u / %3u\n", offsetof(BasicBlock, bbCseIn),
@@ -1888,10 +1913,6 @@ void Compiler::compInit(ArenaAllocator* pAlloc, InlineInfo* inlineInfo)
SIMDVectorHandle = nullptr;
#endif
-#ifdef DEBUG
- inlRNG = nullptr;
-#endif
-
compUsesThrowHelper = false;
}
@@ -2244,14 +2265,14 @@ const char* Compiler::compLocalVarName(unsigned varNum, unsigned offs)
void Compiler::compSetProcessor()
{
- unsigned compileFlags = opts.eeFlags;
+ const JitFlags& jitFlags = *opts.jitFlags;
#if defined(_TARGET_ARM_)
info.genCPU = CPU_ARM;
#elif defined(_TARGET_AMD64_)
- info.genCPU = CPU_X64;
+ info.genCPU = CPU_X64;
#elif defined(_TARGET_X86_)
- if (compileFlags & CORJIT_FLG_TARGET_P4)
+ if (jitFlags.IsSet(JitFlags::JIT_FLAG_TARGET_P4))
info.genCPU = CPU_X86_PENTIUM_4;
else
info.genCPU = CPU_X86;
@@ -2262,33 +2283,66 @@ void Compiler::compSetProcessor()
//
CLANG_FORMAT_COMMENT_ANCHOR;
-#ifdef _TARGET_AMD64_
- opts.compUseFCOMI = false;
- opts.compUseCMOV = true;
- opts.compCanUseSSE2 = true;
+#ifdef _TARGET_XARCH_
+ opts.compCanUseSSE3_4 = false;
+ if (!jitFlags.IsSet(JitFlags::JIT_FLAG_PREJIT) && jitFlags.IsSet(JitFlags::JIT_FLAG_USE_SSE3_4))
+ {
+ if (JitConfig.EnableSSE3_4() != 0)
+ {
+ opts.compCanUseSSE3_4 = true;
+ }
+ }
#ifdef FEATURE_AVX_SUPPORT
// COMPlus_EnableAVX can be used to disable using AVX if available on a target machine.
// Note that FEATURE_AVX_SUPPORT is not enabled for ctpjit
opts.compCanUseAVX = false;
- if (((compileFlags & CORJIT_FLG_PREJIT) == 0) && ((compileFlags & CORJIT_FLG_USE_AVX2) != 0))
+ if (!jitFlags.IsSet(JitFlags::JIT_FLAG_PREJIT) && jitFlags.IsSet(JitFlags::JIT_FLAG_USE_AVX2))
{
if (JitConfig.EnableAVX() != 0)
{
opts.compCanUseAVX = true;
- if (!compIsForInlining())
- {
- codeGen->getEmitter()->SetUseAVX(true);
- }
}
}
-#endif
-#endif //_TARGET_AMD64_
+#endif // FEATURE_AVX_SUPPORT
-#ifdef _TARGET_X86_
- opts.compUseFCOMI = ((opts.eeFlags & CORJIT_FLG_USE_FCOMI) != 0);
- opts.compUseCMOV = ((opts.eeFlags & CORJIT_FLG_USE_CMOV) != 0);
- opts.compCanUseSSE2 = ((opts.eeFlags & CORJIT_FLG_USE_SSE2) != 0);
+ if (!compIsForInlining())
+ {
+#ifdef FEATURE_AVX_SUPPORT
+ if (opts.compCanUseAVX)
+ {
+ codeGen->getEmitter()->SetUseAVX(true);
+ }
+ else
+#endif // FEATURE_AVX_SUPPORT
+ if (opts.compCanUseSSE3_4)
+ {
+ codeGen->getEmitter()->SetUseSSE3_4(true);
+ }
+ }
+#endif // _TARGET_XARCH_
+
+#ifdef _TARGET_AMD64_
+ opts.compUseFCOMI = false;
+ opts.compUseCMOV = true;
+ opts.compCanUseSSE2 = true;
+#elif defined(_TARGET_X86_)
+ opts.compUseFCOMI = jitFlags.IsSet(JitFlags::JIT_FLAG_USE_FCOMI);
+ opts.compUseCMOV = jitFlags.IsSet(JitFlags::JIT_FLAG_USE_CMOV);
+ opts.compCanUseSSE2 = jitFlags.IsSet(JitFlags::JIT_FLAG_USE_SSE2);
+
+#if !defined(LEGACY_BACKEND) && !defined(FEATURE_CORECLR)
+ // RyuJIT/x86 requires SSE2 to be available: there is no support for generating floating-point
+ // code with x87 instructions. On .NET Core, the VM always tells us that SSE2 is available.
+ // However, on desktop, under ngen, (and presumably in the unlikely case you're actually
+ // running on a machine without SSE2), the VM does not set the SSE2 flag. We ignore this and
+ // go ahead and generate SSE2 code anyway.
+ if (!opts.compCanUseSSE2)
+ {
+ JITDUMP("VM didn't set CORJIT_FLG_USE_SSE2! Ignoring, and generating SSE2 code anyway.\n");
+ opts.compCanUseSSE2 = true;
+ }
+#endif // !defined(LEGACY_BACKEND) && !defined(FEATURE_CORECLR)
#ifdef DEBUG
if (opts.compUseFCOMI)
@@ -2296,7 +2350,9 @@ void Compiler::compSetProcessor()
if (opts.compUseCMOV)
opts.compUseCMOV = !compStressCompile(STRESS_USE_CMOV, 50);
- // Should we override the SSE2 setting
+#ifdef LEGACY_BACKEND
+
+ // Should we override the SSE2 setting?
enum
{
SSE2_FORCE_DISABLE = 0,
@@ -2310,7 +2366,17 @@ void Compiler::compSetProcessor()
opts.compCanUseSSE2 = true;
else if (opts.compCanUseSSE2)
opts.compCanUseSSE2 = !compStressCompile(STRESS_GENERIC_VARN, 50);
+
+#else // !LEGACY_BACKEND
+
+ // RyuJIT/x86 requires SSE2 to be available and hence
+ // don't turn off compCanUseSSE2 under stress.
+ assert(opts.compCanUseSSE2);
+
+#endif // !LEGACY_BACKEND
+
#endif // DEBUG
+
#endif // _TARGET_X86_
}
@@ -2378,31 +2444,36 @@ unsigned ReinterpretHexAsDecimal(unsigned in)
return result;
}
-void Compiler::compInitOptions(CORJIT_FLAGS* jitFlags)
+void Compiler::compInitOptions(JitFlags* jitFlags)
{
#ifdef UNIX_AMD64_ABI
opts.compNeedToAlignFrame = false;
#endif // UNIX_AMD64_ABI
memset(&opts, 0, sizeof(opts));
- unsigned compileFlags = jitFlags->corJitFlags;
-
if (compIsForInlining())
{
- assert((compileFlags & CORJIT_FLG_LOST_WHEN_INLINING) == 0);
- assert(compileFlags & CORJIT_FLG_SKIP_VERIFICATION);
+ // The following flags are lost when inlining. (They are removed in
+ // Compiler::fgInvokeInlineeCompiler().)
+ assert(!jitFlags->IsSet(JitFlags::JIT_FLAG_BBOPT));
+ assert(!jitFlags->IsSet(JitFlags::JIT_FLAG_BBINSTR));
+ assert(!jitFlags->IsSet(JitFlags::JIT_FLAG_PROF_ENTERLEAVE));
+ assert(!jitFlags->IsSet(JitFlags::JIT_FLAG_DEBUG_EnC));
+ assert(!jitFlags->IsSet(JitFlags::JIT_FLAG_DEBUG_INFO));
+
+ assert(jitFlags->IsSet(JitFlags::JIT_FLAG_SKIP_VERIFICATION));
}
opts.jitFlags = jitFlags;
- opts.eeFlags = compileFlags;
opts.compFlags = CLFLG_MAXOPT; // Default value is for full optimization
- if (opts.eeFlags & (CORJIT_FLG_DEBUG_CODE | CORJIT_FLG_MIN_OPT))
+ if (jitFlags->IsSet(JitFlags::JIT_FLAG_DEBUG_CODE) || jitFlags->IsSet(JitFlags::JIT_FLAG_MIN_OPT))
{
opts.compFlags = CLFLG_MINOPT;
}
// Don't optimize .cctors (except prejit) or if we're an inlinee
- else if (!(opts.eeFlags & CORJIT_FLG_PREJIT) && ((info.compFlags & FLG_CCTOR) == FLG_CCTOR) && !compIsForInlining())
+ else if (!jitFlags->IsSet(JitFlags::JIT_FLAG_PREJIT) && ((info.compFlags & FLG_CCTOR) == FLG_CCTOR) &&
+ !compIsForInlining())
{
opts.compFlags = CLFLG_MINOPT;
}
@@ -2414,32 +2485,31 @@ void Compiler::compInitOptions(CORJIT_FLAGS* jitFlags)
// If the EE sets SIZE_OPT or if we are compiling a Class constructor
// we will optimize for code size at the expense of speed
//
- if ((opts.eeFlags & CORJIT_FLG_SIZE_OPT) || ((info.compFlags & FLG_CCTOR) == FLG_CCTOR))
+ if (jitFlags->IsSet(JitFlags::JIT_FLAG_SIZE_OPT) || ((info.compFlags & FLG_CCTOR) == FLG_CCTOR))
{
opts.compCodeOpt = SMALL_CODE;
}
//
// If the EE sets SPEED_OPT we will optimize for speed at the expense of code size
//
- else if (opts.eeFlags & CORJIT_FLG_SPEED_OPT)
+ else if (jitFlags->IsSet(JitFlags::JIT_FLAG_SPEED_OPT))
{
opts.compCodeOpt = FAST_CODE;
- assert((opts.eeFlags & CORJIT_FLG_SIZE_OPT) == 0);
+ assert(!jitFlags->IsSet(JitFlags::JIT_FLAG_SIZE_OPT));
}
-//-------------------------------------------------------------------------
+ //-------------------------------------------------------------------------
+
+ opts.compDbgCode = jitFlags->IsSet(JitFlags::JIT_FLAG_DEBUG_CODE);
+ opts.compDbgInfo = jitFlags->IsSet(JitFlags::JIT_FLAG_DEBUG_INFO);
+ opts.compDbgEnC = jitFlags->IsSet(JitFlags::JIT_FLAG_DEBUG_EnC);
-#ifdef DEBUGGING_SUPPORT
- opts.compDbgCode = (opts.eeFlags & CORJIT_FLG_DEBUG_CODE) != 0;
- opts.compDbgInfo = (opts.eeFlags & CORJIT_FLG_DEBUG_INFO) != 0;
- opts.compDbgEnC = (opts.eeFlags & CORJIT_FLG_DEBUG_EnC) != 0;
#if REGEN_SHORTCUTS || REGEN_CALLPAT
// We never want to have debugging enabled when regenerating GC encoding patterns
opts.compDbgCode = false;
opts.compDbgInfo = false;
opts.compDbgEnC = false;
#endif
-#endif
compSetProcessor();
@@ -2473,7 +2543,7 @@ void Compiler::compInitOptions(CORJIT_FLAGS* jitFlags)
#ifdef DEBUG
const JitConfigValues::MethodSet* pfAltJit;
- if (opts.eeFlags & CORJIT_FLG_PREJIT)
+ if (jitFlags->IsSet(JitFlags::JIT_FLAG_PREJIT))
{
pfAltJit = &JitConfig.AltJitNgen();
}
@@ -2498,7 +2568,7 @@ void Compiler::compInitOptions(CORJIT_FLAGS* jitFlags)
#else // !DEBUG
const char* altJitVal;
- if (opts.eeFlags & CORJIT_FLG_PREJIT)
+ if (jitFlags->IsSet(JitFlags::JIT_FLAG_PREJIT))
{
altJitVal = JitConfig.AltJitNgen().list();
}
@@ -2602,7 +2672,7 @@ void Compiler::compInitOptions(CORJIT_FLAGS* jitFlags)
//
if (!compIsForInlining())
{
- if (opts.eeFlags & CORJIT_FLG_PREJIT)
+ if (jitFlags->IsSet(JitFlags::JIT_FLAG_PREJIT))
{
if (JitConfig.NgenDump().contains(info.compMethodName, info.compClassName, &info.compMethodInfo->args))
{
@@ -2952,10 +3022,8 @@ void Compiler::compInitOptions(CORJIT_FLAGS* jitFlags)
#endif // DEBUG
#ifdef FEATURE_SIMD
-#ifdef _TARGET_AMD64_
- // Minimum bar for availing SIMD benefits is SSE2 on AMD64.
- featureSIMD = ((opts.eeFlags & CORJIT_FLG_FEATURE_SIMD) != 0);
-#endif // _TARGET_AMD64_
+ // Minimum bar for availing SIMD benefits is SSE2 on AMD64/x86.
+ featureSIMD = jitFlags->IsSet(JitFlags::JIT_FLAG_FEATURE_SIMD);
#endif // FEATURE_SIMD
if (compIsForInlining() || compIsForImportOnly())
@@ -2978,23 +3046,26 @@ void Compiler::compInitOptions(CORJIT_FLAGS* jitFlags)
opts.compTailCallLoopOpt = true;
#endif
-#ifdef DEBUG
- opts.dspInstrs = false;
- opts.dspEmit = false;
- opts.dspLines = false;
- opts.varNames = false;
- opts.dmpHex = false;
- opts.disAsm = false;
- opts.disAsmSpilled = false;
- opts.disDiffable = false;
- opts.dspCode = false;
- opts.dspEHTable = false;
- opts.dspGCtbls = false;
- opts.disAsm2 = false;
- opts.dspUnwind = false;
- s_dspMemStats = false;
- opts.compLongAddress = false;
+#ifdef PROFILING_SUPPORTED
opts.compJitELTHookEnabled = false;
+#endif // PROFILING_SUPPORTED
+
+#ifdef DEBUG
+ opts.dspInstrs = false;
+ opts.dspEmit = false;
+ opts.dspLines = false;
+ opts.varNames = false;
+ opts.dmpHex = false;
+ opts.disAsm = false;
+ opts.disAsmSpilled = false;
+ opts.disDiffable = false;
+ opts.dspCode = false;
+ opts.dspEHTable = false;
+ opts.dspGCtbls = false;
+ opts.disAsm2 = false;
+ opts.dspUnwind = false;
+ opts.compLongAddress = false;
+ opts.optRepeat = false;
#ifdef LATE_DISASM
opts.doLateDisasm = false;
@@ -3007,7 +3078,7 @@ void Compiler::compInitOptions(CORJIT_FLAGS* jitFlags)
//
if (!altJitConfig || opts.altJit)
{
- if (opts.eeFlags & CORJIT_FLG_PREJIT)
+ if (jitFlags->IsSet(JitFlags::JIT_FLAG_PREJIT))
{
if ((JitConfig.NgenOrder() & 1) == 1)
{
@@ -3084,14 +3155,14 @@ void Compiler::compInitOptions(CORJIT_FLAGS* jitFlags)
opts.dspDiffable = true;
}
- if (JitConfig.DisplayMemStats() != 0)
+ if (JitConfig.JitLongAddress() != 0)
{
- s_dspMemStats = true;
+ opts.compLongAddress = true;
}
- if (JitConfig.JitLongAddress() != 0)
+ if (JitConfig.JitOptRepeat().contains(info.compMethodName, info.compClassName, &info.compMethodInfo->args))
{
- opts.compLongAddress = true;
+ opts.optRepeat = true;
}
}
@@ -3152,7 +3223,6 @@ void Compiler::compInitOptions(CORJIT_FLAGS* jitFlags)
//-------------------------------------------------------------------------
-#ifdef DEBUGGING_SUPPORT
#ifdef DEBUG
assert(!codeGen->isGCTypeFixed());
opts.compGcChecks = (JitConfig.JitGCChecks() != 0) || compStressCompile(STRESS_GENERIC_VARN, 5);
@@ -3173,11 +3243,15 @@ void Compiler::compInitOptions(CORJIT_FLAGS* jitFlags)
opts.compStackCheckOnCall = (dwJitStackChecks & DWORD(STACK_CHECK_ON_CALL)) != 0;
#endif
+#if MEASURE_MEM_ALLOC
+ s_dspMemStats = (JitConfig.DisplayMemStats() != 0);
+#endif
+
#ifdef PROFILING_SUPPORTED
- opts.compNoPInvokeInlineCB = (opts.eeFlags & CORJIT_FLG_PROF_NO_PINVOKE_INLINE) ? true : false;
+ opts.compNoPInvokeInlineCB = jitFlags->IsSet(JitFlags::JIT_FLAG_PROF_NO_PINVOKE_INLINE);
// Cache the profiler handle
- if (opts.eeFlags & CORJIT_FLG_PROF_ENTERLEAVE)
+ if (jitFlags->IsSet(JitFlags::JIT_FLAG_PROF_ENTERLEAVE))
{
BOOL hookNeeded;
BOOL indirected;
@@ -3192,11 +3266,8 @@ void Compiler::compInitOptions(CORJIT_FLAGS* jitFlags)
compProfilerMethHndIndirected = false;
}
-#if defined(_TARGET_ARM_) || defined(_TARGET_AMD64_)
- // Right now this ELT hook option is enabled only for arm and amd64
-
- // Honour complus_JitELTHookEnabled only if VM has not asked us to generate profiler
- // hooks in the first place. That is, Override VM only if it hasn't asked for a
+ // Honour COMPlus_JitELTHookEnabled only if VM has not asked us to generate profiler
+ // hooks in the first place. That is, override VM only if it hasn't asked for a
// profiler callback for this method.
if (!compProfilerHookNeeded && (JitConfig.JitELTHookEnabled() != 0))
{
@@ -3209,7 +3280,6 @@ void Compiler::compInitOptions(CORJIT_FLAGS* jitFlags)
compProfilerMethHnd = (void*)DummyProfilerELTStub;
compProfilerMethHndIndirected = false;
}
-#endif // _TARGET_ARM_ || _TARGET_AMD64_
#endif // PROFILING_SUPPORTED
@@ -3226,10 +3296,9 @@ void Compiler::compInitOptions(CORJIT_FLAGS* jitFlags)
}
#endif
- opts.compMustInlinePInvokeCalli = (opts.eeFlags & CORJIT_FLG_IL_STUB) ? true : false;
+ opts.compMustInlinePInvokeCalli = jitFlags->IsSet(JitFlags::JIT_FLAG_IL_STUB);
opts.compScopeInfo = opts.compDbgInfo;
-#endif // DEBUGGING_SUPPORT
#ifdef LATE_DISASM
codeGen->getDisAssembler().disOpenForLateDisAsm(info.compMethodName, info.compClassName,
@@ -3239,7 +3308,7 @@ void Compiler::compInitOptions(CORJIT_FLAGS* jitFlags)
//-------------------------------------------------------------------------
#if RELOC_SUPPORT
- opts.compReloc = (opts.eeFlags & CORJIT_FLG_RELOC) ? true : false;
+ opts.compReloc = jitFlags->IsSet(JitFlags::JIT_FLAG_RELOC);
#endif
#ifdef DEBUG
@@ -3249,7 +3318,7 @@ void Compiler::compInitOptions(CORJIT_FLAGS* jitFlags)
#endif
#endif // DEBUG
- opts.compProcedureSplitting = (opts.eeFlags & CORJIT_FLG_PROCSPLIT) ? true : false;
+ opts.compProcedureSplitting = jitFlags->IsSet(JitFlags::JIT_FLAG_PROCSPLIT);
#ifdef _TARGET_ARM64_
// TODO-ARM64-NYI: enable hot/cold splitting
@@ -3294,7 +3363,7 @@ void Compiler::compInitOptions(CORJIT_FLAGS* jitFlags)
fgProfileBuffer = nullptr;
fgProfileData_ILSizeMismatch = false;
fgNumProfileRuns = 0;
- if (opts.eeFlags & CORJIT_FLG_BBOPT)
+ if (jitFlags->IsSet(JitFlags::JIT_FLAG_BBOPT))
{
assert(!compIsForInlining());
HRESULT hr;
@@ -3365,7 +3434,7 @@ void Compiler::compInitOptions(CORJIT_FLAGS* jitFlags)
printf("OPTIONS: compProcedureSplitting = %s\n", dspBool(opts.compProcedureSplitting));
printf("OPTIONS: compProcedureSplittingEH = %s\n", dspBool(opts.compProcedureSplittingEH));
- if ((opts.eeFlags & CORJIT_FLG_BBOPT) && fgHaveProfileData())
+ if (jitFlags->IsSet(JitFlags::JIT_FLAG_BBOPT) && fgHaveProfileData())
{
printf("OPTIONS: using real profile data\n");
}
@@ -3375,7 +3444,7 @@ void Compiler::compInitOptions(CORJIT_FLAGS* jitFlags)
printf("OPTIONS: discarded IBC profile data due to mismatch in ILSize\n");
}
- if (opts.eeFlags & CORJIT_FLG_PREJIT)
+ if (jitFlags->IsSet(JitFlags::JIT_FLAG_PREJIT))
{
printf("OPTIONS: Jit invoked for ngen\n");
}
@@ -3384,11 +3453,11 @@ void Compiler::compInitOptions(CORJIT_FLAGS* jitFlags)
#endif
opts.compGCPollType = GCPOLL_NONE;
- if (opts.eeFlags & CORJIT_FLG_GCPOLL_CALLS)
+ if (jitFlags->IsSet(JitFlags::JIT_FLAG_GCPOLL_CALLS))
{
opts.compGCPollType = GCPOLL_CALL;
}
- else if (opts.eeFlags & CORJIT_FLG_GCPOLL_INLINE)
+ else if (jitFlags->IsSet(JitFlags::JIT_FLAG_GCPOLL_INLINE))
{
// make sure that the EE didn't set both flags.
assert(opts.compGCPollType == GCPOLL_NONE);
@@ -3568,14 +3637,11 @@ void Compiler::compInitDebuggingInfo()
info.compVarScopesCount = 0;
-#ifdef DEBUGGING_SUPPORT
if (opts.compScopeInfo)
-#endif
{
eeGetVars();
}
-#ifdef DEBUGGING_SUPPORT
compInitVarScopeMap();
if (opts.compScopeInfo || opts.compDbgCode)
@@ -3598,7 +3664,6 @@ void Compiler::compInitDebuggingInfo()
JITDUMP("Debuggable code - Add new BB%02u to perform initialization of variables [%08X]\n", fgFirstBB->bbNum,
dspPtr(fgFirstBB));
}
-#endif // DEBUGGING_SUPPORT
/*-------------------------------------------------------------------------
*
@@ -3617,9 +3682,7 @@ void Compiler::compInitDebuggingInfo()
info.compStmtOffsetsCount = 0;
-#ifdef DEBUGGING_SUPPORT
if (opts.compDbgInfo)
-#endif
{
/* Get hold of the line# records, if there are any */
@@ -3661,12 +3724,9 @@ void Compiler::compInitDebuggingInfo()
void Compiler::compSetOptimizationLevel()
{
- unsigned compileFlags;
bool theMinOptsValue;
unsigned jitMinOpts;
- compileFlags = opts.eeFlags;
-
if (compIsForInlining())
{
theMinOptsValue = impInlineInfo->InlinerCompiler->opts.MinOpts();
@@ -3757,13 +3817,40 @@ void Compiler::compSetOptimizationLevel()
}
}
+#if 0
+ // The code in this #if can be used to debug optimization issues according to method hash.
+ // To use, uncomment, rebuild and set environment variables minoptshashlo and minoptshashhi.
+#ifdef DEBUG
+ unsigned methHash = info.compMethodHash();
+ char* lostr = getenv("minoptshashlo");
+ unsigned methHashLo = 0;
+ if (lostr != nullptr)
+ {
+ sscanf_s(lostr, "%x", &methHashLo);
+ char* histr = getenv("minoptshashhi");
+ unsigned methHashHi = UINT32_MAX;
+ if (histr != nullptr)
+ {
+ sscanf_s(histr, "%x", &methHashHi);
+ if (methHash >= methHashLo && methHash <= methHashHi)
+ {
+ printf("MinOpts for method %s, hash = 0x%x.\n",
+ info.compFullName, info.compMethodHash());
+ printf(""); // in our logic this causes a flush
+ theMinOptsValue = true;
+ }
+ }
+ }
+#endif
+#endif
+
if (compStressCompile(STRESS_MIN_OPTS, 5))
{
theMinOptsValue = true;
}
// For PREJIT we never drop down to MinOpts
// unless unless CLFLG_MINOPT is set
- else if (!(compileFlags & CORJIT_FLG_PREJIT))
+ else if (!opts.jitFlags->IsSet(JitFlags::JIT_FLAG_PREJIT))
{
if ((unsigned)JitConfig.JitMinOptsCodeSize() < info.compILCodeSize)
{
@@ -3805,7 +3892,7 @@ void Compiler::compSetOptimizationLevel()
// Retail check if we should force Minopts due to the complexity of the method
// For PREJIT we never drop down to MinOpts
// unless unless CLFLG_MINOPT is set
- if (!theMinOptsValue && !(compileFlags & CORJIT_FLG_PREJIT) &&
+ if (!theMinOptsValue && !opts.jitFlags->IsSet(JitFlags::JIT_FLAG_PREJIT) &&
((DEFAULT_MIN_OPTS_CODE_SIZE < info.compILCodeSize) || (DEFAULT_MIN_OPTS_INSTR_COUNT < opts.instrCount) ||
(DEFAULT_MIN_OPTS_BB_COUNT < fgBBcount) || (DEFAULT_MIN_OPTS_LV_NUM_COUNT < lvaCount) ||
(DEFAULT_MIN_OPTS_LV_REF_COUNT < opts.lvRefCount)))
@@ -3828,14 +3915,14 @@ void Compiler::compSetOptimizationLevel()
unsigned methHash = info.compMethodHash();
char* lostr = getenv("opthashlo");
unsigned methHashLo = 0;
- if (lostr != NULL)
+ if (lostr != NULL)
{
sscanf_s(lostr, "%x", &methHashLo);
// methHashLo = (unsigned(atoi(lostr)) << 2); // So we don't have to use negative numbers.
}
char* histr = getenv("opthashhi");
unsigned methHashHi = UINT32_MAX;
- if (histr != NULL)
+ if (histr != NULL)
{
sscanf_s(histr, "%x", &methHashHi);
// methHashHi = (unsigned(atoi(histr)) << 2); // So we don't have to use negative numbers.
@@ -3883,27 +3970,27 @@ _SetMinOpts:
}
#if !defined(_TARGET_AMD64_)
- // The VM sets CORJIT_FLG_FRAMED for two reasons: (1) the COMPlus_JitFramed variable is set, or
+ // The VM sets JitFlags::JIT_FLAG_FRAMED for two reasons: (1) the COMPlus_JitFramed variable is set, or
// (2) the function is marked "noinline". The reason for #2 is that people mark functions
// noinline to ensure the show up on in a stack walk. But for AMD64, we don't need a frame
// pointer for the frame to show up in stack walk.
- if (compileFlags & CORJIT_FLG_FRAMED)
+ if (opts.jitFlags->IsSet(JitFlags::JIT_FLAG_FRAMED))
codeGen->setFrameRequired(true);
#endif
- if (compileFlags & CORJIT_FLG_RELOC)
+ if (opts.jitFlags->IsSet(JitFlags::JIT_FLAG_RELOC))
{
codeGen->genAlignLoops = false; // loop alignment not supported for prejitted code
- // The zapper doesn't set CORJIT_FLG_ALIGN_LOOPS, and there is
+ // The zapper doesn't set JitFlags::JIT_FLAG_ALIGN_LOOPS, and there is
// no reason for it to set it as the JIT doesn't currently support loop alignment
// for prejitted images. (The JIT doesn't know the final address of the code, hence
// it can't align code based on unknown addresses.)
- assert((compileFlags & CORJIT_FLG_ALIGN_LOOPS) == 0);
+ assert(!opts.jitFlags->IsSet(JitFlags::JIT_FLAG_ALIGN_LOOPS));
}
else
{
- codeGen->genAlignLoops = (compileFlags & CORJIT_FLG_ALIGN_LOOPS) != 0;
+ codeGen->genAlignLoops = opts.jitFlags->IsSet(JitFlags::JIT_FLAG_ALIGN_LOOPS);
}
}
@@ -4075,7 +4162,7 @@ void Compiler::compFunctionTraceEnd(void* methodCodePtr, ULONG methodCodeSize, b
// For an overview of the structure of the JIT, see:
// https://github.com/dotnet/coreclr/blob/master/Documentation/botr/ryujit-overview.md
//
-void Compiler::compCompile(void** methodCodePtr, ULONG* methodCodeSize, CORJIT_FLAGS* compileFlags)
+void Compiler::compCompile(void** methodCodePtr, ULONG* methodCodeSize, JitFlags* compileFlags)
{
if (compIsForInlining())
{
@@ -4112,26 +4199,36 @@ void Compiler::compCompile(void** methodCodePtr, ULONG* methodCodeSize, CORJIT_F
fgRemovePreds();
}
+ EndPhase(PHASE_IMPORTATION);
+
if (compIsForInlining())
{
/* Quit inlining if fgImport() failed for any reason. */
- if (compDonotInline())
+ if (!compDonotInline())
{
- return;
+ /* Filter out unimported BBs */
+
+ fgRemoveEmptyBlocks();
}
- /* Filter out unimported BBs */
+ EndPhase(PHASE_POST_IMPORT);
- fgRemoveEmptyBlocks();
+#ifdef FEATURE_JIT_METHOD_PERF
+ if (pCompJitTimer != nullptr)
+ {
+#if MEASURE_CLRAPI_CALLS
+ EndPhase(PHASE_CLR_API);
+#endif
+ pCompJitTimer->Terminate(this, CompTimeSummaryInfo::s_compTimeSummary, false);
+ }
+#endif
return;
}
assert(!compDonotInline());
- EndPhase(PHASE_IMPORTATION);
-
// Maybe the caller was not interested in generating code
if (compIsForImportOnly())
{
@@ -4145,7 +4242,7 @@ void Compiler::compCompile(void** methodCodePtr, ULONG* methodCodeSize, CORJIT_F
fgRemoveEH();
#endif // !FEATURE_EH
- if (compileFlags->corJitFlags & CORJIT_FLG_BBINSTR)
+ if (compileFlags->IsSet(JitFlags::JIT_FLAG_BBINSTR))
{
fgInstrumentMethod();
}
@@ -4180,7 +4277,7 @@ void Compiler::compCompile(void** methodCodePtr, ULONG* methodCodeSize, CORJIT_F
/* Massage the trees so that we can generate code out of them */
fgMorph();
- EndPhase(PHASE_MORPH);
+ EndPhase(PHASE_MORPH_END);
/* GS security checks for unsafe buffers */
if (getNeedsGSSecurityCookie())
@@ -4336,6 +4433,7 @@ void Compiler::compCompile(void** methodCodePtr, ULONG* methodCodeSize, CORJIT_F
bool doCopyProp = true;
bool doAssertionProp = true;
bool doRangeAnalysis = true;
+ int iterations = 1;
#ifdef DEBUG
doSsa = (JitConfig.JitDoSsa() != 0);
@@ -4345,72 +4443,88 @@ void Compiler::compCompile(void** methodCodePtr, ULONG* methodCodeSize, CORJIT_F
doCopyProp = doValueNum && (JitConfig.JitDoCopyProp() != 0);
doAssertionProp = doValueNum && (JitConfig.JitDoAssertionProp() != 0);
doRangeAnalysis = doAssertionProp && (JitConfig.JitDoRangeAnalysis() != 0);
-#endif
- if (doSsa)
+ if (opts.optRepeat)
{
- fgSsaBuild();
- EndPhase(PHASE_BUILD_SSA);
+ iterations = JitConfig.JitOptRepeatCount();
}
+#endif
- if (doEarlyProp)
+ while (iterations > 0)
{
- /* Propagate array length and rewrite getType() method call */
- optEarlyProp();
- EndPhase(PHASE_EARLY_PROP);
- }
+ if (doSsa)
+ {
+ fgSsaBuild();
+ EndPhase(PHASE_BUILD_SSA);
+ }
- if (doValueNum)
- {
- fgValueNumber();
- EndPhase(PHASE_VALUE_NUMBER);
- }
+ if (doEarlyProp)
+ {
+ /* Propagate array length and rewrite getType() method call */
+ optEarlyProp();
+ EndPhase(PHASE_EARLY_PROP);
+ }
- if (doLoopHoisting)
- {
- /* Hoist invariant code out of loops */
- optHoistLoopCode();
- EndPhase(PHASE_HOIST_LOOP_CODE);
- }
+ if (doValueNum)
+ {
+ fgValueNumber();
+ EndPhase(PHASE_VALUE_NUMBER);
+ }
- if (doCopyProp)
- {
- /* Perform VN based copy propagation */
- optVnCopyProp();
- EndPhase(PHASE_VN_COPY_PROP);
- }
+ if (doLoopHoisting)
+ {
+ /* Hoist invariant code out of loops */
+ optHoistLoopCode();
+ EndPhase(PHASE_HOIST_LOOP_CODE);
+ }
+
+ if (doCopyProp)
+ {
+ /* Perform VN based copy propagation */
+ optVnCopyProp();
+ EndPhase(PHASE_VN_COPY_PROP);
+ }
#if FEATURE_ANYCSE
- /* Remove common sub-expressions */
- optOptimizeCSEs();
+ /* Remove common sub-expressions */
+ optOptimizeCSEs();
#endif // FEATURE_ANYCSE
#if ASSERTION_PROP
- if (doAssertionProp)
- {
- /* Assertion propagation */
- optAssertionPropMain();
- EndPhase(PHASE_ASSERTION_PROP_MAIN);
- }
+ if (doAssertionProp)
+ {
+ /* Assertion propagation */
+ optAssertionPropMain();
+ EndPhase(PHASE_ASSERTION_PROP_MAIN);
+ }
- if (doRangeAnalysis)
- {
- /* Optimize array index range checks */
- RangeCheck rc(this);
- rc.OptimizeRangeChecks();
- EndPhase(PHASE_OPTIMIZE_INDEX_CHECKS);
- }
+ if (doRangeAnalysis)
+ {
+ /* Optimize array index range checks */
+ RangeCheck rc(this);
+ rc.OptimizeRangeChecks();
+ EndPhase(PHASE_OPTIMIZE_INDEX_CHECKS);
+ }
#endif // ASSERTION_PROP
- /* update the flowgraph if we modified it during the optimization phase*/
- if (fgModified)
- {
- fgUpdateFlowGraph();
- EndPhase(PHASE_UPDATE_FLOW_GRAPH);
+ /* update the flowgraph if we modified it during the optimization phase*/
+ if (fgModified)
+ {
+ fgUpdateFlowGraph();
+ EndPhase(PHASE_UPDATE_FLOW_GRAPH);
+
+ // Recompute the edge weight if we have modified the flow graph
+ fgComputeEdgeWeights();
+ EndPhase(PHASE_COMPUTE_EDGE_WEIGHTS2);
+ }
- // Recompute the edge weight if we have modified the flow graph
- fgComputeEdgeWeights();
- EndPhase(PHASE_COMPUTE_EDGE_WEIGHTS2);
+ // Iterate if requested, resetting annotations first.
+ if (--iterations == 0)
+ {
+ break;
+ }
+ ResetOptAnnotations();
+ RecomputeLoopInfo();
}
}
@@ -4540,7 +4654,12 @@ void Compiler::compCompile(void** methodCodePtr, ULONG* methodCodeSize, CORJIT_F
#ifdef FEATURE_JIT_METHOD_PERF
if (pCompJitTimer)
- pCompJitTimer->Terminate(this, CompTimeSummaryInfo::s_compTimeSummary);
+ {
+#if MEASURE_CLRAPI_CALLS
+ EndPhase(PHASE_CLR_API);
+#endif
+ pCompJitTimer->Terminate(this, CompTimeSummaryInfo::s_compTimeSummary, true);
+ }
#endif
RecordStateAtEndOfCompilation();
@@ -4569,6 +4688,82 @@ void Compiler::compCompile(void** methodCodePtr, ULONG* methodCodeSize, CORJIT_F
#endif // FUNC_INFO_LOGGING
}
+//------------------------------------------------------------------------
+// ResetOptAnnotations: Clear annotations produced during global optimizations.
+//
+// Notes:
+// The intent of this method is to clear any information typically assumed
+// to be set only once; it is used between iterations when JitOptRepeat is
+// in effect.
+
+void Compiler::ResetOptAnnotations()
+{
+ assert(opts.optRepeat);
+ assert(JitConfig.JitOptRepeatCount() > 0);
+ fgResetForSsa();
+ vnStore = nullptr;
+ m_opAsgnVarDefSsaNums = nullptr;
+ m_blockToEHPreds = nullptr;
+ fgSsaPassesCompleted = 0;
+ fgVNPassesCompleted = 0;
+
+ for (BasicBlock* block = fgFirstBB; block != nullptr; block = block->bbNext)
+ {
+ for (GenTreeStmt* stmt = block->firstStmt(); stmt != nullptr; stmt = stmt->getNextStmt())
+ {
+ stmt->gtFlags &= ~GTF_STMT_HAS_CSE;
+
+ for (GenTreePtr tree = stmt->gtStmt.gtStmtList; tree != nullptr; tree = tree->gtNext)
+ {
+ tree->ClearVN();
+ tree->ClearAssertion();
+ tree->gtCSEnum = NO_CSE;
+
+ // Clear any *_ASG_LHS flags -- these are set during SSA construction,
+ // and the heap live-in calculation depends on them being unset coming
+ // into SSA construction (without clearing them, a block that has a
+ // heap def via one of these before any heap use is treated as not having
+ // an upwards-exposed heap use, even though subsequent heap uses may not
+ // be killed by the store; this seems to be a bug, worked around here).
+ if (tree->OperIsIndir())
+ {
+ tree->gtFlags &= ~GTF_IND_ASG_LHS;
+ }
+ else if (tree->OperGet() == GT_CLS_VAR)
+ {
+ tree->gtFlags &= ~GTF_CLS_VAR_ASG_LHS;
+ }
+ }
+ }
+ }
+}
+
+//------------------------------------------------------------------------
+// RecomputeLoopInfo: Recompute loop annotations between opt-repeat iterations.
+//
+// Notes:
+// The intent of this method is to update loop structure annotations, and those
+// they depend on; these annotations may have become stale during optimization,
+// and need to be up-to-date before running another iteration of optimizations.
+
+void Compiler::RecomputeLoopInfo()
+{
+ assert(opts.optRepeat);
+ assert(JitConfig.JitOptRepeatCount() > 0);
+ // Recompute reachability sets, dominators, and loops.
+ optLoopCount = 0;
+ fgDomsComputed = false;
+ for (BasicBlock* block = fgFirstBB; block != nullptr; block = block->bbNext)
+ {
+ block->bbFlags &= ~BBF_LOOP_FLAGS;
+ }
+ fgComputeReachability();
+ // Rebuild the loop tree annotations themselves. Since this is performed as
+ // part of 'optOptimizeLoops', this will also re-perform loop rotation, but
+ // not other optimizations, as the others are not part of 'optOptimizeLoops'.
+ optOptimizeLoops();
+}
+
/*****************************************************************************/
void Compiler::ProcessShutdownWork(ICorStaticInfo* statInfo)
{
@@ -4696,11 +4891,13 @@ int Compiler::compCompile(CORINFO_METHOD_HANDLE methodHnd,
CORINFO_METHOD_INFO* methodInfo,
void** methodCodePtr,
ULONG* methodCodeSize,
- CORJIT_FLAGS* compileFlags)
+ JitFlags* compileFlags)
{
#ifdef FEATURE_JIT_METHOD_PERF
static bool checkedForJitTimeLog = false;
+ pCompJitTimer = nullptr;
+
if (!checkedForJitTimeLog)
{
// Call into VM to get the config strings. FEATURE_JIT_METHOD_PERF is enabled for
@@ -4713,14 +4910,10 @@ int Compiler::compCompile(CORINFO_METHOD_HANDLE methodHnd,
checkedForJitTimeLog = true;
}
- if ((Compiler::compJitTimeLogFilename != NULL) || (JitTimeLogCsv() != NULL))
+ if ((Compiler::compJitTimeLogFilename != nullptr) || (JitTimeLogCsv() != nullptr))
{
pCompJitTimer = JitTimer::Create(this, methodInfo->ILCodeSize);
}
- else
- {
- pCompJitTimer = NULL;
- }
#endif // FEATURE_JIT_METHOD_PERF
#ifdef DEBUG
@@ -4862,7 +5055,7 @@ int Compiler::compCompile(CORINFO_METHOD_HANDLE methodHnd,
// Set this before the first 'BADCODE'
// Skip verification where possible
- tiVerificationNeeded = (compileFlags->corJitFlags & CORJIT_FLG_SKIP_VERIFICATION) == 0;
+ tiVerificationNeeded = !compileFlags->IsSet(JitFlags::JIT_FLAG_SKIP_VERIFICATION);
assert(!compIsForInlining() || !tiVerificationNeeded); // Inlinees must have been verified.
@@ -4893,8 +5086,8 @@ int Compiler::compCompile(CORINFO_METHOD_HANDLE methodHnd,
case CORINFO_VERIFICATION_CAN_SKIP:
// The VM should first verify the open instantiation. If unverifiable code
- // is detected, it should pass in CORJIT_FLG_SKIP_VERIFICATION.
- assert(!"The VM should have used CORJIT_FLG_SKIP_VERIFICATION");
+ // is detected, it should pass in JitFlags::JIT_FLAG_SKIP_VERIFICATION.
+ assert(!"The VM should have used JitFlags::JIT_FLAG_SKIP_VERIFICATION");
tiVerificationNeeded = false;
break;
@@ -4933,7 +5126,7 @@ int Compiler::compCompile(CORINFO_METHOD_HANDLE methodHnd,
CORINFO_METHOD_INFO* methodInfo;
void** methodCodePtr;
ULONG* methodCodeSize;
- CORJIT_FLAGS* compileFlags;
+ JitFlags* compileFlags;
CorInfoInstantiationVerification instVerInfo;
int result;
@@ -5000,6 +5193,8 @@ void Compiler::compCompileFinish()
// Make the updates.
genMemStats.nraTotalSizeAlloc = compGetAllocator()->getTotalBytesAllocated();
genMemStats.nraTotalSizeUsed = compGetAllocator()->getTotalBytesUsed();
+ memAllocHist.record((unsigned)((genMemStats.nraTotalSizeAlloc + 1023) / 1024));
+ memUsedHist.record((unsigned)((genMemStats.nraTotalSizeUsed + 1023) / 1024));
s_aggMemStats.Add(genMemStats);
if (genMemStats.allocSz > s_maxCompMemStats.allocSz)
{
@@ -5038,6 +5233,7 @@ void Compiler::compCompileFinish()
// the prolog which requires memory
(info.compLocalsCount <= 32) && (!opts.MinOpts()) && // We may have too many local variables, etc
(getJitStressLevel() == 0) && // We need extra memory for stress
+ !opts.optRepeat && // We need extra memory to repeat opts
!compAllocator->bypassHostAllocator() && // ArenaAllocator::getDefaultPageSize() is artificially low for
// DirectAlloc
(compAllocator->getTotalBytesAllocated() > (2 * ArenaAllocator::getDefaultPageSize())) &&
@@ -5071,7 +5267,7 @@ void Compiler::compCompileFinish()
mdMethodDef currentMethodToken = info.compCompHnd->getMethodDefFromMethod(info.compMethodHnd);
unsigned profCallCount = 0;
- if (((opts.eeFlags & CORJIT_FLG_BBOPT) != 0) && fgHaveProfileData())
+ if (opts.jitFlags->IsSet(JitFlags::JIT_FLAG_BBOPT) && fgHaveProfileData())
{
assert(fgProfileBuffer[0].ILOffset == 0);
profCallCount = fgProfileBuffer[0].ExecutionCount;
@@ -5208,7 +5404,7 @@ void Compiler::compCompileFinish()
// For ngen the int3 or breakpoint instruction will be right at the
// start of the ngen method and we will stop when we execute it.
//
- if ((opts.eeFlags & CORJIT_FLG_PREJIT) == 0)
+ if (!opts.jitFlags->IsSet(JitFlags::JIT_FLAG_PREJIT))
{
if (compJitHaltMethod())
{
@@ -5296,7 +5492,7 @@ int Compiler::compCompileHelper(CORINFO_MODULE_HANDLE classPtr,
CORINFO_METHOD_INFO* methodInfo,
void** methodCodePtr,
ULONG* methodCodeSize,
- CORJIT_FLAGS* compileFlags,
+ JitFlags* compileFlags,
CorInfoInstantiationVerification instVerInfo)
{
CORINFO_METHOD_HANDLE methodHnd = info.compMethodHnd;
@@ -5438,7 +5634,7 @@ int Compiler::compCompileHelper(CORINFO_MODULE_HANDLE classPtr,
info.compIsContextful = (info.compClassAttr & CORINFO_FLG_CONTEXTFUL) != 0;
- info.compPublishStubParam = (opts.eeFlags & CORJIT_FLG_PUBLISH_SECRET_PARAM) != 0;
+ info.compPublishStubParam = opts.jitFlags->IsSet(JitFlags::JIT_FLAG_PUBLISH_SECRET_PARAM);
switch (methodInfo->args.getCallConv())
{
@@ -5476,7 +5672,7 @@ int Compiler::compCompileHelper(CORINFO_MODULE_HANDLE classPtr,
const bool forceInline = !!(info.compFlags & CORINFO_FLG_FORCEINLINE);
- if (!compIsForInlining() && (opts.eeFlags & CORJIT_FLG_PREJIT))
+ if (!compIsForInlining() && opts.jitFlags->IsSet(JitFlags::JIT_FLAG_PREJIT))
{
// We're prejitting the root method. We also will analyze it as
// a potential inline candidate.
@@ -5644,10 +5840,6 @@ _Next:
return CORJIT_OK;
}
-/*****************************************************************************/
-#ifdef DEBUGGING_SUPPORT
-/*****************************************************************************/
-
//------------------------------------------------------------------------
// compFindLocalVarLinear: Linear search for variable's scope containing offset.
//
@@ -5992,11 +6184,7 @@ void Compiler::compProcessScopesUntil(unsigned offset,
} while (foundExit || foundEnter);
}
-/*****************************************************************************/
-#endif // DEBUGGING_SUPPORT
-/*****************************************************************************/
-
-#if defined(DEBUGGING_SUPPORT) && defined(DEBUG)
+#if defined(DEBUG)
void Compiler::compDispScopeLists()
{
@@ -6044,10 +6232,6 @@ void Compiler::compDispScopeLists()
}
}
-#endif
-
-#if defined(DEBUG)
-
void Compiler::compDispLocalVars()
{
printf("info.compVarScopesCount = %d\n", info.compVarScopesCount);
@@ -6066,7 +6250,66 @@ void Compiler::compDispLocalVars()
}
}
-#endif
+#endif // DEBUG
+
+/*****************************************************************************/
+
+#if MEASURE_CLRAPI_CALLS
+
+struct WrapICorJitInfo : public ICorJitInfo
+{
+ //------------------------------------------------------------------------
+ // WrapICorJitInfo::makeOne: allocate an instance of WrapICorJitInfo
+ //
+ // Arguments:
+ // alloc - the allocator to get memory from for the instance
+ // compile - the compiler instance
+ // compHndRef - the ICorJitInfo handle from the EE; the caller's
+ // copy may be replaced with a "wrapper" instance
+ //
+ // Return Value:
+ // If the config flags indicate that ICorJitInfo should be wrapped,
+ // we return the "wrapper" instance; otherwise we return "nullptr".
+
+ static WrapICorJitInfo* makeOne(ArenaAllocator* alloc, Compiler* compiler, COMP_HANDLE& compHndRef /* INOUT */)
+ {
+ WrapICorJitInfo* wrap = nullptr;
+
+ if (JitConfig.JitEECallTimingInfo() != 0)
+ {
+ // It's too early to use the default allocator, so we do this
+ // in two steps to be safe (the constructor doesn't need to do
+ // anything except fill in the vtable pointer, so we let the
+ // compiler do it).
+ void* inst = alloc->allocateMemory(roundUp(sizeof(WrapICorJitInfo)));
+ if (inst != nullptr)
+ {
+ // If you get a build error here due to 'WrapICorJitInfo' being
+ // an abstract class, it's very likely that the wrapper bodies
+ // in ICorJitInfo_API_wrapper.hpp are no longer in sync with
+ // the EE interface; please be kind and update the header file.
+ wrap = new (inst, jitstd::placement_t()) WrapICorJitInfo();
+
+ wrap->wrapComp = compiler;
+
+ // Save the real handle and replace it with our wrapped version.
+ wrap->wrapHnd = compHndRef;
+ compHndRef = wrap;
+ }
+ }
+
+ return wrap;
+ }
+
+private:
+ Compiler* wrapComp;
+ COMP_HANDLE wrapHnd; // the "real thing"
+
+public:
+#include "ICorJitInfo_API_wrapper.hpp"
+};
+
+#endif // MEASURE_CLRAPI_CALLS
/*****************************************************************************/
@@ -6078,7 +6321,7 @@ int jitNativeCode(CORINFO_METHOD_HANDLE methodHnd,
CORINFO_METHOD_INFO* methodInfo,
void** methodCodePtr,
ULONG* methodCodeSize,
- CORJIT_FLAGS* compileFlags,
+ JitFlags* compileFlags,
void* inlineInfoPtr)
{
//
@@ -6093,6 +6336,10 @@ START:
ArenaAllocator* pAlloc = nullptr;
ArenaAllocator alloc;
+#if MEASURE_CLRAPI_CALLS
+ WrapICorJitInfo* wrapCLR = nullptr;
+#endif
+
if (inlineInfo)
{
// Use inliner's memory allocator when compiling the inlinee.
@@ -6128,8 +6375,11 @@ START:
CORINFO_METHOD_INFO* methodInfo;
void** methodCodePtr;
ULONG* methodCodeSize;
- CORJIT_FLAGS* compileFlags;
+ JitFlags* compileFlags;
InlineInfo* inlineInfo;
+#if MEASURE_CLRAPI_CALLS
+ WrapICorJitInfo* wrapCLR;
+#endif
int result;
} param;
@@ -6145,7 +6395,10 @@ START:
param.methodCodeSize = methodCodeSize;
param.compileFlags = compileFlags;
param.inlineInfo = inlineInfo;
- param.result = result;
+#if MEASURE_CLRAPI_CALLS
+ param.wrapCLR = nullptr;
+#endif
+ param.result = result;
setErrorTrap(compHnd, Param*, pParamOuter, &param)
{
@@ -6172,6 +6425,10 @@ START:
pParam->pComp = (Compiler*)pParam->pAlloc->allocateMemory(roundUp(sizeof(*pParam->pComp)));
}
+#if MEASURE_CLRAPI_CALLS
+ pParam->wrapCLR = WrapICorJitInfo::makeOne(pParam->pAlloc, pParam->pComp, pParam->compHnd);
+#endif
+
// push this compiler on the stack (TLS)
pParam->pComp->prevCompiler = JitTls::GetCompiler();
JitTls::SetCompiler(pParam->pComp);
@@ -6238,8 +6495,9 @@ START:
jitFallbackCompile = true;
// Update the flags for 'safer' code generation.
- compileFlags->corJitFlags |= CORJIT_FLG_MIN_OPT;
- compileFlags->corJitFlags &= ~(CORJIT_FLG_SIZE_OPT | CORJIT_FLG_SPEED_OPT);
+ compileFlags->Set(JitFlags::JIT_FLAG_MIN_OPT);
+ compileFlags->Clear(JitFlags::JIT_FLAG_SIZE_OPT);
+ compileFlags->Clear(JitFlags::JIT_FLAG_SPEED_OPT);
goto START;
}
@@ -6952,9 +7210,12 @@ void Compiler::compDispCallArgStats(FILE* fout)
// Static variables
CritSecObject CompTimeSummaryInfo::s_compTimeSummaryLock;
CompTimeSummaryInfo CompTimeSummaryInfo::s_compTimeSummary;
+#if MEASURE_CLRAPI_CALLS
+double JitTimer::s_cyclesPerSec = CycleTimer::CyclesPerSecond();
+#endif
#endif // FEATURE_JIT_METHOD_PERF
-#if defined(FEATURE_JIT_METHOD_PERF) || DUMP_FLOWGRAPHS
+#if defined(FEATURE_JIT_METHOD_PERF) || DUMP_FLOWGRAPHS || defined(FEATURE_TRACELOGGING)
const char* PhaseNames[] = {
#define CompPhaseNameMacro(enum_nm, string_nm, short_nm, hasChildren, parent) string_nm,
#include "compphases.h"
@@ -6983,13 +7244,36 @@ int PhaseParent[] = {
};
CompTimeInfo::CompTimeInfo(unsigned byteCodeBytes)
- : m_byteCodeBytes(byteCodeBytes), m_totalCycles(0), m_parentPhaseEndSlop(0), m_timerFailure(false)
+ : m_byteCodeBytes(byteCodeBytes)
+ , m_totalCycles(0)
+ , m_parentPhaseEndSlop(0)
+ , m_timerFailure(false)
+#if MEASURE_CLRAPI_CALLS
+ , m_allClrAPIcalls(0)
+ , m_allClrAPIcycles(0)
+#endif
{
for (int i = 0; i < PHASE_NUMBER_OF; i++)
{
m_invokesByPhase[i] = 0;
m_cyclesByPhase[i] = 0;
+#if MEASURE_CLRAPI_CALLS
+ m_CLRinvokesByPhase[i] = 0;
+ m_CLRcyclesByPhase[i] = 0;
+#endif
}
+
+#if MEASURE_CLRAPI_CALLS
+ assert(ARRAYSIZE(m_perClrAPIcalls) == API_ICorJitInfo_Names::API_COUNT);
+ assert(ARRAYSIZE(m_perClrAPIcycles) == API_ICorJitInfo_Names::API_COUNT);
+ assert(ARRAYSIZE(m_maxClrAPIcycles) == API_ICorJitInfo_Names::API_COUNT);
+ for (int i = 0; i < API_ICorJitInfo_Names::API_COUNT; i++)
+ {
+ m_perClrAPIcalls[i] = 0;
+ m_perClrAPIcycles[i] = 0;
+ m_maxClrAPIcycles[i] = 0;
+ }
+#endif
}
bool CompTimeSummaryInfo::IncludedInFilteredData(CompTimeInfo& info)
@@ -6997,52 +7281,125 @@ bool CompTimeSummaryInfo::IncludedInFilteredData(CompTimeInfo& info)
return false; // info.m_byteCodeBytes < 10;
}
-void CompTimeSummaryInfo::AddInfo(CompTimeInfo& info)
+//------------------------------------------------------------------------
+// CompTimeSummaryInfo::AddInfo: Record timing info from one compile.
+//
+// Arguments:
+// info - The timing information to record.
+// includePhases - If "true", the per-phase info in "info" is valid,
+// which means that a "normal" compile has ended; if
+// the value is "false" we are recording the results
+// of a partial compile (typically an import-only run
+// on behalf of the inliner) in which case the phase
+// info is not valid and so we only record EE call
+// overhead.
+void CompTimeSummaryInfo::AddInfo(CompTimeInfo& info, bool includePhases)
{
if (info.m_timerFailure)
+ {
return; // Don't update if there was a failure.
+ }
CritSecHolder timeLock(s_compTimeSummaryLock);
- m_numMethods++;
- bool includeInFiltered = IncludedInFilteredData(info);
+ if (includePhases)
+ {
+ bool includeInFiltered = IncludedInFilteredData(info);
- // Update the totals and maxima.
- m_total.m_byteCodeBytes += info.m_byteCodeBytes;
- m_maximum.m_byteCodeBytes = max(m_maximum.m_byteCodeBytes, info.m_byteCodeBytes);
- m_total.m_totalCycles += info.m_totalCycles;
- m_maximum.m_totalCycles = max(m_maximum.m_totalCycles, info.m_totalCycles);
+ m_numMethods++;
- if (includeInFiltered)
- {
- m_numFilteredMethods++;
- m_filtered.m_byteCodeBytes += info.m_byteCodeBytes;
- m_filtered.m_totalCycles += info.m_totalCycles;
- m_filtered.m_parentPhaseEndSlop += info.m_parentPhaseEndSlop;
- }
+ // Update the totals and maxima.
+ m_total.m_byteCodeBytes += info.m_byteCodeBytes;
+ m_maximum.m_byteCodeBytes = max(m_maximum.m_byteCodeBytes, info.m_byteCodeBytes);
+ m_total.m_totalCycles += info.m_totalCycles;
+ m_maximum.m_totalCycles = max(m_maximum.m_totalCycles, info.m_totalCycles);
+
+#if MEASURE_CLRAPI_CALLS
+ // Update the CLR-API values.
+ m_total.m_allClrAPIcalls += info.m_allClrAPIcalls;
+ m_maximum.m_allClrAPIcalls = max(m_maximum.m_allClrAPIcalls, info.m_allClrAPIcalls);
+ m_total.m_allClrAPIcycles += info.m_allClrAPIcycles;
+ m_maximum.m_allClrAPIcycles = max(m_maximum.m_allClrAPIcycles, info.m_allClrAPIcycles);
+#endif
- for (int i = 0; i < PHASE_NUMBER_OF; i++)
- {
- m_total.m_invokesByPhase[i] += info.m_invokesByPhase[i];
- m_total.m_cyclesByPhase[i] += info.m_cyclesByPhase[i];
if (includeInFiltered)
{
- m_filtered.m_invokesByPhase[i] += info.m_invokesByPhase[i];
- m_filtered.m_cyclesByPhase[i] += info.m_cyclesByPhase[i];
+ m_numFilteredMethods++;
+ m_filtered.m_byteCodeBytes += info.m_byteCodeBytes;
+ m_filtered.m_totalCycles += info.m_totalCycles;
+ m_filtered.m_parentPhaseEndSlop += info.m_parentPhaseEndSlop;
+ }
+
+ for (int i = 0; i < PHASE_NUMBER_OF; i++)
+ {
+ m_total.m_invokesByPhase[i] += info.m_invokesByPhase[i];
+ m_total.m_cyclesByPhase[i] += info.m_cyclesByPhase[i];
+
+#if MEASURE_CLRAPI_CALLS
+ m_total.m_CLRinvokesByPhase[i] += info.m_CLRinvokesByPhase[i];
+ m_total.m_CLRcyclesByPhase[i] += info.m_CLRcyclesByPhase[i];
+#endif
+
+ if (includeInFiltered)
+ {
+ m_filtered.m_invokesByPhase[i] += info.m_invokesByPhase[i];
+ m_filtered.m_cyclesByPhase[i] += info.m_cyclesByPhase[i];
+#if MEASURE_CLRAPI_CALLS
+ m_filtered.m_CLRinvokesByPhase[i] += info.m_CLRinvokesByPhase[i];
+ m_filtered.m_CLRcyclesByPhase[i] += info.m_CLRcyclesByPhase[i];
+#endif
+ }
+ m_maximum.m_cyclesByPhase[i] = max(m_maximum.m_cyclesByPhase[i], info.m_cyclesByPhase[i]);
+
+#if MEASURE_CLRAPI_CALLS
+ m_maximum.m_CLRcyclesByPhase[i] = max(m_maximum.m_CLRcyclesByPhase[i], info.m_CLRcyclesByPhase[i]);
+#endif
}
- m_maximum.m_cyclesByPhase[i] = max(m_maximum.m_cyclesByPhase[i], info.m_cyclesByPhase[i]);
+ m_total.m_parentPhaseEndSlop += info.m_parentPhaseEndSlop;
+ m_maximum.m_parentPhaseEndSlop = max(m_maximum.m_parentPhaseEndSlop, info.m_parentPhaseEndSlop);
+ }
+#if MEASURE_CLRAPI_CALLS
+ else
+ {
+ m_totMethods++;
+
+ // Update the "global" CLR-API values.
+ m_total.m_allClrAPIcalls += info.m_allClrAPIcalls;
+ m_maximum.m_allClrAPIcalls = max(m_maximum.m_allClrAPIcalls, info.m_allClrAPIcalls);
+ m_total.m_allClrAPIcycles += info.m_allClrAPIcycles;
+ m_maximum.m_allClrAPIcycles = max(m_maximum.m_allClrAPIcycles, info.m_allClrAPIcycles);
+
+ // Update the per-phase CLR-API values.
+ m_total.m_invokesByPhase[PHASE_CLR_API] += info.m_allClrAPIcalls;
+ m_maximum.m_invokesByPhase[PHASE_CLR_API] =
+ max(m_maximum.m_perClrAPIcalls[PHASE_CLR_API], info.m_allClrAPIcalls);
+ m_total.m_cyclesByPhase[PHASE_CLR_API] += info.m_allClrAPIcycles;
+ m_maximum.m_cyclesByPhase[PHASE_CLR_API] =
+ max(m_maximum.m_cyclesByPhase[PHASE_CLR_API], info.m_allClrAPIcycles);
+ }
+
+ for (int i = 0; i < API_ICorJitInfo_Names::API_COUNT; i++)
+ {
+ m_total.m_perClrAPIcalls[i] += info.m_perClrAPIcalls[i];
+ m_maximum.m_perClrAPIcalls[i] = max(m_maximum.m_perClrAPIcalls[i], info.m_perClrAPIcalls[i]);
+
+ m_total.m_perClrAPIcycles[i] += info.m_perClrAPIcycles[i];
+ m_maximum.m_perClrAPIcycles[i] = max(m_maximum.m_perClrAPIcycles[i], info.m_perClrAPIcycles[i]);
+
+ m_maximum.m_maxClrAPIcycles[i] = max(m_maximum.m_maxClrAPIcycles[i], info.m_maxClrAPIcycles[i]);
}
- m_total.m_parentPhaseEndSlop += info.m_parentPhaseEndSlop;
- m_maximum.m_parentPhaseEndSlop = max(m_maximum.m_parentPhaseEndSlop, info.m_parentPhaseEndSlop);
+#endif
}
// Static
-LPCWSTR Compiler::compJitTimeLogFilename = NULL;
+LPCWSTR Compiler::compJitTimeLogFilename = nullptr;
void CompTimeSummaryInfo::Print(FILE* f)
{
- if (f == NULL)
+ if (f == nullptr)
+ {
return;
+ }
// Otherwise...
double countsPerSec = CycleTimer::CyclesPerSecond();
if (countsPerSec == 0.0)
@@ -7051,13 +7408,16 @@ void CompTimeSummaryInfo::Print(FILE* f)
return;
}
+ bool extraInfo = (JitConfig.JitEECallTimingInfo() != 0);
+ double totTime_ms = 0.0;
+
fprintf(f, "JIT Compilation time report:\n");
fprintf(f, " Compiled %d methods.\n", m_numMethods);
if (m_numMethods != 0)
{
fprintf(f, " Compiled %d bytecodes total (%d max, %8.2f avg).\n", m_total.m_byteCodeBytes,
m_maximum.m_byteCodeBytes, (double)m_total.m_byteCodeBytes / (double)m_numMethods);
- double totTime_ms = ((double)m_total.m_totalCycles / countsPerSec) * 1000.0;
+ totTime_ms = ((double)m_total.m_totalCycles / countsPerSec) * 1000.0;
fprintf(f, " Time: total: %10.3f Mcycles/%10.3f ms\n", ((double)m_total.m_totalCycles / 1000000.0),
totTime_ms);
fprintf(f, " max: %10.3f Mcycles/%10.3f ms\n", ((double)m_maximum.m_totalCycles) / 1000000.0,
@@ -7065,15 +7425,36 @@ void CompTimeSummaryInfo::Print(FILE* f)
fprintf(f, " avg: %10.3f Mcycles/%10.3f ms\n",
((double)m_total.m_totalCycles) / 1000000.0 / (double)m_numMethods, totTime_ms / (double)m_numMethods);
- fprintf(f, " Total time by phases:\n");
- fprintf(f, " PHASE inv/meth Mcycles time (ms) %% of total max (ms)\n");
- fprintf(f, " --------------------------------------------------------------------------------------\n");
+ const char* extraHdr1 = "";
+ const char* extraHdr2 = "";
+#if MEASURE_CLRAPI_CALLS
+ if (extraInfo)
+ {
+ extraHdr1 = " CLRs/meth % in CLR";
+ extraHdr2 = "-----------------------";
+ }
+#endif
+
+ fprintf(f, "\n Total time by phases:\n");
+ fprintf(f, " PHASE inv/meth Mcycles time (ms) %% of total max (ms)%s\n",
+ extraHdr1);
+ fprintf(f, " ---------------------------------------------------------------------------------------%s\n",
+ extraHdr2);
+
// Ensure that at least the names array and the Phases enum have the same number of entries:
assert(sizeof(PhaseNames) / sizeof(const char*) == PHASE_NUMBER_OF);
for (int i = 0; i < PHASE_NUMBER_OF; i++)
{
- double phase_tot_ms = (((double)m_total.m_cyclesByPhase[i]) / countsPerSec) * 1000.0;
- double phase_max_ms = (((double)m_maximum.m_cyclesByPhase[i]) / countsPerSec) * 1000.0;
+ double phase_tot_ms = (((double)m_total.m_cyclesByPhase[i]) / countsPerSec) * 1000.0;
+ double phase_max_ms = (((double)m_maximum.m_cyclesByPhase[i]) / countsPerSec) * 1000.0;
+ double phase_tot_pct = 100.0 * phase_tot_ms / totTime_ms;
+
+#if MEASURE_CLRAPI_CALLS
+ // Skip showing CLR API call info if we didn't collect any
+ if (i == PHASE_CLR_API && !extraInfo)
+ continue;
+#endif
+
// Indent nested phases, according to depth.
int ancPhase = PhaseParent[i];
while (ancPhase != -1)
@@ -7081,13 +7462,33 @@ void CompTimeSummaryInfo::Print(FILE* f)
fprintf(f, " ");
ancPhase = PhaseParent[ancPhase];
}
- fprintf(f, " %-30s %5.2f %10.2f %9.3f %8.2f%% %8.3f\n", PhaseNames[i],
+ fprintf(f, " %-30s %6.2f %10.2f %9.3f %8.2f%% %8.3f", PhaseNames[i],
((double)m_total.m_invokesByPhase[i]) / ((double)m_numMethods),
((double)m_total.m_cyclesByPhase[i]) / 1000000.0, phase_tot_ms, (phase_tot_ms * 100.0 / totTime_ms),
phase_max_ms);
+
+#if MEASURE_CLRAPI_CALLS
+ if (extraInfo && i != PHASE_CLR_API)
+ {
+ double nest_tot_ms = (((double)m_total.m_CLRcyclesByPhase[i]) / countsPerSec) * 1000.0;
+ double nest_percent = nest_tot_ms * 100.0 / totTime_ms;
+ double calls_per_fn = ((double)m_total.m_CLRinvokesByPhase[i]) / ((double)m_numMethods);
+
+ if (nest_percent > 0.1 || calls_per_fn > 10)
+ fprintf(f, " %5.1f %8.2f%%", calls_per_fn, nest_percent);
+ }
+#endif
+ fprintf(f, "\n");
+ }
+
+ // Show slop if it's over a certain percentage of the total
+ double pslop_pct = 100.0 * m_total.m_parentPhaseEndSlop * 1000.0 / countsPerSec / totTime_ms;
+ if (pslop_pct >= 1.0)
+ {
+ fprintf(f, "\n 'End phase slop' should be very small (if not, there's unattributed time): %9.3f Mcycles = "
+ "%3.1f%% of total.\n\n",
+ m_total.m_parentPhaseEndSlop / 1000000.0, pslop_pct);
}
- fprintf(f, "\n 'End phase slop' should be very small (if not, there's unattributed time): %9.3f Mcycles.\n",
- m_total.m_parentPhaseEndSlop);
}
if (m_numFilteredMethods > 0)
{
@@ -7121,19 +7522,125 @@ void CompTimeSummaryInfo::Print(FILE* f)
((double)m_filtered.m_cyclesByPhase[i]) / 1000000.0, phase_tot_ms,
(phase_tot_ms * 100.0 / totTime_ms));
}
- fprintf(f, "\n 'End phase slop' should be very small (if not, there's unattributed time): %9.3f Mcycles.\n",
- m_filtered.m_parentPhaseEndSlop);
+
+ double fslop_ms = m_filtered.m_parentPhaseEndSlop * 1000.0 / countsPerSec;
+ if (fslop_ms > 1.0)
+ {
+ fprintf(f,
+ "\n 'End phase slop' should be very small (if not, there's unattributed time): %9.3f Mcycles.\n",
+ m_filtered.m_parentPhaseEndSlop);
+ }
}
+
+#if MEASURE_CLRAPI_CALLS
+ if (m_total.m_allClrAPIcalls > 0 && m_total.m_allClrAPIcycles > 0)
+ {
+ fprintf(f, "\n");
+ if (m_totMethods > 0)
+ fprintf(f, " Imported %u methods.\n\n", m_numMethods + m_totMethods);
+
+ fprintf(f, " CLR API # calls total time max time avg time %% "
+ "of total\n");
+ fprintf(f, " -------------------------------------------------------------------------------");
+ fprintf(f, "---------------------\n");
+
+ static const char* APInames[] = {
+#define DEF_CLR_API(name) #name,
+#include "ICorJitInfo_API_names.h"
+ };
+
+ unsigned shownCalls = 0;
+ double shownMillis = 0.0;
+#ifdef DEBUG
+ unsigned checkedCalls = 0;
+ double checkedMillis = 0.0;
+#endif
+
+ for (unsigned pass = 0; pass < 2; pass++)
+ {
+ for (unsigned i = 0; i < API_ICorJitInfo_Names::API_COUNT; i++)
+ {
+ unsigned calls = m_total.m_perClrAPIcalls[i];
+ if (calls == 0)
+ continue;
+
+ unsigned __int64 cycles = m_total.m_perClrAPIcycles[i];
+ double millis = 1000.0 * cycles / countsPerSec;
+
+ // Don't show the small fry to keep the results manageable
+ if (millis < 0.5)
+ {
+ // We always show the following API because it is always called
+ // exactly once for each method and its body is the simplest one
+ // possible (it just returns an integer constant), and therefore
+ // it can be used to measure the overhead of adding the CLR API
+ // timing code. Roughly speaking, on a 3GHz x64 box the overhead
+ // per call should be around 40 ns when using RDTSC, compared to
+ // about 140 ns when using GetThreadCycles() under Windows.
+ if (i != API_ICorJitInfo_Names::API_getExpectedTargetArchitecture)
+ continue;
+ }
+
+ // In the first pass we just compute the totals.
+ if (pass == 0)
+ {
+ shownCalls += m_total.m_perClrAPIcalls[i];
+ shownMillis += millis;
+ continue;
+ }
+
+ unsigned __int32 maxcyc = m_maximum.m_maxClrAPIcycles[i];
+ double max_ms = 1000.0 * maxcyc / countsPerSec;
+
+ fprintf(f, " %-40s", APInames[i]); // API name
+ fprintf(f, " %8u %9.1f ms", calls, millis); // #calls, total time
+ fprintf(f, " %8.1f ms %8.1f ns", max_ms, 1000000.0 * millis / calls); // max, avg time
+ fprintf(f, " %5.1f%%\n", 100.0 * millis / shownMillis); // % of total
+
+#ifdef DEBUG
+ checkedCalls += m_total.m_perClrAPIcalls[i];
+ checkedMillis += millis;
+#endif
+ }
+ }
+
+#ifdef DEBUG
+ assert(checkedCalls == shownCalls);
+ assert(checkedMillis == shownMillis);
+#endif
+
+ if (shownCalls > 0 || shownMillis > 0)
+ {
+ fprintf(f, " -------------------------");
+ fprintf(f, "---------------------------------------------------------------------------\n");
+ fprintf(f, " Total for calls shown above %8u %10.1f ms", shownCalls, shownMillis);
+ if (totTime_ms > 0.0)
+ fprintf(f, " (%4.1lf%% of overall JIT time)", shownMillis * 100.0 / totTime_ms);
+ fprintf(f, "\n");
+ }
+ fprintf(f, "\n");
+ }
+#endif
+
+ fprintf(f, "\n");
}
JitTimer::JitTimer(unsigned byteCodeSize) : m_info(byteCodeSize)
{
+#if MEASURE_CLRAPI_CALLS
+ m_CLRcallInvokes = 0;
+ m_CLRcallCycles = 0;
+#endif
+
#ifdef DEBUG
m_lastPhase = (Phases)-1;
+#if MEASURE_CLRAPI_CALLS
+ m_CLRcallAPInum = -1;
+#endif
#endif
unsigned __int64 threadCurCycles;
- if (GetThreadCycles(&threadCurCycles))
+ if (_our_GetThreadCycles(&threadCurCycles))
{
m_start = threadCurCycles;
m_curPhaseStart = threadCurCycles;
@@ -7147,9 +7654,10 @@ void JitTimer::EndPhase(Phases phase)
// assert((int)phase > (int)m_lastPhase); // We should end phases in increasing order.
unsigned __int64 threadCurCycles;
- if (GetThreadCycles(&threadCurCycles))
+ if (_our_GetThreadCycles(&threadCurCycles))
{
unsigned __int64 phaseCycles = (threadCurCycles - m_curPhaseStart);
+
// If this is not a leaf phase, the assumption is that the last subphase must have just recently ended.
// Credit the duration to "slop", the total of which should be very small.
if (PhaseHasChildren[phase])
@@ -7161,6 +7669,13 @@ void JitTimer::EndPhase(Phases phase)
// It is a leaf phase. Credit duration to it.
m_info.m_invokesByPhase[phase]++;
m_info.m_cyclesByPhase[phase] += phaseCycles;
+
+#if MEASURE_CLRAPI_CALLS
+ // Record the CLR API timing info as well.
+ m_info.m_CLRinvokesByPhase[phase] += m_CLRcallInvokes;
+ m_info.m_CLRcyclesByPhase[phase] += m_CLRcallCycles;
+#endif
+
// Credit the phase's ancestors, if any.
int ancPhase = PhaseParent[phase];
while (ancPhase != -1)
@@ -7168,8 +7683,13 @@ void JitTimer::EndPhase(Phases phase)
m_info.m_cyclesByPhase[ancPhase] += phaseCycles;
ancPhase = PhaseParent[ancPhase];
}
- // Did we just end the last phase?
- if (phase + 1 == PHASE_NUMBER_OF)
+
+#if MEASURE_CLRAPI_CALLS
+ const Phases lastPhase = PHASE_CLR_API;
+#else
+ const Phases lastPhase = PHASE_NUMBER_OF;
+#endif
+ if (phase + 1 == lastPhase)
{
m_info.m_totalCycles = (threadCurCycles - m_start);
}
@@ -7179,11 +7699,92 @@ void JitTimer::EndPhase(Phases phase)
}
}
}
+
#ifdef DEBUG
m_lastPhase = phase;
#endif
+#if MEASURE_CLRAPI_CALLS
+ m_CLRcallInvokes = 0;
+ m_CLRcallCycles = 0;
+#endif
+}
+
+#if MEASURE_CLRAPI_CALLS
+
+//------------------------------------------------------------------------
+// JitTimer::CLRApiCallEnter: Start the stopwatch for an EE call.
+//
+// Arguments:
+// apix - The API index - an "enum API_ICorJitInfo_Names" value.
+//
+
+void JitTimer::CLRApiCallEnter(unsigned apix)
+{
+ assert(m_CLRcallAPInum == -1); // Nested calls not allowed
+ m_CLRcallAPInum = apix;
+
+ // If we can't get the cycles, we'll just ignore this call
+ if (!_our_GetThreadCycles(&m_CLRcallStart))
+ m_CLRcallStart = 0;
+}
+
+//------------------------------------------------------------------------
+// JitTimer::CLRApiCallLeave: compute / record time spent in an EE call.
+//
+// Arguments:
+// apix - The API's "enum API_ICorJitInfo_Names" value; this value
+// should match the value passed to the most recent call to
+// "CLRApiCallEnter" (i.e. these must come as matched pairs),
+// and they also may not nest.
+//
+
+void JitTimer::CLRApiCallLeave(unsigned apix)
+{
+ // Make sure we're actually inside a measured CLR call.
+ assert(m_CLRcallAPInum != -1);
+ m_CLRcallAPInum = -1;
+
+ // Ignore this one if we don't have a valid starting counter.
+ if (m_CLRcallStart != 0)
+ {
+ if (JitConfig.JitEECallTimingInfo() != 0)
+ {
+ unsigned __int64 threadCurCycles;
+ if (_our_GetThreadCycles(&threadCurCycles))
+ {
+ // Compute the cycles spent in the call.
+ threadCurCycles -= m_CLRcallStart;
+
+ // Add the cycles to the 'phase' and bump its use count.
+ m_info.m_cyclesByPhase[PHASE_CLR_API] += threadCurCycles;
+ m_info.m_invokesByPhase[PHASE_CLR_API] += 1;
+
+ // Add the values to the "per API" info.
+ m_info.m_allClrAPIcycles += threadCurCycles;
+ m_info.m_allClrAPIcalls += 1