Merge pull request #13439 from dotnet-maestro-bot/master-UpdateDependencies
authorWes Haggard <weshaggard@users.noreply.github.com>
Fri, 18 Aug 2017 20:00:50 +0000 (13:00 -0700)
committerGitHub <noreply@github.com>
Fri, 18 Aug 2017 20:00:50 +0000 (13:00 -0700)
Update CoreClr, CoreFx to preview2-25618-04, preview2-25618-02, respectively (master)

19 files changed:
clrdefinitions.cmake
dir.props
src/inc/clrconfigvalues.h
src/jit/codegenlegacy.cpp
src/jit/compiler.h
src/jit/optimizer.cpp
src/vm/CMakeLists.txt
src/vm/eeconfig.cpp
src/vm/eeconfig.h
src/vm/gdbjit.cpp
src/vm/gdbjit.h
tests/src/Common/build_against_pkg_dependencies/build_against_pkg_dependencies.csproj
tests/src/Common/targeting_pack_ref/targeting_pack_ref.csproj
tests/src/Common/test_dependencies/test_dependencies.csproj
tests/src/Common/test_runtime/test_runtime.csproj
tests/src/JIT/Performance/CodeQuality/Layout/SearchLoops.cs [new file with mode: 0644]
tests/src/JIT/Performance/CodeQuality/Layout/SearchLoops.csproj [new file with mode: 0644]
tests/src/JIT/Regression/JitBlue/GitHub_9692/GitHub_9692.cs [new file with mode: 0644]
tests/src/JIT/Regression/JitBlue/GitHub_9692/GitHub_9692.csproj [new file with mode: 0644]

index f6b8de4..1848e3b 100644 (file)
@@ -114,6 +114,12 @@ endif(FEATURE_DBGIPC)
 if(FEATURE_EVENT_TRACE)
     add_definitions(-DFEATURE_EVENT_TRACE=1)
 endif(FEATURE_EVENT_TRACE)
+if(FEATURE_GDBJIT)
+    add_definitions(-DFEATURE_GDBJIT)
+endif()
+if(FEATURE_GDBJIT_FRAME)
+    add_definitions(-DFEATURE_GDBJIT_FRAME)
+endif(FEATURE_GDBJIT_FRAME)
 if(CLR_CMAKE_PLATFORM_LINUX)
     add_definitions(-DFEATURE_PERFTRACING)
 endif(CLR_CMAKE_PLATFORM_LINUX)
index 4ee67e5..4777181 100644 (file)
--- a/dir.props
+++ b/dir.props
@@ -24,7 +24,7 @@
 
   <!-- Profile-based optimization data package versions -->
   <PropertyGroup>
-    <PgoDataPackageVersion>99.99.99-master-20170712-0121</PgoDataPackageVersion>
+    <PgoDataPackageVersion>99.99.99-master-20170817-0120</PgoDataPackageVersion>
     <!--<IbcDataPackageVersion></IbcDataPackageVersion>-->
   </PropertyGroup>
 
index 14fc912..6cd82ae 100644 (file)
@@ -1054,6 +1054,13 @@ RETAIL_CONFIG_DWORD_INFO(EXTERNAL_AllowDComReflection, W("AllowDComReflection"),
 //
 RETAIL_CONFIG_DWORD_INFO(INTERNAL_PerformanceTracing, W("PerformanceTracing"), 0, "Enable/disable performance tracing.  Non-zero values enable tracing.")
 
+#ifdef FEATURE_GDBJIT
+//
+// GDBJIT
+//
+CONFIG_STRING_INFO(INTERNAL_GDBJitElfDump, W("GDBJitElfDump"), "Dump ELF for specified method")
+#endif
+
 //
 // Unknown
 // 
index 67a17ef..acc7220 100644 (file)
@@ -18830,15 +18830,24 @@ regMaskTP CodeGen::genCodeForCall(GenTreeCall* call, bool valUsed)
                                                    (ssize_t)stubAddr);
                             // The stub will write-back to this register, so don't track it
                             regTracker.rsTrackRegTrash(compiler->virtualStubParamInfo->GetReg());
-                            getEmitter()->emitIns_R_R_I(INS_ldr, EA_PTRSIZE, REG_JUMP_THUNK_PARAM,
+                            regNumber indReg;
+                            if (compiler->IsTargetAbi(CORINFO_CORERT_ABI))
+                            {
+                                indReg = regSet.rsGrabReg(RBM_ALLINT & ~compiler->virtualStubParamInfo->GetRegMask());
+                            }
+                            else
+                            {
+                                indReg = REG_JUMP_THUNK_PARAM;
+                            }
+                            getEmitter()->emitIns_R_R_I(INS_ldr, EA_PTRSIZE, indReg,
                                                         compiler->virtualStubParamInfo->GetReg(), 0);
-                            regTracker.rsTrackRegTrash(REG_JUMP_THUNK_PARAM);
+                            regTracker.rsTrackRegTrash(indReg);
                             callTypeStubAddr = emitter::EC_INDIR_R;
                             getEmitter()->emitIns_Call(emitter::EC_INDIR_R,
                                                        NULL,                                // methHnd
                                                        INDEBUG_LDISASM_COMMA(sigInfo) NULL, // addr
                                                        args, retSize, gcInfo.gcVarPtrSetCur, gcInfo.gcRegGCrefSetCur,
-                                                       gcInfo.gcRegByrefSetCur, ilOffset, REG_JUMP_THUNK_PARAM);
+                                                       gcInfo.gcRegByrefSetCur, ilOffset, indReg);
 
 #else
                             // emit an indirect call
index 2c6f9b2..a1c15fb 100644 (file)
@@ -4115,11 +4115,11 @@ public:
     void vnPrint(ValueNum vn, unsigned level);
 #endif
 
+    bool fgDominate(BasicBlock* b1, BasicBlock* b2); // Return true if b1 dominates b2
+
     // Dominator computation member functions
     // Not exposed outside Compiler
 protected:
-    bool fgDominate(BasicBlock* b1, BasicBlock* b2); // Return true if b1 dominates b2
-
     bool fgReachable(BasicBlock* b1, BasicBlock* b2); // Returns true if block b1 can reach block b2
 
     void fgComputeDoms(); // Computes the immediate dominators for each basic block in the
@@ -5317,6 +5317,14 @@ public:
     LoopDsc       optLoopTable[MAX_LOOP_NUM]; // loop descriptor table
     unsigned char optLoopCount;               // number of tracked loops
 
+    bool optRecordLoop(BasicBlock*   head,
+                       BasicBlock*   first,
+                       BasicBlock*   top,
+                       BasicBlock*   entry,
+                       BasicBlock*   bottom,
+                       BasicBlock*   exit,
+                       unsigned char exitCnt);
+
 protected:
     unsigned optCallCount;         // number of calls made in the method
     unsigned optIndirectCallCount; // number of virtual, interface and indirect calls made in the method
@@ -5360,14 +5368,6 @@ protected:
                                 GenTreePtr* ppTest,
                                 GenTreePtr* ppIncr);
 
-    void optRecordLoop(BasicBlock*   head,
-                       BasicBlock*   first,
-                       BasicBlock*   top,
-                       BasicBlock*   entry,
-                       BasicBlock*   bottom,
-                       BasicBlock*   exit,
-                       unsigned char exitCnt);
-
     void optFindNaturalLoops();
 
     // Ensures that all the loops in the loop nest rooted at "loopInd" (an index into the loop table) are 'canonical' --
