diff options
66 files changed, 1333 insertions, 353 deletions
diff --git a/cmdline/cmdline_parser_test.cc b/cmdline/cmdline_parser_test.cc index 288f7ac013..b740b41893 100644 --- a/cmdline/cmdline_parser_test.cc +++ b/cmdline/cmdline_parser_test.cc @@ -202,6 +202,7 @@ TEST_F(CmdlineParserTest, TestSimpleSuccesses) { EXPECT_SINGLE_PARSE_VALUE(false, "-XX:DisableHSpaceCompactForOOM", M::EnableHSpaceCompactForOOM); EXPECT_SINGLE_PARSE_VALUE(0.5, "-XX:HeapTargetUtilization=0.5", M::HeapTargetUtilization); EXPECT_SINGLE_PARSE_VALUE(5u, "-XX:ParallelGCThreads=5", M::ParallelGCThreads); + EXPECT_SINGLE_PARSE_EXISTS("-Xno-dex-file-fallback", M::NoDexFileFallback); } // TEST_F TEST_F(CmdlineParserTest, TestSimpleFailures) { diff --git a/compiler/optimizing/bounds_check_elimination.cc b/compiler/optimizing/bounds_check_elimination.cc index ae9974d7e9..811a3bdf0c 100644 --- a/compiler/optimizing/bounds_check_elimination.cc +++ b/compiler/optimizing/bounds_check_elimination.cc @@ -32,14 +32,7 @@ class ValueBound : public ValueObject { if (instruction != nullptr && instruction->IsIntConstant()) { // Normalize ValueBound with constant instruction. int32_t instr_const = instruction->AsIntConstant()->GetValue(); - if (constant >= 0 && (instr_const <= INT_MAX - constant)) { - // No overflow. - instruction_ = nullptr; - constant_ = instr_const + constant; - return; - } - if (constant < 0 && (instr_const >= INT_MIN - constant)) { - // No underflow. + if (!WouldAddOverflowOrUnderflow(instr_const, constant)) { instruction_ = nullptr; constant_ = instr_const + constant; return; @@ -49,6 +42,22 @@ class ValueBound : public ValueObject { constant_ = constant; } + // Return whether (left + right) overflows or underflows. + static bool WouldAddOverflowOrUnderflow(int32_t left, int32_t right) { + if (right == 0) { + return false; + } + if ((right > 0) && (left <= INT_MAX - right)) { + // No overflow. + return false; + } + if ((right < 0) && (left >= INT_MIN - right)) { + // No underflow. + return false; + } + return true; + } + static bool IsAddOrSubAConstant(HInstruction* instruction, HInstruction** left_instruction, int* right_constant) { @@ -463,10 +472,23 @@ class BCEVisitor : public HGraphVisitor { // 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, - HBasicBlock* successor, ValueRange* range) { + HBasicBlock* successor, ValueRange* range) { ValueRange* existing_range = LookupValueRange(instruction, basic_block); - ValueRange* narrowed_range = (existing_range == nullptr) ? - range : existing_range->Narrow(range); + if (existing_range == nullptr) { + if (range != nullptr) { + GetValueRangeMap(successor)->Overwrite(instruction->GetId(), range); + } + return; + } + if (existing_range->IsMonotonicValueRange()) { + DCHECK(instruction->IsLoopHeaderPhi()); + // Make sure the comparison is in the loop header so each increment is + // checked with a comparison. + if (instruction->GetBlock() != basic_block) { + return; + } + } + ValueRange* narrowed_range = existing_range->Narrow(range); if (narrowed_range != nullptr) { GetValueRangeMap(successor)->Overwrite(instruction->GetId(), narrowed_range); } @@ -705,6 +727,15 @@ class BCEVisitor : public HGraphVisitor { // Here we are interested in the typical triangular case of nested loops, // such as the inner loop 'for (int j=0; j<array.length-i; j++)' where i // is the index for outer loop. In this case, we know j is bounded by array.length-1. + + // Try to handle (array.length - i) or (array.length + c - i) format. + HInstruction* left_of_left; // left input of left. + int32_t right_const = 0; + if (ValueBound::IsAddOrSubAConstant(left, &left_of_left, &right_const)) { + left = left_of_left; + } + // The value of left input of the sub equals (left + right_const). + if (left->IsArrayLength()) { HInstruction* array_length = left->AsArrayLength(); ValueRange* right_range = LookupValueRange(right, sub->GetBlock()); @@ -715,19 +746,83 @@ class BCEVisitor : public HGraphVisitor { HInstruction* upper_inst = upper.GetInstruction(); // Make sure it's the same array. if (ValueBound::Equal(array_length, upper_inst)) { - // (array.length - v) where v is in [c1, array.length + c2] - // gets [-c2, array.length - c1] as its value range. - ValueRange* range = new (GetGraph()->GetArena()) ValueRange( - GetGraph()->GetArena(), - ValueBound(nullptr, - upper.GetConstant()), - ValueBound(array_length, - lower.GetConstant())); - GetValueRangeMap(sub->GetBlock())->Overwrite(sub->GetId(), range); + int32_t c0 = right_const; + int32_t c1 = lower.GetConstant(); + int32_t c2 = upper.GetConstant(); + // (array.length + c0 - v) where v is in [c1, array.length + c2] + // gets [c0 - c2, array.length + c0 - c1] as its value range. + if (!ValueBound::WouldAddOverflowOrUnderflow(c0, -c2) && + !ValueBound::WouldAddOverflowOrUnderflow(c0, -c1)) { + if ((c0 - c1) <= 0) { + // array.length + (c0 - c1) won't overflow/underflow. + ValueRange* range = new (GetGraph()->GetArena()) ValueRange( + GetGraph()->GetArena(), + ValueBound(nullptr, right_const - upper.GetConstant()), + ValueBound(array_length, right_const - lower.GetConstant())); + GetValueRangeMap(sub->GetBlock())->Overwrite(sub->GetId(), range); + } + } } } } } } + void FindAndHandlePartialArrayLength(HBinaryOperation* instruction) { + DCHECK(instruction->IsDiv() || instruction->IsShr() || instruction->IsUShr()); + HInstruction* right = instruction->GetRight(); + int32_t right_const; + if (right->IsIntConstant()) { + right_const = right->AsIntConstant()->GetValue(); + // Detect division by two or more. + if ((instruction->IsDiv() && right_const <= 1) || + (instruction->IsShr() && right_const < 1) || + (instruction->IsUShr() && right_const < 1)) { + return; + } + } else { + return; + } + + // Try to handle array.length/2 or (array.length-1)/2 format. + HInstruction* left = instruction->GetLeft(); + HInstruction* left_of_left; // left input of left. + int32_t c = 0; + if (ValueBound::IsAddOrSubAConstant(left, &left_of_left, &c)) { + left = left_of_left; + } + // The value of left input of instruction equals (left + c). + + // (array_length + 1) or smaller divided by two or more + // always generate a value in [INT_MIN, array_length]. + // This is true even if array_length is INT_MAX. + if (left->IsArrayLength() && c <= 1) { + if (instruction->IsUShr() && c < 0) { + // Make sure for unsigned shift, left side is not negative. + // e.g. if array_length is 2, ((array_length - 3) >>> 2) is way bigger + // than array_length. + return; + } + ValueRange* range = new (GetGraph()->GetArena()) ValueRange( + GetGraph()->GetArena(), + ValueBound(nullptr, INT_MIN), + ValueBound(left, 0)); + GetValueRangeMap(instruction->GetBlock())->Overwrite(instruction->GetId(), range); + } + } + + void VisitDiv(HDiv* div) { + FindAndHandlePartialArrayLength(div); + } + + void VisitShr(HShr* shr) { + FindAndHandlePartialArrayLength(shr); + } + + void VisitUShr(HUShr* ushr) { + FindAndHandlePartialArrayLength(ushr); + } + void VisitNewArray(HNewArray* new_array) { HInstruction* len = new_array->InputAt(0); if (!len->IsIntConstant()) { diff --git a/compiler/optimizing/bounds_check_elimination_test.cc b/compiler/optimizing/bounds_check_elimination_test.cc index 17cb8f35de..a298413d14 100644 --- a/compiler/optimizing/bounds_check_elimination_test.cc +++ b/compiler/optimizing/bounds_check_elimination_test.cc @@ -400,7 +400,6 @@ static HGraph* BuildSSAGraph1(ArenaAllocator* allocator, loop_body->AddSuccessor(loop_header); HPhi* phi = new (allocator) HPhi(allocator, 0, 0, Primitive::kPrimInt); - phi->AddInput(constant_initial); HInstruction* null_check = new (allocator) HNullCheck(parameter, 0); HInstruction* array_length = new (allocator) HArrayLength(null_check); HInstruction* cmp = nullptr; @@ -416,6 +415,7 @@ static HGraph* BuildSSAGraph1(ArenaAllocator* allocator, loop_header->AddInstruction(array_length); loop_header->AddInstruction(cmp); loop_header->AddInstruction(if_inst); + phi->AddInput(constant_initial); null_check = new (allocator) HNullCheck(parameter, 0); array_length = new (allocator) HArrayLength(null_check); @@ -544,7 +544,6 @@ static HGraph* BuildSSAGraph2(ArenaAllocator* allocator, loop_body->AddSuccessor(loop_header); HPhi* phi = new (allocator) HPhi(allocator, 0, 0, Primitive::kPrimInt); - phi->AddInput(array_length); HInstruction* cmp = nullptr; if (cond == kCondLE) { cmp = new (allocator) HLessThanOrEqual(phi, constant_initial); @@ -556,6 +555,7 @@ static HGraph* BuildSSAGraph2(ArenaAllocator* allocator, loop_header->AddPhi(phi); loop_header->AddInstruction(cmp); loop_header->AddInstruction(if_inst); + phi->AddInput(array_length); HInstruction* add = new (allocator) HAdd(Primitive::kPrimInt, phi, constant_minus_1); null_check = new (allocator) HNullCheck(parameter, 0); @@ -632,7 +632,7 @@ TEST(BoundsCheckEliminationTest, LoopArrayBoundsElimination2) { ASSERT_TRUE(IsRemoved(bounds_check)); } -// int[] array = new array[10]; +// int[] array = new int[10]; // for (int i=0; i<10; i+=increment) { array[i] = 10; } static HGraph* BuildSSAGraph3(ArenaAllocator* allocator, HInstruction** bounds_check, @@ -672,7 +672,6 @@ static HGraph* BuildSSAGraph3(ArenaAllocator* allocator, loop_body->AddSuccessor(loop_header); HPhi* phi = new (allocator) HPhi(allocator, 0, 0, Primitive::kPrimInt); - phi->AddInput(constant_initial); HInstruction* cmp = nullptr; if (cond == kCondGE) { cmp = new (allocator) HGreaterThanOrEqual(phi, constant_10); @@ -684,6 +683,7 @@ static HGraph* BuildSSAGraph3(ArenaAllocator* allocator, loop_header->AddPhi(phi); loop_header->AddInstruction(cmp); loop_header->AddInstruction(if_inst); + phi->AddInput(constant_initial); HNullCheck* null_check = new (allocator) HNullCheck(new_array, 0); HArrayLength* array_length = new (allocator) HArrayLength(null_check); @@ -708,7 +708,7 @@ TEST(BoundsCheckEliminationTest, LoopArrayBoundsElimination3) { ArenaPool pool; ArenaAllocator allocator(&pool); - // int[] array = new array[10]; + // int[] array = new int[10]; // for (int i=0; i<10; i++) { array[i] = 10; // Can eliminate. } HInstruction* bounds_check = nullptr; HGraph* graph = BuildSSAGraph3(&allocator, &bounds_check, 0, 1, kCondGE); @@ -718,7 +718,7 @@ TEST(BoundsCheckEliminationTest, LoopArrayBoundsElimination3) { bounds_check_elimination_after_gvn.Run(); ASSERT_TRUE(IsRemoved(bounds_check)); - // int[] array = new array[10]; + // int[] array = new int[10]; // for (int i=1; i<10; i++) { array[i] = 10; // Can eliminate. } graph = BuildSSAGraph3(&allocator, &bounds_check, 1, 1, kCondGE); graph->BuildDominatorTree(); @@ -727,7 +727,7 @@ TEST(BoundsCheckEliminationTest, LoopArrayBoundsElimination3) { bounds_check_elimination_with_initial_1.Run(); ASSERT_TRUE(IsRemoved(bounds_check)); - // int[] array = new array[10]; + // int[] array = new int[10]; // for (int i=0; i<=10; i++) { array[i] = 10; // Can't eliminate. } graph = BuildSSAGraph3(&allocator, &bounds_check, 0, 1, kCondGT); graph->BuildDominatorTree(); @@ -736,7 +736,7 @@ TEST(BoundsCheckEliminationTest, LoopArrayBoundsElimination3) { bounds_check_elimination_with_greater_than.Run(); ASSERT_FALSE(IsRemoved(bounds_check)); - // int[] array = new array[10]; + // int[] array = new int[10]; // for (int i=1; i<10; i+=8) { array[i] = 10; // Can eliminate. } graph = BuildSSAGraph3(&allocator, &bounds_check, 1, 8, kCondGE); graph->BuildDominatorTree(); @@ -785,7 +785,6 @@ static HGraph* BuildSSAGraph4(ArenaAllocator* allocator, loop_body->AddSuccessor(loop_header); HPhi* phi = new (allocator) HPhi(allocator, 0, 0, Primitive::kPrimInt); - phi->AddInput(constant_initial); HInstruction* null_check = new (allocator) HNullCheck(parameter, 0); HInstruction* array_length = new (allocator) HArrayLength(null_check); HInstruction* cmp = nullptr; @@ -800,6 +799,7 @@ static HGraph* BuildSSAGraph4(ArenaAllocator* allocator, loop_header->AddInstruction(array_length); loop_header->AddInstruction(cmp); loop_header->AddInstruction(if_inst); + phi->AddInput(constant_initial); null_check = new (allocator) HNullCheck(parameter, 0); array_length = new (allocator) HArrayLength(null_check); @@ -904,7 +904,6 @@ TEST(BoundsCheckEliminationTest, BubbleSortArrayBoundsElimination) { HBasicBlock* outer_header = new (&allocator) HBasicBlock(graph); graph->AddBlock(outer_header); HPhi* phi_i = new (&allocator) HPhi(&allocator, 0, 0, Primitive::kPrimInt); - phi_i->AddInput(constant_0); HNullCheck* null_check = new (&allocator) HNullCheck(parameter, 0); HArrayLength* array_length = new (&allocator) HArrayLength(null_check); HAdd* add = new (&allocator) HAdd(Primitive::kPrimInt, array_length, constant_minus_1); @@ -916,11 +915,11 @@ TEST(BoundsCheckEliminationTest, BubbleSortArrayBoundsElimination) { outer_header->AddInstruction(add); outer_header->AddInstruction(cmp); outer_header->AddInstruction(if_inst); + phi_i->AddInput(constant_0); HBasicBlock* inner_header = new (&allocator) HBasicBlock(graph); graph->AddBlock(inner_header); HPhi* phi_j = new (&allocator) HPhi(&allocator, 0, 0, Primitive::kPrimInt); - phi_j->AddInput(constant_0); null_check = new (&allocator) HNullCheck(parameter, 0); array_length = new (&allocator) HArrayLength(null_check); HSub* sub = new (&allocator) HSub(Primitive::kPrimInt, array_length, phi_i); @@ -934,6 +933,7 @@ TEST(BoundsCheckEliminationTest, BubbleSortArrayBoundsElimination) { inner_header->AddInstruction(add); inner_header->AddInstruction(cmp); inner_header->AddInstruction(if_inst); + phi_j->AddInput(constant_0); HBasicBlock* inner_body_compare = new (&allocator) HBasicBlock(graph); graph->AddBlock(inner_body_compare); diff --git a/compiler/optimizing/code_generator_x86.cc b/compiler/optimizing/code_generator_x86.cc index e151c6b683..1101569174 100644 --- a/compiler/optimizing/code_generator_x86.cc +++ b/compiler/optimizing/code_generator_x86.cc @@ -1809,7 +1809,7 @@ void LocationsBuilderX86::VisitAdd(HAdd* add) { case Primitive::kPrimFloat: case Primitive::kPrimDouble: { locations->SetInAt(0, Location::RequiresFpuRegister()); - locations->SetInAt(1, Location::Any()); + locations->SetInAt(1, Location::RequiresFpuRegister()); locations->SetOut(Location::SameAsFirstInput()); break; } @@ -1853,8 +1853,6 @@ void InstructionCodeGeneratorX86::VisitAdd(HAdd* add) { case Primitive::kPrimFloat: { if (second.IsFpuRegister()) { __ addss(first.AsFpuRegister<XmmRegister>(), second.AsFpuRegister<XmmRegister>()); - } else { - __ addss(first.AsFpuRegister<XmmRegister>(), Address(ESP, second.GetStackIndex())); } break; } @@ -1862,8 +1860,6 @@ void InstructionCodeGeneratorX86::VisitAdd(HAdd* add) { case Primitive::kPrimDouble: { if (second.IsFpuRegister()) { __ addsd(first.AsFpuRegister<XmmRegister>(), second.AsFpuRegister<XmmRegister>()); - } else { - __ addsd(first.AsFpuRegister<XmmRegister>(), Address(ESP, second.GetStackIndex())); } break; } diff --git a/compiler/optimizing/graph_checker.cc b/compiler/optimizing/graph_checker.cc index ef10428c0f..a7f1f74e27 100644 --- a/compiler/optimizing/graph_checker.cc +++ b/compiler/optimizing/graph_checker.cc @@ -160,6 +160,22 @@ void GraphChecker::VisitInstruction(HInstruction* instruction) { instruction->GetId())); } } + + // Ensure 'instruction' has pointers to its inputs' use entries. + for (size_t i = 0, e = instruction->InputCount(); i < e; ++i) { + HUserRecord<HInstruction*> input_record = instruction->InputRecordAt(i); + HInstruction* input = input_record.GetInstruction(); + HUseListNode<HInstruction*>* use_node = input_record.GetUseNode(); + if (use_node == nullptr || !input->GetUses().Contains(use_node)) { + AddError(StringPrintf("Instruction %s:%d has an invalid pointer to use entry " + "at input %u (%s:%d).", + instruction->DebugName(), + instruction->GetId(), + static_cast<unsigned>(i), + input->DebugName(), + input->GetId())); + } + } } void SSAChecker::VisitBasicBlock(HBasicBlock* block) { diff --git a/compiler/optimizing/gvn.cc b/compiler/optimizing/gvn.cc index 89bba2d9f6..cb448c883f 100644 --- a/compiler/optimizing/gvn.cc +++ b/compiler/optimizing/gvn.cc @@ -270,7 +270,7 @@ void GlobalValueNumberer::VisitBasicBlock(HBasicBlock* block) { set = new (allocator_) ValueSet(allocator_); } else { HBasicBlock* dominator = block->GetDominator(); - set = sets_.Get(dominator->GetBlockId())->Copy(); + set = sets_.Get(dominator->GetBlockId()); if (dominator->GetSuccessors().Size() != 1 || dominator->GetSuccessors().Get(0) != block) { // We have to copy if the dominator has other successors, or `block` is not a successor // of the dominator. diff --git a/compiler/optimizing/licm.cc b/compiler/optimizing/licm.cc index 10f24d8148..bf9b8e59c5 100644 --- a/compiler/optimizing/licm.cc +++ b/compiler/optimizing/licm.cc @@ -66,8 +66,7 @@ static void UpdateLoopPhisIn(HEnvironment* environment, HLoopInformation* info) for (size_t i = 0, e = environment->Size(); i < e; ++i) { HInstruction* input = environment->GetInstructionAt(i); if (input != nullptr && IsPhiOf(input, info->GetHeader())) { - HUseListNode<HEnvironment*>* env_use = environment->GetInstructionEnvUseAt(i); - input->RemoveEnvironmentUser(env_use); + environment->RemoveAsUserOfInput(i); HInstruction* incoming = input->InputAt(0); environment->SetRawEnvAt(i, incoming); incoming->AddEnvUseAt(environment, i); diff --git a/compiler/optimizing/nodes.cc b/compiler/optimizing/nodes.cc index 7a75d260fd..93787b8bfd 100644 --- a/compiler/optimizing/nodes.cc +++ b/compiler/optimizing/nodes.cc @@ -34,17 +34,14 @@ void HGraph::FindBackEdges(ArenaBitVector* visited) { static void RemoveAsUser(HInstruction* instruction) { for (size_t i = 0; i < instruction->InputCount(); i++) { - instruction->InputAt(i)->RemoveUser(instruction, i); + instruction->RemoveAsUserOfInput(i); } HEnvironment* environment = instruction->GetEnvironment(); if (environment != nullptr) { for (size_t i = 0, e = environment->Size(); i < e; ++i) { - HUseListNode<HEnvironment*>* vreg_env_use = environment->GetInstructionEnvUseAt(i); - if (vreg_env_use != nullptr) { - HInstruction* vreg = environment->GetInstructionAt(i); - DCHECK(vreg != nullptr); - vreg->RemoveEnvironmentUser(vreg_env_use); + if (environment->GetInstructionAt(i) != nullptr) { + environment->RemoveAsUserOfInput(i); } } } @@ -64,22 +61,19 @@ void HGraph::RemoveInstructionsAsUsersFromDeadBlocks(const ArenaBitVector& visit } } -void HGraph::RemoveBlock(HBasicBlock* block) const { - for (size_t j = 0; j < block->GetSuccessors().Size(); ++j) { - block->GetSuccessors().Get(j)->RemovePredecessor(block); - } - for (HInstructionIterator it(block->GetPhis()); !it.Done(); it.Advance()) { - block->RemovePhi(it.Current()->AsPhi()); - } - for (HInstructionIterator it(block->GetInstructions()); !it.Done(); it.Advance()) { - block->RemoveInstruction(it.Current()); - } -} - void HGraph::RemoveDeadBlocks(const ArenaBitVector& visited) const { for (size_t i = 0; i < blocks_.Size(); ++i) { if (!visited.IsBitSet(i)) { - RemoveBlock(blocks_.Get(i)); + HBasicBlock* block = blocks_.Get(i); + for (size_t j = 0; j < block->GetSuccessors().Size(); ++j) { + block->GetSuccessors().Get(j)->RemovePredecessor(block); + } + for (HInstructionIterator it(block->GetPhis()); !it.Done(); it.Advance()) { + block->RemovePhi(it.Current()->AsPhi(), /*ensure_safety=*/ false); + } + for (HInstructionIterator it(block->GetInstructions()); !it.Done(); it.Advance()) { + block->RemoveInstruction(it.Current(), /*ensure_safety=*/ false); + } } } } @@ -439,22 +433,24 @@ void HBasicBlock::InsertPhiAfter(HPhi* phi, HPhi* cursor) { static void Remove(HInstructionList* instruction_list, HBasicBlock* block, - HInstruction* instruction) { + HInstruction* instruction, + bool ensure_safety) { DCHECK_EQ(block, instruction->GetBlock()); - DCHECK(instruction->GetUses().IsEmpty()); - DCHECK(instruction->GetEnvUses().IsEmpty()); instruction->SetBlock(nullptr); instruction_list->RemoveInstruction(instruction); - - RemoveAsUser(instruction); + if (ensure_safety) { + DCHECK(instruction->GetUses().IsEmpty()); + DCHECK(instruction->GetEnvUses().IsEmpty()); + RemoveAsUser(instruction); + } } -void HBasicBlock::RemoveInstruction(HInstruction* instruction) { - Remove(&instructions_, this, instruction); +void HBasicBlock::RemoveInstruction(HInstruction* instruction, bool ensure_safety) { + Remove(&instructions_, this, instruction, ensure_safety); } -void HBasicBlock::RemovePhi(HPhi* phi) { - Remove(&phis_, this, phi); +void HBasicBlock::RemovePhi(HPhi* phi, bool ensure_safety) { + Remove(&phis_, this, phi, ensure_safety); } void HEnvironment::CopyFrom(HEnvironment* env) { @@ -467,15 +463,9 @@ void HEnvironment::CopyFrom(HEnvironment* env) { } } -template <typename T> -static void RemoveFromUseList(T user, size_t input_index, HUseList<T>* list) { - HUseListNode<T>* current; - for (HUseIterator<HInstruction*> use_it(*list); !use_it.Done(); use_it.Advance()) { - current = use_it.Current(); - if (current->GetUser() == user && current->GetIndex() == input_index) { - list->Remove(current); - } - } +void HEnvironment::RemoveAsUserOfInput(size_t index) const { + const HUserRecord<HEnvironment*> user_record = vregs_.Get(index); + user_record.GetInstruction()->RemoveEnvironmentUser(user_record.GetUseNode()); } HInstruction* HInstruction::GetNextDisregardingMoves() const { @@ -494,14 +484,6 @@ HInstruction* HInstruction::GetPreviousDisregardingMoves() const { return previous; } -void HInstruction::RemoveUser(HInstruction* user, size_t input_index) { - RemoveFromUseList(user, input_index, &uses_); -} - -void HInstruction::RemoveEnvironmentUser(HUseListNode<HEnvironment*>* use) { - env_uses_.Remove(use); -} - void HInstructionList::AddInstruction(HInstruction* instruction) { if (first_instruction_ == nullptr) { DCHECK(last_instruction_ == nullptr); @@ -612,7 +594,7 @@ void HInstruction::ReplaceWith(HInstruction* other) { } void HInstruction::ReplaceInput(HInstruction* replacement, size_t index) { - InputAt(index)->RemoveUser(this, index); + RemoveAsUserOfInput(index); SetRawInputAt(index, replacement); replacement->AddUseAt(this, index); } @@ -623,7 +605,7 @@ size_t HInstruction::EnvironmentSize() const { void HPhi::AddInput(HInstruction* input) { DCHECK(input->GetBlock() != nullptr); - inputs_.Add(input); + inputs_.Add(HUserRecord<HInstruction*>(input)); input->AddUseAt(this, inputs_.Size() - 1); } diff --git a/compiler/optimizing/nodes.h b/compiler/optimizing/nodes.h index 352403d72c..de448cc483 100644 --- a/compiler/optimizing/nodes.h +++ b/compiler/optimizing/nodes.h @@ -31,6 +31,7 @@ namespace art { +class GraphChecker; class HBasicBlock; class HEnvironment; class HInstruction; @@ -211,7 +212,6 @@ class HGraph : public ArenaObject<kArenaAllocMisc> { ArenaBitVector* visiting); void RemoveInstructionsAsUsersFromDeadBlocks(const ArenaBitVector& visited) const; void RemoveDeadBlocks(const ArenaBitVector& visited) const; - void RemoveBlock(HBasicBlock* block) const; ArenaAllocator* const arena_; @@ -490,14 +490,17 @@ class HBasicBlock : public ArenaObject<kArenaAllocMisc> { void ReplaceWith(HBasicBlock* other); void AddInstruction(HInstruction* instruction); - void RemoveInstruction(HInstruction* instruction); void InsertInstructionBefore(HInstruction* instruction, HInstruction* cursor); // Replace instruction `initial` with `replacement` within this block. void ReplaceAndRemoveInstructionWith(HInstruction* initial, HInstruction* replacement); void AddPhi(HPhi* phi); void InsertPhiAfter(HPhi* instruction, HPhi* cursor); - void RemovePhi(HPhi* phi); + // RemoveInstruction and RemovePhi delete a given instruction from the respective + // instruction list. With 'ensure_safety' set to true, it verifies that the + // instruction is not in use and removes it from the use lists of its inputs. + void RemoveInstruction(HInstruction* instruction, bool ensure_safety = true); + void RemovePhi(HPhi* phi, bool ensure_safety = true); bool IsLoopHeader() const { return (loop_information_ != nullptr) && (loop_information_->GetHeader() == this); @@ -715,6 +718,9 @@ class HUseList : public ValueObject { } void Remove(HUseListNode<T>* node) { + DCHECK(node != nullptr); + DCHECK(Contains(node)); + if (node->prev_ != nullptr) { node->prev_->next_ = node->next_; } @@ -726,6 +732,18 @@ class HUseList : public ValueObject { } } + bool Contains(const HUseListNode<T>* node) const { + if (node == nullptr) { + return false; + } + for (HUseListNode<T>* current = first_; current != nullptr; current = current->GetNext()) { + if (current == node) { + return true; + } + } + return false; + } + bool IsEmpty() const { return first_ == nullptr; } @@ -761,6 +779,33 @@ class HUseIterator : public ValueObject { friend class HValue; }; +// This class is used by HEnvironment and HInstruction classes to record the +// instructions they use and pointers to the corresponding HUseListNodes kept +// by the used instructions. +template <typename T> +class HUserRecord : public ValueObject { + public: + HUserRecord() : instruction_(nullptr), use_node_(nullptr) {} + explicit HUserRecord(HInstruction* instruction) : instruction_(instruction), use_node_(nullptr) {} + + HUserRecord(const HUserRecord<T>& old_record, HUseListNode<T>* use_node) + : instruction_(old_record.instruction_), use_node_(use_node) { + DCHECK(instruction_ != nullptr); + DCHECK(use_node_ != nullptr); + DCHECK(old_record.use_node_ == nullptr); + } + + HInstruction* GetInstruction() const { return instruction_; } + HUseListNode<T>* GetUseNode() const { return use_node_; } + + private: + // Instruction used by the user. + HInstruction* instruction_; + + // Corresponding entry in the use list kept by 'instruction_'. + HUseListNode<T>* use_node_; +}; + // Represents the side effects an instruction may have. class SideEffects : public ValueObject { public: @@ -831,46 +876,36 @@ class HEnvironment : public ArenaObject<kArenaAllocMisc> { : vregs_(arena, number_of_vregs) { vregs_.SetSize(number_of_vregs); for (size_t i = 0; i < number_of_vregs; i++) { - vregs_.Put(i, VRegInfo(nullptr, nullptr)); + vregs_.Put(i, HUserRecord<HEnvironment*>()); } } void CopyFrom(HEnvironment* env); void SetRawEnvAt(size_t index, HInstruction* instruction) { - vregs_.Put(index, VRegInfo(instruction, nullptr)); - } - - // Record instructions' use entries of this environment for constant-time removal. - void RecordEnvUse(HUseListNode<HEnvironment*>* env_use) { - DCHECK(env_use->GetUser() == this); - size_t index = env_use->GetIndex(); - VRegInfo info = vregs_.Get(index); - DCHECK(info.vreg_ != nullptr); - DCHECK(info.node_ == nullptr); - vregs_.Put(index, VRegInfo(info.vreg_, env_use)); + vregs_.Put(index, HUserRecord<HEnvironment*>(instruction)); } HInstruction* GetInstructionAt(size_t index) const { - return vregs_.Get(index).vreg_; + return vregs_.Get(index).GetInstruction(); } - HUseListNode<HEnvironment*>* GetInstructionEnvUseAt(size_t index) const { - return vregs_.Get(index).node_; - } + void RemoveAsUserOfInput(size_t index) const; size_t Size() const { return vregs_.Size(); } private: - struct VRegInfo { - HInstruction* vreg_; - HUseListNode<HEnvironment*>* node_; + // Record instructions' use entries of this environment for constant-time removal. + // It should only be called by HInstruction when a new environment use is added. + void RecordEnvUse(HUseListNode<HEnvironment*>* env_use) { + DCHECK(env_use->GetUser() == this); + size_t index = env_use->GetIndex(); + vregs_.Put(index, HUserRecord<HEnvironment*>(vregs_.Get(index), env_use)); + } - VRegInfo(HInstruction* instruction, HUseListNode<HEnvironment*>* env_use) - : vreg_(instruction), node_(env_use) {} - }; + GrowableArray<HUserRecord<HEnvironment*> > vregs_; - GrowableArray<VRegInfo> vregs_; + friend HInstruction; DISALLOW_COPY_AND_ASSIGN(HEnvironment); }; @@ -989,13 +1024,15 @@ class HInstruction : public ArenaObject<kArenaAllocMisc> { bool IsLoopHeaderPhi() { return IsPhi() && block_->IsLoopHeader(); } virtual size_t InputCount() const = 0; - virtual HInstruction* InputAt(size_t i) const = 0; + HInstruction* InputAt(size_t i) const { return InputRecordAt(i).GetInstruction(); } virtual void Accept(HGraphVisitor* visitor) = 0; virtual const char* DebugName() const = 0; virtual Primitive::Type GetType() const { return Primitive::kPrimVoid; } - virtual void SetRawInputAt(size_t index, HInstruction* input) = 0; + void SetRawInputAt(size_t index, HInstruction* input) { + SetRawInputRecordAt(index, HUserRecord<HInstruction*>(input)); + } virtual bool NeedsEnvironment() const { return false; } virtual bool IsControlFlow() const { return false; } @@ -1018,7 +1055,10 @@ class HInstruction : public ArenaObject<kArenaAllocMisc> { ReferenceTypeInfo GetReferenceTypeInfo() const { return reference_type_info_; } void AddUseAt(HInstruction* user, size_t index) { - uses_.AddUse(user, index, GetBlock()->GetGraph()->GetArena()); + DCHECK(user != nullptr); + HUseListNode<HInstruction*>* use = + uses_.AddUse(user, index, GetBlock()->GetGraph()->GetArena()); + user->SetRawInputRecordAt(index, HUserRecord<HInstruction*>(user->InputRecordAt(index), use)); } void AddEnvUseAt(HEnvironment* user, size_t index) { @@ -1028,11 +1068,13 @@ class HInstruction : public ArenaObject<kArenaAllocMisc> { user->RecordEnvUse(env_use); } - void RemoveUser(HInstruction* user, size_t index); - void RemoveEnvironmentUser(HUseListNode<HEnvironment*>* use); + void RemoveAsUserOfInput(size_t input) { + HUserRecord<HInstruction*> input_use = InputRecordAt(input); + input_use.GetInstruction()->uses_.Remove(input_use.GetUseNode()); + } - const HUseList<HInstruction*>& GetUses() { return uses_; } - const HUseList<HEnvironment*>& GetEnvUses() { return env_uses_; } + const HUseList<HInstruction*>& GetUses() const { return uses_; } + const HUseList<HEnvironment*>& GetEnvUses() const { return env_uses_; } bool HasUses() const { return !uses_.IsEmpty() || !env_uses_.IsEmpty(); } bool HasEnvironmentUses() const { return !env_uses_.IsEmpty(); } @@ -1126,7 +1168,13 @@ class HInstruction : public ArenaObject<kArenaAllocMisc> { return NeedsEnvironment() || IsLoadClass() || IsLoadString(); } + protected: + virtual const HUserRecord<HInstruction*> InputRecordAt(size_t i) const = 0; + virtual void SetRawInputRecordAt(size_t index, const HUserRecord<HInstruction*>& input) = 0; + private: + void RemoveEnvironmentUser(HUseListNode<HEnvironment*>* use_node) { env_uses_.Remove(use_node); } + HInstruction* previous_; HInstruction* next_; HBasicBlock* block_; @@ -1164,7 +1212,9 @@ class HInstruction : public ArenaObject<kArenaAllocMisc> { // TODO: for primitive types this should be marked as invalid. ReferenceTypeInfo reference_type_info_; + friend class GraphChecker; friend class HBasicBlock; + friend class HEnvironment; friend class HGraph; friend class HInstructionList; @@ -1284,15 +1334,16 @@ class HTemplateInstruction: public HInstruction { virtual ~HTemplateInstruction() {} virtual size_t InputCount() const { return N; } - virtual HInstruction* InputAt(size_t i) const { return inputs_[i]; } protected: - virtual void SetRawInputAt(size_t i, HInstruction* instruction) { - inputs_[i] = instruction; + const HUserRecord<HInstruction*> InputRecordAt(size_t i) const OVERRIDE { return inputs_[i]; } + + void SetRawInputRecordAt(size_t i, const HUserRecord<HInstruction*>& input) OVERRIDE { + inputs_[i] = input; } private: - EmbeddedArray<HInstruction*, N> inputs_; + EmbeddedArray<HUserRecord<HInstruction*>, N> inputs_; friend class SsaBuilder; }; @@ -1848,7 +1899,6 @@ std::ostream& operator<<(std::ostream& os, const Intrinsics& intrinsic); class HInvoke : public HInstruction { public: virtual size_t InputCount() const { return inputs_.Size(); } - virtual HInstruction* InputAt(size_t i) const { return inputs_.Get(i); } // Runtime needs to walk the stack, so Dex -> Dex calls need to // know their environment. @@ -1858,10 +1908,6 @@ class HInvoke : public HInstruction { SetRawInputAt(index, argument); } - virtual void SetRawInputAt(size_t index, HInstruction* input) { - inputs_.Put(index, input); - } - virtual Primitive::Type GetType() const { return return_type_; } uint32_t GetDexPc() const { return dex_pc_; } @@ -1893,7 +1939,12 @@ class HInvoke : public HInstruction { inputs_.SetSize(number_of_arguments); } - GrowableArray<HInstruction*> inputs_; + const HUserRecord<HInstruction*> InputRecordAt(size_t i) const OVERRIDE { return inputs_.Get(i); } + void SetRawInputRecordAt(size_t index, const HUserRecord<HInstruction*>& input) OVERRIDE { + inputs_.Put(index, input); + } + + GrowableArray<HUserRecord<HInstruction*> > inputs_; const Primitive::Type return_type_; const uint32_t dex_pc_; const uint32_t dex_method_index_; @@ -2389,11 +2440,6 @@ class HPhi : public HInstruction { } size_t InputCount() const OVERRIDE { return inputs_.Size(); } - HInstruction* InputAt(size_t i) const OVERRIDE { return inputs_.Get(i); } - - void SetRawInputAt(size_t index, HInstruction* input) OVERRIDE { - inputs_.Put(index, input); - } void AddInput(HInstruction* input); @@ -2412,8 +2458,15 @@ class HPhi : public HInstruction { DECLARE_INSTRUCTION(Phi); + protected: + const HUserRecord<HInstruction*> InputRecordAt(size_t i) const OVERRIDE { return inputs_.Get(i); } + + void SetRawInputRecordAt(size_t index, const HUserRecord<HInstruction*>& input) OVERRIDE { + inputs_.Put(index, input); + } + private: - GrowableArray<HInstruction*> inputs_; + GrowableArray<HUserRecord<HInstruction*> > inputs_; const uint32_t reg_number_; Primitive::Type type_; bool is_live_; diff --git a/compiler/optimizing/register_allocator.cc b/compiler/optimizing/register_allocator.cc index bfbe63f6ce..54e62a5b2c 100644 --- a/compiler/optimizing/register_allocator.cc +++ b/compiler/optimizing/register_allocator.cc @@ -48,7 +48,10 @@ RegisterAllocator::RegisterAllocator(ArenaAllocator* allocator, physical_core_register_intervals_(allocator, codegen->GetNumberOfCoreRegisters()), physical_fp_register_intervals_(allocator, codegen->GetNumberOfFloatingPointRegisters()), temp_intervals_(allocator, 4), - spill_slots_(allocator, kDefaultNumberOfSpillSlots), + int_spill_slots_(allocator, kDefaultNumberOfSpillSlots), + long_spill_slots_(allocator, kDefaultNumberOfSpillSlots), + float_spill_slots_(allocator, kDefaultNumberOfSpillSlots), + double_spill_slots_(allocator, kDefaultNumberOfSpillSlots), safepoints_(allocator, 0), processing_core_registers_(false), number_of_registers_(-1), @@ -438,7 +441,7 @@ bool RegisterAllocator::ValidateInternal(bool log_fatal_on_failure) const { } } - return ValidateIntervals(intervals, spill_slots_.Size(), reserved_out_slots_, *codegen_, + return ValidateIntervals(intervals, GetNumberOfSpillSlots(), reserved_out_slots_, *codegen_, allocator_, processing_core_registers_, log_fatal_on_failure); } @@ -1133,41 +1136,62 @@ void RegisterAllocator::AllocateSpillSlotFor(LiveInterval* interval) { } size_t end = last_sibling->GetEnd(); + GrowableArray<size_t>* spill_slots = nullptr; + switch (interval->GetType()) { + case Primitive::kPrimDouble: + spill_slots = &double_spill_slots_; + break; + case Primitive::kPrimLong: + spill_slots = &long_spill_slots_; + break; + case Primitive::kPrimFloat: + spill_slots = &float_spill_slots_; + break; + case Primitive::kPrimNot: + case Primitive::kPrimInt: + case Primitive::kPrimChar: + case Primitive::kPrimByte: + case Primitive::kPrimBoolean: + case Primitive::kPrimShort: + spill_slots = &int_spill_slots_; + break; + case Primitive::kPrimVoid: + LOG(FATAL) << "Unexpected type for interval " << interval->GetType(); + } + // Find an available spill slot. size_t slot = 0; - for (size_t e = spill_slots_.Size(); slot < e; ++slot) { - // We check if it is less rather than less or equal because the parallel move - // resolver does not work when a single spill slot needs to be exchanged with - // a double spill slot. The strict comparison avoids needing to exchange these - // locations at the same lifetime position. - if (spill_slots_.Get(slot) < parent->GetStart() - && (slot == (e - 1) || spill_slots_.Get(slot + 1) < parent->GetStart())) { + for (size_t e = spill_slots->Size(); slot < e; ++slot) { + if (spill_slots->Get(slot) <= parent->GetStart() + && (slot == (e - 1) || spill_slots->Get(slot + 1) <= parent->GetStart())) { break; } } if (parent->NeedsTwoSpillSlots()) { - if (slot == spill_slots_.Size()) { + if (slot == spill_slots->Size()) { // We need a new spill slot. - spill_slots_.Add(end); - spill_slots_.Add(end); - } else if (slot == spill_slots_.Size() - 1) { - spill_slots_.Put(slot, end); - spill_slots_.Add(end); + spill_slots->Add(end); + spill_slots->Add(end); + } else if (slot == spill_slots->Size() - 1) { + spill_slots->Put(slot, end); + spill_slots->Add(end); } else { - spill_slots_.Put(slot, end); - spill_slots_.Put(slot + 1, end); + spill_slots->Put(slot, end); + spill_slots->Put(slot + 1, end); } } else { - if (slot == spill_slots_.Size()) { + if (slot == spill_slots->Size()) { // We need a new spill slot. - spill_slots_.Add(end); + spill_slots->Add(end); } else { - spill_slots_.Put(slot, end); + spill_slots->Put(slot, end); } } - parent->SetSpillSlot((slot + reserved_out_slots_) * kVRegSize); + // Note that the exact spill slot location will be computed when we resolve, + // that is when we know the number of spill slots for each type. + parent->SetSpillSlot(slot); } static bool IsValidDestination(Location destination) { @@ -1516,7 +1540,7 @@ void RegisterAllocator::ConnectSplitSiblings(LiveInterval* interval, } void RegisterAllocator::Resolve() { - codegen_->InitializeCodeGeneration(spill_slots_.Size(), + codegen_->InitializeCodeGeneration(GetNumberOfSpillSlots(), maximum_number_of_live_core_registers_, maximum_number_of_live_fp_registers_, reserved_out_slots_, @@ -1542,6 +1566,39 @@ void RegisterAllocator::Resolve() { } else if (current->HasSpillSlot()) { current->SetSpillSlot(current->GetSpillSlot() + codegen_->GetFrameSize()); } + } else if (current->HasSpillSlot()) { + // Adjust the stack slot, now that we know the number of them for each type. + // The way this implementation lays out the stack is the following: + // [parameter slots ] + // [double spill slots ] + // [long spill slots ] + // [float spill slots ] + // [int/ref values ] + // [maximum out values ] (number of arguments for calls) + // [art method ]. + uint32_t slot = current->GetSpillSlot(); + switch (current->GetType()) { + case Primitive::kPrimDouble: + slot += long_spill_slots_.Size(); + FALLTHROUGH_INTENDED; + case Primitive::kPrimLong: + slot += float_spill_slots_.Size(); + FALLTHROUGH_INTENDED; + case Primitive::kPrimFloat: + slot += int_spill_slots_.Size(); + FALLTHROUGH_INTENDED; + case Primitive::kPrimNot: + case Primitive::kPrimInt: + case Primitive::kPrimChar: + case Primitive::kPrimByte: + case Primitive::kPrimBoolean: + case Primitive::kPrimShort: + slot += reserved_out_slots_; + break; + case Primitive::kPrimVoid: + LOG(FATAL) << "Unexpected type for interval " << current->GetType(); + } + current->SetSpillSlot(slot * kVRegSize); } Location source = current->ToLocation(); diff --git a/compiler/optimizing/register_allocator.h b/compiler/optimizing/register_allocator.h index b8f70bdc18..ff2f106b74 100644 --- a/compiler/optimizing/register_allocator.h +++ b/compiler/optimizing/register_allocator.h @@ -75,7 +75,10 @@ class RegisterAllocator { } size_t GetNumberOfSpillSlots() const { - return spill_slots_.Size(); + return int_spill_slots_.Size() + + long_spill_slots_.Size() + + float_spill_slots_.Size() + + double_spill_slots_.Size(); } private: @@ -171,8 +174,14 @@ class RegisterAllocator { // where an instruction requires a temporary. GrowableArray<LiveInterval*> temp_intervals_; - // The spill slots allocated for live intervals. - GrowableArray<size_t> spill_slots_; + // The spill slots allocated for live intervals. We ensure spill slots + // are typed to avoid (1) doing moves and swaps between two different kinds + // of registers, and (2) swapping between a single stack slot and a double + // stack slot. This simplifies the parallel move resolver. + GrowableArray<size_t> int_spill_slots_; + GrowableArray<size_t> long_spill_slots_; + GrowableArray<size_t> float_spill_slots_; + GrowableArray<size_t> double_spill_slots_; // Instructions that need a safepoint. GrowableArray<HInstruction*> safepoints_; diff --git a/compiler/optimizing/ssa_phi_elimination.cc b/compiler/optimizing/ssa_phi_elimination.cc index f66a1c8de2..2f2e2d1fab 100644 --- a/compiler/optimizing/ssa_phi_elimination.cc +++ b/compiler/optimizing/ssa_phi_elimination.cc @@ -64,31 +64,33 @@ void SsaDeadPhiElimination::EliminateDeadPhis() { HBasicBlock* block = it.Current(); HInstruction* current = block->GetFirstPhi(); HInstruction* next = nullptr; + HPhi* phi; while (current != nullptr) { + phi = current->AsPhi(); next = current->GetNext(); - if (current->AsPhi()->IsDead()) { - if (current->HasUses()) { - for (HUseIterator<HInstruction*> use_it(current->GetUses()); !use_it.Done(); + if (phi->IsDead()) { + // Make sure the phi is only used by other dead phis. + if (kIsDebugBuild) { + for (HUseIterator<HInstruction*> use_it(phi->GetUses()); !use_it.Done(); use_it.Advance()) { - HUseListNode<HInstruction*>* user_node = use_it.Current(); - HInstruction* user = user_node->GetUser(); + HInstruction* user = use_it.Current()->GetUser(); DCHECK(user->IsLoopHeaderPhi()) << user->GetId(); DCHECK(user->AsPhi()->IsDead()) << user->GetId(); - // Just put itself as an input. The phi will be removed in this loop anyway. - user->SetRawInputAt(user_node->GetIndex(), user); - current->RemoveUser(user, user_node->GetIndex()); } } - if (current->HasEnvironmentUses()) { - for (HUseIterator<HEnvironment*> use_it(current->GetEnvUses()); !use_it.Done(); - use_it.Advance()) { - HUseListNode<HEnvironment*>* user_node = use_it.Current(); - HEnvironment* user = user_node->GetUser(); - user->SetRawEnvAt(user_node->GetIndex(), nullptr); - current->RemoveEnvironmentUser(user_node); - } + // Remove the phi from use lists of its inputs. + for (size_t i = 0, e = phi->InputCount(); i < e; ++i) { + phi->RemoveAsUserOfInput(i); + } + // Remove the phi from environments that use it. + for (HUseIterator<HEnvironment*> use_it(phi->GetEnvUses()); !use_it.Done(); + use_it.Advance()) { + HUseListNode<HEnvironment*>* user_node = use_it.Current(); + HEnvironment* user = user_node->GetUser(); + user->SetRawEnvAt(user_node->GetIndex(), nullptr); } - block->RemovePhi(current->AsPhi()); + // Delete it from the instruction list. + block->RemovePhi(phi, /*ensure_safety=*/ false); } current = next; } diff --git a/dex2oat/dex2oat.cc b/dex2oat/dex2oat.cc index e607e15a13..0b1f14dd94 100644 --- a/dex2oat/dex2oat.cc +++ b/dex2oat/dex2oat.cc @@ -36,6 +36,7 @@ #include "arch/instruction_set_features.h" #include "arch/mips/instruction_set_features_mips.h" #include "base/dumpable.h" +#include "base/macros.h" #include "base/stl_util.h" #include "base/stringpiece.h" #include "base/timing_logger.h" @@ -97,7 +98,7 @@ static void UsageError(const char* fmt, ...) { va_end(ap); } -[[noreturn]] static void Usage(const char* fmt, ...) { +NO_RETURN static void Usage(const char* fmt, ...) { va_list ap; va_start(ap, fmt); UsageErrorV(fmt, ap); @@ -326,7 +327,7 @@ class WatchDog { message.c_str()); } - [[noreturn]] static void Fatal(const std::string& message) { + NO_RETURN static void Fatal(const std::string& message) { Message('F', message); exit(1); } diff --git a/patchoat/patchoat.cc b/patchoat/patchoat.cc index 2059a96c70..3c6a23d8a1 100644 --- a/patchoat/patchoat.cc +++ b/patchoat/patchoat.cc @@ -763,7 +763,7 @@ static void UsageError(const char* fmt, ...) { va_end(ap); } -[[noreturn]] static void Usage(const char *fmt, ...) { +NO_RETURN static void Usage(const char *fmt, ...) { va_list ap; va_start(ap, fmt); UsageErrorV(fmt, ap); diff --git a/runtime/arch/arm/context_arm.cc b/runtime/arch/arm/context_arm.cc index c181e43132..5bd23d0def 100644 --- a/runtime/arch/arm/context_arm.cc +++ b/runtime/arch/arm/context_arm.cc @@ -106,7 +106,7 @@ void ArmContext::SmashCallerSaves() { fprs_[S15] = nullptr; } -extern "C" void art_quick_do_long_jump(uint32_t*, uint32_t*); +extern "C" NO_RETURN void art_quick_do_long_jump(uint32_t*, uint32_t*); void ArmContext::DoLongJump() { uintptr_t gprs[kNumberOfCoreRegisters]; diff --git a/runtime/arch/arm/context_arm.h b/runtime/arch/arm/context_arm.h index 1ca973eb80..5bdeda7f81 100644 --- a/runtime/arch/arm/context_arm.h +++ b/runtime/arch/arm/context_arm.h @@ -19,6 +19,7 @@ #include "arch/context.h" #include "base/logging.h" +#include "base/macros.h" #include "registers_arm.h" namespace art { @@ -76,7 +77,7 @@ class ArmContext : public Context { void SetFPR(uint32_t reg, uintptr_t value) OVERRIDE; void SmashCallerSaves() OVERRIDE; - void DoLongJump() OVERRIDE; + NO_RETURN void DoLongJump() OVERRIDE; private: // Pointers to register locations, initialized to NULL or the specific registers below. diff --git a/runtime/arch/arm64/context_arm64.cc b/runtime/arch/arm64/context_arm64.cc index 7fc0555c5b..ec9c122fc3 100644 --- a/runtime/arch/arm64/context_arm64.cc +++ b/runtime/arch/arm64/context_arm64.cc @@ -133,7 +133,7 @@ void Arm64Context::SmashCallerSaves() { fprs_[D31] = nullptr; } -extern "C" void art_quick_do_long_jump(uint64_t*, uint64_t*); +extern "C" NO_RETURN void art_quick_do_long_jump(uint64_t*, uint64_t*); void Arm64Context::DoLongJump() { uint64_t gprs[kNumberOfXRegisters]; diff --git a/runtime/arch/arm64/context_arm64.h b/runtime/arch/arm64/context_arm64.h index 6a4485b259..f4867797f0 100644 --- a/runtime/arch/arm64/context_arm64.h +++ b/runtime/arch/arm64/context_arm64.h @@ -19,6 +19,7 @@ #include "arch/context.h" #include "base/logging.h" +#include "base/macros.h" #include "registers_arm64.h" namespace art { @@ -76,7 +77,7 @@ class Arm64Context : public Context { void SetFPR(uint32_t reg, uintptr_t value) OVERRIDE; void SmashCallerSaves() OVERRIDE; - void DoLongJump() OVERRIDE; + NO_RETURN void DoLongJump() OVERRIDE; private: // Pointers to register locations, initialized to NULL or the specific registers below. diff --git a/runtime/arch/context.h b/runtime/arch/context.h index ed8cab0cd2..f86f9ae117 100644 --- a/runtime/arch/context.h +++ b/runtime/arch/context.h @@ -20,6 +20,7 @@ #include <stddef.h> #include <stdint.h> +#include "base/macros.h" #include "base/mutex.h" namespace art { @@ -78,7 +79,7 @@ class Context { virtual void SmashCallerSaves() = 0; // Switches execution of the executing context to this context - virtual void DoLongJump() = 0; + NO_RETURN virtual void DoLongJump() = 0; protected: enum { diff --git a/runtime/arch/mips/context_mips.cc b/runtime/arch/mips/context_mips.cc index 6c0ab98947..3b525be122 100644 --- a/runtime/arch/mips/context_mips.cc +++ b/runtime/arch/mips/context_mips.cc @@ -90,7 +90,7 @@ void MipsContext::SmashCallerSaves() { gprs_[A3] = nullptr; } -extern "C" void art_quick_do_long_jump(uint32_t*, uint32_t*); +extern "C" NO_RETURN void art_quick_do_long_jump(uint32_t*, uint32_t*); void MipsContext::DoLongJump() { uintptr_t gprs[kNumberOfCoreRegisters]; diff --git a/runtime/arch/mips/context_mips.h b/runtime/arch/mips/context_mips.h index d8a0b673c8..cbad3f963a 100644 --- a/runtime/arch/mips/context_mips.h +++ b/runtime/arch/mips/context_mips.h @@ -19,6 +19,7 @@ #include "arch/context.h" #include "base/logging.h" +#include "base/macros.h" #include "registers_mips.h" namespace art { @@ -75,7 +76,7 @@ class MipsContext : public Context { void SetFPR(uint32_t reg, uintptr_t value) OVERRIDE; void SmashCallerSaves() OVERRIDE; - void DoLongJump() OVERRIDE; + NO_RETURN void DoLongJump() OVERRIDE; private: // Pointers to registers in the stack, initialized to NULL except for the special cases below. diff --git a/runtime/arch/mips64/context_mips64.cc b/runtime/arch/mips64/context_mips64.cc index 1c96bd43ab..ce99b40b92 100644 --- a/runtime/arch/mips64/context_mips64.cc +++ b/runtime/arch/mips64/context_mips64.cc @@ -121,7 +121,7 @@ void Mips64Context::SmashCallerSaves() { fprs_[F23] = nullptr; } -extern "C" void art_quick_do_long_jump(uintptr_t*, uintptr_t*); +extern "C" NO_RETURN void art_quick_do_long_jump(uintptr_t*, uintptr_t*); void Mips64Context::DoLongJump() { uintptr_t gprs[kNumberOfGpuRegisters]; diff --git a/runtime/arch/mips64/context_mips64.h b/runtime/arch/mips64/context_mips64.h index 104672348b..2cc2b8df3e 100644 --- a/runtime/arch/mips64/context_mips64.h +++ b/runtime/arch/mips64/context_mips64.h @@ -19,6 +19,7 @@ #include "arch/context.h" #include "base/logging.h" +#include "base/macros.h" #include "registers_mips64.h" namespace art { @@ -75,7 +76,7 @@ class Mips64Context : public Context { void SetFPR(uint32_t reg, uintptr_t value) OVERRIDE; void SmashCallerSaves() OVERRIDE; - void DoLongJump() OVERRIDE; + NO_RETURN void DoLongJump() OVERRIDE; private: // Pointers to registers in the stack, initialized to NULL except for the special cases below. diff --git a/runtime/arch/x86/context_x86.cc b/runtime/arch/x86/context_x86.cc index 4ea4684f06..52a35dde92 100644 --- a/runtime/arch/x86/context_x86.cc +++ b/runtime/arch/x86/context_x86.cc @@ -133,6 +133,7 @@ void X86Context::DoLongJump() { #else UNIMPLEMENTED(FATAL); #endif + UNREACHABLE(); } } // namespace x86 diff --git a/runtime/arch/x86/context_x86.h b/runtime/arch/x86/context_x86.h index c66a9dcb19..ace4670136 100644 --- a/runtime/arch/x86/context_x86.h +++ b/runtime/arch/x86/context_x86.h @@ -19,6 +19,7 @@ #include "arch/context.h" #include "base/logging.h" +#include "base/macros.h" #include "registers_x86.h" namespace art { @@ -75,7 +76,7 @@ class X86Context : public Context { void SetFPR(uint32_t reg, uintptr_t value) OVERRIDE; void SmashCallerSaves() OVERRIDE; - void DoLongJump() OVERRIDE; + NO_RETURN void DoLongJump() OVERRIDE; private: // Pretend XMM registers are made of uin32_t pieces, because they are manipulated diff --git a/runtime/arch/x86_64/context_x86_64.cc b/runtime/arch/x86_64/context_x86_64.cc index cdc2ec71da..63365411e8 100644 --- a/runtime/arch/x86_64/context_x86_64.cc +++ b/runtime/arch/x86_64/context_x86_64.cc @@ -105,7 +105,7 @@ void X86_64Context::SetFPR(uint32_t reg, uintptr_t value) { *fprs_[reg] = value; } -extern "C" void art_quick_do_long_jump(uintptr_t*, uintptr_t*); +extern "C" NO_RETURN void art_quick_do_long_jump(uintptr_t*, uintptr_t*); void X86_64Context::DoLongJump() { #if defined(__x86_64__) @@ -127,6 +127,7 @@ void X86_64Context::DoLongJump() { art_quick_do_long_jump(gprs, fprs); #else UNIMPLEMENTED(FATAL); + UNREACHABLE(); #endif } diff --git a/runtime/arch/x86_64/context_x86_64.h b/runtime/arch/x86_64/context_x86_64.h index 0dda06ec4a..d03aa45237 100644 --- a/runtime/arch/x86_64/context_x86_64.h +++ b/runtime/arch/x86_64/context_x86_64.h @@ -19,6 +19,7 @@ #include "arch/context.h" #include "base/logging.h" +#include "base/macros.h" #include "registers_x86_64.h" namespace art { @@ -75,7 +76,7 @@ class X86_64Context : public Context { void SetFPR(uint32_t reg, uintptr_t value) OVERRIDE; void SmashCallerSaves() OVERRIDE; - void DoLongJump() OVERRIDE; + NO_RETURN void DoLongJump() OVERRIDE; private: // Pointers to register locations. Values are initialized to NULL or the special registers below. diff --git a/runtime/base/macros.h b/runtime/base/macros.h index f705469c89..3a9de5fecd 100644 --- a/runtime/base/macros.h +++ b/runtime/base/macros.h @@ -186,6 +186,9 @@ template<typename... T> void UNUSED(const T&...) {} // without the UNREACHABLE a return statement would be necessary. #define UNREACHABLE __builtin_unreachable +// Add the C++11 noreturn attribute. +#define NO_RETURN [[ noreturn ]] // NOLINT[whitespace/braces] [5] + // The FALLTHROUGH_INTENDED macro can be used to annotate implicit fall-through // between switch labels: // switch (x) { diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc index ee66b49e7d..327875195d 100644 --- a/runtime/class_linker.cc +++ b/runtime/class_linker.cc @@ -1008,10 +1008,15 @@ bool ClassLinker::OpenDexFilesFromOat(const char* dex_location, const char* oat_ // Failed, bail. if (open_oat_file.get() == nullptr) { - std::string error_msg; // dex2oat was disabled or crashed. Add the dex file in the list of dex_files to make progress. - DexFile::Open(dex_location, dex_location, &error_msg, dex_files); - error_msgs->push_back(error_msg); + if (Runtime::Current()->IsDexFileFallbackEnabled()) { + std::string error_msg; + if (!DexFile::Open(dex_location, dex_location, &error_msg, dex_files)) { + error_msgs->push_back(error_msg); + } + } else { + error_msgs->push_back("Fallback mode disabled, skipping dex files."); + } return false; } diff --git a/runtime/debugger.cc b/runtime/debugger.cc index a3d3b470cc..13bbdeb407 100644 --- a/runtime/debugger.cc +++ b/runtime/debugger.cc @@ -605,7 +605,7 @@ Thread* Dbg::GetDebugThread() { } void Dbg::ClearWaitForEventThread() { - gJdwpState->ClearWaitForEventThread(); + gJdwpState->ReleaseJdwpTokenForEvent(); } void Dbg::Connected() { @@ -3644,6 +3644,11 @@ JDWP::JdwpError Dbg::InvokeMethod(JDWP::ObjectId thread_id, JDWP::ObjectId objec thread_list->Resume(targetThread, true); } + // The target thread is resumed but needs the JDWP token we're holding. + // We release it now and will acquire it again when the invocation is + // complete and the target thread suspends itself. + gJdwpState->ReleaseJdwpTokenForCommand(); + // Wait for the request to finish executing. while (req->invoke_needed) { req->cond.Wait(self); @@ -3653,6 +3658,10 @@ JDWP::JdwpError Dbg::InvokeMethod(JDWP::ObjectId thread_id, JDWP::ObjectId objec /* wait for thread to re-suspend itself */ SuspendThread(thread_id, false /* request_suspension */); + + // Now the thread is suspended again, we can re-acquire the JDWP token. + gJdwpState->AcquireJdwpTokenForCommand(); + self->TransitionFromSuspendedToRunnable(); } @@ -3660,7 +3669,7 @@ JDWP::JdwpError Dbg::InvokeMethod(JDWP::ObjectId thread_id, JDWP::ObjectId objec * Suspend the threads. We waited for the target thread to suspend * itself, so all we need to do is suspend the others. * - * The suspendAllThreads() call will double-suspend the event thread, + * The SuspendAllForDebugger() call will double-suspend the event thread, * so we want to resume the target thread once to keep the books straight. */ if ((options & JDWP::INVOKE_SINGLE_THREADED) == 0) { diff --git a/runtime/gc_root.h b/runtime/gc_root.h index 7e0be64441..c5feda5407 100644 --- a/runtime/gc_root.h +++ b/runtime/gc_root.h @@ -71,7 +71,7 @@ class RootInfo { typedef void (RootCallback)(mirror::Object** root, void* arg, const RootInfo& root_info); template<class MirrorType> -class PACKED(4) GcRoot { +class GcRoot { public: template<ReadBarrierOption kReadBarrierOption = kWithReadBarrier> ALWAYS_INLINE MirrorType* Read() const SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); diff --git a/runtime/indirect_reference_table.h b/runtime/indirect_reference_table.h index 7f7870a412..576a60477e 100644 --- a/runtime/indirect_reference_table.h +++ b/runtime/indirect_reference_table.h @@ -197,7 +197,7 @@ union IRTSegmentState { // Contains multiple entries but only one active one, this helps us detect use after free errors // since the serial stored in the indirect ref wont match. static const size_t kIRTPrevCount = kIsDebugBuild ? 7 : 3; -class PACKED(4) IrtEntry { +class IrtEntry { public: void Add(mirror::Object* obj) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { ++serial_; @@ -218,6 +218,8 @@ class PACKED(4) IrtEntry { uint32_t serial_; GcRoot<mirror::Object> references_[kIRTPrevCount]; }; +static_assert(sizeof(IrtEntry) == (1 + kIRTPrevCount) * sizeof(uintptr_t), + "Unexpected sizeof(IrtEntry)"); class IrtIterator { public: diff --git a/runtime/interpreter/interpreter_common.h b/runtime/interpreter/interpreter_common.h index ce7c1c3817..06b809f028 100644 --- a/runtime/interpreter/interpreter_common.h +++ b/runtime/interpreter/interpreter_common.h @@ -25,6 +25,7 @@ #include <sstream> #include "base/logging.h" +#include "base/macros.h" #include "class_linker-inl.h" #include "common_throws.h" #include "dex_file-inl.h" @@ -349,8 +350,8 @@ uint32_t FindNextInstructionFollowingException(Thread* self, ShadowFrame& shadow uint32_t dex_pc, const instrumentation::Instrumentation* instrumentation) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); -void UnexpectedOpcode(const Instruction* inst, const ShadowFrame& shadow_frame) - __attribute__((cold, noreturn)) +NO_RETURN void UnexpectedOpcode(const Instruction* inst, const ShadowFrame& shadow_frame) + __attribute__((cold)) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); static inline void TraceExecution(const ShadowFrame& shadow_frame, const Instruction* inst, diff --git a/runtime/jdwp/jdwp.h b/runtime/jdwp/jdwp.h index 9f379989a7..e16221c69a 100644 --- a/runtime/jdwp/jdwp.h +++ b/runtime/jdwp/jdwp.h @@ -149,29 +149,19 @@ struct JdwpState { void ExitAfterReplying(int exit_status); - /* - * When we hit a debugger event that requires suspension, it's important - * that we wait for the thread to suspend itself before processing any - * additional requests. (Otherwise, if the debugger immediately sends a - * "resume thread" command, the resume might arrive before the thread has - * suspended itself.) - * - * The thread should call the "set" function before sending the event to - * the debugger. The main JDWP handler loop calls "get" before processing - * an event, and will wait for thread suspension if it's set. Once the - * thread has suspended itself, the JDWP handler calls "clear" and - * continues processing the current event. This works in the suspend-all - * case because the event thread doesn't suspend itself until everything - * else has suspended. - * - * It's possible that multiple threads could encounter thread-suspending - * events at the same time, so we grab a mutex in the "set" call, and - * release it in the "clear" call. - */ - // ObjectId GetWaitForEventThread(); - void SetWaitForEventThread(ObjectId threadId) - LOCKS_EXCLUDED(event_thread_lock_, process_request_lock_); - void ClearWaitForEventThread() LOCKS_EXCLUDED(event_thread_lock_); + // Acquires/releases the JDWP synchronization token for the debugger + // thread (command handler) so no event thread posts an event while + // it processes a command. This must be called only from the debugger + // thread. + void AcquireJdwpTokenForCommand() LOCKS_EXCLUDED(jdwp_token_lock_); + void ReleaseJdwpTokenForCommand() LOCKS_EXCLUDED(jdwp_token_lock_); + + // Acquires/releases the JDWP synchronization token for the event thread + // so no other thread (debugger thread or event thread) interleaves with + // it when posting an event. This must NOT be called from the debugger + // thread, only event thread. + void AcquireJdwpTokenForEvent(ObjectId threadId) LOCKS_EXCLUDED(jdwp_token_lock_); + void ReleaseJdwpTokenForEvent() LOCKS_EXCLUDED(jdwp_token_lock_); /* * These notify the debug code that something interesting has happened. This @@ -330,9 +320,37 @@ struct JdwpState { SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); void SendBufferedRequest(uint32_t type, const std::vector<iovec>& iov); - void StartProcessingRequest() LOCKS_EXCLUDED(process_request_lock_); - void EndProcessingRequest() LOCKS_EXCLUDED(process_request_lock_); - void WaitForProcessingRequest() LOCKS_EXCLUDED(process_request_lock_); + /* + * When we hit a debugger event that requires suspension, it's important + * that we wait for the thread to suspend itself before processing any + * additional requests. Otherwise, if the debugger immediately sends a + * "resume thread" command, the resume might arrive before the thread has + * suspended itself. + * + * It's also important no event thread suspends while we process a command + * from the debugger. Otherwise we could post an event ("thread death") + * before sending the reply of the command being processed ("resume") and + * cause bad synchronization with the debugger. + * + * The thread wanting "exclusive" access to the JDWP world must call the + * SetWaitForJdwpToken method before processing a command from the + * debugger or sending an event to the debugger. + * Once the command is processed or the event thread has posted its event, + * it must call the ClearWaitForJdwpToken method to allow another thread + * to do JDWP stuff. + * + * Therefore the main JDWP handler loop will wait for the event thread + * suspension before processing the next command. Once the event thread + * has suspended itself and cleared the token, the JDWP handler continues + * processing commands. This works in the suspend-all case because the + * event thread doesn't suspend itself until everything else has suspended. + * + * It's possible that multiple threads could encounter thread-suspending + * events at the same time, so we grab a mutex in the SetWaitForJdwpToken + * call, and release it in the ClearWaitForJdwpToken call. + */ + void SetWaitForJdwpToken(ObjectId threadId) LOCKS_EXCLUDED(jdwp_token_lock_); + void ClearWaitForJdwpToken() LOCKS_EXCLUDED(jdwp_token_lock_); public: // TODO: fix privacy const JdwpOptions* options_; @@ -368,24 +386,21 @@ struct JdwpState { // Linked list of events requested by the debugger (breakpoints, class prep, etc). Mutex event_list_lock_ DEFAULT_MUTEX_ACQUIRED_AFTER ACQUIRED_BEFORE(Locks::breakpoint_lock_); - JdwpEvent* event_list_ GUARDED_BY(event_list_lock_); size_t event_list_size_ GUARDED_BY(event_list_lock_); // Number of elements in event_list_. - // Used to synchronize suspension of the event thread (to avoid receiving "resume" - // events before the thread has finished suspending itself). - Mutex event_thread_lock_ DEFAULT_MUTEX_ACQUIRED_AFTER; - ConditionVariable event_thread_cond_ GUARDED_BY(event_thread_lock_); - ObjectId event_thread_id_; - - // Used to synchronize request processing and event sending (to avoid sending an event before - // sending the reply of a command being processed). - Mutex process_request_lock_ ACQUIRED_AFTER(event_thread_lock_); - ConditionVariable process_request_cond_ GUARDED_BY(process_request_lock_); - bool processing_request_ GUARDED_BY(process_request_lock_); + // Used to synchronize JDWP command handler thread and event threads so only one + // thread does JDWP stuff at a time. This prevent from interleaving command handling + // and event notification. Otherwise we could receive a "resume" command for an + // event thread that is not suspended yet, or post a "thread death" or event "VM death" + // event before sending the reply of the "resume" command that caused it. + Mutex jdwp_token_lock_ DEFAULT_MUTEX_ACQUIRED_AFTER; + ConditionVariable jdwp_token_cond_ GUARDED_BY(jdwp_token_lock_); + ObjectId jdwp_token_owner_thread_id_; bool ddm_is_active_; + // Used for VirtualMachine.Exit command handling. bool should_exit_; int exit_status_; }; diff --git a/runtime/jdwp/jdwp_event.cc b/runtime/jdwp/jdwp_event.cc index a8eaa263c5..b71f6cdfc4 100644 --- a/runtime/jdwp/jdwp_event.cc +++ b/runtime/jdwp/jdwp_event.cc @@ -612,7 +612,7 @@ void JdwpState::SuspendByPolicy(JdwpSuspendPolicy suspend_policy, JDWP::ObjectId } /* grab this before posting/suspending again */ - SetWaitForEventThread(thread_self_id); + AcquireJdwpTokenForEvent(thread_self_id); /* leave pReq->invoke_needed_ raised so we can check reentrancy */ Dbg::ExecuteMethod(pReq); @@ -630,7 +630,7 @@ void JdwpState::SendRequestAndPossiblySuspend(ExpandBuf* pReq, JdwpSuspendPolicy JDWP::ObjectId thread_self_id = Dbg::GetThreadSelfId(); self->TransitionFromRunnableToSuspended(kWaitingForDebuggerSend); if (suspend_policy != SP_NONE) { - SetWaitForEventThread(threadId); + AcquireJdwpTokenForEvent(threadId); } EventFinish(pReq); SuspendByPolicy(suspend_policy, thread_self_id); @@ -649,63 +649,82 @@ bool JdwpState::InvokeInProgress() { return pReq->invoke_needed; } +void JdwpState::AcquireJdwpTokenForCommand() { + CHECK_EQ(Thread::Current(), GetDebugThread()) << "Expected debugger thread"; + SetWaitForJdwpToken(debug_thread_id_); +} + +void JdwpState::ReleaseJdwpTokenForCommand() { + CHECK_EQ(Thread::Current(), GetDebugThread()) << "Expected debugger thread"; + ClearWaitForJdwpToken(); +} + +void JdwpState::AcquireJdwpTokenForEvent(ObjectId threadId) { + CHECK_NE(Thread::Current(), GetDebugThread()) << "Expected event thread"; + CHECK_NE(debug_thread_id_, threadId) << "Not expected debug thread"; + SetWaitForJdwpToken(threadId); +} + +void JdwpState::ReleaseJdwpTokenForEvent() { + CHECK_NE(Thread::Current(), GetDebugThread()) << "Expected event thread"; + ClearWaitForJdwpToken(); +} + /* * We need the JDWP thread to hold off on doing stuff while we post an * event and then suspend ourselves. * - * Call this with a threadId of zero if you just want to wait for the - * current thread operation to complete. - * * This could go to sleep waiting for another thread, so it's important * that the thread be marked as VMWAIT before calling here. */ -void JdwpState::SetWaitForEventThread(ObjectId threadId) { +void JdwpState::SetWaitForJdwpToken(ObjectId threadId) { bool waited = false; + Thread* const self = Thread::Current(); + CHECK_NE(threadId, 0u); + CHECK_NE(self->GetState(), kRunnable); + Locks::mutator_lock_->AssertNotHeld(self); /* this is held for very brief periods; contention is unlikely */ - Thread* self = Thread::Current(); - MutexLock mu(self, event_thread_lock_); + MutexLock mu(self, jdwp_token_lock_); + + CHECK_NE(jdwp_token_owner_thread_id_, threadId) << "Thread is already holding event thread lock"; /* * If another thread is already doing stuff, wait for it. This can * go to sleep indefinitely. */ - while (event_thread_id_ != 0) { + while (jdwp_token_owner_thread_id_ != 0) { VLOG(jdwp) << StringPrintf("event in progress (%#" PRIx64 "), %#" PRIx64 " sleeping", - event_thread_id_, threadId); + jdwp_token_owner_thread_id_, threadId); waited = true; - event_thread_cond_.Wait(self); + jdwp_token_cond_.Wait(self); } - if (waited || threadId != 0) { + if (waited || threadId != debug_thread_id_) { VLOG(jdwp) << StringPrintf("event token grabbed (%#" PRIx64 ")", threadId); } - if (threadId != 0) { - event_thread_id_ = threadId; - } + jdwp_token_owner_thread_id_ = threadId; } /* * Clear the threadId and signal anybody waiting. */ -void JdwpState::ClearWaitForEventThread() { +void JdwpState::ClearWaitForJdwpToken() { /* * Grab the mutex. Don't try to go in/out of VMWAIT mode, as this - * function is called by dvmSuspendSelf(), and the transition back + * function is called by Dbg::SuspendSelf(), and the transition back * to RUNNING would confuse it. */ - Thread* self = Thread::Current(); - MutexLock mu(self, event_thread_lock_); - - CHECK_NE(event_thread_id_, 0U); - VLOG(jdwp) << StringPrintf("cleared event token (%#" PRIx64 ")", event_thread_id_); + Thread* const self = Thread::Current(); + MutexLock mu(self, jdwp_token_lock_); - event_thread_id_ = 0; + CHECK_NE(jdwp_token_owner_thread_id_, 0U); + VLOG(jdwp) << StringPrintf("cleared event token (%#" PRIx64 ")", jdwp_token_owner_thread_id_); - event_thread_cond_.Signal(self); + jdwp_token_owner_thread_id_ = 0; + jdwp_token_cond_.Signal(self); } - /* * Prep an event. Allocates storage for the message and leaves space for * the header. @@ -730,11 +749,6 @@ void JdwpState::EventFinish(ExpandBuf* pReq) { Set1(buf + 9, kJdwpEventCommandSet); Set1(buf + 10, kJdwpCompositeCommand); - // Prevents from interleaving commands and events. Otherwise we could end up in sending an event - // before sending the reply of the command being processed and would lead to bad synchronization - // between the debugger and the debuggee. - WaitForProcessingRequest(); - SendRequest(pReq); expandBufFree(pReq); diff --git a/runtime/jdwp/jdwp_handler.cc b/runtime/jdwp/jdwp_handler.cc index a1d2a6c4bf..0ce4de7f61 100644 --- a/runtime/jdwp/jdwp_handler.cc +++ b/runtime/jdwp/jdwp_handler.cc @@ -1633,27 +1633,15 @@ size_t JdwpState::ProcessRequest(Request* request, ExpandBuf* pReply) { /* * If a debugger event has fired in another thread, wait until the - * initiating thread has suspended itself before processing messages + * initiating thread has suspended itself before processing commands * from the debugger. Otherwise we (the JDWP thread) could be told to * resume the thread before it has suspended. * - * We call with an argument of zero to wait for the current event - * thread to finish, and then clear the block. Depending on the thread - * suspend policy, this may allow events in other threads to fire, - * but those events have no bearing on what the debugger has sent us - * in the current request-> - * * Note that we MUST clear the event token before waking the event * thread up, or risk waiting for the thread to suspend after we've * told it to resume. */ - SetWaitForEventThread(0); - - /* - * We do not want events to be sent while we process a request-> Indicate the JDWP thread starts - * to process a request so other threads wait for it to finish before sending an event. - */ - StartProcessingRequest(); + AcquireJdwpTokenForCommand(); /* * Tell the VM that we're running and shouldn't be interrupted by GC. @@ -1719,50 +1707,6 @@ size_t JdwpState::ProcessRequest(Request* request, ExpandBuf* pReply) { return replyLength; } -/* - * Indicates a request is about to be processed. If a thread wants to send an event in the meantime, - * it will need to wait until we processed this request (see EndProcessingRequest). - */ -void JdwpState::StartProcessingRequest() { - Thread* self = Thread::Current(); - CHECK_EQ(self, GetDebugThread()) << "Requests are only processed by debug thread"; - MutexLock mu(self, process_request_lock_); - CHECK_EQ(processing_request_, false); - processing_request_ = true; -} - -/* - * Indicates a request has been processed (and we sent its reply). All threads waiting for us (see - * WaitForProcessingRequest) are waken up so they can send events again. - */ -void JdwpState::EndProcessingRequest() { - Thread* self = Thread::Current(); - CHECK_EQ(self, GetDebugThread()) << "Requests are only processed by debug thread"; - MutexLock mu(self, process_request_lock_); - CHECK_EQ(processing_request_, true); - processing_request_ = false; - process_request_cond_.Broadcast(self); -} - -/* - * Waits for any request being processed so we do not send an event in the meantime. - */ -void JdwpState::WaitForProcessingRequest() { - Thread* self = Thread::Current(); - CHECK_NE(self, GetDebugThread()) << "Events should not be posted by debug thread"; - MutexLock mu(self, process_request_lock_); - bool waited = false; - while (processing_request_) { - VLOG(jdwp) << StringPrintf("wait for processing request"); - waited = true; - process_request_cond_.Wait(self); - } - if (waited) { - VLOG(jdwp) << StringPrintf("finished waiting for processing request"); - } - CHECK_EQ(processing_request_, false); -} - } // namespace JDWP } // namespace art diff --git a/runtime/jdwp/jdwp_main.cc b/runtime/jdwp/jdwp_main.cc index b04aa6e134..b6fedd991d 100644 --- a/runtime/jdwp/jdwp_main.cc +++ b/runtime/jdwp/jdwp_main.cc @@ -220,12 +220,9 @@ JdwpState::JdwpState(const JdwpOptions* options) event_list_lock_("JDWP event list lock", kJdwpEventListLock), event_list_(nullptr), event_list_size_(0), - event_thread_lock_("JDWP event thread lock"), - event_thread_cond_("JDWP event thread condition variable", event_thread_lock_), - event_thread_id_(0), - process_request_lock_("JDWP process request lock"), - process_request_cond_("JDWP process request condition variable", process_request_lock_), - processing_request_(false), + jdwp_token_lock_("JDWP token lock"), + jdwp_token_cond_("JDWP token condition variable", jdwp_token_lock_), + jdwp_token_owner_thread_id_(0), ddm_is_active_(false), should_exit_(false), exit_status_(0) { @@ -331,7 +328,7 @@ void JdwpState::ResetState() { * Should not have one of these in progress. If the debugger went away * mid-request, though, we could see this. */ - if (event_thread_id_ != 0) { + if (jdwp_token_owner_thread_id_ != 0) { LOG(WARNING) << "Resetting state while event in progress"; DCHECK(false); } @@ -382,10 +379,9 @@ bool JdwpState::HandlePacket() { ssize_t cc = netStateBase->WritePacket(pReply, replyLength); /* - * We processed this request and sent its reply. Notify other threads waiting for us they can now - * send events. + * We processed this request and sent its reply so we can release the JDWP token. */ - EndProcessingRequest(); + ReleaseJdwpTokenForCommand(); if (cc != static_cast<ssize_t>(replyLength)) { PLOG(ERROR) << "Failed sending reply to debugger"; diff --git a/runtime/native/java_lang_Runtime.cc b/runtime/native/java_lang_Runtime.cc index 97b17bfd45..84b18ab382 100644 --- a/runtime/native/java_lang_Runtime.cc +++ b/runtime/native/java_lang_Runtime.cc @@ -20,6 +20,7 @@ #include <limits.h> #include <unistd.h> +#include "base/macros.h" #include "gc/heap.h" #include "handle_scope-inl.h" #include "jni_internal.h" @@ -39,7 +40,7 @@ static void Runtime_gc(JNIEnv*, jclass) { Runtime::Current()->GetHeap()->CollectGarbage(false); } -[[noreturn]] static void Runtime_nativeExit(JNIEnv*, jclass, jint status) { +NO_RETURN static void Runtime_nativeExit(JNIEnv*, jclass, jint status) { LOG(INFO) << "System.exit called, status: " << status; Runtime::Current()->CallExitHook(status); exit(status); diff --git a/runtime/parsed_options.cc b/runtime/parsed_options.cc index 99369ca35d..c0c7baaae1 100644 --- a/runtime/parsed_options.cc +++ b/runtime/parsed_options.cc @@ -241,6 +241,8 @@ std::unique_ptr<RuntimeParser> ParsedOptions::MakeParser(bool ignore_unrecognize .Define("-Xzygote-max-failed-boots=_") .WithType<unsigned int>() .IntoKey(M::ZygoteMaxFailedBoots) + .Define("-Xno-dex-file-fallback") + .IntoKey(M::NoDexFileFallback) .Ignore({ "-ea", "-da", "-enableassertions", "-disableassertions", "--runtime-arg", "-esa", "-dsa", "-enablesystemassertions", "-disablesystemassertions", "-Xrs", "-Xint:_", @@ -631,6 +633,8 @@ void ParsedOptions::Usage(const char* fmt, ...) { UsageMessage(stream, " -X[no]relocate\n"); UsageMessage(stream, " -X[no]dex2oat (Whether to invoke dex2oat on the application)\n"); UsageMessage(stream, " -X[no]image-dex2oat (Whether to create and use a boot image)\n"); + UsageMessage(stream, " -Xno-dex-file-fallback " + "(Don't fall back to dex files without oat files)\n"); UsageMessage(stream, "\n"); UsageMessage(stream, "The following previously supported Dalvik options are ignored:\n"); diff --git a/runtime/quick_exception_handler.cc b/runtime/quick_exception_handler.cc index 85ec80314e..7bdf652355 100644 --- a/runtime/quick_exception_handler.cc +++ b/runtime/quick_exception_handler.cc @@ -353,6 +353,7 @@ void QuickExceptionHandler::DoLongJump() { context_->SetPC(handler_quick_frame_pc_); context_->SmashCallerSaves(); context_->DoLongJump(); + UNREACHABLE(); } } // namespace art diff --git a/runtime/quick_exception_handler.h b/runtime/quick_exception_handler.h index 31622de984..162b1dc74c 100644 --- a/runtime/quick_exception_handler.h +++ b/runtime/quick_exception_handler.h @@ -18,6 +18,7 @@ #define ART_RUNTIME_QUICK_EXCEPTION_HANDLER_H_ #include "base/logging.h" +#include "base/macros.h" #include "base/mutex.h" #include "stack.h" // StackReference @@ -48,7 +49,7 @@ class QuickExceptionHandler { SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); void DeoptimizeStack() SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); void UpdateInstrumentationStack() SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); - void DoLongJump() SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + NO_RETURN void DoLongJump() SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); void SetHandlerQuickFrame(StackReference<mirror::ArtMethod>* handler_quick_frame) { handler_quick_frame_ = handler_quick_frame; diff --git a/runtime/runtime.cc b/runtime/runtime.cc index f38f65e2ad..8cd9e2427d 100644 --- a/runtime/runtime.cc +++ b/runtime/runtime.cc @@ -169,6 +169,7 @@ Runtime::Runtime() dump_gc_performance_on_shutdown_(false), preinitialization_transaction_(nullptr), verify_(false), + allow_dex_file_fallback_(true), target_sdk_version_(0), implicit_null_checks_(false), implicit_so_checks_(false), @@ -190,7 +191,8 @@ Runtime::~Runtime() { } Thread* self = Thread::Current(); - if (self == nullptr) { + const bool attach_shutdown_thread = self == nullptr; + if (attach_shutdown_thread) { CHECK(AttachCurrentThread("Shutdown thread", false, nullptr, false)); self = Thread::Current(); } else { @@ -212,8 +214,10 @@ Runtime::~Runtime() { self->GetJniEnv()->CallStaticVoidMethod(WellKnownClasses::java_lang_Daemons, WellKnownClasses::java_lang_Daemons_stop); } - DetachCurrentThread(); - self = nullptr; + if (attach_shutdown_thread) { + DetachCurrentThread(); + self = nullptr; + } // Shut down background profiler before the runtime exits. if (profiler_started_) { @@ -380,7 +384,9 @@ bool Runtime::Create(const RuntimeOptions& options, bool ignore_unrecognized) { InitLogging(NULL); // Calls Locks::Init() as a side effect. instance_ = new Runtime; if (!instance_->Init(options, ignore_unrecognized)) { - delete instance_; + // TODO: Currently deleting the instance will abort the runtime on destruction. Now This will + // leak memory, instead. Fix the destructor. b/19100793. + // delete instance_; instance_ = NULL; return false; } @@ -762,6 +768,7 @@ bool Runtime::Init(const RuntimeOptions& raw_options, bool ignore_unrecognized) intern_table_ = new InternTable; verify_ = runtime_options.GetOrDefault(Opt::Verify); + allow_dex_file_fallback_ = !runtime_options.Exists(Opt::NoDexFileFallback); if (runtime_options.GetOrDefault(Opt::Interpret)) { GetInstrumentation()->ForceInterpretOnly(); @@ -800,6 +807,11 @@ bool Runtime::Init(const RuntimeOptions& raw_options, bool ignore_unrecognized) runtime_options.GetOrDefault(Opt::EnableHSpaceCompactForOOM), runtime_options.GetOrDefault(Opt::HSpaceCompactForOOMMinIntervalsMs)); + if (heap_->GetImageSpace() == nullptr && !allow_dex_file_fallback_) { + LOG(ERROR) << "Dex file fallback disabled, cannot continue without image."; + return false; + } + dump_gc_performance_on_shutdown_ = runtime_options.Exists(Opt::DumpGCPerformanceOnShutdown); if (runtime_options.Exists(Opt::JdwpOptions)) { diff --git a/runtime/runtime.h b/runtime/runtime.h index e7d95fb595..944c8bd0dd 100644 --- a/runtime/runtime.h +++ b/runtime/runtime.h @@ -28,6 +28,7 @@ #include "arch/instruction_set.h" #include "base/allocator.h" +#include "base/macros.h" #include "compiler_callbacks.h" #include "gc_root.h" #include "instrumentation.h" @@ -185,7 +186,7 @@ class Runtime { // Aborts semi-cleanly. Used in the implementation of LOG(FATAL), which most // callers should prefer. - [[noreturn]] static void Abort() LOCKS_EXCLUDED(Locks::abort_lock_); + NO_RETURN static void Abort() LOCKS_EXCLUDED(Locks::abort_lock_); // Returns the "main" ThreadGroup, used when attaching user threads. jobject GetMainThreadGroup() const; @@ -504,6 +505,10 @@ class Runtime { return verify_; } + bool IsDexFileFallbackEnabled() const { + return allow_dex_file_fallback_; + } + bool RunningOnValgrind() const { return running_on_valgrind_; } @@ -667,6 +672,10 @@ class Runtime { // If false, verification is disabled. True by default. bool verify_; + // If true, the runtime may use dex files directly with the interpreter if an oat file is not + // available/usable. + bool allow_dex_file_fallback_; + // Specifies target SDK version to allow workarounds for certain API levels. int32_t target_sdk_version_; diff --git a/runtime/runtime_options.def b/runtime/runtime_options.def index 71a0152956..d9cc4d43e7 100644 --- a/runtime/runtime_options.def +++ b/runtime/runtime_options.def @@ -115,6 +115,6 @@ RUNTIME_OPTIONS_KEY (void (*)(int32_t status), \ // Runtime::Abort. RUNTIME_OPTIONS_KEY (void (*)(), HookAbort, nullptr) RUNTIME_OPTIONS_KEY (unsigned int, ZygoteMaxFailedBoots, 1) - +RUNTIME_OPTIONS_KEY (Unit, NoDexFileFallback) #undef RUNTIME_OPTIONS_KEY diff --git a/runtime/thread.cc b/runtime/thread.cc index cb6ed64f60..3b48f49fb4 100644 --- a/runtime/thread.cc +++ b/runtime/thread.cc @@ -2044,8 +2044,6 @@ void Thread::QuickDeliverException() { } exception_handler.UpdateInstrumentationStack(); exception_handler.DoLongJump(); - LOG(FATAL) << "UNREACHABLE"; - UNREACHABLE(); } Context* Thread::GetLongJumpContext() { diff --git a/runtime/thread.h b/runtime/thread.h index 26b7b6f22c..83cedbb7f2 100644 --- a/runtime/thread.h +++ b/runtime/thread.h @@ -353,7 +353,7 @@ class Thread { } // Find catch block and perform long jump to appropriate exception handle - void QuickDeliverException() SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + NO_RETURN void QuickDeliverException() SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); Context* GetLongJumpContext(); void ReleaseLongJumpContext(Context* context) { diff --git a/runtime/thread_list.cc b/runtime/thread_list.cc index 05a0bff135..d0f014ab11 100644 --- a/runtime/thread_list.cc +++ b/runtime/thread_list.cc @@ -228,8 +228,7 @@ void ThreadList::AssertThreadsAreSuspended(Thread* self, Thread* ignore1, Thread #if HAVE_TIMED_RWLOCK // Attempt to rectify locks so that we dump thread list with required locks before exiting. -static void UnsafeLogFatalForThreadSuspendAllTimeout() __attribute__((noreturn)); -static void UnsafeLogFatalForThreadSuspendAllTimeout() { +NO_RETURN static void UnsafeLogFatalForThreadSuspendAllTimeout() { Runtime* runtime = Runtime::Current(); std::ostringstream ss; ss << "Thread suspend timeout\n"; diff --git a/test/116-nodex2oat/run b/test/116-nodex2oat/run index 9e5c7ddab9..2cdb3f7b46 100755 --- a/test/116-nodex2oat/run +++ b/test/116-nodex2oat/run @@ -24,7 +24,7 @@ if [[ "${flags}" == *--prebuild* || "${flags}" != *--no-prebuild* ]] ; then exit 1 fi -# Make sure we can run without an oat file, +# Make sure we can run without an oat file. echo "Run -Xnodex2oat" ${RUN} ${flags} --runtime-option -Xnodex2oat diff --git a/test/118-noimage-dex2oat/expected.txt b/test/118-noimage-dex2oat/expected.txt index bcb695d232..0103e899f6 100644 --- a/test/118-noimage-dex2oat/expected.txt +++ b/test/118-noimage-dex2oat/expected.txt @@ -1,6 +1,8 @@ Run -Xnoimage-dex2oat Has image is false, is image dex2oat enabled is false, is BOOTCLASSPATH on disk is false. testB18485243 PASS +Run -Xnoimage-dex2oat -Xno-dex-file-fallback +Failed to initialize runtime (check log for details) Run -Ximage-dex2oat Has image is true, is image dex2oat enabled is true, is BOOTCLASSPATH on disk is true. testB18485243 PASS diff --git a/test/118-noimage-dex2oat/run b/test/118-noimage-dex2oat/run index 2037797790..4b1d0cea59 100644 --- a/test/118-noimage-dex2oat/run +++ b/test/118-noimage-dex2oat/run @@ -46,10 +46,14 @@ bpath="${bpath}:${framework}/bouncycastle${bpath_suffix}.jar" bpath_arg="--runtime-option -Xbootclasspath:${bpath}" -# Make sure we can run without an oat file, +# Make sure we can run without an oat file. echo "Run -Xnoimage-dex2oat" ${RUN} ${flags} ${bpath_arg} --runtime-option -Xnoimage-dex2oat --runtime-option -Xnodex2oat +# Make sure we cannot run without an oat file without fallback. +echo "Run -Xnoimage-dex2oat -Xno-dex-file-fallback" +${RUN} ${flags} ${bpath_arg} --runtime-option -Xnoimage-dex2oat --runtime-option -Xnodex2oat --runtime-option -Xno-dex-file-fallback + # Make sure we can run with the oat file. echo "Run -Ximage-dex2oat" ${RUN} ${flags} ${bpath_arg} --runtime-option -Ximage-dex2oat diff --git a/test/119-noimage-patchoat/check b/test/119-noimage-patchoat/check new file mode 100755 index 0000000000..57111bcc99 --- /dev/null +++ b/test/119-noimage-patchoat/check @@ -0,0 +1,20 @@ +#!/bin/bash +# +# Copyright (C) 2014 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. + +# Strip the process pids and line numbers from exact error messages. +sed -e '/^art E.*\] /d' "$2" > "$2.tmp" + +diff --strip-trailing-cr -q "$1" "$2.tmp" >/dev/null diff --git a/test/119-noimage-patchoat/expected.txt b/test/119-noimage-patchoat/expected.txt index e864268a38..ed136621c3 100644 --- a/test/119-noimage-patchoat/expected.txt +++ b/test/119-noimage-patchoat/expected.txt @@ -1,5 +1,7 @@ Run -Xnoimage-dex2oat -Xpatchoat:/system/bin/false Has image is false, is image dex2oat enabled is false. +Run -Xnoimage-dex2oat -Xpatchoat:/system/bin/false -Xno-dex-file-fallback +Failed to initialize runtime (check log for details) Run -Ximage-dex2oat Has image is true, is image dex2oat enabled is true. Run default diff --git a/test/119-noimage-patchoat/run b/test/119-noimage-patchoat/run index c409cbb75e..02a64f8ada 100644 --- a/test/119-noimage-patchoat/run +++ b/test/119-noimage-patchoat/run @@ -29,10 +29,14 @@ else false_bin="/system/bin/false" fi -# Make sure we can run without an image file, +# Make sure we can run without an image file. echo "Run -Xnoimage-dex2oat -Xpatchoat:/system/bin/false" ${RUN} ${flags} ${BPATH} --runtime-option -Xnoimage-dex2oat --runtime-option -Xpatchoat:${false_bin} +# Make sure we cannot run without an image file without fallback. +echo "Run -Xnoimage-dex2oat -Xpatchoat:/system/bin/false -Xno-dex-file-fallback" +${RUN} ${flags} ${BPATH} --runtime-option -Xnoimage-dex2oat --runtime-option -Xpatchoat:${false_bin} --runtime-option -Xno-dex-file-fallback + # Make sure we can run with the image file. echo "Run -Ximage-dex2oat" ${RUN} ${flags} ${BPATH} --runtime-option -Ximage-dex2oat diff --git a/test/134-nodex2oat-nofallback/check b/test/134-nodex2oat-nofallback/check new file mode 100755 index 0000000000..d929c8f87b --- /dev/null +++ b/test/134-nodex2oat-nofallback/check @@ -0,0 +1,32 @@ +#!/bin/bash +# +# Copyright (C) 2014 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. + +# The head of the log. +HEAD=$(head -n 1 "$2") +EXPECTED_HEAD="Unable to locate class 'Main'" + +# Content somewhere inside the output. +grep 'Suppressed: java.io.IOException: Fallback mode disabled, skipping dex files.' "$2" >/dev/null +MSG_FOUND=$? + +if [[ "$HEAD" != "$EXPECTED_HEAD" || $MSG_FOUND -ne 0 ]] ; then + # Print out the log and return with error. + cat "$2" + exit 1 +fi + +# Success. +exit 0
\ No newline at end of file diff --git a/test/134-nodex2oat-nofallback/expected.txt b/test/134-nodex2oat-nofallback/expected.txt new file mode 100644 index 0000000000..9b6c84667c --- /dev/null +++ b/test/134-nodex2oat-nofallback/expected.txt @@ -0,0 +1,64 @@ +# This file isn't used, but gives an example of the output we expect. +Unable to locate class 'Main' +java.lang.ClassNotFoundException: Didn't find class "Main" on path: DexPathList[[zip file "/tmp/user/test-12345/134-nodex2oat-nofallback.jar"],nativeLibraryDirectories=[/ssd2/aosp-master3/out/host/linux-x86/lib]] + at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:56) + at java.lang.ClassLoader.loadClass(ClassLoader.java:511) + at java.lang.ClassLoader.loadClass(ClassLoader.java:469) + Suppressed: java.io.IOException: Fallback mode disabled, skipping dex files. + at dalvik.system.DexFile.openDexFileNative(Native Method) + at dalvik.system.DexFile.openDexFile(DexFile.java:295) + at dalvik.system.DexFile.<init>(DexFile.java:80) + at dalvik.system.DexFile.<init>(DexFile.java:59) + at dalvik.system.DexPathList.loadDexFile(DexPathList.java:262) + at dalvik.system.DexPathList.makeDexElements(DexPathList.java:231) + at dalvik.system.DexPathList.<init>(DexPathList.java:109) + at dalvik.system.BaseDexClassLoader.<init>(BaseDexClassLoader.java:48) + at dalvik.system.PathClassLoader.<init>(PathClassLoader.java:38) + at java.lang.ClassLoader.createSystemClassLoader(ClassLoader.java:128) + at java.lang.ClassLoader.access$000(ClassLoader.java:65) + at java.lang.ClassLoader$SystemClassLoader.<clinit>(ClassLoader.java:81) + at java.lang.ClassLoader.getSystemClassLoader(ClassLoader.java:137) + Caused by: java.io.IOException: Failed to open oat file from dex location '/tmp/user/test-12345/134-nodex2oat-nofallback.jar' + ... 13 more + Caused by: java.io.IOException: Failed to open oat file from /tmp/user/test-12345/x86/134-nodex2oat-nofallback.odex (error Failed to open oat filename for reading: No such file or directory) (no dalvik_cache availible) and relocation failed. + ... 13 more + Caused by: java.io.IOException + ... 13 more + Suppressed: java.lang.ClassNotFoundException: Main + at java.lang.Class.classForName(Native Method) + at java.lang.BootClassLoader.findClass(ClassLoader.java:781) + at java.lang.BootClassLoader.loadClass(ClassLoader.java:841) + at java.lang.ClassLoader.loadClass(ClassLoader.java:504) + ... 1 more + Caused by: java.lang.NoClassDefFoundError: Class not found using the boot class loader; no stack trace available +java.lang.ClassNotFoundException: Didn't find class "Main" on path: DexPathList[[zip file "/tmp/user/test-12345/134-nodex2oat-nofallback.jar"],nativeLibraryDirectories=[/ssd2/aosp-master3/out/host/linux-x86/lib]] + at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:56) + at java.lang.ClassLoader.loadClass(ClassLoader.java:511) + at java.lang.ClassLoader.loadClass(ClassLoader.java:469) + Suppressed: java.io.IOException: Fallback mode disabled, skipping dex files. + at dalvik.system.DexFile.openDexFileNative(Native Method) + at dalvik.system.DexFile.openDexFile(DexFile.java:295) + at dalvik.system.DexFile.<init>(DexFile.java:80) + at dalvik.system.DexFile.<init>(DexFile.java:59) + at dalvik.system.DexPathList.loadDexFile(DexPathList.java:262) + at dalvik.system.DexPathList.makeDexElements(DexPathList.java:231) + at dalvik.system.DexPathList.<init>(DexPathList.java:109) + at dalvik.system.BaseDexClassLoader.<init>(BaseDexClassLoader.java:48) + at dalvik.system.PathClassLoader.<init>(PathClassLoader.java:38) + at java.lang.ClassLoader.createSystemClassLoader(ClassLoader.java:128) + at java.lang.ClassLoader.access$000(ClassLoader.java:65) + at java.lang.ClassLoader$SystemClassLoader.<clinit>(ClassLoader.java:81) + at java.lang.ClassLoader.getSystemClassLoader(ClassLoader.java:137) + Caused by: java.io.IOException: Failed to open oat file from dex location '/tmp/user/test-12345/134-nodex2oat-nofallback.jar' + ... 13 more + Caused by: java.io.IOException: Failed to open oat file from /tmp/user/test-12345/x86/134-nodex2oat-nofallback.odex (error Failed to open oat filename for reading: No such file or directory) (no dalvik_cache availible) and relocation failed. + ... 13 more + Caused by: java.io.IOException: + ... 13 more + Suppressed: java.lang.ClassNotFoundException: Main + at java.lang.Class.classForName(Native Method) + at java.lang.BootClassLoader.findClass(ClassLoader.java:781) + at java.lang.BootClassLoader.loadClass(ClassLoader.java:841) + at java.lang.ClassLoader.loadClass(ClassLoader.java:504) + ... 1 more + Caused by: java.lang.NoClassDefFoundError: Class not found using the boot class loader; no stack trace available diff --git a/test/134-nodex2oat-nofallback/info.txt b/test/134-nodex2oat-nofallback/info.txt new file mode 100644 index 0000000000..300472995c --- /dev/null +++ b/test/134-nodex2oat-nofallback/info.txt @@ -0,0 +1,2 @@ +Test that disables dex2oat'ing the application, and disable fallback. This is derived from test +116. It needs it separate test as it needs a custom check script.
\ No newline at end of file diff --git a/test/134-nodex2oat-nofallback/run b/test/134-nodex2oat-nofallback/run new file mode 100755 index 0000000000..38b4adb183 --- /dev/null +++ b/test/134-nodex2oat-nofallback/run @@ -0,0 +1,20 @@ +#!/bin/bash +# +# Copyright (C) 2014 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. + +flags="${@}" + +# Make sure we cannot run without an oat file without fallback. +${RUN} ${flags} --runtime-option -Xnodex2oat --runtime-option -Xno-dex-file-fallback diff --git a/test/134-nodex2oat-nofallback/src/Main.java b/test/134-nodex2oat-nofallback/src/Main.java new file mode 100644 index 0000000000..37ac9d5b78 --- /dev/null +++ b/test/134-nodex2oat-nofallback/src/Main.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2014 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. + */ + +public class Main { + public static void main(String[] args) { + System.out.println( + "Has oat is " + hasOat() + ", is dex2oat enabled is " + isDex2OatEnabled() + "."); + + if (hasOat() && !isDex2OatEnabled()) { + throw new Error("Application with dex2oat disabled runs with an oat file"); + } else if (!hasOat() && isDex2OatEnabled()) { + throw new Error("Application with dex2oat enabled runs without an oat file"); + } + } + + static { + System.loadLibrary("arttest"); + } + + private native static boolean hasOat(); + + private native static boolean isDex2OatEnabled(); +} diff --git a/test/449-checker-bce/src/Main.java b/test/449-checker-bce/src/Main.java index 5a0e13b7f6..ad4092b5a3 100644 --- a/test/449-checker-bce/src/Main.java +++ b/test/449-checker-bce/src/Main.java @@ -46,6 +46,7 @@ public class Main { return primeCount; } + // CHECK-START: void Main.narrow(int[], int) BCE (before) // CHECK: BoundsCheck // CHECK: ArraySet @@ -61,8 +62,12 @@ public class Main { // CHECK: ArraySet // CHECK: BoundsCheck // CHECK: ArraySet + // CHECK-NOT: BoundsCheck + // CHECK: ArraySet + // CHECK: BoundsCheck + // CHECK: ArraySet - static void narrow(int array[], int offset) { + static void narrow(int[] array, int offset) { if (offset < 0) { return; } @@ -87,10 +92,386 @@ public class Main { // eliminate this bounds check. array[biased_offset2] = 1; } + + // offset_sub1 won't underflow since offset is no less than 0. + int offset_sub1 = offset - Integer.MAX_VALUE; + if (offset_sub1 >= 0) { + array[offset_sub1] = 1; // Bounds check can be eliminated. + } + + // offset_sub2 can underflow. + int offset_sub2 = offset_sub1 - Integer.MAX_VALUE; + if (offset_sub2 >= 0) { + array[offset_sub2] = 1; // Bounds check can't be eliminated. + } + } + } + + + // CHECK-START: void Main.constantIndexing(int[]) BCE (before) + // CHECK: BoundsCheck + // CHECK: ArraySet + // CHECK: BoundsCheck + // CHECK: ArraySet + // CHECK: BoundsCheck + // CHECK: ArraySet + + // CHECK-START: void Main.constantIndexing(int[]) BCE (after) + // CHECK: BoundsCheck + // CHECK: ArraySet + // CHECK-NOT: BoundsCheck + // CHECK: ArraySet + // CHECK: BoundsCheck + // CHECK: ArraySet + + static void constantIndexing(int[] array) { + array[5] = 1; + array[4] = 1; + array[6] = 1; + } + + + // CHECK-START: void Main.loopPattern1(int[]) BCE (before) + // CHECK: BoundsCheck + // CHECK: ArraySet + // CHECK: BoundsCheck + // CHECK: ArraySet + // CHECK: BoundsCheck + // CHECK: ArraySet + // CHECK: BoundsCheck + // CHECK: ArraySet + // CHECK: BoundsCheck + // CHECK: ArraySet + // CHECK: BoundsCheck + // CHECK: ArraySet + // CHECK: BoundsCheck + // CHECK: ArraySet + + // CHECK-START: void Main.loopPattern1(int[]) BCE (after) + // CHECK-NOT: BoundsCheck + // CHECK: ArraySet + // CHECK-NOT: BoundsCheck + // CHECK: ArraySet + // CHECK-NOT: BoundsCheck + // CHECK: ArraySet + // CHECK: BoundsCheck + // CHECK: ArraySet + // CHECK: BoundsCheck + // CHECK: ArraySet + // CHECK: BoundsCheck + // CHECK: ArraySet + // CHECK-NOT: BoundsCheck + // CHECK: ArraySet + + static void loopPattern1(int[] array) { + for (int i = 0; i < array.length; i++) { + array[i] = 1; // Bounds check can be eliminated. + } + + for (int i = 1; i < array.length; i++) { + array[i] = 1; // Bounds check can be eliminated. + } + + for (int i = 1; i < array.length - 1; i++) { + array[i] = 1; // Bounds check can be eliminated. + } + + for (int i = -1; i < array.length; i++) { + array[i] = 1; // Bounds check can't be eliminated. + } + + for (int i = 0; i <= array.length; i++) { + array[i] = 1; // Bounds check can't be eliminated. + } + + for (int i = 0; i < array.length; i += 2) { + // We don't have any assumption on max array length yet. + // Bounds check can't be eliminated due to overflow concern. + array[i] = 1; + } + + for (int i = 1; i < array.length; i += 2) { + // Bounds check can be eliminated since i is odd so the last + // i that's less than array.length is at most (Integer.MAX_VALUE - 2). + array[i] = 1; + } + } + + + // CHECK-START: void Main.loopPattern2(int[]) BCE (before) + // CHECK: BoundsCheck + // CHECK: ArraySet + // CHECK: BoundsCheck + // CHECK: ArraySet + // CHECK: BoundsCheck + // CHECK: ArraySet + // CHECK: BoundsCheck + // CHECK: ArraySet + // CHECK: BoundsCheck + // CHECK: ArraySet + // CHECK: BoundsCheck + // CHECK: ArraySet + + // CHECK-START: void Main.loopPattern2(int[]) BCE (after) + // CHECK-NOT: BoundsCheck + // CHECK: ArraySet + // CHECK-NOT: BoundsCheck + // CHECK: ArraySet + // CHECK-NOT: BoundsCheck + // CHECK: ArraySet + // CHECK: BoundsCheck + // CHECK: ArraySet + // CHECK: BoundsCheck + // CHECK: ArraySet + // CHECK-NOT: BoundsCheck + // CHECK: ArraySet + + static void loopPattern2(int[] array) { + for (int i = array.length - 1; i >= 0; i--) { + array[i] = 1; // Bounds check can be eliminated. + } + + for (int i = array.length; i > 0; i--) { + array[i - 1] = 1; // Bounds check can be eliminated. + } + + for (int i = array.length - 1; i > 0; i--) { + array[i] = 1; // Bounds check can be eliminated. + } + + for (int i = array.length; i >= 0; i--) { + array[i] = 1; // Bounds check can't be eliminated. + } + + for (int i = array.length; i >= 0; i--) { + array[i - 1] = 1; // Bounds check can't be eliminated. + } + + for (int i = array.length; i > 0; i -= 20) { + // For i >= 0, (i - 20 - 1) is guaranteed not to underflow. + array[i - 1] = 1; // Bounds check can be eliminated. + } + } + + + // CHECK-START: void Main.loopPattern3(int[]) BCE (before) + // CHECK: BoundsCheck + // CHECK: ArraySet + + // CHECK-START: void Main.loopPattern3(int[]) BCE (after) + // CHECK: BoundsCheck + // CHECK: ArraySet + + static void loopPattern3(int[] array) { + java.util.Random random = new java.util.Random(); + for (int i = 0; ; i++) { + if (random.nextInt() % 1000 == 0 && i < array.length) { + // Can't eliminate the bound check since not every i++ is + // matched with a array length check, so there is some chance that i + // overflows and is negative. + array[i] = 1; + } } } + + // CHECK-START: void Main.constantNewArray() BCE (before) + // CHECK: BoundsCheck + // CHECK: ArraySet + // CHECK: BoundsCheck + // CHECK: ArraySet + // CHECK: BoundsCheck + // CHECK: ArraySet + // CHECK: BoundsCheck + // CHECK: ArraySet + // CHECK: BoundsCheck + // CHECK: ArraySet + + // CHECK-START: void Main.constantNewArray() BCE (after) + // CHECK-NOT: BoundsCheck + // CHECK: ArraySet + // CHECK: BoundsCheck + // CHECK: ArraySet + // CHECK-NOT: BoundsCheck + // CHECK: ArraySet + // CHECK-NOT: BoundsCheck + // CHECK: ArraySet + // CHECK: BoundsCheck + // CHECK: ArraySet + + static void constantNewArray() { + int[] array = new int[10]; + for (int i = 0; i < 10; i++) { + array[i] = 1; // Bounds check can be eliminated. + } + + for (int i = 0; i <= 10; i++) { + array[i] = 1; // Bounds check can't be eliminated. + } + + array[0] = 1; // Bounds check can be eliminated. + array[9] = 1; // Bounds check can be eliminated. + array[10] = 1; // Bounds check can't be eliminated. + } + + // CHECK-START: void Main.pyramid1(int[]) BCE (before) + // CHECK: BoundsCheck + // CHECK: ArraySet + // CHECK: BoundsCheck + // CHECK: ArraySet + + // CHECK-START: void Main.pyramid1(int[]) BCE (after) + // CHECK-NOT: BoundsCheck + // CHECK: ArraySet + // CHECK-NOT: BoundsCheck + // CHECK: ArraySet + + // Set array to something like {0, 1, 2, 3, 2, 1, 0}. + static void pyramid1(int[] array) { + for (int i = 0; i < (array.length + 1) / 2; i++) { + array[i] = i; + array[array.length - 1 - i] = i; + } + } + + + // CHECK-START: void Main.pyramid2(int[]) BCE (before) + // CHECK: BoundsCheck + // CHECK: ArraySet + // CHECK: BoundsCheck + // CHECK: ArraySet + + // CHECK-START: void Main.pyramid2(int[]) BCE (after) + // CHECK-NOT: BoundsCheck + // CHECK: ArraySet + // CHECK-NOT: BoundsCheck + // CHECK: ArraySet + + // Set array to something like {0, 1, 2, 3, 2, 1, 0}. + static void pyramid2(int[] array) { + for (int i = 0; i < (array.length + 1) >> 1; i++) { + array[i] = i; + array[array.length - 1 - i] = i; + } + } + + + // CHECK-START: void Main.pyramid3(int[]) BCE (before) + // CHECK: BoundsCheck + // CHECK: ArraySet + // CHECK: BoundsCheck + // CHECK: ArraySet + + // CHECK-START: void Main.pyramid3(int[]) BCE (after) + // CHECK-NOT: BoundsCheck + // CHECK: ArraySet + // CHECK-NOT: BoundsCheck + // CHECK: ArraySet + + // Set array to something like {0, 1, 2, 3, 2, 1, 0}. + static void pyramid3(int[] array) { + for (int i = 0; i < (array.length + 1) >>> 1; i++) { + array[i] = i; + array[array.length - 1 - i] = i; + } + } + + + // TODO: bce on the array accesses in this method. + static boolean isPyramid(int[] array) { + int i = 0; + int j = array.length - 1; + while (i <= j) { + if (array[i] != i) { + return false; + } + if (array[j] != i) { + return false; + } + i++; j--; + } + return true; + } + + + // CHECK-START: void Main.bubbleSort(int[]) GVN (before) + // CHECK: BoundsCheck + // CHECK: ArrayGet + // CHECK: BoundsCheck + // CHECK: ArrayGet + // CHECK: BoundsCheck + // CHECK: ArrayGet + // CHECK: BoundsCheck + // CHECK: ArrayGet + // CHECK: BoundsCheck + // CHECK: ArraySet + // CHECK: BoundsCheck + // CHECK: ArraySet + + // CHECK-START: void Main.bubbleSort(int[]) GVN (after) + // CHECK: BoundsCheck + // CHECK: ArrayGet + // CHECK: BoundsCheck + // CHECK: ArrayGet + // CHECK-NOT: ArrayGet + // CHECK-NOT: ArrayGet + // CHECK-NOT: BoundsCheck + // CHECK: ArraySet + // CHECK-NOT: BoundsCheck + // CHECK: ArraySet + + // CHECK-START: void Main.bubbleSort(int[]) BCE (after) + // CHECK-NOT: BoundsCheck + // CHECK: ArrayGet + // CHECK-NOT: BoundsCheck + // CHECK: ArrayGet + // CHECK-NOT: ArrayGet + // CHECK-NOT: ArrayGet + // CHECK-NOT: BoundsCheck + // CHECK: ArraySet + // CHECK-NOT: BoundsCheck + // CHECK: ArraySet + + static void bubbleSort(int[] array) { + for (int i = 0; i < array.length - 1; i++) { + for (int j = 0; j < array.length - i - 1; j++) { + if (array[j] > array[j + 1]) { + int temp = array[j + 1]; + array[j + 1] = array[j]; + array[j] = temp; + } + } + } + } + + public static void main(String[] args) { sieve(20); + + int[] array = {5, 2, 3, 7, 0, 1, 6, 4}; + bubbleSort(array); + for (int i = 0; i < 8; i++) { + if (array[i] != i) { + System.out.println("bubble sort failed!"); + } + } + + array = new int[7]; + pyramid1(array); + if (!isPyramid(array)) { + System.out.println("pyramid1 failed!"); + } + + array = new int[8]; + pyramid2(array); + if (!isPyramid(array)) { + System.out.println("pyramid2 failed!"); + } + + java.util.Arrays.fill(array, -1); + pyramid3(array); + if (!isPyramid(array)) { + System.out.println("pyramid3 failed!"); + } } } diff --git a/test/451-regression-add-float/expected.txt b/test/451-regression-add-float/expected.txt new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/test/451-regression-add-float/expected.txt diff --git a/test/451-regression-add-float/info.txt b/test/451-regression-add-float/info.txt new file mode 100644 index 0000000000..83a5f9d73b --- /dev/null +++ b/test/451-regression-add-float/info.txt @@ -0,0 +1,2 @@ +Tests a regression in float addition for optimizing. The second argument +could be now be a constant for floating point numbers. diff --git a/test/451-regression-add-float/src/Main.java b/test/451-regression-add-float/src/Main.java new file mode 100644 index 0000000000..0d4bf065ea --- /dev/null +++ b/test/451-regression-add-float/src/Main.java @@ -0,0 +1,72 @@ +/* +* Copyright (C) 2015 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. +*/ + + +public class Main { + + public static void main(String[] args) { + assertEqual(4, add3(1)); + assertEqual(4l, add3(1l)); + assertEqual(4f, add3(1f)); + assertEqual(4d, add3(1d)); + } + + public static int add3(int a) { + return 1 + a + 2; + } + + public static long add3(long a) { + return 1l + a + 2l; + } + + public static float add3(float a) { + return 1f + a + 2f; + } + + public static double add3(double a) { + return 1d + a + 2d; + } + + public static void assertEqual(int a, int b) { + if (a != b) { + throw new RuntimeException("Expected: " + a + " Found: " + b); + } + } + + public static void assertEqual(long a, long b) { + if (a != b) { + throw new RuntimeException("Expected: " + a + " Found: " + b); + } + } + + public static void assertEqual(float a, float b) { + boolean aproxEquals = (a > b) + ? ((a - b) < 0.0001f) + : ((b - a) < 0.0001f); + if (!aproxEquals) { + throw new RuntimeException("Expected: " + a + " Found: " + b); + } + } + + public static void assertEqual(double a, double b) { + boolean aproxEquals = (a > b) + ? ((a - b) < 0.0001d) + : ((b - a) < 0.0001d); + if (!aproxEquals) { + throw new RuntimeException("Expected: " + a + " Found: " + b); + } + } +} diff --git a/test/451-spill-splot/expected.txt b/test/451-spill-splot/expected.txt new file mode 100644 index 0000000000..efc3f2e7bd --- /dev/null +++ b/test/451-spill-splot/expected.txt @@ -0,0 +1,6 @@ +85.0 +45.0 +20.0 +56.0 +20.0 +20.0 diff --git a/test/451-spill-splot/info.txt b/test/451-spill-splot/info.txt new file mode 100644 index 0000000000..1772ce7c00 --- /dev/null +++ b/test/451-spill-splot/info.txt @@ -0,0 +1,2 @@ +Regression test for the optimizing compiler and the +way it spills intervals of different types. diff --git a/test/451-spill-splot/src/Main.java b/test/451-spill-splot/src/Main.java new file mode 100644 index 0000000000..f631ebdd6d --- /dev/null +++ b/test/451-spill-splot/src/Main.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2015 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. + */ + +public class Main { + public static void main(String[] args) { + // Create a few local variables to make sure some get spilled, and we get + // a conflict of swapping a single entry stack slot (float) with a double entry + // stack slot (double). + double a = 0.0; + double b = 1.0; + double c = 2.0; + double d = 3.0; + double e = 4.0; + double f = 5.0; + double g = 6.0; + double h = 7.0; + double i = 8.0; + double j = 9.0; + + float aa = 0; + float bb = 1; + float cc = 2; + float dd = 3; + float ee = 4; + float ff = 5; + float gg = 6; + float hh = 7; + float ii = 8; + float jj = 9; + float kk = 10; + float ll = 10; + float mm = 10; + float nn = 10; + + for (int count = 0; count < 2; count++) { + System.out.println(aa + bb + cc + dd + ee + ff + gg + hh + ii + jj + kk + ll + mm + nn); + System.out.println(a + b + c + d + e + f + g + h + i + j); + a = computeDouble(); + b = computeDouble(); + c = computeDouble(); + d = computeDouble(); + e = computeDouble(); + f = computeDouble(); + g = computeDouble(); + h = computeDouble(); + i = computeDouble(); + j = computeDouble(); + System.out.println(a + b + c + d + e + f + g + h + i + j); + aa = computeFloat(); + bb = computeFloat(); + cc = computeFloat(); + dd = computeFloat(); + ee = computeFloat(); + ff = computeFloat(); + gg = computeFloat(); + hh = computeFloat(); + ii = computeFloat(); + jj = computeFloat(); + kk = computeFloat(); + ll = computeFloat(); + mm = computeFloat(); + nn = computeFloat(); + } + } + + static boolean doThrow = false; + + public static double computeDouble() { + if (doThrow) { + // Try defeating inlining. + throw new Error(); + } + return 2.0; + } + + public static float computeFloat() { + if (doThrow) { + // Try defeating inlining. + throw new Error(); + } + return 4.0f; + } +} diff --git a/test/Android.run-test.mk b/test/Android.run-test.mk index c8e0ec5216..8e4b46b10c 100644 --- a/test/Android.run-test.mk +++ b/test/Android.run-test.mk @@ -181,7 +181,8 @@ TEST_ART_TIMING_SENSITIVE_RUN_TESTS := # Note 116-nodex2oat is not broken per-se it just doesn't (and isn't meant to) work with --prebuild. TEST_ART_BROKEN_PREBUILD_RUN_TESTS := \ 116-nodex2oat \ - 118-noimage-dex2oat + 118-noimage-dex2oat \ + 134-nodex2oat-nofallback ifneq (,$(filter prebuild,$(PREBUILD_TYPES))) ART_TEST_KNOWN_BROKEN += $(call all-run-test-names,$(TARGET_TYPES),$(RUN_TYPES),prebuild, \ |