From fdef8cf0efb774900f8d7e38e58bfed5c92bb496 Mon Sep 17 00:00:00 2001 From: BoyBaykiller Date: Wed, 29 Apr 2026 05:55:42 +0200 Subject: [PATCH 1/5] * add missing 'CNS relop op2' to 'op2 relop* CNS' canonicalization * also canonicalize '(A & pow2) == pow2' to '(A & pow2) != 0' --- src/coreclr/jit/morph.cpp | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/coreclr/jit/morph.cpp b/src/coreclr/jit/morph.cpp index 7caba07734be6d..5e761b2a5c683e 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -7768,6 +7768,17 @@ GenTree* Compiler::fgMorphSmpOp(GenTree* tree, MorphAddrContext* mac, bool* optA case GT_EQ: case GT_NE: + // Change "CNS relop op2" to "op2 relop* CNS" + if (op1->IsIntegralConst() && tree->OperIsCompare() && gtCanSwapOrder(op1, op2)) + { + std::swap(tree->AsOp()->gtOp1, tree->AsOp()->gtOp2); + tree->gtOper = GenTree::SwapRelop(tree->OperGet()); + + oper = tree->OperGet(); + op1 = tree->gtGetOp1(); + op2 = tree->gtGetOp2(); + } + if (op2->IsIntegralConst()) { tree = fgOptimizeEqualityComparisonWithConst(tree->AsOp()); @@ -8808,6 +8819,17 @@ GenTree* Compiler::fgOptimizeEqualityComparisonWithConst(GenTreeOp* cmp) fgUpdateConstTreeValueNumber(op2); } + // Canonicalize '(A & pow2) == pow2' -> '(A & pow2) != 0' + if (cmp->OperIs(GT_EQ) && op1->OperIs(GT_AND) && op1->gtGetOp2()->IsIntegralConst()) + { + if (op1->gtGetOp2()->AsIntConCommon()->IconValue() == op2->IconValue()) + { + cmp->SetOper(GT_NE, GenTree::PRESERVE_VN); + op2->SetIntegralValue(0); + fgUpdateConstTreeValueNumber(op2); + } + } + // Here we look for the following tree // // EQ/NE From d0091bbac5bc8963f54f40e38194ae1eb2ade3a9 Mon Sep 17 00:00:00 2001 From: BoyBaykiller Date: Wed, 29 Apr 2026 08:21:30 +0200 Subject: [PATCH 2/5] * check if pow2, this misses 1 << 31... --- 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 5e761b2a5c683e..b2f7a8c317d3a1 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -8820,7 +8820,7 @@ GenTree* Compiler::fgOptimizeEqualityComparisonWithConst(GenTreeOp* cmp) } // Canonicalize '(A & pow2) == pow2' -> '(A & pow2) != 0' - if (cmp->OperIs(GT_EQ) && op1->OperIs(GT_AND) && op1->gtGetOp2()->IsIntegralConst()) + if (cmp->OperIs(GT_EQ) && op1->OperIs(GT_AND) && op1->gtGetOp2()->IsIntegralConstUnsignedPow2()) { if (op1->gtGetOp2()->AsIntConCommon()->IconValue() == op2->IconValue()) { From 6a09c552de1411957d237bb94328b1dff07e211a Mon Sep 17 00:00:00 2001 From: BoyBaykiller Date: Wed, 29 Apr 2026 08:27:44 +0200 Subject: [PATCH 3/5] * AsIntCon instead AsIntConCommon --- 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 b2f7a8c317d3a1..4ee68d6d561094 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -8822,7 +8822,7 @@ GenTree* Compiler::fgOptimizeEqualityComparisonWithConst(GenTreeOp* cmp) // Canonicalize '(A & pow2) == pow2' -> '(A & pow2) != 0' if (cmp->OperIs(GT_EQ) && op1->OperIs(GT_AND) && op1->gtGetOp2()->IsIntegralConstUnsignedPow2()) { - if (op1->gtGetOp2()->AsIntConCommon()->IconValue() == op2->IconValue()) + if (op1->gtGetOp2()->AsIntCon()->IconValue() == op2->IconValue()) { cmp->SetOper(GT_NE, GenTree::PRESERVE_VN); op2->SetIntegralValue(0); From 3fffbe1a98c3c684c4cbd0177179fa4d351f8568 Mon Sep 17 00:00:00 2001 From: BoyBaykiller Date: Wed, 29 Apr 2026 17:32:40 +0200 Subject: [PATCH 4/5] * AsIntCommon instead AsIntCon --- 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 4ee68d6d561094..63c671dd384292 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -8822,7 +8822,7 @@ GenTree* Compiler::fgOptimizeEqualityComparisonWithConst(GenTreeOp* cmp) // Canonicalize '(A & pow2) == pow2' -> '(A & pow2) != 0' if (cmp->OperIs(GT_EQ) && op1->OperIs(GT_AND) && op1->gtGetOp2()->IsIntegralConstUnsignedPow2()) { - if (op1->gtGetOp2()->AsIntCon()->IconValue() == op2->IconValue()) + if (op1->gtGetOp2()->AsIntConCommon()->IntegralValue() == op2->IntegralValue()) { cmp->SetOper(GT_NE, GenTree::PRESERVE_VN); op2->SetIntegralValue(0); From abab549107a9aaf600b8f9635f36b5a2c04380a2 Mon Sep 17 00:00:00 2001 From: BoyBaykiller Date: Wed, 29 Apr 2026 21:54:32 +0200 Subject: [PATCH 5/5] * add UnsignedIntegralValue to handle 1<<31 >> * add '(A & pow2) != pow2' -> '(A & pow2) == 0' >> * move opt up to execute first --- src/coreclr/jit/gentree.h | 8 ++++++++ src/coreclr/jit/morph.cpp | 25 ++++++++++++++----------- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/coreclr/jit/gentree.h b/src/coreclr/jit/gentree.h index e55073378cddd9..f3f87ed8110da7 100644 --- a/src/coreclr/jit/gentree.h +++ b/src/coreclr/jit/gentree.h @@ -3337,6 +3337,7 @@ struct GenTreeIntConCommon : public GenTree inline ssize_t IconValue() const; inline void SetIconValue(ssize_t val); inline INT64 IntegralValue() const; + inline UINT64 UnsignedIntegralValue() const; inline void SetIntegralValue(int64_t value); template @@ -3540,6 +3541,13 @@ inline INT64 GenTreeIntConCommon::IntegralValue() const #endif // TARGET_64BIT } +inline UINT64 GenTreeIntConCommon::UnsignedIntegralValue() const +{ + INT64 signExtended = IntegralValue(); + UINT64 zeroExtended = TypeIs(TYP_LONG) ? signExtended : (uint32_t)signExtended; + return zeroExtended; +} + inline void GenTreeIntConCommon::SetIntegralValue(int64_t value) { #ifdef TARGET_64BIT diff --git a/src/coreclr/jit/morph.cpp b/src/coreclr/jit/morph.cpp index 63c671dd384292..bcec7a85d7c9ff 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -8759,6 +8759,20 @@ GenTree* Compiler::fgOptimizeEqualityComparisonWithConst(GenTreeOp* cmp) GenTree* op1 = cmp->gtGetOp1(); GenTreeIntConCommon* op2 = cmp->gtGetOp2()->AsIntConCommon(); + // Canonicalize + // '(A & pow2) == pow2' -> '(A & pow2) != 0' + // '(A & pow2) != pow2' -> '(A & pow2) == 0' + if (op1->OperIs(GT_AND) && op1->gtGetOp2()->IsIntegralConst()) + { + if (BitOperations::PopCount(op2->UnsignedIntegralValue()) == 1 && + (op1->gtGetOp2()->AsIntConCommon()->IntegralValue() == op2->IntegralValue())) + { + cmp->SetOper(cmp->OperIs(GT_EQ) ? GT_NE : GT_EQ, GenTree::PRESERVE_VN); + op2->SetIntegralValue(0); + fgUpdateConstTreeValueNumber(op2); + } + } + // Fold: (-(x)) == 0 -> x == 0 (avoid neg on compare-to-zero) if (op1->OperIs(GT_NEG) && !op1->gtOverflowEx()) { @@ -8819,17 +8833,6 @@ GenTree* Compiler::fgOptimizeEqualityComparisonWithConst(GenTreeOp* cmp) fgUpdateConstTreeValueNumber(op2); } - // Canonicalize '(A & pow2) == pow2' -> '(A & pow2) != 0' - if (cmp->OperIs(GT_EQ) && op1->OperIs(GT_AND) && op1->gtGetOp2()->IsIntegralConstUnsignedPow2()) - { - if (op1->gtGetOp2()->AsIntConCommon()->IntegralValue() == op2->IntegralValue()) - { - cmp->SetOper(GT_NE, GenTree::PRESERVE_VN); - op2->SetIntegralValue(0); - fgUpdateConstTreeValueNumber(op2); - } - } - // Here we look for the following tree // // EQ/NE