index f5b54e4..efeb24d 100644 (file)
@@ -1119,10 +1119,11 @@ bool Compiler::optExtractInitTestIncr(
 
 /*****************************************************************************
  *
- *  Record the loop in the loop table.
+ *  Record the loop in the loop table.  Return true if successful, false if
+ *  out of entries in loop table.
  */
 
-void Compiler::optRecordLoop(BasicBlock*   head,
+bool Compiler::optRecordLoop(BasicBlock*   head,
                              BasicBlock*   first,
                              BasicBlock*   top,
                              BasicBlock*   entry,
@@ -1138,7 +1139,7 @@ void Compiler::optRecordLoop(BasicBlock*   head,
 #if COUNT_LOOPS
         loopOverflowThisMethod = true;
 #endif
-        return;
+        return false;
     }
 
     // Assumed preconditions on the loop we're adding.
@@ -1318,6 +1319,7 @@ void Compiler::optRecordLoop(BasicBlock*   head,
 DONE_LOOP:
     DBEXEC(verbose, optPrintLoopRecording(loopInd));
     optLoopCount++;
+    return true;
 }
 
 #ifdef DEBUG
@@ -1416,390 +1418,1039 @@ void Compiler::optCheckPreds()
 
 #endif // DEBUG
 
-/*****************************************************************************
- * Find the natural loops, using dominators. Note that the test for
- * a loop is slightly different from the standard one, because we have
- * not done a depth first reordering of the basic blocks.
- */
-
-void Compiler::optFindNaturalLoops()
+namespace
 {
-#ifdef DEBUG
-    if (verbose)
+//------------------------------------------------------------------------
+// LoopSearch: Class that handles scanning a range of blocks to detect a loop,
+//             moving blocks to make the loop body contiguous, and recording
+//             the loop.
+//
+// We will use the following terminology:
+//   HEAD    - the basic block that flows into the loop ENTRY block (Currently MUST be lexically before entry).
+//             Not part of the looping of the loop.
+//   FIRST   - the lexically first basic block (in bbNext order) within this loop.
+//   TOP     - the target of the backward edge from BOTTOM. In most cases FIRST and TOP are the same.
+//   BOTTOM  - the lexically last block in the loop (i.e. the block from which we jump to the top)
+//   EXIT    - the predecessor of loop's unique exit edge, if it has a unique exit edge; else nullptr
+//   ENTRY   - the entry in the loop (not necessarly the TOP), but there must be only one entry
+//
+//   We (currently) require the body of a loop to be a contiguous (in bbNext order) sequence of basic blocks.
+//   When the loop is identified, blocks will be moved out to make it a compact contiguous region if possible,
+//   and in cases where compaction is not possible, we'll subsequently treat all blocks in the lexical range
+//   between TOP and BOTTOM as part of the loop even if they aren't part of the SCC.
+//   Regarding nesting:  Since a given block can only have one back-edge (we only detect loops with back-edges
+//   from BBJ_COND or BBJ_ALWAYS blocks), no two loops will share the same BOTTOM.  Two loops may share the
+//   same FIRST/TOP/ENTRY as reported by LoopSearch, and optCanonicalizeLoopNest will subsequently re-write
+//   the CFG so that no two loops share the same FIRST/TOP/ENTRY anymore.
+//
+//        |
+//        v
+//      head
+//        |
+//        |  top/first <--+
+//        |       |       |
+//        |      ...      |
+//        |       |       |
+//        |       v       |
+//        +---> entry     |
+//                |       |
+//               ...      |
+//                |       |
+//                v       |
+//         +-- exit/tail  |
+//         |      |       |
+//         |     ...      |
+//         |      |       |
+//         |      v       |
+//         |    bottom ---+
+//         |
+//         +------+
+//                |
+//                v
+//
+class LoopSearch
+{
+
+    // Keeping track of which blocks are in the loop requires two block sets since we may add blocks
+    // as we go but the BlockSet type's max ID doesn't increase to accomodate them.  Define a helper
+    // struct to make the ensuing code more readable.
+    struct LoopBlockSet
     {
-        printf("*************** In optFindNaturalLoops()\n");
-    }
-#endif // DEBUG
+    private:
+        // Keep track of blocks with bbNum <= oldBlockMaxNum in a regular BlockSet, since
+        // it can hold all of them.
+        BlockSet oldBlocksInLoop; // Blocks with bbNum <= oldBlockMaxNum
 
-    flowList* pred;
-    flowList* predTop;
-    flowList* predEntry;
+        // Keep track of blocks with bbNum > oldBlockMaxNum in a separate BlockSet, but
+        // indexing them by (blockNum - oldBlockMaxNum); since we won't generate more than
+        // one new block per old block, this must be sufficient to track any new blocks.
+        BlockSet newBlocksInLoop; // Blocks with bbNum > oldBlockMaxNum
 
-    noway_assert(fgDomsComputed);
-    assert(fgHasLoops);
+        Compiler*    comp;
+        unsigned int oldBlockMaxNum;
 
-#if COUNT_LOOPS
-    hasMethodLoops         = false;
-    loopsThisMethod        = 0;
-    loopOverflowThisMethod = false;
+    public:
+        LoopBlockSet(Compiler* comp)
+            : oldBlocksInLoop(BlockSetOps::UninitVal())
+            , newBlocksInLoop(BlockSetOps::UninitVal())
+            , comp(comp)
+            , oldBlockMaxNum(comp->fgBBNumMax)
+        {
+        }
+
+        void Reset(unsigned int seedBlockNum)
+        {
+            if (BlockSetOps::MayBeUninit(oldBlocksInLoop))
+            {
+                // Either the block sets are uninitialized (and long), so we need to initialize
+                // them (and allocate their backing storage), or they are short and empty, so
+                // assigning MakeEmpty to them is as cheap as ClearD.
+                oldBlocksInLoop = BlockSetOps::MakeEmpty(comp);
+                newBlocksInLoop = BlockSetOps::MakeEmpty(comp);
+            }
+            else
+            {
+                // We know the backing storage is already allocated, so just clear it.
+                BlockSetOps::ClearD(comp, oldBlocksInLoop);
+                BlockSetOps::ClearD(comp, newBlocksInLoop);
+            }
+            assert(seedBlockNum <= oldBlockMaxNum);
+            BlockSetOps::AddElemD(comp, oldBlocksInLoop, seedBlockNum);
+        }
+
+        bool CanRepresent(unsigned int blockNum)
+        {
+            // We can represent old blocks up to oldBlockMaxNum, and
+            // new blocks up to 2 * oldBlockMaxNum.
+            return (blockNum <= 2 * oldBlockMaxNum);
+        }
+
+        bool IsMember(unsigned int blockNum)
+        {
+            if (blockNum > oldBlockMaxNum)
+            {
+                return BlockSetOps::IsMember(comp, newBlocksInLoop, blockNum - oldBlockMaxNum);
+            }
+            return BlockSetOps::IsMember(comp, oldBlocksInLoop, blockNum);
+        }
+
+        void Insert(unsigned int blockNum)
+        {
+            if (blockNum > oldBlockMaxNum)
+            {
+                BlockSetOps::AddElemD(comp, newBlocksInLoop, blockNum - oldBlockMaxNum);
+            }
+            else
+            {
+                BlockSetOps::AddElemD(comp, oldBlocksInLoop, blockNum);
+            }
+        }
+
+        bool TestAndInsert(unsigned int blockNum)
+        {
+            if (blockNum > oldBlockMaxNum)
+            {
+                unsigned int shiftedNum = blockNum - oldBlockMaxNum;
+                if (!BlockSetOps::IsMember(comp, newBlocksInLoop, shiftedNum))
+                {
+                    BlockSetOps::AddElemD(comp, newBlocksInLoop, shiftedNum);
+                    return false;
+                }
+            }
+            else
+            {
+                if (!BlockSetOps::IsMember(comp, oldBlocksInLoop, blockNum))
+                {
+                    BlockSetOps::AddElemD(comp, oldBlocksInLoop, blockNum);
+                    return false;
+                }
+            }
+            return true;
+        }
+    };
+
+    LoopBlockSet loopBlocks; // Set of blocks identified as part of the loop
+    Compiler*    comp;
+
+    // See LoopSearch class comment header for a diagram relating these fields:
+    BasicBlock* head;   // Predecessor of unique entry edge
+    BasicBlock* first;  // Lexically first in-loop block
+    BasicBlock* top;    // Successor of back-edge from BOTTOM
+    BasicBlock* bottom; // Predecessor of back-edge to TOP, also lexically last in-loop block
+    BasicBlock* entry;  // Successor of unique entry edge
+
+    BasicBlock*   lastExit;       // Most recently discovered exit block
+    unsigned char exitCount;      // Number of discovered exit edges
+    unsigned int  oldBlockMaxNum; // Used to identify new blocks created during compaction
+    BlockSet      bottomBlocks;   // BOTTOM blocks of already-recorded loops
+#ifdef DEBUG
+    bool forgotExit = false; // Flags a rare case where lastExit gets nulled out, for assertions
 #endif
+    bool changedFlowGraph = false; // Signals that loop compaction has modified the flow graph
 
-    /* We will use the following terminology:
-     * HEAD    - the basic block that flows into the loop ENTRY block (Currently MUST be lexically before entry).
-                 Not part of the looping of the loop.
-     * FIRST   - the lexically first basic block (in bbNext order) within this loop.  (May be part of a nested loop,
-     *           but not the outer loop. ???)
-     * TOP     - the target of the backward edge from BOTTOM. In most cases FIRST and TOP are the same.
-     * BOTTOM  - the lexically last block in the loop (i.e. the block from which we jump to the top)
-     * EXIT    - the loop exit or the block right after the bottom
-     * ENTRY   - the entry in the loop (not necessarly the TOP), but there must be only one entry
-     *
-     * We (currently) require the body of a loop to be a contiguous (in bbNext order) sequence of basic blocks.
-
-            |
-            v
-          head
-            |
-            |    top/beg <--+
-            |       |       |
-            |      ...      |
-            |       |       |
-            |       v       |
-            +---> entry     |
-                    |       |
-                   ...      |
-                    |       |
-                    v       |
-             +-- exit/tail  |
-             |      |       |
-             |     ...      |
-             |      |       |
-             |      v       |
-             |    bottom ---+
-             |
-             +------+
-                    |
-                    v
+public:
+    LoopSearch(Compiler* comp)
+        : loopBlocks(comp), comp(comp), oldBlockMaxNum(comp->fgBBNumMax), bottomBlocks(BlockSetOps::MakeEmpty(comp))
+    {
+        // Make sure we've renumbered such that the bitsets can hold all the bits
+        assert(comp->fgBBNumMax <= comp->fgCurBBEpochSize);
+    }
 
-     */
+    //------------------------------------------------------------------------
+    // RecordLoop: Notify the Compiler that a loop has been found.
+    //
+    // Return Value:
+    //    true  - Loop successfully recorded.
+    //    false - Compiler has run out of loop descriptors; loop not recorded.
+    //
+    bool RecordLoop()
+    {
+        /* At this point we have a compact loop - record it in the loop table
+        * If we found only one exit, record it in the table too
+        * (otherwise an exit = nullptr in the loop table means multiple exits) */
 
-    BasicBlock*   head;
-    BasicBlock*   top;
-    BasicBlock*   bottom;
-    BasicBlock*   entry;
-    BasicBlock*   exit;
-    unsigned char exitCount;
+        BasicBlock* onlyExit = (exitCount == 1 ? lastExit : nullptr);
+        if (comp->optRecordLoop(head, first, top, entry, bottom, onlyExit, exitCount))
+        {
+            // Record the BOTTOM block for future reference before returning.
+            assert(bottom->bbNum <= oldBlockMaxNum);
+            BlockSetOps::AddElemD(comp, bottomBlocks, bottom->bbNum);
+            return true;
+        }
 
-    for (head = fgFirstBB; head->bbNext; head = head->bbNext)
+        // Unable to record this loop because the loop descriptor table overflowed.
+        return false;
+    }
+
+    //------------------------------------------------------------------------
+    // ChangedFlowGraph: Determine whether loop compaction has modified the flow graph.
+    //
+    // Return Value:
+    //    true  - The flow graph has been modified; fgUpdateChangedFlowGraph should
+    //            be called (which is the caller's responsibility).
+    //    false - The flow graph has not been modified by this LoopSearch.
+    //
+    bool ChangedFlowGraph()
     {
-        top       = head->bbNext;
-        exit      = nullptr;
-        exitCount = 0;
+        return changedFlowGraph;
+    }
 
-        //  Blocks that are rarely run have a zero bbWeight and should
-        //  never be optimized here
+    //------------------------------------------------------------------------
+    // FindLoop: Search for a loop with the given HEAD block and back-edge.
+    //
+    // Arguments:
+    //    head - Block to be the HEAD of any loop identified
+    //    top - Block to be the TOP of any loop identified
+    //    bottom - Block to be the BOTTOM of any loop identified
+    //
+    // Return Value:
+    //    true  - Found a valid loop.
+    //    false - Did not find a valid loop.
+    //
+    // Notes:
+    //    May modify flow graph to make loop compact before returning.
+    //    Will set instance fields to track loop's extent and exits if a valid
+    //    loop is found, and potentially trash them otherwise.
+    //
+    bool FindLoop(BasicBlock* head, BasicBlock* top, BasicBlock* bottom)
+    {
+        /* Is this a loop candidate? - We look for "back edges", i.e. an edge from BOTTOM
+        * to TOP (note that this is an abuse of notation since this is not necessarily a back edge
+        * as the definition says, but merely an indication that we have a loop there).
+        * Thus, we have to be very careful and after entry discovery check that it is indeed
+        * the only place we enter the loop (especially for non-reducible flow graphs).
+        */
 
-        if (top->bbWeight == BB_ZERO_WEIGHT)
+        if (top->bbNum > bottom->bbNum) // is this a backward edge? (from BOTTOM to TOP)
         {
-            continue;
+            // Edge from BOTTOM to TOP is not a backward edge
+            return false;
         }
 
-        for (pred = top->bbPreds; pred; pred = pred->flNext)
+        if (bottom->bbNum > oldBlockMaxNum)
         {
-            /* Is this a loop candidate? - We look for "back edges", i.e. an edge from BOTTOM
-             * to TOP (note that this is an abuse of notation since this is not necessarily a back edge
-             * as the definition says, but merely an indication that we have a loop there).
-             * Thus, we have to be very careful and after entry discovery check that it is indeed
-             * the only place we enter the loop (especially for non-reducible flow graphs).
-             */
+            // Not a true back-edge; bottom is a block added to reconnect fall-through during
+            // loop processing, so its block number does not reflect its position.
+            return false;
+        }
+
+        if ((bottom->bbJumpKind == BBJ_EHFINALLYRET) || (bottom->bbJumpKind == BBJ_EHFILTERRET) ||
+            (bottom->bbJumpKind == BBJ_EHCATCHRET) || (bottom->bbJumpKind == BBJ_CALLFINALLY) ||
+            (bottom->bbJumpKind == BBJ_SWITCH))
+        {
+            /* BBJ_EHFINALLYRET, BBJ_EHFILTERRET, BBJ_EHCATCHRET, and BBJ_CALLFINALLY can never form a loop.
+            * BBJ_SWITCH that has a backward jump appears only for labeled break. */
+            return false;
+        }
+
+        /* The presence of a "back edge" is an indication that a loop might be present here
+        *
+        * LOOP:
+        *        1. A collection of STRONGLY CONNECTED nodes i.e. there is a path from any
+        *           node in the loop to any other node in the loop (wholly within the loop)
+        *        2. The loop has a unique ENTRY, i.e. there is only one way to reach a node
+        *           in the loop from outside the loop, and that is through the ENTRY
+        */
+
+        /* Let's find the loop ENTRY */
+        BasicBlock* entry = FindEntry(head, top, bottom);
+
+        if (entry == nullptr)
+        {
+            // For now, we only recognize loops where HEAD has some successor ENTRY in the loop.
+            return false;
+        }
 
-            bottom    = pred->flBlock;
-            exitCount = 0;
+        // Passed the basic checks; initialize instance state for this back-edge.
+        this->head      = head;
+        this->top       = top;
+        this->entry     = entry;
+        this->bottom    = bottom;
+        this->lastExit  = nullptr;
+        this->exitCount = 0;
 
-            if (top->bbNum <= bottom->bbNum) // is this a backward edge? (from BOTTOM to TOP)
+        // Now we find the "first" block -- the earliest block reachable within the loop.
+        // With our current algorithm, this is always the same as "top".
+        this->first = top;
+
+        if (!HasSingleEntryCycle())
+        {
+            // There isn't actually a loop between TOP and BOTTOM
+            return false;
+        }
+
+        if (!loopBlocks.IsMember(top->bbNum))
+        {
+            // The "back-edge" we identified isn't actually part of the flow cycle containing ENTRY
+            return false;
+        }
+
+        // Disqualify loops where the first block of the loop is less nested in EH than
+        // the bottom block. That is, we don't want to handle loops where the back edge
+        // goes from within an EH region to a first block that is outside that same EH
+        // region. Note that we *do* handle loops where the first block is the *first*
+        // block of a more nested EH region (since it is legal to branch to the first
+        // block of an immediately more nested EH region). So, for example, disqualify
+        // this:
+        //
+        // BB02
+        // ...
+        // try {
+        // ...
+        // BB10 BBJ_COND => BB02
+        // ...
+        // }
+        //
+        // Here, BB10 is more nested than BB02.
+
+        if (bottom->hasTryIndex() && !comp->bbInTryRegions(bottom->getTryIndex(), first))
+        {
+            JITDUMP("Loop 'first' BB%02u is in an outer EH region compared to loop 'bottom' BB%02u. Rejecting "
+                    "loop.\n",
+                    first->bbNum, bottom->bbNum);
+            return false;
+        }
+
+#if FEATURE_EH_FUNCLETS && defined(_TARGET_ARM_)
+        // Disqualify loops where the first block of the loop is a finally target.
+        // The main problem is when multiple loops share a 'first' block that is a finally
+        // target and we canonicalize the loops by adding a new loop head. In that case, we
+        // need to update the blocks so the finally target bit is moved to the newly created
+        // block, and removed from the old 'first' block. This is 'hard', so at this point
+        // in the RyuJIT codebase (when we don't expect to keep the "old" ARM32 code generator
+        // long-term), it's easier to disallow the loop than to update the flow graph to
+        // support this case.
+
+        if ((first->bbFlags & BBF_FINALLY_TARGET) != 0)
+        {
+            JITDUMP("Loop 'first' BB%02u is a finally target. Rejecting loop.\n", first->bbNum);
+            return false;
+        }
+#endif // FEATURE_EH_FUNCLETS && defined(_TARGET_ARM_)
+
+        // Compact the loop (sweep through it and move out any blocks that aren't part of the
+        // flow cycle), and find the exits.
+        if (!MakeCompactAndFindExits())
+        {
+            // Unable to preserve well-formed loop during compaction.
+            return false;
+        }
+
+        // We have a valid loop.
+        return true;
+    }
+
+private:
+    //------------------------------------------------------------------------
+    // FindEntry: See if given HEAD flows to valid ENTRY between given TOP and BOTTOM
+    //
+    // Arguments:
+    //    head - Block to be the HEAD of any loop identified
+    //    top - Block to be the TOP of any loop identified
+    //    bottom - Block to be the BOTTOM of any loop identified
+    //
+    // Return Value:
+    //    Block to be the ENTRY of any loop identified, or nullptr if no
+    //    such entry meeting our criteria can be found.
+    //
+    // Notes:
+    //    Returns main entry if one is found, does not check for side-entries.
+    //
+    BasicBlock* FindEntry(BasicBlock* head, BasicBlock* top, BasicBlock* bottom)
+    {
+        if (head->bbJumpKind == BBJ_ALWAYS)
+        {
+            if (head->bbJumpDest->bbNum <= bottom->bbNum && head->bbJumpDest->bbNum >= top->bbNum)
             {
-                if ((bottom->bbJumpKind == BBJ_EHFINALLYRET) || (bottom->bbJumpKind == BBJ_EHFILTERRET) ||
-                    (bottom->bbJumpKind == BBJ_EHCATCHRET) || (bottom->bbJumpKind == BBJ_CALLFINALLY) ||
-                    (bottom->bbJumpKind == BBJ_SWITCH))
-                {
-                    /* BBJ_EHFINALLYRET, BBJ_EHFILTERRET, BBJ_EHCATCHRET, and BBJ_CALLFINALLY can never form a loop.
-                     * BBJ_SWITCH that has a backward jump appears only for labeled break. */
-                    goto NO_LOOP;
-                }
+                /* OK - we enter somewhere within the loop */
+
+                /* some useful asserts
+                * Cannot enter at the top - should have being caught by redundant jumps */
+
+                assert((head->bbJumpDest != top) || (head->bbFlags & BBF_KEEP_BBJ_ALWAYS));
+
+                return head->bbJumpDest;
+            }
+            else
+            {
+                /* special case - don't consider now */
+                // assert (!"Loop entered in weird way!");
+                return nullptr;
+            }
+        }
+        // Can we fall through into the loop?
+        else if (head->bbJumpKind == BBJ_NONE || head->bbJumpKind == BBJ_COND)
+        {
+            /* The ENTRY is at the TOP (a do-while loop) */
+            return top;
+        }
+        else
+        {
+            return nullptr; // head does not flow into the loop bail for now
+        }
+    }
+
+    //------------------------------------------------------------------------
+    // HasSingleEntryCycle: Perform a reverse flow walk from ENTRY, visiting
+    //    only blocks between TOP and BOTTOM, to determine if such a cycle
+    //    exists and if it has a single entry.
+    //
+    // Return Value:
+    //    true  - Found a single-entry cycle.
+    //    false - Did not find a single-entry cycle.
+    //
+    // Notes:
+    //    Will mark (in `loopBlocks`) all blocks found to participate in the
+    //    cycle.
+    //
+    bool HasSingleEntryCycle()
+    {
+        // Now do a backwards flow walk from entry to see if we have a single-entry loop
+        bool foundCycle = false;
 
-                BasicBlock* loopBlock;
+        // Seed the loop block set and worklist with the entry block.
+        loopBlocks.Reset(entry->bbNum);
+        jitstd::list<BasicBlock*> worklist(comp->getAllocator());
+        worklist.push_back(entry);
 
-                /* The presence of a "back edge" is an indication that a loop might be present here
-                 *
-                 * LOOP:
-                 *        1. A collection of STRONGLY CONNECTED nodes i.e. there is a path from any
-                 *           node in the loop to any other node in the loop (wholly within the loop)
-                 *        2. The loop has a unique ENTRY, i.e. there is only one way to reach a node
-                 *           in the loop from outside the loop, and that is through the ENTRY
-                 */
+        while (!worklist.empty())
+        {
+            BasicBlock* block = worklist.back();
+            worklist.pop_back();
+
+            /* Make sure ENTRY dominates all blocks in the loop
+            * This is necessary to ensure condition 2. above
+            */
+            if (block->bbNum > oldBlockMaxNum)
+            {
+                // This is a new block we added to connect fall-through, so the
+                // recorded dominator information doesn't cover it.  Just continue,
+                // and when we process its unique predecessor we'll abort if ENTRY
+                // doesn't dominate that.
+            }
+            else if (!comp->fgDominate(entry, block))
+            {
+                return false;
+            }
+
+            // Add preds to the worklist, checking for side-entries.
+            for (flowList* predIter = block->bbPreds; predIter != nullptr; predIter = predIter->flNext)
+            {
+                BasicBlock* pred = predIter->flBlock;
 
-                /* Let's find the loop ENTRY */
+                unsigned int testNum = PositionNum(pred);
 
-                if (head->bbJumpKind == BBJ_ALWAYS)
+                if ((testNum < top->bbNum) || (testNum > bottom->bbNum))
                 {
-                    if (head->bbJumpDest->bbNum <= bottom->bbNum && head->bbJumpDest->bbNum >= top->bbNum)
+                    // Pred is out of loop range
+                    if (block == entry)
                     {
-                        /* OK - we enter somewhere within the loop */
-                        entry = head->bbJumpDest;
+                        if (pred == head)
+                        {
+                            // This is the single entry we expect.
+                            continue;
+                        }
+                        // ENTRY has some pred other than head outside the loop.  If ENTRY does not
+                        // dominate this pred, we'll consider this a side-entry and skip this loop;
+                        // otherwise the loop is still valid and this may be a (flow-wise) back-edge
+                        // of an outer loop.  For the dominance test, if `pred` is a new block, use
+                        // its unique predecessor since the dominator tree has info for that.
+                        BasicBlock* effectivePred = (pred->bbNum > oldBlockMaxNum ? pred->bbPrev : pred);
+                        if (comp->fgDominate(entry, effectivePred))
+                        {
+                            // Outer loop back-edge
+                            continue;
+                        }
+                    }
 
-                        /* some useful asserts
-                         * Cannot enter at the top - should have being caught by redundant jumps */
+                    // There are multiple entries to this loop, don't consider it.
+                    return false;
+                }
 
-                        assert((entry != top) || (head->bbFlags & BBF_KEEP_BBJ_ALWAYS));
-                    }
-                    else
+                if (pred == entry)
+                {
+                    // We have indeed found a cycle in the flow graph.
+                    foundCycle = true;
+                    assert(loopBlocks.IsMember(pred->bbNum));
+                }
+                else if (!loopBlocks.TestAndInsert(pred->bbNum))
+                {
+                    // Add this pred to the worklist
+                    worklist.push_back(pred);
+
+                    if ((pred->bbNext != nullptr) && (PositionNum(pred->bbNext) == pred->bbNum))
                     {
-                        /* special case - don't consider now */
-                        // assert (!"Loop entered in weird way!");
-                        goto NO_LOOP;
+                        // We've created a new block immediately after `pred` to
+                        // reconnect what was fall-through.  Mark it as in-loop also;
+                        // it needs to stay with `prev` and if it exits the loop we'd
+                        // just need to re-create it if we tried to move it out.
+                        loopBlocks.Insert(pred->bbNext->bbNum);
                     }
                 }
-                // Can we fall through into the loop?
-                else if (head->bbJumpKind == BBJ_NONE || head->bbJumpKind == BBJ_COND)
+            }
+        }
+
+        return foundCycle;
+    }
+
+    //------------------------------------------------------------------------
+    // PositionNum: Get the number identifying a block's position per the
+    //    lexical ordering that existed before searching for (and compacting)
+    //    loops.
+    //
+    // Arguments:
+    //    block - Block whose position is desired.
+    //
+    // Return Value:
+    //    A number indicating that block's position relative to others.
+    //
+    // Notes:
+    //    When the given block is a new one created during loop compaction,
+    //    the number of its unique predecessor is returned.
+    //
+    unsigned int PositionNum(BasicBlock* block)
+    {
+        if (block->bbNum > oldBlockMaxNum)
+        {
+            // This must be a block we inserted to connect fall-through after moving blocks.
+            // To determine if it's in the loop or not, use the number of its unique predecessor
+            // block.
+            assert(block->bbPreds->flBlock == block->bbPrev);
+            assert(block->bbPreds->flNext == nullptr);
+            return block->bbPrev->bbNum;
+        }
+        return block->bbNum;
+    }
+
+    //------------------------------------------------------------------------
+    // MakeCompactAndFindExits: Compact the loop (sweep through it and move out
+    //   any blocks that aren't part of the flow cycle), and find the exits (set
+    //   lastExit and exitCount).
+    //
+    // Return Value:
+    //    true  - Loop successfully compacted (or `loopBlocks` expanded to
+    //            include all blocks in the lexical range), exits enumerated.
+    //    false - Loop cannot be made compact and remain well-formed.
+    //
+    bool MakeCompactAndFindExits()
+    {
+        // Compaction (if it needs to happen) will require an insertion point.
+        BasicBlock* moveAfter = nullptr;
+
+        for (BasicBlock* previous = top->bbPrev; previous != bottom;)
+        {
+            BasicBlock* block = previous->bbNext;
+
+            if (loopBlocks.IsMember(block->bbNum))
+            {
+                // This block is a member of the loop.  Check to see if it may exit the loop.
+                CheckForExit(block);
+
+                // Done processing this block; move on to the next.
+                previous = block;
+                continue;
+            }
+
+            // This blocks is lexically between TOP and BOTTOM, but it does not
+            // participate in the flow cycle.  Check for a run of consecutive
+            // such blocks.
+            BasicBlock* lastNonLoopBlock = block;
+            BasicBlock* nextLoopBlock    = block->bbNext;
+            while (!loopBlocks.IsMember(nextLoopBlock->bbNum))
+            {
+                lastNonLoopBlock = nextLoopBlock;
+                nextLoopBlock    = nextLoopBlock->bbNext;
+                // This loop must terminate because we know BOTTOM is in loopBlocks.
+            }
+
+            // Choose an insertion point for non-loop blocks if we haven't yet done so.
+            if (moveAfter == nullptr)
+            {
+                moveAfter = FindInsertionPoint();
+            }
+
+            if (!BasicBlock::sameEHRegion(previous, nextLoopBlock) || !BasicBlock::sameEHRegion(previous, moveAfter))
+            {
+                // EH regions would be ill-formed if we moved these blocks out.
+                // See if we can consider them loop blocks without introducing
+                // a side-entry.
+                if (CanTreatAsLoopBlocks(block, lastNonLoopBlock))
                 {
-                    /* The ENTRY is at the TOP (a do-while loop) */
-                    entry = top;
+                    // The call to `canTreatAsLoop` marked these blocks as part of the loop;
+                    // iterate without updating `previous` so that we'll analyze them as part
+                    // of the loop.
+                    continue;
                 }
                 else
                 {
-                    goto NO_LOOP; // head does not flow into the loop bail for now
+                    // We can't move these out of the loop or leave them in, so just give
+                    // up on this loop.
+                    return false;
                 }
+            }
 
-                // Now we find the "first" block -- the earliest block reachable within the loop.
-                // With our current algorithm, this is always the same as "top".
-                BasicBlock* first = top;
+            // Now physically move the blocks.
+            BasicBlock* moveBefore = moveAfter->bbNext;
 
-                /* Make sure ENTRY dominates all blocks in the loop
-                 * This is necessary to ensure condition 2. above
-                 * At the same time check if the loop has a single exit
-                 * point - those loops are easier to optimize */
+            comp->fgUnlinkRange(block, lastNonLoopBlock);
+            comp->fgMoveBlocksAfter(block, lastNonLoopBlock, moveAfter);
+            comp->ehUpdateLastBlocks(moveAfter, lastNonLoopBlock);
 
-                for (loopBlock = top; loopBlock != bottom->bbNext; loopBlock = loopBlock->bbNext)
-                {
-                    if (!fgDominate(entry, loopBlock))
-                    {
-                        goto NO_LOOP;
-                    }
+            // Apply any adjustments needed for fallthrough at the boundaries of the moved region.
+            FixupFallThrough(moveAfter, moveBefore, block);
+            FixupFallThrough(lastNonLoopBlock, nextLoopBlock, moveBefore);
+            // Also apply any adjustments needed where the blocks were snipped out of the loop.
+            BasicBlock* newBlock = FixupFallThrough(previous, block, nextLoopBlock);
+            if (newBlock != nullptr)
+            {
+                // This new block is in the loop and is a loop exit.
+                loopBlocks.Insert(newBlock->bbNum);
+                lastExit = newBlock;
+                ++exitCount;
+            }
 
-                    if (loopBlock == bottom)
-                    {
-                        if (bottom->bbJumpKind != BBJ_ALWAYS)
-                        {
-                            /* there is an exit at the bottom */
+            // Update moveAfter for the next insertion.
+            moveAfter = lastNonLoopBlock;
 
-                            noway_assert(bottom->bbJumpDest == top);
-                            exit = bottom;
-                            exitCount++;
-                            continue;
-                        }
-                    }
+            // Note that we've changed the flow graph, and continue without updating
+            // `previous` so that we'll process nextLoopBlock.
+            changedFlowGraph = true;
+        }
 
-                    BasicBlock* exitPoint;
+        if ((exitCount == 1) && (lastExit == nullptr))
+        {
+            // If we happen to have a loop with two exits, one of which goes to an
+            // infinite loop that's lexically nested inside it, where the inner loop
+            // can't be moved out,  we can end up in this situation (because
+            // CanTreatAsLoopBlocks will have decremented the count expecting to find
+            // another exit later).  Bump the exit count to 2, since downstream code
+            // will not be prepared for null lastExit with exitCount of 1.
+            assert(forgotExit);
+            exitCount = 2;
+        }
 
-                    switch (loopBlock->bbJumpKind)
-                    {
-                        case BBJ_COND:
-                        case BBJ_CALLFINALLY:
-                        case BBJ_ALWAYS:
-                        case BBJ_EHCATCHRET:
-                            assert(loopBlock->bbJumpDest);
-                            exitPoint = loopBlock->bbJumpDest;
-
-                            if (exitPoint->bbNum < top->bbNum || exitPoint->bbNum > bottom->bbNum)
-                            {
-                                /* exit from a block other than BOTTOM */
-                                exit = loopBlock;
-                                exitCount++;
-                            }
-                            break;
+        // Loop compaction was successful
+        return true;
+    }
 
-                        case BBJ_NONE:
-                            break;
+    //------------------------------------------------------------------------
+    // FindInsertionPoint: Find an appropriate spot to which blocks that are
+    //    lexically between TOP and BOTTOM but not part of the flow cycle
+    //    can be moved.
+    //
+    // Return Value:
+    //    Block after which to insert moved blocks.
+    //
+    BasicBlock* FindInsertionPoint()
+    {
+        // Find an insertion point for blocks we're going to move.  Move them down
+        // out of the loop, and if possible find a spot that won't break up fall-through.
+        BasicBlock* moveAfter = bottom;
+        while (moveAfter->bbFallsThrough())
+        {
+            // Keep looking for a better insertion point if we can.
+            BasicBlock* newMoveAfter = TryAdvanceInsertionPoint(moveAfter);
 
-                        case BBJ_EHFINALLYRET:
-                        case BBJ_EHFILTERRET:
-                            /* The "try" associated with this "finally" must be in the
-                             * same loop, so the finally block will return control inside the loop */
-                            break;
+            if (newMoveAfter == nullptr)
+            {
+                // Ran out of candidate insertion points, so just split up the fall-through.
+                return moveAfter;
+            }
 
-                        case BBJ_THROW:
-                        case BBJ_RETURN:
-                            /* those are exits from the loop */
-                            exit = loopBlock;
-                            exitCount++;
-                            break;
+            moveAfter = newMoveAfter;
+        }
 
-                        case BBJ_SWITCH:
+        return moveAfter;
+    }
 
-                            unsigned jumpCnt;
-                            jumpCnt = loopBlock->bbJumpSwt->bbsCount;
-                            BasicBlock** jumpTab;
-                            jumpTab = loopBlock->bbJumpSwt->bbsDstTab;
+    //------------------------------------------------------------------------
+    // TryAdvanceInsertionPoint: Find the next legal insertion point after
+    //    the given one, if one exists.
+    //
+    // Arguments:
+    //    oldMoveAfter - Prior insertion point; find the next after this.
+    //
+    // Return Value:
+    //    The next block after `oldMoveAfter` that is a legal insertion point
+    //    (i.e. blocks being swept out of the loop can be moved immediately
+    //    after it), if one exists, else nullptr.
+    //
+    BasicBlock* TryAdvanceInsertionPoint(BasicBlock* oldMoveAfter)
+    {
+        BasicBlock* newMoveAfter = oldMoveAfter->bbNext;
 
-                            do
-                            {
-                                noway_assert(*jumpTab);
-                                exitPoint = *jumpTab;
+        if (!BasicBlock::sameEHRegion(oldMoveAfter, newMoveAfter))
+        {
+            // Don't cross an EH region boundary.
+            return nullptr;
+        }
 
-                                if (exitPoint->bbNum < top->bbNum || exitPoint->bbNum > bottom->bbNum)
-                                {
-                                    exit = loopBlock;
-                                    exitCount++;
-                                }
-                            } while (++jumpTab, --jumpCnt);
-                            break;
+        if ((newMoveAfter->bbJumpKind == BBJ_ALWAYS) || (newMoveAfter->bbJumpKind == BBJ_COND))
+        {
+            unsigned int destNum = newMoveAfter->bbJumpDest->bbNum;
+            if ((destNum >= top->bbNum) && (destNum <= bottom->bbNum) && !loopBlocks.IsMember(destNum))
+            {
+                // Reversing this branch out of block `newMoveAfter` could confuse this algorithm
+                // (in particular, the edge would still be numerically backwards but no longer be
+                // lexically backwards, so a lexical forward walk from TOP would not find BOTTOM),
+                // so don't do that.
+                // We're checking for BBJ_ALWAYS and BBJ_COND only here -- we don't need to
+                // check for BBJ_SWITCH because we'd never consider it a loop back-edge.
+                return nullptr;
+            }
+        }
 
-                        default:
-                            noway_assert(!"Unexpected bbJumpKind");
-                            break;
-                    }
-                }
+        // Similarly check to see if advancing to `newMoveAfter` would reverse the lexical order
+        // of an edge from the run of blocks being moved to `newMoveAfter` -- doing so would
+        // introduce a new lexical back-edge, which could (maybe?) confuse the loop search
+        // algorithm, and isn't desirable layout anyway.
+        for (flowList* predIter = newMoveAfter->bbPreds; predIter != nullptr; predIter = predIter->flNext)
+        {
+            unsigned int predNum = predIter->flBlock->bbNum;
 
-                /* Make sure we can iterate the loop (i.e. there is a way back to ENTRY)
-                 * This is to ensure condition 1. above which prevents marking fake loops
-                 *
-                 * Below is an example:
-                 *          for (....)
-                 *          {
-                 *            ...
-                 *              computations
-                 *            ...
-                 *            break;
-                 *          }
-                 * The example above is not a loop since we bail after the first iteration
-                 *
-                 * The condition we have to check for is
-                 *  1. ENTRY must have at least one predecessor inside the loop. Since we know that that block is
-                 *     reachable, it can only be reached through ENTRY, therefore we have a way back to ENTRY
-                 *
-                 *  2. If we have a GOTO (BBJ_ALWAYS) outside of the loop and that block dominates the
-                 *     loop bottom then we cannot iterate
-                 *
-                 * NOTE that this doesn't entirely satisfy condition 1. since "break" statements are not
-                 * part of the loop nodes (as per definition they are loop exits executed only once),
-                 * but we have no choice but to include them because we consider all blocks within TOP-BOTTOM */
-
-                for (loopBlock = top; loopBlock != bottom; loopBlock = loopBlock->bbNext)
-                {
-                    switch (loopBlock->bbJumpKind)
-                    {
-                        case BBJ_ALWAYS:
-                        case BBJ_THROW:
-                        case BBJ_RETURN:
-                            if (fgDominate(loopBlock, bottom))
-                            {
-                                goto NO_LOOP;
-                            }
-                        default:
-                            break;
-                    }
-                }
+            if ((predNum >= top->bbNum) && (predNum <= bottom->bbNum) && !loopBlocks.IsMember(predNum))
+            {
+                // Don't make this forward edge a backwards edge.
+                return nullptr;
+            }
+        }
 
-                bool canIterateLoop = false;
+        if (IsRecordedBottom(newMoveAfter))
+        {
+            // This is the BOTTOM of another loop; don't move any blocks past it, to avoid moving them
+            // out of that loop (we should have already done so when processing that loop if it were legal).
+            return nullptr;
+        }
 
-                for (predEntry = entry->bbPreds; predEntry; predEntry = predEntry->flNext)
-                {
-                    if (predEntry->flBlock->bbNum >= top->bbNum && predEntry->flBlock->bbNum <= bottom->bbNum)
-                    {
-                        canIterateLoop = true;
-                        break;
-                    }
-                    else if (predEntry->flBlock != head)
-                    {
-                        // The entry block has multiple predecessors outside the loop; the 'head'
-                        // block isn't the only one. We only support a single 'head', so bail.
-                        goto NO_LOOP;
-                    }
-                }
+        // Advancing the insertion point is ok, except that we can't split up any CallFinally/BBJ_ALWAYS
+        // pair, so if we've got such a pair recurse to see if we can move past the whole thing.
+        return (newMoveAfter->isBBCallAlwaysPair() ? TryAdvanceInsertionPoint(newMoveAfter) : newMoveAfter);
+    }
 
-                if (!canIterateLoop)
-                {
-                    goto NO_LOOP;
-                }
+    //------------------------------------------------------------------------
+    // isOuterBottom: Determine if the given block is the BOTTOM of a previously
+    //    recorded loop.
+    //
+    // Arguments:
+    //    block - Block to check for BOTTOM-ness.
+    //
+    // Return Value:
+    //    true - The blocks was recorded as `bottom` of some earlier-processed loop.
+    //    false - No loops yet recorded have this block as their `bottom`.
+    //
+    bool IsRecordedBottom(BasicBlock* block)
+    {
+        if (block->bbNum > oldBlockMaxNum)
+        {
+            // This is a new block, which can't be an outer bottom block because we only allow old blocks
+            // as BOTTOM.
+            return false;
+        }
+        return BlockSetOps::IsMember(comp, bottomBlocks, block->bbNum);
+    }
 
-                /* Double check - make sure that all loop blocks except ENTRY
-                 * have no predecessors outside the loop - this ensures only one loop entry and prevents
-                 * us from considering non-loops due to incorrectly assuming that we had a back edge
-                 *
-                 * OBSERVATION:
-                 *    Loops of the form "while (a || b)" will be treated as 2 nested loops (with the same header)
-                 */
+    //------------------------------------------------------------------------
+    // CanTreatAsLoopBlocks: If the given range of blocks can be treated as
+    //    loop blocks, add them to loopBlockSet and return true.  Otherwise,
+    //    return false.
+    //
+    // Arguments:
+    //    firstNonLoopBlock - First block in the run to be subsumed.
+    //    lastNonLoopBlock - Last block in the run to be subsumed.
+    //
+    // Return Value:
+    //    true - The blocks from `fistNonLoopBlock` to `lastNonLoopBlock` were
+    //           successfully added to `loopBlocks`.
+    //    false - Treating the blocks from `fistNonLoopBlock` to `lastNonLoopBlock`
+    //            would not be legal (it would induce a side-entry).
+    //
+    // Notes:
+    //    `loopBlocks` may be modified even if `false` is returned.
+    //    `exitCount` and `lastExit` may be modified if this process identifies
+    //    in-loop edges that were previously counted as exits.
+    //
+    bool CanTreatAsLoopBlocks(BasicBlock* firstNonLoopBlock, BasicBlock* lastNonLoopBlock)
+    {
+        BasicBlock* nextLoopBlock = lastNonLoopBlock->bbNext;
+        for (BasicBlock* testBlock = firstNonLoopBlock; testBlock != nextLoopBlock; testBlock = testBlock->bbNext)
+        {
+            for (flowList* predIter = testBlock->bbPreds; predIter != nullptr; predIter = predIter->flNext)
+            {
+                BasicBlock*  testPred           = predIter->flBlock;
+                unsigned int predPosNum         = PositionNum(testPred);
+                unsigned int firstNonLoopPosNum = PositionNum(firstNonLoopBlock);
+                unsigned int lastNonLoopPosNum  = PositionNum(lastNonLoopBlock);
 
-                for (loopBlock = top; loopBlock != bottom->bbNext; loopBlock = loopBlock->bbNext)
+                if (loopBlocks.IsMember(predPosNum) ||
+                    ((predPosNum >= firstNonLoopPosNum) && (predPosNum <= lastNonLoopPosNum)))
                 {
-                    if (loopBlock == entry)
-                    {
-                        continue;
-                    }
+                    // This pred is in the loop (or what will be the loop if we determine this
+                    // run of exit blocks doesn't include a side-entry).
 
-                    for (predTop = loopBlock->bbPreds; predTop != nullptr; predTop = predTop->flNext)
+                    if (predPosNum < firstNonLoopPosNum)
                     {
-                        if (predTop->flBlock->bbNum < top->bbNum || predTop->flBlock->bbNum > bottom->bbNum)
+                        // We've already counted this block as an exit, so decrement the count.
+                        --exitCount;
+                        if (lastExit == testPred)
                         {
-                            // noway_assert(!"Found loop with multiple entries");
-                            goto NO_LOOP;
+                            // Erase this now-bogus `lastExit` entry.
+                            lastExit = nullptr;
+                            INDEBUG(forgotExit = true);
                         }
                     }
                 }
+                else
+                {
+                    // This pred is not in the loop, so this constitutes a side-entry.
+                    return false;
+                }
+            }
 
-                // Disqualify loops where the first block of the loop is less nested in EH than
-                // the bottom block. That is, we don't want to handle loops where the back edge
-                // goes from within an EH region to a first block that is outside that same EH
-                // region. Note that we *do* handle loops where the first block is the *first*
-                // block of a more nested EH region (since it is legal to branch to the first
-                // block of an immediately more nested EH region). So, for example, disqualify
-                // this:
-                //
-                // BB02
-                // ...
-                // try {
-                // ...
-                // BB10 BBJ_COND => BB02
-                // ...
-                // }
-                //
-                // Here, BB10 is more nested than BB02.
+            // Either we're going to abort the loop on a subsequent testBlock, or this
+            // testBlock is part of the loop.
+            loopBlocks.Insert(testBlock->bbNum);
+        }
+
+        // All blocks were ok to leave in the loop.
+        return true;
+    }
+
+    //------------------------------------------------------------------------
+    // FixupFallThrough: Re-establish any broken control flow connectivity
+    //    and eliminate any "goto-next"s that were created by changing the
+    //    given block's lexical follower.
+    //
+    // Arguments:
+    //    block - Block whose `bbNext` has changed.
+    //    oldNext - Previous value of `block->bbNext`.
+    //    newNext - New value of `block->bbNext`.
+    //
+    // Return Value:
+    //    If a new block is created to reconnect flow, the new block is
+    //    returned; otherwise, nullptr.
+    //
+    BasicBlock* FixupFallThrough(BasicBlock* block, BasicBlock* oldNext, BasicBlock* newNext)
+    {
+        // If we create a new block, that will be our return value.
+        BasicBlock* newBlock = nullptr;
+
+        if (block->bbFallsThrough())
+        {
+            // Need to reconnect the flow from `block` to `oldNext`.
 
-                if (bottom->hasTryIndex() && !bbInTryRegions(bottom->getTryIndex(), first))
+            if ((block->bbJumpKind == BBJ_COND) && (block->bbJumpDest == newNext))
+            {
+                /* Reverse the jump condition */
+                GenTree* test = block->lastNode();
+                noway_assert(test->OperIsConditionalJump());
+
+                if (test->OperGet() == GT_JTRUE)
                 {
-                    JITDUMP("Loop 'first' BB%02u is in an outer EH region compared to loop 'bottom' BB%02u. Rejecting "
-                            "loop.\n",
-                            first->bbNum, bottom->bbNum);
-                    goto NO_LOOP;
+                    GenTree* cond = comp->gtReverseCond(test->gtOp.gtOp1);
+                    assert(cond == test->gtOp.gtOp1); // Ensure `gtReverseCond` did not create a new node.
+                    test->gtOp.gtOp1 = cond;
                 }
-
-#if FEATURE_EH_FUNCLETS && defined(_TARGET_ARM_)
-                // Disqualify loops where the first block of the loop is a finally target.
-                // The main problem is when multiple loops share a 'first' block that is a finally
-                // target and we canonicalize the loops by adding a new loop head. In that case, we
-                // need to update the blocks so the finally target bit is moved to the newly created
-                // block, and removed from the old 'first' block. This is 'hard', so at this point
-                // in the RyuJIT codebase (when we don't expect to keep the "old" ARM32 code generator
-                // long-term), it's easier to disallow the loop than to update the flow graph to
-                // support this case.
-
-                if ((first->bbFlags & BBF_FINALLY_TARGET) != 0)
+                else
                 {
-                    JITDUMP("Loop 'first' BB%02u is a finally target. Rejecting loop.\n", first->bbNum);
-                    goto NO_LOOP;
+                    comp->gtReverseCond(test);
                 }
-#endif // FEATURE_EH_FUNCLETS && defined(_TARGET_ARM_)
 
-                /* At this point we have a loop - record it in the loop table
-                 * If we found only one exit, record it in the table too
-                 * (otherwise an exit = 0 in the loop table means multiple exits) */
+                // Redirect the Conditional JUMP to go to `oldNext`
+                block->bbJumpDest = oldNext;
+            }
+            else
+            {
+                // Insert an unconditional jump to `oldNext` just after `block`.
+                newBlock = comp->fgConnectFallThrough(block, oldNext);
+                noway_assert((newBlock == nullptr) || loopBlocks.CanRepresent(newBlock->bbNum));
+            }
+        }
+        else if ((block->bbJumpKind == BBJ_ALWAYS) && (block->bbJumpDest == newNext))
+        {
+            // We've made `block`'s jump target its bbNext, so remove the jump.
+            if (!comp->fgOptimizeBranchToNext(block, newNext, block->bbPrev))
+            {
+                // If optimizing away the goto-next failed for some reason, mark it KEEP_BBJ_ALWAYS to
+                // prevent assertions from complaining about it.
+                block->bbFlags |= BBF_KEEP_BBJ_ALWAYS;
+            }
+        }
+
+        // Make sure we don't leave around a goto-next unless it's marked KEEP_BBJ_ALWAYS.
+        assert((block->bbJumpKind != BBJ_COND) || (block->bbJumpKind != BBJ_ALWAYS) || (block->bbJumpDest != newNext) ||
+               ((block->bbFlags & BBF_KEEP_BBJ_ALWAYS) != 0));
+        return newBlock;
+    }
+
+    //------------------------------------------------------------------------
+    // CheckForExit: Check if the given block has any successor edges that are
+    //    loop exits, and update `lastExit` and `exitCount` if so.
+    //
+    // Arguments:
+    //    block - Block whose successor edges are to be checked.
+    //
+    // Notes:
+    //    If one block has multiple exiting successor edges, those are counted
+    //    as multiple exits in `exitCount`.
+    //
+    void CheckForExit(BasicBlock* block)
+    {
+        BasicBlock* exitPoint;
 
-                assert(pred);
-                if (exitCount != 1)
+        switch (block->bbJumpKind)
+        {
+            case BBJ_COND:
+            case BBJ_CALLFINALLY:
+            case BBJ_ALWAYS:
+            case BBJ_EHCATCHRET:
+                assert(block->bbJumpDest);
+                exitPoint = block->bbJumpDest;
+
+                if (!loopBlocks.IsMember(exitPoint->bbNum))
                 {
-                    exit = nullptr;
+                    /* exit from a block other than BOTTOM */
+                    lastExit = block;
+                    exitCount++;
                 }
-                optRecordLoop(head, first, top, entry, bottom, exit, exitCount);
+                break;
+
+            case BBJ_NONE:
+                break;
+
+            case BBJ_EHFINALLYRET:
+            case BBJ_EHFILTERRET:
+                /* The "try" associated with this "finally" must be in the
+                * same loop, so the finally block will return control inside the loop */
+                break;
+
+            case BBJ_THROW:
+            case BBJ_RETURN:
+                /* those are exits from the loop */
+                lastExit = block;
+                exitCount++;
+                break;
+
+            case BBJ_SWITCH:
+
+                unsigned jumpCnt;
+                jumpCnt = block->bbJumpSwt->bbsCount;
+                BasicBlock** jumpTab;
+                jumpTab = block->bbJumpSwt->bbsDstTab;
+
+                do
+                {
+                    noway_assert(*jumpTab);
+                    exitPoint = *jumpTab;
+
+                    if (!loopBlocks.IsMember(exitPoint->bbNum))
+                    {
+                        lastExit = block;
+                        exitCount++;
+                    }
+                } while (++jumpTab, --jumpCnt);
+                break;
+
+            default:
+                noway_assert(!"Unexpected bbJumpKind");
+                break;
+        }
+
+        if (block->bbFallsThrough() && !loopBlocks.IsMember(block->bbNext->bbNum))
+        {
+            // Found a fall-through exit.
+            lastExit = block;
+            exitCount++;
+        }
+    }
+};
+}
+
+/*****************************************************************************
+ * Find the natural loops, using dominators. Note that the test for
+ * a loop is slightly different from the standard one, because we have
+ * not done a depth first reordering of the basic blocks.
+ */
+
+void Compiler::optFindNaturalLoops()
+{
+#ifdef DEBUG
+    if (verbose)
+    {
+        printf("*************** In optFindNaturalLoops()\n");
+    }
+#endif // DEBUG
+
+    noway_assert(fgDomsComputed);
+    assert(fgHasLoops);
+
+#if COUNT_LOOPS
+    hasMethodLoops         = false;
+    loopsThisMethod        = 0;
+    loopOverflowThisMethod = false;
+#endif
+
+    LoopSearch search(this);
+
+    for (BasicBlock* head = fgFirstBB; head->bbNext; head = head->bbNext)
+    {
+        BasicBlock* top = head->bbNext;
+
+        //  Blocks that are rarely run have a zero bbWeight and should
+        //  never be optimized here
+
+        if (top->bbWeight == BB_ZERO_WEIGHT)
+        {
+            continue;
+        }
+
+        for (flowList* pred = top->bbPreds; pred; pred = pred->flNext)
+        {
+            if (search.FindLoop(head, top, pred->flBlock))
+            {
+                // Found a loop; record it and see if we've hit the limit.
+                bool recordedLoop = search.RecordLoop();
+
+                (void)recordedLoop; // avoid unusued variable warnings in COUNT_LOOPS and !DEBUG
 
 #if COUNT_LOOPS
                 if (!hasMethodLoops)
@@ -1815,13 +2466,26 @@ void Compiler::optFindNaturalLoops()
 
                 /* keep track of the number of exits */
                 loopExitCountTable.record(static_cast<unsigned>(exitCount));
+#else  // COUNT_LOOPS
+                assert(recordedLoop);
+                if (optLoopCount == MAX_LOOP_NUM)
+                {
+                    // We won't be able to record any more loops, so stop looking.
+                    goto NO_MORE_LOOPS;
+                }
 #endif // COUNT_LOOPS
-            }
 
-        /* current predecessor not good for a loop - continue with another one, if any */
-        NO_LOOP:;
+                // Continue searching preds of `top` to see if any other are
+                // back-edges (this can happen for nested loops).  The iteration
+                // is safe because the compaction we do only modifies predecessor
+                // lists of blocks that gain or lose fall-through from their
+                // `bbPrev`, but since the motion is from within the loop to below
+                // it, we know we're not altering the relationship between `top`
+                // and its `bbPrev`.
+            }
         }
     }
+NO_MORE_LOOPS:
 
 #if COUNT_LOOPS
     loopCountTable.record(loopsThisMethod);
@@ -1870,9 +2534,10 @@ void Compiler::optFindNaturalLoops()
         }
     }
 
