Fix loop hoist ordering (part I - "swap" case) (dotnet/coreclr#20650)
authormikedn <onemihaid@hotmail.com>
Thu, 22 Aug 2019 22:13:14 +0000 (01:13 +0300)
committerSergey Andreenko <seandree@microsoft.com>
Thu, 22 Aug 2019 22:13:14 +0000 (15:13 -0700)
* Change optHoistLoopExprsForTree to use GenTreeVisitor

* Rename ArrayStack::Index to Top

Having Bottom/Top/Index is risky, sooner or later someone will get confused and use Index instead of Bottom.

* optHoistLoopExprsForTree -> optHoistLoopExprsForStmt

Change `optHoistLoopExprsForTree` to handle the entire statement tree so we no longer need the `hoistable` parameter and `optHoistCandidate` is confined to the visitor.

* optHoistLoopExprsForStmt -> optHoistLoopExprsForBlock

Now move block hoisting to optHoistLoopExprsForStmt so the visitor can maintain `firstBlockAndBeforeSideEffect` itself instead of having a bool* passed to it.

* optHoistLoopExprsForBlock -> optHoistLoopBlocks

Put the block hoisting in one place so we can reuse the visitor,
together with it's value stack and whatever memory it has allocated.

optHoistLoopExprsForBlock -> optHoistLoopBlocks

* Fix loop hoist ordering

When a non-hoistable node is encountered it is not sufficient to hoist all its hoistable children, there may be siblings that are also hoistable and since the tree is traversed in execution order they need to be hoisted first.

* Remove optTreeIsValidAtLoopHead

It is redundant. HoistVisitor traverses a tree, decides that it is invariant and attempts to hoist it using optHoistCandidate.

And optHoistCandidate calls optTreeIsValidAtLoopHead that traverses the tree again and rejects hoisting if the tree contains non-SSA variables or SSA variables defined outside the loop.

Well, just check for such cases during the traversal done by HoistVisitor, there's no need to traverse the tree twice.

* Remove bogus conditions from optHoistCandidate

Nobody's calling it with an invalid loop number or with a loop that isn't hoistable.

* Remove bogus block op check

OperIsCopyBlkOp can't be true - the GT_ASG case was already handled and what's left are STORE_BLK/OBJ nodes, but those don't appear until lowering.

* Remove bogus PHI VN check

The "It's not *really* a definition, rather a pass-through of some other VN." case doesn't actually use VNF_PhiDef functions. If it's a pass-through then the VN of the PHI is just that "other VN".

* Remove MAX_CHILDREN define

It was used only by optHoistLoopExprsForTree. It's also incompatible with the elimination of GT_LIST nodes as that makes nodes have an unbounded number of children.

* PR feedback

* Mark local nodes as hoistable/invariant

Commit migrated from https://github.com/dotnet/coreclr/commit/f0c2c565528ebf71f41e9fa782ac4c2bb121fd7e

12 files changed:
src/coreclr/src/jit/arraystack.h
src/coreclr/src/jit/codegenarm64.cpp
src/coreclr/src/jit/compiler.h
src/coreclr/src/jit/copyprop.cpp
src/coreclr/src/jit/gentree.cpp
src/coreclr/src/jit/gentree.h
src/coreclr/src/jit/morph.cpp
src/coreclr/src/jit/objectalloc.cpp
src/coreclr/src/jit/optimizer.cpp
src/coreclr/src/jit/rationalize.cpp
src/coreclr/tests/src/JIT/Regression/JitBlue/GitHub_7147/GitHub_7147.cs [new file with mode: 0644]
src/coreclr/tests/src/JIT/Regression/JitBlue/GitHub_7147/GitHub_7147.csproj [new file with mode: 0644]

index 5c82705..76268ac 100644 (file)
@@ -71,30 +71,25 @@ public:
         return data[tosIndex];
     }
 
-    T Top()
+    // Pop `count` elements from the stack
+    void Pop(int count)
     {
-        assert(tosIndex > 0);
-        return data[tosIndex - 1];
-    }
-
-    T& TopRef()
-    {
-        assert(tosIndex > 0);
-        return data[tosIndex - 1];
+        assert(tosIndex >= count);
+        tosIndex -= count;
     }
 
-    // return the i'th from the top
-    T Index(int idx)
+    // Return the i'th element from the top
+    T Top(int i = 0)
     {
-        assert(tosIndex > idx);
-        return data[tosIndex - 1 - idx];
+        assert(tosIndex > i);
+        return data[tosIndex - 1 - i];
     }
 
-    // return a reference to the i'th from the top
-    T& IndexRef(int idx)
+    // Return a reference to the i'th element from the top
+    T& TopRef(int i = 0)
     {
-        assert(tosIndex > idx);
-        return data[tosIndex - 1 - idx];
+        assert(tosIndex > i);
+        return data[tosIndex - 1 - i];
     }
 
     int Height()
@@ -107,25 +102,18 @@ public:
         return tosIndex == 0;
     }
 
-    // return the bottom of the stack
-    T Bottom()
-    {
-        assert(tosIndex > 0);
-        return data[0];
-    }
-
-    // return the i'th from the bottom
-    T Bottom(int indx)
+    // Return the i'th element from the bottom
+    T Bottom(int i = 0)
     {
-        assert(tosIndex > indx);
-        return data[indx];
+        assert(tosIndex > i);
+        return data[i];
     }
 
-    // return a reference to the i'th from the bottom
-    T& BottomRef(int indx)
+    // Return a reference to the i'th element from the bottom
+    T& BottomRef(int i = 0)
     {
-        assert(tosIndex > indx);
-        return data[indx];
+        assert(tosIndex > i);
+        return data[i];
     }
 
     void Reset()
index 9f892e6..1d05fde 100644 (file)
@@ -699,7 +699,7 @@ void CodeGen::genRestoreCalleeSavedRegisterGroup(regMaskTP regsMask, int spDelta
             stackDelta = spDelta;
         }
 
