hashbv.cpp
hwintrinsic.cpp
hostallocator.cpp
+ ifconversion.cpp
indirectcalltransformer.cpp
importercalls.cpp
importer.cpp
bool doCse = true;
bool doAssertionProp = true;
bool doRangeAnalysis = true;
- bool doIfConversion = true;
bool doVNBasedDeadStoreRemoval = true;
int iterations = 1;
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)
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
//
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);
--- /dev/null
+// 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;
+}
#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
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
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;
}