From 0d5fb4a426fd802cc596cd0de48de1d938a802f9 Mon Sep 17 00:00:00 2001 From: BoyBaykiller Date: Sat, 4 Apr 2026 02:40:45 +0200 Subject: [PATCH 1/7] * Add 'CONST - x' -> 'xor x, CONST' transformation --- src/coreclr/jit/morph.cpp | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/src/coreclr/jit/morph.cpp b/src/coreclr/jit/morph.cpp index 9456da4e99c5fb..c241fa5b240c41 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -7897,16 +7897,28 @@ GenTree* Compiler::fgMorphSmpOp(GenTree* tree, MorphAddrContext* mac, bool* optA // node ends up feeding directly into a cast, for example in // GT_CAST(GT_SUB(0, s_1.ubyte)) - if (op1->IsIntegralConst(0)) { - tree->ChangeOper(GT_NEG); - tree->gtType = genActualType(op2->TypeGet()); + if (op1->IsIntegralConst(0)) + { + tree->ChangeOper(GT_NEG); + tree->gtType = genActualType(op2->TypeGet()); - tree->AsOp()->gtOp1 = op2; - tree->AsOp()->gtOp2 = nullptr; + tree->AsOp()->gtOp1 = op2; + tree->AsOp()->gtOp2 = nullptr; - DEBUG_DESTROY_NODE(op1); - return tree; + DEBUG_DESTROY_NODE(op1); + return tree; + } + + // Fold e.g "UINT64_MAX - x" -> "xor x, UINT64_MAX" + bool isLong = op2->TypeGet() == TYP_LONG; + if (op1->IsIntegralConst(-1) || op1->IsIntegralConst(isLong ? INT64_MAX : INT32_MAX)) + { + tree->ChangeOper(GT_XOR); + tree->AsOp()->gtOp1 = op2; + tree->AsOp()->gtOp2 = op1; + return fgMorphSmpOp(tree, mac); + } } tree->AsOp()->gtOp2 = op2 = gtNewOperNode(GT_NEG, genActualType(op2->TypeGet()), op2); From 35d21ee966bf6b6976ed08e20a53f8a0ac5efd6d Mon Sep 17 00:00:00 2001 From: BoyBaykiller Date: Sat, 4 Apr 2026 02:52:23 +0200 Subject: [PATCH 2/7] * use std::swap --- src/coreclr/jit/morph.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/coreclr/jit/morph.cpp b/src/coreclr/jit/morph.cpp index c241fa5b240c41..b8e3f5beee0f67 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -7915,8 +7915,7 @@ GenTree* Compiler::fgMorphSmpOp(GenTree* tree, MorphAddrContext* mac, bool* optA if (op1->IsIntegralConst(-1) || op1->IsIntegralConst(isLong ? INT64_MAX : INT32_MAX)) { tree->ChangeOper(GT_XOR); - tree->AsOp()->gtOp1 = op2; - tree->AsOp()->gtOp2 = op1; + std::swap(tree->AsOp()->gtOp1, tree->AsOp()->gtOp2); return fgMorphSmpOp(tree, mac); } } From 26c96f33386c76fc2c6cabf57a2108580213158d Mon Sep 17 00:00:00 2001 From: BoyBaykiller Date: Sat, 4 Apr 2026 07:12:52 +0200 Subject: [PATCH 3/7] try fix CI failure 'conversion from '__int64' to 'ssize_t', possible loss of data' --- src/coreclr/jit/morph.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/coreclr/jit/morph.cpp b/src/coreclr/jit/morph.cpp index b8e3f5beee0f67..53151b515307d7 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -7910,9 +7910,10 @@ GenTree* Compiler::fgMorphSmpOp(GenTree* tree, MorphAddrContext* mac, bool* optA return tree; } - // Fold e.g "UINT64_MAX - x" -> "xor x, UINT64_MAX" + // Fold e.g "UINT32_MAX - x" -> "xor x, UINT32_MAX" bool isLong = op2->TypeGet() == TYP_LONG; - if (op1->IsIntegralConst(-1) || op1->IsIntegralConst(isLong ? INT64_MAX : INT32_MAX)) + INT64 value = op1->AsIntConCommon()->IntegralValue(); + if (value == -1 || value == (isLong ? INT64_MAX : INT32_MAX)) { tree->ChangeOper(GT_XOR); std::swap(tree->AsOp()->gtOp1, tree->AsOp()->gtOp2); From b8a3a15c24fa60f95bcd83332a0dd786959c155c Mon Sep 17 00:00:00 2001 From: BoyBaykiller Date: Sat, 4 Apr 2026 09:27:03 +0200 Subject: [PATCH 4/7] * add missing GT_NOT to VN based folding to fix 29009 libraries_tests_no_tiered_compilation regression --- src/coreclr/jit/assertionprop.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/coreclr/jit/assertionprop.cpp b/src/coreclr/jit/assertionprop.cpp index ea235f6ea230a9..e926b4f077cf1a 100644 --- a/src/coreclr/jit/assertionprop.cpp +++ b/src/coreclr/jit/assertionprop.cpp @@ -6037,6 +6037,7 @@ Compiler::fgWalkResult Compiler::optVNBasedFoldCurStmt(BasicBlock* block, case GT_LSH: case GT_RSH: case GT_RSZ: + case GT_NOT: case GT_NEG: case GT_CAST: case GT_BITCAST: From 04f1758a2cceb8cf3c130f3723cb4cfb92ce1255 Mon Sep 17 00:00:00 2001 From: BoyBaykiller Date: Wed, 8 Apr 2026 01:35:01 +0200 Subject: [PATCH 5/7] * generalize to catch all cases * use 'IntegralRange::ForNode' for somewhat better range info (could be improved still) * move to fgOptimizeAddition --- src/coreclr/jit/morph.cpp | 81 ++++++++++++++++++++++++--------------- 1 file changed, 51 insertions(+), 30 deletions(-) diff --git a/src/coreclr/jit/morph.cpp b/src/coreclr/jit/morph.cpp index 53151b515307d7..40f47125a67645 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -7896,29 +7896,16 @@ GenTree* Compiler::fgMorphSmpOp(GenTree* tree, MorphAddrContext* mac, bool* optA // Otherwise we may sign-extend incorrectly in cases where the GT_NEG // node ends up feeding directly into a cast, for example in // GT_CAST(GT_SUB(0, s_1.ubyte)) - + if (op1->IsIntegralConst(0)) { - if (op1->IsIntegralConst(0)) - { - tree->ChangeOper(GT_NEG); - tree->gtType = genActualType(op2->TypeGet()); + tree->ChangeOper(GT_NEG); + tree->gtType = genActualType(op2->TypeGet()); - tree->AsOp()->gtOp1 = op2; - tree->AsOp()->gtOp2 = nullptr; - - DEBUG_DESTROY_NODE(op1); - return tree; - } + tree->AsOp()->gtOp1 = op2; + tree->AsOp()->gtOp2 = nullptr; - // Fold e.g "UINT32_MAX - x" -> "xor x, UINT32_MAX" - bool isLong = op2->TypeGet() == TYP_LONG; - INT64 value = op1->AsIntConCommon()->IntegralValue(); - if (value == -1 || value == (isLong ? INT64_MAX : INT32_MAX)) - { - tree->ChangeOper(GT_XOR); - std::swap(tree->AsOp()->gtOp1, tree->AsOp()->gtOp2); - return fgMorphSmpOp(tree, mac); - } + DEBUG_DESTROY_NODE(op1); + return tree; } tree->AsOp()->gtOp2 = op2 = gtNewOperNode(GT_NEG, genActualType(op2->TypeGet()), op2); @@ -10436,19 +10423,53 @@ GenTree* Compiler::fgOptimizeAddition(GenTreeOp* add) } } - // - a + b = > b - a - // ADD(NEG(a), b) => SUB(b, a) - - // Do not do this if "op2" is constant for canonicalization purposes. - if (op1->OperIs(GT_NEG) && !op2->OperIs(GT_NEG) && !op2->IsIntegralConst() && gtCanSwapOrder(op1, op2)) + if (op1->OperIs(GT_NEG) && !op2->OperIs(GT_NEG)) { - add->SetOper(GT_SUB); - add->gtOp1 = op2; - add->gtOp2 = op1->AsOp()->gtGetOp1(); + if (op2->IsIntegralConst()) + { + // ADD(NEG(x), CONST) => XOR(x, CONST) - DEBUG_DESTROY_NODE(op1); + auto isSubToXorValid = [=](uint64_t cns, IntegralRange range) { + // cns - x, where x in [lo, hi] + uint64_t lo = IntegralRange::SymbolicToRealValue(range.GetLowerBound()); + uint64_t hi = IntegralRange::SymbolicToRealValue(range.GetUpperBound()); - return add; + // This mask is a OR of all numbers in [lo, hi] + uint64_t mask = UINT64_MAX >> BitOperations::LeadingZeroCount(lo ^ hi); + mask = lo | mask; + + // Borrowing is never performed on MSB (instead overflow occurs), so + // we can allow it to be 0. This handles cases like int.MaxValue - x + uint32_t sizeInBits = genTypeSize(add->TypeGet()) * BITS_PER_BYTE; + mask &= (1ULL << (sizeInBits - 1)) - 1; + + // At every bit pos with a 1 in mask, cns also needs 1. + // Otherwise borrowing occurs and XOR is not equivalent to SUB + return (cns & mask) == mask; + }; + + IntegralRange range = IntegralRange::ForNode(op1->gtGetOp1(), this); + uint64_t cns = (uint64_t)op2->AsIntConCommon()->IntegralValue(); + if (isSubToXorValid(cns, range)) + { + add->ChangeOper(GT_XOR); + add->gtOp1 = op1->gtGetOp1(); + return fgMorphTree(add); + } + } + + // - a + b => b - a + // ADD(NEG(a), b) => SUB(b, a) + // Do not do this if "op2" is constant for canonicalization purposes. + if (!op2->IsIntegralConst() && gtCanSwapOrder(op1, op2)) + { + add->SetOper(GT_SUB); + add->gtOp1 = op2; + add->gtOp2 = op1->AsOp()->gtGetOp1(); + + DEBUG_DESTROY_NODE(op1); + return add; + } } // a + -b = > a - b From 9503eb469be69f2cfa70c0230720bded32204257 Mon Sep 17 00:00:00 2001 From: BoyBaykiller Date: Fri, 10 Apr 2026 04:56:04 +0200 Subject: [PATCH 6/7] * feedback --- 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 40f47125a67645..7e69a04559de34 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -10435,7 +10435,7 @@ GenTree* Compiler::fgOptimizeAddition(GenTreeOp* add) uint64_t hi = IntegralRange::SymbolicToRealValue(range.GetUpperBound()); // This mask is a OR of all numbers in [lo, hi] - uint64_t mask = UINT64_MAX >> BitOperations::LeadingZeroCount(lo ^ hi); + uint64_t mask = (lo == hi) ? 0 : (UINT64_MAX >> BitOperations::LeadingZeroCount(lo ^ hi)); mask = lo | mask; // Borrowing is never performed on MSB (instead overflow occurs), so From 02e294abef2074a3557a3227615493c6df7420e6 Mon Sep 17 00:00:00 2001 From: BoyBaykiller Date: Sun, 12 Apr 2026 02:55:04 +0200 Subject: [PATCH 7/7] * use SetOper instead ChangeOper and use PRESERVE_VN --- src/coreclr/jit/morph.cpp | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/coreclr/jit/morph.cpp b/src/coreclr/jit/morph.cpp index 7e69a04559de34..7e33db0c18bc14 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -10434,25 +10434,24 @@ GenTree* Compiler::fgOptimizeAddition(GenTreeOp* add) uint64_t lo = IntegralRange::SymbolicToRealValue(range.GetLowerBound()); uint64_t hi = IntegralRange::SymbolicToRealValue(range.GetUpperBound()); - // This mask is a OR of all numbers in [lo, hi] - uint64_t mask = (lo == hi) ? 0 : (UINT64_MAX >> BitOperations::LeadingZeroCount(lo ^ hi)); - mask = lo | mask; + // OR of all numbers in [lo, hi] + uint64_t knownBits = (lo == hi) ? 0 : (UINT64_MAX >> BitOperations::LeadingZeroCount(lo ^ hi)); + knownBits = lo | knownBits; - // Borrowing is never performed on MSB (instead overflow occurs), so - // we can allow it to be 0. This handles cases like int.MaxValue - x + // Zero out bits outside of TYPE. This handles cases that rely on overflow uint32_t sizeInBits = genTypeSize(add->TypeGet()) * BITS_PER_BYTE; - mask &= (1ULL << (sizeInBits - 1)) - 1; + knownBits &= (1ULL << (sizeInBits - 1)) - 1; - // At every bit pos with a 1 in mask, cns also needs 1. + // At every bit pos with a 1 in knownBits, cns also needs 1. // Otherwise borrowing occurs and XOR is not equivalent to SUB - return (cns & mask) == mask; + return (cns & knownBits) == knownBits; }; IntegralRange range = IntegralRange::ForNode(op1->gtGetOp1(), this); uint64_t cns = (uint64_t)op2->AsIntConCommon()->IntegralValue(); if (isSubToXorValid(cns, range)) { - add->ChangeOper(GT_XOR); + add->SetOper(GT_XOR, GenTree::PRESERVE_VN); add->gtOp1 = op1->gtGetOp1(); return fgMorphTree(add); }