[RyuJit] Stack level setter (#15597)
authorSergey Andreenko <seandree@microsoft.com>
Wed, 14 Feb 2018 23:38:58 +0000 (15:38 -0800)
committerGitHub <noreply@github.com>
Wed, 14 Feb 2018 23:38:58 +0000 (15:38 -0800)
* 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.

14 files changed:
src/jit/CMakeLists.txt
src/jit/codegencommon.cpp
src/jit/codegeninterface.h
src/jit/compiler.cpp
src/jit/compiler.h
src/jit/compphases.h
src/jit/flowgraph.cpp
src/jit/gentree.h
src/jit/gtstructs.h
src/jit/jit.settings.targets
src/jit/stacklevelsetter.cpp [new file with mode: 0644]
src/jit/stacklevelsetter.h [new file with mode: 0644]
tests/src/JIT/Regression/JitBlue/DevDiv_534476/DevDiv_534476.il [new file with mode: 0644]
tests/src/JIT/Regression/JitBlue/DevDiv_534476/DevDiv_534476.ilproj [new file with mode: 0644]

index f4626ec..8d87336 100644 (file)
@@ -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
index 103ec69..be29a2a 100644 (file)
@@ -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);
index b0eecbd..604e3ae 100644 (file)
@@ -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)
index ce9b3dc..cec1dd3 100644 (file)
@@ -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.
 
index a309d9e..a401c47 100644 (file)
@@ -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();
 
index fe19d91..61db5f3 100644 (file)
@@ -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)
index dc71419..f3a0bf2 100644 (file)
@@ -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;
+}
index 5f534db..ed5664e 100644 (file)
@@ -1294,6 +1294,11 @@ public:
         return gtOper == GT_PUTARG_STK;
     }
 
+    bool OperIsPutArgStkOrSplit() const
+    {
+        return OperIsPutArgStk() || OperIsPutArgSplit();
+    }
+
     bool OperIsPutArgReg() const
     {
         return gtOper == GT_PUTARG_REG;
index f3ff021..6cfb92c 100644 (file)
@@ -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) 
index 220c00f..467930e 100644 (file)
@@ -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 (file)
index 0000000..a3d9259
--- /dev/null
@@ -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 (file)
index 0000000..6a5b61d
--- /dev/null
@@ -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
+};
diff --git a/tests/src/JIT/Regression/JitBlue/DevDiv_534476/DevDiv_534476.il b/tests/src/JIT/Regression/JitBlue/DevDiv_534476/DevDiv_534476.il
new file mode 100644 (file)
index 0000000..1a63094
--- /dev/null
@@ -0,0 +1,57 @@
+// 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.
+
+.assembly extern System.Runtime {auto}
+.assembly extern System.Console {auto}
+.assembly DevDiv_534476 {}
+
+// The test showed a problem with stack level calculations during morph phase
+// that did not reflect code movements in the later phases.
+
+// In this case a helper call, created for IL_002c, will create a put_arg, but it will be moved from IL_0026 to IL_0014.
+// This  put_arg movement will affect throw edge at IL_0021, that will have incorrect stack level.
+
+.class private auto ansi beforefieldinit DevDiv_534476.ILGEN_CLASS
+       extends [System.Runtime]System.Object
+{
+  .method private static float32 
+          ILGEN_METHOD(uint64 e) cil managed
+  {
+       .maxstack  46
+       .locals init (native unsigned int, bool)
+       IL_0000: ldarg 0x0000
+                        ldc.i4.1
+       IL_0012: conv.ovf.u2.un
+       IL_0014: shr.un
+       IL_001b: ldarg 0x0000
+       IL_0021: conv.ovf.u4.un // the first throw block edge.
+       IL_0023: ldloc.s 0x01
+       IL_0025: mul // mul value is known to be null.
+       IL_0026: shl // long decomposition will replace IL_0026 with IL_0014.
+       IL_002c: conv.r4 // will be transformed into helper call, with put_arg before IL_0021.
+       IL_0009: ckfinite // to create the second throw edge.
+
+       IL_002d: ret
+  } // end of method ILGEN_CLASS::ILGEN_METHOD
+
+.method private hidebysig static int32  Main(string[] args) cil managed
+{
+  .entrypoint
+  // Code size       30 (0x1e)
+  .maxstack  6
+  .locals init (int32 V_0)
+  IL_0000:  nop
+  IL_000d:  ldc.i4.1
+  IL_000e:  conv.i8
+  IL_0011:  call       float32 DevDiv_534476.ILGEN_CLASS::ILGEN_METHOD(uint64)
+  IL_0016:  pop
+  IL_0017:  ldc.i4.s   100
+  IL_0019:  stloc.0
+  IL_001a:  br.s       IL_001c
+  IL_001c:  ldloc.0
+  IL_001d:  ret
+} // end of method ILGEN_CLASS::Main
+
+
+} // end of class DevDiv_534476.ILGEN_CLASS
diff --git a/tests/src/JIT/Regression/JitBlue/DevDiv_534476/DevDiv_534476.ilproj b/tests/src/JIT/Regression/JitBlue/DevDiv_534476/DevDiv_534476.ilproj
new file mode 100644 (file)
index 0000000..b2754ed
--- /dev/null
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <AssemblyName>$(MSBuildProjectName)</AssemblyName>
+    <SchemaVersion>2.0</SchemaVersion>
+    <ProjectGuid>{95DFC527-4DC1-495E-97D7-E94EE1F7140D}</ProjectGuid>
+    <OutputType>Exe</OutputType>
+    <ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+    <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\</SolutionDir>
+  </PropertyGroup>
+  <!-- Default configurations to help VS understand the configurations -->
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "></PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "></PropertyGroup>
+  <ItemGroup>
+    <CodeAnalysisDependentAssemblyPaths Condition=" '$(VS100COMNTOOLS)' != '' " Include="$(VS100COMNTOOLS)..\IDE\PrivateAssemblies">
+      <Visible>False</Visible>
+    </CodeAnalysisDependentAssemblyPaths>
+  </ItemGroup>
+  <PropertyGroup>
+    <DebugType>None</DebugType>
+    <Optimize>True</Optimize>
+  </PropertyGroup>
+  <ItemGroup>
+    <Compile Include="DevDiv_534476.il" />
+  </ItemGroup>
+  <ItemGroup>
+    <Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
+  </ItemGroup>
+  <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
+  <PropertyGroup Condition=" '$(MsBuildProjectDirOverride)' != '' "></PropertyGroup>
+</Project>