}
/**
- * Returns narrowest data type.
+ * Returns result of implicit widening type conversion done in HIR.
*/
-static Primitive::Type Narrowest(Primitive::Type type1, Primitive::Type type2) {
- return Primitive::ComponentSize(type1) <= Primitive::ComponentSize(type2) ? type1 : type2;
+static Primitive::Type ImplicitConversion(Primitive::Type type) {
+ switch (type) {
+ case Primitive::kPrimShort:
+ case Primitive::kPrimChar:
+ case Primitive::kPrimByte:
+ case Primitive::kPrimBoolean:
+ return Primitive::kPrimInt;
+ default:
+ return type;
+ }
}
//
} else if (instruction->IsSelect()) {
info = TransferPhi(loop, instruction, /*input_index*/ 0, /*adjust_input_size*/ 1);
} else if (instruction->IsTypeConversion()) {
- info = TransferCnv(LookupInfo(loop, instruction->InputAt(0)),
- instruction->AsTypeConversion()->GetInputType(),
- instruction->AsTypeConversion()->GetResultType());
+ info = TransferConversion(LookupInfo(loop, instruction->InputAt(0)),
+ instruction->AsTypeConversion()->GetInputType(),
+ instruction->AsTypeConversion()->GetResultType());
} else if (instruction->IsBoundsCheck()) {
info = LookupInfo(loop, instruction->InputAt(0)); // Pass-through.
}
return;
}
- // Store interesting cycle.
- AssignCycle(phi->AsPhi());
+ // Store interesting cycle in each loop phi.
+ for (size_t i = 0; i < size; i++) {
+ if (scc_[i]->IsLoopHeaderPhi()) {
+ AssignCycle(scc_[i]->AsPhi());
+ }
+ }
// Singleton is wrap-around induction if all internal links have the same meaning.
if (size == 1) {
} else if (instruction->IsSelect()) {
update = SolvePhi(instruction, /*input_index*/ 0, /*adjust_input_size*/ 1); // acts like Phi
} else if (instruction->IsTypeConversion()) {
- update = SolveCnv(instruction->AsTypeConversion());
+ update = SolveConversion(loop, phi, instruction->AsTypeConversion());
}
if (update == nullptr) {
return;
// wrap-around, or periodic can be combined with an invariant to yield a similar result.
// Two linear or two polynomial inputs can be combined too. Other combinations fail.
if (a != nullptr && b != nullptr) {
- type_ = Narrowest(type_, Narrowest(a->type, b->type));
- if (a->induction_class == kInvariant && b->induction_class == kInvariant) {
+ if (IsNarrowingLinear(a) || IsNarrowingLinear(b)) {
+ return nullptr; // no transfer
+ } else if (a->induction_class == kInvariant && b->induction_class == kInvariant) {
return CreateInvariantOp(op, a, b);
} else if ((a->induction_class == kLinear && b->induction_class == kLinear) ||
(a->induction_class == kPolynomial && b->induction_class == kPolynomial)) {
// Transfer over a unary negation: an invariant, linear, polynomial, geometric (mul),
// wrap-around, or periodic input yields a similar but negated induction as result.
if (a != nullptr) {
- type_ = Narrowest(type_, a->type);
- if (a->induction_class == kInvariant) {
+ if (IsNarrowingLinear(a)) {
+ return nullptr; // no transfer
+ } else if (a->induction_class == kInvariant) {
return CreateInvariantOp(kNeg, nullptr, a);
} else if (a->induction_class != kGeometric || a->operation == kMul) {
return CreateInduction(a->induction_class,
// wrap-around, or periodic can be multiplied with an invariant to yield a similar
// but multiplied result. Two non-invariant inputs cannot be multiplied, however.
if (a != nullptr && b != nullptr) {
- type_ = Narrowest(type_, Narrowest(a->type, b->type));
- if (a->induction_class == kInvariant && b->induction_class == kInvariant) {
+ if (IsNarrowingLinear(a) || IsNarrowingLinear(b)) {
+ return nullptr; // no transfer
+ } else if (a->induction_class == kInvariant && b->induction_class == kInvariant) {
return CreateInvariantOp(kMul, a, b);
} else if (a->induction_class == kInvariant && (b->induction_class != kGeometric ||
b->operation == kMul)) {
return nullptr;
}
-HInductionVarAnalysis::InductionInfo* HInductionVarAnalysis::TransferCnv(InductionInfo* a,
- Primitive::Type from,
- Primitive::Type to) {
+HInductionVarAnalysis::InductionInfo* HInductionVarAnalysis::TransferConversion(
+ InductionInfo* a,
+ Primitive::Type from,
+ Primitive::Type to) {
if (a != nullptr) {
- // Allow narrowing conversion on linear induction in certain cases.
- if (IsNarrowingIntegralConversion(from, to)) {
- if (a->induction_class == kLinear) {
- if (a->type == to || (a->type == from && IsNarrowingIntegralConversion(from, to))) {
- return CreateInduction(kLinear, kNop, a->op_a, a->op_b, /*fetch*/ nullptr, to);
- }
- }
+ // Allow narrowing conversion on linear induction in certain cases:
+ // induction is already at narrow type, or can be made narrower.
+ if (IsNarrowingIntegralConversion(from, to) &&
+ a->induction_class == kLinear &&
+ (a->type == to || IsNarrowingIntegralConversion(a->type, to))) {
+ return CreateInduction(kLinear, kNop, a->op_a, a->op_b, /*fetch*/ nullptr, to);
}
}
return nullptr;
return nullptr;
}
-HInductionVarAnalysis::InductionInfo* HInductionVarAnalysis::SolveCnv(HTypeConversion* conversion) {
+HInductionVarAnalysis::InductionInfo* HInductionVarAnalysis::SolveConversion(
+ HLoopInformation* loop,
+ HInstruction* entry_phi,
+ HTypeConversion* conversion) {
Primitive::Type from = conversion->GetInputType();
Primitive::Type to = conversion->GetResultType();
- // A narrowing conversion is allowed within the cycle of a linear induction, provided that the
- // narrowest encountered type is recorded with the induction to account for the precision loss.
- if (IsNarrowingIntegralConversion(from, to)) {
- auto it = cycle_.find(conversion->GetInput());
- if (it != cycle_.end() && it->second->induction_class == kInvariant) {
- type_ = Narrowest(type_, to);
- return it->second;
+ // A narrowing conversion is allowed as *last* operation of the cycle of a linear induction
+ // with an initial value that fits the type, provided that the narrowest encountered type is
+ // recorded with the induction to account for the precision loss. The narrower induction does
+ // *not* transfer to any wider operations, however, since these may yield out-of-type values
+ if (entry_phi->InputCount() == 2 && conversion == entry_phi->InputAt(1)) {
+ int64_t min = Primitive::MinValueOfIntegralType(to);
+ int64_t max = Primitive::MaxValueOfIntegralType(to);
+ int64_t value = 0;
+ InductionInfo* initial = LookupInfo(loop, entry_phi->InputAt(0));
+ if (IsNarrowingIntegralConversion(from, to) &&
+ IsAtLeast(initial, &value) && value >= min &&
+ IsAtMost(initial, &value) && value <= max) {
+ auto it = cycle_.find(conversion->GetInput());
+ if (it != cycle_.end() && it->second->induction_class == kInvariant) {
+ type_ = to;
+ return it->second;
+ }
}
}
return nullptr;
HCondition* condition = if_expr->AsCondition();
InductionInfo* a = LookupInfo(loop, condition->InputAt(0));
InductionInfo* b = LookupInfo(loop, condition->InputAt(1));
- Primitive::Type type = condition->InputAt(0)->GetType();
+ Primitive::Type type = ImplicitConversion(condition->InputAt(0)->GetType());
// Determine if the loop control uses a known sequence on an if-exit (X outside) or on
// an if-iterate (X inside), expressed as if-iterate when passed into VisitCondition().
if (a == nullptr || b == nullptr) {
int64_t stride_value,
Primitive::Type type,
IfCondition cmp) {
- const int64_t min = Primitive::MinValueOfIntegralType(type);
- const int64_t max = Primitive::MaxValueOfIntegralType(type);
+ int64_t min = Primitive::MinValueOfIntegralType(type);
+ int64_t max = Primitive::MaxValueOfIntegralType(type);
// Some rules under which it is certain at compile-time that the loop is finite.
int64_t value;
switch (cmp) {
min++;
}
// Do both bounds fit the range?
- // Note: The `value` is initialized to please valgrind - the compiler can reorder
- // the return value check with the `value` check, b/27651442 .
int64_t value = 0;
return IsAtLeast(lower_expr, &value) && value >= min &&
IsAtMost(lower_expr, &value) && value <= max &&
return CreateSimplifiedInvariant(kSub, b->op_b, b->op_a);
}
}
- return new (graph_->GetArena()) InductionInfo(kInvariant, op, a, b, nullptr, b->type);
+ return new (graph_->GetArena()) InductionInfo(
+ kInvariant, op, a, b, nullptr, ImplicitConversion(b->type));
}
HInstruction* HInductionVarAnalysis::GetShiftConstant(HLoopInformation* loop,
return InductionVarRange(this).IsConstant(info, InductionVarRange::kAtLeast, value);
}
+bool HInductionVarAnalysis::IsNarrowingLinear(InductionInfo* info) {
+ return info != nullptr &&
+ info->induction_class == kLinear &&
+ (info->type == Primitive::kPrimByte ||
+ info->type == Primitive::kPrimShort ||
+ info->type == Primitive::kPrimChar ||
+ (info->type == Primitive::kPrimInt && (info->op_a->type == Primitive::kPrimLong ||
+ info->op_b->type == Primitive::kPrimLong)));
+}
+
bool HInductionVarAnalysis::InductionEqual(InductionInfo* info1,
InductionInfo* info2) {
// Test structural equality only, without accounting for simplifications.
InductionInfo* TransferAddSub(InductionInfo* a, InductionInfo* b, InductionOp op);
InductionInfo* TransferNeg(InductionInfo* a);
InductionInfo* TransferMul(InductionInfo* a, InductionInfo* b);
- InductionInfo* TransferCnv(InductionInfo* a, Primitive::Type from, Primitive::Type to);
+ InductionInfo* TransferConversion(InductionInfo* a, Primitive::Type from, Primitive::Type to);
// Solvers.
InductionInfo* SolvePhi(HInstruction* phi, size_t input_index, size_t adjust_input_size);
HInstruction* entry_phi,
HInstruction* instruction,
int64_t oppositive_value);
- InductionInfo* SolveCnv(HTypeConversion* conversion);
+ InductionInfo* SolveConversion(HLoopInformation* loop,
+ HInstruction* entry_phi,
+ HTypeConversion* conversion);
// Trip count information.
void VisitControl(HLoopInformation* loop);
bool IsAtLeast(InductionInfo* info, /*out*/ int64_t* value);
// Helpers.
+ static bool IsNarrowingLinear(InductionInfo* info);
static bool InductionEqual(InductionInfo* info1, InductionInfo* info2);
static std::string FetchToString(HInstruction* fetch);
static std::string InductionToString(InductionInfo* info);
iva_->LookupInfo(loop_body_[0]->GetLoopInformation(), instruction2));
}
+ // Returns true for narrowing linear induction.
+ bool IsNarrowingLinear(HInstruction* instruction) {
+ return HInductionVarAnalysis::IsNarrowingLinear(
+ iva_->LookupInfo(loop_body_[0]->GetLoopInformation(), instruction));
+ }
+
// Performs InductionVarAnalysis (after proper set up).
void PerformInductionVarAnalysis() {
graph_->BuildDominatorTree();
// }
BuildLoopNest(1);
HInstruction* conv = InsertInstruction(
- new (&allocator_) HTypeConversion(Primitive::kPrimByte, basic_[0], -1), 0);
+ new (&allocator_) HTypeConversion(Primitive::kPrimByte, basic_[0], kNoDexPc), 0);
HInstruction* store1 = InsertArrayStore(conv, 0);
HInstruction* store2 = InsertArrayStore(basic_[0], 0);
PerformInductionVarAnalysis();
- // Regular int induction (i) is "transferred" over conversion into byte induction (k).
+ // Regular int induction (i) is transferred over conversion into byte induction (k).
EXPECT_STREQ("((1) * i + (0)):PrimByte", GetInductionInfo(store1->InputAt(1), 0).c_str());
EXPECT_STREQ("((1) * i + (0)):PrimInt", GetInductionInfo(store2->InputAt(1), 0).c_str());
EXPECT_STREQ("((1) * i + (1)):PrimInt", GetInductionInfo(increment_[0], 0).c_str());
+ // Narrowing detected.
+ EXPECT_TRUE(IsNarrowingLinear(store1->InputAt(1)));
+ EXPECT_FALSE(IsNarrowingLinear(store2->InputAt(1)));
+
// Type matters!
EXPECT_FALSE(HaveSameInduction(store1->InputAt(1), store2->InputAt(1)));
// }
BuildLoopNest(1);
HInstruction* conv = InsertInstruction(
- new (&allocator_) HTypeConversion(Primitive::kPrimByte, basic_[0], -1), 0);
+ new (&allocator_) HTypeConversion(Primitive::kPrimByte, basic_[0], kNoDexPc), 0);
HInstruction* store1 = InsertArrayStore(conv, 0);
HInstruction* add = InsertInstruction(
new (&allocator_) HAdd(Primitive::kPrimInt, conv, constant1_), 0);
PerformInductionVarAnalysis();
- // Byte induction (k) is "transferred" over conversion into addition (k + 1).
- // This means only values within byte range can be trusted (even though
- // addition can jump out of the range of course).
+ // Byte induction (k) is detected, but it does not transfer over the addition,
+ // since this may yield out-of-type values.
EXPECT_STREQ("((1) * i + (0)):PrimByte", GetInductionInfo(store1->InputAt(1), 0).c_str());
- EXPECT_STREQ("((1) * i + (1)):PrimByte", GetInductionInfo(store2->InputAt(1), 0).c_str());
+ EXPECT_STREQ("", GetInductionInfo(store2->InputAt(1), 0).c_str());
+
+ // Narrowing detected.
+ EXPECT_TRUE(IsNarrowingLinear(store1->InputAt(1)));
+ EXPECT_FALSE(IsNarrowingLinear(store2->InputAt(1))); // works for null
+}
+
+TEST_F(InductionVarAnalysisTest, ByteInduction) {
+ // Setup:
+ // k = -128;
+ // for (int i = 0; i < 100; i++) {
+ // k = k + 1;
+ // k = (byte) k;
+ // }
+ BuildLoopNest(1);
+ HPhi* k_header = InsertLoopPhi(0, 0);
+ k_header->AddInput(graph_->GetIntConstant(-128));
+
+ HInstruction* add = InsertInstruction(
+ new (&allocator_) HAdd(Primitive::kPrimInt, k_header, constant1_), 0);
+ HInstruction* conv = InsertInstruction(
+ new (&allocator_) HTypeConversion(Primitive::kPrimByte, add, kNoDexPc), 0);
+ k_header->AddInput(conv);
+ PerformInductionVarAnalysis();
+
+ // Byte induction (k) is detected, but it does not transfer over the addition,
+ // since this may yield out-of-type values.
+ EXPECT_STREQ("((1) * i + (-128)):PrimByte", GetInductionInfo(k_header, 0).c_str());
+ EXPECT_STREQ("", GetInductionInfo(add, 0).c_str());
+
+ // Narrowing detected.
+ EXPECT_TRUE(IsNarrowingLinear(k_header));
+ EXPECT_FALSE(IsNarrowingLinear(add)); // works for null
+}
+
+TEST_F(InductionVarAnalysisTest, NoByteInduction1) {
+ // Setup:
+ // k = -129; / does not fit!
+ // for (int i = 0; i < 100; i++) {
+ // k = k + 1;
+ // k = (byte) k;
+ // }
+ BuildLoopNest(1);
+ HPhi* k_header = InsertLoopPhi(0, 0);
+ k_header->AddInput(graph_->GetIntConstant(-129));
+
+ HInstruction* add = InsertInstruction(
+ new (&allocator_) HAdd(Primitive::kPrimInt, k_header, constant1_), 0);
+ HInstruction* conv = InsertInstruction(
+ new (&allocator_) HTypeConversion(Primitive::kPrimByte, add, kNoDexPc), 0);
+ k_header->AddInput(conv);
+ PerformInductionVarAnalysis();
+
+ EXPECT_STREQ("", GetInductionInfo(k_header, 0).c_str());
+ EXPECT_STREQ("", GetInductionInfo(add, 0).c_str());
+}
+
+TEST_F(InductionVarAnalysisTest, NoByteInduction2) {
+ // Setup:
+ // k = 0;
+ // for (int i = 0; i < 100; i++) {
+ // k = (byte) k; // conversion not done last!
+ // k = k + 1;
+ // }
+ BuildLoopNest(1);
+ HPhi* k_header = InsertLoopPhi(0, 0);
+ k_header->AddInput(constant0_);
+
+ HInstruction* conv = InsertInstruction(
+ new (&allocator_) HTypeConversion(Primitive::kPrimByte, k_header, kNoDexPc), 0);
+ HInstruction* add = InsertInstruction(
+ new (&allocator_) HAdd(Primitive::kPrimInt, conv, constant1_), 0);
+ k_header->AddInput(add);
+ PerformInductionVarAnalysis();
+
+ EXPECT_STREQ("", GetInductionInfo(k_header, 0).c_str());
+ EXPECT_STREQ("", GetInductionInfo(add, 0).c_str());
}
TEST_F(InductionVarAnalysisTest, ByteLoopControl1) {
basic_[0]->ReplaceInput(graph_->GetIntConstant(-128), 0);
HInstruction* ifs = loop_header_[0]->GetLastInstruction()->GetPrevious();
ifs->ReplaceInput(graph_->GetIntConstant(127), 1);
- HInstruction* conv = new(&allocator_) HTypeConversion(Primitive::kPrimByte, increment_[0], -1);
+ HInstruction* conv =
+ new (&allocator_) HTypeConversion(Primitive::kPrimByte, increment_[0], kNoDexPc);
loop_body_[0]->InsertInstructionBefore(conv, increment_[0]->GetNext());
basic_[0]->ReplaceInput(conv, 1);
PerformInductionVarAnalysis();
- EXPECT_STREQ("((1) * i + ((-128) + (1))):PrimByte", GetInductionInfo(increment_[0], 0).c_str());
+ // Recorded at the phi, but not transferred to increment.
+ EXPECT_STREQ("((1) * i + (-128)):PrimByte", GetInductionInfo(basic_[0], 0).c_str());
+ EXPECT_STREQ("", GetInductionInfo(increment_[0], 0).c_str());
+
+ // Narrowing detected.
+ EXPECT_TRUE(IsNarrowingLinear(basic_[0]));
+ EXPECT_FALSE(IsNarrowingLinear(increment_[0])); // works for null
+
// Trip-count.
EXPECT_STREQ("(((127) - (-128)) (TC-loop) ((-128) < (127)))", GetTripCount(0).c_str());
}
basic_[0]->ReplaceInput(graph_->GetIntConstant(-128), 0);
HInstruction* ifs = loop_header_[0]->GetLastInstruction()->GetPrevious();
ifs->ReplaceInput(graph_->GetIntConstant(128), 1);
- HInstruction* conv = new(&allocator_) HTypeConversion(Primitive::kPrimByte, increment_[0], -1);
+ HInstruction* conv =
+ new (&allocator_) HTypeConversion(Primitive::kPrimByte, increment_[0], kNoDexPc);
loop_body_[0]->InsertInstructionBefore(conv, increment_[0]->GetNext());
basic_[0]->ReplaceInput(conv, 1);
PerformInductionVarAnalysis();
- EXPECT_STREQ("((1) * i + ((-128) + (1))):PrimByte", GetInductionInfo(increment_[0], 0).c_str());
+ // Recorded at the phi, but not transferred to increment.
+ EXPECT_STREQ("((1) * i + (-128)):PrimByte", GetInductionInfo(basic_[0], 0).c_str());
+ EXPECT_STREQ("", GetInductionInfo(increment_[0], 0).c_str());
+
+ // Narrowing detected.
+ EXPECT_TRUE(IsNarrowingLinear(basic_[0]));
+ EXPECT_FALSE(IsNarrowingLinear(increment_[0])); // works for null
+
// Trip-count undefined.
EXPECT_STREQ("", GetTripCount(0).c_str());
}
basic_[0]->ReplaceInput(graph_->GetIntConstant(-32768), 0);
HInstruction* ifs = loop_header_[0]->GetLastInstruction()->GetPrevious();
ifs->ReplaceInput(graph_->GetIntConstant(32767), 1);
- HInstruction* conv = new(&allocator_) HTypeConversion(Primitive::kPrimShort, increment_[0], -1);
+ HInstruction* conv =
+ new (&allocator_) HTypeConversion(Primitive::kPrimShort, increment_[0], kNoDexPc);
loop_body_[0]->InsertInstructionBefore(conv, increment_[0]->GetNext());
basic_[0]->ReplaceInput(conv, 1);
PerformInductionVarAnalysis();
- EXPECT_STREQ("((1) * i + ((-32768) + (1))):PrimShort",
- GetInductionInfo(increment_[0], 0).c_str());
+ // Recorded at the phi, but not transferred to increment.
+ EXPECT_STREQ("((1) * i + (-32768)):PrimShort", GetInductionInfo(basic_[0], 0).c_str());
+ EXPECT_STREQ("", GetInductionInfo(increment_[0], 0).c_str());
+
+ // Narrowing detected.
+ EXPECT_TRUE(IsNarrowingLinear(basic_[0]));
+ EXPECT_FALSE(IsNarrowingLinear(increment_[0])); // works for null
+
// Trip-count.
EXPECT_STREQ("(((32767) - (-32768)) (TC-loop) ((-32768) < (32767)))", GetTripCount(0).c_str());
}
basic_[0]->ReplaceInput(graph_->GetIntConstant(-32768), 0);
HInstruction* ifs = loop_header_[0]->GetLastInstruction()->GetPrevious();
ifs->ReplaceInput(graph_->GetIntConstant(32768), 1);
- HInstruction* conv = new(&allocator_) HTypeConversion(Primitive::kPrimShort, increment_[0], -1);
+ HInstruction* conv =
+ new (&allocator_) HTypeConversion(Primitive::kPrimShort, increment_[0], kNoDexPc);
loop_body_[0]->InsertInstructionBefore(conv, increment_[0]->GetNext());
basic_[0]->ReplaceInput(conv, 1);
PerformInductionVarAnalysis();
- EXPECT_STREQ("((1) * i + ((-32768) + (1))):PrimShort",
- GetInductionInfo(increment_[0], 0).c_str());
+ // Recorded at the phi, but not transferred to increment.
+ EXPECT_STREQ("((1) * i + (-32768)):PrimShort", GetInductionInfo(basic_[0], 0).c_str());
+ EXPECT_STREQ("", GetInductionInfo(increment_[0], 0).c_str());
+
+ // Narrowing detected.
+ EXPECT_TRUE(IsNarrowingLinear(basic_[0]));
+ EXPECT_FALSE(IsNarrowingLinear(increment_[0])); // works for null
+
// Trip-count undefined.
EXPECT_STREQ("", GetTripCount(0).c_str());
}
BuildLoopNest(1);
HInstruction* ifs = loop_header_[0]->GetLastInstruction()->GetPrevious();
ifs->ReplaceInput(graph_->GetIntConstant(65535), 1);
- HInstruction* conv = new(&allocator_) HTypeConversion(Primitive::kPrimChar, increment_[0], -1);
+ HInstruction* conv =
+ new (&allocator_) HTypeConversion(Primitive::kPrimChar, increment_[0], kNoDexPc);
loop_body_[0]->InsertInstructionBefore(conv, increment_[0]->GetNext());
basic_[0]->ReplaceInput(conv, 1);
PerformInductionVarAnalysis();
- EXPECT_STREQ("((1) * i + (1)):PrimChar", GetInductionInfo(increment_[0], 0).c_str());
+ // Recorded at the phi, but not transferred to increment.
+ EXPECT_STREQ("((1) * i + (0)):PrimChar", GetInductionInfo(basic_[0], 0).c_str());
+ EXPECT_STREQ("", GetInductionInfo(increment_[0], 0).c_str());
+
+ // Narrowing detected.
+ EXPECT_TRUE(IsNarrowingLinear(basic_[0]));
+ EXPECT_FALSE(IsNarrowingLinear(increment_[0])); // works for null
+
// Trip-count.
EXPECT_STREQ("((65535) (TC-loop) ((0) < (65535)))", GetTripCount(0).c_str());
}
BuildLoopNest(1);
HInstruction* ifs = loop_header_[0]->GetLastInstruction()->GetPrevious();
ifs->ReplaceInput(graph_->GetIntConstant(65536), 1);
- HInstruction* conv = new(&allocator_) HTypeConversion(Primitive::kPrimChar, increment_[0], -1);
+ HInstruction* conv =
+ new (&allocator_) HTypeConversion(Primitive::kPrimChar, increment_[0], kNoDexPc);
loop_body_[0]->InsertInstructionBefore(conv, increment_[0]->GetNext());
basic_[0]->ReplaceInput(conv, 1);
PerformInductionVarAnalysis();
- EXPECT_STREQ("((1) * i + (1)):PrimChar", GetInductionInfo(increment_[0], 0).c_str());
+ // Recorded at the phi, but not transferred to increment.
+ EXPECT_STREQ("((1) * i + (0)):PrimChar", GetInductionInfo(basic_[0], 0).c_str());
+ EXPECT_STREQ("", GetInductionInfo(increment_[0], 0).c_str());
+
+ // Narrowing detected.
+ EXPECT_TRUE(IsNarrowingLinear(basic_[0]));
+ EXPECT_FALSE(IsNarrowingLinear(increment_[0])); // works for null
+
// Trip-count undefined.
EXPECT_STREQ("", GetTripCount(0).c_str());
}
case Primitive::kPrimByte: {
// Constants within range only.
// TODO: maybe some room for improvement, like allowing widening conversions
- const int32_t min = Primitive::MinValueOfIntegralType(type);
- const int32_t max = Primitive::MaxValueOfIntegralType(type);
+ int32_t min = Primitive::MinValueOfIntegralType(type);
+ int32_t max = Primitive::MaxValueOfIntegralType(type);
return (IsConstantValue(v) && min <= v.b_constant && v.b_constant <= max)
? v
: InductionVarRange::Value();
int64_t b = 0;
if (IsConstant(info->op_a->op_a, kExact, &a) && CanLongValueFitIntoInt(a) && a >= 0 &&
IsConstant(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 known
+ // 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.
Value c = GetVal(info->op_b, trip, in_body, is_min);
if (is_min) {
}
} else if (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() == Primitive::kPrimInt &&
instruction->AsTypeConversion()->GetResultType() == Primitive::kPrimLong) {
return GetFetch(instruction->InputAt(0), trip, in_body, is_min);
InductionVarRange::Value InductionVarRange::AddValue(Value v1, Value v2) const {
if (v1.is_known && v2.is_known && IsSafeAdd(v1.b_constant, v2.b_constant)) {
- const int32_t b = v1.b_constant + v2.b_constant;
+ int32_t b = v1.b_constant + v2.b_constant;
if (v1.a_constant == 0) {
return Value(v2.instruction, v2.a_constant, b);
} else if (v2.a_constant == 0) {
InductionVarRange::Value InductionVarRange::SubValue(Value v1, Value v2) const {
if (v1.is_known && v2.is_known && IsSafeSub(v1.b_constant, v2.b_constant)) {
- const int32_t b = v1.b_constant - v2.b_constant;
+ int32_t b = v1.b_constant - v2.b_constant;
if (v1.a_constant == 0 && IsSafeSub(0, v2.a_constant)) {
return Value(v2.instruction, -v2.a_constant, b);
} else if (v2.a_constant == 0) {
IsConstant(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.
- // TODO: generalize
- HInstruction* c_instr = nullptr;
- if (GenerateCode(info->op_b, nullptr, graph, block, graph ? &c_instr : nullptr, false, false)) {
+ HInstruction* c = nullptr;
+ if (GenerateCode(info->op_b, nullptr, graph, block, graph ? &c : nullptr, false, false)) {
if (graph != nullptr) {
+ Primitive::Type type = info->type;
int64_t sum = a * ((m * (m - 1)) / 2) + b * m;
- *result = Insert(block, new (graph->GetArena()) HAdd(info->type,
- graph->GetIntConstant(sum), c_instr));
+ if (type != Primitive::kPrimLong) {
+ sum = static_cast<int32_t>(sum); // okay to truncate
+ }
+ *result =
+ Insert(block, new (graph->GetArena()) HAdd(type, graph->GetConstant(type, sum), c));
}
return true;
}
DCHECK_EQ(info->induction_class, HInductionVarAnalysis::kGeometric);
// Detect known base and trip count (always taken).
int64_t f = 0;
- int64_t t = 0;
- if (IsIntAndGet(info->fetch, &f) && f >= 1 && IsConstant(trip->op_a, kExact, &t) && t >= 1) {
+ int64_t m = 0;
+ if (IsIntAndGet(info->fetch, &f) && f >= 1 && IsConstant(trip->op_a, kExact, &m) && m >= 1) {
HInstruction* opa = nullptr;
HInstruction* opb = nullptr;
if (GenerateCode(info->op_a, nullptr, graph, block, &opa, false, false) &&
GenerateCode(info->op_b, nullptr, graph, block, &opb, false, false)) {
- // Compute f ^ t.
- int64_t fpowt = IntPow(f, t);
+ // Compute f ^ m for known maximum index value m.
+ int64_t fpow = IntPow(f, m);
if (graph != nullptr) {
- DCHECK(info->type == Primitive::kPrimInt); // due to codegen, generalize?
- if (fpowt == 0) {
+ DCHECK(info->operation == HInductionVarAnalysis::kMul ||
+ info->operation == HInductionVarAnalysis::kDiv);
+ Primitive::Type type = info->type;
+ if (fpow == 0) {
// Special case: repeated mul/div always yields zero.
- *result = graph->GetIntConstant(0);
- } else if (info->operation == HInductionVarAnalysis::kMul) {
- // Last value multiplication: a * f ^ t + b.
- HInstruction* mul = Insert(block,
- new (graph->GetArena()) HMul(info->type,
- opa,
- graph->GetIntConstant(fpowt)));
- *result = Insert(block, new (graph->GetArena()) HAdd(info->type, mul, opb));
+ *result = graph->GetConstant(type, 0);
} else {
- // Last value multiplication: a * f ^ -t + b.
- DCHECK_EQ(info->operation, HInductionVarAnalysis::kDiv);
- HInstruction* div = Insert(block,
- new (graph->GetArena()) HDiv(info->type,
- opa,
- graph->GetIntConstant(fpowt),
- kNoDexPc));
- *result = Insert(block, new (graph->GetArena()) HAdd(info->type, div, opb));
+ // Last value: a * f ^ m + b or a * f ^ -m + b.
+ if (type != Primitive::kPrimLong) {
+ fpow = static_cast<int32_t>(fpow); // okay to truncate
+ }
+ HInstruction* e = nullptr;
+ if (info->operation == HInductionVarAnalysis::kMul) {
+ e = new (graph->GetArena()) HMul(type, opa, graph->GetConstant(type, fpow));
+ } else {
+ e = new (graph->GetArena()) HDiv(type, opa, graph->GetConstant(type, fpow), kNoDexPc);
+ }
+ *result = Insert(block, new (graph->GetArena()) HAdd(type, Insert(block, e), opb));
}
}
return true;
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
- int64_t t = 0;
+ // TODO: generalize, but be careful to adjust the terminal.
+ int64_t m = 0;
if (info->induction_class == HInductionVarAnalysis::kInvariant &&
- IsConstant(trip->op_a, kExact, &t) && t >= depth &&
- GenerateCode(info, nullptr, graph, block, result, false, false)) {
- return true;
+ IsConstant(trip->op_a, kExact, &m) && m >= depth) {
+ return GenerateCode(info, nullptr, graph, block, result, false, false);
}
return false;
}
DCHECK(info != nullptr);
DCHECK_EQ(info->induction_class, HInductionVarAnalysis::kPeriodic);
// Count period.
- int32_t period = 1;
+ int64_t period = 1;
for (HInductionVarAnalysis::InductionInfo* p = info;
p->induction_class == HInductionVarAnalysis::kPeriodic;
p = p->op_b, ++period) {}
- // Handle periodic(x, y) case for restricted types.
- // TODO: generalize
- if (period != 2 ||
- trip->op_a->type != Primitive::kPrimInt ||
- (info->type != Primitive::kPrimInt && info->type != Primitive::kPrimBoolean)) {
- return false;
+ // Handle any periodic(x, periodic(.., y)) for known maximum index value m.
+ int64_t m = 0;
+ if (IsConstant(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(info, nullptr, graph, block, result, false, false);
}
- HInstruction* x_instr = nullptr;
- HInstruction* y_instr = nullptr;
- HInstruction* trip_expr = nullptr;
- if (GenerateCode(info->op_a, nullptr, graph, block, graph ? &x_instr : nullptr, false, false) &&
- GenerateCode(info->op_b, nullptr, graph, block, graph ? &y_instr : nullptr, false, false) &&
- GenerateCode(trip->op_a, nullptr, graph, block, graph ? &trip_expr : nullptr, false, false)) {
- // During actual code generation (graph != nullptr),
- // generate is_even ? x : y select instruction.
+ // 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;
+ if (period == 2 &&
+ GenerateCode(info->op_a, nullptr, graph, block, graph ? &x : nullptr, false, false) &&
+ GenerateCode(info->op_b, nullptr, graph, block, graph ? &y : nullptr, false, false) &&
+ GenerateCode(trip->op_a, nullptr, graph, block, graph ? &t : nullptr, false, false)) {
+ // During actual code generation (graph != nullptr), generate is_even ? x : y.
if (graph != nullptr) {
- HInstruction* is_even = Insert(block, new (graph->GetArena()) HEqual(
- Insert(block, new (graph->GetArena()) HAnd(
- Primitive::kPrimInt, trip_expr, graph->GetIntConstant(1))),
- graph->GetIntConstant(0), kNoDexPc));
- *result = Insert(block, new (graph->GetArena()) HSelect(is_even, x_instr, y_instr, kNoDexPc));
+ Primitive::Type type = trip->type;
+ HInstruction* msk =
+ Insert(block, new (graph->GetArena()) HAnd(type, t, graph->GetConstant(type, 1)));
+ HInstruction* is_even =
+ Insert(block, new (graph->GetArena()) HEqual(msk, graph->GetConstant(type, 0), kNoDexPc));
+ *result = Insert(block, new (graph->GetArena()) HSelect(is_even, x, y, kNoDexPc));
}
// Guard select with taken test if needed.
if (*needs_taken_test) {
- HInstruction* taken_test = nullptr;
- if (!GenerateCode(
- trip->op_b, nullptr, graph, block, graph ? &taken_test : nullptr, false, false)) {
+ HInstruction* is_taken = nullptr;
+ if (GenerateCode(trip->op_b, nullptr, graph, block, graph ? &is_taken : nullptr, false, false)) {
+ if (graph != nullptr) {
+ *result = Insert(block, new (graph->GetArena()) HSelect(is_taken, *result, x, kNoDexPc));
+ }
+ *needs_taken_test = false; // taken care of
+ } else {
return false;
- } else if (graph != nullptr) {
- *result = Insert(block,
- new (graph->GetArena()) HSelect(taken_test, *result, x_instr, kNoDexPc));
}
- *needs_taken_test = false; // taken care of
}
return true;
}
if (graph != nullptr && result == nullptr) {
return true;
}
- // Verify type safety.
- // TODO: generalize
- Primitive::Type type = Primitive::kPrimInt;
- if (info->type != Primitive::kPrimInt && info->type != Primitive::kPrimBoolean) {
- return false;
- }
// Handle current operation.
+ Primitive::Type type = info->type;
HInstruction* opa = nullptr;
HInstruction* opb = nullptr;
switch (info->induction_class) {
case HInductionVarAnalysis::kTripCountInBodyUnsafe:
if (is_min) {
if (graph != nullptr) {
- *result = graph->GetIntConstant(0);
+ *result = graph->GetConstant(type, 0);
}
return true;
} else if (in_body) {
if (GenerateCode(info->op_a, trip, graph, block, &opb, in_body, is_min)) {
if (graph != nullptr) {
- *result = Insert(block,
- new (graph->GetArena())
- HSub(type, opb, graph->GetIntConstant(1)));
+ *result =
+ Insert(block,
+ new (graph->GetArena()) HSub(type, opb, graph->GetConstant(type, 1)));
}
return true;
}
// 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
- // stride yields right value.
- int64_t stride_value = 0;
- if (IsConstant(info->op_a, kExact, &stride_value)) {
- const bool is_min_a = stride_value >= 0 ? is_min : !is_min;
- if (GenerateCode(trip, trip, graph, block, &opa, in_body, is_min_a) &&
- GenerateCode(info->op_b, trip, graph, block, &opb, in_body, is_min)) {
- if (graph != nullptr) {
- HInstruction* oper;
- if (stride_value == 1) {
- oper = new (graph->GetArena()) HAdd(type, opa, opb);
- } else if (stride_value == -1) {
- oper = new (graph->GetArena()) HSub(type, opb, opa);
- } else {
- HInstruction* mul = new (graph->GetArena()) HMul(
- type, graph->GetIntConstant(stride_value), opa);
- oper = new (graph->GetArena()) HAdd(type, Insert(block, mul), opb);
+ // 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(info->op_a, kExact, &stride_value) &&
+ CanLongValueFitIntoInt(stride_value)) {
+ const bool is_min_a = stride_value >= 0 ? is_min : !is_min;
+ if (GenerateCode(trip, trip, graph, block, &opa, in_body, is_min_a) &&
+ GenerateCode(info->op_b, trip, graph, block, &opb, in_body, is_min)) {
+ if (graph != nullptr) {
+ HInstruction* oper;
+ if (stride_value == 1) {
+ oper = new (graph->GetArena()) HAdd(type, opa, opb);
+ } else if (stride_value == -1) {
+ oper = new (graph->GetArena()) HSub(type, opb, opa);
+ } else {
+ HInstruction* mul =
+ new (graph->GetArena()) HMul(type, graph->GetConstant(type, stride_value), opa);
+ oper = new (graph->GetArena()) HAdd(type, Insert(block, mul), opb);
+ }
+ *result = Insert(block, oper);
}
- *result = Insert(block, oper);
+ return true;
}
- return true;
}
}
break;
Value extreme = GetVal(info, trip, in_body, is_min);
if (IsConstantValue(extreme)) {
if (graph != nullptr) {
- *result = graph->GetIntConstant(extreme.b_constant);
+ *result = graph->GetConstant(type, extreme.b_constant);
}
return true;
}
}
}
+ /// CHECK-START: void Main.shortBound1(int[], short) BCE (before)
+ /// CHECK-DAG: BoundsCheck loop:{{B\d+}}
+ //
+ /// CHECK-START: void Main.shortBound1(int[], short) BCE (after)
+ /// CHECK-DAG: Deoptimize loop:none
+ /// CHECK-DAG: Deoptimize loop:none
+ /// CHECK-DAG: Deoptimize loop:none
+ /// CHECK-NOT: Deoptimize
+ //
+ /// CHECK-START: void Main.shortBound1(int[], short) BCE (after)
+ /// CHECK-NOT: BoundsCheck
+ public static void shortBound1(int[] array, short s) {
+ // Lower precision bound will appear in deopt arithmetic
+ // and follows normal implicit widening conversion.
+ for (int i = 0; i < s; i++) {
+ array[i] = 222;
+ }
+ }
+
+ /// CHECK-START: void Main.shortBound2(int[], short) BCE (before)
+ /// CHECK-DAG: BoundsCheck loop:{{B\d+}}
+ //
+ /// CHECK-START: void Main.shortBound2(int[], short) BCE (after)
+ /// CHECK-DAG: Deoptimize loop:none
+ /// CHECK-DAG: Deoptimize loop:none
+ /// CHECK-DAG: Deoptimize loop:none
+ /// CHECK-NOT: Deoptimize
+ //
+ /// CHECK-START: void Main.shortBound2(int[], short) BCE (after)
+ /// CHECK-NOT: BoundsCheck
+ public static void shortBound2(int[] array, short s) {
+ // Lower precision bound will appear in deopt arithmetic
+ // and follows normal implicit widening conversion.
+ for (int i = 0; s > i; i++) {
+ array[i] = 444;
+ }
+ }
+
+ /// CHECK-START: void Main.narrowingFromLong(int[], int) BCE (before)
+ /// CHECK-DAG: BoundsCheck loop:{{B\d+}}
+ //
+ /// CHECK-START: void Main.narrowingFromLong(int[], int) BCE (after)
+ /// CHECK-DAG: BoundsCheck loop:{{B\d+}}
+ public static void narrowingFromLong(int[] array, int n) {
+ // Parallel induction in long precision that is narrowed provides type
+ // conversion challenges for BCE in deopt arithmetic when combined
+ // with the int loop induction. Therefore, currently skipped.
+ long l = 0;
+ for (int i = 0; i < n; i++, l++) {
+ array[(int)l] = 888;
+ }
+ }
+
//
// Verifier.
//
} catch (ArrayIndexOutOfBoundsException e) {
}
+ shortBound1(a, (short)a.length);
+ for (int i = 0; i < a.length; i++) {
+ expectEquals(222, a[i]);
+ }
+ shortBound2(a, (short)a.length);
+ for (int i = 0; i < a.length; i++) {
+ expectEquals(444, a[i]);
+ }
+
+ try {
+ shortBound1(a, (short)(a.length + 1));
+ throw new Error("Should throw AIOOBE");
+ } catch (ArrayIndexOutOfBoundsException e) {
+ }
+ for (int i = 0; i < a.length; i++) {
+ expectEquals(222, a[i]);
+ }
+
+ try {
+ shortBound2(a, (short)(a.length + 1));
+ throw new Error("Should throw AIOOBE");
+ } catch (ArrayIndexOutOfBoundsException e) {
+ }
+ for (int i = 0; i < a.length; i++) {
+ expectEquals(444, a[i]);
+ }
+
+ narrowingFromLong(a, a.length);
+ for (int i = 0; i < a.length; i++) {
+ expectEquals(888, a[i]);
+ }
+
System.out.println("passed");
}
return i;
}
+ // TODO: handle as closed/empty eventually?
+ static int mainIndexShort1(short s) {
+ int i = 0;
+ for (i = 0; i < s; i++) { }
+ return i;
+ }
+
+ // TODO: handle as closed/empty eventually?
+ static int mainIndexShort2(short s) {
+ int i = 0;
+ for (i = 0; s > i; i++) { }
+ return i;
+ }
+
/// CHECK-START: int Main.periodicReturnedN(int) loop_optimization (before)
/// CHECK-DAG: <<Phi1:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none
/// CHECK-DAG: <<Phi2:i\d+>> Phi loop:<<Loop>> outer_loop:none
return x;
}
+ /// CHECK-START: float Main.periodicFloat10() loop_optimization (before)
+ /// CHECK-DAG: <<Phi1:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none
+ /// CHECK-DAG: <<Phi2:f\d+>> Phi loop:<<Loop>> outer_loop:none
+ /// CHECK-DAG: <<Phi3:f\d+>> Phi loop:<<Loop>> outer_loop:none
+ /// CHECK-DAG: <<Phi4:f\d+>> Phi loop:<<Loop>> outer_loop:none
+ /// CHECK-DAG: Return [<<Phi2>>] loop:none
+ //
+ /// CHECK-START: float Main.periodicFloat10() loop_optimization (after)
+ /// CHECK-NOT: Phi
+ //
+ /// CHECK-START: float Main.periodicFloat10() loop_optimization (after)
+ /// CHECK-DAG: <<Float:f\d+>> FloatConstant 2 loop:none
+ /// CHECK-DAG: Return [<<Float>>] loop:none
+ private static float periodicFloat10() {
+ float r = 4.5f;
+ float s = 2.0f;
+ float t = -1.0f;
+ for (int i = 0; i < 10; i++) {
+ float tmp = t; t = r; r = s; s = tmp;
+ }
+ return r;
+ }
+
+ /// CHECK-START: float Main.periodicFloat11() loop_optimization (before)
+ /// CHECK-DAG: <<Phi1:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none
+ /// CHECK-DAG: <<Phi2:f\d+>> Phi loop:<<Loop>> outer_loop:none
+ /// CHECK-DAG: <<Phi3:f\d+>> Phi loop:<<Loop>> outer_loop:none
+ /// CHECK-DAG: <<Phi4:f\d+>> Phi loop:<<Loop>> outer_loop:none
+ /// CHECK-DAG: Return [<<Phi2>>] loop:none
+ //
+ /// CHECK-START: float Main.periodicFloat11() loop_optimization (after)
+ /// CHECK-NOT: Phi
+ //
+ /// CHECK-START: float Main.periodicFloat11() loop_optimization (after)
+ /// CHECK-DAG: <<Float:f\d+>> FloatConstant -1 loop:none
+ /// CHECK-DAG: Return [<<Float>>] loop:none
+ private static float periodicFloat11() {
+ float r = 4.5f;
+ float s = 2.0f;
+ float t = -1.0f;
+ for (int i = 0; i < 11; i++) {
+ float tmp = t; t = r; r = s; s = tmp;
+ }
+ return r;
+ }
+
+ /// CHECK-START: float Main.periodicFloat12() loop_optimization (before)
+ /// CHECK-DAG: <<Phi1:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none
+ /// CHECK-DAG: <<Phi2:f\d+>> Phi loop:<<Loop>> outer_loop:none
+ /// CHECK-DAG: <<Phi3:f\d+>> Phi loop:<<Loop>> outer_loop:none
+ /// CHECK-DAG: <<Phi4:f\d+>> Phi loop:<<Loop>> outer_loop:none
+ /// CHECK-DAG: Return [<<Phi2>>] loop:none
+ //
+ /// CHECK-START: float Main.periodicFloat12() loop_optimization (after)
+ /// CHECK-NOT: Phi
+ //
+ /// CHECK-START: float Main.periodicFloat12() loop_optimization (after)
+ /// CHECK-DAG: <<Float:f\d+>> FloatConstant 4.5 loop:none
+ /// CHECK-DAG: Return [<<Float>>] loop:none
+ private static float periodicFloat12() {
+ float r = 4.5f;
+ float s = 2.0f;
+ float t = -1.0f;
+ for (int i = 0; i < 12; i++) {
+ float tmp = t; t = r; r = s; s = tmp;
+ }
+ return r;
+ }
+
private static int exceptionExitBeforeAdd() {
int k = 0;
try {
for (int n = -4; n < 4; n++) {
int tc = (n <= 0) ? 0 : n;
expectEquals(tc, mainIndexReturnedN(n));
+ expectEquals(tc, mainIndexShort1((short) n));
+ expectEquals(tc, mainIndexShort2((short) n));
expectEquals(tc & 1, periodicReturnedN(n));
expectEquals((tc * (tc + 1)) / 2, getSumN(n));
}
expectEquals(!even, periodicBoolIdiom3N(false, n));
}
+ expectEquals( 2.0f, periodicFloat10());
+ expectEquals(-1.0f, periodicFloat11());
+ expectEquals( 4.5f, periodicFloat12());
+
expectEquals(100, exceptionExitBeforeAdd());
expectEquals(100, exceptionExitAfterAdd());
a = null;
System.out.println("passed");
}
+ private static void expectEquals(float expected, float result) {
+ if (expected != result) {
+ throw new Error("Expected: " + expected + ", found: " + result);
+ }
+ }
+
private static void expectEquals(int expected, int result) {
if (expected != result) {
throw new Error("Expected: " + expected + ", found: " + result);