Add simple Else conditions to If Conversion (#77728)
authorAlan Hayward <a74nh@users.noreply.github.com>
Wed, 30 Nov 2022 10:53:28 +0000 (10:53 +0000)
committerGitHub <noreply@github.com>
Wed, 30 Nov 2022 10:53:28 +0000 (11:53 +0100)
* Add simple Else conditions to If Conversion

For example:
if (x < 7) { a = 5; } else { a = 9; }
a = (cond) ? b : c;

The else condition must write to the same variable as the then
statement.

* Move phase and stop updating ssa

* Wrap JitConfig access

* Add GT_RETURN else cases

* Add test cases with verification checks

* Ensure single only operation condition checks are used

* Remove empty line

* Use DOTNET_ instead of COMPlus_

* Move JitDoIfConversion check

* Move if conversion into it's own file

* Always invert condition

* Rename IfConvertMergeBlocks

* Use gtGetOp1()

* Expand tests

* Add operation type assert

* Allow nested SELECT nodes

* Fix condition directions

src/coreclr/jit/CMakeLists.txt
src/coreclr/jit/compiler.cpp
src/coreclr/jit/ifconversion.cpp [new file with mode: 0644]
src/coreclr/jit/optimizer.cpp
src/tests/JIT/opt/Compares/compares.cs

index e9cee4a..4e3640c 100644 (file)
@@ -124,6 +124,7 @@ set( JIT_SOURCES
   hashbv.cpp
   hwintrinsic.cpp
   hostallocator.cpp
+  ifconversion.cpp
   indirectcalltransformer.cpp
   importercalls.cpp
   importer.cpp
index 5ee38b3..6848341 100644 (file)
@@ -4755,7 +4755,6 @@ void Compiler::compCompile(void** methodCodePtr, uint32_t* methodCodeSize, JitFl
         bool doCse                     = true;
         bool doAssertionProp           = true;
         bool doRangeAnalysis           = true;
-        bool doIfConversion            = true;
         bool doVNBasedDeadStoreRemoval = true;
         int  iterations                = 1;
 
@@ -4769,7 +4768,6 @@ void Compiler::compCompile(void** methodCodePtr, uint32_t* methodCodeSize, JitFl
         doCse                     = doValueNum;
         doAssertionProp           = doValueNum && (JitConfig.JitDoAssertionProp() != 0);
         doRangeAnalysis           = doAssertionProp && (JitConfig.JitDoRangeAnalysis() != 0);
-        doIfConversion            = doIfConversion && (JitConfig.JitDoIfConversion() != 0);
         doVNBasedDeadStoreRemoval = doValueNum && (JitConfig.JitDoVNBasedDeadStoreRemoval() != 0);
 
         if (opts.optRepeat)
@@ -4852,13 +4850,6 @@ void Compiler::compCompile(void** methodCodePtr, uint32_t* methodCodeSize, JitFl
                 DoPhase(this, PHASE_ASSERTION_PROP_MAIN, &Compiler::optAssertionPropMain);
             }
 
-            if (doIfConversion)
-            {
-                // If conversion
-                //
-                DoPhase(this, PHASE_IF_CONVERSION, &Compiler::optIfConversion);
-            }
-
             if (doRangeAnalysis)
             {
                 // Bounds check elimination via range analysis
@@ -4910,6 +4901,10 @@ void Compiler::compCompile(void** methodCodePtr, uint32_t* methodCodeSize, JitFl
         //
         DoPhase(this, PHASE_OPTIMIZE_BOOLS, &Compiler::optOptimizeBools);
 
+        // If conversion
+        //
+        DoPhase(this, PHASE_IF_CONVERSION, &Compiler::optIfConversion);
+
         // Optimize block order
         //
         DoPhase(this, PHASE_OPTIMIZE_LAYOUT, &Compiler::optOptimizeLayout);
diff --git a/src/coreclr/jit/ifconversion.cpp b/src/coreclr/jit/ifconversion.cpp
new file mode 100644 (file)
index 0000000..5b47a9b
--- /dev/null
@@ -0,0 +1,779 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+/*XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+XX                                                                           XX
+XX                              OptIfConversion                              XX
+XX                                                                           XX
+XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+*/
+
+#include "jitpch.h"
+#ifdef _MSC_VER
+#pragma hdrstop
+#endif
+
+//-----------------------------------------------------------------------------
+// OptIfConversionDsc:     Descriptor used for If conversion
+//
+class OptIfConversionDsc
+{
+public:
+    OptIfConversionDsc(Compiler* comp, BasicBlock* startBlock)
+    {
+        m_comp       = comp;
+        m_startBlock = startBlock;
+    }
+
+private:
+    Compiler* m_comp; // The Compiler instance.
+
+    BasicBlock* m_startBlock;           // First block in the If Conversion.
+    BasicBlock* m_finalBlock = nullptr; // Block where the flows merge. In a return case, this can be nullptr.
+
+    // The node, statement and block of an assignment.
+    struct IfConvertOperation
+    {
+        BasicBlock* block = nullptr;
+        Statement*  stmt  = nullptr;
+        GenTree*    node  = nullptr;
+    };
+
+    GenTree*           m_cond;          // The condition in the conversion
+    IfConvertOperation m_thenOperation; // The single operation in the Then case.
+    IfConvertOperation m_elseOperation; // The single operation in the Else case.
+
+    int m_checkLimit = 4; // Max number of chained blocks to allow in both the True and Else cases.
+
+    genTreeOps m_mainOper         = GT_COUNT; // The main oper of the if conversion.
+    bool       m_doElseConversion = false;    // Does the If conversion have an else statement.
+    bool       m_flowFound        = false;    // Has a valid flow been found.
+
+    bool IfConvertCheckInnerBlockFlow(BasicBlock* block);
+    bool IfConvertCheckThenFlow();
+    void IfConvertFindFlow();
+    bool IfConvertCheckStmts(BasicBlock* fromBlock, IfConvertOperation* foundOperation);
+    void IfConvertJoinStmts(BasicBlock* fromBlock);
+
+#ifdef DEBUG
+    void IfConvertDump();
+#endif
+
+public:
+    bool optIfConvert();
+};
+
+//-----------------------------------------------------------------------------
+// IfConvertCheckInnerBlockFlow
+//
+// Check if the flow of a block is valid for use as an inner block (either a Then or Else block)
+// in an If Conversion.
+//
+// Assumptions:
+//   m_startBlock and m_doElseConversion are set.
+//
+// Arguments:
+//   block -- Block to check.
+//
+// Returns:
+//   True if Checks are ok, else false.
+//
+bool OptIfConversionDsc::IfConvertCheckInnerBlockFlow(BasicBlock* block)
+{
+    // Block should have a single successor or be a return.
+    if (!(block->GetUniqueSucc() != nullptr || (m_doElseConversion && (block->bbJumpKind == BBJ_RETURN))))
+    {
+        return false;
+    }
+
+    // Check that we have linear flow and are still in the same EH region
+
+    if (block->GetUniquePred(m_comp) == nullptr)
+    {
+        return false;
+    }
+
+    if (!BasicBlock::sameEHRegion(block, m_startBlock))
+    {
+        return false;
+    }
+
+    return true;
+}
+
+//-----------------------------------------------------------------------------
+// IfConvertCheckThenFlow
+//
+// Check all the Then blocks between m_startBlock and m_finalBlock are valid.
+//
+// Assumptions:
+//   m_startBlock, m_finalBlock and m_doElseConversion are set.
+//
+// Returns:
+//   If a conversion is found, then set m_flowFound and return true.
+//   If a conversion is not found, and it's ok to keep searching, return true.
+//   Otherwise, return false.
+//
+// Notes:
+//   Sets m_flowFound and m_mainOper.
+//
+bool OptIfConversionDsc::IfConvertCheckThenFlow()
+{
+    m_flowFound           = false;
+    BasicBlock* thenBlock = m_startBlock->bbNext;
+
+    for (int thenLimit = 0; thenLimit < m_checkLimit; thenLimit++)
+    {
+        if (!IfConvertCheckInnerBlockFlow(thenBlock))
+        {
+            // Then block is not in a valid flow.
+            return true;
+        }
+        BasicBlock* thenBlockNext = thenBlock->GetUniqueSucc();
+
+        if (thenBlockNext == m_finalBlock)
+        {
+            // All the Then blocks up to m_finalBlock are in a valid flow.
+            m_flowFound = true;
+            if (thenBlock->bbJumpKind == BBJ_RETURN)
+            {
+                assert(m_finalBlock == nullptr);
+                m_mainOper = GT_RETURN;
+            }
+            else
+            {
+                m_mainOper = GT_ASG;
+            }
+            return true;
+        }
+
+        if (thenBlockNext == nullptr)
+        {
+            // Invalid Then and Else combination.
+            return false;
+        }
+
+        thenBlock = thenBlockNext;
+    }
+
+    // Nothing found. Still valid to continue.
+    return true;
+}
+
+//-----------------------------------------------------------------------------
+// IfConvertFindFlow
+//
+// Find a valid if conversion flow from m_startBlock to a final block.
+// There might be multiple Then and Else blocks in the flow - use m_checkLimit to limit this.
+//
+// Notes:
+//   Sets m_flowFound, m_finalBlock, m_doElseConversion and m_mainOper.
+//
+void OptIfConversionDsc::IfConvertFindFlow()
+{
+    // First check for flow with no else case. The final block is the destination of the jump.
+    m_doElseConversion = false;
+    m_finalBlock       = m_startBlock->bbJumpDest;
+    assert(m_finalBlock != nullptr);
+    if (!IfConvertCheckThenFlow() || m_flowFound)
+    {
+        // Either the flow is invalid, or a flow was found.
+        return;
+    }
+
+    // Look for flows with else blocks. The final block is the block after the else block.
+    m_doElseConversion = true;
+    for (int elseLimit = 0; elseLimit < m_checkLimit; elseLimit++)
+    {
+        BasicBlock* elseBlock = m_finalBlock;
+        if (elseBlock == nullptr || !IfConvertCheckInnerBlockFlow(elseBlock))
+        {
+            // Need a valid else block in a valid flow .
+            return;
+        }
+
+        m_finalBlock = elseBlock->GetUniqueSucc();
+
+        if (!IfConvertCheckThenFlow() || m_flowFound)
+        {
+            // Either the flow is invalid, or a flow was found.
+            return;
+        }
+    }
+}
+
+//-----------------------------------------------------------------------------
+// IfConvertCheckStmts
+//
+// From the given block to the final block, check all the statements and nodes are
+// valid for an If conversion. Chain of blocks must contain only a single assignment
+// and no other operations.
+//
+// Arguments:
+//   fromBlock  -- Block inside the if statement to start from (Either Then or Else path).
+//   foundOperation -- Returns the found operation.
+//
+// Returns:
+//   If everything is valid, then set foundOperation to the assignment and return true.
+//   Otherwise return false.
+//
+bool OptIfConversionDsc::IfConvertCheckStmts(BasicBlock* fromBlock, IfConvertOperation* foundOperation)
+{
+    bool found = false;
+
+    for (BasicBlock* block = fromBlock; block != m_finalBlock; block = block->GetUniqueSucc())
+    {
+        assert(block != nullptr);
+
+        // Can all the nodes within the block be made to conditionally execute?
+        for (Statement* const stmt : block->Statements())
+        {
+            GenTree* tree = stmt->GetRootNode();
+            switch (tree->gtOper)
+            {
+                case GT_ASG:
+                {
+                    GenTree* op1 = tree->gtGetOp1();
+                    GenTree* op2 = tree->gtGetOp2();
+
+                    // Only one per operation per block can be conditionally executed.
+                    if (found)
+                    {
+                        return false;
+                    }
+
+                    // Ensure the operarand is a local variable with integer type.
+                    if (!op1->OperIs(GT_LCL_VAR) || !varTypeIsIntegralOrI(op1))
+                    {
+                        return false;
+                    }
+
+                    // Ensure it won't cause any additional side effects.
+                    if ((op1->gtFlags & (GTF_SIDE_EFFECT | GTF_ORDER_SIDEEFF)) != 0 ||
+                        (op2->gtFlags & (GTF_SIDE_EFFECT | GTF_ORDER_SIDEEFF)) != 0)
+                    {
+                        return false;
+                    }
+
+                    // Ensure the source isn't a phi.
+                    if (op2->OperIs(GT_PHI))
+                    {
+                        return false;
+                    }
+
+                    // Evaluating unconditionally effectively has the same effect as reordering
+                    // with the condition (for example, the condition could be an explicit bounds
+                    // check and the operand could read an array element). Disallow this except
+                    // for some common cases that we know are always side effect free.
+                    if (((m_cond->gtFlags & GTF_ORDER_SIDEEFF) != 0) && !op2->IsInvariant() && !op2->OperIsLocal())
+                    {
+                        return false;
+                    }
+
+                    found                 = true;
+                    foundOperation->block = block;
+                    foundOperation->stmt  = stmt;
+                    foundOperation->node  = tree;
+                    break;
+                }
+
+                case GT_RETURN:
+                {
+                    GenTree* op1 = tree->gtGetOp1();
+
+                    // Only allow RETURNs if else conversion is being used.
+                    if (!m_doElseConversion)
+                    {
+                        return false;
+                    }
+
+                    // Only one per operation per block can be conditionally executed.
+                    if (found || op1 == nullptr)
+                    {
+                        return false;
+                    }
+
+                    // Ensure the operation has integer type.
+                    if (!varTypeIsIntegralOrI(tree))
+                    {
+                        return false;
+                    }
+
+                    // Ensure it won't cause any additional side effects.
+                    if ((op1->gtFlags & (GTF_SIDE_EFFECT | GTF_ORDER_SIDEEFF)) != 0)
+                    {
+                        return false;
+                    }
+
+                    // Evaluating unconditionally effectively has the same effect as reordering
+                    // with the condition (for example, the condition could be an explicit bounds
+                    // check and the operand could read an array element). Disallow this except
+                    // for some common cases that we know are always side effect free.
+                    if (((m_cond->gtFlags & GTF_ORDER_SIDEEFF) != 0) && !op1->IsInvariant() && !op1->OperIsLocal())
+                    {
+                        return false;
+                    }
+
+                    found                 = true;
+                    foundOperation->block = block;
+                    foundOperation->stmt  = stmt;
+                    foundOperation->node  = tree;
+                    break;
+                }
+
+                // These do not need conditional execution.
+                case GT_NOP:
+                    if (tree->gtGetOp1() != nullptr || (tree->gtFlags & (GTF_SIDE_EFFECT | GTF_ORDER_SIDEEFF)) != 0)
+                    {
+                        return false;
+                    }
+                    break;
+
+                // Cannot optimise this block.
+                default:
+                    return false;
+            }
+        }
+    }
+    return found;
+}
+
+//-----------------------------------------------------------------------------
+// IfConvertJoinStmts
+//
+// Move all the statements from a block onto the end of the start block.
+//
+// Arguments:
+//   fromBlock  -- Source block
+//
+void OptIfConversionDsc::IfConvertJoinStmts(BasicBlock* fromBlock)
+{
+    Statement* stmtList1 = m_startBlock->firstStmt();
+    Statement* stmtList2 = fromBlock->firstStmt();
+    Statement* stmtLast1 = m_startBlock->lastStmt();
+    Statement* stmtLast2 = fromBlock->lastStmt();
+    stmtLast1->SetNextStmt(stmtList2);
+    stmtList2->SetPrevStmt(stmtLast1);
+    stmtList1->SetPrevStmt(stmtLast2);
+    fromBlock->bbStmtList = nullptr;
+}
+
+//-----------------------------------------------------------------------------
+// IfConvertDump
+//
+// Dump all the blocks in the If Conversion.
+//
+#ifdef DEBUG
+void OptIfConversionDsc::IfConvertDump()
+{
+    assert(m_startBlock != nullptr);
+    m_comp->fgDumpBlock(m_startBlock);
+    for (BasicBlock* dumpBlock = m_startBlock->bbNext; dumpBlock != m_finalBlock;
+         dumpBlock             = dumpBlock->GetUniqueSucc())
+    {
+        m_comp->fgDumpBlock(dumpBlock);
+    }
+    if (m_doElseConversion)
+    {
+        for (BasicBlock* dumpBlock = m_startBlock->bbJumpDest; dumpBlock != m_finalBlock;
+             dumpBlock             = dumpBlock->GetUniqueSucc())
+        {
+            m_comp->fgDumpBlock(dumpBlock);
+        }
+    }
+}
+#endif
+
+//-----------------------------------------------------------------------------
+// optIfConvert
+//
+// Find blocks representing simple if statements represented by conditional jumps
+// over another block. Try to replace the jumps by use of SELECT nodes.
+//
+// Returns:
+//   true if any IR changes possibly made.
+//
+// Notes:
+//
+// Example of simple if conversion:
+//
+// This is optimising a simple if statement. There is a single condition being
+// tested, and a single assignment inside the body. There must be no else
+// statement. For example:
+// if (x < 7) { a = 5; }
+//
+// This is represented in IR by two basic blocks. The first block (block) ends with
+// a JTRUE statement which conditionally jumps to the second block (thenBlock).
+// The second block just contains a single assign statement. Both blocks then jump
+// to the same destination (finalBlock).  Note that the first block may contain
+// additional statements prior to the JTRUE statement.
+//
+// For example:
+//
+// ------------ BB03 [009..00D) -> BB05 (cond), preds={BB02} succs={BB04,BB05}
+// STMT00004
+//   *  JTRUE     void   $VN.Void
+//   \--*  GE        int    $102
+//      +--*  LCL_VAR   int    V02
+//      \--*  CNS_INT   int    7 $46
+//
+// ------------ BB04 [00D..010), preds={BB03} succs={BB05}
+// STMT00005
+//   *  ASG       int    $VN.Void
+// +--*  LCL_VAR   int    V00 arg0
+// \--*  CNS_INT   int    5 $47
+//
+//
+// This is optimised by conditionally executing the store and removing the conditional
+// jumps. First the JTRUE is replaced with a NOP. The assignment is updated so that
+// the source of the store is a SELECT node with the condition set to the inverse of
+// the original JTRUE condition. If the condition passes the original assign happens,
+// otherwise the existing source value is used.
+//
+// In the example above, local var 0 is set to 5 if the LT returns true, otherwise
+// the existing value of local var 0 is used:
+//
+// ------------ BB03 [009..00D) -> BB05 (always), preds={BB02} succs={BB05}
+// STMT00004
+//   *  NOP       void
+//
+// STMT00005
+//   *  ASG       int    $VN.Void
+//   +--*  LCL_VAR   int    V00 arg0
+//   \--*  SELECT    int
+//      +--*  LT        int    $102
+//      |  +--*  LCL_VAR   int    V02
+//      |  \--*  CNS_INT   int    7 $46
+//      +--*  CNS_INT   int    5 $47
+//      \--*  LCL_VAR   int    V00
+//
+// ------------ BB04 [00D..010), preds={} succs={BB05}
+//
+//
+// Example of simple if conversion with an else condition
+//
+// This is similar to the simple if conversion above, but with an else statement
+// that assigns to the same variable as the then statement. For example:
+// if (x < 7) { a = 5; } else { a = 9; }
+//
+// ------------ BB03 [009..00D) -> BB05 (cond), preds={BB02} succs={BB04,BB05}
+// STMT00004
+//   *  JTRUE     void   $VN.Void
+//   \--*  GE        int    $102
+//      +--*  LCL_VAR   int    V02
+//      \--*  CNS_INT   int    7 $46
+//
+// ------------ BB04 [00D..010), preds={BB03} succs={BB06}
+// STMT00005
+//   *  ASG       int    $VN.Void
+// +--*  LCL_VAR   int    V00 arg0
+// \--*  CNS_INT   int    5 $47
+//
+// ------------ BB05 [00D..010), preds={BB03} succs={BB06}
+// STMT00006
+//   *  ASG       int    $VN.Void
+// +--*  LCL_VAR   int    V00 arg0
+// \--*  CNS_INT   int    9 $48
+//
+// Again this is squashed into a single block, with the SELECT node handling both cases.
+//
+// ------------ BB03 [009..00D) -> BB05 (always), preds={BB02} succs={BB05}
+// STMT00004
+//   *  NOP       void
+//
+// STMT00005
+//   *  ASG       int    $VN.Void
+//   +--*  LCL_VAR   int    V00 arg0
+//   \--*  SELECT    int
+//      +--*  LT        int    $102
+//      |  +--*  LCL_VAR   int    V02
+//      |  \--*  CNS_INT   int    7 $46
+//      +--*  CNS_INT   int    5 $47
+//      +--*  CNS_INT   int    9 $48
+//
+// STMT00006
+//   *  NOP       void
+//
+// ------------ BB04 [00D..010), preds={} succs={BB06}
+// ------------ BB05 [00D..010), preds={} succs={BB06}
+//
+// Alternatively, an if conversion with an else condition may use RETURNs.
+// return (x < 7) ? 5 : 9;
+//
+// ------------ BB03 [009..00D) -> BB05 (cond), preds={BB02} succs={BB04,BB05}
+// STMT00004
+//   *  JTRUE     void   $VN.Void
+//   \--*  GE        int    $102
+//      +--*  LCL_VAR   int    V02
+//      \--*  CNS_INT   int    7 $46
+//
+// ------------ BB04 [00D..010), preds={BB03} succs={BB06}
+// STMT00005
+//   *  RETURN    int    $VN.Void
+// +--*  CNS_INT   int    5 $41
+//
+// ------------ BB05 [00D..010), preds={BB03} succs={BB06}
+// STMT00006
+//   *  RETURN    int    $VN.Void
+// +--*  CNS_INT   int    9 $43
+//
+// becomes:
+//
+// ------------ BB03 [009..00D) -> BB05 (always), preds={BB02} succs={BB05}
+// STMT00004
+//   *  NOP       void
+//
+// STMT00005
+//   *  RETURN    int    $VN.Void
+//   \--*  SELECT    int
+//      +--*  LT        int    $102
+//      |  +--*  LCL_VAR   int    V02
+//      |  \--*  CNS_INT   int    7 $46
+//      +--*  CNS_INT   int    5 $41
+//      +--*  CNS_INT   int    9 $43
+//
+// STMT00006
+//   *  NOP       void
+//
+// ------------ BB04 [00D..010), preds={} succs={BB06}
+// ------------ BB05 [00D..010), preds={} succs={BB06}
+//
+bool OptIfConversionDsc::optIfConvert()
+{
+#ifndef TARGET_ARM64
+    return false;
+#endif
+
+    // Don't optimise the block if it is inside a loop
+    // When inside a loop, branches are quicker than selects.
+    // Detect via the block weight as that will be high when inside a loop.
+    if ((m_startBlock->getBBWeight(m_comp) > BB_UNITY_WEIGHT) &&
+        !m_comp->compStressCompile(m_comp->STRESS_IF_CONVERSION_INNER_LOOPS, 25))
+    {
+        return false;
+    }
+
+    // Does the block end by branching via a JTRUE after a compare?
+    if (m_startBlock->bbJumpKind != BBJ_COND || m_startBlock->NumSucc() != 2)
+    {
+        return false;
+    }
+
+    // Verify the test block ends with a condition that we can manipulate.
+    GenTree* last = m_startBlock->lastStmt()->GetRootNode();
+    noway_assert(last->OperIs(GT_JTRUE));
+    m_cond = last->gtGetOp1();
+    if (!m_cond->OperIsCompare())
+    {
+        return false;
+    }
+
+    // Look for valid flow of Then and Else blocks.
+    IfConvertFindFlow();
+    if (!m_flowFound)
+    {
+        return false;
+    }
+
+    // Check the Then and Else blocks have a single operation each.
+    if (!IfConvertCheckStmts(m_startBlock->bbNext, &m_thenOperation))
+    {
+        return false;
+    }
+    assert(m_thenOperation.node->gtOper == GT_ASG || m_thenOperation.node->gtOper == GT_RETURN);
+    if (m_doElseConversion)
+    {
+        if (!IfConvertCheckStmts(m_startBlock->bbJumpDest, &m_elseOperation))
+        {
+            return false;
+        }
+
+        // Both operations must be the same node type.
+        if (m_thenOperation.node->gtOper != m_elseOperation.node->gtOper)
+        {
+            return false;
+        }
+
+        // Currently can only support Else Asg Blocks that have the same destination as the Then block.
+        if (m_thenOperation.node->gtOper == GT_ASG)
+        {
+            unsigned lclNumThen = m_thenOperation.node->gtGetOp1()->AsLclVarCommon()->GetLclNum();
+            unsigned lclNumElse = m_elseOperation.node->gtGetOp1()->AsLclVarCommon()->GetLclNum();
+            if (lclNumThen != lclNumElse)
+            {
+                return false;
+            }
+        }
+    }
+
+#ifdef DEBUG
+    if (m_comp->verbose)
+    {
+        JITDUMP("\nConditionally executing " FMT_BB, m_thenOperation.block->bbNum);
+        if (m_doElseConversion)
+        {
+            JITDUMP(" and " FMT_BB, m_elseOperation.block->bbNum);
+        }
+        JITDUMP(" inside " FMT_BB "\n", m_startBlock->bbNum);
+        IfConvertDump();
+    }
+#endif
+
+    // Using SELECT nodes means that both Then and Else operations are fully evaluated.
+    // Put a limit on the original source and destinations.
+    if (!m_comp->compStressCompile(m_comp->STRESS_IF_CONVERSION_COST, 25))
+    {
+        int thenCost = 0;
+        int elseCost = 0;
+
+        if (m_mainOper == GT_ASG)
+        {
+            thenCost = m_thenOperation.node->gtGetOp2()->GetCostEx() +
+                       (m_comp->gtIsLikelyRegVar(m_thenOperation.node->gtGetOp1()) ? 0 : 2);
+            if (m_doElseConversion)
+            {
+                elseCost = m_elseOperation.node->gtGetOp2()->GetCostEx() +
+                           (m_comp->gtIsLikelyRegVar(m_elseOperation.node->gtGetOp1()) ? 0 : 2);
+            }
+        }
+        else
+        {
+            assert(m_mainOper == GT_RETURN);
+            thenCost = m_thenOperation.node->gtGetOp1()->GetCostEx();
+            if (m_doElseConversion)
+            {
+                elseCost = m_elseOperation.node->gtGetOp1()->GetCostEx();
+            }
+        }
+
+        // Cost to allow for "x = cond ? a + b : c + d".
+        if (thenCost > 7 || elseCost > 7)
+        {
+            JITDUMP("Skipping if-conversion that will evaluate RHS unconditionally at costs %d,%d\n", thenCost,
+                    elseCost);
+            return false;
+        }
+    }
+
+    // Get the select node inputs.
+    GenTree* selectTrueInput;
+    GenTree* selectFalseInput;
+    if (m_mainOper == GT_ASG)
+    {
+        if (m_doElseConversion)
+        {
+            selectTrueInput  = m_elseOperation.node->gtGetOp2();
+            selectFalseInput = m_thenOperation.node->gtGetOp2();
+        }
+        else
+        {
+            // Invert the condition (to help matching condition codes back to CIL).
+            GenTree* revCond = m_comp->gtReverseCond(m_cond);
+            assert(m_cond == revCond); // Ensure `gtReverseCond` did not create a new node.
+
+            // Duplicate the destination of the Then assignment.
+            assert(m_thenOperation.node->gtGetOp1()->IsLocal());
+            selectFalseInput = m_comp->gtCloneExpr(m_thenOperation.node->gtGetOp1());
+            selectFalseInput->gtFlags &= GTF_EMPTY;
+
+            selectTrueInput = m_thenOperation.node->gtGetOp2();
+        }
+    }
+    else
+    {
+        assert(m_mainOper == GT_RETURN);
+        assert(m_doElseConversion);
+        assert(m_thenOperation.node->TypeGet() == m_elseOperation.node->TypeGet());
+
+        selectTrueInput  = m_elseOperation.node->gtGetOp1();
+        selectFalseInput = m_thenOperation.node->gtGetOp1();
+    }
+
+    // Create a select node.
+    GenTreeConditional* select = m_comp->gtNewConditionalNode(GT_SELECT, m_cond, selectTrueInput, selectFalseInput,
+                                                              m_thenOperation.node->TypeGet());
+    m_thenOperation.node->AsOp()->gtFlags |= (select->gtFlags & GTF_ALL_EFFECT);
+
+    // Use the select as the source of the Then operation.
+    if (m_mainOper == GT_ASG)
+    {
+        m_thenOperation.node->AsOp()->gtOp2 = select;
+    }
+    else
+    {
+        m_thenOperation.node->AsOp()->gtOp1 = select;
+    }
+    m_comp->gtSetEvalOrder(m_thenOperation.node);
+    m_comp->fgSetStmtSeq(m_thenOperation.stmt);
+
+    // Remove statements.
+    last->ReplaceWith(m_comp->gtNewNothingNode(), m_comp);
+    m_comp->gtSetEvalOrder(last);
+    m_comp->fgSetStmtSeq(m_startBlock->lastStmt());
+    if (m_doElseConversion)
+    {
+        m_elseOperation.node->ReplaceWith(m_comp->gtNewNothingNode(), m_comp);
+        m_comp->gtSetEvalOrder(m_elseOperation.node);
+        m_comp->fgSetStmtSeq(m_elseOperation.stmt);
+    }
+
+    // Merge all the blocks.
+    IfConvertJoinStmts(m_thenOperation.block);
+    if (m_doElseConversion)
+    {
+        IfConvertJoinStmts(m_elseOperation.block);
+    }
+
+    // Update the flow from the original block.
+    m_comp->fgRemoveAllRefPreds(m_startBlock->bbNext, m_startBlock);
+    m_startBlock->bbJumpKind = BBJ_ALWAYS;
+
+#ifdef DEBUG
+    if (m_comp->verbose)
+    {
+        JITDUMP("\nAfter if conversion\n");
+        IfConvertDump();
+    }
+#endif
+
+    return true;
+}
+
+//-----------------------------------------------------------------------------
+// optIfConversion: If conversion
+//
+// Returns:
+//   suitable phase status
+//
+PhaseStatus Compiler::optIfConversion()
+{
+    if (!opts.OptimizationEnabled())
+    {
+        return PhaseStatus::MODIFIED_NOTHING;
+    }
+
+#if defined(DEBUG)
+    if (JitConfig.JitDoIfConversion() == 0)
+    {
+        return PhaseStatus::MODIFIED_NOTHING;
+    }
+#endif
+
+    bool madeChanges = false;
+
+    // This phase does not repect SSA: assignments are deleted/moved.
+    assert(!fgDomsComputed);
+
+    // Reverse iterate through the blocks.
+    BasicBlock* block = fgLastBB;
+    while (block != nullptr)
+    {
+        OptIfConversionDsc optIfConversionDsc(this, block);
+        madeChanges |= optIfConversionDsc.optIfConvert();
+        block = block->bbPrev;
+    }
+
+    return madeChanges ? PhaseStatus::MODIFIED_EVERYTHING : PhaseStatus::MODIFIED_NOTHING;
+}
index 0e9e43a..fd8ddb1 100644 (file)
@@ -4594,370 +4594,6 @@ PhaseStatus Compiler::optUnrollLoops()
 #pragma warning(pop)
 #endif
 
-//-----------------------------------------------------------------------------
-// optIfConvert
-//
-// Find blocks representing simple if statements represented by conditional jumps
-// over another block. Try to replace the jumps by use of SELECT nodes.
-//
-// Arguments:
-//   block -- block that may represent the conditional jump in an if statement.
-//
-// Returns:
-//   true if any IR changes possibly made.
-//
-// Notes:
-//
-// Example of simple if conversion:
-//
-// This is optimising a simple if statement. There is a single condition being
-// tested, and a single assignment inside the body. There must be no else
-// statement. For example:
-// if (x < 7) { a = 5; }
-//
-// This is represented in IR by two basic blocks. The first block (block) ends with
-// a JTRUE statement which conditionally jumps to the second block (asgBlock).
-// The second block just contains a single assign statement. Both blocks then jump
-// to the same destination (finalBlock).  Note that the first block may contain
-// additional statements prior to the JTRUE statement.
-//
-// For example:
-//
-// ------------ BB03 [009..00D) -> BB05 (cond), preds={BB02} succs={BB04,BB05}
-// STMT00004
-//   *  JTRUE     void   $VN.Void
-//   \--*  GE        int    $102
-//      +--*  LCL_VAR   int    V02
-//      \--*  CNS_INT   int    7 $46
-//
-// ------------ BB04 [00D..010), preds={BB03} succs={BB05}
-// STMT00005
-//   *  ASG       int    $VN.Void
-// +--*  LCL_VAR   int    V00 arg0
-// \--*  CNS_INT   int    5 $47
-//
-//
-// This is optimised by conditionally executing the store and removing the conditional
-// jumps. First the JTRUE is replaced with a NOP. The assignment is updated so that
-// the source of the store is a SELECT node with the condition set to the inverse of
-// the original JTRUE condition. If the condition passes the original assign happens,
-// otherwise the existing source value is used.
-//
-// In the example above, local var 0 is set to 5 if the LT returns true, otherwise
-// the existing value of local var 0 is used:
-//
-// ------------ BB03 [009..00D) -> BB05 (always), preds={BB02} succs={BB05}
-// STMT00004
-//   *  NOP       void
-//
-// STMT00005
-//   *  ASG       int    $VN.Void
-//   +--*  LCL_VAR   int    V00 arg0
-//   \--*  SELECT    int
-//      +--*  LT        int    $102
-//      |  +--*  LCL_VAR   int    V02
-//      |  \--*  CNS_INT   int    7 $46
-//      +--*  CNS_INT   int    5 $47
-//      \--*  LCL_VAR   int    V00
-//
-// ------------ BB04 [00D..010), preds={} succs={BB05}
-//
-bool Compiler::optIfConvert(BasicBlock* block)
-{
-#ifndef TARGET_ARM64
-    return false;
-#else
-
-    // Don't optimise the block if it is inside a loop
-    // When inside a loop, branches are quicker than selects.
-    // Detect via the block weight as that will be high when inside a loop.
-    if ((block->getBBWeight(this) > BB_UNITY_WEIGHT) && !compStressCompile(STRESS_IF_CONVERSION_INNER_LOOPS, 25))
-    {
-        return false;
-    }
-
-    // Does the block end by branching via a JTRUE after a compare?
-    if (block->bbJumpKind != BBJ_COND || block->NumSucc() != 2)
-    {
-        return false;
-    }
-
-    // Verify the test block ends with a condition that we can manipulate.
-    GenTree* last = block->lastStmt()->GetRootNode();
-    noway_assert(last->OperIs(GT_JTRUE));
-    GenTree* cond = last->gtGetOp1();
-    if (!cond->OperIsCompare())
-    {
-        return false;
-    }
-
-    // Block where the flows merge.
-    BasicBlock* finalBlock = block->bbNext;
-    // The node, statement and block of the assignment.
-    GenTree*    asgNode  = nullptr;
-    Statement*  asgStmt  = nullptr;
-    BasicBlock* asgBlock = nullptr;
-
-    // Check the block is followed by a block or chain of blocks that only contain NOPs and
-    // a single ASG statement. The destination of the final block must point to the same as the
-    // true path of the JTRUE block.
-    bool foundMiddle = false;
-    while (!foundMiddle)
-    {
-        BasicBlock* middleBlock = finalBlock;
-        noway_assert(middleBlock != nullptr);
-
-        // middleBlock should have a single successor.
-        finalBlock = middleBlock->GetUniqueSucc();
-        if (finalBlock == nullptr)
-        {
-            return false;
-        }
-
-        if (finalBlock == block->bbJumpDest)
-        {
-            // This is our final middle block.
-            foundMiddle = true;
-        }
-
-        // Check that we have linear flow and are still in the same EH region
-
-        if (middleBlock->GetUniquePred(this) == nullptr)
-        {
-            return false;
-        }
-
-        if (!BasicBlock::sameEHRegion(middleBlock, block))
-        {
-            return false;
-        }
-
-        // Can all the nodes within the middle block be made to conditionally execute?
-        for (Statement* const stmt : middleBlock->Statements())
-        {
-            GenTree* tree = stmt->GetRootNode();
-            switch (tree->gtOper)
-            {
-                case GT_ASG:
-                {
-                    GenTree* op1 = tree->gtGetOp1();
-                    GenTree* op2 = tree->gtGetOp2();
-
-                    // Only one per assignment per block can be conditionally executed.
-                    if (asgNode != nullptr || op2->OperIs(GT_SELECT))
-                    {
-                        return false;
-                    }
-
-                    // Ensure the destination of the assign is a local variable with integer type.
-                    if (!op1->OperIs(GT_LCL_VAR) || !varTypeIsIntegralOrI(op1))
-                    {
-                        return false;
-                    }
-
-                    // Ensure the nodes of the assign won't cause any additional side effects.
-                    if ((op1->gtFlags & (GTF_SIDE_EFFECT | GTF_ORDER_SIDEEFF)) != 0 ||
-                        (op2->gtFlags & (GTF_SIDE_EFFECT | GTF_ORDER_SIDEEFF)) != 0)
-                    {
-                        return false;
-                    }
-
-                    // Ensure the source isn't a phi.
-                    if (op2->OperIs(GT_PHI))
-                    {
-                        return false;
-                    }
-
-                    asgNode  = tree;
-                    asgStmt  = stmt;
-                    asgBlock = middleBlock;
-                    break;
-                }
-
-                // These do not need conditional execution.
-                case GT_NOP:
-                    if (tree->gtGetOp1() != nullptr || (tree->gtFlags & (GTF_SIDE_EFFECT | GTF_ORDER_SIDEEFF)) != 0)
-                    {
-                        return false;
-                    }
-                    break;
-
-                // Cannot optimise this block.
-                default:
-                    return false;
-            }
-        }
-    }
-    if (asgNode == nullptr)
-    {
-        // The blocks checked didn't contain any ASG nodes.
-        return false;
-    }
-
-    // Evaluating op1/op2 unconditionally effectively has the same effect as
-    // reordering them with the condition (for example, the condition could be
-    // an explicit bounds check and the operand could read an array element).
-    // Disallow this except for some common cases that we know are always side
-    // effect free.
-    if (((cond->gtFlags & GTF_ORDER_SIDEEFF) != 0) && !asgNode->gtGetOp2()->IsInvariant() &&
-        !asgNode->gtGetOp2()->OperIsLocal())
-    {
-        return false;
-    }
-
-#ifdef DEBUG
-    if (verbose)
-    {
-        JITDUMP("\nConditionally executing " FMT_BB " inside " FMT_BB "\n", asgBlock->bbNum, block->bbNum);
-        fgDumpBlock(block);
-        for (BasicBlock* dumpBlock = block->bbNext; dumpBlock != finalBlock; dumpBlock = dumpBlock->GetUniqueSucc())
-        {
-            fgDumpBlock(dumpBlock);
-        }
-        JITDUMP("\n");
-    }
-#endif
-
-    // Using SELECT nodes means that full assignment is always evaluated.
-    // Put a limit on the original source and destination of the assignment.
-    if (!compStressCompile(STRESS_IF_CONVERSION_COST, 25))
-    {
-        int cost = asgNode->gtGetOp2()->GetCostEx() + (gtIsLikelyRegVar(asgNode->gtGetOp1()) ? 0 : 2);
-
-        // Cost to allow for "x = cond ? a + b : x".
-        if (cost > 7)
-        {
-            JITDUMP("Skipping if-conversion that will evaluate RHS unconditionally at cost %d", cost);
-            return false;
-        }
-    }
-
-    // Duplicate the destination of the assign.
-    // This will be used as the false result of the select node.
-    assert(asgNode->AsOp()->gtOp1->IsLocal());
-    GenTreeLclVarCommon* destination = asgNode->AsOp()->gtOp1->AsLclVarCommon();
-    GenTree*             falseInput  = gtCloneExpr(destination);
-    falseInput->gtFlags &= GTF_EMPTY;
-
-    // Create a new SSA entry for the false result.
-    if (destination->HasSsaName())
-    {
-        unsigned      lclNum            = destination->GetLclNum();
-        unsigned      destinationSsaNum = destination->GetSsaNum();
-        LclSsaVarDsc* destinationSsaDef = lvaGetDesc(lclNum)->GetPerSsaData(destinationSsaNum);
-
-        // Create a new SSA num.
-        unsigned newSsaNum = lvaGetDesc(lclNum)->lvPerSsaData.AllocSsaNum(getAllocator(CMK_SSA));
-        assert(newSsaNum != SsaConfig::RESERVED_SSA_NUM);
-        LclSsaVarDsc* newSsaDef = lvaGetDesc(lclNum)->GetPerSsaData(newSsaNum);
-
-        // Copy across the SSA data.
-        newSsaDef->SetBlock(destinationSsaDef->GetBlock());
-        newSsaDef->SetAssignment(destinationSsaDef->GetAssignment());
-        newSsaDef->m_vnPair = destinationSsaDef->m_vnPair;
-        falseInput->AsLclVarCommon()->SetSsaNum(newSsaNum);
-
-        if (newSsaDef->m_vnPair.BothDefined())
-        {
-            fgValueNumberSsaVarDef(falseInput->AsLclVarCommon());
-        }
-    }
-
-    // Invert the condition.
-    GenTree* revCond = gtReverseCond(cond);
-    assert(cond == revCond); // Ensure `gtReverseCond` did not create a new node.
-
-    // Create a select node.
-    GenTreeConditional* select =
-        gtNewConditionalNode(GT_SELECT, cond, asgNode->gtGetOp2(), falseInput, asgNode->TypeGet());
-
-    // Use the select as the source of the assignment.
-    asgNode->AsOp()->gtOp2 = select;
-    asgNode->AsOp()->gtFlags |= (select->gtFlags & GTF_ALL_EFFECT);
-    gtSetEvalOrder(asgNode);
-    fgSetStmtSeq(asgStmt);
-
-    // Remove the JTRUE statement.
-    last->ReplaceWith(gtNewNothingNode(), this);
-    gtSetEvalOrder(last);
-    fgSetStmtSeq(block->lastStmt());
-
-    // Before moving anything, fix up any SSAs in the asgBlock
-    for (Statement* const stmt : asgBlock->Statements())
-    {
-        for (GenTree* const node : stmt->TreeList())
-        {
-            if (node->IsLocal())
-            {
-                GenTreeLclVarCommon* lclVar = node->AsLclVarCommon();
-                unsigned             lclNum = lclVar->GetLclNum();
-                unsigned             ssaNum = lclVar->GetSsaNum();
-                if (ssaNum != SsaConfig::RESERVED_SSA_NUM)
-                {
-                    LclSsaVarDsc* ssaDef = lvaGetDesc(lclNum)->GetPerSsaData(ssaNum);
-                    if (ssaDef->GetBlock() == asgBlock)
-                    {
-                        JITDUMP("SSA def %d for V%02u moved from " FMT_BB " to " FMT_BB ".\n", ssaNum, lclNum,
-                                ssaDef->GetBlock()->bbNum, block->bbNum);
-                        ssaDef->SetBlock(block);
-                    }
-                }
-            }
-        }
-    }
-
-    // Move the Asg to the end of the original block
-    Statement* stmtList1 = block->firstStmt();
-    Statement* stmtList2 = asgBlock->firstStmt();
-    Statement* stmtLast1 = block->lastStmt();
-    Statement* stmtLast2 = asgBlock->lastStmt();
-    stmtLast1->SetNextStmt(stmtList2);
-    stmtList2->SetPrevStmt(stmtLast1);
-    stmtList1->SetPrevStmt(stmtLast2);
-    asgBlock->bbStmtList = nullptr;
-
-    // Update the flow from the original block.
-    fgRemoveAllRefPreds(block->bbNext, block);
-    block->bbJumpKind = BBJ_ALWAYS;
-
-#ifdef DEBUG
-    if (verbose)
-    {
-        JITDUMP("\nAfter if conversion\n");
-        fgDumpBlock(block);
-        for (BasicBlock* dumpBlock = block->bbNext; dumpBlock != finalBlock; dumpBlock = dumpBlock->GetUniqueSucc())
-        {
-            fgDumpBlock(dumpBlock);
-        }
-        JITDUMP("\n");
-    }
-#endif
-
-    return true;
-#endif
-}
-
-//-----------------------------------------------------------------------------
-// optIfConversion: If conversion
-//
-// Returns:
-//   suitable phase status
-//
-PhaseStatus Compiler::optIfConversion()
-{
-    bool madeChanges = false;
-
-    // Reverse iterate through the blocks.
-    BasicBlock* block = fgLastBB;
-    while (block != nullptr)
-    {
-        madeChanges |= optIfConvert(block);
-        block = block->bbPrev;
-    }
-
-    return madeChanges ? PhaseStatus::MODIFIED_EVERYTHING : PhaseStatus::MODIFIED_NOTHING;
-}
-
 /*****************************************************************************
  *
  *  Return false if there is a code path from 'topBB' to 'botBB' that might
index 8a35e76..9a83ee5 100644 (file)
@@ -177,6 +177,164 @@ public class FullRangeComparisonTest
         consume<double>(a1, a2);
     }
 
+    /* If/Else conditions that consume. */
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    public static void Ne_else_byte_consume(byte a1, byte a2)
+    {
+        //ARM64-FULL-LINE: cmp {{w[0-9]+}}, {{w[0-9]+}}
+        //ARM64-NEXT-FULL-LINE: csel {{w[0-9]+}}, {{w[0-9]+}}, {{w[0-9]+}}, ne
+        if (a1 != a2) { a1 = 10; } else { a1 = 100; }
+        consume<byte>(a1, a2);
+    }
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    public static void Lt_else_short_consume(short a1, short a2)
+    {
+        //ARM64-FULL-LINE: cmp {{w[0-9]+}}, {{w[0-9]+}}
+        //ARM64-NEXT-FULL-LINE: csel {{w[0-9]+}}, {{w[0-9]+}}, {{w[0-9]+}}, lt
+        if (a1 < a2) { a1 = 11; } else { a1 = 101; }
+        consume<short>(a1, a2);
+    }
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    public static void Le_else_int_consume(int a1, int a2)
+    {
+        //ARM64-FULL-LINE: cmp {{w[0-9]+}}, {{w[0-9]+}}
+        //ARM64-NEXT-FULL-LINE: csel {{w[0-9]+}}, {{w[0-9]+}}, {{w[0-9]+}}, le
+        if (a1 <= a2) { a1 = 12; } else { a1 = 102; }
+        consume<int>(a1, a2);
+    }
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    public static void Gt_else_long_consume(long a1, long a2)
+    {
+        //ARM64-FULL-LINE: cmp {{x[0-9]+}}, {{x[0-9]+}}
+        //ARM64-NEXT-FULL-LINE: csel {{x[0-9]+}}, {{x[0-9]+}}, {{x[0-9]+}}, gt
+        if (a1 > a2) { a1 = 13; } else { a1 = 103; }
+        consume<long>(a1, a2);
+    }
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    public static void Ge_else_ushort_consume(ushort a1, ushort a2)
+    {
+        //ARM64-FULL-LINE: cmp {{w[0-9]+}}, {{w[0-9]+}}
+        //ARM64-NEXT-FULL-LINE: csel {{w[0-9]+}}, {{w[0-9]+}}, {{w[0-9]+}}, ge
+        if (a1 >= a2) { a1 = 14; } else { a1 = 104; }
+        consume<ushort>(a1, a2);
+    }
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    public static void Eq_else_uint_consume(uint a1, uint a2)
+    {
+        //ARM64-FULL-LINE: cmp {{w[0-9]+}}, {{w[0-9]+}}
+        //ARM64-NEXT-FULL-LINE: csel {{w[0-9]+}}, {{w[0-9]+}}, {{w[0-9]+}}, eq
+        if (a1 == a2) { a1 = 15; } else { a1 = 105; }
+        consume<uint>(a1, a2);
+    }
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    public static void Ne_else_ulong_consume(ulong a1, ulong a2)
+    {
+        //ARM64-FULL-LINE: cmp {{x[0-9]+}}, {{x[0-9]+}}
+        //ARM64-NEXT-FULL-LINE: csel {{x[0-9]+}}, {{x[0-9]+}}, {{x[0-9]+}}, ne
+        if (a1 != a2) { a1 = 16; } else { a1 = 106; }
+        consume<ulong>(a1, a2);
+    }
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    public static void Lt_else_float_int_consume(float f1, float f2, int a1, int a2)
+    {
+        //ARM64-FULL-LINE: fcmp {{s[0-9]+}}, {{s[0-9]+}}
+        //ARM64-NEXT-FULL-LINE: csel {{w[0-9]+}}, {{w[0-9]+}}, {{w[0-9]+}}, lt
+        if (f1 < f2) { a1 = 17; } else { a1 = 107; }
+        consume<float>(a1, a2);
+    }
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    public static void Le_else_double_int_consume(double f1, double f2, int a1, int a2)
+    {
+        //ARM64-FULL-LINE: fcmp {{d[0-9]+}}, {{d[0-9]+}}
+        //ARM64-NEXT-FULL-LINE: csel {{w[0-9]+}}, {{w[0-9]+}}, {{w[0-9]+}}, le
+        if (f1 <= f2) { a1 = 18; } else { a1 = 108; }
+        consume<double>(a1, a2);
+    }
+
+    /* If/Else conditions that return. */
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    public static byte Lt_else_byte_return(byte a1, byte a2)
+    {
+        //ARM64-FULL-LINE: cmp {{w[0-9]+}}, {{w[0-9]+}}
+        //ARM64-NEXT-FULL-LINE: csel {{w[0-9]+}}, {{w[0-9]+}}, {{w[0-9]+}}, lt
+        return (a1 < a2) ? (byte)10 : (byte)100;
+    }
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    public static short Le_else_short_return(short a1, short a2)
+    {
+        //ARM64-FULL-LINE: cmp {{w[0-9]+}}, {{w[0-9]+}}
+        //ARM64-NEXT-FULL-LINE: csel {{w[0-9]+}}, {{w[0-9]+}}, {{w[0-9]+}}, le
+        return (a1 <= a2) ? (short)11 : (short)101;
+    }
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    public static int Gt_else_int_return(int a1, int a2)
+    {
+        //ARM64-FULL-LINE: cmp {{w[0-9]+}}, {{w[0-9]+}}
+        //ARM64-NEXT-FULL-LINE: csel {{w[0-9]+}}, {{w[0-9]+}}, {{w[0-9]+}}, gt
+        return (a1 > a2) ? (int)12 : (int)102;
+    }
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    public static long Ge_else_long_return(long a1, long a2)
+    {
+        //ARM64-FULL-LINE: cmp {{x[0-9]+}}, {{x[0-9]+}}
+        //ARM64-NEXT-FULL-LINE: csel {{x[0-9]+}}, {{x[0-9]+}}, {{x[0-9]+}}, ge
+        return (a1 >= a2) ? (long)13 : (long)103;
+    }
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    public static ushort Eq_else_ushort_return(ushort a1, ushort a2)
+    {
+        //ARM64-FULL-LINE: cmp {{w[0-9]+}}, {{w[0-9]+}}
+        //ARM64-NEXT-FULL-LINE: csel {{w[0-9]+}}, {{w[0-9]+}}, {{w[0-9]+}}, eq
+        return (a1 == a2) ? (ushort)14 : (ushort)104;
+    }
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    public static uint Ne_else_uint_return(uint a1, uint a2)
+    {
+        //ARM64-FULL-LINE: cmp {{w[0-9]+}}, {{w[0-9]+}}
+        //ARM64-NEXT-FULL-LINE: csel {{w[0-9]+}}, {{w[0-9]+}}, {{w[0-9]+}}, ne
+        return (a1 != a2) ? (uint)15 : (uint)105;
+    }
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    public static ulong Lt_else_ulong_return(ulong a1, ulong a2)
+    {
+        //ARM64-FULL-LINE: cmp {{x[0-9]+}}, {{x[0-9]+}}
+        //ARM64-NEXT-FULL-LINE: csel {{x[0-9]+}}, {{x[0-9]+}}, {{x[0-9]+}}, lt
+        return (a1 < a2) ? (ulong)16 : (ulong)106;
+    }
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    public static int Le_else_float_int_return(float a1, float a2)
+    {
+        //ARM64-FULL-LINE: fcmp {{s[0-9]+}}, {{s[0-9]+}}
+        //ARM64-NEXT-FULL-LINE: csel {{w[0-9]+}}, {{w[0-9]+}}, {{w[0-9]+}}, le
+        return (a1 <= a2) ? 17 : 107;
+    }
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    public static int Gt_else_double_int_return(double a1, double a2)
+    {
+        //ARM64-FULL-LINE: fcmp {{d[0-9]+}}, {{d[0-9]+}}
+        //ARM64-NEXT-FULL-LINE: csel {{w[0-9]+}}, {{w[0-9]+}}, {{w[0-9]+}}, gt
+        return (a1 > a2) ? 18 : 108;
+    }
+
+
     public static int Main()
     {
         // Optimize comparison with full range values
@@ -315,6 +473,62 @@ public class FullRangeComparisonTest
         Eq_double_long_consume(10.1, 11.1, 12, 13);
         Ne_double_int_consume(10.1, 11.1, 12, 13);
 
+        Ne_else_byte_consume(20, 21);
+        Lt_else_short_consume(10, 11);
+        Le_else_int_consume(10, 11);
+        Gt_else_long_consume(10, 11);
+        Ge_else_ushort_consume(10, 11);
+        Eq_else_uint_consume(10, 11);
+        Ne_else_ulong_consume(10, 11);
+        Lt_else_float_int_consume(10.1F, 11.1F, 12, 13);
+        Le_else_double_int_consume(10.1, 11.1, 12, 13);
+
+        if (Lt_else_byte_return(10,11) != 10)
+        {
+            Console.WriteLine("FullRangeComparisonTest:Lt_else_byte_return() failed");
+            return 101;
+        }
+        if (Le_else_short_return(10, 11) != 11)
+        {
+            Console.WriteLine("FullRangeComparisonTest:Le_else_short_return() failed");
+            return 101;
+        }
+        if (Gt_else_int_return(10, 11) != 102)
+        {
+            Console.WriteLine("FullRangeComparisonTest:Gt_else_int_return() failed");
+            return 101;
+        }
+        if (Ge_else_long_return(10, 11) != 103)
+        {
+            Console.WriteLine("FullRangeComparisonTest:Ge_else_long_return() failed");
+            return 101;
+        }
+        if (Eq_else_ushort_return(10, 11) != 104)
+        {
+            Console.WriteLine("FullRangeComparisonTest:Eq_else_ushort_return() failed");
+            return 101;
+        }
+        if (Ne_else_uint_return(10, 11) != 15)
+        {
+            Console.WriteLine("FullRangeComparisonTest:Ne_else_uint_return() failed");
+            return 101;
+        }
+        if (Lt_else_ulong_return(10, 11) != 16)
+        {
+            Console.WriteLine("FullRangeComparisonTest:Lt_else_ulong_return() failed");
+            return 101;
+        }
+        if (Le_else_float_int_return(10.1F, 11.1F) != 17)
+        {
+            Console.WriteLine("FullRangeComparisonTest:Le_else_float_int_return() failed");
+            return 101;
+        }
+        if (Gt_else_double_int_return(10.1, 11.1) != 108)
+        {
+            Console.WriteLine("FullRangeComparisonTest:Gt_else_double_int_return() failed");
+            return 101;
+        }
+
         Console.WriteLine("PASSED");
         return 100;
     }