summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorSergey Andreenko <seandree@microsoft.com>2018-02-14 15:38:58 -0800
committerGitHub <noreply@github.com>2018-02-14 15:38:58 -0800
commit94510ef330ad1ecb31540a949a68d4e09c164c64 (patch)
tree37161f66b2cf10ad90232a23c1700026b3734e27 /src
parent7910a37a1cf5b963997994b6884475c224585e90 (diff)
downloadcoreclr-94510ef330ad1ecb31540a949a68d4e09c164c64.tar.gz
coreclr-94510ef330ad1ecb31540a949a68d4e09c164c64.tar.bz2
coreclr-94510ef330ad1ecb31540a949a68d4e09c164c64.zip
[RyuJit] Stack level setter (#15597)
* create a new phase: StackLevelSetter * add repro * Fix grammar mistakes * use the default hash * delete values from the map. * create gentree::OperIsPutArgStkOrSplit * fix more comments * delete an extra condition that is always true * use GTSTRUCT_2_SPECIAL for PutArgStk * extract fgUseThrowHelperBlocks * optimize memory for amd64 and additional checks for x86 * change checks The previous version was wrong, because morph can call fgAddCodeRef several times for the same instruction during different phases. * fix comments * fix genJumpToThrowHlpBlk * small ref in genJumpToThrowHlpBlk * fix rebase problems. * use fgUseThrowHelperBlocks instead of !opts.compDbgCode * add throwHelperBlocksUsed for throughput.
Diffstat (limited to 'src')
-rw-r--r--src/jit/CMakeLists.txt1
-rw-r--r--src/jit/codegencommon.cpp68
-rw-r--r--src/jit/codegeninterface.h11
-rw-r--r--src/jit/compiler.cpp4
-rw-r--r--src/jit/compiler.h40
-rw-r--r--src/jit/compphases.h1
-rw-r--r--src/jit/flowgraph.cpp37
-rw-r--r--src/jit/gentree.h5
-rw-r--r--src/jit/gtstructs.h6
-rw-r--r--src/jit/jit.settings.targets1
-rw-r--r--src/jit/stacklevelsetter.cpp268
-rw-r--r--src/jit/stacklevelsetter.h42
12 files changed, 438 insertions, 46 deletions
diff --git a/src/jit/CMakeLists.txt b/src/jit/CMakeLists.txt
index f4626ec562..8d87336948 100644
--- a/src/jit/CMakeLists.txt
+++ b/src/jit/CMakeLists.txt
@@ -75,6 +75,7 @@ set( JIT_SOURCES
unwind.cpp
utils.cpp
valuenum.cpp
+ stacklevelsetter.cpp
)
# Add header files to Visual Studio vcxproj, not required for unixes
diff --git a/src/jit/codegencommon.cpp b/src/jit/codegencommon.cpp
index 103ec69768..be29a2a10d 100644
--- a/src/jit/codegencommon.cpp
+++ b/src/jit/codegencommon.cpp
@@ -2787,17 +2787,21 @@ void CodeGen::genExitCode(BasicBlock* block)
genReserveEpilog(block);
}
-/*****************************************************************************
- *
- * Generate code for an out-of-line exception.
- * For debuggable code, we generate the 'throw' inline.
- * For non-dbg code, we share the helper blocks created by fgAddCodeRef().
- */
-
+//------------------------------------------------------------------------
+// genJumpToThrowHlpBlk: Generate code for an out-of-line exception.
+//
+// Notes:
+// For code that uses throw helper blocks, we share the helper blocks created by fgAddCodeRef().
+// Otherwise, we generate the 'throw' inline.
+//
+// Arguments:
+// jumpKind - jump kind to generate;
+// codeKind - the special throw-helper kind;
+// failBlk - optional fail target block, if it is already known;
+//
void CodeGen::genJumpToThrowHlpBlk(emitJumpKind jumpKind, SpecialCodeKind codeKind, GenTree* failBlk)
{
- bool useThrowHlpBlk = !compiler->opts.compDbgCode;
-
+ bool useThrowHlpBlk = compiler->fgUseThrowHelperBlocks();
#if defined(UNIX_X86_ABI) && FEATURE_EH_FUNCLETS
// Inline exception-throwing code in funclet to make it possible to unwind funclet frames.
useThrowHlpBlk = useThrowHlpBlk && (compiler->funCurrentFunc()->funKind == FUNC_ROOT);
@@ -2805,41 +2809,47 @@ void CodeGen::genJumpToThrowHlpBlk(emitJumpKind jumpKind, SpecialCodeKind codeKi
if (useThrowHlpBlk)
{
- /* For non-debuggable code, find and use the helper block for
- raising the exception. The block may be shared by other trees too. */
+ // For code with throw helper blocks, find and use the helper block for
+ // raising the exception. The block may be shared by other trees too.
- BasicBlock* tgtBlk;
+ BasicBlock* excpRaisingBlock;
- if (failBlk)
+ if (failBlk != nullptr)
{
- /* We already know which block to jump to. Use that. */
+ // We already know which block to jump to. Use that.
+ assert(failBlk->gtOper == GT_LABEL);
+ excpRaisingBlock = failBlk->gtLabel.gtLabBB;
- noway_assert(failBlk->gtOper == GT_LABEL);
- tgtBlk = failBlk->gtLabel.gtLabBB;
- noway_assert(
- tgtBlk ==
- compiler->fgFindExcptnTarget(codeKind, compiler->bbThrowIndex(compiler->compCurBB))->acdDstBlk);
+#ifdef DEBUG
+ Compiler::AddCodeDsc* add =
+ compiler->fgFindExcptnTarget(codeKind, compiler->bbThrowIndex(compiler->compCurBB));
+ assert(excpRaisingBlock == add->acdDstBlk);
+#if !FEATURE_FIXED_OUT_ARGS
+ assert(add->acdStkLvlInit || isFramePointerUsed());
+#endif // !FEATURE_FIXED_OUT_ARGS
+#endif // DEBUG
}
else
{
- /* Find the helper-block which raises the exception. */
-
+ // Find the helper-block which raises the exception.
Compiler::AddCodeDsc* add =
compiler->fgFindExcptnTarget(codeKind, compiler->bbThrowIndex(compiler->compCurBB));
PREFIX_ASSUME_MSG((add != nullptr), ("ERROR: failed to find exception throw block"));
- tgtBlk = add->acdDstBlk;
+ excpRaisingBlock = add->acdDstBlk;
+#if !FEATURE_FIXED_OUT_ARGS
+ assert(add->acdStkLvlInit || isFramePointerUsed());
+#endif // !FEATURE_FIXED_OUT_ARGS
}
- noway_assert(tgtBlk);
-
- // Jump to the excption-throwing block on error.
+ noway_assert(excpRaisingBlock != nullptr);
- inst_JMP(jumpKind, tgtBlk);
+ // Jump to the exception-throwing block on error.
+ inst_JMP(jumpKind, excpRaisingBlock);
}
else
{
- /* The code to throw the exception will be generated inline, and
- we will jump around it in the normal non-exception case */
+ // The code to throw the exception will be generated inline, and
+ // we will jump around it in the normal non-exception case.
BasicBlock* tgtBlk = nullptr;
emitJumpKind reverseJumpKind = emitter::emitReverseJumpKind(jumpKind);
@@ -2851,7 +2861,7 @@ void CodeGen::genJumpToThrowHlpBlk(emitJumpKind jumpKind, SpecialCodeKind codeKi
genEmitHelperCall(compiler->acdHelper(codeKind), 0, EA_UNKNOWN);
- /* Define the spot for the normal non-exception case to jump to */
+ // Define the spot for the normal non-exception case to jump to.
if (tgtBlk != nullptr)
{
assert(reverseJumpKind != jumpKind);
diff --git a/src/jit/codegeninterface.h b/src/jit/codegeninterface.h
index b0eecbd9f4..604e3aea8d 100644
--- a/src/jit/codegeninterface.h
+++ b/src/jit/codegeninterface.h
@@ -225,10 +225,21 @@ public:
{
return m_cgFramePointerRequired;
}
+
void setFramePointerRequired(bool value)
{
m_cgFramePointerRequired = value;
}
+
+ //------------------------------------------------------------------------
+ // resetWritePhaseForFramePointerRequired: Return m_cgFramePointerRequired into the write phase.
+ // It is used only before the first phase, that locks this value, currently it is LSRA.
+ // Use it if you want to skip checks that set this value to true if the value is already true.
+ void resetWritePhaseForFramePointerRequired()
+ {
+ m_cgFramePointerRequired.ResetWritePhase();
+ }
+
void setFramePointerRequiredEH(bool value);
void setFramePointerRequiredGCInfo(bool value)
diff --git a/src/jit/compiler.cpp b/src/jit/compiler.cpp
index ce9b3dca13..cec1dd361d 100644
--- a/src/jit/compiler.cpp
+++ b/src/jit/compiler.cpp
@@ -22,6 +22,7 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
#ifndef LEGACY_BACKEND
#include "lower.h"
+#include "stacklevelsetter.h"
#endif // !LEGACY_BACKEND
#include "jittelemetry.h"
@@ -5002,6 +5003,9 @@ void Compiler::compCompile(void** methodCodePtr, ULONG* methodCodeSize, JitFlags
m_pLowering = new (this, CMK_LSRA) Lowering(this, m_pLinearScan); // PHASE_LOWERING
m_pLowering->Run();
+ StackLevelSetter stackLevelSetter(this); // PHASE_STACK_LEVEL_SETTER
+ stackLevelSetter.Run();
+
assert(lvaSortAgain == false); // We should have re-run fgLocalVarLiveness() in lower.Run()
lvaTrackedFixed = true; // We can not add any new tracked variables after this point.
diff --git a/src/jit/compiler.h b/src/jit/compiler.h
index a309d9e867..a401c47aa8 100644
--- a/src/jit/compiler.h
+++ b/src/jit/compiler.h
@@ -4826,8 +4826,32 @@ private:
unsigned fgPtrArgCntCur;
unsigned fgPtrArgCntMax;
- hashBv* fgOutgoingArgTemps;
- hashBv* fgCurrentlyInUseArgTemps;
+
+public:
+ //------------------------------------------------------------------------
+ // fgGetPtrArgCntMax: Return the maximum number of pointer-sized stack arguments that calls inside this method
+ // can push on the stack. This value is calculated during morph.
+ //
+ // Return Value:
+ // Returns fgPtrArgCntMax, that is a private field.
+ //
+ unsigned fgGetPtrArgCntMax() const
+ {
+ return fgPtrArgCntMax;
+ }
+
+ //------------------------------------------------------------------------
+ // fgSetPtrArgCntMax: Set the maximum number of pointer-sized stack arguments that calls inside this method
+ // can push on the stack. This function is used during StackLevelSetter to fix incorrect morph calculations.
+ //
+ void fgSetPtrArgCntMax(unsigned argCntMax)
+ {
+ fgPtrArgCntMax = argCntMax;
+ }
+
+private:
+ hashBv* fgOutgoingArgTemps;
+ hashBv* fgCurrentlyInUseArgTemps;
bool compCanEncodePtrArgCntMax();
@@ -5012,7 +5036,10 @@ public:
BasicBlock* acdDstBlk; // block to which we jump
unsigned acdData;
SpecialCodeKind acdKind; // what kind of a special block is this?
- unsigned short acdStkLvl;
+#if !FEATURE_FIXED_OUT_ARGS
+ bool acdStkLvlInit; // has acdStkLvl value been already set?
+ unsigned acdStkLvl;
+#endif // !FEATURE_FIXED_OUT_ARGS
};
private:
@@ -5030,6 +5057,13 @@ private:
public:
AddCodeDsc* fgFindExcptnTarget(SpecialCodeKind kind, unsigned refData);
+ bool fgUseThrowHelperBlocks();
+
+ AddCodeDsc* fgGetAdditionalCodeDescriptors()
+ {
+ return fgAddCodeList;
+ }
+
private:
bool fgIsCodeAdded();
diff --git a/src/jit/compphases.h b/src/jit/compphases.h
index fe19d91970..61db5f3c6c 100644
--- a/src/jit/compphases.h
+++ b/src/jit/compphases.h
@@ -92,6 +92,7 @@ CompPhaseNameMacro(PHASE_RA_ASSIGN_VARS, "RA assign vars",
CompPhaseNameMacro(PHASE_LOWERING_DECOMP, "Lowering decomposition", "LWR-DEC", false, -1, false)
CompPhaseNameMacro(PHASE_LOWERING, "Lowering nodeinfo", "LWR-INFO", false, -1, true)
#ifndef LEGACY_BACKEND
+CompPhaseNameMacro(PHASE_STACK_LEVEL_SETTER, "Calculate stack level slots", "STK-SET", false, -1, false)
CompPhaseNameMacro(PHASE_LINEAR_SCAN, "Linear scan register alloc", "LSRA", true, -1, true)
CompPhaseNameMacro(PHASE_LINEAR_SCAN_BUILD, "LSRA build intervals", "LSRA-BLD", false, PHASE_LINEAR_SCAN, false)
CompPhaseNameMacro(PHASE_LINEAR_SCAN_ALLOC, "LSRA allocate", "LSRA-ALL", false, PHASE_LINEAR_SCAN, false)
diff --git a/src/jit/flowgraph.cpp b/src/jit/flowgraph.cpp
index dc714191ef..f3a0bf2ced 100644
--- a/src/jit/flowgraph.cpp
+++ b/src/jit/flowgraph.cpp
@@ -18201,9 +18201,7 @@ BasicBlock* Compiler::fgAddCodeRef(BasicBlock* srcBlk, unsigned refData, Special
// arg slots on the stack frame if there are no other calls.
compUsesThrowHelper = true;
- // For debuggable code, genJumpToThrowHlpBlk() will generate the 'throw'
- // code inline. It has to be kept consistent with fgAddCodeRef()
- if (opts.compDbgCode)
+ if (!fgUseThrowHelperBlocks())
{
return nullptr;
}
@@ -18226,7 +18224,7 @@ BasicBlock* Compiler::fgAddCodeRef(BasicBlock* srcBlk, unsigned refData, Special
if (add) // found it
{
-#ifdef _TARGET_X86_
+#if !FEATURE_FIXED_OUT_ARGS
// If different range checks happen at different stack levels,
// they can't all jump to the same "call @rngChkFailed" AND have
// frameless methods, as the rngChkFailed may need to unwind the
@@ -18261,19 +18259,22 @@ BasicBlock* Compiler::fgAddCodeRef(BasicBlock* srcBlk, unsigned refData, Special
codeGen->setFramePointerRequiredGCInfo(true);
}
#endif // !defined(UNIX_X86_ABI)
-#endif // _TARGET_X86_
+#endif // !FEATURE_FIXED_OUT_ARGS
return add->acdDstBlk;
}
/* We have to allocate a new entry and prepend it to the list */
- add = new (this, CMK_Unknown) AddCodeDsc;
- add->acdData = refData;
- add->acdKind = kind;
- add->acdStkLvl = (unsigned short)stkDepth;
- noway_assert(add->acdStkLvl == stkDepth);
- add->acdNext = fgAddCodeList;
+ add = new (this, CMK_Unknown) AddCodeDsc;
+ add->acdData = refData;
+ add->acdKind = kind;
+ add->acdNext = fgAddCodeList;
+#if !FEATURE_FIXED_OUT_ARGS
+ add->acdStkLvl = stkDepth;
+ add->acdStkLvlInit = false;
+#endif // !FEATURE_FIXED_OUT_ARGS
+
fgAddCodeList = add;
/* Create the target basic block */
@@ -18424,7 +18425,7 @@ BasicBlock* Compiler::fgAddCodeRef(BasicBlock* srcBlk, unsigned refData, Special
Compiler::AddCodeDsc* Compiler::fgFindExcptnTarget(SpecialCodeKind kind, unsigned refData)
{
- assert(!opts.compDbgCode);
+ assert(fgUseThrowHelperBlocks());
if (!(fgExcptnTargetCache[kind] && // Try the cached value first
fgExcptnTargetCache[kind]->acdData == refData))
{
@@ -25904,3 +25905,15 @@ bool Compiler::fgNeedReturnSpillTemp()
assert(compIsForInlining());
return (lvaInlineeReturnSpillTemp != BAD_VAR_NUM);
}
+
+//------------------------------------------------------------------------
+// fgUseThrowHelperBlocks: Determinate does compiler use throw helper blocks.
+//
+// Note:
+// For debuggable code, codegen will generate the 'throw' code inline.
+// Return Value:
+// true if 'throw' helper block should be created.
+bool Compiler::fgUseThrowHelperBlocks()
+{
+ return !opts.compDbgCode;
+}
diff --git a/src/jit/gentree.h b/src/jit/gentree.h
index 5f534db8f1..ed5664efb2 100644
--- a/src/jit/gentree.h
+++ b/src/jit/gentree.h
@@ -1294,6 +1294,11 @@ public:
return gtOper == GT_PUTARG_STK;
}
+ bool OperIsPutArgStkOrSplit() const
+ {
+ return OperIsPutArgStk() || OperIsPutArgSplit();
+ }
+
bool OperIsPutArgReg() const
{
return gtOper == GT_PUTARG_REG;
diff --git a/src/jit/gtstructs.h b/src/jit/gtstructs.h
index f3ff0217f2..6cfb92c98e 100644
--- a/src/jit/gtstructs.h
+++ b/src/jit/gtstructs.h
@@ -109,10 +109,12 @@ GTSTRUCT_1(Qmark , GT_QMARK)
GTSTRUCT_1(PhiArg , GT_PHI_ARG)
GTSTRUCT_1(StoreInd , GT_STOREIND)
GTSTRUCT_N(Indir , GT_STOREIND, GT_IND, GT_NULLCHECK, GT_BLK, GT_STORE_BLK, GT_OBJ, GT_STORE_OBJ, GT_DYN_BLK, GT_STORE_DYN_BLK)
+#if defined(LEGACY_BACKEND) || !defined(_TARGET_ARM_)
GTSTRUCT_1(PutArgStk , GT_PUTARG_STK)
-#if !defined(LEGACY_BACKEND) && defined(_TARGET_ARM_)
+#else // defined(_TARGET_ARM_) && !defined(LEGACY_BACKEND)
+GTSTRUCT_2_SPECIAL(PutArgStk, GT_PUTARG_STK, GT_PUTARG_SPLIT)
GTSTRUCT_1(PutArgSplit , GT_PUTARG_SPLIT)
-#endif
+#endif // defined(LEGACY_BACKEND) || !defined(_TARGET_ARM_)
GTSTRUCT_1(PhysReg , GT_PHYSREG)
#ifdef FEATURE_SIMD
GTSTRUCT_1(SIMD , GT_SIMD)
diff --git a/src/jit/jit.settings.targets b/src/jit/jit.settings.targets
index 220c00fea6..467930e051 100644
--- a/src/jit/jit.settings.targets
+++ b/src/jit/jit.settings.targets
@@ -87,6 +87,7 @@
<CppCompile Include="..\hostallocator.cpp" />
<CppCompile Include="..\objectalloc.cpp" />
<CppCompile Include="..\sideeffects.cpp" />
+ <CppCompile Include="..\stacklevelsetter.cpp" />
<CppCompile Condition="'$(ClDefines.Contains(`LEGACY_BACKEND`))'=='True'" Include="..\CodeGenLegacy.cpp" />
<CppCompile Condition="'$(ClDefines.Contains(`LEGACY_BACKEND`))'=='False'" Include="..\Lower.cpp" />
<CppCompile Condition="'$(ClDefines.Contains(`LEGACY_BACKEND`))'=='False'" Include="..\LSRA.cpp" />
diff --git a/src/jit/stacklevelsetter.cpp b/src/jit/stacklevelsetter.cpp
new file mode 100644
index 0000000000..a3d9259257
--- /dev/null
+++ b/src/jit/stacklevelsetter.cpp
@@ -0,0 +1,268 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+#include "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 "stacklevelsetter.h"
+
+StackLevelSetter::StackLevelSetter(Compiler* compiler)
+ : Phase(compiler, "StackLevelSetter", PHASE_STACK_LEVEL_SETTER)
+ , currentStackLevel(0)
+ , maxStackLevel(0)
+ , memAllocator(compiler, CMK_fgArgInfoPtrArr)
+ , putArgNumSlots(&memAllocator)
+#if !FEATURE_FIXED_OUT_ARGS
+ , framePointerRequired(compiler->codeGen->isFramePointerRequired())
+ , throwHelperBlocksUsed(comp->fgUseThrowHelperBlocks() && comp->compUsesThrowHelper)
+#endif // !FEATURE_FIXED_OUT_ARGS
+{
+}
+
+//------------------------------------------------------------------------
+// DoPhase: Calculate stack slots numbers for outgoing args.
+//
+// Notes:
+// For non-x86 platforms it calculates the max number of slots
+// that calls inside this method can push on the stack.
+// This value is used for sanity checks in the emitter.
+//
+// Stack slots are pointer-sized: 4 bytes for 32-bit platforms, 8 bytes for 64-bit platforms.
+//
+// For x86 it also sets throw-helper blocks incoming stack depth and set
+// framePointerRequired when it is necessary. These values are used to pop
+// pushed args when an exception occurs.
+void StackLevelSetter::DoPhase()
+{
+ for (BasicBlock* block = comp->fgFirstBB; block != nullptr; block = block->bbNext)
+ {
+ ProcessBlock(block);
+ }
+#if !FEATURE_FIXED_OUT_ARGS
+
+ if (framePointerRequired && !comp->codeGen->isFramePointerRequired())
+ {
+ JITDUMP("framePointerRequired is not set when it is required\n");
+ comp->codeGen->resetWritePhaseForFramePointerRequired();
+ comp->codeGen->setFramePointerRequired(true);
+ }
+#endif // !FEATURE_FIXED_OUT_ARGS
+ assert(maxStackLevel <= comp->fgGetPtrArgCntMax());
+ if (maxStackLevel != comp->fgGetPtrArgCntMax())
+ {
+ JITDUMP("fgPtrArgCntMax was calculated wrong during the morph, the old value: %u, the right value: %u.\n",
+ comp->fgGetPtrArgCntMax(), maxStackLevel);
+ comp->fgSetPtrArgCntMax(maxStackLevel);
+ }
+}
+
+//------------------------------------------------------------------------
+// ProcessBlock: Do stack level calculations for one block.
+//
+// Notes:
+// Block starts and ends with an empty outgoing stack.
+// Nodes in blocks are iterated in the reverse order to memorize GT_PUTARG_STK
+// and GT_PUTARG_SPLIT stack sizes.
+//
+// Arguments:
+// block - the block to process.
+//
+void StackLevelSetter::ProcessBlock(BasicBlock* block)
+{
+ assert(currentStackLevel == 0);
+ LIR::ReadOnlyRange& range = LIR::AsRange(block);
+ for (auto i = range.rbegin(); i != range.rend(); ++i)
+ {
+ GenTree* node = *i;
+ if (node->OperIsPutArgStkOrSplit())
+ {
+ GenTreePutArgStk* putArg = node->AsPutArgStk();
+ unsigned numSlots = putArgNumSlots[putArg];
+ putArgNumSlots.Remove(putArg);
+ SubStackLevel(numSlots);
+ }
+
+#if !FEATURE_FIXED_OUT_ARGS
+ // Set throw blocks incoming stack depth for x86.
+ if (throwHelperBlocksUsed && !framePointerRequired)
+ {
+ bool operMightThrow = ((node->gtFlags & GTF_EXCEPT) != 0);
+ if (operMightThrow)
+ {
+ SetThrowHelperBlocks(node, block);
+ }
+ }
+#endif // !FEATURE_FIXED_OUT_ARGS
+
+ if (node->IsCall())
+ {
+ GenTreeCall* call = node->AsCall();
+
+ unsigned usedStackSlotsCount = PopArgumentsFromCall(call);
+#if defined(UNIX_X86_ABI)
+ assert(call->fgArgInfo->GetStkSizeBytes() == usedStackSlotsCount * TARGET_POINTER_SIZE);
+ call->fgArgInfo->SetStkSizeBytes(usedStackSlotsCount * TARGET_POINTER_SIZE);
+#endif // UNIX_X86_ABI
+ }
+ }
+ assert(currentStackLevel == 0);
+}
+
+#if !FEATURE_FIXED_OUT_ARGS
+//------------------------------------------------------------------------
+// SetThrowHelperBlocks: Set throw helper blocks incoming stack levels targeted
+// from the node.
+//
+// Notes:
+// one node can target several helper blocks.
+//
+// Arguments:
+// node - the node to process;
+// block - the source block for the node.
+void StackLevelSetter::SetThrowHelperBlocks(GenTree* node, BasicBlock* block)
+{
+ // Check that it uses throw block, find its kind, find the block, set level.
+ switch (node->OperGet())
+ {
+ case GT_ARR_BOUNDS_CHECK:
+#ifdef FEATURE_SIMD
+ case GT_SIMD_CHK:
+#endif // FEATURE_SIMD
+ {
+ GenTreeBoundsChk* bndsChk = node->AsBoundsChk();
+ SetThrowHelperBlock(bndsChk->gtThrowKind, block);
+ }
+ break;
+ case GT_INDEX_ADDR:
+ case GT_ARR_ELEM:
+ case GT_ARR_INDEX:
+ {
+ SetThrowHelperBlock(SCK_RNGCHK_FAIL, block);
+ }
+ break;
+
+ case GT_CKFINITE:
+ {
+ SetThrowHelperBlock(SCK_ARITH_EXCPN, block);
+ }
+ break;
+ }
+ if (node->gtOverflowEx())
+ {
+ SetThrowHelperBlock(SCK_OVERFLOW, block);
+ }
+}
+
+//------------------------------------------------------------------------
+// SetThrowHelperBlock: Set throw helper block incoming stack levels targeted
+// from the block with this kind.
+//
+// Notes:
+// Set framePointerRequired if finds that the block has several incoming edges
+// with different stack levels.
+//
+// Arguments:
+// kind - the special throw-helper kind;
+// block - the source block that targets helper.
+void StackLevelSetter::SetThrowHelperBlock(SpecialCodeKind kind, BasicBlock* block)
+{
+ Compiler::AddCodeDsc* add = comp->fgFindExcptnTarget(kind, comp->bbThrowIndex(block));
+ assert(add != nullptr);
+ if (add->acdStkLvlInit)
+ {
+ if (add->acdStkLvl != currentStackLevel)
+ {
+ framePointerRequired = true;
+ }
+ }
+ else
+ {
+ add->acdStkLvlInit = true;
+ if (add->acdStkLvl != currentStackLevel)
+ {
+ JITDUMP("Wrong stack level was set for block %d\n", add->acdDstBlk->bbNum);
+ }
+#ifdef DEBUG
+ add->acdDstBlk->bbTgtStkDepth = currentStackLevel;
+#endif // Debug
+ add->acdStkLvl = currentStackLevel;
+ }
+}
+
+#endif // !FEATURE_FIXED_OUT_ARGS
+
+//------------------------------------------------------------------------
+// PopArgumentsFromCall: Calculate the number of stack arguments that are used by the call.
+//
+// Notes:
+// memorize number of slots that each stack argument use.
+//
+// Arguments:
+// call - the call to process.
+//
+// Return value:
+// the number of stack slots in stack arguments for the call.
+unsigned StackLevelSetter::PopArgumentsFromCall(GenTreeCall* call)
+{
+ unsigned usedStackSlotsCount = 0;
+ fgArgInfo* argInfo = call->fgArgInfo;
+ if (argInfo->HasStackArgs())
+ {
+ for (unsigned i = 0; i < argInfo->ArgCount(); ++i)
+ {
+ fgArgTabEntry* argTab = argInfo->ArgTable()[i];
+ if (argTab->numSlots != 0)
+ {
+ GenTree* node = argTab->node;
+ assert(node->OperIsPutArgStkOrSplit());
+
+ GenTreePutArgStk* putArg = node->AsPutArgStk();
+
+#if !FEATURE_FIXED_OUT_ARGS
+ assert(argTab->numSlots == putArg->gtNumSlots);
+#endif // !FEATURE_FIXED_OUT_ARGS
+
+ putArgNumSlots.Set(putArg, argTab->numSlots);
+
+ usedStackSlotsCount += argTab->numSlots;
+ AddStackLevel(argTab->numSlots);
+ }
+ }
+ }
+ return usedStackSlotsCount;
+}
+
+//------------------------------------------------------------------------
+// SubStackLevel: Reflect pushing to the stack.
+//
+// Arguments:
+// value - a positive value to add.
+//
+void StackLevelSetter::AddStackLevel(unsigned value)
+{
+ currentStackLevel += value;
+
+ if (currentStackLevel > maxStackLevel)
+ {
+ maxStackLevel = currentStackLevel;
+ }
+}
+
+//------------------------------------------------------------------------
+// SubStackLevel: Reflect popping from the stack.
+//
+// Arguments:
+// value - a positive value to subtract.
+//
+void StackLevelSetter::SubStackLevel(unsigned value)
+{
+ assert(currentStackLevel >= value);
+ currentStackLevel -= value;
+}
+
+#endif // !LEGACY_BACKEND
diff --git a/src/jit/stacklevelsetter.h b/src/jit/stacklevelsetter.h
new file mode 100644
index 0000000000..6a5b61dbb1
--- /dev/null
+++ b/src/jit/stacklevelsetter.h
@@ -0,0 +1,42 @@
+// 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.
+
+#pragma once
+
+#include "compiler.h"
+#include "phase.h"
+
+class StackLevelSetter : public Phase
+{
+public:
+ StackLevelSetter(Compiler* compiler);
+
+ virtual void DoPhase() override;
+
+private:
+ void ProcessBlock(BasicBlock* block);
+
+#if !FEATURE_FIXED_OUT_ARGS
+ void SetThrowHelperBlocks(GenTree* node, BasicBlock* block);
+ void SetThrowHelperBlock(SpecialCodeKind kind, BasicBlock* block);
+#endif // !FEATURE_FIXED_OUT_ARGS
+
+ unsigned PopArgumentsFromCall(GenTreeCall* call);
+ void AddStackLevel(unsigned value);
+ void SubStackLevel(unsigned value);
+
+private:
+ unsigned currentStackLevel; // current number of stack slots used by arguments.
+ unsigned maxStackLevel; // max number of stack slots for arguments.
+
+ CompAllocator memAllocator;
+
+ typedef JitHashTable<GenTreePutArgStk*, JitPtrKeyFuncs<GenTreePutArgStk>, unsigned> PutArgNumSlotsMap;
+ PutArgNumSlotsMap putArgNumSlots; // The hash table keeps stack slot sizes for active GT_PUTARG_STK nodes.
+
+#if !FEATURE_FIXED_OUT_ARGS
+ bool framePointerRequired; // Is frame pointer required based on the analysis made by this phase.
+ bool throwHelperBlocksUsed; // Were any throw helper blocks created for this method.
+#endif // !FEATURE_FIXED_OUT_ARGS
+};