+    bool mod = search.ChangedFlowGraph();
+
     // Make sure that loops are canonical: that every loop has a unique "top", by creating an empty "nop"
     // one, if necessary, for loops containing others that share a "top."
-    bool mod = false;
     for (unsigned char loopInd = 0; loopInd < optLoopCount; loopInd++)
     {
         // Traverse the outermost loops as entries into the loop nest; so skip non-outermost.
index 78f9eaf..e9f09ff 100644 (file)
@@ -30,11 +30,7 @@ if(FEATURE_GDBJIT)
     set(VM_SOURCES_GDBJIT
         gdbjit.cpp
     )
-    add_definitions(-DFEATURE_GDBJIT)
 endif(FEATURE_GDBJIT)
-if(FEATURE_GDBJIT_FRAME)
-    add_definitions(-DFEATURE_GDBJIT_FRAME)
-endif(FEATURE_GDBJIT_FRAME)
 
 if(FEATURE_JIT_PITCHING)
    add_definitions(-DFEATURE_JIT_PITCHING)
index d7c700e..44a9a13 100644 (file)
@@ -381,6 +381,10 @@ HRESULT EEConfig::Init()
     fTieredCompilation = false;
 #endif
     
+#if defined(FEATURE_GDBJIT) && defined(_DEBUG)
+    pszGDBJitElfDump = NULL;
+#endif // FEATURE_GDBJIT && _DEBUG
+
     // After initialization, register the code:#GetConfigValueCallback method with code:CLRConfig to let
     // CLRConfig access config files. This is needed because CLRConfig lives outside the VM and can't
     // statically link to EEConfig.
@@ -1236,6 +1240,14 @@ HRESULT EEConfig::sync()
     fTieredCompilation = CLRConfig::GetConfigValue(CLRConfig::UNSUPPORTED_TieredCompilation) != 0;
 #endif
 
+#if defined(FEATURE_GDBJIT) && defined(_DEBUG)
+    {
+        LPWSTR pszGDBJitElfDumpW = NULL;
+        CLRConfig::GetConfigValue(CLRConfig::INTERNAL_GDBJitElfDump, &pszGDBJitElfDumpW);
+        pszGDBJitElfDump = NarrowWideChar(pszGDBJitElfDumpW);
+    }
+#endif // FEATURE_GDBJIT && _DEBUG
+
     return hr;
 }
 