-        RegPair regPair = regStack.Index(i);
+        RegPair regPair = regStack.Top(i);
         if (regPair.reg2 != REG_NA)
         {
             spOffset -= 2 * slotSize;
index 41c8ab8..b9130bb 100644 (file)
@@ -5686,25 +5686,11 @@ protected:
     // Hoist all expressions in "blk" that are invariant in loop "lnum" (an index into the optLoopTable)
     // outside of that loop.  Exempt expressions whose value number is in "m_hoistedInParentLoops"; add VN's of hoisted
     // expressions to "hoistInLoop".
-    void optHoistLoopExprsForBlock(BasicBlock* blk, unsigned lnum, LoopHoistContext* hoistCtxt);
+    void optHoistLoopBlocks(unsigned loopNum, ArrayStack<BasicBlock*>* blocks, LoopHoistContext* hoistContext);
 
     // Return true if the tree looks profitable to hoist out of loop 'lnum'.
     bool optIsProfitableToHoistableTree(GenTree* tree, unsigned lnum);
 
-    // Hoist all proper sub-expressions of "tree" (which occurs in "stmt", which occurs in "blk")
-    // that are invariant in loop "lnum" (an index into the optLoopTable)
-    // outside of that loop.  Exempt expressions whose value number is in "hoistedInParents"; add VN's of hoisted
-    // expressions to "hoistInLoop".
-    // Returns "true" iff "tree" is loop-invariant (wrt "lnum").
-    // Assumes that the value of "*firstBlockAndBeforeSideEffect" indicates that we're in the first block, and before
-    // any possible globally visible side effects.  Assume is called in evaluation order, and updates this.
-    bool optHoistLoopExprsForTree(GenTree*          tree,
-                                  unsigned          lnum,
-                                  LoopHoistContext* hoistCtxt,
-                                  bool*             firstBlockAndBeforeSideEffect,
-                                  bool*             pHoistable,
-                                  bool*             pCctorDependent);
-
     // Performs the hoisting 'tree' into the PreHeader for loop 'lnum'
     void optHoistCandidate(GenTree* tree, unsigned lnum, LoopHoistContext* hoistCtxt);
 
@@ -5713,12 +5699,6 @@ protected:
     //   VNPhi's connect VN's to the SSA definition, so we can know if the SSA def occurs in the loop.
     bool optVNIsLoopInvariant(ValueNum vn, unsigned lnum, VNToBoolMap* recordedVNs);
 
-    // Returns "true" iff "tree" is valid at the head of loop "lnum", in the context of the hoist substitution
-    // "subst".  If "tree" is a local SSA var, it is valid if its SSA definition occurs outside of the loop, or
-    // if it is in the domain of "subst" (meaning that it's definition has been previously hoisted, with a "standin"
-    // local.)  If tree is a constant, it is valid.  Otherwise, if it is an operator, it is valid iff its children are.
-    bool optTreeIsValidAtLoopHead(GenTree* tree, unsigned lnum);
-
     // If "blk" is the entry block of a natural loop, returns true and sets "*pLnum" to the index of the loop
     // in the loop table.
     bool optBlockIsLoopEntry(BasicBlock* blk, unsigned* pLnum);
index 5c22533..784987e 100644 (file)
@@ -62,7 +62,7 @@ void Compiler::optDumpCopyPropStack(LclNumToGenTreePtrStack* curSsaName)
     JITDUMP("{ ");
     for (LclNumToGenTreePtrStack::KeyIterator iter = curSsaName->Begin(); !iter.Equal(curSsaName->End()); ++iter)
     {
-        GenTree* node = iter.GetValue()->Index(0);
+        GenTree* node = iter.GetValue()->Top();
         JITDUMP("%d-[%06d]:V%02u ", iter.Get(), dspTreeID(node), node->AsLclVarCommon()->gtLclNum);
     }
     JITDUMP("}\n\n");
@@ -165,7 +165,7 @@ void Compiler::optCopyProp(BasicBlock* block, GenTreeStmt* stmt, GenTree* tree,
     {
         unsigned newLclNum = iter.Get();
 
-        GenTree* op = iter.GetValue()->Index(0);
+        GenTree* op = iter.GetValue()->Top();
 
         // Nothing to do if same.
         if (lclNum == newLclNum)
index 127ebef..d35debf 100644 (file)
@@ -109,7 +109,7 @@ struct IndentStack
         for (unsigned i = 0; i < indentCount; i++)
         {
             unsigned index = indentCount - 1 - i;
-            switch (stack.Index(index))
+            switch (stack.Top(index))
             {
                 case Compiler::IndentInfo::IINone:
                     printf("   ");
@@ -8164,7 +8164,6 @@ unsigned GenTree::NumChildren()
 GenTree* GenTree::GetChild(unsigned childNum)
 {
     assert(childNum < NumChildren()); // Precondition.
-    assert(NumChildren() <= MAX_CHILDREN);
     assert(!(OperIsConst() || OperIsLeaf()));
     if (OperIsUnary())
     {
@@ -15159,7 +15158,7 @@ bool Compiler::gtHasCatchArg(GenTree* tree)
 {
     for (int i = 0; i < parentStack->Height(); i++)
     {
-        GenTree* node = parentStack->Index(i);
+        GenTree* node = parentStack->Top(i);
         if (node->OperGet() == GT_CALL)
         {
             return true;
index 6e613f3..778e9e1 100644 (file)
@@ -2113,9 +2113,6 @@ private:
 public:
     bool Precedes(GenTree* other);
 
-    // The maximum possible # of children of any node.
-    static const int MAX_CHILDREN = 6;
-
     bool IsReuseRegVal() const
     {
         // This can be extended to non-constant nodes, but not to local or indir nodes.
index b68d9f2..8ff4553 100644 (file)
@@ -2699,7 +2699,7 @@ void Compiler::fgInitArgInfo(GenTreeCall* call)
         {
             for (int i = 0; i < args.Height(); i++)
             {
-                if (node == args.Index(i).node)
+                if (node == args.Top(i).node)
                 {
                     return i;
                 }
@@ -2725,7 +2725,7 @@ void Compiler::fgInitArgInfo(GenTreeCall* call)
         {
             for (int i = 0; i < args.Height(); i++)
             {
-                NonStandardArg& nsa = args.IndexRef(i);
+                NonStandardArg& nsa = args.TopRef(i);
                 if (node == nsa.node)
                 {
                     *pReg = nsa.reg;
@@ -2749,7 +2749,7 @@ void Compiler::fgInitArgInfo(GenTreeCall* call)
         //
         void Replace(int index, GenTree* node)
         {
-            args.IndexRef(index).node = node;
+            args.TopRef(index).node = node;
         }
 
     } nonStandardArgs(getAllocator(CMK_ArrayStack));
@@ -18309,7 +18309,7 @@ private:
 
     Value& TopValue(unsigned index)
     {
-        return m_valueStack.IndexRef(index);
+        return m_valueStack.TopRef(index);
     }
 
     void PopValue()
index 1a8f779..40d46e4 100644 (file)
@@ -171,7 +171,7 @@ void ObjectAllocator::MarkEscapingVarsAndBuildConnGraph()
             if ((tree->OperGet() == GT_LCL_VAR) && (type == TYP_REF || type == TYP_BYREF || type == TYP_I_IMPL))
             {
                 unsigned int lclNum = tree->AsLclVar()->GetLclNum();
-                assert(tree == m_ancestors.Index(0));
+                assert(tree == m_ancestors.Top());
 
                 if (m_allocator->CanLclVarEscapeViaParentStack(&m_ancestors, lclNum))
                 {
@@ -588,8 +588,8 @@ bool ObjectAllocator::CanLclVarEscapeViaParentStack(ArrayStack<GenTree*>* parent
         }
 
         canLclVarEscapeViaParentStack = true;
-        GenTree* tree                 = parentStack->Index(parentIndex - 1);
-        GenTree* parent               = parentStack->Index(parentIndex);
+        GenTree* tree                 = parentStack->Top(parentIndex - 1);
+        GenTree* parent               = parentStack->Top(parentIndex);
         keepChecking                  = false;
 
         switch (parent->OperGet())
@@ -643,7 +643,7 @@ bool ObjectAllocator::CanLclVarEscapeViaParentStack(ArrayStack<GenTree*>* parent
                 break;
 
             case GT_COMMA:
-                if (parent->AsOp()->gtGetOp1() == parentStack->Index(parentIndex - 1))
+                if (parent->AsOp()->gtGetOp1() == parentStack->Top(parentIndex - 1))
                 {
                     // Left child of GT_COMMA, it will be discarded
                     canLclVarEscapeViaParentStack = false;
@@ -663,7 +663,7 @@ bool ObjectAllocator::CanLclVarEscapeViaParentStack(ArrayStack<GenTree*>* parent
             {
                 int grandParentIndex = parentIndex + 1;
                 if ((parentStack->Height() > grandParentIndex) &&
-                    (parentStack->Index(grandParentIndex)->OperGet() == GT_ADDR))
+                    (parentStack->Top(grandParentIndex)->OperGet() == GT_ADDR))
                 {
                     // Check if the address of the field/ind escapes.
                     parentIndex += 2;
@@ -727,7 +727,7 @@ void ObjectAllocator::UpdateAncestorTypes(GenTree* tree, ArrayStack<GenTree*>* p
 
     while (keepChecking && (parentStack->Height() > parentIndex))
     {
-        GenTree* parent = parentStack->Index(parentIndex);
+        GenTree* parent = parentStack->Top(parentIndex);
         keepChecking    = false;
 
         switch (parent->OperGet())
@@ -750,7 +750,7 @@ void ObjectAllocator::UpdateAncestorTypes(GenTree* tree, ArrayStack<GenTree*>* p
                 break;
 
             case GT_COMMA:
-                if (parent->AsOp()->gtGetOp1() == parentStack->Index(parentIndex - 1))
+                if (parent->AsOp()->gtGetOp1() == parentStack->Top(parentIndex - 1))
                 {
                     // Left child of GT_COMMA, it will be discarded
                     break;
@@ -787,7 +787,7 @@ void ObjectAllocator::UpdateAncestorTypes(GenTree* tree, ArrayStack<GenTree*>* p
 
                 if (parentStack->Height() > grandParentIndex)
                 {
-                    GenTree* grandParent = parentStack->Index(grandParentIndex);
+                    GenTree* grandParent = parentStack->Top(grandParentIndex);
                     if (grandParent->OperGet() == GT_ADDR)
                     {
                         if (grandParent->TypeGet() == TYP_REF)
@@ -807,7 +807,7 @@ void ObjectAllocator::UpdateAncestorTypes(GenTree* tree, ArrayStack<GenTree*>* p
 
         if (keepChecking)
         {
-            tree = parentStack->Index(parentIndex - 1);
+            tree = parentStack->Top(parentIndex - 1);
         }
     }
 
index f881e44..14fcdbd 100644 (file)
@@ -6616,7 +6616,7 @@ void Compiler::optHoistThisLoop(unsigned lnum, LoopHoistContext* hoistCtxt)
     // Find the set of definitely-executed blocks.
     // Ideally, the definitely-executed blocks are the ones that post-dominate the entry block.
     // Until we have post-dominators, we'll special-case for single-exit blocks.
-    JitExpandArrayStack<BasicBlock*> defExec(getAllocatorLoopHoist());
+    ArrayStack<BasicBlock*> defExec(getAllocatorLoopHoist());
     if (pLoopDsc->lpFlags & LPFLG_ONE_EXIT)
     {
         assert(pLoopDsc->lpExit != nullptr);
@@ -6641,54 +6641,7 @@ void Compiler::optHoistThisLoop(unsigned lnum, LoopHoistContext* hoistCtxt)
         defExec.Push(pLoopDsc->lpEntry);
     }
 
-    while (defExec.Size() > 0)
-    {
-        // Consider in reverse order: dominator before dominatee.
-        BasicBlock* blk = defExec.Pop();
-        optHoistLoopExprsForBlock(blk, lnum, hoistCtxt);
-    }
-}
-
-// Hoist any expressions in "blk" that are invariant in loop "lnum" outside of "blk" and into a PreHead for loop "lnum".
-void Compiler::optHoistLoopExprsForBlock(BasicBlock* blk, unsigned lnum, LoopHoistContext* hoistCtxt)
-{
-    LoopDsc* pLoopDsc                      = &optLoopTable[lnum];
-    bool     firstBlockAndBeforeSideEffect = (blk == pLoopDsc->lpEntry);
-    unsigned blkWeight                     = blk->getBBWeight(this);
-
-#ifdef DEBUG
-    if (verbose)
-    {
-        printf("    optHoistLoopExprsForBlock " FMT_BB " (weight=%6s) of loop L%02u <" FMT_BB ".." FMT_BB
-               ">, firstBlock is %s\n",
-               blk->bbNum, refCntWtd2str(blkWeight), lnum, pLoopDsc->lpFirst->bbNum, pLoopDsc->lpBottom->bbNum,
-               firstBlockAndBeforeSideEffect ? "true" : "false");
-        if (blkWeight < (BB_UNITY_WEIGHT / 10))
-        {
-            printf("      block weight is too small to perform hoisting.\n");
-        }
-    }
-#endif
-
-    if (blkWeight < (BB_UNITY_WEIGHT / 10))
-    {
-        // Block weight is too small to perform hoisting.
-        return;
-    }
-
-    for (GenTreeStmt* stmt = blk->FirstNonPhiDef(); stmt != nullptr; stmt = stmt->getNextStmt())
-    {
-        GenTree* stmtTree = stmt->gtStmtExpr;
-        bool     hoistable;
-        bool     cctorDependent;
-        (void)optHoistLoopExprsForTree(stmtTree, lnum, hoistCtxt, &firstBlockAndBeforeSideEffect, &hoistable,
-                                       &cctorDependent);
-        if (hoistable)
-        {
-            // we will try to hoist the top-level stmtTree
-            optHoistCandidate(stmtTree, lnum, hoistCtxt);
-        }
-    }
+    optHoistLoopBlocks(lnum, &defExec, hoistCtxt);
 }
 
 bool Compiler::optIsProfitableToHoistableTree(GenTree* tree, unsigned lnum)
@@ -6786,254 +6739,409 @@ bool Compiler::optIsProfitableToHoistableTree(GenTree* tree, unsigned lnum)
     return true;
 }
 
+//------------------------------------------------------------------------
+// optHoistLoopBlocks: Hoist invariant expression out of the loop.
 //
-//  This function returns true if 'tree' is a loop invariant expression.
-//  It also sets '*pHoistable' to true if 'tree' can be hoisted into a loop PreHeader block,
-//  and sets '*pCctorDependent' if 'tree' is a function of a static field that must not be
-//  hoisted (even if '*pHoistable' is true) unless a preceding corresponding cctor init helper
-//  call is also hoisted.
+// Arguments:
+//    loopNum - The number of the loop
+//    blocks - A stack of blocks belonging to the loop
+//    hoistContext - The loop hoist context
 //
-bool Compiler::optHoistLoopExprsForTree(GenTree*          tree,
-                                        unsigned          lnum,
-                                        LoopHoistContext* hoistCtxt,
-                                        bool*             pFirstBlockAndBeforeSideEffect,
-                                        bool*             pHoistable,
-                                        bool*             pCctorDependent)
+// Assumptions:
+//    The `blocks` stack contains the definitely-executed blocks in
+//    the loop, in the execution order, starting with the loop entry
+//    block on top of the stack.
+//
+void Compiler::optHoistLoopBlocks(unsigned loopNum, ArrayStack<BasicBlock*>* blocks, LoopHoistContext* hoistContext)
 {
-    // First do the children.
-    // We must keep track of whether each child node was hoistable or not
-    //
-    unsigned nChildren = tree->NumChildren();
-    bool     childrenHoistable[GenTree::MAX_CHILDREN];
-    bool     childrenCctorDependent[GenTree::MAX_CHILDREN];
-
-    // Initialize the array elements for childrenHoistable[] to false
-    for (unsigned i = 0; i < nChildren; i++)
+    class HoistVisitor : public GenTreeVisitor<HoistVisitor>
     {
-        childrenHoistable[i]      = false;
-        childrenCctorDependent[i] = false;
-    }
+        class Value
+        {
+            GenTree* m_node;
 
-    // Initclass CLS_VARs and IconHandles are the base cases of cctor dependent trees.
-    // In the IconHandle case, it's of course the dereference, rather than the constant itself, that is
-    // truly dependent on the cctor.  So a more precise approach would be to separately propagate
-    // isCctorDependent and isAddressWhoseDereferenceWouldBeCctorDependent, but we don't for simplicity/throughput;
-    // the constant itself would be considered non-hoistable anyway, since optIsCSEcandidate returns
-    // false for constants.
-    bool treeIsCctorDependent = ((tree->OperIs(GT_CLS_VAR) && ((tree->gtFlags & GTF_CLS_VAR_INITCLASS) != 0)) ||
-                                 (tree->OperIs(GT_CNS_INT) && ((tree->gtFlags & GTF_ICON_INITCLASS) != 0)));
-    bool treeIsInvariant = true;
-    for (unsigned childNum = 0; childNum < nChildren; childNum++)
-    {
-        if (!optHoistLoopExprsForTree(tree->GetChild(childNum), lnum, hoistCtxt, pFirstBlockAndBeforeSideEffect,
-                                      &childrenHoistable[childNum], &childrenCctorDependent[childNum]))
+        public:
+            bool m_hoistable;
+            bool m_cctorDependent;
+            bool m_invariant;
+
+            Value(GenTree* node) : m_node(node), m_hoistable(false), m_cctorDependent(false), m_invariant(false)
+            {
+            }
+
+            GenTree* Node()
+            {
+                return m_node;
+            }
+        };
+
+        ArrayStack<Value> m_valueStack;
+        bool              m_beforeSideEffect;
+        unsigned          m_loopNum;
+        LoopHoistContext* m_hoistContext;
+
+        bool IsNodeHoistable(GenTree* node)
+        {
+            // TODO-CQ: This is a more restrictive version of a check that optIsCSEcandidate already does - it allows
+            // a struct typed node if a class handle can be recovered from it.
+            if (node->TypeGet() == TYP_STRUCT)
+            {
+                return false;
+            }
+
+            // Tree must be a suitable CSE candidate for us to be able to hoist it.
+            return m_compiler->optIsCSEcandidate(node);
+        }
+
+        bool IsTreeVNInvariant(GenTree* tree)
         {
-            treeIsInvariant = false;
+            return m_compiler->optVNIsLoopInvariant(tree->gtVNPair.GetLiberal(), m_loopNum,
+                                                    &m_hoistContext->m_curLoopVnInvariantCache);
         }
 
-        if (childrenCctorDependent[childNum])
+    public:
+        enum
+        {
+            ComputeStack      = false,
+            DoPreOrder        = true,
+            DoPostOrder       = true,
+            DoLclVarsOnly     = false,
+            UseExecutionOrder = true,
+        };
+
+        HoistVisitor(Compiler* compiler, unsigned loopNum, LoopHoistContext* hoistContext)
+            : GenTreeVisitor(compiler)
+            , m_valueStack(compiler->getAllocator(CMK_LoopHoist))
+            , m_beforeSideEffect(true)
+            , m_loopNum(loopNum)
+            , m_hoistContext(hoistContext)
         {
-            // Normally, a parent of a cctor-dependent tree is also cctor-dependent.
-            treeIsCctorDependent = true;
+        }
 
-            // Check for the case where we can stop propagating cctor-dependent upwards.
-            if (tree->OperIs(GT_COMMA) && (childNum == 1))
+        void HoistBlock(BasicBlock* block)
+        {
+            for (GenTreeStmt* stmt = block->FirstNonPhiDef(); stmt != nullptr; stmt = stmt->gtNextStmt)
             {
-                GenTree* op1 = tree->gtGetOp1();
-                if (op1->OperIs(GT_CALL))
+                WalkTree(&stmt->gtStmtExpr, nullptr);
+                assert(m_valueStack.TopRef().Node() == stmt->gtStmtExpr);
+
+                if (m_valueStack.TopRef().m_hoistable)
                 {
-                    GenTreeCall* call = op1->AsCall();
-                    if ((call->gtCallType == CT_HELPER) &&
-                        s_helperCallProperties.MayRunCctor(eeGetHelperNum(call->gtCallMethHnd)))
-                    {
-                        // Hoisting the comma is ok because it would hoist the initialization along
-                        // with the static field reference.
-                        treeIsCctorDependent = false;
-                        // Hoisting the static field without hoisting the initialization would be
-                        // incorrect, make sure we consider the field (which we flagged as
-                        // cctor-dependent) non-hoistable.
-                        noway_assert(!childrenHoistable[childNum]);
-                    }
+                    m_compiler->optHoistCandidate(stmt->gtStmtExpr, m_loopNum, m_hoistContext);
                 }
+
+                m_valueStack.Reset();
             }
-        }
-    }
 
-    // If all the children of "tree" are hoistable, then "tree" itself can be hoisted,
-    // unless it has a static var reference that can't be hoisted past its cctor call.
-    bool treeIsHoistable = treeIsInvariant && !treeIsCctorDependent;
+            // Only uncondtionally executed blocks in the loop are visited (see optHoistThisLoop)
+            // so after we're done visiting the first block we need to assume the worst, that the
+            // blocks that are not visisted have side effects.
+            m_beforeSideEffect = false;
+        }
 
-    // But we must see if anything else prevents "tree" from being hoisted.
-    //
-    if (treeIsInvariant)
-    {
-        // Tree must be a suitable CSE candidate for us to be able to hoist it.
-        treeIsHoistable &= optIsCSEcandidate(tree);
+        fgWalkResult PreOrderVisit(GenTree** use, GenTree* user)
+        {
+            GenTree* node = *use;
+            m_valueStack.Emplace(node);
+            return fgWalkResult::WALK_CONTINUE;
+        }
 
-        if (treeIsHoistable)
+        fgWalkResult PostOrderVisit(GenTree** use, GenTree* user)
         {
-            // We cannot hoist an r-value of TYP_STRUCT
-            // as they currently do not carry full descriptors of the struct type
-            if (tree->TypeGet() == TYP_STRUCT)
+            GenTree* tree = *use;
+
+            if (tree->OperIsLocal())
             {
-                treeIsHoistable = false;
+                GenTreeLclVarCommon* lclVar = tree->AsLclVarCommon();
+                unsigned             lclNum = lclVar->GetLclNum();
+
+                // To be invariant a LclVar node must not be the LHS of an assignment ...
+                bool isInvariant = !user->OperIs(GT_ASG) || (user->AsOp()->gtGetOp1() != tree);
+                // and the variable must be in SSA ...
+                isInvariant = isInvariant && m_compiler->lvaInSsa(lclNum);
+                // and the SSA definition must be outside the loop we're hoisting from ...
+                isInvariant = isInvariant &&
+                              !m_compiler->optLoopTable[m_loopNum].lpContains(
+                                  m_compiler->lvaGetDesc(lclNum)->GetPerSsaData(lclVar->GetSsaNum())->m_defLoc.m_blk);
+                // and the VN of the tree is considered invariant as well.
+                //
+                // TODO-CQ: This VN invariance check should not be necessary and in some cases it is conservative - it
+                // is possible that the SSA def is outside the loop but VN does not understand what the node is doing
+                // (e.g. LCL_FLD-based type reinterpretation) and assigns a "new, unique VN" to the node. This VN is
+                // associated with the block where the node is, a loop block, and thus the VN is considered to not be
+                // invariant.
+                // On the other hand, it is possible for a SSA def to be inside the loop yet the use to be invariant,
+                // if the defining expression is also invariant. In such a case the VN invariance would help but it is
+                // blocked by the SSA invariance check.
+                isInvariant = isInvariant && IsTreeVNInvariant(tree);
+
+                if (isInvariant)
+                {
+                    Value& top = m_valueStack.TopRef();
+                    assert(top.Node() == tree);
+                    top.m_invariant = true;
+                    // In general it doesn't make sense to hoist a local node but there are exceptions, for example
+                    // LCL_FLD nodes (because then the variable cannot be enregistered and the node always turns
+                    // into a memory access).
+                    top.m_hoistable = IsNodeHoistable(tree);
+                }
+
+                return fgWalkResult::WALK_CONTINUE;
             }
 
-            // If it's a call, it must be a helper call, and be pure.
-            // Further, if it may run a cctor, it must be labeled as "Hoistable"
-            // (meaning it won't run a cctor because the class is not precise-init).
-            if (tree->OperGet() == GT_CALL)
+            // Initclass CLS_VARs and IconHandles are the base cases of cctor dependent trees.
+            // In the IconHandle case, it's of course the dereference, rather than the constant itself, that is
+            // truly dependent on the cctor.  So a more precise approach would be to separately propagate
+            // isCctorDependent and isAddressWhoseDereferenceWouldBeCctorDependent, but we don't for
+            // simplicity/throughput; the constant itself would be considered non-hoistable anyway, since
+            // optIsCSEcandidate returns false for constants.
+            bool treeIsCctorDependent = ((tree->OperIs(GT_CLS_VAR) && ((tree->gtFlags & GTF_CLS_VAR_INITCLASS) != 0)) ||
+                                         (tree->OperIs(GT_CNS_INT) && ((tree->gtFlags & GTF_ICON_INITCLASS) != 0)));
+            bool treeIsInvariant          = true;
+            bool treeHasHoistableChildren = false;
+            int  childCount;
+
+            for (childCount = 0; m_valueStack.TopRef(childCount).Node() != tree; childCount++)
             {
-                GenTreeCall* call = tree->AsCall();
-                if (call->gtCallType != CT_HELPER)
+                Value& child = m_valueStack.TopRef(childCount);
+
+                if (child.m_hoistable)
                 {
-                    treeIsHoistable = false;
+                    treeHasHoistableChildren = true;
                 }
-                else
+
+                if (!child.m_invariant)
                 {
-                    CorInfoHelpFunc helpFunc = eeGetHelperNum(call->gtCallMethHnd);
-                    if (!s_helperCallProperties.IsPure(helpFunc))
-                    {
-                        treeIsHoistable = false;
-                    }
-                    else if (s_helperCallProperties.MayRunCctor(helpFunc) && (call->gtFlags & GTF_CALL_HOISTABLE) == 0)
+                    treeIsInvariant = false;
+                }
+
+                if (child.m_cctorDependent)
+                {
+                    // Normally, a parent of a cctor-dependent tree is also cctor-dependent.
+                    treeIsCctorDependent = true;
+
+                    // Check for the case where we can stop propagating cctor-dependent upwards.
+                    if (tree->OperIs(GT_COMMA) && (child.Node() == tree->gtGetOp2()))
                     {
-                        treeIsHoistable = false;
+                        GenTree* op1 = tree->gtGetOp1();
+                        if (op1->OperIs(GT_CALL))
+                        {
+                            GenTreeCall* call = op1->AsCall();
+                            if ((call->gtCallType == CT_HELPER) &&
+                                s_helperCallProperties.MayRunCctor(eeGetHelperNum(call->gtCallMethHnd)))
+                            {
+                                // Hoisting the comma is ok because it would hoist the initialization along
+                                // with the static field reference.
+                                treeIsCctorDependent = false;
+                                // Hoisting the static field without hoisting the initialization would be
+                                // incorrect, make sure we consider the field (which we flagged as
+                                // cctor-dependent) non-hoistable.
+                                noway_assert(!child.m_hoistable);
+                            }
+                        }
                     }
                 }
             }
-        }
 
-        if (treeIsHoistable)
-        {
-            if (!(*pFirstBlockAndBeforeSideEffect))
+            // If all the children of "tree" are hoistable, then "tree" itself can be hoisted,
+            // unless it has a static var reference that can't be hoisted past its cctor call.
+            bool treeIsHoistable = treeIsInvariant && !treeIsCctorDependent;
+
+            // But we must see if anything else prevents "tree" from being hoisted.
+            //
+            if (treeIsInvariant)
             {
-                // For now, we give up on an expression that might raise an exception if it is after the
-                // first possible global side effect (and we assume we're after that if we're not in the first block).
-                // TODO-CQ: this is when we might do loop cloning.
-                //
-                if ((tree->gtFlags & GTF_EXCEPT) != 0)
+                if (treeIsHoistable)
                 {
-                    treeIsHoistable = false;
+                    treeIsHoistable = IsNodeHoistable(tree);
                 }
-            }
-        }
 
-        // Is the value of the whole tree loop invariant?
-        treeIsInvariant =
-            optVNIsLoopInvariant(tree->gtVNPair.GetLiberal(), lnum, &hoistCtxt->m_curLoopVnInvariantCache);
+                // If it's a call, it must be a helper call, and be pure.
+                // Further, if it may run a cctor, it must be labeled as "Hoistable"
+                // (meaning it won't run a cctor because the class is not precise-init).
+                if (treeIsHoistable && tree->IsCall())
+                {
+                    GenTreeCall* call = tree->AsCall();
+                    if (call->gtCallType != CT_HELPER)
+                    {
+                        treeIsHoistable = false;
+                    }
+                    else
+                    {
+                        CorInfoHelpFunc helpFunc = eeGetHelperNum(call->gtCallMethHnd);
+                        if (!s_helperCallProperties.IsPure(helpFunc))
+                        {
+                            treeIsHoistable = false;
+                        }
+                        else if (s_helperCallProperties.MayRunCctor(helpFunc) &&
+                                 ((call->gtFlags & GTF_CALL_HOISTABLE) == 0))
+                        {
+                            treeIsHoistable = false;
+                        }
+                    }
+                }
 
-        // Is the value of the whole tree loop invariant?
-        if (!treeIsInvariant)
-        {
-            treeIsHoistable = false;
-        }
-    }
+                if (treeIsHoistable)
+                {
+                    if (!m_beforeSideEffect)
+                    {
+                        // For now, we give up on an expression that might raise an exception if it is after the
+                        // first possible global side effect (and we assume we're after that if we're not in the first
+                        // block).
+                        // TODO-CQ: this is when we might do loop cloning.
+                        //
+                        if ((tree->gtFlags & GTF_EXCEPT) != 0)
+                        {
+                            treeIsHoistable = false;
+                        }
+                    }
+                }
 
-    // Check if we need to set '*pFirstBlockAndBeforeSideEffect' to false.
-    // If we encounter a tree with a call in it
-    //  or if we see an assignment to global we set it to false.
-    //
-    // If we are already set to false then we can skip these checks
-    //
-    if (*pFirstBlockAndBeforeSideEffect)
-    {
-        // For this purpose, we only care about memory side effects.  We assume that expressions will
-        // be hoisted so that they are evaluated in the same order as they would have been in the loop,
-        // and therefore throw exceptions in the same order.  (So we don't use GTF_GLOBALLY_VISIBLE_SIDE_EFFECTS
-        // here, since that includes exceptions.)
-        if (tree->IsCall())
-        {
-            // If it's a call, it must be a helper call that does not mutate the heap.
-            // Further, if it may run a cctor, it must be labeled as "Hoistable"
-            // (meaning it won't run a cctor because the class is not precise-init).
-            GenTreeCall* call = tree->AsCall();
-            if (call->gtCallType != CT_HELPER)
-            {
-                *pFirstBlockAndBeforeSideEffect = false;
+                // Is the value of the whole tree loop invariant?
+                treeIsInvariant = IsTreeVNInvariant(tree);
+
+                // Is the value of the whole tree loop invariant?
+                if (!treeIsInvariant)
+                {
+                    treeIsHoistable = false;
+                }
             }
-            else
+
+            // Check if we need to set 'm_beforeSideEffect' to false.
+            // If we encounter a tree with a call in it
+            //  or if we see an assignment to global we set it to false.
+            //
+            // If we are already set to false then we can skip these checks
+            //
+            if (m_beforeSideEffect)
             {
-                CorInfoHelpFunc helpFunc = eeGetHelperNum(call->gtCallMethHnd);
-                if (s_helperCallProperties.MutatesHeap(helpFunc))
+                // For this purpose, we only care about memory side effects.  We assume that expressions will
+                // be hoisted so that they are evaluated in the same order as they would have been in the loop,
+                // and therefore throw exceptions in the same order.  (So we don't use GTF_GLOBALLY_VISIBLE_SIDE_EFFECTS
+                // here, since that includes exceptions.)
+                if (tree->IsCall())
                 {
-                    *pFirstBlockAndBeforeSideEffect = false;
+                    // If it's a call, it must be a helper call that does not mutate the heap.
+                    // Further, if it may run a cctor, it must be labeled as "Hoistable"
+                    // (meaning it won't run a cctor because the class is not precise-init).
+                    GenTreeCall* call = tree->AsCall();
+                    if (call->gtCallType != CT_HELPER)
+                    {
+                        m_beforeSideEffect = false;
+                    }
+                    else
+                    {
+                        CorInfoHelpFunc helpFunc = eeGetHelperNum(call->gtCallMethHnd);
+                        if (s_helperCallProperties.MutatesHeap(helpFunc))
+                        {
+                            m_beforeSideEffect = false;
+                        }
+                        else if (s_helperCallProperties.MayRunCctor(helpFunc) &&
+                                 (call->gtFlags & GTF_CALL_HOISTABLE) == 0)
+                        {
+                            m_beforeSideEffect = false;
+                        }
+                    }
                 }
-                else if (s_helperCallProperties.MayRunCctor(helpFunc) && (call->gtFlags & GTF_CALL_HOISTABLE) == 0)
+                else if (tree->OperIs(GT_ASG))
                 {
-                    *pFirstBlockAndBeforeSideEffect = false;
+                    // If the LHS of the assignment has a global reference, then assume it's a global side effect.
+                    GenTree* lhs = tree->gtOp.gtOp1;
+                    if (lhs->gtFlags & GTF_GLOB_REF)
+                    {
+                        m_beforeSideEffect = false;
+                    }
                 }
             }
-        }
-        else if (tree->OperIs(GT_ASG))
-        {
-            // If the LHS of the assignment has a global reference, then assume it's a global side effect.
-            GenTree* lhs = tree->gtOp.gtOp1;
-            if (lhs->gtFlags & GTF_GLOB_REF)
-            {
-                *pFirstBlockAndBeforeSideEffect = false;
-            }
-        }
-        else if (tree->OperIsCopyBlkOp())
-        {
-            GenTree* args = tree->gtOp.gtOp1;
-            assert(args->OperGet() == GT_LIST);
-            if (args->gtOp.gtOp1->gtFlags & GTF_GLOB_REF)
-            {
-                *pFirstBlockAndBeforeSideEffect = false;
-            }
-        }
-    }
 
-    // If this 'tree' is hoistable then we return and the caller will
-    // decide to hoist it as part of larger hoistable expression.
-    //
-    if (!treeIsHoistable)
-    {
-        // We are not hoistable so we will now hoist any hoistable children.
-        //
-        for (unsigned childNum = 0; childNum < nChildren; childNum++)
-        {
-            if (childrenHoistable[childNum])
+            // If this 'tree' is hoistable then we return and the caller will
+            // decide to hoist it as part of larger hoistable expression.
+            //
+            if (!treeIsHoistable && treeHasHoistableChildren)
             {
-                // We can't hoist the LHS of an assignment, isn't a real use.
-                if ((childNum == 0) && tree->OperIs(GT_ASG))
+                // The current tree is not hoistable but it has hoistable children that we need
+                // to hoist now.
+                //
+                // In order to preserve the original execution order, we also need to hoist any
+                // other hoistable trees that we encountered so far.
+                // At this point the stack contains (in top to bottom order):
+                //   - the current node's children
+                //   - the current node
+                //   - ancestors of the current node and some of their descendants
+                //
+                // The ancestors have not been visited yet in post order so they're not hoistable
+                // (and they cannot become hoistable because the current node is not) but some of
+                // their descendants may have already been traversed and be hoistable.
+                //
+                // The execution order is actually bottom to top so we'll start hoisting from
+                // the bottom of the stack, skipping the current node (which is expected to not
+                // be hoistable).
+                //
+                // Note that the treeHasHoistableChildren check avoids unnecessary stack traversing
+                // and also prevents hoisting trees too early. If the current tree is not hoistable
+                // and it doesn't have any hoistable children then there's no point in hoisting any
+                // other trees. Doing so would interfere with the cctor dependent case, where the
+                // cctor dependent node is initially not hoistable and may become hoistable later,
+                // when its parent comma node is visited.
+                //
+                for (int i = 0; i < m_valueStack.Height(); i++)
                 {
-                    continue;
-                }
+                    Value& value = m_valueStack.BottomRef(i);
 
-                GenTree* child = tree->GetChild(childNum);
+                    if (value.m_hoistable)
+                    {
+                        assert(value.Node() != tree);
+
+                        // Don't hoist this tree again.
+                        value.m_hoistable = false;
+                        value.m_invariant = false;
 
-                // We try to hoist this 'child' tree
-                optHoistCandidate(child, lnum, hoistCtxt);
+                        m_compiler->optHoistCandidate(value.Node(), m_loopNum, m_hoistContext);
+                    }
+                }
             }
+
+            m_valueStack.Pop(childCount);
+
+            Value& top = m_valueStack.TopRef();
+            assert(top.Node() == tree);
+            top.m_hoistable      = treeIsHoistable;
+            top.m_cctorDependent = treeIsCctorDependent;
+            top.m_invariant      = treeIsInvariant;
+
+            return fgWalkResult::WALK_CONTINUE;
         }
-    }
+    };
 
-    *pHoistable      = treeIsHoistable;
-    *pCctorDependent = treeIsCctorDependent;
-    return treeIsInvariant;
-}
+    LoopDsc* loopDsc = &optLoopTable[loopNum];
+    assert(blocks->Top() == loopDsc->lpEntry);
 
-void Compiler::optHoistCandidate(GenTree* tree, unsigned lnum, LoopHoistContext* hoistCtxt)
-{
-    if (lnum == BasicBlock::NOT_IN_LOOP)
-    {
-        // The hoisted expression isn't valid at any loop head so don't hoist this expression.
-        return;
-    }
+    HoistVisitor visitor(this, loopNum, hoistContext);
 
-    // The outer loop also must be suitable for hoisting...
-    if ((optLoopTable[lnum].lpFlags & LPFLG_HOISTABLE) == 0)
+    while (!blocks->Empty())
     {
-        return;
-    }
+        BasicBlock* block       = blocks->Pop();
+        unsigned    blockWeight = block->getBBWeight(this);
 
-    // If the hoisted expression isn't valid at this loop head then break
-    if (!optTreeIsValidAtLoopHead(tree, lnum))
-    {
-        return;
+        JITDUMP("    optHoistLoopBlocks " FMT_BB " (weight=%6s) of loop L%02u <" FMT_BB ".." FMT_BB
+                ">, firstBlock is %s\n",
+                block->bbNum, refCntWtd2str(blockWeight), loopNum, loopDsc->lpFirst->bbNum, loopDsc->lpBottom->bbNum,
+                (block == loopDsc->lpEntry) ? "true" : "false");
+
+        if (blockWeight < (BB_UNITY_WEIGHT / 10))
+        {
+            JITDUMP("      block weight is too small to perform hoisting.\n");
+            continue;
+        }
+
+        visitor.HoistBlock(block);
     }
+}
+
+void Compiler::optHoistCandidate(GenTree* tree, unsigned lnum, LoopHoistContext* hoistCtxt)
+{
+    assert(lnum != BasicBlock::NOT_IN_LOOP);
+    assert((optLoopTable[lnum].lpFlags & LPFLG_HOISTABLE) != 0);
 
     // It must pass the hoistable profitablity tests for this loop level
     if (!optIsProfitableToHoistableTree(tree, lnum))
@@ -7105,23 +7213,11 @@ bool Compiler::optVNIsLoopInvariant(ValueNum vn, unsigned lnum, VNToBoolMap* loo
     {
         if (funcApp.m_func == VNF_PhiDef)
         {
-            // First, make sure it's a "proper" phi -- the definition is a Phi application.
-            VNFuncApp phiDefValFuncApp;
-            if (!vnStore->GetVNFunc(funcApp.m_args[2], &phiDefValFuncApp) || phiDefValFuncApp.m_func != VNF_Phi)
-            {
-                // It's not *really* a definition, rather a pass-through of some other VN.
-                // (This could occur, say if both sides of an if-then-else diamond made the
-                // same assignment to a variable.)
-                res = optVNIsLoopInvariant(funcApp.m_args[2], lnum, loopVnInvariantCache);
-            }
-            else
-            {
-                // Is the definition within the loop?  If so, is not loop-invariant.
-                unsigned      lclNum = funcApp.m_args[0];
-                unsigned      ssaNum = funcApp.m_args[1];
-                LclSsaVarDsc* ssaDef = lvaTable[lclNum].GetPerSsaData(ssaNum);
-                res                  = !optLoopContains(lnum, ssaDef->m_defLoc.m_blk->bbNatLoopNum);
-            }
+            // Is the definition within the loop?  If so, is not loop-invariant.
+            unsigned      lclNum = funcApp.m_args[0];
+            unsigned      ssaNum = funcApp.m_args[1];
+            LclSsaVarDsc* ssaDef = lvaTable[lclNum].GetPerSsaData(ssaNum);
+            res                  = !optLoopContains(lnum, ssaDef->m_defLoc.m_blk->bbNatLoopNum);
         }
         else if (funcApp.m_func == VNF_PhiMemoryDef)
         {
@@ -7163,44 +7259,6 @@ bool Compiler::optVNIsLoopInvariant(ValueNum vn, unsigned lnum, VNToBoolMap* loo
     return res;
 }
 
-bool Compiler::optTreeIsValidAtLoopHead(GenTree* tree, unsigned lnum)
-{
-    if (tree->OperIsLocal())
-    {
-        GenTreeLclVarCommon* lclVar = tree->AsLclVarCommon();
-        unsigned             lclNum = lclVar->gtLclNum;
-
-        // The lvlVar must be have an Ssa tracked lifetime
-        if (!lvaInSsa(lclNum))
-        {
-            return false;
-        }
-
-        // If the loop does not contains the SSA def we can hoist it.
-        if (!optLoopTable[lnum].lpContains(lvaTable[lclNum].GetPerSsaData(lclVar->GetSsaNum())->m_defLoc.m_blk))
-        {
-            return true;
-        }
-    }
-    else if (tree->OperIsConst())
-    {
-        return true;
-    }
-    else // If every one of the children nodes are valid at this Loop's Head.
-    {
-        unsigned nChildren = tree->NumChildren();
-        for (unsigned childNum = 0; childNum < nChildren; childNum++)
-        {
-            if (!optTreeIsValidAtLoopHead(tree->GetChild(childNum), lnum))
-            {
-                return false;
-            }
-        }
-        return true;
-    }
-    return false;
-}
-
 /*****************************************************************************
  *
  *  Creates a pre-header block for the given loop - a preheader is a BBJ_NONE
index f70cc55..555325f 100644 (file)
@@ -165,7 +165,7 @@ void Rationalizer::RewriteNodeAsCall(GenTree**             use,
     // Replace "tree" with "call"
     if (parents.Height() > 1)
     {
-        parents.Index(1)->ReplaceOperand(use, call);
+        parents.Top(1)->ReplaceOperand(use, call);
     }
     else
     {
@@ -181,7 +181,7 @@ void Rationalizer::RewriteNodeAsCall(GenTree**             use,
     // 0 is current node, so start at 1
     for (int i = 1; i < parents.Height(); i++)
     {
-        parents.Index(i)->gtFlags |= (call->gtFlags & GTF_ALL_EFFECT) | GTF_CALL;
+        parents.Top(i)->gtFlags |= (call->gtFlags & GTF_ALL_EFFECT) | GTF_CALL;
     }
 
     // Since "tree" is replaced with "call", pop "tree" node (i.e the current node)
@@ -593,7 +593,7 @@ Compiler::fgWalkResult Rationalizer::RewriteNode(GenTree** useEdge, Compiler::Ge
     }
     else
     {
-        use = LIR::Use(BlockRange(), useEdge, parentStack.Index(1));
+        use = LIR::Use(BlockRange(), useEdge, parentStack.Top(1));
     }
 
     assert(node == use.Def());
diff --git a/src/coreclr/tests/src/JIT/Regression/JitBlue/GitHub_7147/GitHub_7147.cs b/src/coreclr/tests/src/JIT/Regression/JitBlue/GitHub_7147/GitHub_7147.cs
new file mode 100644 (file)
index 0000000..e647d49
--- /dev/null
@@ -0,0 +1,95 @@
+// 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.
+
+using System;
+using System.Runtime.CompilerServices;
+
+namespace N
+{
+    public class C
+    {
+        int f = 2;
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        static bool cross(int k, int y, C c)
+        {
+            bool b = false;
+
+            for (int i = 0; i < k; ++i)
+            {
+                // Here "c.f" is invariant, but is evaluated after "i / y"
+                // which may throw a different kind of exception, so can't
+                // be hoisted without potentially changing the exception kind
+                // thrown.
+                b = (i / y < i + c.f);
+            }
+
+            return b;
+        }
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        static bool swap(int k, int x, int y, C c)
+        {
+            bool b = false;
+
+            for (int i = 0; i < k; ++i)
+            {
+                // Sub-expressions "x / y" and "c.f" are both invariant
+                // w.r.t. this loop, and can be hoisted.  Since each can
+                // raise an exception, and the exceptions have different
+                // types, their relative order must be preserved -- the
+                // hoisted copy of "x / y" must be evaluated before the
+                // hoisted copy of "c.f"
+                b = (x / y < i + c.f);
+            }
+
+            return b;
+        }
+
+        public static int Main(string[] args)
+        {
+            int errors = 0;
+
+#if NOT_FIXED
+            try
+            {
+                cross(10, 0, null);
+                // DivByZero should be raised from 'swap'; normal return
+                // is an error.
+                errors |= 1;
+            }
+            catch (DivideByZeroException)
+            {
+                // This is the correct result -- i / y should be evaluated and
+                // raise this exception (before c.f raises nulllref).
+            }
+            catch
+            {
+                // Any exception other than DivideByZero is a failure
+                errors |= 2;
+            }
+#endif
+
+            try
+            {
+                swap(10, 11, 0, null);
+                // DivByZero should be raised from 'swap'; normal return
+                // is an error.
+                errors |= 4;
+            }
+            catch (DivideByZeroException)
+            {
+                // This is the correct result -- x / y should be evaluated and
+                // raise this exception (before c.f raises nulllref).
+            }
+            catch
+            {
+                // Any exception other than DivideByZero is a failure
+                errors |= 8;
+            }
+
+            return 100 + errors;
+        }
+    }
+}
diff --git a/src/coreclr/tests/src/JIT/Regression/JitBlue/GitHub_7147/GitHub_7147.csproj b/src/coreclr/tests/src/JIT/Regression/JitBlue/GitHub_7147/GitHub_7147.csproj
new file mode 100644 (file)
index 0000000..19781e2
--- /dev/null
@@ -0,0 +1,10 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <DebugType />
+    <Optimize>True</Optimize>
+  </PropertyGroup>
+  <ItemGroup>
+    <Compile Include="$(MSBuildProjectName).cs" />
+  </ItemGroup>
+</Project>