diff options
-rw-r--r-- | Android.mk | 2 | ||||
-rw-r--r-- | compiler/optimizing/bounds_check_elimination.cc | 337 | ||||
-rw-r--r-- | patchoat/patchoat.cc | 599 | ||||
-rw-r--r-- | patchoat/patchoat.h | 10 | ||||
-rw-r--r-- | runtime/art_method-inl.h | 37 | ||||
-rw-r--r-- | runtime/gc/space/image_space.cc | 9 | ||||
-rw-r--r-- | runtime/jit/jit.cc | 6 | ||||
-rw-r--r-- | runtime/openjdkjvm/Android.mk | 20 | ||||
-rw-r--r-- | runtime/openjdkjvm/MODULE_LICENSE_GPL_WITH_CLASSPATH_EXCEPTION | 0 | ||||
-rw-r--r-- | runtime/runtime.cc | 1 | ||||
-rw-r--r-- | runtime/verifier/method_verifier.cc | 13 | ||||
-rw-r--r-- | test/449-checker-bce/src/Main.java | 271 | ||||
-rw-r--r-- | test/800-smali/expected.txt | 1 | ||||
-rw-r--r-- | test/800-smali/smali/b_26965384.smali | 20 | ||||
-rw-r--r-- | test/800-smali/smali/b_26965384Super.smali | 10 | ||||
-rw-r--r-- | test/800-smali/src/Main.java | 2 | ||||
-rw-r--r-- | test/Android.run-test.mk | 2 |
17 files changed, 807 insertions, 533 deletions
diff --git a/Android.mk b/Android.mk index 4f73127123..2e05d33209 100644 --- a/Android.mk +++ b/Android.mk @@ -547,3 +547,5 @@ endif # !art_dont_bother art_dont_bother := art_test_bother := TEST_ART_TARGET_SYNC_DEPS := + +include $(art_path)/runtime/openjdkjvm/Android.mk diff --git a/compiler/optimizing/bounds_check_elimination.cc b/compiler/optimizing/bounds_check_elimination.cc index c307522f2b..c694ea8118 100644 --- a/compiler/optimizing/bounds_check_elimination.cc +++ b/compiler/optimizing/bounds_check_elimination.cc @@ -63,9 +63,10 @@ class ValueBound : public ValueObject { return true; } + // Return true if instruction can be expressed as "left_instruction + right_constant". static bool IsAddOrSubAConstant(HInstruction* instruction, - HInstruction** left_instruction, - int* right_constant) { + /* out */ HInstruction** left_instruction, + /* out */ int32_t* right_constant) { if (instruction->IsAdd() || instruction->IsSub()) { HBinaryOperation* bin_op = instruction->AsBinaryOperation(); HInstruction* left = bin_op->GetLeft(); @@ -82,9 +83,22 @@ class ValueBound : public ValueObject { return false; } + // Expresses any instruction as a value bound. + static ValueBound AsValueBound(HInstruction* instruction) { + if (instruction->IsIntConstant()) { + return ValueBound(nullptr, instruction->AsIntConstant()->GetValue()); + } + HInstruction *left; + int32_t right; + if (IsAddOrSubAConstant(instruction, &left, &right)) { + return ValueBound(left, right); + } + return ValueBound(instruction, 0); + } + // Try to detect useful value bound format from an instruction, e.g. // a constant or array length related value. - static ValueBound DetectValueBoundFromValue(HInstruction* instruction, bool* found) { + static ValueBound DetectValueBoundFromValue(HInstruction* instruction, /* out */ bool* found) { DCHECK(instruction != nullptr); if (instruction->IsIntConstant()) { *found = true; @@ -227,7 +241,7 @@ class ValueBound : public ValueObject { // Add a constant to a ValueBound. // `overflow` or `underflow` will return whether the resulting bound may // overflow or underflow an int. - ValueBound Add(int32_t c, bool* overflow, bool* underflow) const { + ValueBound Add(int32_t c, /* out */ bool* overflow, /* out */ bool* underflow) const { *overflow = *underflow = false; if (c == 0) { return *this; @@ -488,10 +502,10 @@ class BCEVisitor : public HGraphVisitor { // the deoptimization technique. static constexpr size_t kThresholdForAddingDeoptimize = 2; - // Very large constant index is considered as an anomaly. This is a threshold - // beyond which we don't bother to apply the deoptimization technique since - // it's likely some AIOOBE will be thrown. - static constexpr int32_t kMaxConstantForAddingDeoptimize = + // Very large lengths are considered an anomaly. This is a threshold beyond which we don't + // bother to apply the deoptimization technique since it's likely, or sometimes certain, + // an AIOOBE will be thrown. + static constexpr uint32_t kMaxLengthForAddingDeoptimize = std::numeric_limits<int32_t>::max() - 1024 * 1024; // Added blocks for loop body entry test. @@ -508,7 +522,7 @@ class BCEVisitor : public HGraphVisitor { std::less<int>(), graph->GetArena()->Adapter(kArenaAllocBoundsCheckElimination)), graph->GetArena()->Adapter(kArenaAllocBoundsCheckElimination)), - first_constant_index_bounds_check_map_( + first_index_bounds_check_map_( std::less<int>(), graph->GetArena()->Adapter(kArenaAllocBoundsCheckElimination)), early_exit_loop_( @@ -518,23 +532,16 @@ class BCEVisitor : public HGraphVisitor { std::less<uint32_t>(), graph->GetArena()->Adapter(kArenaAllocBoundsCheckElimination)), finite_loop_(graph->GetArena()->Adapter(kArenaAllocBoundsCheckElimination)), - need_to_revisit_block_(false), - has_deoptimization_on_constant_subscripts_(false), + has_dom_based_dynamic_bce_(false), initial_block_size_(graph->GetBlocks().size()), side_effects_(side_effects), induction_range_(induction_analysis) {} void VisitBasicBlock(HBasicBlock* block) OVERRIDE { DCHECK(!IsAddedBlock(block)); - first_constant_index_bounds_check_map_.clear(); + first_index_bounds_check_map_.clear(); HGraphVisitor::VisitBasicBlock(block); - if (need_to_revisit_block_) { - AddComparesWithDeoptimization(block); - need_to_revisit_block_ = false; - first_constant_index_bounds_check_map_.clear(); - GetValueRangeMap(block)->clear(); - HGraphVisitor::VisitBasicBlock(block); - } + AddComparesWithDeoptimization(block); } void Finish() { @@ -555,8 +562,7 @@ class BCEVisitor : public HGraphVisitor { // Added blocks don't keep value ranges. return nullptr; } - uint32_t block_id = basic_block->GetBlockId(); - return &maps_[block_id]; + return &maps_[basic_block->GetBlockId()]; } // Traverse up the dominator tree to look for value range info. @@ -576,6 +582,11 @@ class BCEVisitor : public HGraphVisitor { return nullptr; } + // Helper method to assign a new range to an instruction in given basic block. + void AssignRange(HBasicBlock* basic_block, HInstruction* instruction, ValueRange* range) { + GetValueRangeMap(basic_block)->Overwrite(instruction->GetId(), range); + } + // Narrow the value range of `instruction` at the end of `basic_block` with `range`, // and push the narrowed value range to `successor`. void ApplyRangeFromComparison(HInstruction* instruction, HBasicBlock* basic_block, @@ -583,7 +594,7 @@ class BCEVisitor : public HGraphVisitor { ValueRange* existing_range = LookupValueRange(instruction, basic_block); if (existing_range == nullptr) { if (range != nullptr) { - GetValueRangeMap(successor)->Overwrite(instruction->GetId(), range); + AssignRange(successor, instruction, range); } return; } @@ -595,8 +606,7 @@ class BCEVisitor : public HGraphVisitor { return; } } - ValueRange* narrowed_range = existing_range->Narrow(range); - GetValueRangeMap(successor)->Overwrite(instruction->GetId(), narrowed_range); + AssignRange(successor, instruction, existing_range->Narrow(range)); } // Special case that we may simultaneously narrow two MonotonicValueRange's to @@ -778,37 +788,37 @@ class BCEVisitor : public HGraphVisitor { array_length->IsPhi()); bool try_dynamic_bce = true; + // Analyze index range. if (!index->IsIntConstant()) { - // Non-constant subscript. + // Non-constant index. ValueBound lower = ValueBound(nullptr, 0); // constant 0 ValueBound upper = ValueBound(array_length, -1); // array_length - 1 ValueRange array_range(GetGraph()->GetArena(), lower, upper); - // Try range obtained by dominator-based analysis. + // Try index range obtained by dominator-based analysis. ValueRange* index_range = LookupValueRange(index, block); if (index_range != nullptr && index_range->FitsIn(&array_range)) { ReplaceInstruction(bounds_check, index); return; } - // Try range obtained by induction variable analysis. + // Try index range obtained by induction variable analysis. // Disables dynamic bce if OOB is certain. if (InductionRangeFitsIn(&array_range, bounds_check, index, &try_dynamic_bce)) { ReplaceInstruction(bounds_check, index); return; } } else { - // Constant subscript. + // Constant index. int32_t constant = index->AsIntConstant()->GetValue(); if (constant < 0) { // Will always throw exception. return; - } - if (array_length->IsIntConstant()) { + } else if (array_length->IsIntConstant()) { if (constant < array_length->AsIntConstant()->GetValue()) { ReplaceInstruction(bounds_check, index); } return; } - + // Analyze array length range. DCHECK(array_length->IsArrayLength()); ValueRange* existing_range = LookupValueRange(array_length, block); if (existing_range != nullptr) { @@ -823,37 +833,35 @@ class BCEVisitor : public HGraphVisitor { // bounds check. } } - - if (first_constant_index_bounds_check_map_.find(array_length->GetId()) == - first_constant_index_bounds_check_map_.end()) { - // Remember the first bounds check against array_length of a constant index. - // That bounds check instruction has an associated HEnvironment where we - // may add an HDeoptimize to eliminate bounds checks of constant indices - // against array_length. - first_constant_index_bounds_check_map_.Put(array_length->GetId(), bounds_check); - } else { - // We've seen it at least twice. It's beneficial to introduce a compare with - // deoptimization fallback to eliminate the bounds checks. - need_to_revisit_block_ = true; - } - // Once we have an array access like 'array[5] = 1', we record array.length >= 6. // We currently don't do it for non-constant index since a valid array[i] can't prove // a valid array[i-1] yet due to the lower bound side. if (constant == std::numeric_limits<int32_t>::max()) { // Max() as an index will definitely throw AIOOBE. return; + } else { + ValueBound lower = ValueBound(nullptr, constant + 1); + ValueBound upper = ValueBound::Max(); + ValueRange* range = new (GetGraph()->GetArena()) + ValueRange(GetGraph()->GetArena(), lower, upper); + AssignRange(block, array_length, range); } - ValueBound lower = ValueBound(nullptr, constant + 1); - ValueBound upper = ValueBound::Max(); - ValueRange* range = new (GetGraph()->GetArena()) - ValueRange(GetGraph()->GetArena(), lower, upper); - GetValueRangeMap(block)->Overwrite(array_length->GetId(), range); } // If static analysis fails, and OOB is not certain, try dynamic elimination. if (try_dynamic_bce) { - TryDynamicBCE(bounds_check); + // Try loop-based dynamic elimination. + if (TryDynamicBCE(bounds_check)) { + return; + } + // Prepare dominator-based dynamic elimination. + if (first_index_bounds_check_map_.find(array_length->GetId()) == + first_index_bounds_check_map_.end()) { + // Remember the first bounds check against each array_length. That bounds check + // instruction has an associated HEnvironment where we may add an HDeoptimize + // to eliminate subsequent bounds checks against the same array_length. + first_index_bounds_check_map_.Put(array_length->GetId(), bounds_check); + } } } @@ -914,7 +922,7 @@ class BCEVisitor : public HGraphVisitor { increment, bound); } - GetValueRangeMap(phi->GetBlock())->Overwrite(phi->GetId(), range); + AssignRange(phi->GetBlock(), phi, range); } } } @@ -942,7 +950,7 @@ class BCEVisitor : public HGraphVisitor { } ValueRange* range = left_range->Add(right->AsIntConstant()->GetValue()); if (range != nullptr) { - GetValueRangeMap(add->GetBlock())->Overwrite(add->GetId(), range); + AssignRange(add->GetBlock(), add, range); } } } @@ -957,7 +965,7 @@ class BCEVisitor : public HGraphVisitor { } ValueRange* range = left_range->Add(-right->AsIntConstant()->GetValue()); if (range != nullptr) { - GetValueRangeMap(sub->GetBlock())->Overwrite(sub->GetId(), range); + AssignRange(sub->GetBlock(), sub, range); return; } } @@ -997,7 +1005,7 @@ class BCEVisitor : public HGraphVisitor { GetGraph()->GetArena(), ValueBound(nullptr, right_const - upper.GetConstant()), ValueBound(array_length, right_const - lower.GetConstant())); - GetValueRangeMap(sub->GetBlock())->Overwrite(sub->GetId(), range); + AssignRange(sub->GetBlock(), sub, range); } } } @@ -1045,7 +1053,7 @@ class BCEVisitor : public HGraphVisitor { GetGraph()->GetArena(), ValueBound(nullptr, std::numeric_limits<int32_t>::min()), ValueBound(left, 0)); - GetValueRangeMap(instruction->GetBlock())->Overwrite(instruction->GetId(), range); + AssignRange(instruction->GetBlock(), instruction, range); } } @@ -1071,7 +1079,7 @@ class BCEVisitor : public HGraphVisitor { GetGraph()->GetArena(), ValueBound(nullptr, 0), ValueBound(nullptr, constant)); - GetValueRangeMap(instruction->GetBlock())->Overwrite(instruction->GetId(), range); + AssignRange(instruction->GetBlock(), instruction, range); } } } @@ -1095,30 +1103,11 @@ class BCEVisitor : public HGraphVisitor { if (existing_range != nullptr) { range = existing_range->Narrow(range); } - GetValueRangeMap(new_array->GetBlock())->Overwrite(left->GetId(), range); + AssignRange(new_array->GetBlock(), left, range); } } } - void VisitDeoptimize(HDeoptimize* deoptimize) OVERRIDE { - if (!deoptimize->InputAt(0)->IsLessThanOrEqual()) { - return; - } - // If this instruction was added by AddCompareWithDeoptimization(), narrow - // the range accordingly in subsequent basic blocks. - HLessThanOrEqual* less_than_or_equal = deoptimize->InputAt(0)->AsLessThanOrEqual(); - HInstruction* instruction = less_than_or_equal->InputAt(0); - if (instruction->IsArrayLength()) { - HInstruction* constant = less_than_or_equal->InputAt(1); - DCHECK(constant->IsIntConstant()); - DCHECK(constant->AsIntConstant()->GetValue() <= kMaxConstantForAddingDeoptimize); - ValueBound lower = ValueBound(nullptr, constant->AsIntConstant()->GetValue() + 1); - ValueRange* range = new (GetGraph()->GetArena()) - ValueRange(GetGraph()->GetArena(), lower, ValueBound::Max()); - GetValueRangeMap(deoptimize->GetBlock())->Overwrite(instruction->GetId(), range); - } - } - /** * After null/bounds checks are eliminated, some invariant array references * may be exposed underneath which can be hoisted out of the loop to the @@ -1130,13 +1119,12 @@ class BCEVisitor : public HGraphVisitor { * a[i][j] = 0; --a[i]--+ * } * - * Note: this optimization is no longer applied after deoptimization on array references - * with constant subscripts has occurred (see AddCompareWithDeoptimization()), since in - * those cases it would be unsafe to hoist array references across their deoptimization - * instruction inside a loop. + * Note: this optimization is no longer applied after dominator-based dynamic deoptimization + * has occurred (see AddCompareWithDeoptimization()), since in those cases it would be + * unsafe to hoist array references across their deoptimization instruction inside a loop. */ void VisitArrayGet(HArrayGet* array_get) OVERRIDE { - if (!has_deoptimization_on_constant_subscripts_ && array_get->IsInLoop()) { + if (!has_dom_based_dynamic_bce_ && array_get->IsInLoop()) { HLoopInformation* loop = array_get->GetBlock()->GetLoopInformation(); if (loop->IsDefinedOutOfTheLoop(array_get->InputAt(0)) && loop->IsDefinedOutOfTheLoop(array_get->InputAt(1))) { @@ -1148,69 +1136,107 @@ class BCEVisitor : public HGraphVisitor { } } - void AddCompareWithDeoptimization(HInstruction* array_length, - HIntConstant* const_instr, - HBasicBlock* block) { - DCHECK(array_length->IsArrayLength()); - ValueRange* range = LookupValueRange(array_length, block); - ValueBound lower_bound = range->GetLower(); - DCHECK(lower_bound.IsConstant()); - DCHECK(const_instr->GetValue() <= kMaxConstantForAddingDeoptimize); - // Note that the lower bound of the array length may have been refined - // through other instructions (such as `HNewArray(length - 4)`). - DCHECK_LE(const_instr->GetValue() + 1, lower_bound.GetConstant()); - - // If array_length is less than lower_const, deoptimize. - HBoundsCheck* bounds_check = first_constant_index_bounds_check_map_.Get( - array_length->GetId())->AsBoundsCheck(); - HCondition* cond = new (GetGraph()->GetArena()) HLessThanOrEqual(array_length, const_instr); - HDeoptimize* deoptimize = new (GetGraph()->GetArena()) - HDeoptimize(cond, bounds_check->GetDexPc()); - block->InsertInstructionBefore(cond, bounds_check); - block->InsertInstructionBefore(deoptimize, bounds_check); - deoptimize->CopyEnvironmentFrom(bounds_check->GetEnvironment()); - // Flag that this kind of deoptimization on array references with constant - // subscripts has occurred to prevent further hoisting of these references. - has_deoptimization_on_constant_subscripts_ = true; + // Perform dominator-based dynamic elimination on suitable set of bounds checks. + void AddCompareWithDeoptimization(HBasicBlock* block, + HInstruction* array_length, + HInstruction* base, + int32_t min_c, int32_t max_c) { + HBoundsCheck* bounds_check = + first_index_bounds_check_map_.Get(array_length->GetId())->AsBoundsCheck(); + // Construct deoptimization on single or double bounds on range [base-min_c,base+max_c], + // for example either for a[0]..a[3] just 3 or for a[base-1]..a[base+3] both base-1 + // and base+3, since we made the assumption any in between value may occur too. + static_assert(kMaxLengthForAddingDeoptimize < std::numeric_limits<int32_t>::max(), + "Incorrect max length may be subject to arithmetic wrap-around"); + HInstruction* upper = GetGraph()->GetIntConstant(max_c); + if (base == nullptr) { + DCHECK_GE(min_c, 0); + } else { + HInstruction* lower = new (GetGraph()->GetArena()) + HAdd(Primitive::kPrimInt, base, GetGraph()->GetIntConstant(min_c)); + upper = new (GetGraph()->GetArena()) HAdd(Primitive::kPrimInt, base, upper); + block->InsertInstructionBefore(lower, bounds_check); + block->InsertInstructionBefore(upper, bounds_check); + InsertDeoptInBlock(bounds_check, new (GetGraph()->GetArena()) HAbove(lower, upper)); + } + InsertDeoptInBlock(bounds_check, new (GetGraph()->GetArena()) HAboveOrEqual(upper, array_length)); + // Flag that this kind of deoptimization has occurred. + has_dom_based_dynamic_bce_ = true; } + // Attempt dominator-based dynamic elimination on remaining candidates. void AddComparesWithDeoptimization(HBasicBlock* block) { - for (ArenaSafeMap<int, HBoundsCheck*>::iterator it = - first_constant_index_bounds_check_map_.begin(); - it != first_constant_index_bounds_check_map_.end(); - ++it) { - HBoundsCheck* bounds_check = it->second; + for (const auto& it : first_index_bounds_check_map_) { + HBoundsCheck* bounds_check = it.second; + HInstruction* index = bounds_check->InputAt(0); HInstruction* array_length = bounds_check->InputAt(1); if (!array_length->IsArrayLength()) { - // Prior deoptimizations may have changed the array length to a phi. - // TODO(mingyao): propagate the range to the phi? - DCHECK(array_length->IsPhi()) << array_length->DebugName(); - continue; + continue; // disregard phis and constants } - HIntConstant* lower_bound_const_instr = nullptr; - int32_t lower_bound_const = std::numeric_limits<int32_t>::min(); - size_t counter = 0; - // Count the constant indexing for which bounds checks haven't - // been removed yet. - for (HUseIterator<HInstruction*> it2(array_length->GetUses()); - !it2.Done(); - it2.Advance()) { + // Collect all bounds checks are still there and that are related as "a[base + constant]" + // for a base instruction (possibly absent) and various constants. Note that no attempt + // is made to partition the set into matching subsets (viz. a[0], a[1] and a[base+1] and + // a[base+2] are considered as one set). + // TODO: would such a partitioning be worthwhile? + ValueBound value = ValueBound::AsValueBound(index); + HInstruction* base = value.GetInstruction(); + int32_t min_c = base == nullptr ? 0 : value.GetConstant(); + int32_t max_c = value.GetConstant(); + ArenaVector<HBoundsCheck*> candidates( + GetGraph()->GetArena()->Adapter(kArenaAllocBoundsCheckElimination)); + ArenaVector<HBoundsCheck*> standby( + GetGraph()->GetArena()->Adapter(kArenaAllocBoundsCheckElimination)); + for (HUseIterator<HInstruction*> it2(array_length->GetUses()); !it2.Done(); it2.Advance()) { + // Another bounds check in same or dominated block? HInstruction* user = it2.Current()->GetUser(); - if (user->GetBlock() == block && - user->IsBoundsCheck() && - user->AsBoundsCheck()->InputAt(0)->IsIntConstant()) { - DCHECK_EQ(array_length, user->AsBoundsCheck()->InputAt(1)); - HIntConstant* const_instr = user->AsBoundsCheck()->InputAt(0)->AsIntConstant(); - if (const_instr->GetValue() > lower_bound_const) { - lower_bound_const = const_instr->GetValue(); - lower_bound_const_instr = const_instr; + HBasicBlock* other_block = user->GetBlock(); + if (user->IsBoundsCheck() && block->Dominates(other_block)) { + HBoundsCheck* other_bounds_check = user->AsBoundsCheck(); + HInstruction* other_index = other_bounds_check->InputAt(0); + HInstruction* other_array_length = other_bounds_check->InputAt(1); + ValueBound other_value = ValueBound::AsValueBound(other_index); + if (array_length == other_array_length && base == other_value.GetInstruction()) { + int32_t other_c = other_value.GetConstant(); + // Since a subsequent dominated block could be under a conditional, only accept + // the other bounds check if it is in same block or both blocks dominate the exit. + // TODO: we could improve this by testing proper post-dominance, or even if this + // constant is seen along *all* conditional paths that follow. + HBasicBlock* exit = GetGraph()->GetExitBlock(); + if (block == user->GetBlock() || + (block->Dominates(exit) && other_block->Dominates(exit))) { + min_c = std::min(min_c, other_c); + max_c = std::max(max_c, other_c); + candidates.push_back(other_bounds_check); + } else { + // Add this candidate later only if it falls into the range. + standby.push_back(other_bounds_check); + } } - counter++; } } - if (counter >= kThresholdForAddingDeoptimize && - lower_bound_const_instr->GetValue() <= kMaxConstantForAddingDeoptimize) { - AddCompareWithDeoptimization(array_length, lower_bound_const_instr, block); + // Add standby candidates that fall in selected range. + for (size_t i = 0; i < standby.size(); ++i) { + HBoundsCheck* other_bounds_check = standby[i]->AsBoundsCheck(); + HInstruction* other_index = other_bounds_check->InputAt(0); + int32_t other_c = ValueBound::AsValueBound(other_index).GetConstant(); + if (min_c <= other_c && other_c <= max_c) { + candidates.push_back(other_bounds_check); + } + } + // Perform dominator-based deoptimization if it seems profitable. Note that we reject cases + // where the distance min_c:max_c range gets close to the maximum possible array length, + // since those cases are likely to always deopt (such situations do not necessarily go + // OOB, though, since the programmer could rely on wrap-around from max to min). + size_t threshold = kThresholdForAddingDeoptimize + (base == nullptr ? 0 : 1); // extra test? + uint32_t distance = static_cast<uint32_t>(max_c) - static_cast<uint32_t>(min_c); + if (candidates.size() >= threshold && + (base != nullptr || min_c >= 0) && // reject certain OOB + distance <= kMaxLengthForAddingDeoptimize) { // reject likely/certain deopt + AddCompareWithDeoptimization(block, array_length, base, min_c, max_c); + for (size_t i = 0; i < candidates.size(); ++i) { + HInstruction* other_bounds_check = candidates[i]; + ReplaceInstruction(other_bounds_check, other_bounds_check->InputAt(0)); + } } } } @@ -1259,7 +1285,7 @@ class BCEVisitor : public HGraphVisitor { * deoptimization). If no deoptimization occurs, the loop is executed with all corresponding * bounds checks and related null checks removed. */ - void TryDynamicBCE(HBoundsCheck* instruction) { + bool TryDynamicBCE(HBoundsCheck* instruction) { HLoopInformation* loop = instruction->GetBlock()->GetLoopInformation(); HInstruction* index = instruction->InputAt(0); HInstruction* length = instruction->InputAt(1); @@ -1285,11 +1311,13 @@ class BCEVisitor : public HGraphVisitor { HBasicBlock* block = GetPreHeader(loop, instruction); induction_range_.GenerateRangeCode(instruction, index, GetGraph(), block, &lower, &upper); if (lower != nullptr) { - InsertDeopt(loop, block, new (GetGraph()->GetArena()) HAbove(lower, upper)); + InsertDeoptInLoop(loop, block, new (GetGraph()->GetArena()) HAbove(lower, upper)); } - InsertDeopt(loop, block, new (GetGraph()->GetArena()) HAboveOrEqual(upper, length)); + InsertDeoptInLoop(loop, block, new (GetGraph()->GetArena()) HAboveOrEqual(upper, length)); ReplaceInstruction(instruction, index); + return true; } + return false; } /** @@ -1382,7 +1410,7 @@ class BCEVisitor : public HGraphVisitor { HBasicBlock* block = GetPreHeader(loop, check); HInstruction* cond = new (GetGraph()->GetArena()) HEqual(array, GetGraph()->GetNullConstant()); - InsertDeopt(loop, block, cond); + InsertDeoptInLoop(loop, block, cond); ReplaceInstruction(check, array); return true; } @@ -1448,8 +1476,8 @@ class BCEVisitor : public HGraphVisitor { return loop->GetPreHeader(); } - /** Inserts a deoptimization test. */ - void InsertDeopt(HLoopInformation* loop, HBasicBlock* block, HInstruction* condition) { + /** Inserts a deoptimization test in a loop preheader. */ + void InsertDeoptInLoop(HLoopInformation* loop, HBasicBlock* block, HInstruction* condition) { HInstruction* suspend = loop->GetSuspendCheck(); block->InsertInstructionBefore(condition, block->GetLastInstruction()); HDeoptimize* deoptimize = @@ -1461,6 +1489,16 @@ class BCEVisitor : public HGraphVisitor { } } + /** Inserts a deoptimization test right before a bounds check. */ + void InsertDeoptInBlock(HBoundsCheck* bounds_check, HInstruction* condition) { + HBasicBlock* block = bounds_check->GetBlock(); + block->InsertInstructionBefore(condition, bounds_check); + HDeoptimize* deoptimize = + new (GetGraph()->GetArena()) HDeoptimize(condition, bounds_check->GetDexPc()); + block->InsertInstructionBefore(deoptimize, bounds_check); + deoptimize->CopyEnvironmentFrom(bounds_check->GetEnvironment()); + } + /** Hoists instruction out of the loop to preheader or deoptimization block. */ void HoistToPreHeaderOrDeoptBlock(HLoopInformation* loop, HInstruction* instruction) { HBasicBlock* block = GetPreHeader(loop, instruction); @@ -1628,9 +1666,9 @@ class BCEVisitor : public HGraphVisitor { // A set of maps, one per basic block, from instruction to range. ArenaVector<ArenaSafeMap<int, ValueRange*>> maps_; - // Map an HArrayLength instruction's id to the first HBoundsCheck instruction in - // a block that checks a constant index against that HArrayLength. - ArenaSafeMap<int, HBoundsCheck*> first_constant_index_bounds_check_map_; + // Map an HArrayLength instruction's id to the first HBoundsCheck instruction + // in a block that checks an index against that HArrayLength. + ArenaSafeMap<int, HBoundsCheck*> first_index_bounds_check_map_; // Early-exit loop bookkeeping. ArenaSafeMap<uint32_t, bool> early_exit_loop_; @@ -1641,15 +1679,8 @@ class BCEVisitor : public HGraphVisitor { // Finite loop bookkeeping. ArenaSet<uint32_t> finite_loop_; - // For the block, there is at least one HArrayLength instruction for which there - // is more than one bounds check instruction with constant indexing. And it's - // beneficial to add a compare instruction that has deoptimization fallback and - // eliminate those bounds checks. - bool need_to_revisit_block_; - - // Flag that denotes whether deoptimization has occurred on array references - // with constant subscripts (see AddCompareWithDeoptimization()). - bool has_deoptimization_on_constant_subscripts_; + // Flag that denotes whether dominator-based dynamic elimination has occurred. + bool has_dom_based_dynamic_bce_; // Initial number of blocks. uint32_t initial_block_size_; diff --git a/patchoat/patchoat.cc b/patchoat/patchoat.cc index 1668dc5f25..1d80bda258 100644 --- a/patchoat/patchoat.cc +++ b/patchoat/patchoat.cc @@ -150,30 +150,17 @@ static bool FinishFile(File* file, bool close) { } } -bool PatchOat::Patch(File* input_oat, const std::string& image_location, off_t delta, - File* output_oat, File* output_image, InstructionSet isa, - TimingLogger* timings, - bool output_oat_opened_from_fd ATTRIBUTE_UNUSED, - bool new_oat_out) { +bool PatchOat::Patch(const std::string& image_location, + off_t delta, + const std::string& output_directory, + InstructionSet isa, + TimingLogger* timings) { CHECK(Runtime::Current() == nullptr); - CHECK(output_image != nullptr); - CHECK_GE(output_image->Fd(), 0); - CHECK(input_oat != nullptr); - CHECK(output_oat != nullptr); - CHECK_GE(input_oat->Fd(), 0); - CHECK_GE(output_oat->Fd(), 0); CHECK(!image_location.empty()) << "image file must have a filename."; TimingLogger::ScopedTiming t("Runtime Setup", timings); - if (isa == kNone) { - Elf32_Ehdr elf_hdr; - if (sizeof(elf_hdr) != input_oat->Read(reinterpret_cast<char*>(&elf_hdr), sizeof(elf_hdr), 0)) { - LOG(ERROR) << "unable to read elf header"; - return false; - } - isa = GetInstructionSetFromELF(elf_hdr.e_machine, elf_hdr.e_flags); - } + CHECK_NE(isa, kNone); const char* isa_name = GetInstructionSetString(isa); // Set up the runtime @@ -193,8 +180,6 @@ bool PatchOat::Patch(File* input_oat, const std::string& image_location, off_t d Thread::Current()->TransitionFromRunnableToSuspended(kNative); ScopedObjectAccess soa(Thread::Current()); - std::string output_directory = - output_image->GetPath().substr(0, output_image->GetPath().find_last_of("/")); t.NewTiming("Image and oat Patching setup"); std::vector<gc::space::ImageSpace*> spaces = Runtime::Current()->GetHeap()->GetBootImageSpaces(); std::map<gc::space::ImageSpace*, std::unique_ptr<File>> space_to_file_map; @@ -325,6 +310,7 @@ bool PatchOat::Patch(File* input_oat, const std::string& image_location, off_t d std::string output_image_filename = output_directory + (StartsWith(converted_image_filename, "/") ? "" : "/") + converted_image_filename; + bool new_oat_out; std::unique_ptr<File> output_image_file(CreateOrOpen(output_image_filename.c_str(), &new_oat_out)); if (output_image_file.get() == nullptr) { @@ -932,21 +918,9 @@ NO_RETURN static void Usage(const char *fmt, ...) { UsageError(" --output-image-file=<file.art>: Specifies the exact file to write the patched"); UsageError(" image file to."); UsageError(""); - UsageError(" --output-image-fd=<file-descriptor>: Specifies the file-descriptor to write the"); - UsageError(" the patched image file to."); - UsageError(""); - UsageError(" --orig-base-offset=<original-base-offset>: Specify the base offset the input file"); - UsageError(" was compiled with. This is needed if one is specifying a --base-offset"); - UsageError(""); - UsageError(" --base-offset=<new-base-offset>: Specify the base offset we will repatch the"); - UsageError(" given files to use. This requires that --orig-base-offset is also given."); - UsageError(""); UsageError(" --base-offset-delta=<delta>: Specify the amount to change the old base-offset by."); UsageError(" This value may be negative."); UsageError(""); - UsageError(" --patched-image-file=<file.art>: Relocate the oat file to be the same as the"); - UsageError(" given image file."); - UsageError(""); UsageError(" --patched-image-location=<file.art>: Relocate the oat file to be the same as the"); UsageError(" image at the given location. If used one must also specify the"); UsageError(" --instruction-set flag. It will search for this image in the same way that"); @@ -992,207 +966,75 @@ static bool ReadBaseDelta(const char* name, off_t* delta, std::string* error_msg return true; } -static int patchoat(int argc, char **argv) { - InitLogging(argv); - MemMap::Init(); - const bool debug = kIsDebugBuild; - orig_argc = argc; - orig_argv = argv; - TimingLogger timings("patcher", false, false); +static int patchoat_image(TimingLogger& timings, + InstructionSet isa, + const std::string& input_image_location, + const std::string& output_image_filename, + off_t base_delta, + bool base_delta_set, + bool debug) { + CHECK(!input_image_location.empty()); + if (output_image_filename.empty()) { + Usage("Image patching requires --output-image-file"); + } - InitLogging(argv); + if (!base_delta_set) { + Usage("Must supply a desired new offset or delta."); + } - // Skip over the command name. - argv++; - argc--; + if (!IsAligned<kPageSize>(base_delta)) { + Usage("Base offset/delta must be aligned to a pagesize (0x%08x) boundary.", kPageSize); + } - if (argc == 0) { - Usage("No arguments specified"); + if (debug) { + LOG(INFO) << "moving offset by " << base_delta + << " (0x" << std::hex << base_delta << ") bytes or " + << std::dec << (base_delta/kPageSize) << " pages."; } - timings.StartTiming("Patchoat"); + TimingLogger::ScopedTiming pt("patch image and oat", &timings); - // cmd line args - bool isa_set = false; - InstructionSet isa = kNone; - std::string input_oat_filename; - std::string input_oat_location; - int input_oat_fd = -1; - bool have_input_oat = false; - std::string input_image_location; - std::string output_oat_filename; - int output_oat_fd = -1; - bool have_output_oat = false; - std::string output_image_filename; - int output_image_fd = -1; - bool have_output_image = false; - uintptr_t base_offset = 0; - bool base_offset_set = false; - uintptr_t orig_base_offset = 0; - bool orig_base_offset_set = false; - off_t base_delta = 0; - bool base_delta_set = false; - bool match_delta = false; - std::string patched_image_filename; - std::string patched_image_location; - bool dump_timings = kIsDebugBuild; - bool lock_output = true; + std::string output_directory = + output_image_filename.substr(0, output_image_filename.find_last_of("/")); + bool ret = PatchOat::Patch(input_image_location, base_delta, output_directory, isa, &timings); - for (int i = 0; i < argc; ++i) { - const StringPiece option(argv[i]); - const bool log_options = false; - if (log_options) { - LOG(INFO) << "patchoat: option[" << i << "]=" << argv[i]; - } - if (option.starts_with("--instruction-set=")) { - isa_set = true; - const char* isa_str = option.substr(strlen("--instruction-set=")).data(); - isa = GetInstructionSetFromString(isa_str); - if (isa == kNone) { - Usage("Unknown or invalid instruction set %s", isa_str); - } - } else if (option.starts_with("--input-oat-location=")) { - if (have_input_oat) { - Usage("Only one of --input-oat-file, --input-oat-location and --input-oat-fd may be used."); - } - have_input_oat = true; - input_oat_location = option.substr(strlen("--input-oat-location=")).data(); - } else if (option.starts_with("--input-oat-file=")) { - if (have_input_oat) { - Usage("Only one of --input-oat-file, --input-oat-location and --input-oat-fd may be used."); - } - have_input_oat = true; - input_oat_filename = option.substr(strlen("--input-oat-file=")).data(); - } else if (option.starts_with("--input-oat-fd=")) { - if (have_input_oat) { - Usage("Only one of --input-oat-file, --input-oat-location and --input-oat-fd may be used."); - } - have_input_oat = true; - const char* oat_fd_str = option.substr(strlen("--input-oat-fd=")).data(); - if (!ParseInt(oat_fd_str, &input_oat_fd)) { - Usage("Failed to parse --input-oat-fd argument '%s' as an integer", oat_fd_str); - } - if (input_oat_fd < 0) { - Usage("--input-oat-fd pass a negative value %d", input_oat_fd); - } - } else if (option.starts_with("--input-image-location=")) { - input_image_location = option.substr(strlen("--input-image-location=")).data(); - } else if (option.starts_with("--output-oat-file=")) { - if (have_output_oat) { - Usage("Only one of --output-oat-file, and --output-oat-fd may be used."); - } - have_output_oat = true; - output_oat_filename = option.substr(strlen("--output-oat-file=")).data(); - } else if (option.starts_with("--output-oat-fd=")) { - if (have_output_oat) { - Usage("Only one of --output-oat-file, --output-oat-fd may be used."); - } - have_output_oat = true; - const char* oat_fd_str = option.substr(strlen("--output-oat-fd=")).data(); - if (!ParseInt(oat_fd_str, &output_oat_fd)) { - Usage("Failed to parse --output-oat-fd argument '%s' as an integer", oat_fd_str); - } - if (output_oat_fd < 0) { - Usage("--output-oat-fd pass a negative value %d", output_oat_fd); - } - } else if (option.starts_with("--output-image-file=")) { - if (have_output_image) { - Usage("Only one of --output-image-file, and --output-image-fd may be used."); - } - have_output_image = true; - output_image_filename = option.substr(strlen("--output-image-file=")).data(); - } else if (option.starts_with("--output-image-fd=")) { - if (have_output_image) { - Usage("Only one of --output-image-file, and --output-image-fd may be used."); - } - have_output_image = true; - const char* image_fd_str = option.substr(strlen("--output-image-fd=")).data(); - if (!ParseInt(image_fd_str, &output_image_fd)) { - Usage("Failed to parse --output-image-fd argument '%s' as an integer", image_fd_str); - } - if (output_image_fd < 0) { - Usage("--output-image-fd pass a negative value %d", output_image_fd); - } - } else if (option.starts_with("--orig-base-offset=")) { - const char* orig_base_offset_str = option.substr(strlen("--orig-base-offset=")).data(); - orig_base_offset_set = true; - if (!ParseUint(orig_base_offset_str, &orig_base_offset)) { - Usage("Failed to parse --orig-base-offset argument '%s' as an uintptr_t", - orig_base_offset_str); - } - } else if (option.starts_with("--base-offset=")) { - const char* base_offset_str = option.substr(strlen("--base-offset=")).data(); - base_offset_set = true; - if (!ParseUint(base_offset_str, &base_offset)) { - Usage("Failed to parse --base-offset argument '%s' as an uintptr_t", base_offset_str); - } - } else if (option.starts_with("--base-offset-delta=")) { - const char* base_delta_str = option.substr(strlen("--base-offset-delta=")).data(); - base_delta_set = true; - if (!ParseInt(base_delta_str, &base_delta)) { - Usage("Failed to parse --base-offset-delta argument '%s' as an off_t", base_delta_str); - } - } else if (option.starts_with("--patched-image-location=")) { - patched_image_location = option.substr(strlen("--patched-image-location=")).data(); - } else if (option.starts_with("--patched-image-file=")) { - patched_image_filename = option.substr(strlen("--patched-image-file=")).data(); - } else if (option == "--lock-output") { - lock_output = true; - } else if (option == "--no-lock-output") { - lock_output = false; - } else if (option == "--dump-timings") { - dump_timings = true; - } else if (option == "--no-dump-timings") { - dump_timings = false; - } else { - Usage("Unknown argument %s", option.data()); - } + if (kIsDebugBuild) { + LOG(INFO) << "Exiting with return ... " << ret; } + return ret ? EXIT_SUCCESS : EXIT_FAILURE; +} +static int patchoat_oat(TimingLogger& timings, + InstructionSet isa, + const std::string& patched_image_location, + off_t base_delta, + bool base_delta_set, + int input_oat_fd, + const std::string& input_oat_location, + std::string input_oat_filename, + bool have_input_oat, + int output_oat_fd, + std::string output_oat_filename, + bool have_output_oat, + bool lock_output, + bool debug) { { // Only 1 of these may be set. uint32_t cnt = 0; cnt += (base_delta_set) ? 1 : 0; - cnt += (base_offset_set && orig_base_offset_set) ? 1 : 0; - cnt += (!patched_image_filename.empty()) ? 1 : 0; cnt += (!patched_image_location.empty()) ? 1 : 0; if (cnt > 1) { - Usage("Only one of --base-offset/--orig-base-offset, --base-offset-delta, " - "--patched-image-filename or --patched-image-location may be used."); + Usage("Only one of --base-offset-delta or --patched-image-location may be used."); } else if (cnt == 0) { - Usage("Must specify --base-offset-delta, --base-offset and --orig-base-offset, " - "--patched-image-location or --patched-image-file"); + Usage("Must specify --base-offset-delta or --patched-image-location."); } } - if (have_input_oat != have_output_oat) { - Usage("Either both input and output oat must be supplied or niether must be."); - } - - if ((!input_image_location.empty()) != have_output_image) { - Usage("Either both input and output image must be supplied or niether must be."); - } - - // We know we have both the input and output so rename for clarity. - bool have_image_files = have_output_image; - bool have_oat_files = have_output_oat; - - if (!have_oat_files) { - if (have_image_files) { - Usage("Cannot patch an image file without an oat file"); - } else { - Usage("Must be patching either an oat file or an image file with an oat file."); - } - } - - if (!have_oat_files && !isa_set) { - Usage("Must include ISA if patching an image file without an oat file."); + if (!have_input_oat || !have_output_oat) { + Usage("Both input and output oat must be supplied to patch an app odex."); } if (!input_oat_location.empty()) { - if (!isa_set) { - Usage("specifying a location requires specifying an instruction set"); - } if (!LocationToFilename(input_oat_location, isa, &input_oat_filename)) { Usage("Unable to find filename for input oat location %s", input_oat_location.c_str()); } @@ -1200,10 +1042,9 @@ static int patchoat(int argc, char **argv) { LOG(INFO) << "Using input-oat-file " << input_oat_filename; } } + + bool match_delta = false; if (!patched_image_location.empty()) { - if (!isa_set) { - Usage("specifying a location requires specifying an instruction set"); - } std::string system_filename; bool has_system = false; std::string cache_filename; @@ -1216,11 +1057,12 @@ static int patchoat(int argc, char **argv) { &is_global_cache)) { Usage("Unable to determine image file for location %s", patched_image_location.c_str()); } + std::string patched_image_filename; if (has_cache) { patched_image_filename = cache_filename; } else if (has_system) { LOG(WARNING) << "Only image file found was in /system for image location " - << patched_image_location; + << patched_image_location; patched_image_filename = system_filename; } else { Usage("Unable to determine image file for location %s", patched_image_location.c_str()); @@ -1228,28 +1070,12 @@ static int patchoat(int argc, char **argv) { if (debug) { LOG(INFO) << "Using patched-image-file " << patched_image_filename; } - } - if (!base_delta_set) { - if (orig_base_offset_set && base_offset_set) { - base_delta_set = true; - base_delta = base_offset - orig_base_offset; - } else if (!patched_image_filename.empty()) { - if (have_image_files) { - Usage("--patched-image-location should not be used when patching other images"); - } - base_delta_set = true; - match_delta = true; - std::string error_msg; - if (!ReadBaseDelta(patched_image_filename.c_str(), &base_delta, &error_msg)) { - Usage(error_msg.c_str(), patched_image_filename.c_str()); - } - } else { - if (base_offset_set) { - Usage("Unable to determine original base offset."); - } else { - Usage("Must supply a desired new offset or delta."); - } + base_delta_set = true; + match_delta = true; + std::string error_msg; + if (!ReadBaseDelta(patched_image_filename.c_str(), &base_delta, &error_msg)) { + Usage(error_msg.c_str(), patched_image_filename.c_str()); } } @@ -1258,88 +1084,59 @@ static int patchoat(int argc, char **argv) { } // Do we need to cleanup output files if we fail? - bool new_image_out = false; bool new_oat_out = false; std::unique_ptr<File> input_oat; std::unique_ptr<File> output_oat; - std::unique_ptr<File> output_image; - - if (have_image_files) { - CHECK(!input_image_location.empty()); - if (output_image_fd != -1) { - if (output_image_filename.empty()) { - output_image_filename = "output-image-file"; - } - output_image.reset(new File(output_image_fd, output_image_filename, true)); - } else { - CHECK(!output_image_filename.empty()); - output_image.reset(CreateOrOpen(output_image_filename.c_str(), &new_image_out)); + if (input_oat_fd != -1) { + if (input_oat_filename.empty()) { + input_oat_filename = "input-oat-file"; + } + input_oat.reset(new File(input_oat_fd, input_oat_filename, false)); + if (input_oat_fd == output_oat_fd) { + input_oat.get()->DisableAutoClose(); + } + if (input_oat == nullptr) { + // Unlikely, but ensure exhaustive logging in non-0 exit code case + LOG(ERROR) << "Failed to open input oat file by its FD" << input_oat_fd; } } else { - CHECK(output_image_filename.empty() && output_image_fd == -1 && input_image_location.empty()); + CHECK(!input_oat_filename.empty()); + input_oat.reset(OS::OpenFileForReading(input_oat_filename.c_str())); + if (input_oat == nullptr) { + int err = errno; + LOG(ERROR) << "Failed to open input oat file " << input_oat_filename + << ": " << strerror(err) << "(" << err << ")"; + } } - if (have_oat_files) { - if (input_oat_fd != -1) { - if (input_oat_filename.empty()) { - input_oat_filename = "input-oat-file"; - } - input_oat.reset(new File(input_oat_fd, input_oat_filename, false)); - if (input_oat_fd == output_oat_fd) { - input_oat.get()->DisableAutoClose(); - } - if (input_oat == nullptr) { - // Unlikely, but ensure exhaustive logging in non-0 exit code case - LOG(ERROR) << "Failed to open input oat file by its FD" << input_oat_fd; - } - } else { - CHECK(!input_oat_filename.empty()); - input_oat.reset(OS::OpenFileForReading(input_oat_filename.c_str())); - if (input_oat == nullptr) { - int err = errno; - LOG(ERROR) << "Failed to open input oat file " << input_oat_filename - << ": " << strerror(err) << "(" << err << ")"; - } + if (output_oat_fd != -1) { + if (output_oat_filename.empty()) { + output_oat_filename = "output-oat-file"; } - - if (output_oat_fd != -1) { - if (output_oat_filename.empty()) { - output_oat_filename = "output-oat-file"; - } - output_oat.reset(new File(output_oat_fd, output_oat_filename, true)); - if (output_oat == nullptr) { - // Unlikely, but ensure exhaustive logging in non-0 exit code case - LOG(ERROR) << "Failed to open output oat file by its FD" << output_oat_fd; - } - } else { - CHECK(!output_oat_filename.empty()); - output_oat.reset(CreateOrOpen(output_oat_filename.c_str(), &new_oat_out)); - if (output_oat == nullptr) { - int err = errno; - LOG(ERROR) << "Failed to open output oat file " << output_oat_filename - << ": " << strerror(err) << "(" << err << ")"; - } + output_oat.reset(new File(output_oat_fd, output_oat_filename, true)); + if (output_oat == nullptr) { + // Unlikely, but ensure exhaustive logging in non-0 exit code case + LOG(ERROR) << "Failed to open output oat file by its FD" << output_oat_fd; + } + } else { + CHECK(!output_oat_filename.empty()); + output_oat.reset(CreateOrOpen(output_oat_filename.c_str(), &new_oat_out)); + if (output_oat == nullptr) { + int err = errno; + LOG(ERROR) << "Failed to open output oat file " << output_oat_filename + << ": " << strerror(err) << "(" << err << ")"; } } // TODO: get rid of this. - auto cleanup = [&output_image_filename, &output_oat_filename, - &new_oat_out, &new_image_out, &timings, &dump_timings](bool success) { - timings.EndTiming(); + auto cleanup = [&output_oat_filename, &new_oat_out](bool success) { if (!success) { if (new_oat_out) { CHECK(!output_oat_filename.empty()); TEMP_FAILURE_RETRY(unlink(output_oat_filename.c_str())); } - if (new_image_out) { - CHECK(!output_image_filename.empty()); - TEMP_FAILURE_RETRY(unlink(output_image_filename.c_str())); - } - } - if (dump_timings) { - LOG(INFO) << Dumpable<TimingLogger>(timings); } if (kIsDebugBuild) { @@ -1347,18 +1144,13 @@ static int patchoat(int argc, char **argv) { } }; - if (have_oat_files && (input_oat.get() == nullptr || output_oat.get() == nullptr)) { + if (input_oat.get() == nullptr || output_oat.get() == nullptr) { LOG(ERROR) << "Failed to open input/output oat files"; cleanup(false); return EXIT_FAILURE; - } else if (have_image_files && output_image.get() == nullptr) { - LOG(ERROR) << "Failed to open output image file"; - cleanup(false); - return EXIT_FAILURE; } if (match_delta) { - CHECK(!have_image_files); // We will not do this with images. std::string error_msg; // Figure out what the current delta is so we can match it to the desired delta. std::unique_ptr<ElfFile> elf(ElfFile::Open(input_oat.get(), PROT_READ, MAP_PRIVATE, @@ -1385,48 +1177,189 @@ static int patchoat(int argc, char **argv) { if (debug) { LOG(INFO) << "moving offset by " << base_delta - << " (0x" << std::hex << base_delta << ") bytes or " - << std::dec << (base_delta/kPageSize) << " pages."; + << " (0x" << std::hex << base_delta << ") bytes or " + << std::dec << (base_delta/kPageSize) << " pages."; } - // TODO: is it going to be promatic to unlink a file that was flock-ed? ScopedFlock output_oat_lock; if (lock_output) { std::string error_msg; - if (have_oat_files && !output_oat_lock.Init(output_oat.get(), &error_msg)) { - LOG(ERROR) << "Unable to lock output oat " << output_image->GetPath() << ": " << error_msg; + if (!output_oat_lock.Init(output_oat.get(), &error_msg)) { + LOG(ERROR) << "Unable to lock output oat " << output_oat->GetPath() << ": " << error_msg; cleanup(false); return EXIT_FAILURE; } } - bool ret; - if (have_image_files && have_oat_files) { - TimingLogger::ScopedTiming pt("patch image and oat", &timings); - ret = PatchOat::Patch(input_oat.get(), input_image_location, base_delta, - output_oat.get(), output_image.get(), isa, &timings, - output_oat_fd >= 0, // was it opened from FD? - new_oat_out); - // The order here doesn't matter. If the first one is successfully saved and the second one - // erased, ImageSpace will still detect a problem and not use the files. - ret = FinishFile(output_image.get(), ret); - ret = FinishFile(output_oat.get(), ret); - } else if (have_oat_files) { - TimingLogger::ScopedTiming pt("patch oat", &timings); - ret = PatchOat::Patch(input_oat.get(), base_delta, output_oat.get(), &timings, - output_oat_fd >= 0, // was it opened from FD? - new_oat_out); - ret = FinishFile(output_oat.get(), ret); - } else { - CHECK(false); - ret = true; - } + TimingLogger::ScopedTiming pt("patch oat", &timings); + bool ret = PatchOat::Patch(input_oat.get(), base_delta, output_oat.get(), &timings, + output_oat_fd >= 0, // was it opened from FD? + new_oat_out); + ret = FinishFile(output_oat.get(), ret); if (kIsDebugBuild) { LOG(INFO) << "Exiting with return ... " << ret; } cleanup(ret); - return (ret) ? EXIT_SUCCESS : EXIT_FAILURE; + return ret ? EXIT_SUCCESS : EXIT_FAILURE; +} + +static int patchoat(int argc, char **argv) { + InitLogging(argv); + MemMap::Init(); + const bool debug = kIsDebugBuild; + orig_argc = argc; + orig_argv = argv; + TimingLogger timings("patcher", false, false); + + InitLogging(argv); + + // Skip over the command name. + argv++; + argc--; + + if (argc == 0) { + Usage("No arguments specified"); + } + + timings.StartTiming("Patchoat"); + + // cmd line args + bool isa_set = false; + InstructionSet isa = kNone; + std::string input_oat_filename; + std::string input_oat_location; + int input_oat_fd = -1; + bool have_input_oat = false; + std::string input_image_location; + std::string output_oat_filename; + int output_oat_fd = -1; + bool have_output_oat = false; + std::string output_image_filename; + off_t base_delta = 0; + bool base_delta_set = false; + std::string patched_image_filename; + std::string patched_image_location; + bool dump_timings = kIsDebugBuild; + bool lock_output = true; + + for (int i = 0; i < argc; ++i) { + const StringPiece option(argv[i]); + const bool log_options = false; + if (log_options) { + LOG(INFO) << "patchoat: option[" << i << "]=" << argv[i]; + } + if (option.starts_with("--instruction-set=")) { + isa_set = true; + const char* isa_str = option.substr(strlen("--instruction-set=")).data(); + isa = GetInstructionSetFromString(isa_str); + if (isa == kNone) { + Usage("Unknown or invalid instruction set %s", isa_str); + } + } else if (option.starts_with("--input-oat-location=")) { + if (have_input_oat) { + Usage("Only one of --input-oat-file, --input-oat-location and --input-oat-fd may be used."); + } + have_input_oat = true; + input_oat_location = option.substr(strlen("--input-oat-location=")).data(); + } else if (option.starts_with("--input-oat-file=")) { + if (have_input_oat) { + Usage("Only one of --input-oat-file, --input-oat-location and --input-oat-fd may be used."); + } + have_input_oat = true; + input_oat_filename = option.substr(strlen("--input-oat-file=")).data(); + } else if (option.starts_with("--input-oat-fd=")) { + if (have_input_oat) { + Usage("Only one of --input-oat-file, --input-oat-location and --input-oat-fd may be used."); + } + have_input_oat = true; + const char* oat_fd_str = option.substr(strlen("--input-oat-fd=")).data(); + if (!ParseInt(oat_fd_str, &input_oat_fd)) { + Usage("Failed to parse --input-oat-fd argument '%s' as an integer", oat_fd_str); + } + if (input_oat_fd < 0) { + Usage("--input-oat-fd pass a negative value %d", input_oat_fd); + } + } else if (option.starts_with("--input-image-location=")) { + input_image_location = option.substr(strlen("--input-image-location=")).data(); + } else if (option.starts_with("--output-oat-file=")) { + if (have_output_oat) { + Usage("Only one of --output-oat-file, and --output-oat-fd may be used."); + } + have_output_oat = true; + output_oat_filename = option.substr(strlen("--output-oat-file=")).data(); + } else if (option.starts_with("--output-oat-fd=")) { + if (have_output_oat) { + Usage("Only one of --output-oat-file, --output-oat-fd may be used."); + } + have_output_oat = true; + const char* oat_fd_str = option.substr(strlen("--output-oat-fd=")).data(); + if (!ParseInt(oat_fd_str, &output_oat_fd)) { + Usage("Failed to parse --output-oat-fd argument '%s' as an integer", oat_fd_str); + } + if (output_oat_fd < 0) { + Usage("--output-oat-fd pass a negative value %d", output_oat_fd); + } + } else if (option.starts_with("--output-image-file=")) { + output_image_filename = option.substr(strlen("--output-image-file=")).data(); + } else if (option.starts_with("--base-offset-delta=")) { + const char* base_delta_str = option.substr(strlen("--base-offset-delta=")).data(); + base_delta_set = true; + if (!ParseInt(base_delta_str, &base_delta)) { + Usage("Failed to parse --base-offset-delta argument '%s' as an off_t", base_delta_str); + } + } else if (option.starts_with("--patched-image-location=")) { + patched_image_location = option.substr(strlen("--patched-image-location=")).data(); + } else if (option == "--lock-output") { + lock_output = true; + } else if (option == "--no-lock-output") { + lock_output = false; + } else if (option == "--dump-timings") { + dump_timings = true; + } else if (option == "--no-dump-timings") { + dump_timings = false; + } else { + Usage("Unknown argument %s", option.data()); + } + } + + // The instruction set is mandatory. This simplifies things... + if (!isa_set) { + Usage("Instruction set must be set."); + } + + int ret; + if (!input_image_location.empty()) { + ret = patchoat_image(timings, + isa, + input_image_location, + output_image_filename, + base_delta, + base_delta_set, + debug); + } else { + ret = patchoat_oat(timings, + isa, + patched_image_location, + base_delta, + base_delta_set, + input_oat_fd, + input_oat_location, + input_oat_filename, + have_input_oat, + output_oat_fd, + output_oat_filename, + have_output_oat, + lock_output, + debug); + } + + timings.EndTiming(); + if (dump_timings) { + LOG(INFO) << Dumpable<TimingLogger>(timings); + } + + return ret; } } // namespace art diff --git a/patchoat/patchoat.h b/patchoat/patchoat.h index ceddc343be..a6a8feeb3c 100644 --- a/patchoat/patchoat.h +++ b/patchoat/patchoat.h @@ -53,11 +53,11 @@ class PatchOat { TimingLogger* timings); // Patch both the image and the oat file - static bool Patch(File* oat_in, const std::string& art_location, - off_t delta, File* oat_out, File* art_out, InstructionSet isa, - TimingLogger* timings, - bool output_oat_opened_from_fd, // Was this using --oatput-oat-fd ? - bool new_oat_out); // Output oat was a new file created by us? + static bool Patch(const std::string& art_location, + off_t delta, + const std::string& output_directory, + InstructionSet isa, + TimingLogger* timings); ~PatchOat() {} PatchOat(PatchOat&&) = default; diff --git a/runtime/art_method-inl.h b/runtime/art_method-inl.h index cc45c385b8..ebe89bbbd2 100644 --- a/runtime/art_method-inl.h +++ b/runtime/art_method-inl.h @@ -449,24 +449,25 @@ template<typename RootVisitorType> void ArtMethod::VisitRoots(RootVisitorType& visitor, size_t pointer_size) { ArtMethod* interface_method = nullptr; mirror::Class* klass = declaring_class_.Read(); - if (UNLIKELY(klass != nullptr && klass->IsProxyClass())) { - // For normal methods, dex cache shortcuts will be visited through the declaring class. - // However, for proxies we need to keep the interface method alive, so we visit its roots. - interface_method = mirror::DexCache::GetElementPtrSize( - GetDexCacheResolvedMethods(pointer_size), - GetDexMethodIndex(), - pointer_size); - DCHECK(interface_method != nullptr); - DCHECK_EQ(interface_method, - Runtime::Current()->GetClassLinker()->FindMethodForProxy(klass, this)); - interface_method->VisitRoots(visitor, pointer_size); - } - - visitor.VisitRootIfNonNull(declaring_class_.AddressWithoutBarrier()); - if (!IsNative()) { - ProfilingInfo* profiling_info = GetProfilingInfo(pointer_size); - if (profiling_info != nullptr) { - profiling_info->VisitRoots(visitor); + if (LIKELY(klass != nullptr)) { + if (UNLIKELY(klass->IsProxyClass())) { + // For normal methods, dex cache shortcuts will be visited through the declaring class. + // However, for proxies we need to keep the interface method alive, so we visit its roots. + interface_method = mirror::DexCache::GetElementPtrSize( + GetDexCacheResolvedMethods(pointer_size), + GetDexMethodIndex(), + pointer_size); + DCHECK(interface_method != nullptr); + DCHECK_EQ(interface_method, + Runtime::Current()->GetClassLinker()->FindMethodForProxy(klass, this)); + interface_method->VisitRoots(visitor, pointer_size); + } + visitor.VisitRoot(declaring_class_.AddressWithoutBarrier()); + if (!IsNative()) { + ProfilingInfo* profiling_info = GetProfilingInfo(pointer_size); + if (profiling_info != nullptr) { + profiling_info->VisitRoots(visitor); + } } } } diff --git a/runtime/gc/space/image_space.cc b/runtime/gc/space/image_space.cc index 92693395f1..0c06c386b5 100644 --- a/runtime/gc/space/image_space.cc +++ b/runtime/gc/space/image_space.cc @@ -305,12 +305,6 @@ static bool RelocateImage(const char* image_location, const char* dest_filename, std::string output_image_filename_arg("--output-image-file="); output_image_filename_arg += dest_filename; - std::string input_oat_location_arg("--input-oat-location="); - input_oat_location_arg += ImageHeader::GetOatLocationFromImageLocation(image_location); - - std::string output_oat_filename_arg("--output-oat-file="); - output_oat_filename_arg += ImageHeader::GetOatLocationFromImageLocation(dest_filename); - std::string instruction_set_arg("--instruction-set="); instruction_set_arg += GetInstructionSetString(isa); @@ -324,9 +318,6 @@ static bool RelocateImage(const char* image_location, const char* dest_filename, argv.push_back(input_image_location_arg); argv.push_back(output_image_filename_arg); - argv.push_back(input_oat_location_arg); - argv.push_back(output_oat_filename_arg); - argv.push_back(instruction_set_arg); argv.push_back(base_offset_arg); diff --git a/runtime/jit/jit.cc b/runtime/jit/jit.cc index fcfa457bf7..31c278e748 100644 --- a/runtime/jit/jit.cc +++ b/runtime/jit/jit.cc @@ -36,6 +36,8 @@ namespace art { namespace jit { +static constexpr bool kEnableOnStackReplacement = false; + JitOptions* JitOptions::CreateFromRuntimeArguments(const RuntimeArgumentMap& options) { auto* jit_options = new JitOptions; jit_options->use_jit_ = options.GetOrDefault(RuntimeArgumentMap::UseJIT); @@ -278,6 +280,10 @@ bool Jit::MaybeDoOnStackReplacement(Thread* thread, uint32_t dex_pc, int32_t dex_pc_offset, JValue* result) { + if (!kEnableOnStackReplacement) { + return false; + } + Jit* jit = Runtime::Current()->GetJit(); if (jit == nullptr) { return false; diff --git a/runtime/openjdkjvm/Android.mk b/runtime/openjdkjvm/Android.mk new file mode 100644 index 0000000000..9b7404ebf5 --- /dev/null +++ b/runtime/openjdkjvm/Android.mk @@ -0,0 +1,20 @@ +# +# Copyright (C) 2016 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) +LOCAL_MODULE := openjdkjvm-phony +include $(BUILD_PHONY_PACKAGE) diff --git a/runtime/openjdkjvm/MODULE_LICENSE_GPL_WITH_CLASSPATH_EXCEPTION b/runtime/openjdkjvm/MODULE_LICENSE_GPL_WITH_CLASSPATH_EXCEPTION new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/runtime/openjdkjvm/MODULE_LICENSE_GPL_WITH_CLASSPATH_EXCEPTION diff --git a/runtime/runtime.cc b/runtime/runtime.cc index 1b59c6fde0..da28da8f07 100644 --- a/runtime/runtime.cc +++ b/runtime/runtime.cc @@ -335,6 +335,7 @@ struct AbortState { os << "Runtime aborting...\n"; if (Runtime::Current() == nullptr) { os << "(Runtime does not yet exist!)\n"; + DumpNativeStack(os, GetTid(), nullptr, " native: ", nullptr); return; } Thread* self = Thread::Current(); diff --git a/runtime/verifier/method_verifier.cc b/runtime/verifier/method_verifier.cc index 8d5e6eaf0b..1d31408cf0 100644 --- a/runtime/verifier/method_verifier.cc +++ b/runtime/verifier/method_verifier.cc @@ -4526,6 +4526,19 @@ void MethodVerifier::VerifyISFieldAccess(const Instruction* inst, const RegType& if (UNLIKELY(have_pending_hard_failure_)) { return; } + if (should_adjust) { + if (field == nullptr) { + Fail(VERIFY_ERROR_BAD_CLASS_SOFT) << "Might be accessing a superclass instance field prior " + << "to the superclass being initialized in " + << PrettyMethod(dex_method_idx_, *dex_file_); + } else if (field->GetDeclaringClass() != GetDeclaringClass().GetClass()) { + Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "cannot access superclass instance field " + << PrettyField(field) << " of a not fully initialized " + << "object within the context of " + << PrettyMethod(dex_method_idx_, *dex_file_); + return; + } + } } const RegType* field_type = nullptr; if (field != nullptr) { diff --git a/test/449-checker-bce/src/Main.java b/test/449-checker-bce/src/Main.java index 8f9a32ab3a..31bb94cb8c 100644 --- a/test/449-checker-bce/src/Main.java +++ b/test/449-checker-bce/src/Main.java @@ -122,8 +122,9 @@ public class Main { /// CHECK: ArraySet static void constantIndexing1(int[] array) { - array[5] = 1; - array[4] = 1; + // Decreasing order: bc for 5 but not for 4. + array[5] = 11; + array[4] = 11; } @@ -136,17 +137,18 @@ public class Main { /// CHECK: ArraySet /// CHECK: BoundsCheck /// CHECK: ArraySet + /// CHECK: BoundsCheck + /// CHECK: ArraySet /// CHECK-START: void Main.$opt$noinline$constantIndexing2(int[]) BCE (after) - /// CHECK: LessThanOrEqual - /// CHECK: Deoptimize - /// CHECK-NOT: BoundsCheck + /// CHECK-NOT: Deoptimize + /// CHECK: BoundsCheck /// CHECK: ArraySet - /// CHECK-NOT: BoundsCheck + /// CHECK: BoundsCheck /// CHECK: ArraySet - /// CHECK-NOT: BoundsCheck + /// CHECK: BoundsCheck /// CHECK: ArraySet - /// CHECK-NOT: BoundsCheck + /// CHECK: BoundsCheck /// CHECK: ArraySet /// CHECK: BoundsCheck /// CHECK: ArraySet @@ -156,12 +158,39 @@ public class Main { array[2] = 1; array[3] = 1; array[4] = 1; - array[-1] = 1; + array[-1] = 1; // prevents the whole opt on [-1:4] if (array[1] == 1) { throw new Error(""); } } + /// CHECK-START: void Main.constantIndexing2b(int[]) BCE (before) + /// CHECK: BoundsCheck + /// CHECK: ArraySet + /// CHECK: BoundsCheck + /// CHECK: ArraySet + /// CHECK: BoundsCheck + /// CHECK: ArraySet + /// CHECK: BoundsCheck + /// CHECK: ArraySet + + /// CHECK-START: void Main.constantIndexing2b(int[]) BCE (after) + /// CHECK: Deoptimize + /// CHECK-NOT: BoundsCheck + /// CHECK: ArraySet + /// CHECK-NOT: BoundsCheck + /// CHECK: ArraySet + /// CHECK-NOT: BoundsCheck + /// CHECK: ArraySet + /// CHECK-NOT: BoundsCheck + /// CHECK: ArraySet + + static void constantIndexing2b(int[] array) { + array[0] = 7; + array[1] = 7; + array[2] = 7; + array[3] = 7; + } /// CHECK-START: int[] Main.constantIndexing3(int[], int[], boolean) BCE (before) /// CHECK: BoundsCheck @@ -182,11 +211,9 @@ public class Main { /// CHECK: ArraySet /// CHECK-START: int[] Main.constantIndexing3(int[], int[], boolean) BCE (after) - /// CHECK: LessThanOrEqual /// CHECK: Deoptimize /// CHECK-NOT: BoundsCheck /// CHECK: ArrayGet - /// CHECK: LessThanOrEqual /// CHECK: Deoptimize /// CHECK-NOT: BoundsCheck /// CHECK: ArraySet @@ -220,14 +247,14 @@ public class Main { /// CHECK: ArraySet /// CHECK-START: void Main.constantIndexing4(int[]) BCE (after) - /// CHECK-NOT: LessThanOrEqual + /// CHECK-NOT: Deoptimize /// CHECK: BoundsCheck /// CHECK: ArraySet // There is only one array access. It's not beneficial // to create a compare with deoptimization instruction. static void constantIndexing4(int[] array) { - array[0] = 1; + array[0] = -1; } @@ -260,10 +287,221 @@ public class Main { /// CHECK-START: void Main.constantIndexing6(int[]) BCE (after) /// CHECK: Deoptimize + /// CHECK-NOT: BoundsCheck + /// CHECK: ArraySet + /// CHECK-NOT: BoundsCheck + /// CHECK: ArraySet static void constantIndexing6(int[] array) { - array[3] = 1; - array[4] = 1; + array[3] = 111; + array[4] = 111; + } + + /// CHECK-START: void Main.constantIndexing7(int[], int) BCE (before) + /// CHECK: BoundsCheck + /// CHECK: ArraySet + /// CHECK: BoundsCheck + /// CHECK: ArraySet + /// CHECK: BoundsCheck + /// CHECK: ArraySet + /// CHECK: BoundsCheck + /// CHECK: ArraySet + + /// CHECK-START: void Main.constantIndexing7(int[], int) BCE (after) + /// CHECK: Deoptimize + /// CHECK: Deoptimize + /// CHECK-NOT: BoundsCheck + /// CHECK: ArraySet + /// CHECK-NOT: BoundsCheck + /// CHECK: ArraySet + /// CHECK-NOT: BoundsCheck + /// CHECK: ArraySet + /// CHECK-NOT: BoundsCheck + /// CHECK: ArraySet + + static void constantIndexing7(int[] array, int base) { + // With constant offsets to symbolic base. + array[base] = 10; + array[base + 1] = 20; + array[base + 2] = 30; + array[base + 3] = 40; + } + + /// CHECK-START: void Main.constantIndexing8(int[], int) BCE (before) + /// CHECK: BoundsCheck + /// CHECK: ArraySet + /// CHECK: BoundsCheck + /// CHECK: ArraySet + /// CHECK: BoundsCheck + /// CHECK: ArraySet + /// CHECK: BoundsCheck + /// CHECK: ArraySet + + /// CHECK-START: void Main.constantIndexing8(int[], int) BCE (after) + /// CHECK: Deoptimize + /// CHECK: Deoptimize + /// CHECK-NOT: BoundsCheck + /// CHECK: ArraySet + /// CHECK-NOT: BoundsCheck + /// CHECK: ArraySet + /// CHECK-NOT: BoundsCheck + /// CHECK: ArraySet + /// CHECK-NOT: BoundsCheck + /// CHECK: ArraySet + + static void constantIndexing8(int[] array, int base) { + // With constant offsets "both ways" to symbolic base. + array[base - 1] = 100; + array[base] = 200; + array[base + 1] = 300; + array[base + 2] = 400; + } + + /// CHECK-START: void Main.constantIndexing9(int[], int) BCE (before) + /// CHECK: BoundsCheck + /// CHECK: ArraySet + /// CHECK: BoundsCheck + /// CHECK: ArraySet + /// CHECK: BoundsCheck + /// CHECK: ArraySet + /// CHECK: BoundsCheck + /// CHECK: ArraySet + + /// CHECK-START: void Main.constantIndexing9(int[], int) BCE (after) + /// CHECK: Deoptimize + /// CHECK: Deoptimize + /// CHECK-NOT: BoundsCheck + /// CHECK: ArraySet + /// CHECK-NOT: BoundsCheck + /// CHECK: ArraySet + /// CHECK-NOT: BoundsCheck + /// CHECK: ArraySet + /// CHECK-NOT: BoundsCheck + /// CHECK: ArraySet + /// CHECK-NOT: BoundsCheck + + static void constantIndexing9(int[] array, int base) { + // Final range is base..base+3 so conditional + // references may be included in the end. + array[base] = 0; + if (base != 12345) + array[base + 2] = 2; + array[base + 3] = 3; + if (base != 67890) + array[base + 1] = 1; + } + + static void runAllConstantIndices() { + int[] a1 = { 0 }; + int[] a6 = { 0, 0, 0, 0, 0, 0 }; + + boolean caught = false; + try { + constantIndexing1(a1); + } catch (ArrayIndexOutOfBoundsException e) { + caught = true; + } + if (!caught) { + System.out.println("constant indices 1 failed!"); + } + + constantIndexing1(a6); + if (a6[4] != 11 || a6[5] != 11) { + System.out.println("constant indices 1 failed!"); + } + + caught = false; + try { + $opt$noinline$constantIndexing2(a6); + } catch (ArrayIndexOutOfBoundsException e) { + caught = true; + } + if (!caught || a6[0] != 0 || a6[1] != 1 || a6[2] != 1 || + a6[3] != 1 || a6[4] != 1 || a6[5] != 11) { + System.out.println("constant indices 2 failed!"); + } + + caught = false; + try { + constantIndexing2b(a1); + } catch (ArrayIndexOutOfBoundsException e) { + caught = true; + } + if (!caught || a1[0] != 7) { + System.out.println("constant indices 2b failed!"); + } + + constantIndexing2b(a6); + if (a6[0] != 7 || a6[1] != 7 || a6[2] != 7 || + a6[3] != 7 || a6[4] != 1 || a6[5] != 11) { + System.out.println("constant indices 2b failed!"); + } + + int[] b4 = new int[4]; + constantIndexing3(a6, b4, true); + if (b4[0] != 7 || b4[1] != 7 || b4[2] != 7 || b4[3] != 7) { + System.out.println("constant indices 3 failed!"); + } + + constantIndexing4(a1); + if (a1[0] != -1) { + System.out.println("constant indices 4 failed!"); + } + + caught = false; + try { + constantIndexing5(a6); + } catch (ArrayIndexOutOfBoundsException e) { + caught = true; + } + if (!caught) { + System.out.println("constant indices 5 failed!"); + } + + constantIndexing6(a6); + if (a6[0] != 7 || a6[1] != 7 || a6[2] != 7 || + a6[3] != 111 || a6[4] != 111 || a6[5] != 11) { + System.out.println("constant indices 6 failed!"); + } + + constantIndexing7(a6, 1); + if (a6[0] != 7 || a6[1] != 10 || a6[2] != 20 || + a6[3] != 30 || a6[4] != 40 || a6[5] != 11) { + System.out.println("constant indices 7 failed!"); + } + + caught = false; + try { + constantIndexing7(a6, 5); + } catch (ArrayIndexOutOfBoundsException e) { + caught = true; + } + if (!caught || a6[0] != 7 || a6[1] != 10 || a6[2] != 20 || + a6[3] != 30 || a6[4] != 40 || a6[5] != 10) { + System.out.println("constant indices 7 failed!"); + } + + constantIndexing8(a6, 1); + if (a6[0] != 100 || a6[1] != 200 || a6[2] != 300 || + a6[3] != 400 || a6[4] != 40 || a6[5] != 10) { + System.out.println("constant indices 8 failed!"); + } + + caught = false; + try { + constantIndexing8(a6, 0); + } catch (ArrayIndexOutOfBoundsException e) { + caught = true; + } + if (!caught || a6[0] != 100) { + System.out.println("constant indices 8 failed!"); + } + + constantIndexing9(a6, 0); + if (a6[0] != 0 || a6[1] != 1 || a6[2] != 2 || + a6[3] != 3 || a6[4] != 40 || a6[5] != 10) { + System.out.println("constant indices 9 failed!"); + } } // A helper into which the actual throwing function should be inlined. @@ -1102,6 +1340,9 @@ public class Main { static void testUnknownBounds() { boolean caught = false; + + runAllConstantIndices(); + Main main = new Main(); main.foo1(new int[10], 0, 10, false); if (main.sum != 10) { diff --git a/test/800-smali/expected.txt b/test/800-smali/expected.txt index edbb7b52a5..8808a50f75 100644 --- a/test/800-smali/expected.txt +++ b/test/800-smali/expected.txt @@ -59,4 +59,5 @@ b/26594149 (6) b/26594149 (7) b/26594149 (8) b/27148248 +b/26965384 Done! diff --git a/test/800-smali/smali/b_26965384.smali b/test/800-smali/smali/b_26965384.smali new file mode 100644 index 0000000000..47ed4188bb --- /dev/null +++ b/test/800-smali/smali/b_26965384.smali @@ -0,0 +1,20 @@ +.class public LB26965384; +.super LB26965384Super; + +.method public constructor <init>()V + .locals 1 + const v0, 0 + iput v0, p0, LB26965384;->a:I + invoke-direct {p0}, LB26965384Super;-><init>()V + return-void +.end method + + +# Just by loading this class we should fail. It doesn't really matter what's in +# this method. +.method public static run()V + .registers 4 + new-instance v0, LB26965384; + invoke-direct {v0}, LB26965384;-><init>()V + return-void +.end method diff --git a/test/800-smali/smali/b_26965384Super.smali b/test/800-smali/smali/b_26965384Super.smali new file mode 100644 index 0000000000..32faea790e --- /dev/null +++ b/test/800-smali/smali/b_26965384Super.smali @@ -0,0 +1,10 @@ +.class public LB26965384Super; +.super Ljava/lang/Object; + +.field public a:I + +.method public constructor <init>()V + .locals 0 + invoke-direct {p0}, Ljava/lang/Object;-><init>()V + return-void +.end method diff --git a/test/800-smali/src/Main.java b/test/800-smali/src/Main.java index 2ea3367ac3..4e6de46caa 100644 --- a/test/800-smali/src/Main.java +++ b/test/800-smali/src/Main.java @@ -162,6 +162,8 @@ public class Main { null)); testCases.add(new TestCase("b/27148248", "B27148248", "run", null, new VerifyError(), null)); + testCases.add(new TestCase("b/26965384", "B26965384", "run", null, new VerifyError(), + null)); } public void runTests() { diff --git a/test/Android.run-test.mk b/test/Android.run-test.mk index b3560b634b..7c71ce3c6a 100644 --- a/test/Android.run-test.mk +++ b/test/Android.run-test.mk @@ -446,7 +446,9 @@ TEST_ART_BROKEN_INTERPRETER_RUN_TESTS := # Known broken tests for the JIT. # CFI unwinding expects managed frames, and the test does not iterate enough to even compile. JIT # also uses Generic JNI instead of the JNI compiler. +# Disable 570 while investigating OSR issues. TEST_ART_BROKEN_JIT_RUN_TESTS := \ + 570-checker-osr \ 137-cfi ifneq (,$(filter jit,$(COMPILER_TYPES))) |