From 53568571ac9a32608136aac54fafcf580ee47fa2 Mon Sep 17 00:00:00 2001 From: Alan Hayward Date: Wed, 30 Nov 2022 10:53:28 +0000 Subject: [PATCH] Add simple Else conditions to If Conversion (#77728) * 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 | 1 + src/coreclr/jit/compiler.cpp | 13 +- src/coreclr/jit/ifconversion.cpp | 779 +++++++++++++++++++++++++++++++++ src/coreclr/jit/optimizer.cpp | 364 --------------- src/tests/JIT/opt/Compares/compares.cs | 214 +++++++++ 5 files changed, 998 insertions(+), 373 deletions(-) create mode 100644 src/coreclr/jit/ifconversion.cpp diff --git a/src/coreclr/jit/CMakeLists.txt b/src/coreclr/jit/CMakeLists.txt index e9cee4a..4e3640c 100644 --- a/src/coreclr/jit/CMakeLists.txt +++ b/src/coreclr/jit/CMakeLists.txt @@ -124,6 +124,7 @@ set( JIT_SOURCES hashbv.cpp hwintrinsic.cpp hostallocator.cpp + ifconversion.cpp indirectcalltransformer.cpp importercalls.cpp importer.cpp diff --git a/src/coreclr/jit/compiler.cpp b/src/coreclr/jit/compiler.cpp index 5ee38b3..6848341 100644 --- a/src/coreclr/jit/compiler.cpp +++ b/src/coreclr/jit/compiler.cpp @@ -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 index 0000000..5b47a9b --- /dev/null +++ b/src/coreclr/jit/ifconversion.cpp @@ -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; +} diff --git a/src/coreclr/jit/optimizer.cpp b/src/coreclr/jit/optimizer.cpp index 0e9e43a..fd8ddb1 100644 --- a/src/coreclr/jit/optimizer.cpp +++ b/src/coreclr/jit/optimizer.cpp @@ -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 diff --git a/src/tests/JIT/opt/Compares/compares.cs b/src/tests/JIT/opt/Compares/compares.cs index 8a35e76..9a83ee5 100644 --- a/src/tests/JIT/opt/Compares/compares.cs +++ b/src/tests/JIT/opt/Compares/compares.cs @@ -177,6 +177,164 @@ public class FullRangeComparisonTest consume(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(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(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(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(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(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(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(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(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(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; } -- 2.7.4