index 872765b..e36ec00 100644 (file)
@@ -286,6 +286,18 @@ public:
     bool          TieredCompilation(void)           const {LIMITED_METHOD_CONTRACT;  return fTieredCompilation; }
 #endif
 
+#if defined(FEATURE_GDBJIT) && defined(_DEBUG)
+    inline bool ShouldDumpElfOnMethod(LPCUTF8 methodName) const
+    {
+        CONTRACTL {
+            NOTHROW;
+            GC_NOTRIGGER;
+            PRECONDITION(CheckPointer(methodName, NULL_OK));
+        } CONTRACTL_END
+        return RegexOrExactMatch(pszGDBJitElfDump, methodName);
+    }
+#endif // FEATURE_GDBJIT && _DEBUG
+
     BOOL PInvokeRestoreEsp(BOOL fDefault) const
     {
         LIMITED_METHOD_CONTRACT;
@@ -1098,6 +1110,10 @@ private: //----------------------------------------------------------------
     bool fTieredCompilation;
 #endif
 
+#if defined(FEATURE_GDBJIT) && defined(_DEBUG)
+    LPCUTF8 pszGDBJitElfDump;
+#endif // FEATURE_GDBJIT && _DEBUG
+
 public:
 
     HRESULT GetConfiguration_DontUse_(__in_z LPCWSTR pKey, ConfigSearch direction, __deref_out_opt LPCWSTR* value);
index 1d0bcc8..f5f95ef 100644 (file)
 
 __declspec(thread) bool tls_isSymReaderInProgress = false;
 
+#ifdef _DEBUG
+static void DumpElf(const char* methodName, const char *addr, size_t size)
+{
+    char dump[1024] = { 0, };
+
+    strcat(dump, methodName);
+    strcat(dump, ".o");
+
+    FILE *f = fopen(dump,  "wb");
+    fwrite(addr, sizeof(char), size, f);
+    fclose(f);
+}
+#endif
+
 TypeInfoBase*
 GetTypeInfoFromTypeHandle(TypeHandle typeHandle,
                           NotifyGdb::PTK_TypeInfoMap pTypeMap,
@@ -2067,7 +2081,7 @@ class Elf_Builder
         void Finalize(void);
 
     public:
-        char *Export(UINT64 *len);
+        char *Export(size_t *len);
 };
 
 Elf_Builder::Elf_Builder()
@@ -2167,7 +2181,7 @@ void Elf_Builder::CloseSection()
     m_Curr = nullptr;
 }
 
