/** Returns true if 64-bit constant fits in 32-bit constant. */ staticbool CanLongValueFitIntoInt(int64_t c) { return std::numeric_limits<int32_t>::min() <= c && c <= std::numeric_limits<int32_t>::max();
}
/** Returns true if 32-bit addition can be done safely. */ staticbool IsSafeAdd(int32_t c1, int32_t c2) { return CanLongValueFitIntoInt(static_cast<int64_t>(c1) + static_cast<int64_t>(c2));
}
/** Returns true if 32-bit subtraction can be done safely. */ staticbool IsSafeSub(int32_t c1, int32_t c2) { return CanLongValueFitIntoInt(static_cast<int64_t>(c1) - static_cast<int64_t>(c2));
}
/** Returns true if 32-bit multiplication can be done safely. */ staticbool IsSafeMul(int32_t c1, int32_t c2) { return CanLongValueFitIntoInt(static_cast<int64_t>(c1) * static_cast<int64_t>(c2));
}
/** Returns true if 32-bit division can be done safely. */ staticbool IsSafeDiv(int32_t c1, int32_t c2) { return c2 != 0 && CanLongValueFitIntoInt(static_cast<int64_t>(c1) / static_cast<int64_t>(c2));
}
/** Computes a * b for a,b > 0 (at least until first overflow happens). */ static int64_t SafeMul(int64_t a, int64_t b, /*out*/ bool* overflow) {
int64_t result; if (__builtin_mul_overflow(a, b, &result)) {
*overflow = true;
} return result;
}
/** Returns b^e for b,e > 0. Sets overflow if arithmetic wrap-around occurred. */ static int64_t IntPow(int64_t b, int64_t e, /*out*/ bool* overflow) {
DCHECK_LT(0, b);
DCHECK_LT(0, e);
int64_t pow = 1; while (e) { if (e & 1) {
pow = SafeMul(pow, b, overflow);
}
e >>= 1; if (e) {
b = SafeMul(b, b, overflow);
}
} return pow;
}
/** Hunts "under the hood" for a suitable instruction at the hint. */ staticbool IsMaxAtHint(
HInstruction* instruction, HInstruction* hint, /*out*/HInstruction** suitable) { if (instruction->IsMin()) { // For MIN(x, y), return most suitable x or y as maximum. return IsMaxAtHint(instruction->InputAt(0), hint, suitable) ||
IsMaxAtHint(instruction->InputAt(1), hint, suitable);
} else {
*suitable = instruction; return HuntForDeclaration(instruction) == hint;
}
}
/** Post-analysis simplification of a minimum value that makes the bound more useful to clients. */ static InductionVarRange::Value SimplifyMin(InductionVarRange::Value v) { if (v.is_known && v.a_constant == 1 && v.b_constant <= 0) { // If a == 1, instruction >= 0 and b <= 0, just return the constant b. // No arithmetic wrap-around can occur. if (IsGEZero(v.instruction)) { return InductionVarRange::Value(v.b_constant);
}
} return v;
}
/** Post-analysis simplification of a maximum value that makes the bound more useful to clients. */ static InductionVarRange::Value SimplifyMax(InductionVarRange::Value v, HInstruction* hint) { if (v.is_known && v.a_constant >= 1) { // An upper bound a * (length / a) + b, where a >= 1, can be conservatively rewritten as // length + b because length >= 0 is true.
int64_t value; if (v.instruction->IsDiv() &&
v.instruction->InputAt(0)->IsArrayLength() &&
IsInt64AndGet(v.instruction->InputAt(1), &value) && v.a_constant == value) { return InductionVarRange::Value(v.instruction->InputAt(0), 1, v.b_constant);
} // If a == 1, the most suitable one suffices as maximum value.
HInstruction* suitable = nullptr; if (v.a_constant == 1 && IsMaxAtHint(v.instruction, hint, &suitable)) { return InductionVarRange::Value(suitable, 1, v.b_constant);
}
} return v;
}
/** Tests for a constant value. */ staticbool IsConstantValue(InductionVarRange::Value v) { return v.is_known && v.a_constant == 0;
}
/** Corrects a value for type to account for arithmetic wrap-around in lower precision. */ static InductionVarRange::Value CorrectForType(InductionVarRange::Value v, DataType::Type type) { switch (type) { case DataType::Type::kUint8: case DataType::Type::kInt8: case DataType::Type::kUint16: case DataType::Type::kInt16: { // Constants within range only. // TODO: maybe some room for improvement, like allowing widening conversions
int32_t min = DataType::MinValueOfIntegralType(type);
int32_t max = DataType::MaxValueOfIntegralType(type); return (IsConstantValue(v) && min <= v.b_constant && v.b_constant <= max)
? v
: InductionVarRange::Value();
} default: return v;
}
}
/** Determines whether the `context` is in the body of the `loop`. */ staticbool IsContextInBody(const HBasicBlock* context, const HLoopInformation* loop) {
DCHECK(loop != nullptr); // We're currently classifying trip count only for the exit condition from loop header. // All other blocks in the loop are considered loop body. return context != loop->GetHeader() && loop->Contains(*context);
}
/** Determines whether to use the full trip count for given `context`, `loop` and `is_min`. */ bool UseFullTripCount(const HBasicBlock* context, const HLoopInformation* loop, bool is_min) { // We're currently classifying trip count only for the exit condition from loop header. // So, we should call this helper function only if the loop control is an `HIf` with // one edge leaving the loop. The loop header is the only block that's both inside // the loop and not in the loop body.
DCHECK(GetLoopControl(loop)->IsIf());
DCHECK_NE(loop->Contains(*GetLoopControl(loop)->AsIf()->IfTrueSuccessor()),
loop->Contains(*GetLoopControl(loop)->AsIf()->IfFalseSuccessor())); if (loop->Contains(*context)) { // Use the full trip count if determining the maximum and context is not in the loop body.
DCHECK_NE(context == loop->GetHeader(), IsContextInBody(context, loop)); return !is_min && context == loop->GetHeader();
} else { // Trip count after the loop is always the maximum (ignoring `is_min`), // as long as the `context` is dominated by the loop control exit block. // If there are additional exit edges, the value is unknown on those paths.
HInstruction* loop_control = GetLoopControl(loop);
HBasicBlock* then_block = loop_control->AsIf()->IfTrueSuccessor();
HBasicBlock* else_block = loop_control->AsIf()->IfFalseSuccessor();
HBasicBlock* loop_exit_block = loop->Contains(*then_block) ? else_block : then_block; return loop_exit_block->Dominates(context);
}
}
bool InductionVarRange::HasKnownTripCount(const HLoopInformation* loop, /*out*/ int64_t* trip_count) const { bool is_constant = false;
CheckForFiniteAndConstantProps(loop, &is_constant, trip_count); // Set negative trip counts as 0, since it means that no trips would happen. Note that if the // `is_constant` value is false, `trip_count` would be disregareded. if (*trip_count < 0) {
*trip_count = 0;
} return is_constant;
}
bool InductionVarRange::IsWellBehavedTripCount(const HBasicBlock* context, const HLoopInformation* loop,
HInductionVarAnalysis::InductionInfo* trip) const { if (trip != nullptr) { // Both bounds that define a trip-count are well-behaved if they either are not defined // in any loop, or are contained in a proper interval. This allows finding the min/max // of an expression by chasing outward.
InductionVarRange range(induction_analysis_);
HInductionVarAnalysis::InductionInfo* lower = trip->op_b->op_a;
HInductionVarAnalysis::InductionInfo* upper = trip->op_b->op_b;
int64_t not_used = 0; return
(!HasFetchInLoop(lower) || range.IsConstant(context, loop, lower, kAtLeast, ¬_used)) &&
(!HasFetchInLoop(upper) || range.IsConstant(context, loop, upper, kAtLeast, ¬_used));
} returntrue;
}
InductionVarRange::Value InductionVarRange::GetLinear(const HBasicBlock* context, const HLoopInformation* loop,
HInductionVarAnalysis::InductionInfo* info,
HInductionVarAnalysis::InductionInfo* trip, bool is_min) const {
DCHECK(info != nullptr);
DCHECK_EQ(info->induction_class, HInductionVarAnalysis::kLinear); // Detect common situation where an offset inside the trip-count cancels out during range // analysis (finding max a * (TC - 1) + OFFSET for a == 1 and TC = UPPER - OFFSET or finding // min a * (TC - 1) + OFFSET for a == -1 and TC = OFFSET - UPPER) to avoid losing information // with intermediate results that only incorporate single instructions. if (trip != nullptr) {
HInductionVarAnalysis::InductionInfo* trip_expr = trip->op_a; if (trip_expr->type == info->type && trip_expr->operation == HInductionVarAnalysis::kSub) {
int64_t stride_value = 0; if (IsConstant(context, loop, info->op_a, kExact, &stride_value)) { if (!is_min && stride_value == 1) { // Test original trip's negative operand (trip_expr->op_b) against offset of induction. if (HInductionVarAnalysis::InductionEqual(trip_expr->op_b, info->op_b)) { // Analyze cancelled trip with just the positive operand (trip_expr->op_a).
HInductionVarAnalysis::InductionInfo cancelled_trip(
trip->induction_class,
trip->operation,
trip_expr->op_a,
trip->op_b,
nullptr,
trip->type); return GetVal(context, loop, &cancelled_trip, trip, is_min);
}
} elseif (is_min && stride_value == -1) { // Test original trip's positive operand (trip_expr->op_a) against offset of induction. if (HInductionVarAnalysis::InductionEqual(trip_expr->op_a, info->op_b)) { // Analyze cancelled trip with just the negative operand (trip_expr->op_b).
HInductionVarAnalysis::InductionInfo neg(
HInductionVarAnalysis::kInvariant,
HInductionVarAnalysis::kNeg,
nullptr,
trip_expr->op_b,
nullptr,
trip->type);
HInductionVarAnalysis::InductionInfo cancelled_trip(
trip->induction_class, trip->operation, &neg, trip->op_b, nullptr, trip->type); return SubValue(Value(0), GetVal(context, loop, &cancelled_trip, trip, !is_min));
}
}
}
}
} // General rule of linear induction a * i + b, for normalized 0 <= i < TC. return AddValue(GetMul(context, loop, info->op_a, trip, trip, is_min),
GetVal(context, loop, info->op_b, trip, is_min));
}
InductionVarRange::Value InductionVarRange::GetPolynomial( const HBasicBlock* context, const HLoopInformation* loop,
HInductionVarAnalysis::InductionInfo* info,
HInductionVarAnalysis::InductionInfo* trip, bool is_min) const {
DCHECK(info != nullptr);
DCHECK_EQ(info->induction_class, HInductionVarAnalysis::kPolynomial);
int64_t a = 0;
int64_t b = 0; if (IsConstant(context, loop, info->op_a->op_a, kExact, &a) &&
CanLongValueFitIntoInt(a) &&
a >= 0 &&
IsConstant(context, loop, info->op_a->op_b, kExact, &b) &&
CanLongValueFitIntoInt(b) &&
b >= 0) { // Evaluate bounds on sum_i=0^m-1(a * i + b) + c with a,b >= 0 for // maximum index value m as a * (m * (m-1)) / 2 + b * m + c. // Do not simply return `c` as minimum because the trip count may be non-zero // if the `context` is after the `loop` (and therefore ignoring `is_min`).
Value c = GetVal(context, loop, info->op_b, trip, is_min);
Value m = GetVal(context, loop, trip, trip, is_min);
Value t = DivValue(MulValue(m, SubValue(m, Value(1))), Value(2));
Value x = MulValue(Value(a), t);
Value y = MulValue(Value(b), m); return AddValue(AddValue(x, y), c);
} return Value();
}
InductionVarRange::Value InductionVarRange::GetGeometric(const HBasicBlock* context, const HLoopInformation* loop,
HInductionVarAnalysis::InductionInfo* info,
HInductionVarAnalysis::InductionInfo* trip, bool is_min) const {
DCHECK(info != nullptr);
DCHECK_EQ(info->induction_class, HInductionVarAnalysis::kGeometric);
int64_t a = 0;
int64_t f = 0; if (IsConstant(context, loop, info->op_a, kExact, &a) &&
CanLongValueFitIntoInt(a) &&
IsInt64AndGet(info->fetch, &f) && f >= 1) { // Conservative bounds on a * f^-i + b with f >= 1 can be computed without // trip count. Other forms would require a much more elaborate evaluation. constbool is_min_a = a >= 0 ? is_min : !is_min; if (info->operation == HInductionVarAnalysis::kDiv) {
Value b = GetVal(context, loop, info->op_b, trip, is_min); return is_min_a ? b : AddValue(Value(a), b);
}
} return Value();
}
InductionVarRange::Value InductionVarRange::GetFetch(const HBasicBlock* context, const HLoopInformation* loop,
HInstruction* instruction,
HInductionVarAnalysis::InductionInfo* trip, bool is_min) const { // Special case when chasing constants: single instruction that denotes trip count in the // loop-body is minimal 1 and maximal, with safe trip-count, max int, if (chase_hint_ == nullptr &&
IsContextInBody(context, loop) &&
trip != nullptr &&
instruction == trip->op_a->fetch) { if (is_min) { return Value(1);
} elseif (!instruction->IsConstant() && !IsUnsafeTripCount(trip)) { return Value(std::numeric_limits<int32_t>::max());
}
} // Unless at a constant or hint, chase the instruction a bit deeper into the HIR tree, so that // it becomes more likely range analysis will compare the same instructions as terminal nodes.
int64_t value; if (IsInt64AndGet(instruction, &value) && CanLongValueFitIntoInt(value)) { // Proper constant reveals best information. return Value(static_cast<int32_t>(value));
} elseif (instruction == chase_hint_) { // At hint, fetch is represented by itself. return Value(instruction, 1, 0);
} elseif (instruction->IsAdd()) { // Incorporate suitable constants in the chased value. if (IsInt64AndGet(instruction->InputAt(0), &value) && CanLongValueFitIntoInt(value)) { return AddValue(Value(static_cast<int32_t>(value)),
GetFetch(context, loop, instruction->InputAt(1), trip, is_min));
} elseif (IsInt64AndGet(instruction->InputAt(1), &value) && CanLongValueFitIntoInt(value)) { return AddValue(GetFetch(context, loop, instruction->InputAt(0), trip, is_min),
Value(static_cast<int32_t>(value)));
}
} elseif (instruction->IsSub()) { // Incorporate suitable constants in the chased value. if (IsInt64AndGet(instruction->InputAt(0), &value) && CanLongValueFitIntoInt(value)) { return SubValue(Value(static_cast<int32_t>(value)),
GetFetch(context, loop, instruction->InputAt(1), trip, !is_min));
} elseif (IsInt64AndGet(instruction->InputAt(1), &value) && CanLongValueFitIntoInt(value)) { return SubValue(GetFetch(context, loop, instruction->InputAt(0), trip, is_min),
Value(static_cast<int32_t>(value)));
}
} elseif (instruction->IsArrayLength()) { // Exploit length properties when chasing constants or chase into a new array declaration. if (chase_hint_ == nullptr) { return is_min ? Value(0) : Value(std::numeric_limits<int32_t>::max());
} elseif (instruction->InputAt(0)->IsNewArray()) { return GetFetch(
context, loop, instruction->InputAt(0)->AsNewArray()->GetLength(), trip, is_min);
}
} elseif (instruction->IsTypeConversion()) { // Since analysis is 32-bit (or narrower), chase beyond widening along the path. // For example, this discovers the length in: for (long i = 0; i < a.length; i++); if (instruction->AsTypeConversion()->GetInputType() == DataType::Type::kInt32 &&
instruction->AsTypeConversion()->GetResultType() == DataType::Type::kInt64) { return GetFetch(context, loop, instruction->InputAt(0), trip, is_min);
}
} // Chase an invariant fetch that is defined by another loop if the trip-count used // so far is well-behaved in both bounds and the next trip-count is safe. // Example: // for (int i = 0; i <= 100; i++) // safe // for (int j = 0; j <= i; j++) // well-behaved // j is in range [0, i ] (if i is chase hint) // or in range [0, 100] (otherwise) // Example: // for (i = 0; i < 100; ++i) // <some-code> // for (j = 0; j < 10; ++j) // sum += i; // The `i` is a "fetch" of a loop Phi from the previous loop. const HLoopInformation* next_loop = nullptr;
HInductionVarAnalysis::InductionInfo* next_info = nullptr;
HInductionVarAnalysis::InductionInfo* next_trip = nullptr; if (HasInductionInfo(instruction->GetBlock(), instruction, &next_loop, &next_info, &next_trip) &&
IsWellBehavedTripCount(context, next_loop, trip) &&
!IsUnsafeTripCount(next_trip)) { return GetVal(context, next_loop, next_info, next_trip, is_min);
} // Fetch is represented by itself. return Value(instruction, 1, 0);
}
InductionVarRange::Value InductionVarRange::GetVal(const HBasicBlock* context, const HLoopInformation* loop,
HInductionVarAnalysis::InductionInfo* info,
HInductionVarAnalysis::InductionInfo* trip, bool is_min) const { if (info != nullptr) { switch (info->induction_class) { case HInductionVarAnalysis::kInvariant: // Invariants. switch (info->operation) { case HInductionVarAnalysis::kAdd: return AddValue(GetVal(context, loop, info->op_a, trip, is_min),
GetVal(context, loop, info->op_b, trip, is_min)); case HInductionVarAnalysis::kSub: // second reversed! return SubValue(GetVal(context, loop, info->op_a, trip, is_min),
GetVal(context, loop, info->op_b, trip, !is_min)); case HInductionVarAnalysis::kNeg: // second reversed! return SubValue(Value(0),
GetVal(context, loop, info->op_b, trip, !is_min)); case HInductionVarAnalysis::kMul: return GetMul(context, loop, info->op_a, info->op_b, trip, is_min); case HInductionVarAnalysis::kDiv: return GetDiv(context, loop, info->op_a, info->op_b, trip, is_min); case HInductionVarAnalysis::kRem: return GetRem(context, loop, info->op_a, info->op_b); case HInductionVarAnalysis::kXor: return GetXor(context, loop, info->op_a, info->op_b); case HInductionVarAnalysis::kFetch: return GetFetch(context, loop, info->fetch, trip, is_min); case HInductionVarAnalysis::kTripCountInLoop: case HInductionVarAnalysis::kTripCountInLoopUnsafe: if (UseFullTripCount(context, loop, is_min)) { // Return the full trip count (do not subtract 1 as we do in loop body). return GetVal(context, loop, info->op_a, trip, is_min);
}
FALLTHROUGH_INTENDED; case HInductionVarAnalysis::kTripCountInBody: case HInductionVarAnalysis::kTripCountInBodyUnsafe: if (is_min) { return Value(0);
} elseif (IsContextInBody(context, loop)) { return SubValue(GetVal(context, loop, info->op_a, trip, is_min), Value(1));
} break; default: break;
} break; case HInductionVarAnalysis::kLinear: return CorrectForType(GetLinear(context, loop, info, trip, is_min), info->type); case HInductionVarAnalysis::kPolynomial: return GetPolynomial(context, loop, info, trip, is_min); case HInductionVarAnalysis::kGeometric: return GetGeometric(context, loop, info, trip, is_min); case HInductionVarAnalysis::kWrapAround: case HInductionVarAnalysis::kPeriodic: return MergeVal(GetVal(context, loop, info->op_a, trip, is_min),
GetVal(context, loop, info->op_b, trip, is_min),
is_min);
}
} return Value();
}
bool InductionVarRange::GenerateRangeOrLastValue(const HBasicBlock* context,
HInstruction* instruction, bool is_last_value,
HGraph* graph,
HBasicBlock* block, /*out*/HInstruction** lower, /*out*/HInstruction** upper, /*out*/HInstruction** taken_test, /*out*/int64_t* stride_value, /*out*/bool* needs_finite_test, /*out*/bool* needs_taken_test) const { const HLoopInformation* loop = nullptr;
HInductionVarAnalysis::InductionInfo* info = nullptr;
HInductionVarAnalysis::InductionInfo* trip = nullptr; if (!HasInductionInfo(context, instruction, &loop, &info, &trip) || trip == nullptr) { returnfalse; // codegen needs all information, including tripcount
} // Determine what tests are needed. A finite test is needed if the evaluation code uses the // trip-count and the loop maybe unsafe (because in such cases, the index could "overshoot" // the computed range). A taken test is needed for any unknown trip-count, even if evaluation // code does not use the trip-count explicitly (since there could be an implicit relation // between e.g. an invariant subscript and a not-taken condition).
*stride_value = 0;
*needs_finite_test = NeedsTripCount(context, loop, info, stride_value) && IsUnsafeTripCount(trip);
*needs_taken_test = IsBodyTripCount(trip); // Handle last value request. if (is_last_value) {
DCHECK(!IsContextInBody(context, loop)); switch (info->induction_class) { case HInductionVarAnalysis::kLinear: if (*stride_value > 0) {
lower = nullptr; return GenerateLastValueLinear(
context, loop, info, trip, graph, block, /*is_min=*/false, upper, needs_taken_test);
} else {
upper = nullptr; return GenerateLastValueLinear(
context, loop, info, trip, graph, block, /*is_min=*/true, lower, needs_taken_test);
} case HInductionVarAnalysis::kPolynomial: return GenerateLastValuePolynomial(context, loop, info, trip, graph, block, lower); case HInductionVarAnalysis::kGeometric: return GenerateLastValueGeometric(context, loop, info, trip, graph, block, lower); case HInductionVarAnalysis::kWrapAround: return GenerateLastValueWrapAround(context, loop, info, trip, graph, block, lower); case HInductionVarAnalysis::kPeriodic: return GenerateLastValuePeriodic(
context, loop, info, trip, graph, block, lower, needs_taken_test); default: returnfalse;
}
} // Code generation for taken test: generate the code when requested or otherwise analyze // if code generation is feasible when taken test is needed. if (taken_test != nullptr) { return GenerateCode(context,
loop,
trip->op_b, /*trip=*/ nullptr,
graph,
block, /*is_min=*/ false,
taken_test);
} elseif (*needs_taken_test) { if (!GenerateCode(context,
loop,
trip->op_b, /*trip=*/ nullptr, /*graph=*/ nullptr, /*block=*/ nullptr, /*is_min=*/ false, /*result=*/ nullptr)) { returnfalse;
}
} // Code generation for lower and upper. return // Success on lower if invariant (not set), or code can be generated.
((info->induction_class == HInductionVarAnalysis::kInvariant) ||
GenerateCode(context, loop, info, trip, graph, block, /*is_min=*/ true, lower)) && // And success on upper.
GenerateCode(context, loop, info, trip, graph, block, /*is_min=*/ false, upper);
}
bool InductionVarRange::GenerateLastValueLinear(const HBasicBlock* context, const HLoopInformation* loop,
HInductionVarAnalysis::InductionInfo* info,
HInductionVarAnalysis::InductionInfo* trip,
HGraph* graph,
HBasicBlock* block, bool is_min, /*out*/ HInstruction** result, /*inout*/ bool* needs_taken_test) const {
DataType::Type type = info->type; // Avoid any narrowing linear induction or any type mismatch between the linear induction and the // trip count expression. if (HInductionVarAnalysis::IsNarrowingLinear(info) || trip->type != type) { returnfalse;
}
// Stride value must be a known constant that fits into int32. The stride will be the `i` in `a * // i + b`.
int64_t stride_value = 0; if (!IsConstant(context, loop, info->op_a, kExact, &stride_value) ||
!CanLongValueFitIntoInt(stride_value)) { returnfalse;
}
// We require the calculation of `a` to not overflow. constbool is_min_a = stride_value >= 0 ? is_min : !is_min;
HInstruction* opa;
HInstruction* opb; if (!GenerateCode(context,
loop,
trip,
trip,
graph,
block,
is_min_a,
&opa, /*allow_potential_overflow=*/false) ||
!GenerateCode(context, loop, info->op_b, trip, graph, block, is_min, &opb)) { returnfalse;
}
if (graph != nullptr) {
ArenaAllocator* allocator = graph->GetAllocator();
HInstruction* oper; // Emit instructions for `a * i + b`. These are fine to overflow as they would have overflown // also if we had kept the loop. if (stride_value == 1) {
oper = new (allocator) HAdd(type, opa, opb);
} elseif (stride_value == -1) {
oper = new (graph->GetAllocator()) HSub(type, opb, opa);
} else {
HInstruction* mul = new (allocator) HMul(type, graph->GetConstant(type, stride_value), opa);
oper = new (allocator) HAdd(type, Insert(block, mul), opb);
}
*result = Insert(block, oper);
}
if (*needs_taken_test) { if (TryGenerateTakenTest(context, loop, trip->op_b, graph, block, result, opb)) {
*needs_taken_test = false; // taken care of
} else { returnfalse;
}
}
returntrue;
}
bool InductionVarRange::GenerateLastValuePolynomial(const HBasicBlock* context, const HLoopInformation* loop,
HInductionVarAnalysis::InductionInfo* info,
HInductionVarAnalysis::InductionInfo* trip,
HGraph* graph,
HBasicBlock* block, /*out*/HInstruction** result) const {
DCHECK(info != nullptr);
DCHECK_EQ(info->induction_class, HInductionVarAnalysis::kPolynomial); // Detect known coefficients and trip count (always taken).
int64_t a = 0;
int64_t b = 0;
int64_t m = 0; if (IsConstant(context, loop, info->op_a->op_a, kExact, &a) &&
IsConstant(context, loop, info->op_a->op_b, kExact, &b) &&
IsConstant(context, loop, trip->op_a, kExact, &m) &&
m >= 1) { // Evaluate bounds on sum_i=0^m-1(a * i + b) + c for known // maximum index value m as a * (m * (m-1)) / 2 + b * m + c.
HInstruction* c = nullptr; if (GenerateCode(context,
loop,
info->op_b, /*trip=*/ nullptr,
graph,
block, /*is_min=*/ false,
graph ? &c : nullptr)) { if (graph != nullptr) {
DataType::Type type = info->type;
int64_t sum = a * ((m * (m - 1)) / 2) + b * m; if (type != DataType::Type::kInt64) {
sum = static_cast<int32_t>(sum); // okay to truncate
}
*result =
Insert(block, new (graph->GetAllocator()) HAdd(type, graph->GetConstant(type, sum), c));
} returntrue;
}
} returnfalse;
}
bool InductionVarRange::GenerateLastValueGeometric(const HBasicBlock* context, const HLoopInformation* loop,
HInductionVarAnalysis::InductionInfo* info,
HInductionVarAnalysis::InductionInfo* trip,
HGraph* graph,
HBasicBlock* block, /*out*/HInstruction** result) const {
DCHECK(info != nullptr);
DCHECK_EQ(info->induction_class, HInductionVarAnalysis::kGeometric); // Detect known base and trip count (always taken).
int64_t f = 0;
int64_t m = 0; if (IsInt64AndGet(info->fetch, &f) &&
f >= 1 &&
IsConstant(context, loop, trip->op_a, kExact, &m) &&
m >= 1) {
HInstruction* opa = nullptr;
HInstruction* opb = nullptr; if (GenerateCode(
context, loop, info->op_a, /*trip=*/ nullptr, graph, block, /*is_min=*/ false, &opa) &&
GenerateCode(
context, loop, info->op_b, /*trip=*/ nullptr, graph, block, /*is_min=*/ false, &opb)) { if (graph != nullptr) {
DataType::Type type = info->type; // Compute f ^ m for known maximum index value m. bool overflow = false;
int64_t fpow = IntPow(f, m, &overflow); if (info->operation == HInductionVarAnalysis::kDiv) { // For division, any overflow truncates to zero. if (overflow || (type != DataType::Type::kInt64 && !CanLongValueFitIntoInt(fpow))) {
fpow = 0;
}
} elseif (type != DataType::Type::kInt64) { // For multiplication, okay to truncate to required precision.
DCHECK(info->operation == HInductionVarAnalysis::kMul);
fpow = static_cast<int32_t>(fpow);
} // Generate code. if (fpow == 0) { // Special case: repeated mul/div always yields zero.
*result = graph->GetConstant(type, 0);
} else { // Last value: a * f ^ m + b or a * f ^ -m + b.
HInstruction* e = nullptr;
ArenaAllocator* allocator = graph->GetAllocator(); if (info->operation == HInductionVarAnalysis::kMul) {
e = new (allocator) HMul(type, opa, graph->GetConstant(type, fpow));
} else {
e = new (allocator) HDiv(type, opa, graph->GetConstant(type, fpow), kNoDexPc);
}
*result = Insert(block, new (allocator) HAdd(type, Insert(block, e), opb));
}
} returntrue;
}
} returnfalse;
}
bool InductionVarRange::GenerateLastValueWrapAround(const HBasicBlock* context, const HLoopInformation* loop,
HInductionVarAnalysis::InductionInfo* info,
HInductionVarAnalysis::InductionInfo* trip,
HGraph* graph,
HBasicBlock* block, /*out*/HInstruction** result) const {
DCHECK(info != nullptr);
DCHECK_EQ(info->induction_class, HInductionVarAnalysis::kWrapAround); // Count depth.
int32_t depth = 0; for (; info->induction_class == HInductionVarAnalysis::kWrapAround;
info = info->op_b, ++depth) {} // Handle wrap(x, wrap(.., y)) if trip count reaches an invariant at end. // TODO: generalize, but be careful to adjust the terminal.
int64_t m = 0; if (info->induction_class == HInductionVarAnalysis::kInvariant &&
IsConstant(context, loop, trip->op_a, kExact, &m) &&
m >= depth) { return GenerateCode(
context, loop, info, /*trip=*/ nullptr, graph, block, /*is_min=*/ false, result);
} returnfalse;
}
bool InductionVarRange::GenerateLastValuePeriodic(const HBasicBlock* context, const HLoopInformation* loop,
HInductionVarAnalysis::InductionInfo* info,
HInductionVarAnalysis::InductionInfo* trip,
HGraph* graph,
HBasicBlock* block, /*out*/ HInstruction** result, /*inout*/ bool* needs_taken_test) const {
DCHECK(info != nullptr);
DCHECK_EQ(info->induction_class, HInductionVarAnalysis::kPeriodic); // Count period and detect all-invariants.
int64_t period = 1; bool all_invariants = true;
HInductionVarAnalysis::InductionInfo* p = info; for (; p->induction_class == HInductionVarAnalysis::kPeriodic; p = p->op_b, ++period) {
DCHECK_EQ(p->op_a->induction_class, HInductionVarAnalysis::kInvariant); if (p->op_a->operation != HInductionVarAnalysis::kFetch) {
all_invariants = false;
}
}
DCHECK_EQ(p->induction_class, HInductionVarAnalysis::kInvariant); if (p->operation != HInductionVarAnalysis::kFetch) {
all_invariants = false;
} // Don't rely on FP arithmetic to be precise, unless the full period // consist of pre-computed expressions only. if (info->type == DataType::Type::kFloat32 || info->type == DataType::Type::kFloat64) { if (!all_invariants) { returnfalse;
}
} // Handle any periodic(x, periodic(.., y)) for known maximum index value m.
int64_t m = 0; if (IsConstant(context, loop, trip->op_a, kExact, &m) && m >= 1) {
int64_t li = m % period; for (int64_t i = 0; i < li; info = info->op_b, i++) {} if (info->induction_class == HInductionVarAnalysis::kPeriodic) {
info = info->op_a;
} return GenerateCode(
context, loop, info, /*trip=*/ nullptr, graph, block, /*is_min=*/ false, result);
} // Handle periodic(x, y) using even/odd-select on trip count. Enter trip count expression // directly to obtain the maximum index value t even if taken test is needed.
HInstruction* x = nullptr;
HInstruction* y = nullptr;
HInstruction* t = nullptr;
// Overflows when the stride is equal to `1` are fine since the periodicity is // `2` and the lowest bit is the same. Similar with `-1`. auto allow_potential_overflow = [&]() {
int64_t stride_value = 0; return IsConstant(context, loop, trip->op_a->op_b, kExact, &stride_value) &&
(stride_value == 1 || stride_value == -1);
};
if (period == 2 &&
GenerateCode(context,
loop,
info->op_a, /*trip=*/ nullptr,
graph,
block, /*is_min=*/ false,
graph ? &x : nullptr) &&
GenerateCode(context,
loop,
info->op_b, /*trip=*/ nullptr,
graph,
block, /*is_min=*/ false,
graph ? &y : nullptr) &&
GenerateCode(context,
loop,
trip->op_a, /*trip=*/ nullptr,
graph,
block, /*is_min=*/ false,
graph ? &t : nullptr,
allow_potential_overflow())) { // During actual code generation (graph != nullptr), generate is_even ? x : y. if (graph != nullptr) {
DataType::Type type = trip->type;
ArenaAllocator* allocator = graph->GetAllocator();
HInstruction* msk =
Insert(block, new (allocator) HAnd(type, t, graph->GetConstant(type, 1)));
HInstruction* is_even =
Insert(block, new (allocator) HEqual(msk, graph->GetConstant(type, 0), kNoDexPc));
*result = Insert(block, new (graph->GetAllocator()) HSelect(is_even, x, y, kNoDexPc));
}
if (*needs_taken_test) { if (TryGenerateTakenTest(context, loop, trip->op_b, graph, block, result, x)) {
*needs_taken_test = false; // taken care of
} else { returnfalse;
}
} returntrue;
} returnfalse;
}
bool InductionVarRange::GenerateCode(const HBasicBlock* context, const HLoopInformation* loop,
HInductionVarAnalysis::InductionInfo* info,
HInductionVarAnalysis::InductionInfo* trip,
HGraph* graph, // when set, code is generated
HBasicBlock* block, bool is_min, /*out*/ HInstruction** result, bool allow_potential_overflow) const { if (info != nullptr) { // If during codegen, the result is not needed (nullptr), simply return success. if (graph != nullptr && result == nullptr) { returntrue;
} // Handle current operation.
DataType::Type type = info->type;
HInstruction* opa = nullptr;
HInstruction* opb = nullptr; switch (info->induction_class) { case HInductionVarAnalysis::kInvariant: // Invariants (note that since invariants only have other invariants as // sub expressions, viz. no induction, there is no need to adjust is_min). switch (info->operation) { case HInductionVarAnalysis::kAdd: case HInductionVarAnalysis::kSub: case HInductionVarAnalysis::kMul: case HInductionVarAnalysis::kDiv: case HInductionVarAnalysis::kRem: case HInductionVarAnalysis::kXor: case HInductionVarAnalysis::kLT: case HInductionVarAnalysis::kLE: case HInductionVarAnalysis::kGT: case HInductionVarAnalysis::kGE: if (GenerateCode(context,
loop,
info->op_a,
trip,
graph,
block,
is_min,
&opa,
allow_potential_overflow) &&
GenerateCode(context,
loop,
info->op_b,
trip,
graph,
block,
is_min,
&opb,
allow_potential_overflow)) { // Check for potentially invalid operations. if (!allow_potential_overflow) { switch (info->operation) { case HInductionVarAnalysis::kAdd: return TryGenerateAddWithoutOverflow(
context, loop, info, graph, opa, opb, result); case HInductionVarAnalysis::kSub: return TryGenerateSubWithoutOverflow(context, loop, info, graph, opa, result); default: // The rest of the operations are not relevant in the cases where // `allow_potential_overflow` is false. Fall through to the allowed overflow // case. break;
}
}
// Overflows here are accepted. if (graph != nullptr) {
HInstruction* operation = nullptr; switch (info->operation) { case HInductionVarAnalysis::kAdd:
operation = new (graph->GetAllocator()) HAdd(type, opa, opb); break; case HInductionVarAnalysis::kSub:
operation = new (graph->GetAllocator()) HSub(type, opa, opb); break; case HInductionVarAnalysis::kMul:
operation = new (graph->GetAllocator()) HMul(type, opa, opb, kNoDexPc); break; case HInductionVarAnalysis::kDiv:
operation = new (graph->GetAllocator()) HDiv(type, opa, opb, kNoDexPc); break; case HInductionVarAnalysis::kRem:
operation = new (graph->GetAllocator()) HRem(type, opa, opb, kNoDexPc); break; case HInductionVarAnalysis::kXor:
operation = new (graph->GetAllocator()) HXor(type, opa, opb); break; case HInductionVarAnalysis::kLT:
operation = new (graph->GetAllocator()) HLessThan(opa, opb); break; case HInductionVarAnalysis::kLE:
operation = new (graph->GetAllocator()) HLessThanOrEqual(opa, opb); break; case HInductionVarAnalysis::kGT:
operation = new (graph->GetAllocator()) HGreaterThan(opa, opb); break; case HInductionVarAnalysis::kGE:
operation = new (graph->GetAllocator()) HGreaterThanOrEqual(opa, opb); break; default:
LOG(FATAL) << "unknown operation";
}
*result = Insert(block, operation);
} returntrue;
} break; case HInductionVarAnalysis::kNeg: if (GenerateCode(context,
loop,
info->op_b,
trip,
graph,
block,
!is_min,
&opb,
allow_potential_overflow)) { if (graph != nullptr) {
*result = Insert(block, new (graph->GetAllocator()) HNeg(type, opb));
} returntrue;
} break; case HInductionVarAnalysis::kFetch: if (graph != nullptr) {
*result = info->fetch; // already in HIR
} returntrue; case HInductionVarAnalysis::kTripCountInLoop: case HInductionVarAnalysis::kTripCountInLoopUnsafe: if (UseFullTripCount(context, loop, is_min)) { // Generate the full trip count (do not subtract 1 as we do in loop body). return GenerateCode(context,
loop,
info->op_a,
trip,
graph,
block,
is_min,
result,
allow_potential_overflow);
}
FALLTHROUGH_INTENDED; case HInductionVarAnalysis::kTripCountInBody: case HInductionVarAnalysis::kTripCountInBodyUnsafe: if (is_min) { if (graph != nullptr) {
*result = graph->GetConstant(type, 0);
} returntrue;
} elseif (IsContextInBody(context, loop) ||
(context == loop->GetHeader() && !allow_potential_overflow)) { if (GenerateCode(context,
loop,
info->op_a,
trip,
graph,
block,
is_min,
&opb,
allow_potential_overflow)) { if (graph != nullptr) { if (IsContextInBody(context, loop)) {
ArenaAllocator* allocator = graph->GetAllocator();
*result =
Insert(block, new (allocator) HSub(type, opb, graph->GetConstant(type, 1)));
} else { // We want to generate the full trip count since we want the last value. This // will be combined with an `is_taken` test so we don't want to subtract one.
DCHECK(context == loop->GetHeader()); // TODO(solanes): Remove the !allow_potential_overflow restriction and allow // other parts e.g. BCE to take advantage of this.
DCHECK(!allow_potential_overflow);
*result = opb;
}
} returntrue;
}
} break; case HInductionVarAnalysis::kNop:
LOG(FATAL) << "unexpected invariant nop";
} // switch invariant operation break; case HInductionVarAnalysis::kLinear: { // Linear induction a * i + b, for normalized 0 <= i < TC. For ranges, this should // be restricted to a unit stride to avoid arithmetic wrap-around situations that // are harder to guard against. For a last value, requesting min/max based on any // known stride yields right value. Always avoid any narrowing linear induction or // any type mismatch between the linear induction and the trip count expression. // TODO: careful runtime type conversions could generalize this latter restriction. if (!HInductionVarAnalysis::IsNarrowingLinear(info) && trip->type == type) {
int64_t stride_value = 0; if (IsConstant(context, loop, info->op_a, kExact, &stride_value) &&
CanLongValueFitIntoInt(stride_value)) { constbool is_min_a = stride_value >= 0 ? is_min : !is_min; if (GenerateCode(context,
loop,
trip,
trip,
graph,
block,
is_min_a,
&opa,
allow_potential_overflow) &&
GenerateCode(context,
loop,
info->op_b,
trip,
graph,
block,
is_min,
&opb,
allow_potential_overflow)) { if (graph != nullptr) {
ArenaAllocator* allocator = graph->GetAllocator();
HInstruction* oper; if (stride_value == 1) {
oper = new (allocator) HAdd(type, opa, opb);
} elseif (stride_value == -1) {
oper = new (graph->GetAllocator()) HSub(type, opb, opa);
} else {
HInstruction* mul = new (allocator) HMul(type, graph->GetConstant(type, stride_value), opa);
oper = new (allocator) HAdd(type, Insert(block, mul), opb);
}
*result = Insert(block, oper);
} returntrue;
}
}
} break;
} case HInductionVarAnalysis::kPolynomial: case HInductionVarAnalysis::kGeometric: break; case HInductionVarAnalysis::kWrapAround: case HInductionVarAnalysis::kPeriodic: { // Wrap-around and periodic inductions are restricted to constants only, so that extreme // values are easy to test at runtime without complications of arithmetic wrap-around.
Value extreme = GetVal(context, loop, info, trip, is_min); if (IsConstantValue(extreme)) { if (graph != nullptr) {
*result = graph->GetConstant(type, extreme.b_constant);
} returntrue;
} break;
}
} // switch induction class
} returnfalse;
}
bool InductionVarRange::TryGenerateAddWithoutOverflow(const HBasicBlock* context, const HLoopInformation* loop,
HInductionVarAnalysis::InductionInfo* info,
HGraph* graph, /*in*/ HInstruction* opa, /*in*/ HInstruction* opb, /*out*/ HInstruction** result) const { // Calculate `a + b` making sure we can't overflow.
int64_t val_a; constbool a_is_const = IsConstant(context, loop, info->op_a, kExact, &val_a);
int64_t val_b; constbool b_is_const = IsConstant(context, loop, info->op_b, kExact, &val_b); if (a_is_const && b_is_const) { // Calculate `a + b` and use that. Note that even when the values are known, // their addition can still overflow.
Value add_val = AddValue(Value(val_a), Value(val_b)); if (add_val.is_known) {
DCHECK(IsConstantValue(add_val)); // Known value not overflowing. if (graph != nullptr) {
*result = graph->GetConstant(info->type, add_val.b_constant);
} returntrue;
}
}
// When `a` is `0`, we can just use `b`. if (a_is_const && val_a == 0) { if (graph != nullptr) {
*result = opb;
} returntrue;
}
if (b_is_const && val_b == 0) { if (graph != nullptr) {
*result = opa;
} returntrue;
}
// Couldn't safely calculate the addition. returnfalse;
}
bool InductionVarRange::TryGenerateSubWithoutOverflow(const HBasicBlock* context, const HLoopInformation* loop,
HInductionVarAnalysis::InductionInfo* info,
HGraph* graph, /*in*/ HInstruction* opa, /*out*/ HInstruction** result) const { // Calculate `a - b` making sure we can't overflow.
int64_t val_b; if (!IsConstant(context, loop, info->op_b, kExact, &val_b)) { // If b is unknown, a - b can potentially overflow for any value of a since b // can be Integer.MIN_VALUE. returnfalse;
}
int64_t val_a; if (IsConstant(context, loop, info->op_a, kExact, &val_a)) { // Calculate `a - b` and use that. Note that even when the values are known, // their subtraction can still overflow.
Value sub_val = SubValue(Value(val_a), Value(val_b)); if (sub_val.is_known) {
DCHECK(IsConstantValue(sub_val)); // Known value not overflowing. if (graph != nullptr) {
*result = graph->GetConstant(info->type, sub_val.b_constant);
} returntrue;
}
}
// When `b` is `0`, we can just use `a`. if (val_b == 0) { if (graph != nullptr) {
*result = opa;
} returntrue;
}
// Couldn't safely calculate the subtraction. returnfalse;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.