From 1860741db2a99cc1a3ac1236822f657be9be4d10 Mon Sep 17 00:00:00 2001 From: Sergey Andreenko Date: Thu, 21 Nov 2019 12:19:07 -0800 Subject: [PATCH 1/6] add a new test case to osr015. --- .../tests/src/JIT/jit64/opt/osr/osr015.il | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/src/coreclr/tests/src/JIT/jit64/opt/osr/osr015.il b/src/coreclr/tests/src/JIT/jit64/opt/osr/osr015.il index 6e31c2e50ab884..5c206755a4dc17 100644 --- a/src/coreclr/tests/src/JIT/jit64/opt/osr/osr015.il +++ b/src/coreclr/tests/src/JIT/jit64/opt/osr/osr015.il @@ -1485,6 +1485,29 @@ TRY_END_b029: TRY_END_b030: + .try + { + call void test::test_001c() + ldstr "Fail: test_001c" + call void [System.Console]System.Console::WriteLine(string) + ldc.i4 1 + stloc 0 + leave TRY_END_c001 + } + catch [mscorlib]System.IndexOutOfRangeException + { + leave TRY_END_c001 + } + catch [mscorlib]System.Exception + { + ldstr "Fail: test_001c" + call void [System.Console]System.Console::WriteLine(string) + call void [System.Console]System.Console::WriteLine(object) + ldc.i4 1 + stloc 0 + leave TRY_END_c001 + } +TRY_END_c001: @@ -4138,6 +4161,55 @@ FOR_TEST: + .method public static void test_001c() cil managed + { +// Check for exception +// use int32 +// Exceed array bounds with greater than +// with a back branch that goes to a try region +// so Jit can't clone the loop check and transofm it +// to a postcondition loop. + + + .locals init (int32[], int32) + + + ldc.i4 100 + newarr int32 + stloc 0 + + +FOR_START: + ldloc 1 + ldc.i4 100 + bgt FOR_END + + .try + { + ldloc 0 + ldloc 1 + ldloc 1 + stelem.i4 + leave TRY_END_a + } + catch [mscorlib]System.IndexOutOfRangeException + { + leave TRY_END_a + } + TRY_END_a: + + ldloc 1 + ldc.i4 1 + add + stloc 1 + br FOR_START + +FOR_END: + + ret + + } + From 42398903d28aea18476a773a5ea994ca38d6814b Mon Sep 17 00:00:00 2001 From: Sergey Andreenko Date: Thu, 21 Nov 2019 14:26:06 -0800 Subject: [PATCH 2/6] Improve JitDump output for range checks. --- src/coreclr/src/jit/rangecheck.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/coreclr/src/jit/rangecheck.cpp b/src/coreclr/src/jit/rangecheck.cpp index 9220ce96b7cd68..2fc277d851dffe 100644 --- a/src/coreclr/src/jit/rangecheck.cpp +++ b/src/coreclr/src/jit/rangecheck.cpp @@ -313,9 +313,9 @@ void RangeCheck::Widen(BasicBlock* block, GenTree* tree, Range* pRange) { // To determine the lower bound, ask if the loop increases monotonically. bool increasing = IsMonotonicallyIncreasing(tree, false); - JITDUMP("IsMonotonicallyIncreasing %d", increasing); if (increasing) { + JITDUMP("[%06d] is monotonically increasing.\n", Compiler::dspTreeID(tree)); GetRangeMap()->RemoveAll(); *pRange = GetRange(block, tree, true DEBUGARG(0)); } @@ -1039,6 +1039,7 @@ bool RangeCheck::ComputeDoesOverflow(BasicBlock* block, GenTree* expr) } GetOverflowMap()->Set(expr, overflows, OverflowMap::Overwrite); m_pSearchPath->Remove(expr); + JITDUMP("[%06d] %s\n", Compiler::dspTreeID(expr), ((overflows) ? "overflows" : "does not overflow")); return overflows; } From 9ef0ca3452649eba443ab2276d09ca8f5753b2b3 Mon Sep 17 00:00:00 2001 From: Sergey Andreenko Date: Thu, 21 Nov 2019 14:35:12 -0800 Subject: [PATCH 3/6] Rename `monotonic` to `monIncreasing`. --- src/coreclr/src/jit/rangecheck.cpp | 32 +++++++++++++++--------------- src/coreclr/src/jit/rangecheck.h | 24 +++++++++++----------- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/src/coreclr/src/jit/rangecheck.cpp b/src/coreclr/src/jit/rangecheck.cpp index 2fc277d851dffe..876f987437446f 100644 --- a/src/coreclr/src/jit/rangecheck.cpp +++ b/src/coreclr/src/jit/rangecheck.cpp @@ -338,7 +338,7 @@ bool RangeCheck::IsBinOpMonotonicallyIncreasing(GenTreeOp* binop) } if (op1->OperGet() != GT_LCL_VAR) { - JITDUMP("Not monotonic because op1 is not lclVar.\n"); + JITDUMP("Not monIncreasing because op1 is not lclVar.\n"); return false; } switch (op2->OperGet()) @@ -351,7 +351,7 @@ bool RangeCheck::IsBinOpMonotonicallyIncreasing(GenTreeOp* binop) return (op2->AsIntConCommon()->IconValue() >= 0) && IsMonotonicallyIncreasing(op1, false); default: - JITDUMP("Not monotonic because expression is not recognized.\n"); + JITDUMP("Not monIncreasing because expression is not recognized.\n"); return false; } } @@ -414,7 +414,7 @@ bool RangeCheck::IsMonotonicallyIncreasing(GenTree* expr, bool rejectNegativeCon } if (!IsMonotonicallyIncreasing(use.GetNode(), rejectNegativeConst)) { - JITDUMP("Phi argument not monotonic\n"); + JITDUMP("Phi argument not monotonically increasing\n"); return false; } } @@ -771,7 +771,7 @@ void RangeCheck::MergeAssertion(BasicBlock* block, GenTree* op, Range* pRange DE } // Compute the range for a binary operation. -Range RangeCheck::ComputeRangeForBinOp(BasicBlock* block, GenTreeOp* binop, bool monotonic DEBUGARG(int indent)) +Range RangeCheck::ComputeRangeForBinOp(BasicBlock* block, GenTreeOp* binop, bool monIncreasing DEBUGARG(int indent)) { assert(binop->OperIs(GT_ADD)); @@ -791,7 +791,7 @@ Range RangeCheck::ComputeRangeForBinOp(BasicBlock* block, GenTreeOp* binop, bool } else { - op1Range = GetRange(block, op1, monotonic DEBUGARG(indent)); + op1Range = GetRange(block, op1, monIncreasing DEBUGARG(indent)); } MergeAssertion(block, op1, &op1Range DEBUGARG(indent + 1)); } @@ -813,7 +813,7 @@ Range RangeCheck::ComputeRangeForBinOp(BasicBlock* block, GenTreeOp* binop, bool } else { - op2Range = GetRange(block, op2, monotonic DEBUGARG(indent)); + op2Range = GetRange(block, op2, monIncreasing DEBUGARG(indent)); } MergeAssertion(block, op2, &op2Range DEBUGARG(indent + 1)); } @@ -831,7 +831,7 @@ Range RangeCheck::ComputeRangeForBinOp(BasicBlock* block, GenTreeOp* binop, bool // Compute the range for a local var definition. Range RangeCheck::ComputeRangeForLocalDef(BasicBlock* block, GenTreeLclVarCommon* lcl, - bool monotonic DEBUGARG(int indent)) + bool monIncreasing DEBUGARG(int indent)) { LclSsaVarDsc* ssaDef = GetSsaDefAsg(lcl); if (ssaDef == nullptr) @@ -846,7 +846,7 @@ Range RangeCheck::ComputeRangeForLocalDef(BasicBlock* block, JITDUMP("----------------------------------------------------\n"); } #endif - Range range = GetRange(ssaDef->GetBlock(), ssaDef->GetAssignment()->gtGetOp2(), monotonic DEBUGARG(indent)); + Range range = GetRange(ssaDef->GetBlock(), ssaDef->GetAssignment()->gtGetOp2(), monIncreasing DEBUGARG(indent)); if (!BitVecOps::MayBeUninit(block->bbAssertionIn)) { JITDUMP("Merge assertions from " FMT_BB ":%s for assignment about [%06d]\n", block->bbNum, @@ -1048,9 +1048,9 @@ bool RangeCheck::ComputeDoesOverflow(BasicBlock* block, GenTree* expr) // If the result cannot be determined i.e., the dependency chain does not terminate in a value, // but continues to loop, which will happen with phi nodes. We end the looping by calling the // value as "dependent" (dep). -// If the loop is proven to be "monotonic", then make liberal decisions while merging phi node. +// If the loop is proven to be "monIncreasing", then make liberal decisions while merging phi node. // eg.: merge((0, dep), (dep, dep)) = (0, dep) -Range RangeCheck::ComputeRange(BasicBlock* block, GenTree* expr, bool monotonic DEBUGARG(int indent)) +Range RangeCheck::ComputeRange(BasicBlock* block, GenTree* expr, bool monIncreasing DEBUGARG(int indent)) { bool newlyAdded = !m_pSearchPath->Set(expr, block, SearchPath::Overwrite); Range range = Limit(Limit::keUndef); @@ -1100,13 +1100,13 @@ Range RangeCheck::ComputeRange(BasicBlock* block, GenTree* expr, bool monotonic // If local, find the definition from the def map and evaluate the range for rhs. else if (expr->IsLocal()) { - range = ComputeRangeForLocalDef(block, expr->AsLclVarCommon(), monotonic DEBUGARG(indent + 1)); + range = ComputeRangeForLocalDef(block, expr->AsLclVarCommon(), monIncreasing DEBUGARG(indent + 1)); MergeAssertion(block, expr, &range DEBUGARG(indent + 1)); } // If add, then compute the range for the operands and add them. else if (expr->OperGet() == GT_ADD) { - range = ComputeRangeForBinOp(block, expr->AsOp(), monotonic DEBUGARG(indent + 1)); + range = ComputeRangeForBinOp(block, expr->AsOp(), monIncreasing DEBUGARG(indent + 1)); } // If phi, then compute the range for arguments, calling the result "dependent" when looping begins. else if (expr->OperGet() == GT_PHI) @@ -1121,14 +1121,14 @@ Range RangeCheck::ComputeRange(BasicBlock* block, GenTree* expr, bool monotonic } else { - argRange = GetRange(block, use.GetNode(), monotonic DEBUGARG(indent + 1)); + argRange = GetRange(block, use.GetNode(), monIncreasing DEBUGARG(indent + 1)); } assert(!argRange.LowerLimit().IsUndef()); assert(!argRange.UpperLimit().IsUndef()); MergeAssertion(block, use.GetNode(), &argRange DEBUGARG(indent + 1)); JITDUMP("Merging ranges %s %s:", range.ToString(m_pCompiler->getAllocatorDebugOnly()), argRange.ToString(m_pCompiler->getAllocatorDebugOnly())); - range = RangeOps::Merge(range, argRange, monotonic); + range = RangeOps::Merge(range, argRange, monIncreasing); JITDUMP("%s\n", range.ToString(m_pCompiler->getAllocatorDebugOnly())); } } @@ -1177,7 +1177,7 @@ void Indent(int indent) #endif // Get the range, if it is already computed, use the cached range value, else compute it. -Range RangeCheck::GetRange(BasicBlock* block, GenTree* expr, bool monotonic DEBUGARG(int indent)) +Range RangeCheck::GetRange(BasicBlock* block, GenTree* expr, bool monIncreasing DEBUGARG(int indent)) { #ifdef DEBUG if (m_pCompiler->verbose) @@ -1192,7 +1192,7 @@ Range RangeCheck::GetRange(BasicBlock* block, GenTree* expr, bool monotonic DEBU Range* pRange = nullptr; Range range = - GetRangeMap()->Lookup(expr, &pRange) ? *pRange : ComputeRange(block, expr, monotonic DEBUGARG(indent)); + GetRangeMap()->Lookup(expr, &pRange) ? *pRange : ComputeRange(block, expr, monIncreasing DEBUGARG(indent)); #ifdef DEBUG if (m_pCompiler->verbose) diff --git a/src/coreclr/src/jit/rangecheck.h b/src/coreclr/src/jit/rangecheck.h index 9d353658231729..f96dac2b9abe3f 100644 --- a/src/coreclr/src/jit/rangecheck.h +++ b/src/coreclr/src/jit/rangecheck.h @@ -51,7 +51,7 @@ // // **Step 4. Check if the dependency chain is monotonic. // -// **Step 5. If monotonic is true, then perform a widening step, where we assume, the +// **Step 5. If monotonic increasing is true, then perform a widening step, where we assume, the // SSA variables that are "dependent" get their values from the definitions in the // dependency loop and their initial values must be the definitions that are not in // the dependency loop, in this case i_0's value which is 0. @@ -292,9 +292,9 @@ struct RangeOps return result; } - // Given two ranges "r1" and "r2", do a Phi merge. If "monotonic" is true, + // Given two ranges "r1" and "r2", do a Phi merge. If "monIncreasing" is true, // then ignore the dependent variables. - static Range Merge(Range& r1, Range& r2, bool monotonic) + static Range Merge(Range& r1, Range& r2, bool monIncreasing) { Limit& r1lo = r1.LowerLimit(); Limit& r1hi = r1.UpperLimit(); @@ -314,7 +314,7 @@ struct RangeOps } else if (r1lo.IsDependent() || r2lo.IsDependent()) { - if (monotonic) + if (monIncreasing) { result.lLimit = r1lo.IsDependent() ? r2lo : r1lo; } @@ -335,7 +335,7 @@ struct RangeOps } else if (r1hi.IsDependent() || r2hi.IsDependent()) { - if (monotonic) + if (monIncreasing) { result.uLimit = r1hi.IsDependent() ? r2hi : r1hi; } @@ -460,19 +460,19 @@ class RangeCheck // Given the index expression try to find its range. // The range of a variable depends on its rhs which in turn depends on its constituent variables. // The "path" is the path taken in the search for the rhs' range and its constituents' range. - // If "monotonic" is true, the calculations are made more liberally assuming initial values + // If "monIncreasing" is true, the calculations are made more liberally assuming initial values // at phi definitions. - Range GetRange(BasicBlock* block, GenTree* expr, bool monotonic DEBUGARG(int indent)); + Range GetRange(BasicBlock* block, GenTree* expr, bool monIncreasing DEBUGARG(int indent)); // Given the local variable, first find the definition of the local and find the range of the rhs. // Helper for GetRange. - Range ComputeRangeForLocalDef(BasicBlock* block, GenTreeLclVarCommon* lcl, bool monotonic DEBUGARG(int indent)); + Range ComputeRangeForLocalDef(BasicBlock* block, GenTreeLclVarCommon* lcl, bool monIncreasing DEBUGARG(int indent)); // Compute the range, rather than retrieve a cached value. Helper for GetRange. - Range ComputeRange(BasicBlock* block, GenTree* expr, bool monotonic DEBUGARG(int indent)); + Range ComputeRange(BasicBlock* block, GenTree* expr, bool monIncreasing DEBUGARG(int indent)); // Compute the range for the op1 and op2 for the given binary operator. - Range ComputeRangeForBinOp(BasicBlock* block, GenTreeOp* binop, bool monotonic DEBUGARG(int indent)); + Range ComputeRangeForBinOp(BasicBlock* block, GenTreeOp* binop, bool monIncreasing DEBUGARG(int indent)); // Merge assertions from AssertionProp's flags, for the corresponding "phiArg." // Requires "pRange" to contain range that is computed partially. @@ -504,8 +504,8 @@ class RangeCheck // Does the current "expr" which is a use involve a definition, that overflows. bool DoesOverflow(BasicBlock* block, GenTree* tree); - // Widen the range by first checking if the induction variable is monotonic. Requires "pRange" - // to be partially computed. + // Widen the range by first checking if the induction variable is monotonically increasing. + // Requires "pRange" to be partially computed. void Widen(BasicBlock* block, GenTree* tree, Range* pRange); // Is the binary operation increasing the value. From 396198f7b778bf952922de2abe8ca63d06f8e214 Mon Sep 17 00:00:00 2001 From: Sergey Andreenko Date: Thu, 21 Nov 2019 14:49:58 -0800 Subject: [PATCH 4/6] Fix the bug. `monIncreasing` can be used only to liberally assume the lower bound. --- src/coreclr/src/jit/rangecheck.cpp | 3 ++- src/coreclr/src/jit/rangecheck.h | 15 ++++----------- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/src/coreclr/src/jit/rangecheck.cpp b/src/coreclr/src/jit/rangecheck.cpp index 876f987437446f..7861ba6602c0c2 100644 --- a/src/coreclr/src/jit/rangecheck.cpp +++ b/src/coreclr/src/jit/rangecheck.cpp @@ -1049,7 +1049,8 @@ bool RangeCheck::ComputeDoesOverflow(BasicBlock* block, GenTree* expr) // but continues to loop, which will happen with phi nodes. We end the looping by calling the // value as "dependent" (dep). // If the loop is proven to be "monIncreasing", then make liberal decisions while merging phi node. -// eg.: merge((0, dep), (dep, dep)) = (0, dep) +// eg.: merge((0, dep), (dep, dep)) = (0, dep), merge((0, 1), (dep, dep)) = (0, dep), +// merge((0, 5), (dep, 10)) = (0, 10). Range RangeCheck::ComputeRange(BasicBlock* block, GenTree* expr, bool monIncreasing DEBUGARG(int indent)) { bool newlyAdded = !m_pSearchPath->Set(expr, block, SearchPath::Overwrite); diff --git a/src/coreclr/src/jit/rangecheck.h b/src/coreclr/src/jit/rangecheck.h index f96dac2b9abe3f..702736bb7501d3 100644 --- a/src/coreclr/src/jit/rangecheck.h +++ b/src/coreclr/src/jit/rangecheck.h @@ -52,7 +52,7 @@ // **Step 4. Check if the dependency chain is monotonic. // // **Step 5. If monotonic increasing is true, then perform a widening step, where we assume, the -// SSA variables that are "dependent" get their values from the definitions in the +// SSA variables that are "dependent" get their lower bound values from the definitions in the // dependency loop and their initial values must be the definitions that are not in // the dependency loop, in this case i_0's value which is 0. // @@ -293,7 +293,7 @@ struct RangeOps } // Given two ranges "r1" and "r2", do a Phi merge. If "monIncreasing" is true, - // then ignore the dependent variables. + // then ignore the dependent variables for the lower bound. static Range Merge(Range& r1, Range& r2, bool monIncreasing) { Limit& r1lo = r1.LowerLimit(); @@ -335,14 +335,7 @@ struct RangeOps } else if (r1hi.IsDependent() || r2hi.IsDependent()) { - if (monIncreasing) - { - result.uLimit = r1hi.IsDependent() ? r2hi : r1hi; - } - else - { - result.uLimit = Limit(Limit::keDependent); - } + result.uLimit = Limit(Limit::keDependent); } if (r1lo.IsConstant() && r2lo.IsConstant()) @@ -461,7 +454,7 @@ class RangeCheck // The range of a variable depends on its rhs which in turn depends on its constituent variables. // The "path" is the path taken in the search for the rhs' range and its constituents' range. // If "monIncreasing" is true, the calculations are made more liberally assuming initial values - // at phi definitions. + // at phi definitions for the lower bound. Range GetRange(BasicBlock* block, GenTree* expr, bool monIncreasing DEBUGARG(int indent)); // Given the local variable, first find the definition of the local and find the range of the rhs. From 36ad47d73df542b8d2d9bcad06e2ab28f03a717a Mon Sep 17 00:00:00 2001 From: Sergey Andreenko Date: Fri, 22 Nov 2019 10:37:07 -0800 Subject: [PATCH 5/6] Fix the test. I have used a wrong exception type when copied the test. I have checked that it fails without the fix on x86 and passes with it. --- src/coreclr/tests/src/JIT/jit64/opt/osr/osr015.il | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/tests/src/JIT/jit64/opt/osr/osr015.il b/src/coreclr/tests/src/JIT/jit64/opt/osr/osr015.il index 5c206755a4dc17..75227f03dd09ef 100644 --- a/src/coreclr/tests/src/JIT/jit64/opt/osr/osr015.il +++ b/src/coreclr/tests/src/JIT/jit64/opt/osr/osr015.il @@ -4192,7 +4192,7 @@ FOR_START: stelem.i4 leave TRY_END_a } - catch [mscorlib]System.IndexOutOfRangeException + catch [mscorlib]System.NullReferenceException { leave TRY_END_a } From e1d27960124cbf086a1b196905d561cfe3e7ff4d Mon Sep 17 00:00:00 2001 From: Sergey Andreenko Date: Fri, 22 Nov 2019 11:19:20 -0800 Subject: [PATCH 6/6] Fix comments/printings. --- src/coreclr/src/jit/rangecheck.cpp | 33 +++++++++++++++++++++--------- src/coreclr/src/jit/rangecheck.h | 2 +- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/src/coreclr/src/jit/rangecheck.cpp b/src/coreclr/src/jit/rangecheck.cpp index 7861ba6602c0c2..ce2b2a65c8ca62 100644 --- a/src/coreclr/src/jit/rangecheck.cpp +++ b/src/coreclr/src/jit/rangecheck.cpp @@ -338,7 +338,7 @@ bool RangeCheck::IsBinOpMonotonicallyIncreasing(GenTreeOp* binop) } if (op1->OperGet() != GT_LCL_VAR) { - JITDUMP("Not monIncreasing because op1 is not lclVar.\n"); + JITDUMP("Not monotonically increasing because op1 is not lclVar.\n"); return false; } switch (op2->OperGet()) @@ -351,7 +351,7 @@ bool RangeCheck::IsBinOpMonotonicallyIncreasing(GenTreeOp* binop) return (op2->AsIntConCommon()->IconValue() >= 0) && IsMonotonicallyIncreasing(op1, false); default: - JITDUMP("Not monIncreasing because expression is not recognized.\n"); + JITDUMP("Not monotonically increasing because expression is not recognized.\n"); return false; } } @@ -1043,14 +1043,27 @@ bool RangeCheck::ComputeDoesOverflow(BasicBlock* block, GenTree* expr) return overflows; } -// Compute the range recursively by asking for the range of each variable in the dependency chain. -// eg.: c = a + b; ask range of "a" and "b" and add the results. -// If the result cannot be determined i.e., the dependency chain does not terminate in a value, -// but continues to loop, which will happen with phi nodes. We end the looping by calling the -// value as "dependent" (dep). -// If the loop is proven to be "monIncreasing", then make liberal decisions while merging phi node. -// eg.: merge((0, dep), (dep, dep)) = (0, dep), merge((0, 1), (dep, dep)) = (0, dep), -// merge((0, 5), (dep, 10)) = (0, 10). +//------------------------------------------------------------------------ +// ComputeRange: Compute the range recursively by asking for the range of each variable in the dependency chain. +// +// Arguments: +// block - the block that contains `expr`; +// expr - expression to compute the range for; +// monIncreasing - true if `expr` is proven to be monotonically increasing; +// indent - debug printing indent. +// +// Return value: +// 'expr' range as lower and upper limits. +// +// Notes: +// eg.: c = a + b; ask range of "a" and "b" and add the results. +// If the result cannot be determined i.e., the dependency chain does not terminate in a value, +// but continues to loop, which will happen with phi nodes we end the looping by calling the +// value as "dependent" (dep). +// If the loop is proven to be "monIncreasing", then make liberal decisions for the lower bound +// while merging phi node. eg.: merge((0, dep), (dep, dep)) = (0, dep), +// merge((0, 1), (dep, dep)) = (0, dep), merge((0, 5), (dep, 10)) = (0, 10). +// Range RangeCheck::ComputeRange(BasicBlock* block, GenTree* expr, bool monIncreasing DEBUGARG(int indent)) { bool newlyAdded = !m_pSearchPath->Set(expr, block, SearchPath::Overwrite); diff --git a/src/coreclr/src/jit/rangecheck.h b/src/coreclr/src/jit/rangecheck.h index 702736bb7501d3..136d0f3185f9f8 100644 --- a/src/coreclr/src/jit/rangecheck.h +++ b/src/coreclr/src/jit/rangecheck.h @@ -293,7 +293,7 @@ struct RangeOps } // Given two ranges "r1" and "r2", do a Phi merge. If "monIncreasing" is true, - // then ignore the dependent variables for the lower bound. + // then ignore the dependent variables for the lower bound but not for the upper bound. static Range Merge(Range& r1, Range& r2, bool monIncreasing) { Limit& r1lo = r1.LowerLimit();