-char *Elf_Builder::Export(UINT64 *pLen)
+char *Elf_Builder::Export(size_t *pLen)
 {
     unsigned int len = m_Buffer.GetPos();
     const char  *src = m_Buffer.GetPtr();
@@ -2473,12 +2487,27 @@ void NotifyGdb::OnMethodPrepared(MethodDesc* methodDescPtr)
 
     elfBuilder.Finalize();
 
+    char   *symfile_addr = NULL;
+    size_t  symfile_size = 0;
+
+    symfile_addr = elfBuilder.Export(&symfile_size);
+
+#ifdef _DEBUG
+    LPCUTF8 methodName = methodDescPtr->GetName();
+
+    if (g_pConfig->ShouldDumpElfOnMethod(methodName))
+    {
+        DumpElf(methodName, symfile_addr, symfile_size);
+    }
+#endif
+
     /* Create GDB JIT structures */
     NewHolder<jit_code_entry> jit_symbols = new jit_code_entry;
 
     /* Fill the new entry */
     jit_symbols->next_entry = jit_symbols->prev_entry = 0;
-    jit_symbols->symfile_addr = elfBuilder.Export(&jit_symbols->symfile_size);
+    jit_symbols->symfile_addr = symfile_addr;
+    jit_symbols->symfile_size = symfile_size;
 
     /* Link into list */
     jit_code_entry *head = __jit_debug_descriptor.first_entry;
@@ -3266,19 +3295,6 @@ void NotifyGdb::SplitPathname(const char* path, const char*& pathName, const cha
     }
 }
 
