From 33be29e9787e033267e78693f62ba3a508be2139 Mon Sep 17 00:00:00 2001 From: Alan Hayward Date: Tue, 1 Nov 2022 12:40:41 +0000 Subject: [PATCH] Arm64: Add If conversion pass (#73472) * Arm64: Add If conversion pass * Minor review fixups * Return a PhaseStatus * Fix formatting * Check for side effects on NOPs * Add function block comments for the phase * Remove creation of AND chains from if conversion pass * Update middleBlock flow * Check for order side effects * Remove COLON_COND check * Remove flag toggling * Move the conditional assignment to the JTRUE block * Fix formatting * Allow conditions with side effects * Fix formatting * Correct all moved SSA statements * Add size costing check * Only move middle block ssa defs * Fix formatting * Fewer SSA assumptions * Use implicit func for value numbering * Update header for gtFoldExprConditional * Cost based on speed * Add Stress mode for inner loops * Rework costings * Check for invalid VNs * Ignore compares against zero * Ensure float compares are contained * Allow if conversion of test compares * Do not contain test compares within compare chains * Add float versions of the JIT/opt/Compares tests * Fix formatting * Compare chains use CmpCompares, selects use Compares * Fix flow checking for empty blocks * Fix to contexts setting JitStdOutFile * Fix attr and reg producing in select generation --- src/coreclr/jit/codegenarm64.cpp | 26 +- src/coreclr/jit/codegenlinear.cpp | 5 +- src/coreclr/jit/compiler.cpp | 9 + src/coreclr/jit/compiler.h | 12 + src/coreclr/jit/compphases.h | 1 + src/coreclr/jit/gentree.cpp | 130 +++++++ src/coreclr/jit/jitconfigvalues.h | 1 + src/coreclr/jit/lowerarmarch.cpp | 34 +- src/coreclr/jit/lsrabuild.cpp | 5 +- src/coreclr/jit/morph.cpp | 16 + src/coreclr/jit/optimizer.cpp | 346 ++++++++++++++++++ src/coreclr/jit/valuenum.cpp | 96 +++-- .../JIT/opt/Compares/compareAnd2Chains.cs | 98 ++++- .../JIT/opt/Compares/compareAnd3Chains.cs | 96 +++++ .../JIT/opt/Compares/compareAndTestChains.cs | 96 +++++ 15 files changed, 923 insertions(+), 48 deletions(-) diff --git a/src/coreclr/jit/codegenarm64.cpp b/src/coreclr/jit/codegenarm64.cpp index 7737953d21240..4032cc944957a 100644 --- a/src/coreclr/jit/codegenarm64.cpp +++ b/src/coreclr/jit/codegenarm64.cpp @@ -4613,6 +4613,9 @@ void CodeGen::genCodeForConditionalCompare(GenTreeOp* tree, GenCondition prevCon // Should only be called on contained nodes. assert(targetReg == REG_NA); + // Should not be called for test conditionals (Arm64 does not have a ctst). + assert(tree->OperIsCmpCompare()); + // For the ccmp flags, invert the condition of the compare. insCflags cflags = InsCflagsForCcmp(GenCondition::FromRelop(tree)); @@ -4711,7 +4714,7 @@ void CodeGen::genCodeForSelect(GenTreeConditional* tree) GenTree* op2 = tree->gtOp2; var_types op1Type = genActualType(op1->TypeGet()); var_types op2Type = genActualType(op2->TypeGet()); - emitAttr cmpSize = EA_ATTR(genTypeSize(op1Type)); + emitAttr attr = emitActualTypeSize(tree->TypeGet()); assert(!op1->isUsedFromMemory()); assert(genTypeSize(op1Type) == genTypeSize(op2Type)); @@ -4721,10 +4724,20 @@ void CodeGen::genCodeForSelect(GenTreeConditional* tree) if (opcond->isContained()) { // Generate the contained condition. - bool chain = false; - JITDUMP("Generating compare chain:\n"); - genCodeForContainedCompareChain(opcond, &chain, &prevCond); - assert(chain); + if (opcond->OperIsCompare()) + { + genCodeForCompare(opcond->AsOp()); + prevCond = GenCondition::FromRelop(opcond); + } + else + { + // Condition is a compare chain. Try to contain it. + assert(opcond->OperIs(GT_AND)); + bool chain = false; + JITDUMP("Generating compare chain:\n"); + genCodeForContainedCompareChain(opcond, &chain, &prevCond); + assert(chain); + } } else { @@ -4738,8 +4751,9 @@ void CodeGen::genCodeForSelect(GenTreeConditional* tree) regNumber srcReg2 = genConsumeReg(op2); const GenConditionDesc& prevDesc = GenConditionDesc::Get(prevCond); - emit->emitIns_R_R_R_COND(INS_csel, cmpSize, targetReg, srcReg1, srcReg2, JumpKindToInsCond(prevDesc.jumpKind1)); + emit->emitIns_R_R_R_COND(INS_csel, attr, targetReg, srcReg1, srcReg2, JumpKindToInsCond(prevDesc.jumpKind1)); regSet.verifyRegUsed(targetReg); + genProduceReg(tree); } //------------------------------------------------------------------------ diff --git a/src/coreclr/jit/codegenlinear.cpp b/src/coreclr/jit/codegenlinear.cpp index 089e12d1cf28f..b2c29c24c7939 100644 --- a/src/coreclr/jit/codegenlinear.cpp +++ b/src/coreclr/jit/codegenlinear.cpp @@ -1634,9 +1634,10 @@ void CodeGen::genConsumeRegs(GenTree* tree) assert(cast->isContained()); genConsumeAddress(cast->CastOp()); } - else if (tree->OperIsCmpCompare() || tree->OperIs(GT_AND)) + else if (tree->OperIsCompare() || tree->OperIs(GT_AND)) { - // Compares and ANDs may be contained in a conditional chain. + // Compares can be contained by a SELECT. + // ANDs and Cmp Compares may be contained in a chain. genConsumeRegs(tree->gtGetOp1()); genConsumeRegs(tree->gtGetOp2()); } diff --git a/src/coreclr/jit/compiler.cpp b/src/coreclr/jit/compiler.cpp index 132153dba8fae..63f56abd4245c 100644 --- a/src/coreclr/jit/compiler.cpp +++ b/src/coreclr/jit/compiler.cpp @@ -4741,6 +4741,7 @@ void Compiler::compCompile(void** methodCodePtr, uint32_t* methodCodeSize, JitFl bool doCse = true; bool doAssertionProp = true; bool doRangeAnalysis = true; + bool doIfConversion = true; int iterations = 1; #if defined(OPT_CONFIG) @@ -4753,6 +4754,7 @@ 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); if (opts.optRepeat) { @@ -4834,6 +4836,13 @@ 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 diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index eded6bac15450..eef9a7a01f928 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -2392,6 +2392,9 @@ class Compiler GenTreeLclVar* gtNewLclVarAddrNode(unsigned lclNum, var_types type = TYP_I_IMPL); GenTreeLclFld* gtNewLclFldAddrNode(unsigned lclNum, unsigned lclOffs, var_types type = TYP_I_IMPL); + GenTreeConditional* gtNewConditionalNode( + genTreeOps oper, GenTree* cond, GenTree* op1, GenTree* op2, var_types type); + #ifdef FEATURE_SIMD GenTreeSIMD* gtNewSIMDNode( var_types type, GenTree* op1, SIMDIntrinsicID simdIntrinsicID, CorInfoType simdBaseJitType, unsigned simdSize); @@ -2831,6 +2834,7 @@ class Compiler GenTree* gtFoldExprSpecial(GenTree* tree); GenTree* gtFoldBoxNullable(GenTree* tree); GenTree* gtFoldExprCompare(GenTree* tree); + GenTree* gtFoldExprConditional(GenTree* tree); GenTree* gtCreateHandleCompare(genTreeOps oper, GenTree* op1, GenTree* op2, @@ -4869,6 +4873,9 @@ class Compiler // Does value-numbering for a block assignment. void fgValueNumberBlockAssignment(GenTree* tree); + // Does value-numbering for a variable definition that has SSA. + void fgValueNumberSsaVarDef(GenTreeLclVarCommon* lcl); + // Does value-numbering for a cast tree. void fgValueNumberCastTree(GenTree* tree); @@ -6082,6 +6089,7 @@ class Compiler void optEnsureUniqueHead(unsigned loopInd, weight_t ambientWeight); PhaseStatus optUnrollLoops(); // Unrolls loops (needs to have cost info) void optRemoveRedundantZeroInits(); + PhaseStatus optIfConversion(); // If conversion protected: // This enumeration describes what is killed by a call. @@ -6470,6 +6478,7 @@ class Compiler OptInvertCountTreeInfoType optInvertCountTreeInfo(GenTree* tree); bool optInvertWhileLoop(BasicBlock* block); + bool optIfConvert(BasicBlock* block); private: static bool optIterSmallOverflow(int iterAtExit, var_types incrType); @@ -7378,6 +7387,7 @@ class Compiler GenTree* optAssertionProp_Cast(ASSERT_VALARG_TP assertions, GenTreeCast* cast, Statement* stmt); GenTree* optAssertionProp_Call(ASSERT_VALARG_TP assertions, GenTreeCall* call, Statement* stmt); GenTree* optAssertionProp_RelOp(ASSERT_VALARG_TP assertions, GenTree* tree, Statement* stmt); + GenTree* optAssertionProp_ConditionalOp(ASSERT_VALARG_TP assertions, GenTree* tree, Statement* stmt); GenTree* optAssertionProp_Comma(ASSERT_VALARG_TP assertions, GenTree* tree, Statement* stmt); GenTree* optAssertionProp_BndsChk(ASSERT_VALARG_TP assertions, GenTree* tree, Statement* stmt); GenTree* optAssertionPropGlobal_RelOp(ASSERT_VALARG_TP assertions, GenTree* tree, Statement* stmt); @@ -9576,6 +9586,8 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX STRESS_MODE(EMITTER) \ STRESS_MODE(CHK_REIMPORT) \ STRESS_MODE(GENERIC_CHECK) \ + STRESS_MODE(IF_CONVERSION_COST) \ + STRESS_MODE(IF_CONVERSION_INNER_LOOPS) \ STRESS_MODE(COUNT) enum compStressArea diff --git a/src/coreclr/jit/compphases.h b/src/coreclr/jit/compphases.h index a0e3a2b076335..fd040f3acd03e 100644 --- a/src/coreclr/jit/compphases.h +++ b/src/coreclr/jit/compphases.h @@ -87,6 +87,7 @@ CompPhaseNameMacro(PHASE_OPTIMIZE_VALNUM_CSES, "Optimize Valnum CSEs", CompPhaseNameMacro(PHASE_VN_COPY_PROP, "VN based copy prop", false, -1, false) CompPhaseNameMacro(PHASE_OPTIMIZE_BRANCHES, "Redundant branch opts", false, -1, false) CompPhaseNameMacro(PHASE_ASSERTION_PROP_MAIN, "Assertion prop", false, -1, false) +CompPhaseNameMacro(PHASE_IF_CONVERSION, "If conversion", false, -1, false) CompPhaseNameMacro(PHASE_OPT_UPDATE_FLOW_GRAPH, "Update flow graph opt pass", false, -1, false) CompPhaseNameMacro(PHASE_COMPUTE_EDGE_WEIGHTS2, "Compute edge weights (2, false)",false, -1, false) CompPhaseNameMacro(PHASE_INSERT_GC_POLLS, "Insert GC Polls", false, -1, true) diff --git a/src/coreclr/jit/gentree.cpp b/src/coreclr/jit/gentree.cpp index aaff3916e0fdf..02b70564da058 100644 --- a/src/coreclr/jit/gentree.cpp +++ b/src/coreclr/jit/gentree.cpp @@ -5847,6 +5847,9 @@ unsigned Compiler::gtSetEvalOrder(GenTree* tree) level = max(level, lvl2); costEx += tree->AsConditional()->gtOp2->GetCostEx(); costSz += tree->AsConditional()->gtOp2->GetCostSz(); + + costEx += 1; + costSz += 1; break; default: @@ -7387,6 +7390,17 @@ GenTreeLclFld* Compiler::gtNewLclFldAddrNode(unsigned lclNum, unsigned lclOffs, return node; } +GenTreeConditional* Compiler::gtNewConditionalNode( + genTreeOps oper, GenTree* cond, GenTree* op1, GenTree* op2, var_types type) +{ + assert(GenTree::OperIsConditional(oper)); + GenTreeConditional* node = new (this, oper) GenTreeConditional(oper, type, cond, op1, op2); + node->gtFlags |= (cond->gtFlags & GTF_ALL_EFFECT); + node->gtFlags |= (op1->gtFlags & GTF_ALL_EFFECT); + node->gtFlags |= (op2->gtFlags & GTF_ALL_EFFECT); + return node; +} + GenTreeLclFld* Compiler::gtNewLclFldNode(unsigned lnum, var_types type, unsigned offset) { GenTreeLclFld* node = new (this, GT_LCL_FLD) GenTreeLclFld(GT_LCL_FLD, type, lnum, offset); @@ -12643,6 +12657,10 @@ GenTree* Compiler::gtFoldExpr(GenTree* tree) if (!(kind & GTK_SMPOP)) { + if (tree->OperIsConditional()) + { + return gtFoldExprConditional(tree); + } return tree; } @@ -12911,6 +12929,118 @@ GenTree* Compiler::gtFoldExprCompare(GenTree* tree) return cons; } +//------------------------------------------------------------------------ +// gtFoldExprConditional: see if a conditional is foldable +// +// Arguments: +// tree - condition to examine +// +// Returns: +// The original call if no folding happened. +// An alternative tree if folding happens. +// +// Notes: +// Supporting foldings are: +// SELECT TRUE X Y -> X +// SELECT FALSE X Y -> Y +// SELECT COND X X -> X +// +GenTree* Compiler::gtFoldExprConditional(GenTree* tree) +{ + GenTree* cond = tree->AsConditional()->gtCond; + GenTree* op1 = tree->AsConditional()->gtOp1; + GenTree* op2 = tree->AsConditional()->gtOp2; + + assert(tree->OperIsConditional()); + + // Check for a constant conditional + if (cond->OperIsConst()) + { + // Constant conditions must be folded away. + + JITDUMP("\nFolding conditional op with constant condition:\n"); + DISPTREE(tree); + + assert(cond->TypeIs(TYP_INT)); + assert((tree->gtFlags & GTF_SIDE_EFFECT & ~GTF_ASG) == 0); + assert((tree->gtFlags & GTF_ORDER_SIDEEFF) == 0); + + GenTree* replacement = nullptr; + if (cond->IsIntegralConst(0)) + { + JITDUMP("Bashed to false path:\n"); + replacement = op2; + } + else + { + // Condition should never be a constant other than 0 or 1 + assert(cond->IsIntegralConst(1)); + JITDUMP("Bashed to true path:\n"); + replacement = op1; + } + + if (fgGlobalMorph) + { + fgMorphTreeDone(replacement); + } + else + { + replacement->gtNext = tree->gtNext; + replacement->gtPrev = tree->gtPrev; + } + DISPTREE(replacement); + JITDUMP("\n"); + + // If we bashed to a compare, try to fold that. + if (replacement->OperIsCompare()) + { + return gtFoldExprCompare(replacement); + } + + return replacement; + } + + assert(cond->OperIsCompare()); + + if (((tree->gtFlags & GTF_SIDE_EFFECT) != 0) || !GenTree::Compare(op1, op2, true)) + { + // No folding. + return tree; + } + + // GTF_ORDER_SIDEEFF here may indicate volatile subtrees. + // Or it may indicate a non-null assertion prop into an indir subtree. + if ((tree->gtFlags & GTF_ORDER_SIDEEFF) != 0) + { + // If op1 is "volatile" and op2 is not, we can still fold. + const bool op1MayBeVolatile = (op1->gtFlags & GTF_ORDER_SIDEEFF) != 0; + const bool op2MayBeVolatile = (op2->gtFlags & GTF_ORDER_SIDEEFF) != 0; + + if (!op1MayBeVolatile || op2MayBeVolatile) + { + // No folding. + return tree; + } + } + + JITDUMP("Bashed to first of two identical paths:\n"); + GenTree* replacement = op1; + + if (fgGlobalMorph) + { + fgMorphTreeDone(replacement); + } + else + { + replacement->gtNext = tree->gtNext; + replacement->gtPrev = tree->gtPrev; + } + DISPTREE(replacement); + JITDUMP("\n"); + + return replacement; +} + //------------------------------------------------------------------------ // gtCreateHandleCompare: generate a type handle comparison // diff --git a/src/coreclr/jit/jitconfigvalues.h b/src/coreclr/jit/jitconfigvalues.h index faf634baa24fe..eea4ae64e9b64 100644 --- a/src/coreclr/jit/jitconfigvalues.h +++ b/src/coreclr/jit/jitconfigvalues.h @@ -426,6 +426,7 @@ CONFIG_INTEGER(JitDoValueNumber, W("JitDoValueNumber"), 1) // Perform value numb CONFIG_METHODSET(JitOptRepeat, W("JitOptRepeat")) // Runs optimizer multiple times on the method CONFIG_INTEGER(JitOptRepeatCount, W("JitOptRepeatCount"), 2) // Number of times to repeat opts when repeating +CONFIG_INTEGER(JitDoIfConversion, W("JitDoIfConversion"), 1) // Perform If conversion #endif // defined(OPT_CONFIG) CONFIG_INTEGER(JitTelemetry, W("JitTelemetry"), 1) // If non-zero, gather JIT telemetry data diff --git a/src/coreclr/jit/lowerarmarch.cpp b/src/coreclr/jit/lowerarmarch.cpp index 206ca70794269..1401de10b4705 100644 --- a/src/coreclr/jit/lowerarmarch.cpp +++ b/src/coreclr/jit/lowerarmarch.cpp @@ -2327,8 +2327,7 @@ bool Lowering::ContainCheckCompareChain(GenTree* child, GenTree* parent, GenTree child->SetContained(); return true; } - else if (child->OperIsCmpCompare() && varTypeIsIntegral(child->gtGetOp1()) && - varTypeIsIntegral(child->gtGetOp2())) + else if (child->OperIsCmpCompare()) { child->AsOp()->SetContained(); @@ -2422,17 +2421,28 @@ void Lowering::ContainCheckSelect(GenTreeConditional* node) return; } - // Check if the compare does not need to be generated into a register. - GenTree* startOfChain = nullptr; - ContainCheckCompareChain(node->gtCond, node, &startOfChain); - - if (startOfChain != nullptr) + if (node->gtCond->OperIsCompare()) + { + // All compare node types (including TEST_) are containable. + if (IsSafeToContainMem(node, node->gtCond)) + { + node->gtCond->AsOp()->SetContained(); + } + } + else { - // The earliest node in the chain will be generated as a standard compare. - assert(startOfChain->OperIsCmpCompare()); - startOfChain->AsOp()->gtGetOp1()->ClearContained(); - startOfChain->AsOp()->gtGetOp2()->ClearContained(); - ContainCheckCompare(startOfChain->AsOp()); + // Check for a compare chain and try to contain it. + GenTree* startOfChain = nullptr; + ContainCheckCompareChain(node->gtCond, node, &startOfChain); + + if (startOfChain != nullptr) + { + // The earliest node in the chain will be generated as a standard compare. + assert(startOfChain->OperIsCmpCompare()); + startOfChain->AsOp()->gtGetOp1()->ClearContained(); + startOfChain->AsOp()->gtGetOp2()->ClearContained(); + ContainCheckCompare(startOfChain->AsOp()); + } } } diff --git a/src/coreclr/jit/lsrabuild.cpp b/src/coreclr/jit/lsrabuild.cpp index 02e92dacce7d6..d5510858a2b9f 100644 --- a/src/coreclr/jit/lsrabuild.cpp +++ b/src/coreclr/jit/lsrabuild.cpp @@ -3226,10 +3226,11 @@ int LinearScan::BuildOperandUses(GenTree* node, regMaskTP candidates) } #endif // FEATURE_HW_INTRINSICS #ifdef TARGET_ARM64 - if (node->OperIs(GT_MUL) || node->OperIsCmpCompare() || node->OperIs(GT_AND)) + if (node->OperIs(GT_MUL) || node->OperIsCompare() || node->OperIs(GT_AND)) { // MUL can be contained for madd or msub on arm64. - // Compare and AND may be contained due to If Conversion. + // Compares can be contained by a SELECT. + // ANDs and Cmp Compares may be contained in a chain. return BuildBinaryUses(node->AsOp(), candidates); } if (node->OperIs(GT_NEG, GT_CAST, GT_LSH, GT_RSH, GT_RSZ)) diff --git a/src/coreclr/jit/morph.cpp b/src/coreclr/jit/morph.cpp index ecf70ebf5d262..18ba00e9add32 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -13893,6 +13893,22 @@ GenTree* Compiler::fgMorphTree(GenTree* tree, MorphAddrContext* mac) tree = fgMorphStoreDynBlock(tree->AsStoreDynBlk()); break; + case GT_SELECT: + tree->AsConditional()->gtCond = fgMorphTree(tree->AsConditional()->gtCond); + tree->AsConditional()->gtOp1 = fgMorphTree(tree->AsConditional()->gtOp1); + tree->AsConditional()->gtOp2 = fgMorphTree(tree->AsConditional()->gtOp2); + + tree->gtFlags &= (~GTF_EXCEPT & ~GTF_CALL); + + tree->gtFlags |= tree->AsConditional()->gtCond->gtFlags & GTF_ALL_EFFECT; + tree->gtFlags |= tree->AsConditional()->gtOp1->gtFlags & GTF_ALL_EFFECT; + tree->gtFlags |= tree->AsConditional()->gtOp2->gtFlags & GTF_ALL_EFFECT; + + // Try to fold away any constants etc. + tree = gtFoldExpr(tree); + + break; + default: #ifdef DEBUG gtDispTree(tree); diff --git a/src/coreclr/jit/optimizer.cpp b/src/coreclr/jit/optimizer.cpp index db9981d36f7a9..b73a410f7a8d4 100644 --- a/src/coreclr/jit/optimizer.cpp +++ b/src/coreclr/jit/optimizer.cpp @@ -4594,6 +4594,352 @@ 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; + } + + 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; + } + +#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. + cond->gtOper = GenTree::ReverseRelop(cond->gtOper); + + // 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/coreclr/jit/valuenum.cpp b/src/coreclr/jit/valuenum.cpp index 11ffa8951c7ea..283fb5f92f9df 100644 --- a/src/coreclr/jit/valuenum.cpp +++ b/src/coreclr/jit/valuenum.cpp @@ -8530,6 +8530,45 @@ void Compiler::fgValueNumberBlockAssignment(GenTree* tree) tree->gtVNPair = vnStore->VNPWithExc(vnStore->VNPForVoid(), vnpExcSet); } +//------------------------------------------------------------------------ +// fgValueNumberSsaVarDef: Perform value numbering for a variable definition that has SSA +// +// Arguments: +// lcl - the variable definition. +// +void Compiler::fgValueNumberSsaVarDef(GenTreeLclVarCommon* lcl) +{ + unsigned lclNum = lcl->GetLclNum(); + LclVarDsc* varDsc = lvaGetDesc(lclNum); + + assert((lcl->gtFlags & GTF_VAR_DEF) == 0); + assert(lcl->HasSsaName()); + assert(lvaInSsa(lclNum)); + + ValueNumPair wholeLclVarVNP = varDsc->GetPerSsaData(lcl->GetSsaNum())->m_vnPair; + assert(wholeLclVarVNP.BothDefined()); + + // Account for type mismatches. + if (genActualType(varDsc) != genActualType(lcl)) + { + if (genTypeSize(varDsc) != genTypeSize(lcl)) + { + assert((varDsc->TypeGet() == TYP_LONG) && lcl->TypeIs(TYP_INT)); + lcl->gtVNPair = vnStore->VNPairForCast(wholeLclVarVNP, lcl->TypeGet(), varDsc->TypeGet()); + } + else + { + assert(((varDsc->TypeGet() == TYP_I_IMPL) && lcl->TypeIs(TYP_BYREF)) || + ((varDsc->TypeGet() == TYP_BYREF) && lcl->TypeIs(TYP_I_IMPL))); + lcl->gtVNPair = wholeLclVarVNP; + } + } + else + { + lcl->gtVNPair = wholeLclVarVNP; + } +} + void Compiler::fgValueNumberTree(GenTree* tree) { genTreeOps oper = tree->OperGet(); @@ -8566,31 +8605,7 @@ void Compiler::fgValueNumberTree(GenTree* tree) { if (lcl->HasSsaName()) { - assert(lvaInSsa(lclNum)); - - ValueNumPair wholeLclVarVNP = varDsc->GetPerSsaData(lcl->GetSsaNum())->m_vnPair; - assert(wholeLclVarVNP.BothDefined()); - - // Account for type mismatches. - if (genActualType(varDsc) != genActualType(lcl)) - { - if (genTypeSize(varDsc) != genTypeSize(lcl)) - { - assert((varDsc->TypeGet() == TYP_LONG) && lcl->TypeIs(TYP_INT)); - lcl->gtVNPair = - vnStore->VNPairForCast(wholeLclVarVNP, lcl->TypeGet(), varDsc->TypeGet()); - } - else - { - assert(((varDsc->TypeGet() == TYP_I_IMPL) && lcl->TypeIs(TYP_BYREF)) || - ((varDsc->TypeGet() == TYP_BYREF) && lcl->TypeIs(TYP_I_IMPL))); - lcl->gtVNPair = wholeLclVarVNP; - } - } - else - { - lcl->gtVNPair = wholeLclVarVNP; - } + fgValueNumberSsaVarDef(lcl); } else if (varDsc->IsAddressExposed()) { @@ -9212,6 +9227,37 @@ void Compiler::fgValueNumberTree(GenTree* tree) } break; + case GT_SELECT: + { + GenTreeConditional* const conditional = tree->AsConditional(); + + ValueNumPair condvnp; + ValueNumPair condXvnp; + vnStore->VNPUnpackExc(conditional->gtCond->gtVNPair, &condvnp, &condXvnp); + + ValueNumPair op1vnp; + ValueNumPair op1Xvnp; + vnStore->VNPUnpackExc(conditional->gtOp1->gtVNPair, &op1vnp, &op1Xvnp); + + ValueNumPair op2vnp; + ValueNumPair op2Xvnp; + vnStore->VNPUnpackExc(conditional->gtOp2->gtVNPair, &op2vnp, &op2Xvnp); + + // Collect the exception sets. + ValueNumPair vnpExcSet = vnStore->VNPExcSetUnion(condXvnp, op1Xvnp); + vnpExcSet = vnStore->VNPExcSetUnion(vnpExcSet, op2Xvnp); + + // Get the normal value using the VN func. + VNFunc vnf = GetVNFuncForNode(tree); + assert(ValueNumStore::VNFuncIsLegal(vnf)); + ValueNumPair normalPair = vnStore->VNPairForFunc(tree->TypeGet(), vnf, condvnp, op1vnp, op2vnp); + + // Attach the combined exception set + tree->gtVNPair = vnStore->VNPWithExc(normalPair, vnpExcSet); + + break; + } + default: assert(!"Unhandled special node in fgValueNumberTree"); tree->gtVNPair.SetBoth(vnStore->VNForExpr(compCurBB, tree->TypeGet())); diff --git a/src/tests/JIT/opt/Compares/compareAnd2Chains.cs b/src/tests/JIT/opt/Compares/compareAnd2Chains.cs index 0fd39d96cbcfa..44ce6c88f6004 100644 --- a/src/tests/JIT/opt/Compares/compareAnd2Chains.cs +++ b/src/tests/JIT/opt/Compares/compareAnd2Chains.cs @@ -31,6 +31,12 @@ public class ComparisonTestAnd2Chains [MethodImpl(MethodImplOptions.NoInlining)] public static bool Eq_ulong_2(ulong a1, ulong a2) => a1 == 10 & a2 == 11; + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Eq_float_2(float a1, float a2) => a1 == 10.5f & a2 == 11.5f; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Eq_double_2(double a1, double a2) => a1 == 10.5 & a2 == 11.5; + [MethodImpl(MethodImplOptions.NoInlining)] public static bool Ne_byte_2(byte a1, byte a2) => a1 != 5 & a2 != 5; @@ -53,6 +59,12 @@ public class ComparisonTestAnd2Chains [MethodImpl(MethodImplOptions.NoInlining)] public static bool Ne_ulong_2(ulong a1, ulong a2) => a1 != 5 & a2 != 5; + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ne_float_2(float a1, float a2) => a1 != 5.5f & a2 != 5.5f; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ne_double_2(double a1, double a2) => a1 != 5.5 & a2 != 5.5; + [MethodImpl(MethodImplOptions.NoInlining)] public static bool Lt_byte_2(byte a1, byte a2) => a1 < 5 & a2 < 5; @@ -75,6 +87,12 @@ public class ComparisonTestAnd2Chains [MethodImpl(MethodImplOptions.NoInlining)] public static bool Lt_ulong_2(ulong a1, ulong a2) => a1 < 5 & a2 < 5; + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Lt_float_2(float a1, float a2) => a1 < 5.5f & a2 < 5.5f; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Lt_double_2(double a1, double a2) => a1 < 5.5 & a2 < 5.5; + [MethodImpl(MethodImplOptions.NoInlining)] public static bool Le_byte_2(byte a1, byte a2) => a1 <= 5 & a2 <= 5; @@ -97,6 +115,12 @@ public class ComparisonTestAnd2Chains [MethodImpl(MethodImplOptions.NoInlining)] public static bool Le_ulong_2(ulong a1, ulong a2) => a1 <= 5 & a2 <= 5; + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Le_float_2(float a1, float a2) => a1 <= 5.5f & a2 <= 5.5f; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Le_double_2(double a1, double a2) => a1 <= 5.5 & a2 <= 5.5; + [MethodImpl(MethodImplOptions.NoInlining)] public static bool Gt_byte_2(byte a1, byte a2) => a1 > 5 & a2 > 5; @@ -119,6 +143,12 @@ public class ComparisonTestAnd2Chains [MethodImpl(MethodImplOptions.NoInlining)] public static bool Gt_ulong_2(ulong a1, ulong a2) => a1 > 5 & a2 > 5; + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Gt_float_2(float a1, float a2) => a1 > 5.5f & a2 > 5.5f; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Gt_double_2(double a1, double a2) => a1 > 5.5 & a2 > 5.5; + [MethodImpl(MethodImplOptions.NoInlining)] public static bool Ge_byte_2(byte a1, byte a2) => a1 >= 5 & a2 >= 5; @@ -141,6 +171,12 @@ public class ComparisonTestAnd2Chains [MethodImpl(MethodImplOptions.NoInlining)] public static bool Ge_ulong_2(ulong a1, ulong a2) => a1 >= 5 & a2 >= 5; + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ge_float_2(float a1, float a2) => a1 >= 5.5f & a2 >= 5.5f; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ge_double_2(double a1, double a2) => a1 >= 5.5 & a2 >= 5.5; + [MethodImpl(MethodImplOptions.NoInlining)] public static int Main() @@ -180,6 +216,16 @@ public static int Main() Console.WriteLine("ComparisonTestAnd2Chains:Eq_ulong_2(10, 11) failed"); return 101; } + if (!Eq_float_2(10.5f, 11.5f)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Eq_float_2(10.5, 11.5) failed"); + return 101; + } + if (!Eq_double_2(10.5, 11.5)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Eq_ulong_2(10.5, 11.5) failed"); + return 101; + } if (!Ne_byte_2(10, 11)) { @@ -216,6 +262,16 @@ public static int Main() Console.WriteLine("ComparisonTestAnd2Chains:Ne_ulong_2(10, 11) failed"); return 101; } + if (!Ne_float_2(10.5f, 11.5f)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Ne_float_2(10.5, 11.5) failed"); + return 101; + } + if (!Ne_double_2(10.5, 11.5)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Ne_double_2(10.5, 11.5) failed"); + return 101; + } if (!Lt_byte_2(3, 4)) { @@ -252,6 +308,16 @@ public static int Main() Console.WriteLine("ComparisonTestAnd2Chains:Lt_ulong_2(3, 4) failed"); return 101; } + if (!Lt_float_2(3f, 4f)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Lt_float_2(3.5, 4.5) failed"); + return 101; + } + if (!Lt_double_2(3, 4)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Lt_double_2(3.5, 4.5) failed"); + return 101; + } if (!Le_byte_2(3, 4)) { @@ -288,6 +354,16 @@ public static int Main() Console.WriteLine("ComparisonTestAnd2Chains:Le_ulong_2(3, 4) failed"); return 101; } + if (!Le_float_2(3f, 4f)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Le_float_2(3.5, 4.5) failed"); + return 101; + } + if (!Le_double_2(3, 4)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Le_double_2(3.5, 4.5) failed"); + return 101; + } if (!Gt_byte_2(10, 11)) { @@ -324,6 +400,16 @@ public static int Main() Console.WriteLine("ComparisonTestAnd2Chains:Gt_ulong_2(10, 11) failed"); return 101; } + if (!Gt_float_2(10.5f, 11.5f)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Gt_float_2(10.5, 11.5) failed"); + return 101; + } + if (!Gt_double_2(10.5, 11.5)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Gt_ulong_2(10.5, 11.5) failed"); + return 101; + } if (!Ge_byte_2(10, 11)) { @@ -357,7 +443,17 @@ public static int Main() } if (!Ge_ulong_2(10, 11)) { - Console.WriteLine("ComparisonTestAnd2Chains:Le_ulong_2(10, 11) failed"); + Console.WriteLine("ComparisonTestAnd2Chains:Ge_ulong_2(10, 11) failed"); + return 101; + } + if (!Ge_float_2(10.5f, 11.5f)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Ge_float_2(10.5, 11.5) failed"); + return 101; + } + if (!Ge_double_2(10.5, 11.5)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Ge_double_2(10.5, 11.5) failed"); return 101; } diff --git a/src/tests/JIT/opt/Compares/compareAnd3Chains.cs b/src/tests/JIT/opt/Compares/compareAnd3Chains.cs index c609cdebc2d01..2f6dcfa84f6e9 100644 --- a/src/tests/JIT/opt/Compares/compareAnd3Chains.cs +++ b/src/tests/JIT/opt/Compares/compareAnd3Chains.cs @@ -31,6 +31,12 @@ public class ComparisonTestAnd3Chains [MethodImpl(MethodImplOptions.NoInlining)] public static bool Eq_ulong_3(ulong a1, ulong a2, ulong a3) => a1 == 10 & a2 == 11 & a3 == 12; + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Eq_float_3(float a1, float a2, float a3) => a1 == 10.5f & a2 == 11.5f & a3 == 12.5f; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Eq_double_3(double a1, double a2, double a3) => a1 == 10.5 & a2 == 11.5 & a3 == 12.5; + [MethodImpl(MethodImplOptions.NoInlining)] public static bool Ne_byte_3(byte a1, byte a2, byte a3) => a1 != 5 & a2 != 5 & a3 != 5; @@ -53,6 +59,12 @@ public class ComparisonTestAnd3Chains [MethodImpl(MethodImplOptions.NoInlining)] public static bool Ne_ulong_3(ulong a1, ulong a2, ulong a3) => a1 != 5 & a2 != 5 & a3 != 5; + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ne_float_3(float a1, float a2, float a3) => a1 != 5.5f & a2 != 5.5f & a3 != 5.5f; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ne_double_3(double a1, double a2, double a3) => a1 != 5.5 & a2 != 5.5 & a3 != 5.5; + [MethodImpl(MethodImplOptions.NoInlining)] public static bool Lt_byte_3(byte a1, byte a2, byte a3) => a1 < 5 & a2 < 5 & a3 < 5; @@ -75,6 +87,12 @@ public class ComparisonTestAnd3Chains [MethodImpl(MethodImplOptions.NoInlining)] public static bool Lt_ulong_3(ulong a1, ulong a2, ulong a3) => a1 < 5 & a2 < 5 & a3 < 5; + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Lt_float_3(float a1, float a2, float a3) => a1 < 5.5f & a2 < 5.5f & a3 < 5.5f; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Lt_double_3(double a1, double a2, double a3) => a1 < 5.5 & a2 < 5.5 & a3 < 5.5; + [MethodImpl(MethodImplOptions.NoInlining)] public static bool Le_byte_3(byte a1, byte a2, byte a3) => a1 <= 5 & a2 <= 5 & a3 <= 5; @@ -97,6 +115,12 @@ public class ComparisonTestAnd3Chains [MethodImpl(MethodImplOptions.NoInlining)] public static bool Le_ulong_3(ulong a1, ulong a2, ulong a3) => a1 <= 5 & a2 <= 5 & a3 <= 5; + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Le_float_3(float a1, float a2, float a3) => a1 <= 5.5f & a2 <= 5.5f & a3 <= 5.5f; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Le_double_3(double a1, double a2, double a3) => a1 <= 5.5 & a2 <= 5.5 & a3 <= 5.5; + [MethodImpl(MethodImplOptions.NoInlining)] public static bool Gt_byte_3(byte a1, byte a2, byte a3) => a1 > 5 & a2 > 5 & a3 > 5; @@ -119,6 +143,12 @@ public class ComparisonTestAnd3Chains [MethodImpl(MethodImplOptions.NoInlining)] public static bool Gt_ulong_3(ulong a1, ulong a2, ulong a3) => a1 > 5 & a2 > 5 & a3 > 5; + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Gt_float_3(float a1, float a2, float a3) => a1 > 5.5f & a2 > 5.5f & a3 > 5.5f; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Gt_double_3(double a1, double a2, double a3) => a1 > 5.5 & a2 > 5.5 & a3 > 5.5; + [MethodImpl(MethodImplOptions.NoInlining)] public static bool Ge_byte_3(byte a1, byte a2, byte a3) => a1 >= 5 & a2 >= 5 & a3 >= 5; @@ -141,6 +171,12 @@ public class ComparisonTestAnd3Chains [MethodImpl(MethodImplOptions.NoInlining)] public static bool Ge_ulong_3(ulong a1, ulong a2, ulong a3) => a1 >= 5 & a2 >= 5 & a3 >= 5; + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ge_float_3(float a1, float a2, float a3) => a1 >= 5.5f & a2 >= 5.5f & a3 >= 5.5f; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ge_double_3(double a1, double a2, double a3) => a1 >= 5.5 & a2 >= 5.5 & a3 >= 5.5; + [MethodImpl(MethodImplOptions.NoInlining)] public static int Main() @@ -180,6 +216,16 @@ public static int Main() Console.WriteLine("ComparisonTestAnd2Chains:Eq_ulong_3(10, 11, 12) failed"); return 101; } + if (!Eq_float_3(10.5f, 11.5f, 12.5f)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Eq_float_3(10.5, 11.5, 12.5) failed"); + return 101; + } + if (!Eq_double_3(10.5, 11.5, 12.5)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Eq_double_3(10.5, 11.5, 12.5) failed"); + return 101; + } if (!Ne_byte_3(10, 11, 12)) { @@ -216,6 +262,16 @@ public static int Main() Console.WriteLine("ComparisonTestAnd2Chains:Ne_ulong_3(10, 11, 12) failed"); return 101; } + if (!Ne_float_3(10.5f, 11.5f, 12.5f)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Ne_float_3(10.5, 11.5, 12.5) failed"); + return 101; + } + if (!Ne_double_3(10.5, 11.5, 12.5)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Ne_double_3(10.5, 11.5, 12.5) failed"); + return 101; + } if (!Lt_byte_3(2, 3, 4)) { @@ -252,6 +308,16 @@ public static int Main() Console.WriteLine("ComparisonTestAnd2Chains:Lt_ulong_3(2, 3, 4) failed"); return 101; } + if (!Lt_float_3(2.5f, 3.5f, 4.5f)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Lt_float_3(2.5, 3.5, 4.5) failed"); + return 101; + } + if (!Lt_double_3(2.5, 3.5, 4.5)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Lt_double_3(2.5, 3.5, 4.5) failed"); + return 101; + } if (!Le_byte_3(2, 3, 4)) { @@ -288,6 +354,16 @@ public static int Main() Console.WriteLine("ComparisonTestAnd2Chains:Le_ulong_3(2, 3, 4) failed"); return 101; } + if (!Le_float_3(2.5f, 3.5f, 4.5f)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Le_float_3(2.5, 3.5, 4.5) failed"); + return 101; + } + if (!Le_double_3(2.5, 3.5, 4.5)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Le_double_3(2.5, 3.5, 4.5) failed"); + return 101; + } if (!Gt_byte_3(10, 11, 12)) { @@ -324,6 +400,16 @@ public static int Main() Console.WriteLine("ComparisonTestAnd2Chains:Gt_ulong_3(10, 11, 12) failed"); return 101; } + if (!Gt_float_3(10.5f, 11.5f, 12.5f)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Gt_float_3(10.5, 11.5, 12.5) failed"); + return 101; + } + if (!Gt_double_3(10.5, 11.5, 12.5)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Gt_double_3(10.5, 11.5, 12.5) failed"); + return 101; + } if (!Ge_byte_3(10, 11, 12)) { @@ -360,6 +446,16 @@ public static int Main() Console.WriteLine("ComparisonTestAnd2Chains:Le_ulong_3(10, 11, 12) failed"); return 101; } + if (!Ge_float_3(10.5f, 11.5f, 12.5f)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Ge_float_3(10.5, 11.5, 12.5) failed"); + return 101; + } + if (!Ge_double_3(10.5, 11.5, 12.5)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Le_double_3(10.5, 11.5, 12.5) failed"); + return 101; + } Console.WriteLine("PASSED"); return 100; diff --git a/src/tests/JIT/opt/Compares/compareAndTestChains.cs b/src/tests/JIT/opt/Compares/compareAndTestChains.cs index c89e04f487ecf..575a9372509d3 100644 --- a/src/tests/JIT/opt/Compares/compareAndTestChains.cs +++ b/src/tests/JIT/opt/Compares/compareAndTestChains.cs @@ -31,6 +31,12 @@ public class ComparisonTestAndTestChains [MethodImpl(MethodImplOptions.NoInlining)] public static bool Eq_ulong_bool(ulong a1, bool a2) => (a1 == 10) & !a2; + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Eq_float_bool(float a1, bool a2) => (a1 == 10.1f) & !a2; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Eq_double_bool(double a1, bool a2) => (a1 == 10.1) & !a2; + [MethodImpl(MethodImplOptions.NoInlining)] public static bool Ne_byte_bool(byte a1, bool a2) => (a1 != 5) & !a2; @@ -53,6 +59,12 @@ public class ComparisonTestAndTestChains [MethodImpl(MethodImplOptions.NoInlining)] public static bool Ne_ulong_bool(ulong a1, bool a2) => (a1 != 5) & !a2; + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ne_float_bool(float a1, bool a2) => (a1 != 5.1f) & !a2; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ne_double_bool(double a1, bool a2) => (a1 != 5.1) & !a2; + [MethodImpl(MethodImplOptions.NoInlining)] public static bool Lt_byte_bool(byte a1, bool a2) => (a1 < 5) & !a2; @@ -75,6 +87,12 @@ public class ComparisonTestAndTestChains [MethodImpl(MethodImplOptions.NoInlining)] public static bool Lt_ulong_bool(ulong a1, bool a2) => (a1 < 5) & !a2; + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Lt_float_bool(float a1, bool a2) => (a1 < 5.1f) & !a2; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Lt_double_bool(double a1, bool a2) => (a1 < 5.1) & !a2; + [MethodImpl(MethodImplOptions.NoInlining)] public static bool Le_byte_bool(byte a1, bool a2) => (a1 <= 5) & !a2; @@ -97,6 +115,12 @@ public class ComparisonTestAndTestChains [MethodImpl(MethodImplOptions.NoInlining)] public static bool Le_ulong_bool(ulong a1, bool a2) => (a1 <= 5) & !a2; + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Le_float_bool(float a1, bool a2) => (a1 <= 5.1f) & !a2; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Le_double_bool(double a1, bool a2) => (a1 <= 5.1) & !a2; + [MethodImpl(MethodImplOptions.NoInlining)] public static bool Gt_byte_bool(byte a1, bool a2) => (a1 > 5) & !a2; @@ -119,6 +143,12 @@ public class ComparisonTestAndTestChains [MethodImpl(MethodImplOptions.NoInlining)] public static bool Gt_ulong_bool(ulong a1, bool a2) => (a1 > 5) & !a2; + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Gt_float_bool(float a1, bool a2) => (a1 > 5.1f) & !a2; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Gt_double_bool(double a1, bool a2) => (a1 > 5.1) & !a2; + [MethodImpl(MethodImplOptions.NoInlining)] public static bool Ge_byte_bool(byte a1, bool a2) => (a1 >= 5) & !a2; @@ -141,6 +171,12 @@ public class ComparisonTestAndTestChains [MethodImpl(MethodImplOptions.NoInlining)] public static bool Ge_ulong_bool(ulong a1, bool a2) => (a1 >= 5) & !a2; + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ge_float_bool(float a1, bool a2) => (a1 >= 5.1f) & !a2; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ge_double_bool(double a1, bool a2) => (a1 >= 5.1) & !a2; + [MethodImpl(MethodImplOptions.NoInlining)] public static int Main() @@ -180,6 +216,16 @@ public static int Main() Console.WriteLine("ComparisonTestAndTestChains:Eq_ulong_bool(10, false) failed"); return 101; } + if (!Eq_float_bool(10.1f, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Eq_float_bool(10.1, false) failed"); + return 101; + } + if (!Eq_double_bool(10.1, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Eq_double_bool(10.1, false) failed"); + return 101; + } if (!Ne_byte_bool(10, false)) { @@ -216,6 +262,16 @@ public static int Main() Console.WriteLine("ComparisonTestAndTestChains:Ne_ulong_bool(10, false) failed"); return 101; } + if (!Ne_float_bool(10.1f, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Ne_float_bool(10.1, false) failed"); + return 101; + } + if (!Ne_double_bool(10.1, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Ne_double_bool(10.1, false) failed"); + return 101; + } if (!Lt_byte_bool(3, false)) { @@ -252,6 +308,16 @@ public static int Main() Console.WriteLine("ComparisonTestAndTestChains:Lt_ulong_bool(3, false) failed"); return 101; } + if (!Lt_float_bool(3.1f, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Lt_float_bool(3.1, false) failed"); + return 101; + } + if (!Lt_double_bool(3.1, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Lt_double_bool(3.1, false) failed"); + return 101; + } if (!Le_byte_bool(3, false)) { @@ -288,6 +354,16 @@ public static int Main() Console.WriteLine("ComparisonTestAndTestChains:Le_ulong_bool(3, false) failed"); return 101; } + if (!Le_float_bool(3.1f, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Le_float_bool(3.1, false) failed"); + return 101; + } + if (!Le_double_bool(3.1, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Le_double_bool(3.1, false) failed"); + return 101; + } if (!Gt_byte_bool(10, false)) { @@ -324,6 +400,16 @@ public static int Main() Console.WriteLine("ComparisonTestAndTestChains:Gt_ulong_bool(10, false) failed"); return 101; } + if (!Gt_float_bool(10.1f, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Gt_float_bool(10.1, false) failed"); + return 101; + } + if (!Gt_double_bool(10.1, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Gt_double_bool(10.1, false) failed"); + return 101; + } if (!Ge_byte_bool(10, false)) { @@ -360,6 +446,16 @@ public static int Main() Console.WriteLine("ComparisonTestAndTestChains:Le_ulong_bool(10, false) failed"); return 101; } + if (!Ge_float_bool(10.1f, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Ge_float_bool(10.1, false) failed"); + return 101; + } + if (!Ge_double_bool(10.1, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Ge_double_bool(10.1, false) failed"); + return 101; + } Console.WriteLine("PASSED"); return 100;