@@ -152,6 +152,142 @@ struct RelopImplicationRule
152152 bool reverse;
153153};
154154
155+ enum ImpliedRangeCheckStatus
156+ {
157+ Unknown,
158+ NeverIntersects,
159+ AlwaysIncluded
160+ };
161+
162+ // ------------------------------------------------------------------------
163+ // IsRange2ImpliedByRange1: Given two range checks, determine if the 2nd one is redundant or not.
164+ // It is assumed, that the both range checks are for the same X on LHS of the comparison operators.
165+ // e.g. "x > 100 && x > 10" -> AlwaysIncluded
166+ //
167+ // Arguments:
168+ // oper1 - the first range check operator [X *oper1* bound1 ]
169+ // bound1 - the first range check constant bound [X oper1 *bound1*]
170+ // oper2 - the second range check operator [X *oper2* bound2 ]
171+ // bound2 - the second range check constant bound [X oper2 *bound2*]
172+ //
173+ // Returns:
174+ // "AlwaysIncluded" means that the 2nd range check is always "true"
175+ // "NeverIntersects" means that the 2nd range check is never "true"
176+ // "Unknown" means that we can't determine if the 2nd range check is redundant or not.
177+ //
178+ ImpliedRangeCheckStatus IsRange2ImpliedByRange1 (genTreeOps oper1, ssize_t bound1, genTreeOps oper2, ssize_t bound2)
179+ {
180+ struct IntegralRange
181+ {
182+ ssize_t startIncl;
183+ ssize_t endIncl;
184+ };
185+
186+ IntegralRange range1 = {INTPTR_MIN, INTPTR_MAX};
187+ IntegralRange range2 = {INTPTR_MIN, INTPTR_MAX};
188+
189+ // Update ranges based on inputs
190+ auto setRange = [](genTreeOps oper, ssize_t bound, IntegralRange* range) -> bool {
191+ switch (oper)
192+ {
193+ case GT_LT:
194+ // x < cns -> [INTPTR_MIN, cns - 1]
195+ if (bound == INTPTR_MIN)
196+ {
197+ // overflows
198+ return false ;
199+ }
200+ range->endIncl = bound - 1 ;
201+ return true ;
202+
203+ case GT_LE:
204+ // x <= cns -> [INTPTR_MIN, cns]
205+ range->endIncl = bound;
206+ return true ;
207+
208+ case GT_GT:
209+ // x > cns -> [cns + 1, INTPTR_MAX]
210+ if (bound == INTPTR_MAX)
211+ {
212+ // overflows
213+ return false ;
214+ }
215+ range->startIncl = bound + 1 ;
216+ return true ;
217+
218+ case GT_GE:
219+ // x >= cns -> [cns, INTPTR_MAX]
220+ range->startIncl = bound;
221+ return true ;
222+
223+ case GT_EQ:
224+ // x == cns -> [cns, cns]
225+ range->startIncl = bound;
226+ range->endIncl = bound;
227+ return true ;
228+
229+ case GT_NE:
230+ // special cased below (we can't represent it as a range)
231+ return true ;
232+
233+ default :
234+ // unsupported operator
235+ return false ;
236+ }
237+ };
238+
239+ const bool success1 = setRange (oper1, bound1, &range1);
240+ const bool success2 = setRange (oper2, bound2, &range2);
241+ if (!success1 || !success2)
242+ {
243+ return Unknown;
244+ }
245+
246+ // NE is special since we can't represent it as a range, let's only handle it if it's the 2nd operand
247+ // for simplicity (driven by jit-diffs).
248+ if (oper1 == GT_NE)
249+ {
250+ return Unknown;
251+ }
252+ if (oper2 == GT_NE)
253+ {
254+ if ((bound2 < range1.startIncl ) || (bound2 > range1.endIncl ))
255+ {
256+ // "x > 100 && x != 10", the 2nd range check is always true
257+ return AlwaysIncluded;
258+ }
259+ if ((range1.startIncl == bound2) && (range1.endIncl == bound2))
260+ {
261+ // "x == 100 && x != 100", the 2nd range check is never true
262+ return NeverIntersects;
263+ }
264+ return Unknown;
265+ }
266+
267+ // If ranges never intersect, then the 2nd range is never "true"
268+ if ((range1.startIncl > range2.endIncl ) || (range2.startIncl > range1.endIncl ))
269+ {
270+ // E.g.:
271+ //
272+ // range1: [100 .. INT_MAX]
273+ // range2: [INT_MIN .. 10]
274+ return NeverIntersects;
275+ }
276+
277+ // Check if range1 is fully included into range2
278+ if ((range2.startIncl <= range1.startIncl ) && (range1.endIncl <= range2.endIncl ))
279+ {
280+ // E.g.:
281+ //
282+ // range1: [100 .. INT_MAX]
283+ // range2: [10 .. INT_MAX]
284+ return AlwaysIncluded;
285+ }
286+
287+ // Ranges intersect, but we can't determine if the 2nd range is redundant or not.
288+ return Unknown;
289+ }
290+
155291// ------------------------------------------------------------------------
156292// s_implicationRules: rule table for unrelated relops
157293//
@@ -338,6 +474,38 @@ void Compiler::optRelopImpliesRelop(RelopImplicationInfo* rii)
338474 }
339475 }
340476 }
477+ // Given R(x, cns1) and R*(x, cns2) see if we can infer R* from R.
478+ else if ((treeApp.m_args [0 ] == domApp.m_args [0 ]) && vnStore->IsVNConstant (treeApp.m_args [1 ]) &&
479+ vnStore->IsVNConstant (domApp.m_args [1 ]) && varTypeIsIntOrI (vnStore->TypeOfVN (treeApp.m_args [1 ])) &&
480+ varTypeIsIntOrI (vnStore->TypeOfVN (domApp.m_args [1 ])))
481+ {
482+ const ssize_t domCns = vnStore->CoercedConstantValue <ssize_t >(domApp.m_args [1 ]);
483+ const ssize_t treeCns = vnStore->CoercedConstantValue <ssize_t >(treeApp.m_args [1 ]);
484+
485+ // We currently don't handle VNF_relop_UN funcs here, they'll be ignored.
486+ const genTreeOps treeOper = static_cast <genTreeOps>(treeApp.m_func );
487+ const genTreeOps domOper = static_cast <genTreeOps>(domApp.m_func );
488+
489+ bool canInferFromTrue = true ;
490+ ImpliedRangeCheckStatus result = IsRange2ImpliedByRange1 (domOper, domCns, treeOper, treeCns);
491+ if ((result == Unknown) && GenTree::OperIsCompare (domOper))
492+ {
493+ // Reverse the dominating compare and try again, if it succeeds, we can infer from "false".
494+ result = IsRange2ImpliedByRange1 (GenTree::ReverseRelop (domOper), domCns, treeOper, treeCns);
495+ canInferFromTrue = false ;
496+ }
497+
498+ // TODO: handle NeverIntersects case.
499+ if (result == AlwaysIncluded)
500+ {
501+ rii->canInfer = true ;
502+ rii->vnRelation = ValueNumStore::VN_RELATION_KIND::VRK_Inferred;
503+ rii->canInferFromTrue = canInferFromTrue;
504+ rii->canInferFromFalse = !canInferFromTrue;
505+ rii->reverseSense = false ;
506+ return ;
507+ }
508+ }
341509 }
342510
343511 // See if dominating compare is a compound comparison that might
0 commit comments