-#ifdef _DEBUG
-void NotifyGdb::DumpElf(const char* methodName, const MemBuf& elfFile)
-{
-    char dump[1024];
-    strcpy(dump, "./");
-    strcat(dump, methodName);
-    strcat(dump, ".o");
-    FILE *f = fopen(dump,  "wb");
-    fwrite(elfFile.MemPtr, sizeof(char),elfFile.MemSize, f);
-    fclose(f);
-}
-#endif
-
 /* ELF 32bit header */
 Elf32_Ehdr::Elf32_Ehdr()
 {
index 533c496..1548a70 100644 (file)
@@ -430,9 +430,6 @@ private:
     static void SplitPathname(const char* path, const char*& pathName, const char*& fileName);
     static bool CollectCalledMethods(CalledMethod* pCM, TADDR nativeCode, FunctionMemberPtrArrayHolder &method,
                                      NewArrayHolder<Elf_Symbol> &symbolNames, int &symbolCount);
-#ifdef _DEBUG
-    static void DumpElf(const char* methodName, const MemBuf& buf);
-#endif
 };
 
 class FunctionMember: public TypeMember
index 0885285..e61cb44 100644 (file)
     <TargetFramework>netcoreapp2.1</TargetFramework>
     <TargetFrameworkIdentifier>.NETCoreApp</TargetFrameworkIdentifier>
     <PackageTargetFallback>$(PackageTargetFallback);portable-net45+win8</PackageTargetFallback>
