From 8334eef40bc532837bc62399b4d3cf2263629964 Mon Sep 17 00:00:00 2001 From: EgorBo Date: Fri, 28 Nov 2025 15:59:31 +0100 Subject: [PATCH 1/4] Fold Span.Slice(X) bound checks --- src/coreclr/jit/assertionprop.cpp | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/coreclr/jit/assertionprop.cpp b/src/coreclr/jit/assertionprop.cpp index 651ce13ca5092e..1527737725d175 100644 --- a/src/coreclr/jit/assertionprop.cpp +++ b/src/coreclr/jit/assertionprop.cpp @@ -4461,6 +4461,31 @@ GenTree* Compiler::optAssertionPropGlobal_RelOp(ASSERT_VALARG_TP assertions, return optAssertionProp_Update(newTree, tree, stmt); } } + + // If op1VN is actually ADD(X, CNS), we can try peeling the constant offset and adjusting op2Cns accordingly. + // + ValueNum peeledOp1VN = op1VN; + int peeledOffset = 0; + vnStore->PeelOffsetsI32(&peeledOp1VN, &peeledOffset); + + if (peeledOffset != 0) + { + Range peeledOp1Rng = Range(Limit(Limit::keUnknown)); + if (RangeCheck::TryGetRangeFromAssertions(this, peeledOp1VN, assertions, &peeledOp1Rng)) + { + Range peeledOffsetRng = Range(Limit(Limit::keConstant, peeledOffset)); + rng2 = RangeOps::Add(rng2, peeledOffsetRng); // Add handles overflow internally. + + RangeOps::RelationKind kind = + RangeOps::EvalRelop(tree->OperGet(), tree->IsUnsigned(), peeledOp1Rng, rng2); + if ((kind != RangeOps::RelationKind::Unknown)) + { + newTree = kind == RangeOps::RelationKind::AlwaysTrue ? gtNewTrue() : gtNewFalse(); + newTree = gtWrapWithSideEffects(newTree, tree, GTF_ALL_EFFECT); + return optAssertionProp_Update(newTree, tree, stmt); + } + } + } } // Else check if we have an equality check involving a local or an indir From 9adbf3f3566bd283c1b90147c21f5a86c901dd47 Mon Sep 17 00:00:00 2001 From: EgorBo Date: Fri, 28 Nov 2025 16:07:08 +0100 Subject: [PATCH 2/4] change Add to Subtract --- src/coreclr/jit/assertionprop.cpp | 2 +- src/coreclr/jit/rangecheck.h | 59 +++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/src/coreclr/jit/assertionprop.cpp b/src/coreclr/jit/assertionprop.cpp index 1527737725d175..1158f9308635c8 100644 --- a/src/coreclr/jit/assertionprop.cpp +++ b/src/coreclr/jit/assertionprop.cpp @@ -4474,7 +4474,7 @@ GenTree* Compiler::optAssertionPropGlobal_RelOp(ASSERT_VALARG_TP assertions, if (RangeCheck::TryGetRangeFromAssertions(this, peeledOp1VN, assertions, &peeledOp1Rng)) { Range peeledOffsetRng = Range(Limit(Limit::keConstant, peeledOffset)); - rng2 = RangeOps::Add(rng2, peeledOffsetRng); // Add handles overflow internally. + rng2 = RangeOps::Subtract(rng2, peeledOffsetRng); // Subtract handles overflow internally. RangeOps::RelationKind kind = RangeOps::EvalRelop(tree->OperGet(), tree->IsUnsigned(), peeledOp1Rng, rng2); diff --git a/src/coreclr/jit/rangecheck.h b/src/coreclr/jit/rangecheck.h index 45e88633f14d88..8038d8df520f86 100644 --- a/src/coreclr/jit/rangecheck.h +++ b/src/coreclr/jit/rangecheck.h @@ -328,6 +328,24 @@ struct RangeOps return Limit(Limit::keUnknown); } + static Limit SubtractConstantLimit(const Limit& value, const Limit& cns) + { + assert(cns.IsConstant()); + + if (cns.GetConstant() == INT_MIN) + { + // Subtracting INT_MIN would overflow + return Limit(Limit::keUnknown); + } + + Limit l = value; + if (l.AddConstant(-cns.GetConstant())) + { + return l; + } + return Limit(Limit::keUnknown); + } + // Perform 'value' * 'cns' static Limit MultiplyConstantLimit(const Limit& value, const Limit& cns) { @@ -393,6 +411,47 @@ struct RangeOps return result; } + // Given two ranges "r1" and "r2", perform an add operation on the + // ranges. + static Range Subtract(Range& r1, Range& r2) + { + Limit& r1lo = r1.LowerLimit(); + Limit& r1hi = r1.UpperLimit(); + Limit& r2lo = r2.LowerLimit(); + Limit& r2hi = r2.UpperLimit(); + + Range result = Limit(Limit::keUnknown); + + // Check lo ranges if they are dependent and not unknown. + if ((r1lo.IsDependent() && !r1lo.IsUnknown()) || (r2lo.IsDependent() && !r2lo.IsUnknown())) + { + result.lLimit = Limit(Limit::keDependent); + } + // Check hi ranges if they are dependent and not unknown. + if ((r1hi.IsDependent() && !r1hi.IsUnknown()) || (r2hi.IsDependent() && !r2hi.IsUnknown())) + { + result.uLimit = Limit(Limit::keDependent); + } + + if (r1lo.IsConstant()) + { + result.lLimit = SubtractConstantLimit(r2lo, r1lo); + } + if (r2lo.IsConstant()) + { + result.lLimit = SubtractConstantLimit(r1lo, r2lo); + } + if (r1hi.IsConstant()) + { + result.uLimit = SubtractConstantLimit(r2hi, r1hi); + } + if (r2hi.IsConstant()) + { + result.uLimit = SubtractConstantLimit(r1hi, r2hi); + } + return result; + } + static Range ShiftRight(Range& r1, Range& r2) { Limit& r1lo = r1.LowerLimit(); From 69f0939c5f4e98c1b76ef41b8f7c56d1eda27a97 Mon Sep 17 00:00:00 2001 From: EgorBo Date: Fri, 28 Nov 2025 17:11:55 +0100 Subject: [PATCH 3/4] clean up --- src/coreclr/jit/assertionprop.cpp | 7 +- src/coreclr/jit/rangecheck.h | 109 +++++++----------------------- 2 files changed, 29 insertions(+), 87 deletions(-) diff --git a/src/coreclr/jit/assertionprop.cpp b/src/coreclr/jit/assertionprop.cpp index 1158f9308635c8..a63352ec6b5c26 100644 --- a/src/coreclr/jit/assertionprop.cpp +++ b/src/coreclr/jit/assertionprop.cpp @@ -4470,11 +4470,12 @@ GenTree* Compiler::optAssertionPropGlobal_RelOp(ASSERT_VALARG_TP assertions, if (peeledOffset != 0) { - Range peeledOp1Rng = Range(Limit(Limit::keUnknown)); + Range peeledOffsetRng = Range(Limit(Limit::keConstant, peeledOffset)); + Range peeledOp1Rng = Range(Limit(Limit::keUnknown)); if (RangeCheck::TryGetRangeFromAssertions(this, peeledOp1VN, assertions, &peeledOp1Rng)) { - Range peeledOffsetRng = Range(Limit(Limit::keConstant, peeledOffset)); - rng2 = RangeOps::Subtract(rng2, peeledOffsetRng); // Subtract handles overflow internally. + // Subtract handles overflow internally. + rng2 = RangeOps::Subtract(rng2, peeledOffsetRng); RangeOps::RelationKind kind = RangeOps::EvalRelop(tree->OperGet(), tree->IsUnsigned(), peeledOp1Rng, rng2); diff --git a/src/coreclr/jit/rangecheck.h b/src/coreclr/jit/rangecheck.h index 8038d8df520f86..b4959670b5f8b3 100644 --- a/src/coreclr/jit/rangecheck.h +++ b/src/coreclr/jit/rangecheck.h @@ -328,6 +328,7 @@ struct RangeOps return Limit(Limit::keUnknown); } + // Perform 'value' - 'cns' static Limit SubtractConstantLimit(const Limit& value, const Limit& cns) { assert(cns.IsConstant()); @@ -370,9 +371,9 @@ struct RangeOps return Limit(Limit::keUnknown); } - // Given two ranges "r1" and "r2", perform an add operation on the - // ranges. - static Range Add(Range& r1, Range& r2) + // Given two ranges "r1" and "r2", perform a generic 'op' operation on the ranges. + template + static Range ApplyRangeOp(Range& r1, Range& r2, Operation op) { Limit& r1lo = r1.LowerLimit(); Limit& r1hi = r1.UpperLimit(); @@ -394,62 +395,43 @@ struct RangeOps if (r1lo.IsConstant()) { - result.lLimit = AddConstantLimit(r2lo, r1lo); + result.lLimit = op(r2lo, r1lo); } if (r2lo.IsConstant()) { - result.lLimit = AddConstantLimit(r1lo, r2lo); + result.lLimit = op(r1lo, r2lo); } if (r1hi.IsConstant()) { - result.uLimit = AddConstantLimit(r2hi, r1hi); + result.uLimit = op(r2hi, r1hi); } if (r2hi.IsConstant()) { - result.uLimit = AddConstantLimit(r1hi, r2hi); + result.uLimit = op(r1hi, r2hi); } + return result; } - // Given two ranges "r1" and "r2", perform an add operation on the - // ranges. - static Range Subtract(Range& r1, Range& r2) + static Range Add(Range& r1, Range& r2) { - Limit& r1lo = r1.LowerLimit(); - Limit& r1hi = r1.UpperLimit(); - Limit& r2lo = r2.LowerLimit(); - Limit& r2hi = r2.UpperLimit(); - - Range result = Limit(Limit::keUnknown); + return ApplyRangeOp(r1, r2, [](Limit& a, Limit& b) { + return AddConstantLimit(a, b); + }); + } - // Check lo ranges if they are dependent and not unknown. - if ((r1lo.IsDependent() && !r1lo.IsUnknown()) || (r2lo.IsDependent() && !r2lo.IsUnknown())) - { - result.lLimit = Limit(Limit::keDependent); - } - // Check hi ranges if they are dependent and not unknown. - if ((r1hi.IsDependent() && !r1hi.IsUnknown()) || (r2hi.IsDependent() && !r2hi.IsUnknown())) - { - result.uLimit = Limit(Limit::keDependent); - } + static Range Subtract(Range& r1, Range& r2) + { + return ApplyRangeOp(r1, r2, [](Limit& a, Limit& b) { + return SubtractConstantLimit(a, b); + }); + } - if (r1lo.IsConstant()) - { - result.lLimit = SubtractConstantLimit(r2lo, r1lo); - } - if (r2lo.IsConstant()) - { - result.lLimit = SubtractConstantLimit(r1lo, r2lo); - } - if (r1hi.IsConstant()) - { - result.uLimit = SubtractConstantLimit(r2hi, r1hi); - } - if (r2hi.IsConstant()) - { - result.uLimit = SubtractConstantLimit(r1hi, r2hi); - } - return result; + static Range Multiply(Range& r1, Range& r2) + { + return ApplyRangeOp(r1, r2, [](Limit& a, Limit& b) { + return MultiplyConstantLimit(a, b); + }); } static Range ShiftRight(Range& r1, Range& r2) @@ -489,47 +471,6 @@ struct RangeOps return result; } - // Given two ranges "r1" and "r2", perform an multiply operation on the - // ranges. - static Range Multiply(Range& r1, Range& r2) - { - Limit& r1lo = r1.LowerLimit(); - Limit& r1hi = r1.UpperLimit(); - Limit& r2lo = r2.LowerLimit(); - Limit& r2hi = r2.UpperLimit(); - - Range result = Limit(Limit::keUnknown); - - // Check lo ranges if they are dependent and not unknown. - if ((r1lo.IsDependent() && !r1lo.IsUnknown()) || (r2lo.IsDependent() && !r2lo.IsUnknown())) - { - result.lLimit = Limit(Limit::keDependent); - } - // Check hi ranges if they are dependent and not unknown. - if ((r1hi.IsDependent() && !r1hi.IsUnknown()) || (r2hi.IsDependent() && !r2hi.IsUnknown())) - { - result.uLimit = Limit(Limit::keDependent); - } - - if (r1lo.IsConstant()) - { - result.lLimit = MultiplyConstantLimit(r2lo, r1lo); - } - if (r2lo.IsConstant()) - { - result.lLimit = MultiplyConstantLimit(r1lo, r2lo); - } - if (r1hi.IsConstant()) - { - result.uLimit = MultiplyConstantLimit(r2hi, r1hi); - } - if (r2hi.IsConstant()) - { - result.uLimit = MultiplyConstantLimit(r1hi, r2hi); - } - return result; - } - // Given two ranges "r1" and "r2", do a Phi merge. If "monIncreasing" is true, // then ignore the dependent variables for the lower bound but not for the upper bound. static Range Merge(const Range& r1, const Range& r2, bool monIncreasing) From dd5ef963a585cf414c8864bb22e0155a245f990c Mon Sep 17 00:00:00 2001 From: EgorBo Date: Fri, 28 Nov 2025 18:22:10 +0100 Subject: [PATCH 4/4] disable unsigned comparisons --- src/coreclr/jit/assertionprop.cpp | 38 +++++++++++++++++-------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/src/coreclr/jit/assertionprop.cpp b/src/coreclr/jit/assertionprop.cpp index a63352ec6b5c26..61cfb57382a612 100644 --- a/src/coreclr/jit/assertionprop.cpp +++ b/src/coreclr/jit/assertionprop.cpp @@ -4463,27 +4463,31 @@ GenTree* Compiler::optAssertionPropGlobal_RelOp(ASSERT_VALARG_TP assertions, } // If op1VN is actually ADD(X, CNS), we can try peeling the constant offset and adjusting op2Cns accordingly. + // It's a bit more complicated for unsigned comparisons, so only do it for signed ones for now. // - ValueNum peeledOp1VN = op1VN; - int peeledOffset = 0; - vnStore->PeelOffsetsI32(&peeledOp1VN, &peeledOffset); - - if (peeledOffset != 0) + if (!tree->IsUnsigned()) { - Range peeledOffsetRng = Range(Limit(Limit::keConstant, peeledOffset)); - Range peeledOp1Rng = Range(Limit(Limit::keUnknown)); - if (RangeCheck::TryGetRangeFromAssertions(this, peeledOp1VN, assertions, &peeledOp1Rng)) - { - // Subtract handles overflow internally. - rng2 = RangeOps::Subtract(rng2, peeledOffsetRng); + ValueNum peeledOp1VN = op1VN; + int peeledOffset = 0; + vnStore->PeelOffsetsI32(&peeledOp1VN, &peeledOffset); - RangeOps::RelationKind kind = - RangeOps::EvalRelop(tree->OperGet(), tree->IsUnsigned(), peeledOp1Rng, rng2); - if ((kind != RangeOps::RelationKind::Unknown)) + if (peeledOffset != 0) + { + Range peeledOffsetRng = Range(Limit(Limit::keConstant, peeledOffset)); + Range peeledOp1Rng = Range(Limit(Limit::keUnknown)); + if (RangeCheck::TryGetRangeFromAssertions(this, peeledOp1VN, assertions, &peeledOp1Rng)) { - newTree = kind == RangeOps::RelationKind::AlwaysTrue ? gtNewTrue() : gtNewFalse(); - newTree = gtWrapWithSideEffects(newTree, tree, GTF_ALL_EFFECT); - return optAssertionProp_Update(newTree, tree, stmt); + // Subtract handles overflow internally. + rng2 = RangeOps::Subtract(rng2, peeledOffsetRng); + + RangeOps::RelationKind kind = + RangeOps::EvalRelop(tree->OperGet(), tree->IsUnsigned(), peeledOp1Rng, rng2); + if ((kind != RangeOps::RelationKind::Unknown)) + { + newTree = kind == RangeOps::RelationKind::AlwaysTrue ? gtNewTrue() : gtNewFalse(); + newTree = gtWrapWithSideEffects(newTree, tree, GTF_ALL_EFFECT); + return optAssertionProp_Update(newTree, tree, stmt); + } } } }