From 4548d836ff8d0445580b003e5d8bfcf540bcbec8 Mon Sep 17 00:00:00 2001 From: BoyBaykiller Date: Thu, 30 Apr 2026 22:45:48 +0200 Subject: [PATCH 1/5] * cleanup select opts in if-conversion * remove redundant select-to-cond opt in lower --- src/coreclr/jit/ifconversion.cpp | 286 ++++++++++++++++--------------- src/coreclr/jit/lower.cpp | 28 --- 2 files changed, 149 insertions(+), 165 deletions(-) diff --git a/src/coreclr/jit/ifconversion.cpp b/src/coreclr/jit/ifconversion.cpp index d14473d12f551a..7fd740ea7748dd 100644 --- a/src/coreclr/jit/ifconversion.cpp +++ b/src/coreclr/jit/ifconversion.cpp @@ -52,9 +52,11 @@ class OptIfConversionDsc bool IfConvertCheckStmts(BasicBlock* block, IfConvertOperation* foundOperation); bool IfConvertTryGetElseFromJtrueBlock(GenTreeLclVar* thenStore, IfConvertOperation* foundOperation); - GenTree* TryTransformSelectOperOrLocal(GenTree* oper, GenTree* lcl); - GenTree* TryTransformSelectOperOrZero(GenTree* oper, GenTree* lcl); - GenTree* TryTransformSelectToOrdinaryOps(GenTree* trueInput, GenTree* falseInput); + GenTree* TryOptimizeSelect(GenTreeConditional* select); + GenTree* TrySelectToCnsOpCond(GenTreeConditional* select); + GenTree* TrySelectToLclOpCond(GenTreeConditional* select); + GenTree* TrySelectToCondOpLcl(GenTreeConditional* select); + #ifdef DEBUG void IfConvertDump(); #endif @@ -567,17 +569,22 @@ bool OptIfConversionDsc::optIfConvert(int* pReachabilityBudget) } } - // Get the select node inputs. - var_types selectType; - GenTree* selectTrueInput; - GenTree* selectFalseInput; + // Get the SELECT inputs. + GenTree* selectTrueInput; + GenTree* selectFalseInput; if (m_mainOper == GT_STORE_LCL_VAR) { selectFalseInput = m_thenOperation.node->AsLclVar()->Data(); - selectTrueInput = (m_elseOperation.block != nullptr) ? m_elseOperation.node->AsLclVar()->Data() : nullptr; - - // Pick the type as the type of the local, which should always be compatible even for implicit coercions. - selectType = genActualType(m_thenOperation.node); + if (m_elseOperation.block == nullptr) + { + // The code doesn't explicitly express an Else operation, use the unmodified local. + GenTreeLclVar* store = m_thenOperation.node->AsLclVar(); + selectTrueInput = m_compiler->gtNewLclVarNode(store->GetLclNum(), store->TypeGet()); + } + else + { + selectTrueInput = m_elseOperation.node->AsLclVar()->Data(); + } } else { @@ -587,26 +594,23 @@ bool OptIfConversionDsc::optIfConvert(int* pReachabilityBudget) selectTrueInput = m_elseOperation.node->AsOp()->GetReturnValue(); selectFalseInput = m_thenOperation.node->AsOp()->GetReturnValue(); - selectType = genActualType(m_thenOperation.node); } - GenTree* select = TryTransformSelectToOrdinaryOps(selectTrueInput, selectFalseInput); - if (select == nullptr) + GenTree* select = m_compiler->gtNewConditionalNode(GT_SELECT, m_cond, selectTrueInput, selectFalseInput, + genActualType(m_thenOperation.node)); + + if (GenTree* optSelect = TryOptimizeSelect(select->AsConditional()); optSelect != nullptr) { + select = optSelect; + } + #ifdef TARGET_RISCV64 - JITDUMP("Skipping if-conversion that cannot be transformed to ordinary operations\n"); + if (select->OperIs(GT_SELECT)) + { + JITDUMP("Skipping if-conversion that could not be optimized to ordinary operations\n"); return false; -#endif - if (selectTrueInput == nullptr) - { - // Duplicate the destination of the Then store. - assert(m_mainOper == GT_STORE_LCL_VAR && (m_elseOperation.block == nullptr)); - GenTreeLclVar* store = m_thenOperation.node->AsLclVar(); - selectTrueInput = m_compiler->gtNewLclVarNode(store->GetLclNum(), store->TypeGet()); - } - // Create a select node - select = m_compiler->gtNewConditionalNode(GT_SELECT, m_cond, selectTrueInput, selectFalseInput, selectType); } +#endif // Use the SELECT as the source of the Then STORE/RETURN. m_thenOperation.node->AddAllEffectsFlags(select); @@ -674,6 +678,33 @@ bool OptIfConversionDsc::optIfConvert(int* pReachabilityBudget) return true; } +//----------------------------------------------------------------------------- +// TryOptimizeSelect: Try to optimize SELECT +// +// Arguments: +// select - The SELECT node +// +// Return Value: +// Optimized node, otherwise nullptr. +// +GenTree* OptIfConversionDsc::TryOptimizeSelect(GenTreeConditional* select) +{ + if (GenTree* opt = TrySelectToCnsOpCond(select); opt != nullptr) + { + return opt; + } + if (GenTree* opt = TrySelectToLclOpCond(select); opt != nullptr) + { + return opt; + } + if (GenTree* opt = TrySelectToCondOpLcl(select); opt != nullptr) + { + return opt; + } + + return nullptr; +} + struct IntConstSelectOper { genTreeOps oper; @@ -685,17 +716,6 @@ struct IntConstSelectOper return oper != GT_NONE; } }; - -//----------------------------------------------------------------------------- -// MatchIntConstSelectValues: Matches an operation so that `trueVal` can be calculated as: -// oper(type, falseVal, condition) -// -// Notes: -// A non-zero bitIndex (log2(trueVal)) differentiates (condition << bitIndex) from (falseVal << condition). -// -// Return Value: -// The matched operation (if any). -// static IntConstSelectOper MatchIntConstSelectValues(int64_t trueVal, int64_t falseVal) { if (trueVal == falseVal + 1) @@ -739,19 +759,90 @@ static IntConstSelectOper MatchIntConstSelectValues(int64_t trueVal, int64_t fal } //----------------------------------------------------------------------------- -// TryTransformSelectOperOrLocal: Try to trasform "cond ? oper(lcl, (-)1) : lcl" into "oper(')(lcl, cond)" +// TrySelectToCnsOpCond: Try to optimize: +// SELECT(cond, 0, 1) -> cond +// SELECT(cond, 3, 3) -> 3 +// SELECT(cond, 6, 5) -> 5 + cond +// SELECT(cond, -25, -13) -> -25 >> cond +// +// Arguments: +// select - The SELECT node +// +// Return Value: +// Optimized node, otherwise nullptr. +// +GenTree* OptIfConversionDsc::TrySelectToCnsOpCond(GenTreeConditional* select) +{ + GenTree* cond = select->gtCond; + GenTree* trueInput = select->gtOp1; + GenTree* falseInput = select->gtOp2; + + if (!trueInput->IsIntegralConst() || !falseInput->IsIntegralConst()) + { + return nullptr; + } + + int64_t trueVal = trueInput->AsIntConCommon()->IntegralValue(); + int64_t falseVal = falseInput->AsIntConCommon()->IntegralValue(); + + if (trueVal == 1 && falseVal == 0) + { + return cond; + } + else if (trueVal == 0 && falseVal == 1) + { + return m_compiler->gtReverseCond(cond); + } + +#ifdef TARGET_RISCV64 + bool isCondReversed = false; + IntConstSelectOper selectOper = MatchIntConstSelectValues(trueVal, falseVal); + if (!selectOper.isMatched()) + { + isCondReversed = true; + selectOper = MatchIntConstSelectValues(falseVal, trueVal); + } + if (selectOper.isMatched()) + { + GenTree* left = isCondReversed ? trueInput : falseInput; + GenTree* right = isCondReversed ? m_compiler->gtReverseCond(cond) : cond; + if (selectOper.bitIndex > 0) + { + assert(selectOper.oper == GT_LSH); + left->AsIntConCommon()->SetIntegralValue(selectOper.bitIndex); + std::swap(left, right); + } + return m_compiler->gtNewOperNode(selectOper.oper, selectOper.type, left, right); + } +#endif // TARGET_RISCV64 + + return nullptr; +} + +//----------------------------------------------------------------------------- +// TrySelectToLclOpCond: Try to optimize: +// SELECT(cond, x + 1, x) -> x + cond +// SELECT(cond, x | 1, x) -> x | cond +// SELECT(cond, x ^ 1, x) -> x ^ cond +// SELECT(cond, x << 1, x) -> x << cond // // Arguments: -// trueInput - expression to be evaluated when m_cond is true -// falseInput - expression to be evaluated when m_cond is false +// select - The SELECT node // // Return Value: -// The transformed expression, or null if no transformation took place +// Optimized node, otherwise nullptr. // -GenTree* OptIfConversionDsc::TryTransformSelectOperOrLocal(GenTree* trueInput, GenTree* falseInput) +GenTree* OptIfConversionDsc::TrySelectToLclOpCond(GenTreeConditional* select) { - GenTree* oper = trueInput; - GenTree* lcl = falseInput; +#ifdef TARGET_RISCV64 + GenTree* cond = select->gtCond; + GenTree* oper = select->gtOp1; + GenTree* lcl = select->gtOp2; + + if (!cond->OperIsCompare()) + { + return nullptr; + } bool isCondReversed = !lcl->OperIsAnyLocal(); if (isCondReversed) @@ -771,32 +862,36 @@ GenTree* OptIfConversionDsc::TryTransformSelectOperOrLocal(GenTree* trueInput, G if (lcl2->OperIs(GT_LCL_VAR) && (lcl2->AsLclVar()->GetLclNum() == lclNum)) { oper->AsOp()->gtOp1 = lcl2; - oper->AsOp()->gtOp2 = isCondReversed ? m_compiler->gtReverseCond(m_cond) : m_cond; + oper->AsOp()->gtOp2 = isCondReversed ? m_compiler->gtReverseCond(cond) : cond; if (isDecrement) oper->ChangeOper(GT_SUB); - oper->gtFlags |= m_cond->gtFlags & GTF_ALL_EFFECT; + oper->gtFlags |= cond->gtFlags & GTF_ALL_EFFECT; return oper; } } } +#endif // TARGET_RISCV64 return nullptr; } //----------------------------------------------------------------------------- -// TryTransformSelectOperOrZero: Try to trasform "cond ? oper(1, expr) : 0" into "oper(cond, expr)" +// TrySelectToCondOpLcl: Try to optimize: +// SELECT(cond, 1 << x, 0) -> cond << x +// SELECT(cond, 1 & x, 0) -> cond & x // // Arguments: -// trueInput - expression to be evaluated when m_cond is true -// falseInput - expression to be evaluated when m_cond is false +// select - The SELECT node // // Return Value: -// The transformed expression, or null if no transformation took place +// Optimized node, otherwise nullptr. // -GenTree* OptIfConversionDsc::TryTransformSelectOperOrZero(GenTree* trueInput, GenTree* falseInput) +GenTree* OptIfConversionDsc::TrySelectToCondOpLcl(GenTreeConditional* select) { - GenTree* oper = trueInput; - GenTree* zero = falseInput; +#ifdef TARGET_RISCV64 + GenTree* cond = select->gtCond; + GenTree* oper = select->gtOp1; + GenTree* zero = select->gtOp2; bool isCondReversed = !zero->IsIntegralConst(); if (isCondReversed) @@ -811,96 +906,13 @@ GenTree* OptIfConversionDsc::TryTransformSelectOperOrZero(GenTree* trueInput, Ge if (one->IsIntegralConst(1)) { - oper->AsOp()->gtOp1 = isCondReversed ? m_compiler->gtReverseCond(m_cond) : m_cond; + oper->AsOp()->gtOp1 = isCondReversed ? m_compiler->gtReverseCond(cond) : cond; oper->AsOp()->gtOp2 = expr; - oper->gtFlags |= m_cond->gtFlags & GTF_ALL_EFFECT; + oper->gtFlags |= cond->gtFlags & GTF_ALL_EFFECT; return oper; } } - return nullptr; -} - -//----------------------------------------------------------------------------- -// TryTransformSelectToOrdinaryOps: Try transforming the identified if-else expressions to a single expression -// -// This is meant mostly for RISC-V where the condition (1 or 0) is stored in a regular general-purpose register -// which can be fed as an argument to standard operations, e.g. -// * (cond ? 6 : 5) becomes (5 + cond) -// * (cond ? -25 : -13) becomes (-25 >> cond) -// * if (cond) a++; becomes (a + cond) -// * (cond ? 1 << a : 0) becomes (cond << a) -// -// Arguments: -// trueInput - expression to be evaluated when m_cond is true, or null if there is no else expression -// falseInput - expression to be evaluated when m_cond is false -// -// Return Value: -// The transformed single expression equivalent to the if-else expressions, or null if no transformation took place -// -GenTree* OptIfConversionDsc::TryTransformSelectToOrdinaryOps(GenTree* trueInput, GenTree* falseInput) -{ - assert(falseInput != nullptr); - - if ((trueInput != nullptr && trueInput->IsIntegralConst()) && falseInput->IsIntegralConst()) - { - int64_t trueVal = trueInput->AsIntConCommon()->IntegralValue(); - int64_t falseVal = falseInput->AsIntConCommon()->IntegralValue(); - if (trueInput->TypeIs(TYP_INT) && falseInput->TypeIs(TYP_INT)) - { - if (trueVal == 1 && falseVal == 0) - { - // compare ? true : false --> compare - return m_cond; - } - else if (trueVal == 0 && falseVal == 1) - { - // compare ? false : true --> reversed_compare - return m_compiler->gtReverseCond(m_cond); - } - } -#ifdef TARGET_RISCV64 - if (varTypeIsIntegral(trueInput) && varTypeIsIntegral(falseInput) && (trueVal != falseVal)) - { - bool isCondReversed = false; - IntConstSelectOper selectOper = MatchIntConstSelectValues(trueVal, falseVal); - if (!selectOper.isMatched()) - { - isCondReversed = true; - selectOper = MatchIntConstSelectValues(falseVal, trueVal); - } - if (selectOper.isMatched()) - { - GenTree* left = isCondReversed ? trueInput : falseInput; - GenTree* right = isCondReversed ? m_compiler->gtReverseCond(m_cond) : m_cond; - if (selectOper.bitIndex > 0) - { - assert(selectOper.oper == GT_LSH); - left->AsIntConCommon()->SetIntegralValue(selectOper.bitIndex); - std::swap(left, right); - } - return m_compiler->gtNewOperNode(selectOper.oper, selectOper.type, left, right); - } - } -#endif // TARGET_RISCV64 - } -#ifdef TARGET_RISCV64 - else - { - if (trueInput == nullptr) - { - assert(m_mainOper == GT_STORE_LCL_VAR && (m_elseOperation.block == nullptr)); - trueInput = m_thenOperation.node; - } - - GenTree* transformed = TryTransformSelectOperOrLocal(trueInput, falseInput); - if (transformed != nullptr) - return transformed; - - transformed = TryTransformSelectOperOrZero(trueInput, falseInput); - if (transformed != nullptr) - return transformed; - } #endif // TARGET_RISCV64 return nullptr; } diff --git a/src/coreclr/jit/lower.cpp b/src/coreclr/jit/lower.cpp index 5f8f8166f57fa9..73bba8ae8a1914 100644 --- a/src/coreclr/jit/lower.cpp +++ b/src/coreclr/jit/lower.cpp @@ -4862,34 +4862,6 @@ GenTree* Lowering::LowerSelect(GenTreeConditional* select) GenTree* trueVal = select->gtOp1; GenTree* falseVal = select->gtOp2; - // Replace SELECT cond 1/0 0/1 with (perhaps reversed) cond - if (cond->OperIsCompare() && ((trueVal->IsIntegralConst(0) && falseVal->IsIntegralConst(1)) || - (trueVal->IsIntegralConst(1) && falseVal->IsIntegralConst(0)))) - { - assert(select->TypeIs(TYP_INT, TYP_LONG)); - - LIR::Use use; - if (BlockRange().TryGetUse(select, &use)) - { - if (trueVal->IsIntegralConst(0)) - { - GenTree* reversed = m_compiler->gtReverseCond(cond); - assert(reversed == cond); - } - - // Codegen supports also TYP_LONG typed compares so we can just - // retype the compare instead of inserting a cast. - cond->gtType = select->TypeGet(); - - BlockRange().Remove(trueVal); - BlockRange().Remove(falseVal); - BlockRange().Remove(select); - use.ReplaceWith(cond); - - return cond->gtNext; - } - } - JITDUMP("Lowering select:\n"); DISPTREERANGE(BlockRange(), select); JITDUMP("\n"); From 6d0a94c18c8682c040438e2f982a06d7f3154edc Mon Sep 17 00:00:00 2001 From: BoyBaykiller Date: Fri, 1 May 2026 03:08:41 +0200 Subject: [PATCH 2/5] * format --- src/coreclr/jit/ifconversion.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/jit/ifconversion.cpp b/src/coreclr/jit/ifconversion.cpp index 7fd740ea7748dd..194713dd8541fd 100644 --- a/src/coreclr/jit/ifconversion.cpp +++ b/src/coreclr/jit/ifconversion.cpp @@ -583,7 +583,7 @@ bool OptIfConversionDsc::optIfConvert(int* pReachabilityBudget) } else { - selectTrueInput = m_elseOperation.node->AsLclVar()->Data(); + selectTrueInput = m_elseOperation.node->AsLclVar()->Data(); } } else From 08bab0b338b142da647e9db0dd4ef4d08a4c8d28 Mon Sep 17 00:00:00 2001 From: BoyBaykiller Date: Fri, 1 May 2026 05:32:24 +0200 Subject: [PATCH 3/5] * debug print SELECT before/after optimization --- src/coreclr/jit/ifconversion.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/coreclr/jit/ifconversion.cpp b/src/coreclr/jit/ifconversion.cpp index 194713dd8541fd..295bc2d832ace7 100644 --- a/src/coreclr/jit/ifconversion.cpp +++ b/src/coreclr/jit/ifconversion.cpp @@ -599,9 +599,25 @@ bool OptIfConversionDsc::optIfConvert(int* pReachabilityBudget) GenTree* select = m_compiler->gtNewConditionalNode(GT_SELECT, m_cond, selectTrueInput, selectFalseInput, genActualType(m_thenOperation.node)); +#ifdef DEBUG + JITDUMP("\nSELECT created:\n"); + if (m_compiler->verbose) + { + m_compiler->gtDispTree(select); + } +#endif + if (GenTree* optSelect = TryOptimizeSelect(select->AsConditional()); optSelect != nullptr) { select = optSelect; + +#ifdef DEBUG + JITDUMP("\nSELECT after optimizations:\n"); + if (m_compiler->verbose) + { + m_compiler->gtDispTree(select); + } +#endif } #ifdef TARGET_RISCV64 From 05f43cfe0f71a58fae4521deeead80dedf2cf80c Mon Sep 17 00:00:00 2001 From: BoyBaykiller Date: Sat, 2 May 2026 03:53:23 +0200 Subject: [PATCH 4/5] * don't declare var inside if-stmt --- src/coreclr/jit/ifconversion.cpp | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/src/coreclr/jit/ifconversion.cpp b/src/coreclr/jit/ifconversion.cpp index 295bc2d832ace7..e4d3bba5a52f63 100644 --- a/src/coreclr/jit/ifconversion.cpp +++ b/src/coreclr/jit/ifconversion.cpp @@ -607,17 +607,20 @@ bool OptIfConversionDsc::optIfConvert(int* pReachabilityBudget) } #endif - if (GenTree* optSelect = TryOptimizeSelect(select->AsConditional()); optSelect != nullptr) { - select = optSelect; + GenTree* optSelect = TryOptimizeSelect(select->AsConditional()); + if (optSelect != nullptr) + { + select = optSelect; #ifdef DEBUG - JITDUMP("\nSELECT after optimizations:\n"); - if (m_compiler->verbose) - { - m_compiler->gtDispTree(select); - } + JITDUMP("\nSELECT after optimizations:\n"); + if (m_compiler->verbose) + { + m_compiler->gtDispTree(select); + } #endif + } } #ifdef TARGET_RISCV64 @@ -705,15 +708,20 @@ bool OptIfConversionDsc::optIfConvert(int* pReachabilityBudget) // GenTree* OptIfConversionDsc::TryOptimizeSelect(GenTreeConditional* select) { - if (GenTree* opt = TrySelectToCnsOpCond(select); opt != nullptr) + GenTree* opt = TrySelectToCnsOpCond(select); + if (opt != nullptr) { return opt; } - if (GenTree* opt = TrySelectToLclOpCond(select); opt != nullptr) + + opt = TrySelectToLclOpCond(select); + if (opt != nullptr) { return opt; } - if (GenTree* opt = TrySelectToCondOpLcl(select); opt != nullptr) + + opt = TrySelectToCondOpLcl(select); + if (opt != nullptr) { return opt; } From cf93b9e572bab78521968d0cdfa2643454d11988 Mon Sep 17 00:00:00 2001 From: BoyBaykiller Date: Tue, 5 May 2026 22:33:26 +0200 Subject: [PATCH 5/5] * creating new tree counts as change, return true --- src/coreclr/jit/ifconversion.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/jit/ifconversion.cpp b/src/coreclr/jit/ifconversion.cpp index e4d3bba5a52f63..53b1d8ef68e242 100644 --- a/src/coreclr/jit/ifconversion.cpp +++ b/src/coreclr/jit/ifconversion.cpp @@ -627,7 +627,7 @@ bool OptIfConversionDsc::optIfConvert(int* pReachabilityBudget) if (select->OperIs(GT_SELECT)) { JITDUMP("Skipping if-conversion that could not be optimized to ordinary operations\n"); - return false; + return true; } #endif