-    <RuntimeIdentifiers>win-arm;win-x64;win-x86;win7-x86;win7-x64;win10-arm64;ubuntu.14.04-x64;ubuntu.16.04-x64;ubuntu.16.10-x64;osx.10.12-x64;osx-x64;centos.7-x64;rhel.7-x64;debian.8-x64;fedora.24-x64;opensuse.42.1-x64;linux-x64</RuntimeIdentifiers>
+    <RuntimeIdentifiers>win-arm;win-x64;win-x86;win7-x86;win7-x64;win10-arm64;ubuntu.14.04-x64;ubuntu.16.04-x64;ubuntu.16.10-x64;osx.10.12-x64;osx-x64;centos.7-x64;rhel.6-x64;rhel.7-x64;debian.8-x64;fedora.24-x64;opensuse.42.1-x64;linux-x64</RuntimeIdentifiers>
     <ContainsPackageReferences>true</ContainsPackageReferences>
     <PrereleaseResolveNuGetPackages>false</PrereleaseResolveNuGetPackages>
   </PropertyGroup>
   <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
   <Target Name="Build"
-     DependsOnTargets="ResolveReferences" /> 
+     DependsOnTargets="ResolveReferences" />
 </Project>
index a7828fe..1cbc5ef 100644 (file)
     <TargetFramework>netcoreapp1.1</TargetFramework>
     <TargetFrameworkIdentifier>.NETCoreApp</TargetFrameworkIdentifier>
     <PackageTargetFallback>$(PackageTargetFallback);dnxcore50;portable-net45+win8</PackageTargetFallback>
-    <RuntimeIdentifiers>win-arm;win7-x86;win7-x64;ubuntu.14.04-x64;ubuntu.16.04-x64;ubuntu.16.10-x64;osx.10.12-x64;centos.7-x64;rhel.7-x64;debian.8-x64;fedora.24-x64</RuntimeIdentifiers>
+    <RuntimeIdentifiers>win-arm;win7-x86;win7-x64;ubuntu.14.04-x64;ubuntu.16.04-x64;ubuntu.16.10-x64;osx.10.12-x64;centos.7-x64;rhel.6-x64;rhel.7-x64;debian.8-x64;fedora.24-x64</RuntimeIdentifiers>
     <ContainsPackageReferences>true</ContainsPackageReferences>
     <PrereleaseResolveNuGetPackages>false</PrereleaseResolveNuGetPackages>
   </PropertyGroup>
   <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
   <Target Name="Build"
-     DependsOnTargets="ResolveReferences" /> 
+     DependsOnTargets="ResolveReferences" />
 </Project>
index cf1576a..9ff057e 100644 (file)
@@ -27,7 +27,7 @@
     <TargetFramework>netcoreapp2.1</TargetFramework>
     <TargetFrameworkIdentifier>.NETCoreApp</TargetFrameworkIdentifier>
     <PackageTargetFallback>$(PackageTargetFallback);dnxcore50;netcoreapp1.1;portable-net45+win8</PackageTargetFallback>
-    <RuntimeIdentifiers>win-arm;win-x64;win-x86;win7-x86;win7-x64;win-arm64;win-arm;win7-arm64;win7-arm;ubuntu.14.04-x64;ubuntu.16.04-x64;ubuntu.16.10-x64;osx.10.12-x64;osx-x64;centos.7-x64;rhel.7-x64;debian.8-x64;fedora.24-x64;linux-x64</RuntimeIdentifiers>
+    <RuntimeIdentifiers>win-arm;win-x64;win-x86;win7-x86;win7-x64;win-arm64;win-arm;win7-arm64;win7-arm;ubuntu.14.04-x64;ubuntu.16.04-x64;ubuntu.16.10-x64;osx.10.12-x64;osx-x64;centos.7-x64;rhel.6-x64;rhel.7-x64;debian.8-x64;fedora.24-x64;linux-x64</RuntimeIdentifiers>
     <ContainsPackageReferences>true</ContainsPackageReferences>
     <PrereleaseResolveNuGetPackages>false</PrereleaseResolveNuGetPackages>
   </PropertyGroup>
index 5838715..facaf92 100644 (file)
@@ -30,7 +30,7 @@
     <TargetFramework>netcoreapp2.1</TargetFramework>
     <TargetFrameworkIdentifier>.NETCoreApp</TargetFrameworkIdentifier>
     <PackageTargetFallback>$(PackageTargetFallback);dnxcore50;portable-net45+win8</PackageTargetFallback>
-    <RuntimeIdentifiers>win-arm;win-x64;win-x86;win7-x86;win7-x64;win-arm64;win-arm;win7-arm64;win7-arm;ubuntu.14.04-x64;ubuntu.16.04-x64;ubuntu.16.10-x64;osx.10.12-x64;osx-x64;centos.7-x64;rhel.7-x64;debian.8-x64;fedora.24-x64;linux-x64</RuntimeIdentifiers>
+    <RuntimeIdentifiers>win-arm;win-x64;win-x86;win7-x86;win7-x64;win-arm64;win-arm;win7-arm64;win7-arm;ubuntu.14.04-x64;ubuntu.16.04-x64;ubuntu.16.10-x64;osx.10.12-x64;osx-x64;centos.7-x64;rhel.6-x64;rhel.7-x64;debian.8-x64;fedora.24-x64;linux-x64</RuntimeIdentifiers>
     <ContainsPackageReferences>true</ContainsPackageReferences>
     <PrereleaseResolveNuGetPackages>false</PrereleaseResolveNuGetPackages>
   </PropertyGroup>
