From 97985dbe76f0b30dd98e2f8501aae6ab32d8b292 Mon Sep 17 00:00:00 2001 From: BoyBaykiller Date: Mon, 13 Apr 2026 20:43:45 +0200 Subject: [PATCH 01/11] * initial impl --- src/coreclr/jit/compiler.h | 1 + src/coreclr/jit/morph.cpp | 61 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 9568aceac47f88..a88b054550dcde 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -7136,6 +7136,7 @@ class Compiler GenTree* fgOptimizeHWIntrinsicAssociative(GenTreeHWIntrinsic* node); #endif // FEATURE_HW_INTRINSICS GenTree* fgOptimizeCommutativeArithmetic(GenTreeOp* tree); + GenTreeOp* fgOptimizeDistributiveArithemtic(GenTreeOp* tree); GenTree* fgOptimizeRelationalComparisonWithCasts(GenTreeOp* cmp); GenTree* fgOptimizeAddition(GenTreeOp* add); GenTree* fgOptimizeMultiply(GenTreeOp* mul); diff --git a/src/coreclr/jit/morph.cpp b/src/coreclr/jit/morph.cpp index 0a8073a84b3d23..fc398e3cf62f2f 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -10397,6 +10397,62 @@ GenTree* Compiler::fgOptimizeHWIntrinsicAssociative(GenTreeHWIntrinsic* tree) } #endif // FEATURE_HW_INTRINSICS +//------------------------------------------------------------------------ +// fgOptimizeDistributiveArithemtic: Optimizes distributive operations. +// +// Arguments: +// tree - the unchecked GT_ADD/GT_SUB/GT_OR/GT_AND tree to optimize. +// +// Return Value: +// The optimized tree that can have any shape. +// +GenTreeOp* Compiler::fgOptimizeDistributiveArithemtic(GenTreeOp* tree) +{ + assert(!tree->gtOverflowEx()); + + if (((tree->gtFlags & GTF_PERSISTENT_SIDE_EFFECTS) != 0) || ((tree->gtFlags & GTF_ORDER_SIDEEFF) != 0)) + { + return tree; + } + + GenTree* op1 = tree->gtGetOp1(); + GenTree* op2 = tree->gtGetOp2(); + + auto isDistributiveOver = [](genTreeOps op1, genTreeOps op2) { + // op1 is distributive over op2 iff: + // ((A op2 B) op1 (A op2 C)) <==> (A op1 (B op2 C)) + + switch (op1) + { + case GT_MUL: + return op2 == GT_ADD || op2 == GT_SUB; + + case GT_AND: + return op2 == GT_OR; + + case GT_OR: + return op2 == GT_AND; + + default: + return false; + } + }; + + if ((op1->OperGet() == op2->OperGet()) && isDistributiveOver(op1->OperGet(), tree->OperGet()) && + varTypeIsIntegralOrI(tree)) + { + if (GenTree::Compare(op1->gtGetOp1(), op2->gtGetOp1())) + { + tree->AsOp()->gtOp1 = op1->gtGetOp1(); + tree->AsOp()->gtOp2 = gtNewOperNode(tree->OperGet(), op1->TypeGet(), op1->gtGetOp2(), op2->gtGetOp2()); + tree->SetOper(op1->OperGet(), GenTree::PRESERVE_VN); + fgMorphTreeDone(tree->gtGetOp2()); + } + } + + return tree; +} + //------------------------------------------------------------------------ // fgPushConstantsRight: Pushes constants to the right to help canonicalize the shape // @@ -10480,6 +10536,11 @@ GenTree* Compiler::fgOptimizeCommutativeArithmetic(GenTreeOp* tree) } } + if (tree->OperIs(GT_ADD, GT_SUB, GT_MUL, GT_AND, GT_OR)) + { + tree = fgOptimizeDistributiveArithemtic(tree); + } + GenTree* optimizedTree = nullptr; if (tree->OperIs(GT_ADD)) { From 2c5e2eb63e9c9621c23d8b41ca70e76656a36ff2 Mon Sep 17 00:00:00 2001 From: BoyBaykiller Date: Mon, 13 Apr 2026 23:57:27 +0200 Subject: [PATCH 02/11] * call from fgMorphSmpOp * add OR and AND are 'distributive' over themselves --- src/coreclr/jit/compiler.h | 2 +- src/coreclr/jit/morph.cpp | 41 ++++++++++++++++++++++++-------------- 2 files changed, 27 insertions(+), 16 deletions(-) diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index a88b054550dcde..6250af8c1a992c 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -7135,8 +7135,8 @@ class Compiler GenTree* fgOptimizeHWIntrinsic(GenTreeHWIntrinsic* node); GenTree* fgOptimizeHWIntrinsicAssociative(GenTreeHWIntrinsic* node); #endif // FEATURE_HW_INTRINSICS + GenTree* fgOptimizeDistributiveArithemtic(GenTreeOp* tree); GenTree* fgOptimizeCommutativeArithmetic(GenTreeOp* tree); - GenTreeOp* fgOptimizeDistributiveArithemtic(GenTreeOp* tree); GenTree* fgOptimizeRelationalComparisonWithCasts(GenTreeOp* cmp); GenTree* fgOptimizeAddition(GenTreeOp* add); GenTree* fgOptimizeMultiply(GenTreeOp* mul); diff --git a/src/coreclr/jit/morph.cpp b/src/coreclr/jit/morph.cpp index fc398e3cf62f2f..b5116408cec007 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -7940,6 +7940,8 @@ GenTree* Compiler::fgMorphSmpOp(GenTree* tree, bool* optAssertionPropDone) goto CM_OVF_OP; } + // TODO: Call fgOptimizeDistributiveArithemtic + fgOptimizeCommutativeArithmetic + if (!fgGlobalMorph) { break; @@ -8150,6 +8152,10 @@ GenTree* Compiler::fgMorphSmpOp(GenTree* tree, bool* optAssertionPropDone) case GT_OR: case GT_XOR: case GT_AND: + if (oper == GT_ADD || oper == GT_OR || oper == GT_AND) + { + tree = fgOptimizeDistributiveArithemtic(tree->AsOp()); + } tree = fgOptimizeCommutativeArithmetic(tree->AsOp()); if (!tree->OperIsSimple()) { @@ -10398,18 +10404,29 @@ GenTree* Compiler::fgOptimizeHWIntrinsicAssociative(GenTreeHWIntrinsic* tree) #endif // FEATURE_HW_INTRINSICS //------------------------------------------------------------------------ -// fgOptimizeDistributiveArithemtic: Optimizes distributive operations. +// fgOptimizeDistributiveArithemtic: Optimizes distributive arithemtic. // // Arguments: // tree - the unchecked GT_ADD/GT_SUB/GT_OR/GT_AND tree to optimize. // // Return Value: -// The optimized tree that can have any shape. +// The unchanged tree or optimized tree with oper GT_MUL/GT_OR/GT_AND. // -GenTreeOp* Compiler::fgOptimizeDistributiveArithemtic(GenTreeOp* tree) +GenTree* Compiler::fgOptimizeDistributiveArithemtic(GenTreeOp* tree) { + assert(tree->OperIs(GT_ADD, GT_SUB, GT_OR, GT_AND)); assert(!tree->gtOverflowEx()); + if (opts.OptimizationDisabled()) + { + return tree; + } + + if (!varTypeIsIntegralOrI(tree)) + { + return tree; + } + if (((tree->gtFlags & GTF_PERSISTENT_SIDE_EFFECTS) != 0) || ((tree->gtFlags & GTF_ORDER_SIDEEFF) != 0)) { return tree; @@ -10420,7 +10437,7 @@ GenTreeOp* Compiler::fgOptimizeDistributiveArithemtic(GenTreeOp* tree) auto isDistributiveOver = [](genTreeOps op1, genTreeOps op2) { // op1 is distributive over op2 iff: - // ((A op2 B) op1 (A op2 C)) <==> (A op1 (B op2 C)) + // ((A op1 B) op2 (A op1 C)) <==> (A op1 (B op2 C)) switch (op1) { @@ -10428,23 +10445,22 @@ GenTreeOp* Compiler::fgOptimizeDistributiveArithemtic(GenTreeOp* tree) return op2 == GT_ADD || op2 == GT_SUB; case GT_AND: - return op2 == GT_OR; + return op2 == GT_OR || op2 == GT_AND; case GT_OR: - return op2 == GT_AND; + return op2 == GT_AND || op2 == GT_OR; default: return false; } }; - if ((op1->OperGet() == op2->OperGet()) && isDistributiveOver(op1->OperGet(), tree->OperGet()) && - varTypeIsIntegralOrI(tree)) + if ((op1->OperGet() == op2->OperGet()) && isDistributiveOver(op1->OperGet(), tree->OperGet())) { if (GenTree::Compare(op1->gtGetOp1(), op2->gtGetOp1())) { tree->AsOp()->gtOp1 = op1->gtGetOp1(); - tree->AsOp()->gtOp2 = gtNewOperNode(tree->OperGet(), op1->TypeGet(), op1->gtGetOp2(), op2->gtGetOp2()); + tree->AsOp()->gtOp2 = gtNewOperNode(tree->OperGet(), tree->TypeGet(), op1->gtGetOp2(), op2->gtGetOp2()); tree->SetOper(op1->OperGet(), GenTree::PRESERVE_VN); fgMorphTreeDone(tree->gtGetOp2()); } @@ -10496,7 +10512,7 @@ void Compiler::fgPushConstantsRight(GenTreeOp* tree) } //------------------------------------------------------------------------ -// fgOptimizeCommutativeArithmetic: Optimizes commutative operations. +// fgOptimizeCommutativeArithmetic: Optimizes commutative arithemtic. // // Arguments: // tree - the unchecked GT_ADD/GT_MUL/GT_OR/GT_XOR/GT_AND tree to optimize. @@ -10536,11 +10552,6 @@ GenTree* Compiler::fgOptimizeCommutativeArithmetic(GenTreeOp* tree) } } - if (tree->OperIs(GT_ADD, GT_SUB, GT_MUL, GT_AND, GT_OR)) - { - tree = fgOptimizeDistributiveArithemtic(tree); - } - GenTree* optimizedTree = nullptr; if (tree->OperIs(GT_ADD)) { From 538dd18eb992d550922e8ff86da0cc3a13f6ce45 Mon Sep 17 00:00:00 2001 From: BoyBaykiller Date: Wed, 15 Apr 2026 02:46:12 +0200 Subject: [PATCH 03/11] * apply the opt to the code itself --- src/coreclr/jit/morph.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/jit/morph.cpp b/src/coreclr/jit/morph.cpp index b5116408cec007..d70ac36aacaa65 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -10427,7 +10427,7 @@ GenTree* Compiler::fgOptimizeDistributiveArithemtic(GenTreeOp* tree) return tree; } - if (((tree->gtFlags & GTF_PERSISTENT_SIDE_EFFECTS) != 0) || ((tree->gtFlags & GTF_ORDER_SIDEEFF) != 0)) + if ((tree->gtFlags & (GTF_PERSISTENT_SIDE_EFFECTS | GTF_ORDER_SIDEEFF)) != 0) { return tree; } From 5f2952d39688e3e89e4c70cd28abb80dfa7b87c5 Mon Sep 17 00:00:00 2001 From: BoyBaykiller Date: Wed, 15 Apr 2026 05:21:54 +0200 Subject: [PATCH 04/11] * call morph in optimizeBool to catch more cases after control flow simplification like '(A & 4) != 0 || (A & 8) != 0' --- src/coreclr/jit/optimizebools.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/coreclr/jit/optimizebools.cpp b/src/coreclr/jit/optimizebools.cpp index 54f2b5bc4203be..481a49aad65ab7 100644 --- a/src/coreclr/jit/optimizebools.cpp +++ b/src/coreclr/jit/optimizebools.cpp @@ -346,6 +346,9 @@ bool OptBoolsDsc::optOptimizeBoolsCondBlock() optOptimizeBoolsUpdateTrees(); + // There may be new opportunities for distributive arithmetic optimization + m_compiler->fgMorphBlockStmt(m_b1, s1 DEBUGARG(__FUNCTION__), false); + #ifdef DEBUG if (m_compiler->verbose) { From 93d2457dc218b0d33714e96fead10b7c96b0f01e Mon Sep 17 00:00:00 2001 From: BoyBaykiller Date: Sat, 18 Apr 2026 00:49:50 +0200 Subject: [PATCH 05/11] * call fgOptimizeDistributiveArithemtic directly instead of fgMorphBlockStmt * call gtFoldExpr --- src/coreclr/jit/compiler.h | 3 ++- src/coreclr/jit/morph.cpp | 3 ++- src/coreclr/jit/optimizebools.cpp | 9 ++++++--- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 6250af8c1a992c..b0f3eeb4682922 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -7118,6 +7118,8 @@ class Compiler public: GenTree* fgMorphInitBlock(GenTree* tree); GenTree* fgMorphCopyBlock(GenTree* tree); + + GenTree* fgOptimizeDistributiveArithemtic(GenTreeOp* tree); private: GenTree* fgMorphSmpOp(GenTree* tree, bool* optAssertionPropDone = nullptr); bool fgTryReplaceStructLocalWithFields(GenTree** use); @@ -7135,7 +7137,6 @@ class Compiler GenTree* fgOptimizeHWIntrinsic(GenTreeHWIntrinsic* node); GenTree* fgOptimizeHWIntrinsicAssociative(GenTreeHWIntrinsic* node); #endif // FEATURE_HW_INTRINSICS - GenTree* fgOptimizeDistributiveArithemtic(GenTreeOp* tree); GenTree* fgOptimizeCommutativeArithmetic(GenTreeOp* tree); GenTree* fgOptimizeRelationalComparisonWithCasts(GenTreeOp* cmp); GenTree* fgOptimizeAddition(GenTreeOp* add); diff --git a/src/coreclr/jit/morph.cpp b/src/coreclr/jit/morph.cpp index d70ac36aacaa65..a81d78168678c0 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -10460,7 +10460,8 @@ GenTree* Compiler::fgOptimizeDistributiveArithemtic(GenTreeOp* tree) if (GenTree::Compare(op1->gtGetOp1(), op2->gtGetOp1())) { tree->AsOp()->gtOp1 = op1->gtGetOp1(); - tree->AsOp()->gtOp2 = gtNewOperNode(tree->OperGet(), tree->TypeGet(), op1->gtGetOp2(), op2->gtGetOp2()); + tree->AsOp()->gtOp2 = + gtFoldExpr(gtNewOperNode(tree->OperGet(), tree->TypeGet(), op1->gtGetOp2(), op2->gtGetOp2())); tree->SetOper(op1->OperGet(), GenTree::PRESERVE_VN); fgMorphTreeDone(tree->gtGetOp2()); } diff --git a/src/coreclr/jit/optimizebools.cpp b/src/coreclr/jit/optimizebools.cpp index 481a49aad65ab7..1e9b2c1e23b717 100644 --- a/src/coreclr/jit/optimizebools.cpp +++ b/src/coreclr/jit/optimizebools.cpp @@ -346,9 +346,6 @@ bool OptBoolsDsc::optOptimizeBoolsCondBlock() optOptimizeBoolsUpdateTrees(); - // There may be new opportunities for distributive arithmetic optimization - m_compiler->fgMorphBlockStmt(m_b1, s1 DEBUGARG(__FUNCTION__), false); - #ifdef DEBUG if (m_compiler->verbose) { @@ -1201,6 +1198,12 @@ void OptBoolsDsc::optOptimizeBoolsUpdateTrees() GenTree* cmpOp1 = m_foldOp == GT_NONE ? m_c1 : m_compiler->gtNewOperNode(m_foldOp, m_foldType, m_c1, m_c2); + // There may be new opportunities for distributive arithmetic optimization + if (m_foldOp == GT_OR || m_foldOp == GT_AND) + { + cmpOp1 = m_compiler->fgOptimizeDistributiveArithemtic(cmpOp1->AsOp()); + } + GenTree* t1Comp = m_testInfo1.compTree; t1Comp->SetOper(m_cmpOp); t1Comp->AsOp()->gtOp1 = cmpOp1; From 83c4e4ddb40099777064b38d3f38c2daccdf9f85 Mon Sep 17 00:00:00 2001 From: BoyBaykiller Date: Sat, 18 Apr 2026 19:49:46 +0200 Subject: [PATCH 06/11] * add AND over XOR --- src/coreclr/jit/morph.cpp | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/src/coreclr/jit/morph.cpp b/src/coreclr/jit/morph.cpp index a81d78168678c0..f55680bcfc760d 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -8152,7 +8152,7 @@ GenTree* Compiler::fgMorphSmpOp(GenTree* tree, bool* optAssertionPropDone) case GT_OR: case GT_XOR: case GT_AND: - if (oper == GT_ADD || oper == GT_OR || oper == GT_AND) + if (oper != GT_MUL) { tree = fgOptimizeDistributiveArithemtic(tree->AsOp()); } @@ -10407,14 +10407,14 @@ GenTree* Compiler::fgOptimizeHWIntrinsicAssociative(GenTreeHWIntrinsic* tree) // fgOptimizeDistributiveArithemtic: Optimizes distributive arithemtic. // // Arguments: -// tree - the unchecked GT_ADD/GT_SUB/GT_OR/GT_AND tree to optimize. +// tree - the unchecked GT_AND/GT_OR/GT_XOR/GT_ADD/GT_SUB tree to optimize. // // Return Value: // The unchanged tree or optimized tree with oper GT_MUL/GT_OR/GT_AND. // GenTree* Compiler::fgOptimizeDistributiveArithemtic(GenTreeOp* tree) { - assert(tree->OperIs(GT_ADD, GT_SUB, GT_OR, GT_AND)); + assert(tree->OperIs(GT_AND, GT_OR, GT_XOR, GT_ADD, GT_SUB)); assert(!tree->gtOverflowEx()); if (opts.OptimizationDisabled()) @@ -10435,27 +10435,24 @@ GenTree* Compiler::fgOptimizeDistributiveArithemtic(GenTreeOp* tree) GenTree* op1 = tree->gtGetOp1(); GenTree* op2 = tree->gtGetOp2(); - auto isDistributiveOver = [](genTreeOps op1, genTreeOps op2) { - // op1 is distributive over op2 iff: - // ((A op1 B) op2 (A op1 C)) <==> (A op1 (B op2 C)) - + auto isLeftDistributive = [](genTreeOps op1, genTreeOps op2) { + // op1 is left distributive over op2 iff: + // "A op1 (B op2 C)" <==> "(A op1 B) op2 (A op1 C)" switch (op1) { - case GT_MUL: - return op2 == GT_ADD || op2 == GT_SUB; - case GT_AND: - return op2 == GT_OR || op2 == GT_AND; + return op2 == GT_OR || op2 == GT_XOR || op2 == GT_AND; case GT_OR: return op2 == GT_AND || op2 == GT_OR; - default: - return false; + case GT_MUL: + return op2 == GT_ADD || op2 == GT_SUB; } + return false; }; - if ((op1->OperGet() == op2->OperGet()) && isDistributiveOver(op1->OperGet(), tree->OperGet())) + if ((op1->OperGet() == op2->OperGet()) && isLeftDistributive(op1->OperGet(), tree->OperGet())) { if (GenTree::Compare(op1->gtGetOp1(), op2->gtGetOp1())) { From e2379fae7e9f6c94b2ce3331b1199ac936029231 Mon Sep 17 00:00:00 2001 From: BoyBaykiller Date: Sat, 18 Apr 2026 20:15:37 +0200 Subject: [PATCH 07/11] * fix build error on linux --- src/coreclr/jit/morph.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/coreclr/jit/morph.cpp b/src/coreclr/jit/morph.cpp index f55680bcfc760d..441ccdae627534 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -10448,8 +10448,10 @@ GenTree* Compiler::fgOptimizeDistributiveArithemtic(GenTreeOp* tree) case GT_MUL: return op2 == GT_ADD || op2 == GT_SUB; + + default: + return false; } - return false; }; if ((op1->OperGet() == op2->OperGet()) && isLeftDistributive(op1->OperGet(), tree->OperGet())) From 445e20d4bd3667cd06e5c4be863236abefd13946 Mon Sep 17 00:00:00 2001 From: BoyBaykiller Date: Mon, 20 Apr 2026 14:47:40 +0200 Subject: [PATCH 08/11] * fix typo --- src/coreclr/jit/compiler.h | 2 +- src/coreclr/jit/morph.cpp | 29 +++++++++++++++-------------- src/coreclr/jit/optimizebools.cpp | 2 +- 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index b0f3eeb4682922..3fbd1411e4b599 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -7119,7 +7119,7 @@ class Compiler GenTree* fgMorphInitBlock(GenTree* tree); GenTree* fgMorphCopyBlock(GenTree* tree); - GenTree* fgOptimizeDistributiveArithemtic(GenTreeOp* tree); + GenTree* fgOptimizeDistributiveArithmetic(GenTreeOp* tree); private: GenTree* fgMorphSmpOp(GenTree* tree, bool* optAssertionPropDone = nullptr); bool fgTryReplaceStructLocalWithFields(GenTree** use); diff --git a/src/coreclr/jit/morph.cpp b/src/coreclr/jit/morph.cpp index 441ccdae627534..221b7a2e734ed7 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -8152,7 +8152,7 @@ GenTree* Compiler::fgMorphSmpOp(GenTree* tree, bool* optAssertionPropDone) case GT_OR: case GT_XOR: case GT_AND: - if (oper != GT_MUL) + if (oper == GT_ADD || oper == GT_OR || oper == GT_AND) { tree = fgOptimizeDistributiveArithemtic(tree->AsOp()); } @@ -10407,14 +10407,14 @@ GenTree* Compiler::fgOptimizeHWIntrinsicAssociative(GenTreeHWIntrinsic* tree) // fgOptimizeDistributiveArithemtic: Optimizes distributive arithemtic. // // Arguments: -// tree - the unchecked GT_AND/GT_OR/GT_XOR/GT_ADD/GT_SUB tree to optimize. +// tree - the unchecked GT_ADD/GT_SUB/GT_OR/GT_AND tree to optimize. // // Return Value: // The unchanged tree or optimized tree with oper GT_MUL/GT_OR/GT_AND. // GenTree* Compiler::fgOptimizeDistributiveArithemtic(GenTreeOp* tree) { - assert(tree->OperIs(GT_AND, GT_OR, GT_XOR, GT_ADD, GT_SUB)); + assert(tree->OperIs(GT_ADD, GT_SUB, GT_OR, GT_AND)); assert(!tree->gtOverflowEx()); if (opts.OptimizationDisabled()) @@ -10427,7 +10427,7 @@ GenTree* Compiler::fgOptimizeDistributiveArithemtic(GenTreeOp* tree) return tree; } - if ((tree->gtFlags & (GTF_PERSISTENT_SIDE_EFFECTS | GTF_ORDER_SIDEEFF)) != 0) + if (((tree->gtFlags & GTF_PERSISTENT_SIDE_EFFECTS) != 0) || ((tree->gtFlags & GTF_ORDER_SIDEEFF) != 0)) { return tree; } @@ -10435,32 +10435,32 @@ GenTree* Compiler::fgOptimizeDistributiveArithemtic(GenTreeOp* tree) GenTree* op1 = tree->gtGetOp1(); GenTree* op2 = tree->gtGetOp2(); - auto isLeftDistributive = [](genTreeOps op1, genTreeOps op2) { - // op1 is left distributive over op2 iff: - // "A op1 (B op2 C)" <==> "(A op1 B) op2 (A op1 C)" + auto isDistributiveOver = [](genTreeOps op1, genTreeOps op2) { + // op1 is distributive over op2 iff: + // ((A op1 B) op2 (A op1 C)) <==> (A op1 (B op2 C)) + switch (op1) { + case GT_MUL: + return op2 == GT_ADD || op2 == GT_SUB; + case GT_AND: - return op2 == GT_OR || op2 == GT_XOR || op2 == GT_AND; + return op2 == GT_OR || op2 == GT_AND; case GT_OR: return op2 == GT_AND || op2 == GT_OR; - case GT_MUL: - return op2 == GT_ADD || op2 == GT_SUB; - default: return false; } }; - if ((op1->OperGet() == op2->OperGet()) && isLeftDistributive(op1->OperGet(), tree->OperGet())) + if ((op1->OperGet() == op2->OperGet()) && isDistributiveOver(op1->OperGet(), tree->OperGet())) { if (GenTree::Compare(op1->gtGetOp1(), op2->gtGetOp1())) { tree->AsOp()->gtOp1 = op1->gtGetOp1(); - tree->AsOp()->gtOp2 = - gtFoldExpr(gtNewOperNode(tree->OperGet(), tree->TypeGet(), op1->gtGetOp2(), op2->gtGetOp2())); + tree->AsOp()->gtOp2 = gtNewOperNode(tree->OperGet(), tree->TypeGet(), op1->gtGetOp2(), op2->gtGetOp2()); tree->SetOper(op1->OperGet(), GenTree::PRESERVE_VN); fgMorphTreeDone(tree->gtGetOp2()); } @@ -10470,6 +10470,7 @@ GenTree* Compiler::fgOptimizeDistributiveArithemtic(GenTreeOp* tree) } //------------------------------------------------------------------------ +<<<<<<< HEAD // fgPushConstantsRight: Pushes constants to the right to help canonicalize the shape // // Arguments: diff --git a/src/coreclr/jit/optimizebools.cpp b/src/coreclr/jit/optimizebools.cpp index 1e9b2c1e23b717..a0fe2c2e35b4ea 100644 --- a/src/coreclr/jit/optimizebools.cpp +++ b/src/coreclr/jit/optimizebools.cpp @@ -1201,7 +1201,7 @@ void OptBoolsDsc::optOptimizeBoolsUpdateTrees() // There may be new opportunities for distributive arithmetic optimization if (m_foldOp == GT_OR || m_foldOp == GT_AND) { - cmpOp1 = m_compiler->fgOptimizeDistributiveArithemtic(cmpOp1->AsOp()); + cmpOp1 = m_compiler->fgOptimizeDistributiveArithmetic(cmpOp1->AsOp()); } GenTree* t1Comp = m_testInfo1.compTree; From 2abb78c8c3113a3506eaad0c6af8ff556e9cf8c7 Mon Sep 17 00:00:00 2001 From: BoyBaykiller Date: Mon, 27 Apr 2026 19:46:09 +0200 Subject: [PATCH 09/11] * move to gtFoldExpr --- src/coreclr/jit/compiler.h | 3 +- src/coreclr/jit/gentree.cpp | 73 +++++++++++++++++++++++++++++++ src/coreclr/jit/morph.cpp | 70 ----------------------------- src/coreclr/jit/optimizebools.cpp | 4 +- 4 files changed, 76 insertions(+), 74 deletions(-) diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 3fbd1411e4b599..4e95fdcf4a707e 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -3941,6 +3941,7 @@ class Compiler GenTree* gtFoldExpr(GenTree* tree); GenTree* gtFoldExprConst(GenTree* tree); + GenTree* gtFoldDistributiveArithmetic(GenTree* tree); GenTree* gtFoldIndirConst(GenTreeIndir* indir); GenTree* gtFoldExprSpecial(GenTree* tree); GenTree* gtFoldExprSpecialFloating(GenTree* tree); @@ -7118,8 +7119,6 @@ class Compiler public: GenTree* fgMorphInitBlock(GenTree* tree); GenTree* fgMorphCopyBlock(GenTree* tree); - - GenTree* fgOptimizeDistributiveArithmetic(GenTreeOp* tree); private: GenTree* fgMorphSmpOp(GenTree* tree, bool* optAssertionPropDone = nullptr); bool fgTryReplaceStructLocalWithFields(GenTree** use); diff --git a/src/coreclr/jit/gentree.cpp b/src/coreclr/jit/gentree.cpp index d91d1961bc2ba4..0b6c29b737059a 100644 --- a/src/coreclr/jit/gentree.cpp +++ b/src/coreclr/jit/gentree.cpp @@ -15050,6 +15050,10 @@ GenTree* Compiler::gtFoldExpr(GenTree* tree) return gtFoldExprCompare(tree); } + else if (tree->OperIs(GT_AND, GT_OR, GT_XOR, GT_ADD, GT_SUB)) + { + return gtFoldDistributiveArithmetic(tree); + } } /* Return the original node (folded/bashed or not) */ @@ -18019,6 +18023,75 @@ GenTree* Compiler::gtFoldExprConst(GenTree* tree) return tree; } +//------------------------------------------------------------------------ +// gtFoldDistributiveArithmetic: Optimizes distributive Arithmetic. +// +// Arguments: +// tree - the unchecked GT_AND/GT_OR/GT_XOR/GT_ADD/GT_SUB tree to optimize. +// +// Return Value: +// The unchanged tree or optimized tree with oper GT_MUL/GT_OR/GT_AND. +// +GenTree* Compiler::gtFoldDistributiveArithmetic(GenTree* tree) +{ + assert(tree->OperIs(GT_AND, GT_OR, GT_XOR, GT_ADD, GT_SUB)); + + if (opts.OptimizationDisabled()) + { + return tree; + } + + if (tree->gtOverflowEx() || !varTypeIsIntegralOrI(tree)) + { + return tree; + } + + if ((tree->gtFlags & (GTF_PERSISTENT_SIDE_EFFECTS | GTF_ORDER_SIDEEFF)) != 0) + { + return tree; + } + + GenTree* op1 = tree->gtGetOp1(); + GenTree* op2 = tree->gtGetOp2(); + + auto isLeftDistributive = [](genTreeOps op1, genTreeOps op2) { + // op1 is left distributive over op2 iff: + // "A op1 (B op2 C)" <==> "(A op1 B) op2 (A op1 C)" + switch (op1) + { + case GT_AND: + return op2 == GT_OR || op2 == GT_XOR || op2 == GT_AND; + + case GT_OR: + return op2 == GT_AND || op2 == GT_OR; + + case GT_MUL: + return op2 == GT_ADD || op2 == GT_SUB; + + default: + return false; + } + }; + + if ((op1->OperGet() == op2->OperGet()) && isLeftDistributive(op1->OperGet(), tree->OperGet())) + { + if (GenTree::Compare(op1->gtGetOp1(), op2->gtGetOp1())) + { + tree->AsOp()->gtOp1 = op1->gtGetOp1(); + tree->AsOp()->gtOp2 = + gtFoldExpr(gtNewOperNode(tree->OperGet(), tree->TypeGet(), op1->gtGetOp2(), op2->gtGetOp2())); + tree->SetOper(op1->OperGet(), GenTree::PRESERVE_VN); + + if (fgGlobalMorph) + { + fgMorphTreeDone(tree->gtGetOp2()); + } + } + } + + return tree; +} + //------------------------------------------------------------------------ // gtFoldIndirConst: Attempt to fold an "IND(addr)" expression to a constant. // diff --git a/src/coreclr/jit/morph.cpp b/src/coreclr/jit/morph.cpp index 221b7a2e734ed7..4d1a2cd2d41be5 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -8152,10 +8152,6 @@ GenTree* Compiler::fgMorphSmpOp(GenTree* tree, bool* optAssertionPropDone) case GT_OR: case GT_XOR: case GT_AND: - if (oper == GT_ADD || oper == GT_OR || oper == GT_AND) - { - tree = fgOptimizeDistributiveArithemtic(tree->AsOp()); - } tree = fgOptimizeCommutativeArithmetic(tree->AsOp()); if (!tree->OperIsSimple()) { @@ -10403,72 +10399,6 @@ GenTree* Compiler::fgOptimizeHWIntrinsicAssociative(GenTreeHWIntrinsic* tree) } #endif // FEATURE_HW_INTRINSICS -//------------------------------------------------------------------------ -// fgOptimizeDistributiveArithemtic: Optimizes distributive arithemtic. -// -// Arguments: -// tree - the unchecked GT_ADD/GT_SUB/GT_OR/GT_AND tree to optimize. -// -// Return Value: -// The unchanged tree or optimized tree with oper GT_MUL/GT_OR/GT_AND. -// -GenTree* Compiler::fgOptimizeDistributiveArithemtic(GenTreeOp* tree) -{ - assert(tree->OperIs(GT_ADD, GT_SUB, GT_OR, GT_AND)); - assert(!tree->gtOverflowEx()); - - if (opts.OptimizationDisabled()) - { - return tree; - } - - if (!varTypeIsIntegralOrI(tree)) - { - return tree; - } - - if (((tree->gtFlags & GTF_PERSISTENT_SIDE_EFFECTS) != 0) || ((tree->gtFlags & GTF_ORDER_SIDEEFF) != 0)) - { - return tree; - } - - GenTree* op1 = tree->gtGetOp1(); - GenTree* op2 = tree->gtGetOp2(); - - auto isDistributiveOver = [](genTreeOps op1, genTreeOps op2) { - // op1 is distributive over op2 iff: - // ((A op1 B) op2 (A op1 C)) <==> (A op1 (B op2 C)) - - switch (op1) - { - case GT_MUL: - return op2 == GT_ADD || op2 == GT_SUB; - - case GT_AND: - return op2 == GT_OR || op2 == GT_AND; - - case GT_OR: - return op2 == GT_AND || op2 == GT_OR; - - default: - return false; - } - }; - - if ((op1->OperGet() == op2->OperGet()) && isDistributiveOver(op1->OperGet(), tree->OperGet())) - { - if (GenTree::Compare(op1->gtGetOp1(), op2->gtGetOp1())) - { - tree->AsOp()->gtOp1 = op1->gtGetOp1(); - tree->AsOp()->gtOp2 = gtNewOperNode(tree->OperGet(), tree->TypeGet(), op1->gtGetOp2(), op2->gtGetOp2()); - tree->SetOper(op1->OperGet(), GenTree::PRESERVE_VN); - fgMorphTreeDone(tree->gtGetOp2()); - } - } - - return tree; -} - //------------------------------------------------------------------------ <<<<<<< HEAD // fgPushConstantsRight: Pushes constants to the right to help canonicalize the shape diff --git a/src/coreclr/jit/optimizebools.cpp b/src/coreclr/jit/optimizebools.cpp index a0fe2c2e35b4ea..830c548c3807da 100644 --- a/src/coreclr/jit/optimizebools.cpp +++ b/src/coreclr/jit/optimizebools.cpp @@ -1199,9 +1199,9 @@ void OptBoolsDsc::optOptimizeBoolsUpdateTrees() GenTree* cmpOp1 = m_foldOp == GT_NONE ? m_c1 : m_compiler->gtNewOperNode(m_foldOp, m_foldType, m_c1, m_c2); // There may be new opportunities for distributive arithmetic optimization - if (m_foldOp == GT_OR || m_foldOp == GT_AND) + if (m_foldOp != GT_NONE) { - cmpOp1 = m_compiler->fgOptimizeDistributiveArithmetic(cmpOp1->AsOp()); + cmpOp1 = m_compiler->gtFoldExpr(cmpOp1); } GenTree* t1Comp = m_testInfo1.compTree; From b89a3c4f262c97a4b2a129f90e9fddbb1a51cce7 Mon Sep 17 00:00:00 2001 From: BoyBaykiller Date: Wed, 29 Apr 2026 04:18:40 +0200 Subject: [PATCH 10/11] * replace GenTree::Compare with lclCheck (gets us 0.84x of the diffs) --- src/coreclr/jit/gentree.cpp | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/coreclr/jit/gentree.cpp b/src/coreclr/jit/gentree.cpp index 0b6c29b737059a..d9ff69d669d6e5 100644 --- a/src/coreclr/jit/gentree.cpp +++ b/src/coreclr/jit/gentree.cpp @@ -18075,16 +18075,21 @@ GenTree* Compiler::gtFoldDistributiveArithmetic(GenTree* tree) if ((op1->OperGet() == op2->OperGet()) && isLeftDistributive(op1->OperGet(), tree->OperGet())) { - if (GenTree::Compare(op1->gtGetOp1(), op2->gtGetOp1())) + if (op1->gtGetOp1()->OperIs(GT_LCL_VAR) && op2->gtGetOp1()->OperIs(GT_LCL_VAR)) { - tree->AsOp()->gtOp1 = op1->gtGetOp1(); - tree->AsOp()->gtOp2 = - gtFoldExpr(gtNewOperNode(tree->OperGet(), tree->TypeGet(), op1->gtGetOp2(), op2->gtGetOp2())); - tree->SetOper(op1->OperGet(), GenTree::PRESERVE_VN); - - if (fgGlobalMorph) + bool sameLcl = + op1->gtGetOp1()->AsLclVarCommon()->GetLclNum() == op2->gtGetOp1()->AsLclVarCommon()->GetLclNum(); + if (sameLcl) { - fgMorphTreeDone(tree->gtGetOp2()); + tree->AsOp()->gtOp1 = op1->gtGetOp1(); + tree->AsOp()->gtOp2 = + gtFoldExpr(gtNewOperNode(tree->OperGet(), tree->TypeGet(), op1->gtGetOp2(), op2->gtGetOp2())); + tree->SetOper(op1->OperGet(), GenTree::PRESERVE_VN); + + if (fgGlobalMorph) + { + fgMorphTreeDone(tree->gtGetOp2()); + } } } } From 472e994658684540d98b3028e71ab649e20d054c Mon Sep 17 00:00:00 2001 From: BoyBaykiller Date: Sun, 10 May 2026 18:17:46 +0200 Subject: [PATCH 11/11] * allow for any OperIsAnyLocal() --- src/coreclr/jit/gentree.cpp | 6 ++---- src/coreclr/jit/morph.cpp | 3 --- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/coreclr/jit/gentree.cpp b/src/coreclr/jit/gentree.cpp index d9ff69d669d6e5..5d55dbeb2d084b 100644 --- a/src/coreclr/jit/gentree.cpp +++ b/src/coreclr/jit/gentree.cpp @@ -18075,11 +18075,9 @@ GenTree* Compiler::gtFoldDistributiveArithmetic(GenTree* tree) if ((op1->OperGet() == op2->OperGet()) && isLeftDistributive(op1->OperGet(), tree->OperGet())) { - if (op1->gtGetOp1()->OperIs(GT_LCL_VAR) && op2->gtGetOp1()->OperIs(GT_LCL_VAR)) + if (op1->gtGetOp1()->OperIsAnyLocal() && op2->gtGetOp1()->OperIsAnyLocal()) { - bool sameLcl = - op1->gtGetOp1()->AsLclVarCommon()->GetLclNum() == op2->gtGetOp1()->AsLclVarCommon()->GetLclNum(); - if (sameLcl) + if (GenTree::Compare(op1->gtGetOp1(), op2->gtGetOp1())) { tree->AsOp()->gtOp1 = op1->gtGetOp1(); tree->AsOp()->gtOp2 = diff --git a/src/coreclr/jit/morph.cpp b/src/coreclr/jit/morph.cpp index 4d1a2cd2d41be5..465c8c6335095b 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -7940,8 +7940,6 @@ GenTree* Compiler::fgMorphSmpOp(GenTree* tree, bool* optAssertionPropDone) goto CM_OVF_OP; } - // TODO: Call fgOptimizeDistributiveArithemtic + fgOptimizeCommutativeArithmetic - if (!fgGlobalMorph) { break; @@ -10400,7 +10398,6 @@ GenTree* Compiler::fgOptimizeHWIntrinsicAssociative(GenTreeHWIntrinsic* tree) #endif // FEATURE_HW_INTRINSICS //------------------------------------------------------------------------ -<<<<<<< HEAD // fgPushConstantsRight: Pushes constants to the right to help canonicalize the shape // // Arguments: