summaryrefslogtreecommitdiff
path: root/src/jit/rangecheck.h
diff options
context:
space:
mode:
Diffstat (limited to 'src/jit/rangecheck.h')
-rw-r--r--src/jit/rangecheck.h603
1 files changed, 603 insertions, 0 deletions
diff --git a/src/jit/rangecheck.h b/src/jit/rangecheck.h
new file mode 100644
index 0000000000..b00bfb8a67
--- /dev/null
+++ b/src/jit/rangecheck.h
@@ -0,0 +1,603 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+//
+//
+// We take the following approach to range check analysis:
+//
+// Consider the following loop:
+// for (int i = 0; i < a.len; ++i) {
+// a[i] = 0;
+// }
+//
+// This would be represented as:
+// i_0 = 0; BB0
+// / ______ a[i_1] = 0; BB2
+// / / i_2 = i_1 + 1;
+// / / ^
+// i_1 = phi(i_0, i_2); BB1 |
+// i_1 < a.len -------------------+
+//
+// BB0 -> BB1
+// BB1 -> (i_1 < a.len) ? BB2 : BB3
+// BB2 -> BB1
+// BB3 -> return
+//
+// **Step 1. Walk the statements in the method checking if there is a bounds check.
+// If there is a bounds check, ask the range of the index variable.
+// In the above example i_1's range.
+//
+// **Step 2. Follow the defs and the dependency chain:
+// i_1 is a local, so go to its definition which is i_1 = phi(i_0, i_2).
+//
+// Since rhs is a phi, we ask the range for i_0 and i_2 in the hopes of merging
+// the resulting ranges for i_1.
+//
+// The range of i_0 follows immediately when going to its definition.
+// Ask for the range of i_2, which leads to i_1 + 1.
+// Ask for the range of i_1 and figure we are looping. Call the range of i_1 as
+// "dependent" and quit looping further. The range of "1" is just <1, 1>.
+//
+// Now we have exhausted all the variables for which the range can be determined.
+// The others are either "unknown" or "dependent."
+//
+// We also merge assertions from its pred block's edges for a phi argument otherwise
+// from the block's assertionIn. This gives us an upper bound for i_1 as a.len.
+//
+// **Step 3. Check if an overflow occurs in the dependency chain (loop.)
+// In the above case, we want to make sure there is no overflow in the definitions
+// involving i_1 and i_2. Merge assertions from the block's edges whenever possible.
+//
+// **Step 4. Check if the dependency chain is monotonic.
+//
+// **Step 5. If monotonic 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.
+//
+
+#pragma once
+#include "compiler.h"
+#include "expandarray.h"
+
+static bool IntAddOverflows(int max1, int max2)
+{
+ if (max1 > 0 && max2 > 0 && INT_MAX - max1 < max2)
+ {
+ return true;
+ }
+ if (max1 < 0 && max2 < 0 && max1 < INT_MIN - max2)
+ {
+ return true;
+ }
+ return false;
+}
+
+// BNF for range and limit structures
+// Range -> Limit, Limit | Dependent | None | Unknown
+// Limit -> Symbol | BinOp | int
+// BinOp -> Symbol + int
+// SsaVar -> lclNum, ssaNum
+// Symbol -> SsaVar | ArrLen
+// ArrLen -> SsaVar
+// SsaVar -> vn
+struct Limit
+{
+ enum LimitType
+ {
+ keUndef, // The limit is yet to be computed.
+ keBinOp,
+ keBinOpArray,
+ keSsaVar,
+ keArray,
+ keConstant,
+ keDependent, // The limit is dependent on some other value.
+ keUnknown, // The limit could not be determined.
+ };
+
+ Limit() : type(keUndef)
+ {
+ }
+
+ Limit(LimitType type) : type(type)
+ {
+ }
+
+ Limit(LimitType type, int cns) : cns(cns), type(type)
+ {
+ assert(type == keConstant);
+ }
+
+ Limit(LimitType type, ValueNum vn, int cns) : cns(cns), vn(vn), type(type)
+ {
+ assert(type == keBinOpArray || keBinOp);
+ }
+
+ bool IsUndef()
+ {
+ return type == keUndef;
+ }
+ bool IsDependent()
+ {
+ return type == keDependent;
+ }
+ bool IsUnknown()
+ {
+ return type == keUnknown;
+ }
+ bool IsConstant()
+ {
+ return type == keConstant;
+ }
+ int GetConstant()
+ {
+ return cns;
+ }
+ bool IsArray()
+ {
+ return type == keArray;
+ }
+ bool IsSsaVar()
+ {
+ return type == keSsaVar;
+ }
+ bool IsBinOpArray()
+ {
+ return type == keBinOpArray;
+ }
+ bool IsBinOp()
+ {
+ return type == keBinOp;
+ }
+ bool AddConstant(int i)
+ {
+ switch (type)
+ {
+ case keDependent:
+ return true;
+ case keBinOp:
+ case keBinOpArray:
+ if (IntAddOverflows(cns, i))
+ {
+ return false;
+ }
+ cns += i;
+ return true;
+
+ case keSsaVar:
+ type = keBinOp;
+ cns = i;
+ return true;
+
+ case keArray:
+ type = keBinOpArray;
+ cns = i;
+ return true;
+
+ case keConstant:
+ if (IntAddOverflows(cns, i))
+ {
+ return false;
+ }
+ cns += i;
+ return true;
+
+ case keUndef:
+ case keUnknown:
+ // For these values of 'type', conservatively return false
+ break;
+ }
+
+ return false;
+ }
+
+ bool Equals(Limit& l)
+ {
+ switch (type)
+ {
+ case keUndef:
+ case keUnknown:
+ case keDependent:
+ return l.type == type;
+
+ case keBinOp:
+ case keBinOpArray:
+ return l.type == type && l.vn == vn && l.cns == cns;
+
+ case keSsaVar:
+ case keArray:
+ return l.type == type && l.vn == vn;
+
+ case keConstant:
+ return l.type == type && l.cns == cns;
+ }
+ return false;
+ }
+#ifdef DEBUG
+ const char* ToString(IAllocator* alloc)
+ {
+ unsigned size = 64;
+ char* buf = (char*)alloc->Alloc(size);
+ switch (type)
+ {
+ case keUndef:
+ return "Undef";
+
+ case keUnknown:
+ return "Unknown";
+
+ case keDependent:
+ return "Dependent";
+
+ case keBinOp:
+ case keBinOpArray:
+ sprintf_s(buf, size, "VN%04X + %d", vn, cns);
+ return buf;
+
+ case keSsaVar:
+ sprintf_s(buf, size, "VN%04X", vn);
+ return buf;
+
+ case keArray:
+ sprintf_s(buf, size, "VN%04X", vn);
+ return buf;
+
+ case keConstant:
+ sprintf_s(buf, size, "%d", cns);
+ return buf;
+ }
+ unreached();
+ }
+#endif
+ int cns;
+ ValueNum vn;
+ LimitType type;
+};
+
+// Range struct contains upper and lower limit.
+struct Range
+{
+ Limit uLimit;
+ Limit lLimit;
+
+ Range(const Limit& limit) : uLimit(limit), lLimit(limit)
+ {
+ }
+
+ Range(const Limit& lLimit, const Limit& uLimit) : uLimit(uLimit), lLimit(lLimit)
+ {
+ }
+
+ Limit& UpperLimit()
+ {
+ return uLimit;
+ }
+
+ Limit& LowerLimit()
+ {
+ return lLimit;
+ }
+
+#ifdef DEBUG
+ char* ToString(IAllocator* alloc)
+ {
+ size_t size = 64;
+ char* buf = (char*)alloc->Alloc(size);
+ sprintf_s(buf, size, "<%s, %s>", lLimit.ToString(alloc), uLimit.ToString(alloc));
+ return buf;
+ }
+#endif
+};
+
+// Helpers for operations performed on ranges
+struct RangeOps
+{
+ // Given a constant limit in "l1", add it to l2 and mutate "l2".
+ static Limit AddConstantLimit(Limit& l1, Limit& l2)
+ {
+ assert(l1.IsConstant());
+ Limit l = l2;
+ if (l.AddConstant(l1.GetConstant()))
+ {
+ return l;
+ }
+ else
+ {
+ return Limit(Limit::keUnknown);
+ }
+ }
+
+ // Given two ranges "r1" and "r2", perform an add operation on the
+ // ranges.
+ 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);
+
+ // 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 = AddConstantLimit(r1lo, r2lo);
+ }
+ if (r2lo.IsConstant())
+ {
+ result.lLimit = AddConstantLimit(r2lo, r1lo);
+ }
+ if (r1hi.IsConstant())
+ {
+ result.uLimit = AddConstantLimit(r1hi, r2hi);
+ }
+ if (r2hi.IsConstant())
+ {
+ result.uLimit = AddConstantLimit(r2hi, r1hi);
+ }
+ return result;
+ }
+
+ // Given two ranges "r1" and "r2", do a Phi merge. If "monotonic" is true,
+ // then ignore the dependent variables.
+ static Range Merge(Range& r1, Range& r2, bool monotonic)
+ {
+ Limit& r1lo = r1.LowerLimit();
+ Limit& r1hi = r1.UpperLimit();
+ Limit& r2lo = r2.LowerLimit();
+ Limit& r2hi = r2.UpperLimit();
+
+ // Take care of lo part.
+ Range result = Limit(Limit::keUnknown);
+ if (r1lo.IsUnknown() || r2lo.IsUnknown())
+ {
+ result.lLimit = Limit(Limit::keUnknown);
+ }
+ // Uninitialized, just copy.
+ else if (r1lo.IsUndef())
+ {
+ result.lLimit = r2lo;
+ }
+ else if (r1lo.IsDependent() || r2lo.IsDependent())
+ {
+ if (monotonic)
+ {
+ result.lLimit = r1lo.IsDependent() ? r2lo : r1lo;
+ }
+ else
+ {
+ result.lLimit = Limit(Limit::keDependent);
+ }
+ }
+
+ // Take care of hi part.
+ if (r1hi.IsUnknown() || r2hi.IsUnknown())
+ {
+ result.uLimit = Limit(Limit::keUnknown);
+ }
+ else if (r1hi.IsUndef())
+ {
+ result.uLimit = r2hi;
+ }
+ else if (r1hi.IsDependent() || r2hi.IsDependent())
+ {
+ if (monotonic)
+ {
+ result.uLimit = r1hi.IsDependent() ? r2hi : r1hi;
+ }
+ else
+ {
+ result.uLimit = Limit(Limit::keDependent);
+ }
+ }
+
+ if (r1lo.IsConstant() && r2lo.IsConstant())
+ {
+ result.lLimit = Limit(Limit::keConstant, min(r1lo.GetConstant(), r2lo.GetConstant()));
+ }
+ if (r1hi.IsConstant() && r2hi.IsConstant())
+ {
+ result.uLimit = Limit(Limit::keConstant, max(r1hi.GetConstant(), r2hi.GetConstant()));
+ }
+ if (r2hi.Equals(r1hi))
+ {
+ result.uLimit = r2hi;
+ }
+ if (r2lo.Equals(r1lo))
+ {
+ result.lLimit = r1lo;
+ }
+ // Widen Upper Limit => Max(k, (a.len + n)) yields (a.len + n),
+ // This is correct if k >= 0 and n >= k, since a.len always >= 0
+ // (a.len + n) could overflow, but the result (a.len + n) also
+ // preserves the overflow.
+ if (r1hi.IsConstant() && r1hi.GetConstant() >= 0 && r2hi.IsBinOpArray() &&
+ r2hi.GetConstant() >= r1hi.GetConstant())
+ {
+ result.uLimit = r2hi;
+ }
+ if (r2hi.IsConstant() && r2hi.GetConstant() >= 0 && r1hi.IsBinOpArray() &&
+ r1hi.GetConstant() >= r2hi.GetConstant())
+ {
+ result.uLimit = r1hi;
+ }
+ if (r1hi.IsBinOpArray() && r2hi.IsBinOpArray() && r1hi.vn == r2hi.vn)
+ {
+ result.uLimit = r1hi;
+ // Widen the upper bound if the other constant is greater.
+ if (r2hi.GetConstant() > r1hi.GetConstant())
+ {
+ result.uLimit = r2hi;
+ }
+ }
+ return result;
+ }
+};
+
+class RangeCheck
+{
+public:
+ // Constructor
+ RangeCheck(Compiler* pCompiler);
+
+ // Location information is used to map where the defs occur in the method.
+ struct Location
+ {
+ BasicBlock* block;
+ GenTreePtr stmt;
+ GenTreePtr tree;
+ GenTreePtr parent;
+ Location(BasicBlock* block, GenTreePtr stmt, GenTreePtr tree, GenTreePtr parent)
+ : block(block), stmt(stmt), tree(tree), parent(parent)
+ {
+ }
+
+ private:
+ Location();
+ };
+
+ typedef SimplerHashTable<GenTreePtr, PtrKeyFuncs<GenTree>, bool, JitSimplerHashBehavior> OverflowMap;
+ typedef SimplerHashTable<GenTreePtr, PtrKeyFuncs<GenTree>, Range*, JitSimplerHashBehavior> RangeMap;
+ typedef SimplerHashTable<GenTreePtr, PtrKeyFuncs<GenTree>, BasicBlock*, JitSimplerHashBehavior> SearchPath;
+ typedef SimplerHashTable<INT64, LargePrimitiveKeyFuncs<INT64>, Location*, JitSimplerHashBehavior> VarToLocMap;
+ typedef SimplerHashTable<INT64, LargePrimitiveKeyFuncs<INT64>, ExpandArrayStack<Location*>*, JitSimplerHashBehavior>
+ VarToLocArrayMap;
+
+ // Generate a hashcode unique for this ssa var.
+ UINT64 HashCode(unsigned lclNum, unsigned ssaNum);
+
+ // Add a location of the definition of ssa var to the location map.
+ // Requires "hash" to be computed using HashCode.
+ // Requires "location" to be the local definition.
+ void SetDef(UINT64 hash, Location* loc);
+
+ // Given a tree node that is a local, return the Location defining the local.
+ Location* GetDef(GenTreePtr tree);
+ Location* GetDef(unsigned lclNum, unsigned ssaNum);
+
+ int GetArrLength(ValueNum vn);
+
+ // Check whether the computed range is within lower and upper bounds. This function
+ // assumes that the lower range is resolved and upper range is symbolic as in an
+ // increasing loop.
+ // TODO-CQ: This is not general enough.
+ bool BetweenBounds(Range& range, int lower, GenTreePtr upper);
+
+ // Given a statement, check if it is a def and add its locations in a map.
+ void MapStmtDefs(const Location& loc);
+
+ // Given the CFG, check if it has defs and add their locations in a map.
+ void MapMethodDefs();
+
+ // Entry point to optimize range checks in the block. Assumes value numbering
+ // and assertion prop phases are completed.
+ void OptimizeRangeChecks();
+
+ // Given a "tree" node, check if it contains array bounds check node and
+ // optimize to remove it, if possible. Requires "stmt" and "block" that
+ // contain the tree.
+ void OptimizeRangeCheck(BasicBlock* block, GenTreePtr stmt, GenTreePtr tree);
+
+ // 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
+ // at phi definitions.
+ Range GetRange(
+ BasicBlock* block, GenTreePtr stmt, GenTreePtr expr, SearchPath* path, bool monotonic 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, GenTreePtr stmt, GenTreePtr expr, SearchPath* path, bool monotonic DEBUGARG(int indent));
+
+ // Compute the range, rather than retrieve a cached value. Helper for GetRange.
+ Range ComputeRange(
+ BasicBlock* block, GenTreePtr stmt, GenTreePtr expr, SearchPath* path, bool monotonic DEBUGARG(int indent));
+
+ // Compute the range for the op1 and op2 for the given binary operator.
+ Range ComputeRangeForBinOp(BasicBlock* block,
+ GenTreePtr stmt,
+ GenTreePtr op1,
+ GenTreePtr op2,
+ genTreeOps oper,
+ SearchPath* path,
+ bool monotonic DEBUGARG(int indent));
+
+ // Merge assertions from AssertionProp's flags, for the corresponding "phiArg."
+ // Requires "pRange" to contain range that is computed partially.
+ void MergeAssertion(
+ BasicBlock* block, GenTreePtr stmt, GenTreePtr phiArg, SearchPath* path, Range* pRange DEBUGARG(int indent));
+
+ // Inspect the "assertions" and extract assertions about the given "phiArg" and
+ // refine the "pRange" value.
+ void MergeEdgeAssertions(GenTreePtr phiArg, const ASSERT_VALARG_TP assertions, Range* pRange);
+
+ // The maximum possible value of the given "limit." If such a value could not be determined
+ // return "false." For example: ARRLEN_MAX for array length.
+ bool GetLimitMax(Limit& limit, int* pMax);
+
+ // Does the addition of the two limits overflow?
+ bool AddOverflows(Limit& limit1, Limit& limit2);
+
+ // Does the binary operation between the operands overflow? Check recursively.
+ bool DoesBinOpOverflow(BasicBlock* block, GenTreePtr stmt, GenTreePtr op1, GenTreePtr op2, SearchPath* path);
+
+ // Does the phi operands involve an assignment that could overflow?
+ bool DoesPhiOverflow(BasicBlock* block, GenTreePtr stmt, GenTreePtr expr, SearchPath* path);
+
+ // Find the def of the "expr" local and recurse on the arguments if any of them involve a
+ // calculation that overflows.
+ bool DoesVarDefOverflow(BasicBlock* block, GenTreePtr stmt, GenTreePtr expr, SearchPath* path);
+
+ bool ComputeDoesOverflow(BasicBlock* block, GenTreePtr stmt, GenTreePtr expr, SearchPath* path);
+
+ // Does the current "expr" which is a use involve a definition, that overflows.
+ bool DoesOverflow(BasicBlock* block, GenTreePtr stmt, GenTreePtr tree, SearchPath* path);
+
+ // Widen the range by first checking if the induction variable is monotonic. Requires "pRange"
+ // to be partially computed.
+ void Widen(BasicBlock* block, GenTreePtr stmt, GenTreePtr tree, SearchPath* path, Range* pRange);
+
+ // Is the binary operation increasing the value.
+ bool IsBinOpMonotonicallyIncreasing(GenTreePtr op1, GenTreePtr op2, genTreeOps oper, SearchPath* path);
+
+ // Given an "expr" trace its rhs and their definitions to check if all the assignments
+ // are monotonically increasing.
+ bool IsMonotonicallyIncreasing(GenTreePtr tree, SearchPath* path);
+
+ // We allocate a budget to avoid walking long UD chains. When traversing each link in the UD
+ // chain, we decrement the budget. When the budget hits 0, then no more range check optimization
+ // will be applied for the currently compiled method.
+ bool IsOverBudget();
+
+private:
+ GenTreeBoundsChk* m_pCurBndsChk;
+
+ // Get the cached overflow values.
+ OverflowMap* GetOverflowMap();
+ OverflowMap* m_pOverflowMap;
+
+ // Get the cached range values.
+ RangeMap* GetRangeMap();
+ RangeMap* m_pRangeMap;
+
+ bool m_fMappedDefs;
+ VarToLocMap* m_pDefTable;
+ Compiler* m_pCompiler;
+
+ // The number of nodes for which range is computed throughout the current method.
+ // When this limit is zero, we have exhausted all the budget to walk the ud-chain.
+ int m_nVisitBudget;
+};