diff --git a/tests/src/JIT/Performance/CodeQuality/Layout/SearchLoops.cs b/tests/src/JIT/Performance/CodeQuality/Layout/SearchLoops.cs
new file mode 100644 (file)
index 0000000..c08e314
--- /dev/null
@@ -0,0 +1,120 @@
+// 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 Microsoft.Xunit.Performance;
+using Microsoft.Xunit.Performance.Api;
+using System;
+using System.Reflection;
+using Xunit;
+
+[assembly: OptimizeForBenchmarks]
+
+// Test code taken directly from GitHub issue #9692 (https://github.com/dotnet/coreclr/issues/9692)
+// Laying the loop's early return path in-line can cost 30% on this micro-benchmark.
+
+namespace Layout
+{
+    public unsafe class SearchLoops
+    {
+        public static int Main(string[] args)
+        {
+            // Make sure equal strings compare as such
+            if (!LoopReturn("hello", "hello") || !LoopGoto("goodbye", "goodbye"))
+            {
+                return -1;
+            }
+
+            // Make sure not-equal strings compare as such
+            if (LoopReturn("hello", "goodbye") || LoopGoto("goodbye", "hello"))
+            {
+                return -1;
+            }
+
+            // Success
+            return 100;
+        }
+
+        public int length = 100;
+
+        private string test1;
+        private string test2;
+
+        public SearchLoops()
+        {
+            test1 = new string('A', length);
+            test2 = new string('A', length);
+        }
+
+        [Benchmark]
+        public void LoopReturn()
+        {
+            Benchmark.Iterate(() => LoopReturn(test1, test2));
+        }
+
+        [Benchmark]
+        public void LoopGoto()
+        {
+            Benchmark.Iterate(() => LoopGoto(test1, test2));
+        }
+
+        // Variant with code written naturally -- need JIT to lay this out
+        // with return path out of loop for best performance.
+        public static bool LoopReturn(String strA, String strB)
+        {
+            int length = strA.Length;
+
+            fixed (char* ap = strA) fixed (char* bp = strB)
+            {
+                char* a = ap;
+                char* b = bp;
+
+                while (length != 0)
+                {
+                    int charA = *a;
+                    int charB = *b;
+
+                    if (charA != charB)
+                        return false;  // placement of prolog for this return is the issue
+
+                    a++;
+                    b++;
+                    length--;
+                }
+
+                return true;
+            }
+        }
+
+        // Variant with code written awkwardly but which acheives the desired
+        // performance if JIT simply lays out code in source order.
+        public static bool LoopGoto(String strA, String strB)
+        {
+            int length = strA.Length;
+
+            fixed (char* ap = strA) fixed (char* bp = strB)
+            {
+                char* a = ap;
+                char* b = bp;
+
+                while (length != 0)
+                {
+                    int charA = *a;
+                    int charB = *b;
+
+                    if (charA != charB)
+                        goto ReturnFalse;  // placement of prolog for this return is the issue
+
+                    a++;
+                    b++;
+                    length--;
+                }
+
+                return true;
+
+                ReturnFalse:
+                return false;
+            }
+        }
+    }
+}
diff --git a/tests/src/JIT/Performance/CodeQuality/Layout/SearchLoops.csproj b/tests/src/JIT/Performance/CodeQuality/Layout/SearchLoops.csproj
new file mode 100644 (file)
index 0000000..2826c93
--- /dev/null
@@ -0,0 +1,39 @@
+<?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>
+    <SchemaVersion>2.0</SchemaVersion>
+    <ProjectGuid>{C1BFD48A-A83F-4767-8EB2-3E2C50906681}</ProjectGuid>
+    <OutputType>Exe</OutputType>
+    <ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+    <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\</SolutionDir>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+  </PropertyGroup>
+  <!-- Default configurations to help VS understand the configurations -->
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <DebugType>pdbonly</DebugType>
+    <Optimize>true</Optimize>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <DebugType>pdbonly</DebugType>
+    <Optimize>true</Optimize>
+  </PropertyGroup>
+  <ItemGroup>
+    <CodeAnalysisDependentAssemblyPaths Condition=" '$(VS100COMNTOOLS)' != '' " Include="$(VS100COMNTOOLS)..\IDE\PrivateAssemblies">
+      <Visible>False</Visible>
+    </CodeAnalysisDependentAssemblyPaths>
+  </ItemGroup>
+  <ItemGroup>
+    <Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="SearchLoops.cs" />
+  </ItemGroup>
+  <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
+  <PropertyGroup Condition=" '$(MsBuildProjectDirOverride)' != '' "></PropertyGroup>
+  <PropertyGroup>
+    <ProjectAssetsFile>$(JitPackagesConfigFileDirectory)benchmark\obj\project.assets.json</ProjectAssetsFile>
+  </PropertyGroup>
+</Project>
diff --git a/tests/src/JIT/Regression/JitBlue/GitHub_9692/GitHub_9692.cs b/tests/src/JIT/Regression/JitBlue/GitHub_9692/GitHub_9692.cs
new file mode 100644 (file)
index 0000000..bd0b49a
--- /dev/null
@@ -0,0 +1,411 @@
+// 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;
+
+// Tests for moving exits out of loops and ensuring that doing so doesn't
+// violate EH clause nesting rules.
+
+namespace N
+{
+    class C
+    {
+        // Simple search loop: should move the "return true" out of the loop
+        static bool Simple(int[] values)
+        {
+            foreach (int value in values)
+            {
+                if (value == 5)
+                {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        // Nested loop with return that exits both: should move exit out of both.
+        static bool Nested(int[][] values)
+        {
+            foreach (int[] innerValues in values)
+            {
+                foreach(int value in innerValues)
+                {
+                    if (value == 5)
+                    {
+                        CallSomeMethod();
+                        return true;
+                    }
+                }
+            }
+            return false;
+        }
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        static void CallSomeMethod(int n = 0) { }
+
+        static int SharedTop(int n, int a, int b, int x, int y)
+        {
+            int result = 0;
+
+            do
+            {
+                do
+                {
+                    result += x * y + x * x + y * y;
+                } while ((--n & 3) != 1);
+                ++x;
+                result += a * b + a * a + b * b;
+            } while ((--n) != 0);
+
+            return result;
+        }
+
+        class Node
+        {
+            public int value;
+            public Node next;
+        }
+
+        // Entire loop is enclosed in a try block: should still be able to move
+        // exit path out of loop.
+        static bool InTry(Node node)
+        {
+            try
+            {
+                while (node != null)
+                {
+                    if (node.value == 5)
+                    {
+                        return (node.next.value == 7);
+                    }
+                    node = node.next;
+                }
+            }
+            catch (NullReferenceException) { }
+            return false;
+        }
+
+        // Nested loop again, make sure throws are recognized as exits -- throw should
+        // be moved out of loop
+        static bool NestedThrows(int[][] values)
+        {
+            try
+            {
+                foreach (int[] innerValues in values)
+                {
+                    foreach (int value in innerValues)
+                    {
+                        if (value == 5)
+                        {
+                            throw new System.Exception("foo");
+                        }
+                    }
+                }
+                return false;
+            }
+            catch
+            {
+                return true;
+            }
+        }
+
+        // Can't move out of loop without crossing try region boundary; should leave in loop.
+        // Loop should still be recognized, and invariant multiplication hoisted out of it.
+        static bool CrossTry(Node node, int x, int y)
+        {
+            int r = 0;
+            while (node != null)
+            {
+                r += x * y * x * y * x * y * x * y * x * y * x * y;
+                try
+                {
+                    if (node.value == 5)
+                    {
+                        return (node.next.value == 7);
+                    }
+                }
+                catch (NullReferenceException) { return true; }
+                node = node.next;
+            }
+            return (r < 13);
+        }
+
+        // Example where we move a branch out to its target and so should make
+        // it fall through.
+        static bool MoveToTarget(int n, int j)
+        {
+            CallSomeMethod();
+
+            do // Loop 1 starts here
+            {
+                if (n == 1)
+                {
+                    // This path exits Loop 1, so should get moved out.
+                    CallSomeMethod(1);
+                    goto One;
+                }
+                n = ((n & 1) == 0 ? n / 2 : 3 * n + 1);
+            } while (n != 0);
+            // Exit when n == 0 falls through here, so we'll move Call(1) farther down
+            CallSomeMethod(2);
+            return false;
+            // No fallthrough here, so we'll move CallSomeMethod(1) here.
+            // But that means the 'goto One' will become a goto-next that we should
+            // clean up.
+            One:
+            // Making the block at 'One' a loop head prompts an assertion failure
+            // analyzing that loop (wasn't expecting branch-to-next since it runs
+            // after flow opts) if we don't clean up the branch-to-next.
+            do
+            {
+                CallSomeMethod(3);
+            } while (--j != 0);
+
+            return true;
+        }
+
+        // Example where we move a run of blocks that a goto in the loop
+        // branches around.
+        static bool ContractGoto(int n, int j)
+        {
+            CallSomeMethod();
+
+            do  // Loop 1
+            {
+                CallSomeMethod(1);
+                goto StillInLoop;  // this goto will become goto-next when Call(2) gets moved
+
+                EarlyReturn:   // this should get moved out-of-line
+                CallSomeMethod(2);
+                return false;
+
+                StillInLoop:  // make this the top of a loop so assertion would fire if goto left
+                do  // Loop 2
+                {
+                    n = ((n & 1) == 0 ? n / 2 : 3 * n + 1);
+                } while (n == 0);
+
+                if (j < 0)
+                {
+                    goto EarlyReturn;
+                }
+
+            } while (n != 1);
+
+            return true;
+        }
+
+        // Example where we move a label to just after a goto targeting it and so should
+        // changethat goto-next to fall through.
+        static bool MoveAfterGoto(int n, int j)
+        {
+            CallSomeMethod();
+
+            do // Loop 1 starts here
+            {
+                if (j > 0)
+                {
+                    // This path exits Loop 1, so should get moved out.
+                    CallSomeMethod(1);
+                    goto MovingLabel;
+                }
+                // This block stays in the loop.
+                n = ((n & 1) == 0 ? n / 2 : 3 * n + 1);
+                continue;
+                MovingLabel: // This gets moved out, just after its goto, requring cleanup
+                do  // Loop 2 (gets moved out of Loop 1)
+                {
+                    CallSomeMethod(3);
+                } while (--j != 0);
+                return true;
+            } while (n != 1);
+            // Exit when n == 0 falls through here, so we'll move Call(1) farther down
+            CallSomeMethod(2);
+            return false;
+        }
+
+        // Test to make sure we check for exits on fall-through edges.
+        static bool FallThroughExit(int n, int j, int k, int l)
+        {
+            CallSomeMethod(1);
+
+            do
+            {
+                CallSomeMethod(2);
+                if (n == 1)  // There is an exit here but it's fall-through
+                {
+                    CallSomeMethod(3);
+                    break;
+                }
+                // This is not an articulated block, but missing the first exit
+                // would make us think it is and we might hoist this big expression.
+                CallSomeMethod(k * j + k * k + j * j + k * l + j * l + l * l);
+                n = ((n & 1) == 0 ? n / 2 : 3 * n + 1);
+            } while (n > 1);  // This will be the only exit we see if we miss the other
+            CallSomeMethod(4);
+            return true;
+        }
+
+
+        // Test to make sure we can safely handle single-exit loops when the
+        // algorithm to compact loops decrements the exit count from two back
+        // to one and leaves it there.
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        static void MaybeThrow(int n)
+        {
+            if (n == 21)
+            {
+                throw new Exception("Twenty-One");
+            }
+        }
+        static bool InnerInfiniteLoop(int n, int j, int k, int l)
+        {
+            CallSomeMethod();
+
+            do
+            {
+                CallSomeMethod(1);
+
+                if (n == 1)  // This is the first exit
+                {
+                    break;
+                }
+
+                // This is not an articulated block, but missing the first exit
+                // would make us think it is and we might hoist this big expression.
+                CallSomeMethod(k * j + k * k + j * j + k * l + j * l + l * l);
+
+                try
+                {
+                    if (n == 23)  // This is the second exit, but the exit path
+                    {             // cannot be moved out due to the try block.
+                        CallSomeMethod(3);
+                        do
+                        {
+                            MaybeThrow(--n);
+                        } while (true);
+                    }
+                }
+                catch
+                {
+                    return false;
+                }
+                n = ((n & 1) == 0 ? n / 2 : 3 * n + 1);
+            } while (true);
+
+            CallSomeMethod(4);
+
+            if (j < 0)
+            {
+                return false;
+            }
+            return true;
+        }
+
+        class Box
+        {
+            public int Data;
+        }
+
+        // Test to make sure we don't record a loop and then
+        // move some of its blocks out.
+        static bool InOut(int n, Box box)
+        {
+            int value = box.Data;
+            int target = 0;
+            int result = 0;
+
+            do  // Outer loop starts here
+            {
+                result += box.Data;  // Hoisting this is illegal due to the write in the inner loop
+                goto enterInnerLoop; // branch around 'continueOuterLoop'
+
+                continueOuterLoop:  // Target for lexically-backward exit from inner loop that
+                ++result;           // is not an exit from the outer loop
+                continue;
+
+                enterInnerLoop:
+                --result;
+                do  // Inner loop starts here
+                {
+                    if (target == 0)  // Conditional exit from inner loop that we'll want to move out
+                    {                 // but need to be careful not to move it out of the outer loop
+                        box.Data = value + 1;  // ValueNumbering needs to see that this store is part of
+                        target = value * n;    // the outer loop, else we'll think the load above is invariant.
+                        goto continueOuterLoop;
+                    }
+                } while (box.Data < 0);
+            } while (--n > 0);
+
+            return (result == target);
+        }
+
+        public static int Main(string[] args)
+        {
+            int[] has5 = new int[] { 1, 2, 3, 4, 5 };
+            int[] no5 = new int[] { 6, 7, 8, 9 };
+            int[][] has5jagged = new int[][] { no5, has5 };
+            int[][] no5jagged = new int[][] { no5, no5 };
+            Node faultHead = new Node { value = 6, next = new Node { value = 13, next = new Node { value = 5, next = null } } };
+            Node trueHead = new Node { value = 23, next = new Node { value = 5, next = new Node { value = 7, next = null } } };
+            Node falseHead = new Node { value = 5, next = new Node { value = 8, next = null } };
+
+            int result = 100; // 100 indicates success; increment for errors.
+
+            if (!Simple(has5) || Simple(no5))
+            {
+                ++result;
+            }
+            if (!Nested(has5jagged) || Nested(no5jagged))
+            {
+                ++result;
+            }
+            if (SharedTop(17, 2, 3, 4, 5) != 1145)
+            {
+                ++result;
+            }
+            if (!InTry(trueHead) || InTry(falseHead) || InTry(faultHead))
+            {
+                ++result;
+            }
+            if (!NestedThrows(has5jagged) || NestedThrows(no5jagged))
+            {
+                ++result;
+            }
+            if (!CrossTry(trueHead, 8, 4) || CrossTry(falseHead, 8, 4) || !CrossTry(faultHead, 8, 4))
+            {
+                ++result;
+            }
+
+            if (!MoveToTarget(7, 2))
+            {
+                ++result;
+            }
+            if (!ContractGoto(23, 5) || ContractGoto(42, -5))
+            {
+                ++result;
+            }
+            if (!MoveAfterGoto(23, 5) || MoveAfterGoto(42, -5))
+            {
+                ++result;
+            }
+
+            if (!FallThroughExit(8, 5, 7, 22))
+            {
+                ++result;
+            }
+            if (!InnerInfiniteLoop(8, 5, 3, 18) || InnerInfiniteLoop(16, -5, 2, 4) || InnerInfiniteLoop(23, 7, 6, 5) || !InnerInfiniteLoop(1, 0, 11, 22))
+            {
+                ++result;
+            }
+
+            if (!InOut(17, new Box() { Data = 5 }))
+            {
+                ++result;
+            }
+
+            return result;
+        }
+    }
+}
diff --git a/tests/src/JIT/Regression/JitBlue/GitHub_9692/GitHub_9692.csproj b/tests/src/JIT/Regression/JitBlue/GitHub_9692/GitHub_9692.csproj
new file mode 100644 (file)
index 0000000..e96c8ec
--- /dev/null
@@ -0,0 +1,42 @@
+<?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>{9711BC9D-A24D-458D-8357-007D6423C212}</ProjectGuid>
+    <OutputType>Exe</OutputType>
+    <AppDesignerFolder>Properties</AppDesignerFolder>
+    <FileAlignment>512</FileAlignment>
+    <ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+    <ReferencePath>$(ProgramFiles)\Common Files\microsoft shared\VSTT\11.0\UITestExtensionPackages</ReferencePath>
+    <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\</SolutionDir>
+
+    <NuGetPackageImportStamp>7a9bfb7d</NuGetPackageImportStamp>
+  </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></DebugType>
+    <Optimize>True</Optimize>
+  </PropertyGroup>
+  <ItemGroup>
+    <Compile Include="$(MSBuildProjectName).cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
+  </ItemGroup>
+  <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
+  <PropertyGroup Condition=" '$(MsBuildProjectDirOverride)' != '' ">
+  </PropertyGroup> 
+</Project>