From 3e18738bd338e9f8363b26bc895f38c0ec682824 Mon Sep 17 00:00:00 2001 From: David Brazdil Date: Fri, 26 Jun 2015 09:59:52 +0000 Subject: [PATCH] Revert "ART: Implement try/catch blocks in Builder" Causes OutOfMemory issues, need to investigate. This reverts commit 0b5c7d1994b76090afcc825e737f2b8c546da2f8. Change-Id: I263e6cc4df5f9a56ad2ce44e18932ca51d7e349f --- compiler/optimizing/builder.cc | 249 ++----- compiler/optimizing/builder.h | 8 +- compiler/optimizing/code_generator.cc | 6 +- compiler/optimizing/code_generator_arm.cc | 28 +- compiler/optimizing/code_generator_arm.h | 1 - compiler/optimizing/code_generator_arm64.cc | 26 +- compiler/optimizing/code_generator_arm64.h | 2 +- compiler/optimizing/code_generator_mips64.cc | 26 +- compiler/optimizing/code_generator_mips64.h | 1 - compiler/optimizing/code_generator_x86.cc | 26 +- compiler/optimizing/code_generator_x86.h | 1 - compiler/optimizing/code_generator_x86_64.cc | 26 +- compiler/optimizing/code_generator_x86_64.h | 1 - compiler/optimizing/graph_checker.cc | 21 +- compiler/optimizing/graph_checker.h | 4 - compiler/optimizing/graph_visualizer.cc | 37 +- compiler/optimizing/nodes.cc | 66 +- compiler/optimizing/nodes.h | 108 +-- compiler/utils/growable_array.h | 4 +- test/510-checker-try-catch/expected.txt | 0 test/510-checker-try-catch/info.txt | 1 - test/510-checker-try-catch/smali/Builder.smali | 914 ------------------------- test/510-checker-try-catch/src/Main.java | 23 - 23 files changed, 100 insertions(+), 1479 deletions(-) delete mode 100644 test/510-checker-try-catch/expected.txt delete mode 100644 test/510-checker-try-catch/info.txt delete mode 100644 test/510-checker-try-catch/smali/Builder.smali delete mode 100644 test/510-checker-try-catch/src/Main.java diff --git a/compiler/optimizing/builder.cc b/compiler/optimizing/builder.cc index 742429cc5..e527e8bc7 100644 --- a/compiler/optimizing/builder.cc +++ b/compiler/optimizing/builder.cc @@ -259,165 +259,6 @@ bool HGraphBuilder::SkipCompilation(const DexFile::CodeItem& code_item, return false; } -bool HGraphBuilder::IsBlockInPcRange(HBasicBlock* block, - uint32_t dex_pc_start, - uint32_t dex_pc_end) { - uint32_t dex_pc = block->GetDexPc(); - return block != entry_block_ - && block != exit_block_ - && dex_pc >= dex_pc_start - && dex_pc < dex_pc_end; -} - -void HGraphBuilder::CreateBlocksForTryCatch(const DexFile::CodeItem& code_item) { - if (code_item.tries_size_ == 0) { - return; - } - - // Create branch targets at the start/end of the TryItem range. These are - // places where the program might fall through into/out of the a block and - // where TryBoundary instructions will be inserted later. Other edges which - // enter/exit the try blocks are a result of branches/switches. - for (size_t idx = 0; idx < code_item.tries_size_; ++idx) { - const DexFile::TryItem* try_item = DexFile::GetTryItems(code_item, idx); - uint32_t dex_pc_start = try_item->start_addr_; - uint32_t dex_pc_end = dex_pc_start + try_item->insn_count_; - FindOrCreateBlockStartingAt(dex_pc_start); - if (dex_pc_end < code_item.insns_size_in_code_units_) { - // TODO: Do not create block if the last instruction cannot fall through. - FindOrCreateBlockStartingAt(dex_pc_end); - } else { - // The TryItem spans until the very end of the CodeItem (or beyond if - // invalid) and therefore cannot have any code afterwards. - } - } - - // Create branch targets for exception handlers. - const uint8_t* handlers_ptr = DexFile::GetCatchHandlerData(code_item, 0); - uint32_t handlers_size = DecodeUnsignedLeb128(&handlers_ptr); - for (uint32_t idx = 0; idx < handlers_size; ++idx) { - CatchHandlerIterator iterator(handlers_ptr); - for (; iterator.HasNext(); iterator.Next()) { - uint32_t address = iterator.GetHandlerAddress(); - HBasicBlock* block = FindOrCreateBlockStartingAt(address); - block->SetIsCatchBlock(); - } - handlers_ptr = iterator.EndDataPointer(); - } -} - -void HGraphBuilder::InsertTryBoundaryBlocks(const DexFile::CodeItem& code_item) { - if (code_item.tries_size_ == 0) { - return; - } - - for (size_t idx = 0; idx < code_item.tries_size_; ++idx) { - const DexFile::TryItem* try_item = DexFile::GetTryItems(code_item, idx); - uint32_t try_start = try_item->start_addr_; - uint32_t try_end = try_start + try_item->insn_count_; - - // Iterate over all blocks in the dex pc range of the TryItem and: - // (a) split edges which enter/exit the try range, - // (b) create TryBoundary instructions in the new blocks, - // (c) link the new blocks to corresponding exception handlers. - for (uint32_t inner_pc = try_start; inner_pc < try_end; ++inner_pc) { - HBasicBlock* try_block = FindBlockStartingAt(inner_pc); - if (try_block == nullptr) { - continue; - } - - // Find predecessors which are not covered by the same TryItem range. Such - // edges enter the try block and will have a TryBoundary inserted. - for (size_t i = 0; i < try_block->GetPredecessors().Size(); ++i) { - HBasicBlock* predecessor = try_block->GetPredecessors().Get(i); - HTryBoundary* try_boundary = nullptr; - if (predecessor->IsSingleTryBoundary()) { - try_boundary = predecessor->GetLastInstruction()->AsTryBoundary(); - if (try_boundary->GetNormalFlowSuccessor() == try_block - && try_block->IsFirstIndexOfPredecessor(predecessor, i)) { - // The edge was already split because of an exit from a neighbouring - // TryItem and `predecessor` is the block with a TryBoundary created - // between the two original blocks. We do not split the edge again. - DCHECK(!IsBlockInPcRange(predecessor->GetSinglePredecessor(), try_start, try_end)); - DCHECK(try_boundary->IsTryExit()); - DCHECK(!try_boundary->IsTryEntry()); - try_boundary->SetIsTryEntry(); - } else { - // This is an edge between a previously created TryBoundary and its - // handler. We skip it because it is exceptional flow. - DCHECK(try_block->IsCatchBlock()); - DCHECK(try_boundary->HasExceptionHandler(try_block)); - continue; - } - } else if (!IsBlockInPcRange(predecessor, try_start, try_end)) { - // This is an entry point into the TryItem and the edge has not been - // split yet. That means that either `predecessor` is not in a TryItem, - // or it is in a different TryItem and we happened to iterate over - // this block first. We split the edge and `predecessor` may add its - // own exception handlers later. - try_boundary = new (arena_) HTryBoundary(/* is_entry */ true, /* is_exit */ false); - HBasicBlock* try_entry_block = graph_->SplitEdge(predecessor, try_block); - try_entry_block->AddInstruction(try_boundary); - } else { - // Not an edge on the boundary of the try block. - continue; - } - DCHECK(try_boundary != nullptr); - - // Link the TryBoundary block to the handlers of this TryItem. - for (CatchHandlerIterator it(code_item, *try_item); it.HasNext(); it.Next()) { - try_boundary->AddExceptionHandler(FindBlockStartingAt(it.GetHandlerAddress())); - } - } - - // Find successors which are not covered by the same TryItem range. Such - // edges exit the try block and will have a TryBoundary inserted. - for (size_t i = 0; i < try_block->GetSuccessors().Size(); ++i) { - HBasicBlock* successor = try_block->GetSuccessors().Get(i); - HTryBoundary* try_boundary = nullptr; - if (successor->IsSingleTryBoundary()) { - // The edge was already split because of an entry into a neighbouring - // TryItem. We do not split the edge again. - try_boundary = successor->GetLastInstruction()->AsTryBoundary(); - DCHECK_EQ(try_block, successor->GetSinglePredecessor()); - DCHECK(try_boundary->IsTryEntry()); - DCHECK(!try_boundary->IsTryExit()); - DCHECK(!IsBlockInPcRange(try_boundary->GetNormalFlowSuccessor(), try_start, try_end)); - try_boundary->SetIsTryExit(); - } else if (!IsBlockInPcRange(successor, try_start, try_end)) { - // This is an exit out of the TryItem and the edge has not been split - // yet. That means that either `successor` is not in a TryItem, or it - // is in a different TryItem and we happened to iterate over this - // block first. We split the edge and `successor` may add its own - // exception handlers later. - HInstruction* last_instruction = try_block->GetLastInstruction(); - if (last_instruction->IsReturn() || last_instruction->IsReturnVoid()) { - DCHECK_EQ(successor, exit_block_); - // Control flow exits the try block with a Return(Void). Because - // splitting the edge would invalidate the invariant that Return - // always jumps to Exit, we move the Return outside the try block. - HBasicBlock* return_block = try_block->SplitBefore(last_instruction); - graph_->AddBlock(return_block); - successor = return_block; - } - try_boundary = new (arena_) HTryBoundary(/* is_entry */ false, /* is_exit */ true); - HBasicBlock* try_exit_block = graph_->SplitEdge(try_block, successor); - try_exit_block->AddInstruction(try_boundary); - } else { - // Not an edge on the boundary of the try block. - continue; - } - DCHECK(try_boundary != nullptr); - - // Link the TryBoundary block to the handlers of this TryItem. - for (CatchHandlerIterator it(code_item, *try_item); it.HasNext(); it.Next()) { - try_boundary->AddExceptionHandler(FindBlockStartingAt(it.GetHandlerAddress())); - } - } - } - } -} - bool HGraphBuilder::BuildGraph(const DexFile::CodeItem& code_item) { DCHECK(graph_->GetBlocks().IsEmpty()); @@ -451,7 +292,24 @@ bool HGraphBuilder::BuildGraph(const DexFile::CodeItem& code_item) { return false; } - CreateBlocksForTryCatch(code_item); + // Also create blocks for catch handlers. + if (code_item.tries_size_ != 0) { + const uint8_t* handlers_ptr = DexFile::GetCatchHandlerData(code_item, 0); + uint32_t handlers_size = DecodeUnsignedLeb128(&handlers_ptr); + for (uint32_t idx = 0; idx < handlers_size; ++idx) { + CatchHandlerIterator iterator(handlers_ptr); + for (; iterator.HasNext(); iterator.Next()) { + uint32_t address = iterator.GetHandlerAddress(); + HBasicBlock* block = FindBlockStartingAt(address); + if (block == nullptr) { + block = new (arena_) HBasicBlock(graph_, address); + branch_targets_.Put(address, block); + } + block->SetIsCatchBlock(); + } + handlers_ptr = iterator.EndDataPointer(); + } + } InitializeParameters(code_item.ins_size_); @@ -467,24 +325,18 @@ bool HGraphBuilder::BuildGraph(const DexFile::CodeItem& code_item) { code_ptr += instruction.SizeInCodeUnits(); } + // Add the exit block at the end to give it the highest id. + graph_->AddBlock(exit_block_); + exit_block_->AddInstruction(new (arena_) HExit()); // Add the suspend check to the entry block. entry_block_->AddInstruction(new (arena_) HSuspendCheck(0)); entry_block_->AddInstruction(new (arena_) HGoto()); - // Add Exit to the exit block. - exit_block_->AddInstruction(new (arena_) HExit()); - - // Iterate over blocks covered by TryItems and insert TryBoundaries at entry - // and exit points. This requires all control-flow instructions and - // non-exceptional edges to have been created. - InsertTryBoundaryBlocks(code_item); - // Add the exit block at the end to give it the highest id. - graph_->AddBlock(exit_block_); return true; } -void HGraphBuilder::MaybeUpdateCurrentBlock(size_t dex_pc) { - HBasicBlock* block = FindBlockStartingAt(dex_pc); +void HGraphBuilder::MaybeUpdateCurrentBlock(size_t index) { + HBasicBlock* block = FindBlockStartingAt(index); if (block == nullptr) { return; } @@ -519,8 +371,10 @@ bool HGraphBuilder::ComputeBranchTargets(const uint16_t* code_ptr, (*number_of_branches)++; int32_t target = instruction.GetTargetOffset() + dex_pc; // Create a block for the target instruction. - FindOrCreateBlockStartingAt(target); - + if (FindBlockStartingAt(target) == nullptr) { + block = new (arena_) HBasicBlock(graph_, target); + branch_targets_.Put(target, block); + } dex_pc += instruction.SizeInCodeUnits(); code_ptr += instruction.SizeInCodeUnits(); @@ -529,8 +383,9 @@ bool HGraphBuilder::ComputeBranchTargets(const uint16_t* code_ptr, // In the normal case we should never hit this but someone can artificially forge a dex // file to fall-through out the method code. In this case we bail out compilation. return false; - } else { - FindOrCreateBlockStartingAt(dex_pc); + } else if (FindBlockStartingAt(dex_pc) == nullptr) { + block = new (arena_) HBasicBlock(graph_, dex_pc); + branch_targets_.Put(dex_pc, block); } } } else if (instruction.IsSwitch()) { @@ -546,7 +401,10 @@ bool HGraphBuilder::ComputeBranchTargets(const uint16_t* code_ptr, for (size_t i = 0; i < num_entries; ++i) { // The target of the case. uint32_t target = dex_pc + table.GetEntryAt(i + offset); - FindOrCreateBlockStartingAt(target); + if (FindBlockStartingAt(target) == nullptr) { + block = new (arena_) HBasicBlock(graph_, target); + branch_targets_.Put(target, block); + } // The next case gets its own block. if (i < num_entries) { @@ -563,8 +421,9 @@ bool HGraphBuilder::ComputeBranchTargets(const uint16_t* code_ptr, // file to fall-through out the method code. In this case we bail out compilation. // (A switch can fall-through so we don't need to check CanFlowThrough().) return false; - } else { - FindOrCreateBlockStartingAt(dex_pc); + } else if (FindBlockStartingAt(dex_pc) == nullptr) { + block = new (arena_) HBasicBlock(graph_, dex_pc); + branch_targets_.Put(dex_pc, block); } } else { code_ptr += instruction.SizeInCodeUnits(); @@ -574,19 +433,9 @@ bool HGraphBuilder::ComputeBranchTargets(const uint16_t* code_ptr, return true; } -HBasicBlock* HGraphBuilder::FindBlockStartingAt(int32_t dex_pc) const { - DCHECK_GE(dex_pc, 0); - DCHECK_LT(static_cast(dex_pc), branch_targets_.Size()); - return branch_targets_.Get(dex_pc); -} - -HBasicBlock* HGraphBuilder::FindOrCreateBlockStartingAt(int32_t dex_pc) { - HBasicBlock* block = FindBlockStartingAt(dex_pc); - if (block == nullptr) { - block = new (arena_) HBasicBlock(graph_, dex_pc); - branch_targets_.Put(dex_pc, block); - } - return block; +HBasicBlock* HGraphBuilder::FindBlockStartingAt(int32_t index) const { + DCHECK_GE(index, 0); + return branch_targets_.Get(index); } template @@ -2259,29 +2108,15 @@ bool HGraphBuilder::AnalyzeDexInstruction(const Instruction& instruction, uint32 case Instruction::MOVE_RESULT: case Instruction::MOVE_RESULT_WIDE: - case Instruction::MOVE_RESULT_OBJECT: { + case Instruction::MOVE_RESULT_OBJECT: if (latest_result_ == nullptr) { // Only dead code can lead to this situation, where the verifier // does not reject the method. } else { - // An Invoke/FilledNewArray and its MoveResult could have landed in - // different blocks if there was a try/catch block boundary between - // them. We need to insert the StoreLocal after the result definition - // but before any potential Temporaries. - HStoreLocal* update_local = - new (arena_) HStoreLocal(GetLocalAt(instruction.VRegA()), latest_result_); - HBasicBlock* block = latest_result_->GetBlock(); - if (latest_result_->IsInvoke()) { - block->InsertInstructionAfter(update_local, latest_result_); - } else { - DCHECK(latest_result_->IsNewArray()); - DCHECK(latest_result_->GetNext()->IsTemporary()); - block->InsertInstructionAfter(update_local, latest_result_->GetNext()); - } + UpdateLocal(instruction.VRegA(), latest_result_); latest_result_ = nullptr; } break; - } case Instruction::CMP_LONG: { Binop_23x_cmp(instruction, Primitive::kPrimLong, HCompare::kNoBias, dex_pc); diff --git a/compiler/optimizing/builder.h b/compiler/optimizing/builder.h index 58d85e9ef..052aaf8b4 100644 --- a/compiler/optimizing/builder.h +++ b/compiler/optimizing/builder.h @@ -94,12 +94,8 @@ class HGraphBuilder : public ValueObject { bool ComputeBranchTargets(const uint16_t* start, const uint16_t* end, size_t* number_of_branches); - void MaybeUpdateCurrentBlock(size_t dex_pc); - HBasicBlock* FindBlockStartingAt(int32_t dex_pc) const; - HBasicBlock* FindOrCreateBlockStartingAt(int32_t dex_pc); - bool IsBlockInPcRange(HBasicBlock* block, uint32_t dex_pc_start, uint32_t dex_pc_end); - void CreateBlocksForTryCatch(const DexFile::CodeItem& code_item); - void InsertTryBoundaryBlocks(const DexFile::CodeItem& code_item); + void MaybeUpdateCurrentBlock(size_t index); + HBasicBlock* FindBlockStartingAt(int32_t index) const; void InitializeLocals(uint16_t count); HLocal* GetLocalAt(int register_index) const; diff --git a/compiler/optimizing/code_generator.cc b/compiler/optimizing/code_generator.cc index 4607ebe54..cd1093580 100644 --- a/compiler/optimizing/code_generator.cc +++ b/compiler/optimizing/code_generator.cc @@ -146,7 +146,7 @@ bool CodeGenerator::GoesToNextBlock(HBasicBlock* current, HBasicBlock* next) con HBasicBlock* CodeGenerator::GetNextBlockToEmit() const { for (size_t i = current_block_index_ + 1; i < block_order_->Size(); ++i) { HBasicBlock* block = block_order_->Get(i); - if (!block->IsSingleJump()) { + if (!block->IsSingleGoto()) { return block; } } @@ -154,7 +154,7 @@ HBasicBlock* CodeGenerator::GetNextBlockToEmit() const { } HBasicBlock* CodeGenerator::FirstNonEmptyBlock(HBasicBlock* block) const { - while (block->IsSingleJump()) { + while (block->IsSingleGoto()) { block = block->GetSuccessors().Get(0); } return block; @@ -214,7 +214,7 @@ void CodeGenerator::CompileInternal(CodeAllocator* allocator, bool is_baseline) // Don't generate code for an empty block. Its predecessors will branch to its successor // directly. Also, the label of that block will not be emitted, so this helps catch // errors where we reference that label. - if (block->IsSingleJump()) continue; + if (block->IsSingleGoto()) continue; Bind(block); for (HInstructionIterator it(block->GetInstructions()); !it.Done(); it.Advance()) { HInstruction* current = it.Current(); diff --git a/compiler/optimizing/code_generator_arm.cc b/compiler/optimizing/code_generator_arm.cc index ff9373ab0..9abe2e7be 100644 --- a/compiler/optimizing/code_generator_arm.cc +++ b/compiler/optimizing/code_generator_arm.cc @@ -431,7 +431,7 @@ void CodeGeneratorARM::Finalize(CodeAllocator* allocator) { // FirstNonEmptyBlock() which could lead to adjusting a label more than once. DCHECK_LT(static_cast(block->GetBlockId()), block_labels_.Size()); Label* block_label = &block_labels_.GetRawStorage()[block->GetBlockId()]; - DCHECK_EQ(block_label->IsBound(), !block->IsSingleJump()); + DCHECK_EQ(block_label->IsBound(), !block->IsSingleGoto()); if (block_label->IsBound()) { __ AdjustLabelPosition(block_label); } @@ -962,7 +962,12 @@ void CodeGeneratorARM::InvokeRuntime(int32_t entry_point_offset, || !IsLeafMethod()); } -void InstructionCodeGeneratorARM::HandleGoto(HInstruction* got, HBasicBlock* successor) { +void LocationsBuilderARM::VisitGoto(HGoto* got) { + got->SetLocations(nullptr); +} + +void InstructionCodeGeneratorARM::VisitGoto(HGoto* got) { + HBasicBlock* successor = got->GetSuccessor(); DCHECK(!successor->IsExitBlock()); HBasicBlock* block = got->GetBlock(); @@ -983,25 +988,6 @@ void InstructionCodeGeneratorARM::HandleGoto(HInstruction* got, HBasicBlock* suc } } -void LocationsBuilderARM::VisitGoto(HGoto* got) { - got->SetLocations(nullptr); -} - -void InstructionCodeGeneratorARM::VisitGoto(HGoto* got) { - HandleGoto(got, got->GetSuccessor()); -} - -void LocationsBuilderARM::VisitTryBoundary(HTryBoundary* try_boundary) { - try_boundary->SetLocations(nullptr); -} - -void InstructionCodeGeneratorARM::VisitTryBoundary(HTryBoundary* try_boundary) { - HBasicBlock* successor = try_boundary->GetNormalFlowSuccessor(); - if (!successor->IsExitBlock()) { - HandleGoto(try_boundary, successor); - } -} - void LocationsBuilderARM::VisitExit(HExit* exit) { exit->SetLocations(nullptr); } diff --git a/compiler/optimizing/code_generator_arm.h b/compiler/optimizing/code_generator_arm.h index a96ecbd43..953e733c4 100644 --- a/compiler/optimizing/code_generator_arm.h +++ b/compiler/optimizing/code_generator_arm.h @@ -211,7 +211,6 @@ class InstructionCodeGeneratorARM : public HGraphVisitor { void DivRemByPowerOfTwo(HBinaryOperation* instruction); void GenerateDivRemWithAnyConstant(HBinaryOperation* instruction); void GenerateDivRemConstantIntegral(HBinaryOperation* instruction); - void HandleGoto(HInstruction* got, HBasicBlock* successor); ArmAssembler* const assembler_; CodeGeneratorARM* const codegen_; diff --git a/compiler/optimizing/code_generator_arm64.cc b/compiler/optimizing/code_generator_arm64.cc index 9b7124d33..7ec6b5428 100644 --- a/compiler/optimizing/code_generator_arm64.cc +++ b/compiler/optimizing/code_generator_arm64.cc @@ -1971,7 +1971,12 @@ void InstructionCodeGeneratorARM64::VisitFloatConstant(HFloatConstant* constant) // Will be generated at use site. } -void InstructionCodeGeneratorARM64::HandleGoto(HInstruction* got, HBasicBlock* successor) { +void LocationsBuilderARM64::VisitGoto(HGoto* got) { + got->SetLocations(nullptr); +} + +void InstructionCodeGeneratorARM64::VisitGoto(HGoto* got) { + HBasicBlock* successor = got->GetSuccessor(); DCHECK(!successor->IsExitBlock()); HBasicBlock* block = got->GetBlock(); HInstruction* previous = got->GetPrevious(); @@ -1990,25 +1995,6 @@ void InstructionCodeGeneratorARM64::HandleGoto(HInstruction* got, HBasicBlock* s } } -void LocationsBuilderARM64::VisitGoto(HGoto* got) { - got->SetLocations(nullptr); -} - -void InstructionCodeGeneratorARM64::VisitGoto(HGoto* got) { - HandleGoto(got, got->GetSuccessor()); -} - -void LocationsBuilderARM64::VisitTryBoundary(HTryBoundary* try_boundary) { - try_boundary->SetLocations(nullptr); -} - -void InstructionCodeGeneratorARM64::VisitTryBoundary(HTryBoundary* try_boundary) { - HBasicBlock* successor = try_boundary->GetNormalFlowSuccessor(); - if (!successor->IsExitBlock()) { - HandleGoto(try_boundary, successor); - } -} - void InstructionCodeGeneratorARM64::GenerateTestAndBranch(HInstruction* instruction, vixl::Label* true_target, vixl::Label* false_target, diff --git a/compiler/optimizing/code_generator_arm64.h b/compiler/optimizing/code_generator_arm64.h index 2c610380e..bbe3adc76 100644 --- a/compiler/optimizing/code_generator_arm64.h +++ b/compiler/optimizing/code_generator_arm64.h @@ -181,7 +181,7 @@ class InstructionCodeGeneratorARM64 : public HGraphVisitor { void DivRemByPowerOfTwo(HBinaryOperation* instruction); void GenerateDivRemWithAnyConstant(HBinaryOperation* instruction); void GenerateDivRemIntegral(HBinaryOperation* instruction); - void HandleGoto(HInstruction* got, HBasicBlock* successor); + Arm64Assembler* const assembler_; CodeGeneratorARM64* const codegen_; diff --git a/compiler/optimizing/code_generator_mips64.cc b/compiler/optimizing/code_generator_mips64.cc index aa4fd2659..ab684d41d 100644 --- a/compiler/optimizing/code_generator_mips64.cc +++ b/compiler/optimizing/code_generator_mips64.cc @@ -1938,7 +1938,12 @@ void InstructionCodeGeneratorMIPS64::VisitFloatConstant(HFloatConstant* constant // Will be generated at use site. } -void InstructionCodeGeneratorMIPS64::HandleGoto(HInstruction* got, HBasicBlock* successor) { +void LocationsBuilderMIPS64::VisitGoto(HGoto* got) { + got->SetLocations(nullptr); +} + +void InstructionCodeGeneratorMIPS64::VisitGoto(HGoto* got) { + HBasicBlock* successor = got->GetSuccessor(); DCHECK(!successor->IsExitBlock()); HBasicBlock* block = got->GetBlock(); HInstruction* previous = got->GetPrevious(); @@ -1957,25 +1962,6 @@ void InstructionCodeGeneratorMIPS64::HandleGoto(HInstruction* got, HBasicBlock* } } -void LocationsBuilderMIPS64::VisitGoto(HGoto* got) { - got->SetLocations(nullptr); -} - -void InstructionCodeGeneratorMIPS64::VisitGoto(HGoto* got) { - HandleGoto(got, got->GetSuccessor()); -} - -void LocationsBuilderMIPS64::VisitTryBoundary(HTryBoundary* try_boundary) { - try_boundary->SetLocations(nullptr); -} - -void InstructionCodeGeneratorMIPS64::VisitTryBoundary(HTryBoundary* try_boundary) { - HBasicBlock* successor = try_boundary->GetNormalFlowSuccessor(); - if (!successor->IsExitBlock()) { - HandleGoto(try_boundary, successor); - } -} - void InstructionCodeGeneratorMIPS64::GenerateTestAndBranch(HInstruction* instruction, Label* true_target, Label* false_target, diff --git a/compiler/optimizing/code_generator_mips64.h b/compiler/optimizing/code_generator_mips64.h index c724a217d..ec36496a4 100644 --- a/compiler/optimizing/code_generator_mips64.h +++ b/compiler/optimizing/code_generator_mips64.h @@ -196,7 +196,6 @@ class InstructionCodeGeneratorMIPS64 : public HGraphVisitor { Label* true_target, Label* false_target, Label* always_true_target); - void HandleGoto(HInstruction* got, HBasicBlock* successor); Mips64Assembler* const assembler_; CodeGeneratorMIPS64* const codegen_; diff --git a/compiler/optimizing/code_generator_x86.cc b/compiler/optimizing/code_generator_x86.cc index 6c82fe99c..4d106c4fa 100644 --- a/compiler/optimizing/code_generator_x86.cc +++ b/compiler/optimizing/code_generator_x86.cc @@ -842,7 +842,12 @@ void CodeGeneratorX86::Move(HInstruction* instruction, Location location, HInstr } } -void InstructionCodeGeneratorX86::HandleGoto(HInstruction* got, HBasicBlock* successor) { +void LocationsBuilderX86::VisitGoto(HGoto* got) { + got->SetLocations(nullptr); +} + +void InstructionCodeGeneratorX86::VisitGoto(HGoto* got) { + HBasicBlock* successor = got->GetSuccessor(); DCHECK(!successor->IsExitBlock()); HBasicBlock* block = got->GetBlock(); @@ -862,25 +867,6 @@ void InstructionCodeGeneratorX86::HandleGoto(HInstruction* got, HBasicBlock* suc } } -void LocationsBuilderX86::VisitGoto(HGoto* got) { - got->SetLocations(nullptr); -} - -void InstructionCodeGeneratorX86::VisitGoto(HGoto* got) { - HandleGoto(got, got->GetSuccessor()); -} - -void LocationsBuilderX86::VisitTryBoundary(HTryBoundary* try_boundary) { - try_boundary->SetLocations(nullptr); -} - -void InstructionCodeGeneratorX86::VisitTryBoundary(HTryBoundary* try_boundary) { - HBasicBlock* successor = try_boundary->GetNormalFlowSuccessor(); - if (!successor->IsExitBlock()) { - HandleGoto(try_boundary, successor); - } -} - void LocationsBuilderX86::VisitExit(HExit* exit) { exit->SetLocations(nullptr); } diff --git a/compiler/optimizing/code_generator_x86.h b/compiler/optimizing/code_generator_x86.h index 623e83222..1ad89c9bf 100644 --- a/compiler/optimizing/code_generator_x86.h +++ b/compiler/optimizing/code_generator_x86.h @@ -201,7 +201,6 @@ class InstructionCodeGeneratorX86 : public HGraphVisitor { Label* true_target, Label* false_target, Label* always_true_target); - void HandleGoto(HInstruction* got, HBasicBlock* successor); X86Assembler* const assembler_; CodeGeneratorX86* const codegen_; diff --git a/compiler/optimizing/code_generator_x86_64.cc b/compiler/optimizing/code_generator_x86_64.cc index 22f5d9663..a8f57cc9a 100644 --- a/compiler/optimizing/code_generator_x86_64.cc +++ b/compiler/optimizing/code_generator_x86_64.cc @@ -786,7 +786,12 @@ void CodeGeneratorX86_64::Move(HInstruction* instruction, } } -void InstructionCodeGeneratorX86_64::HandleGoto(HInstruction* got, HBasicBlock* successor) { +void LocationsBuilderX86_64::VisitGoto(HGoto* got) { + got->SetLocations(nullptr); +} + +void InstructionCodeGeneratorX86_64::VisitGoto(HGoto* got) { + HBasicBlock* successor = got->GetSuccessor(); DCHECK(!successor->IsExitBlock()); HBasicBlock* block = got->GetBlock(); @@ -806,25 +811,6 @@ void InstructionCodeGeneratorX86_64::HandleGoto(HInstruction* got, HBasicBlock* } } -void LocationsBuilderX86_64::VisitGoto(HGoto* got) { - got->SetLocations(nullptr); -} - -void InstructionCodeGeneratorX86_64::VisitGoto(HGoto* got) { - HandleGoto(got, got->GetSuccessor()); -} - -void LocationsBuilderX86_64::VisitTryBoundary(HTryBoundary* try_boundary) { - try_boundary->SetLocations(nullptr); -} - -void InstructionCodeGeneratorX86_64::VisitTryBoundary(HTryBoundary* try_boundary) { - HBasicBlock* successor = try_boundary->GetNormalFlowSuccessor(); - if (!successor->IsExitBlock()) { - HandleGoto(try_boundary, successor); - } -} - void LocationsBuilderX86_64::VisitExit(HExit* exit) { exit->SetLocations(nullptr); } diff --git a/compiler/optimizing/code_generator_x86_64.h b/compiler/optimizing/code_generator_x86_64.h index c2aa56b63..a18e89a3e 100644 --- a/compiler/optimizing/code_generator_x86_64.h +++ b/compiler/optimizing/code_generator_x86_64.h @@ -202,7 +202,6 @@ class InstructionCodeGeneratorX86_64 : public HGraphVisitor { Label* true_target, Label* false_target, Label* always_true_target); - void HandleGoto(HInstruction* got, HBasicBlock* successor); X86_64Assembler* const assembler_; CodeGeneratorX86_64* const codegen_; diff --git a/compiler/optimizing/graph_checker.cc b/compiler/optimizing/graph_checker.cc index d7e6bd816..fd28f0b83 100644 --- a/compiler/optimizing/graph_checker.cc +++ b/compiler/optimizing/graph_checker.cc @@ -81,10 +81,7 @@ void GraphChecker::VisitBasicBlock(HBasicBlock* block) { } // Ensure `block` ends with a branch instruction. - // This invariant is not enforced on non-SSA graphs. Graph built from DEX with - // dead code that falls out of the method will not end with a control-flow - // instruction. Such code is removed during the SSA-building DCE phase. - if (GetGraph()->IsInSsaForm() && !block->EndsWithControlFlowInstruction()) { + if (!block->EndsWithControlFlowInstruction()) { AddError(StringPrintf("Block %d does not end with a branch instruction.", block->GetBlockId())); } @@ -256,22 +253,6 @@ void GraphChecker::VisitInvokeStaticOrDirect(HInvokeStaticOrDirect* invoke) { } } -void GraphChecker::VisitReturn(HReturn* ret) { - if (!ret->GetBlock()->GetSingleSuccessor()->IsExitBlock()) { - AddError(StringPrintf("%s:%d does not jump to the exit block.", - ret->DebugName(), - ret->GetId())); - } -} - -void GraphChecker::VisitReturnVoid(HReturnVoid* ret) { - if (!ret->GetBlock()->GetSingleSuccessor()->IsExitBlock()) { - AddError(StringPrintf("%s:%d does not jump to the exit block.", - ret->DebugName(), - ret->GetId())); - } -} - void SSAChecker::VisitBasicBlock(HBasicBlock* block) { super_type::VisitBasicBlock(block); diff --git a/compiler/optimizing/graph_checker.h b/compiler/optimizing/graph_checker.h index bafa69d42..b4314da03 100644 --- a/compiler/optimizing/graph_checker.h +++ b/compiler/optimizing/graph_checker.h @@ -48,10 +48,6 @@ class GraphChecker : public HGraphDelegateVisitor { // Check that the HasBoundsChecks() flag is set for bounds checks. void VisitBoundsCheck(HBoundsCheck* check) OVERRIDE; - // Check that the Return and ReturnVoid jump to the exit block. - void VisitReturn(HReturn* ret) OVERRIDE; - void VisitReturnVoid(HReturnVoid* ret) OVERRIDE; - // Was the last visit of the graph valid? bool IsValid() const { return errors_.empty(); diff --git a/compiler/optimizing/graph_visualizer.cc b/compiler/optimizing/graph_visualizer.cc index 30d61ef04..7d723ef13 100644 --- a/compiler/optimizing/graph_visualizer.cc +++ b/compiler/optimizing/graph_visualizer.cc @@ -252,22 +252,8 @@ class HGraphVisualizerPrinter : public HGraphVisitor { AddIndent(); output_ << "successors"; for (size_t i = 0, e = block->GetSuccessors().Size(); i < e; ++i) { - if (!block->IsExceptionalSuccessor(i)) { - HBasicBlock* successor = block->GetSuccessors().Get(i); - output_ << " \"B" << successor->GetBlockId() << "\" "; - } - } - output_<< std::endl; - } - - void PrintExceptionHandlers(HBasicBlock* block) { - AddIndent(); - output_ << "xhandlers"; - for (size_t i = 0, e = block->GetSuccessors().Size(); i < e; ++i) { - if (block->IsExceptionalSuccessor(i)) { - HBasicBlock* handler = block->GetSuccessors().Get(i); - output_ << " \"B" << handler->GetBlockId() << "\" "; - } + HBasicBlock* successor = block->GetSuccessors().Get(i); + output_ << " \"B" << successor->GetBlockId() << "\" "; } if (block->IsExitBlock() && (disasm_info_ != nullptr) && @@ -379,15 +365,6 @@ class HGraphVisualizerPrinter : public HGraphVisitor { << std::noboolalpha; } - void VisitTryBoundary(HTryBoundary* try_boundary) OVERRIDE { - StartAttributeStream("is_entry") << std::boolalpha - << try_boundary->IsTryEntry() - << std::noboolalpha; - StartAttributeStream("is_exit") << std::boolalpha - << try_boundary->IsTryExit() - << std::noboolalpha; - } - bool IsPass(const char* name) { return strcmp(pass_name_, name) == 0; } @@ -602,14 +579,8 @@ class HGraphVisualizerPrinter : public HGraphVisitor { } PrintPredecessors(block); PrintSuccessors(block); - PrintExceptionHandlers(block); - - if (block->IsCatchBlock()) { - PrintProperty("flags", "catch_block"); - } else { - PrintEmptyProperty("flags"); - } - + PrintEmptyProperty("xhandlers"); + PrintEmptyProperty("flags"); if (block->GetDominator() != nullptr) { PrintProperty("dominator", "B", block->GetDominator()->GetBlockId()); } diff --git a/compiler/optimizing/nodes.cc b/compiler/optimizing/nodes.cc index 881f9ec11..a6390af1f 100644 --- a/compiler/optimizing/nodes.cc +++ b/compiler/optimizing/nodes.cc @@ -189,20 +189,15 @@ void HGraph::TransformToSsa() { ssa_builder.BuildSsa(); } -HBasicBlock* HGraph::SplitEdge(HBasicBlock* block, HBasicBlock* successor) { +void HGraph::SplitCriticalEdge(HBasicBlock* block, HBasicBlock* successor) { + // Insert a new node between `block` and `successor` to split the + // critical edge. HBasicBlock* new_block = new (arena_) HBasicBlock(this, successor->GetDexPc()); AddBlock(new_block); + new_block->AddInstruction(new (arena_) HGoto()); // Use `InsertBetween` to ensure the predecessor index and successor index of // `block` and `successor` are preserved. new_block->InsertBetween(block, successor); - return new_block; -} - -void HGraph::SplitCriticalEdge(HBasicBlock* block, HBasicBlock* successor) { - // Insert a new node between `block` and `successor` to split the - // critical edge. - HBasicBlock* new_block = SplitEdge(block, successor); - new_block->AddInstruction(new (arena_) HGoto()); if (successor->IsLoopHeader()) { // If we split at a back edge boundary, make the new block the back edge. HLoopInformation* info = successor->GetLoopInformation(); @@ -1024,35 +1019,6 @@ void HInstruction::MoveBefore(HInstruction* cursor) { } } -HBasicBlock* HBasicBlock::SplitBefore(HInstruction* cursor) { - DCHECK(!graph_->IsInSsaForm()) << "Support for SSA form not implemented"; - DCHECK_EQ(cursor->GetBlock(), this); - - HBasicBlock* new_block = new (GetGraph()->GetArena()) HBasicBlock(GetGraph(), GetDexPc()); - new_block->instructions_.first_instruction_ = cursor; - new_block->instructions_.last_instruction_ = instructions_.last_instruction_; - instructions_.last_instruction_ = cursor->previous_; - if (cursor->previous_ == nullptr) { - instructions_.first_instruction_ = nullptr; - } else { - cursor->previous_->next_ = nullptr; - cursor->previous_ = nullptr; - } - - new_block->instructions_.SetBlockOfInstructions(new_block); - AddInstruction(new (GetGraph()->GetArena()) HGoto()); - - for (size_t i = 0, e = GetSuccessors().Size(); i < e; ++i) { - HBasicBlock* successor = GetSuccessors().Get(i); - new_block->successors_.Add(successor); - successor->predecessors_.Put(successor->GetPredecessorIndexOf(this), new_block); - } - successors_.Reset(); - AddSuccessor(new_block); - - return new_block; -} - HBasicBlock* HBasicBlock::SplitAfter(HInstruction* cursor) { DCHECK(!cursor->IsControlFlow()); DCHECK_NE(instructions_.last_instruction_, cursor); @@ -1082,24 +1048,14 @@ HBasicBlock* HBasicBlock::SplitAfter(HInstruction* cursor) { return new_block; } -bool HBasicBlock::IsExceptionalSuccessor(size_t idx) const { - return !GetInstructions().IsEmpty() - && GetLastInstruction()->IsTryBoundary() - && GetLastInstruction()->AsTryBoundary()->IsExceptionalSuccessor(idx); -} - -static bool HasOnlyOneInstruction(const HBasicBlock& block) { - return block.GetPhis().IsEmpty() - && !block.GetInstructions().IsEmpty() - && block.GetFirstInstruction() == block.GetLastInstruction(); -} - bool HBasicBlock::IsSingleGoto() const { - return HasOnlyOneInstruction(*this) && GetLastInstruction()->IsGoto(); -} - -bool HBasicBlock::IsSingleTryBoundary() const { - return HasOnlyOneInstruction(*this) && GetLastInstruction()->IsTryBoundary(); + HLoopInformation* loop_info = GetLoopInformation(); + DCHECK(EndsWithControlFlowInstruction()); + return GetPhis().IsEmpty() + && GetFirstInstruction() == GetLastInstruction() + && GetLastInstruction()->IsGoto() + // Back edges generate the suspend check. + && (loop_info == nullptr || !loop_info->IsBackEdge(*this)); } bool HBasicBlock::EndsWithControlFlowInstruction() const { diff --git a/compiler/optimizing/nodes.h b/compiler/optimizing/nodes.h index f2e9e2205..0f5b1abbb 100644 --- a/compiler/optimizing/nodes.h +++ b/compiler/optimizing/nodes.h @@ -39,7 +39,6 @@ class HCurrentMethod; class HDoubleConstant; class HEnvironment; class HFloatConstant; -class HGraphBuilder; class HGraphVisitor; class HInstruction; class HIntConstant; @@ -208,12 +207,6 @@ class HGraph : public ArenaObject { // Removes `block` from the graph. void DeleteDeadBlock(HBasicBlock* block); - // Splits the edge between `block` and `successor` while preserving the - // indices in the predecessor/successor lists. If there are multiple edges - // between the blocks, the lowest indices are used. - // Returns the new block which is empty and has the same dex pc as `successor`. - HBasicBlock* SplitEdge(HBasicBlock* block, HBasicBlock* successor); - void SplitCriticalEdge(HBasicBlock* block, HBasicBlock* successor); void SimplifyLoop(HBasicBlock* header); @@ -573,15 +566,6 @@ class HBasicBlock : public ArenaObject { } bool IsSingleGoto() const; - bool IsSingleTryBoundary() const; - - // Returns true if this block emits nothing but a jump. - bool IsSingleJump() const { - HLoopInformation* loop_info = GetLoopInformation(); - return (IsSingleGoto() || IsSingleTryBoundary()) - // Back edges generate a suspend check. - && (loop_info == nullptr || !loop_info->IsBackEdge(*this)); - } void AddBackEdge(HBasicBlock* back_edge) { if (loop_information_ == nullptr) { @@ -690,7 +674,7 @@ class HBasicBlock : public ArenaObject { successors_.Put(1, temp); } - size_t GetPredecessorIndexOf(HBasicBlock* predecessor) const { + size_t GetPredecessorIndexOf(HBasicBlock* predecessor) { for (size_t i = 0, e = predecessors_.Size(); i < e; ++i) { if (predecessors_.Get(i) == predecessor) { return i; @@ -699,7 +683,7 @@ class HBasicBlock : public ArenaObject { return -1; } - size_t GetSuccessorIndexOf(HBasicBlock* successor) const { + size_t GetSuccessorIndexOf(HBasicBlock* successor) { for (size_t i = 0, e = successors_.Size(); i < e; ++i) { if (successors_.Get(i) == successor) { return i; @@ -708,32 +692,6 @@ class HBasicBlock : public ArenaObject { return -1; } - HBasicBlock* GetSinglePredecessor() const { - DCHECK_EQ(GetPredecessors().Size(), 1u); - return GetPredecessors().Get(0); - } - - HBasicBlock* GetSingleSuccessor() const { - DCHECK_EQ(GetSuccessors().Size(), 1u); - return GetSuccessors().Get(0); - } - - // Returns whether the first occurrence of `predecessor` in the list of - // predecessors is at index `idx`. - bool IsFirstIndexOfPredecessor(HBasicBlock* predecessor, size_t idx) const { - DCHECK_EQ(GetPredecessors().Get(idx), predecessor); - return GetPredecessorIndexOf(predecessor) == idx; - } - - // Returns whether successor at index `idx` is an exception handler. - bool IsExceptionalSuccessor(size_t idx) const; - - // Split the block into two blocks just before `cursor`. Returns the newly - // created, latter block. Note that this method will create a Goto at the end - // of the former block and will create an edge between them. It will not, - // however, update the graph, reverse post order or loop information. - HBasicBlock* SplitBefore(HInstruction* cursor); - // Split the block into two blocks just after `cursor`. Returns the newly // created block. Note that this method just updates raw block information, // like predecessors, successors, dominators, and instruction list. It does not @@ -955,7 +913,6 @@ class HLoopInformationOutwardIterator : public ValueObject { M(SuspendCheck, Instruction) \ M(Temporary, Instruction) \ M(Throw, Instruction) \ - M(TryBoundary, Instruction) \ M(TypeConversion, Instruction) \ M(UShr, BinaryOperation) \ M(Xor, BinaryOperation) \ @@ -1901,7 +1858,7 @@ class HGoto : public HTemplateInstruction<0> { bool IsControlFlow() const OVERRIDE { return true; } HBasicBlock* GetSuccessor() const { - return GetBlock()->GetSingleSuccessor(); + return GetBlock()->GetSuccessors().Get(0); } DECLARE_INSTRUCTION(Goto); @@ -1935,65 +1892,6 @@ class HIf : public HTemplateInstruction<1> { DISALLOW_COPY_AND_ASSIGN(HIf); }; - -// Abstract instruction which marks the beginning and/or end of a try block and -// links it to the respective exception handlers. Behaves the same as a Goto in -// non-exceptional control flow. -// Normal-flow successor is stored at index zero, exception handlers under -// higher indices in no particular order. -class HTryBoundary : public HTemplateInstruction<0> { - public: - HTryBoundary(bool is_entry, bool is_exit) - : HTemplateInstruction(SideEffects::None()), is_entry_(is_entry), is_exit_(is_exit) {} - - bool IsControlFlow() const OVERRIDE { return true; } - - // Returns the block's non-exceptional successor (index zero). - HBasicBlock* GetNormalFlowSuccessor() const { return GetBlock()->GetSuccessors().Get(0); } - - // Returns whether `handler` is among its exception handlers (non-zero index - // successors). - bool HasExceptionHandler(HBasicBlock* handler) const { - DCHECK(handler->IsCatchBlock()); - return GetBlock()->GetSuccessors().Contains(handler, /* start_from */ 1); - } - - // Returns whether successor at index `idx` is an exception handler. - bool IsExceptionalSuccessor(size_t idx) const { - DCHECK_LT(idx, GetBlock()->GetSuccessors().Size()); - bool is_handler = (idx != 0); - DCHECK(!is_handler || GetBlock()->GetSuccessors().Get(idx)->IsCatchBlock()); - return is_handler; - } - - // If not present already, adds `handler` to its block's list of exception - // handlers. - void AddExceptionHandler(HBasicBlock* handler) { - if (!HasExceptionHandler(handler)) { - GetBlock()->AddSuccessor(handler); - } - } - - bool IsTryEntry() const { return is_entry_; } - bool IsTryExit() const { return is_exit_; } - - DECLARE_INSTRUCTION(TryBoundary); - - private: - // Only for debugging purposes. - bool is_entry_; - bool is_exit_; - - // Only set by HGraphBuilder. - void SetIsTryEntry() { is_entry_ = true; } - void SetIsTryExit() { is_exit_ = true; } - - friend HGraphBuilder; - - DISALLOW_COPY_AND_ASSIGN(HTryBoundary); -}; - - // Deoptimize to interpreter, upon checking a condition. class HDeoptimize : public HTemplateInstruction<1> { public: diff --git a/compiler/utils/growable_array.h b/compiler/utils/growable_array.h index f85e026f1..e4b1e7d0e 100644 --- a/compiler/utils/growable_array.h +++ b/compiler/utils/growable_array.h @@ -46,8 +46,8 @@ class GrowableArray : public ArenaObject { } } - bool Contains(T value, size_t start_from = 0) const { - for (size_t i = start_from; i < num_used_; ++i) { + bool Contains(T value) const { + for (size_t i = 0; i < num_used_; ++i) { if (elem_list_[i] == value) { return true; } diff --git a/test/510-checker-try-catch/expected.txt b/test/510-checker-try-catch/expected.txt deleted file mode 100644 index e69de29bb..000000000 diff --git a/test/510-checker-try-catch/info.txt b/test/510-checker-try-catch/info.txt deleted file mode 100644 index 9ffcb197d..000000000 --- a/test/510-checker-try-catch/info.txt +++ /dev/null @@ -1 +0,0 @@ -Tests the generation of try/catch blocks in Optimizing. \ No newline at end of file diff --git a/test/510-checker-try-catch/smali/Builder.smali b/test/510-checker-try-catch/smali/Builder.smali deleted file mode 100644 index a388aa198..000000000 --- a/test/510-checker-try-catch/smali/Builder.smali +++ /dev/null @@ -1,914 +0,0 @@ -# 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. - -.class public LBuilder; - -.super Ljava/lang/Object; - -# Basic test case with two try blocks and three catch handlers, one of which -# is shared by the two tries. - -## CHECK-START: int Builder.testMultipleTryCatch(int, int, int) builder (after) - -## CHECK: name "B0" -## CHECK: successors "<>" -## CHECK: <> IntConstant -1 -## CHECK: <> IntConstant -2 -## CHECK: <> IntConstant -3 - -## CHECK: name "<>" -## CHECK: predecessors "<>" -## CHECK: successors "<>" -## CHECK: DivZeroCheck - -## CHECK: name "<>" -## CHECK: predecessors "<>" -## CHECK: successors "<>" -## CHECK: Add - -## CHECK: name "<>" -## CHECK: predecessors "<>" -## CHECK: successors "<>" -## CHECK: DivZeroCheck - -## CHECK: name "<>" -## CHECK: predecessors "<>" "<>" "<>" "<>" -## CHECK: Return - -## CHECK: name "<>" -## CHECK: predecessors "<>" "<>" -## CHECK: successors "<>" -## CHECK: flags "catch_block" -## CHECK: StoreLocal [v0,<>] - -## CHECK: name "<>" -## CHECK: predecessors "<>" "<>" -## CHECK: successors "<>" -## CHECK: flags "catch_block" -## CHECK: StoreLocal [v0,<>] - -## CHECK: name "<>" -## CHECK: predecessors "<>" "<>" "<>" "<>" -## CHECK: successors "<>" -## CHECK: flags "catch_block" -## CHECK: StoreLocal [v0,<>] - -## CHECK: name "<>" -## CHECK: predecessors "B0" -## CHECK: successors "<>" -## CHECK: xhandlers "<>" "<>" -## CHECK: TryBoundary is_entry:true is_exit:false - -## CHECK: name "<>" -## CHECK: predecessors "<>" -## CHECK: successors "<>" -## CHECK: xhandlers "<>" "<>" -## CHECK: TryBoundary is_entry:false is_exit:true - -## CHECK: name "<>" -## CHECK: predecessors "<>" -## CHECK: successors "<>" -## CHECK: xhandlers "<>" "<>" -## CHECK: TryBoundary is_entry:true is_exit:false - -## CHECK: name "<>" -## CHECK: predecessors "<>" -## CHECK: successors "<>" -## CHECK: xhandlers "<>" "<>" -## CHECK: TryBoundary is_entry:false is_exit:true - -.method public static testMultipleTryCatch(III)I - .registers 3 - - :try_start_1 - div-int/2addr p0, p1 - :try_end_1 - .catch Ljava/lang/ArithmeticException; {:try_start_1 .. :try_end_1} :catch_arith - .catchall {:try_start_1 .. :try_end_1} :catch_other - - add-int/2addr p0, p0 - - :try_start_2 - div-int/2addr p0, p2 - :try_end_2 - .catch Ljava/lang/OutOfMemoryError; {:try_start_2 .. :try_end_2} :catch_mem - .catchall {:try_start_2 .. :try_end_2} :catch_other - - :return - return p0 - - :catch_arith - const/4 p0, -0x1 - goto :return - - :catch_mem - const/4 p0, -0x2 - goto :return - - :catch_other - const/4 p0, -0x3 - goto :return -.end method - -# Test that multiple try-entry blocks are generated if there are multiple entry -# points into the try block. - -## CHECK-START: int Builder.testMultipleEntries(int, int, int, int) builder (after) - -## CHECK: name "B0" -## CHECK: successors "<>" -## CHECK: <> IntConstant -1 - -## CHECK: name "<>" -## CHECK: predecessors "B0" -## CHECK: successors "<>" "<>" -## CHECK: If - -## CHECK: name "<>" -## CHECK: predecessors "<>" -## CHECK: successors "<>" -## CHECK: Div - -## CHECK: name "<>" -## CHECK: predecessors "<>" -## CHECK: successors "<>" -## CHECK: Div - -## CHECK: name "<>" -## CHECK: predecessors "<>" "<>" -## CHECK: successors "<>" -## CHECK: Div - -## CHECK: name "<>" -## CHECK: predecessors "<>" "<>" -## CHECK: Return - -## CHECK: name "<>" -## CHECK: predecessors "<>" "<>" "<>" -## CHECK: successors "<>" -## CHECK: flags "catch_block" -## CHECK: StoreLocal [v0,<>] - -## CHECK: name "<>" -## CHECK: predecessors "<>" -## CHECK: successors "<>" -## CHECK: xhandlers "<>" -## CHECK: TryBoundary is_entry:true is_exit:false - -## CHECK: name "<>" -## CHECK: predecessors "<>" -## CHECK: successors "<>" -## CHECK: xhandlers "<>" -## CHECK: TryBoundary is_entry:true is_exit:false - -## CHECK: name "<>" -## CHECK: predecessors "<>" -## CHECK: successors "<>" -## CHECK: xhandlers "<>" -## CHECK: TryBoundary is_entry:false is_exit:true - -.method public static testMultipleEntries(IIII)I - .registers 4 - - if-eqz p2, :else - - div-int/2addr p0, p1 - - :try_start - div-int/2addr p0, p2 - - :else - div-int/2addr p0, p3 - :try_end - .catchall {:try_start .. :try_end} :catch_all - - :return - return p0 - - :catch_all - const/4 p0, -0x1 - goto :return - -.end method - -# Test that multiple try-exit blocks are generated if (normal) control flow can -# jump out of the try block at multiple points. - -## CHECK-START: int Builder.testMultipleExits(int, int) builder (after) - -## CHECK: name "B0" -## CHECK: successors "<>" -## CHECK: <> IntConstant -1 -## CHECK: <> IntConstant -2 - -## CHECK: name "<>" -## CHECK: predecessors "<>" -## CHECK: successors "<>" "<>" -## CHECK: Div -## CHECK: If - -## CHECK: name "<>" -## CHECK: predecessors "<>" "<>" "<>" -## CHECK: Return - -## CHECK: name "<>" -## CHECK: predecessors "<>" -## CHECK: successors "<>" -## CHECK: StoreLocal [v0,<>] - -## CHECK: name "<>" -## CHECK: predecessors "<>" "<>" "<>" -## CHECK: successors "<>" -## CHECK: flags "catch_block" -## CHECK: StoreLocal [v0,<>] - -## CHECK: name "<>" -## CHECK: predecessors "B0" -## CHECK: successors "<>" -## CHECK: xhandlers "<>" -## CHECK: TryBoundary is_entry:true is_exit:false - -## CHECK: name "<>" -## CHECK: predecessors "<>" -## CHECK: successors "<>" -## CHECK: xhandlers "<>" -## CHECK: TryBoundary is_entry:false is_exit:true - -## CHECK: name "<>" -## CHECK: predecessors "<>" -## CHECK: successors "<>" -## CHECK: xhandlers "<>" -## CHECK: TryBoundary is_entry:false is_exit:true - -.method public static testMultipleExits(II)I - .registers 2 - - :try_start - div-int/2addr p0, p1 - if-eqz p0, :then - :try_end - .catchall {:try_start .. :try_end} :catch_all - - :return - return p0 - - :then - const/4 p0, -0x1 - goto :return - - :catch_all - const/4 p0, -0x2 - goto :return -.end method - -# Test that only one TryBoundary is inserted when an edge connects two different -# try ranges. - -## CHECK-START: int Builder.testSharedBoundary(int, int, int) builder (after) - -## CHECK: name "B0" -## CHECK: successors "<>" -## CHECK: <> IntConstant -1 -## CHECK: <> IntConstant -2 - -## CHECK: name "<>" -## CHECK: predecessors "<>" -## CHECK: successors "<>" -## CHECK: Div - -## CHECK: name "<>" -## CHECK: predecessors "<>" -## CHECK: successors "<>" -## CHECK: Div - -## CHECK: name "<>" -## CHECK: predecessors "<>" "<>" "<>" -## CHECK: Return - -## CHECK: name "<>" -## CHECK: predecessors "<>" "<>" -## CHECK: successors "<>" -## CHECK: flags "catch_block" -## CHECK: StoreLocal [v0,<>] - -## CHECK: name "<>" -## CHECK: predecessors "<>" "<>" -## CHECK: successors "<>" -## CHECK: flags "catch_block" -## CHECK: StoreLocal [v0,<>] - -## CHECK: name "<>" -## CHECK: predecessors "B0" -## CHECK: successors "<>" -## CHECK: xhandlers "<>" -## CHECK: TryBoundary is_entry:true is_exit:false - -## CHECK: name "<>" -## CHECK: predecessors "<>" -## CHECK: successors "<>" -## CHECK: xhandlers "<>" "<>" -## CHECK: TryBoundary is_entry:true is_exit:true - -## CHECK: name "<>" -## CHECK: predecessors "<>" -## CHECK: successors "<>" -## CHECK: xhandlers "<>" -## CHECK: TryBoundary is_entry:false is_exit:true - -.method public static testSharedBoundary(III)I - .registers 3 - - :try_start_1 - div-int/2addr p0, p1 - :try_end_1 - .catchall {:try_start_1 .. :try_end_1} :catch_all_1 - - :try_start_2 - div-int/2addr p0, p2 - :try_end_2 - .catchall {:try_start_2 .. :try_end_2} :catch_all_2 - - :return - return p0 - - :catch_all_1 - const/4 p0, -0x1 - goto :return - - :catch_all_2 - const/4 p0, -0x2 - goto :return -.end method - -# Same as previous test, only the blocks are processed in the opposite order. - -## CHECK-START: int Builder.testSharedBoundary_Reverse(int, int, int) builder (after) - -## CHECK: name "B0" -## CHECK: successors "<>" -## CHECK: <> IntConstant -1 -## CHECK: <> IntConstant -2 - -## CHECK: name "<>" -## CHECK: successors "<>" -## CHECK: Goto - -## CHECK: name "<>" -## CHECK: predecessors "<>" -## CHECK: successors "<>" -## CHECK: Div - -## CHECK: name "<>" -## CHECK: predecessors "<>" -## CHECK: successors "<>" -## CHECK: Div - -## CHECK: name "<>" -## CHECK: predecessors "<>" "<>" "<>" -## CHECK: Return - -## CHECK: name "<>" -## CHECK: predecessors "<>" "<>" -## CHECK: successors "<>" -## CHECK: flags "catch_block" -## CHECK: StoreLocal [v0,<>] - -## CHECK: name "<>" -## CHECK: predecessors "<>" "<>" -## CHECK: successors "<>" -## CHECK: flags "catch_block" -## CHECK: StoreLocal [v0,<>] - -## CHECK: name "<>" -## CHECK: predecessors "<>" -## CHECK: successors "<>" -## CHECK: xhandlers "<>" "<>" -## CHECK: TryBoundary is_entry:true is_exit:true - -## CHECK: name "<>" -## CHECK: predecessors "<>" -## CHECK: successors "<>" -## CHECK: xhandlers "<>" -## CHECK: TryBoundary is_entry:false is_exit:true - -## CHECK: name "<>" -## CHECK: predecessors "<>" -## CHECK: successors "<>" -## CHECK: xhandlers "<>" -## CHECK: TryBoundary is_entry:true is_exit:false - -.method public static testSharedBoundary_Reverse(III)I - .registers 3 - - goto :try_start_2 - - :try_start_1 - div-int/2addr p0, p1 - goto :return - :try_end_1 - .catchall {:try_start_1 .. :try_end_1} :catch_all_1 - - :try_start_2 - div-int/2addr p0, p2 - goto :try_start_1 - :try_end_2 - .catchall {:try_start_2 .. :try_end_2} :catch_all_2 - - :return - return p0 - - :catch_all_1 - const/4 p0, -0x1 - goto :return - - :catch_all_2 - const/4 p0, -0x2 - goto :return -.end method - -# Test that nested tries are split into non-overlapping blocks and TryBoundary -# blocks are correctly created between them. - -## CHECK-START: int Builder.testNestedTry(int, int, int, int) builder (after) - -## CHECK: name "B0" -## CHECK: <> IntConstant -1 -## CHECK: <> IntConstant -2 - -## CHECK: name "<>" -## CHECK: predecessors "<>" -## CHECK: successors "<>" -## CHECK: Div - -## CHECK: name "<>" -## CHECK: predecessors "<>" -## CHECK: successors "<>" -## CHECK: Div - -## CHECK: name "<>" -## CHECK: predecessors "<>" -## CHECK: successors "<>" -## CHECK: Div - -## CHECK: name "<>" -## CHECK: predecessors "<>" "<>" "<>" - -## CHECK: name "<>" -## CHECK: predecessors "<>" "<>" -## CHECK: successors "<>" -## CHECK: flags "catch_block" -## CHECK: StoreLocal [v0,<>] - -## CHECK: name "<>" -## CHECK: predecessors "<>" "<>" "<>" "<>" -## CHECK: successors "<>" -## CHECK: flags "catch_block" -## CHECK: StoreLocal [v0,<>] - -## CHECK: name "<>" -## CHECK: predecessors "B0" -## CHECK: successors "<>" -## CHECK: xhandlers "<>" -## CHECK: TryBoundary is_entry:true is_exit:false - -## CHECK: name "<>" -## CHECK: predecessors "<>" -## CHECK: successors "<>" -## CHECK: xhandlers "<>" "<>" -## CHECK: TryBoundary is_entry:true is_exit:true - -## CHECK: name "<>" -## CHECK: predecessors "<>" -## CHECK: successors "<>" -## CHECK: xhandlers "<>" "<>" -## CHECK: TryBoundary is_entry:true is_exit:true - -## CHECK: name "<>" -## CHECK: predecessors "<>" -## CHECK: successors "<>" -## CHECK: xhandlers "<>" -## CHECK: TryBoundary is_entry:false is_exit:true - -.method public static testNestedTry(IIII)I - .registers 4 - - :try_start_1 - div-int/2addr p0, p1 - - :try_start_2 - div-int/2addr p0, p2 - :try_end_2 - .catch Ljava/lang/ArithmeticException; {:try_start_2 .. :try_end_2} :catch_arith - - div-int/2addr p0, p3 - :try_end_1 - .catchall {:try_start_1 .. :try_end_1} :catch_all - - :return - return p0 - - :catch_arith - const/4 p0, -0x1 - goto :return - - :catch_all - const/4 p0, -0x2 - goto :return -.end method - -# Test control flow that enters a try block, leaves it and returns again. - -## CHECK-START: int Builder.testIncontinuousTry(int, int, int, int) builder (after) - -## CHECK: name "B0" -## CHECK: <> IntConstant -1 - -## CHECK: name "<>" -## CHECK: predecessors "<>" -## CHECK: successors "<>" -## CHECK: Div - -## CHECK: name "<>" -## CHECK: predecessors "<>" -## CHECK: successors "<>" -## CHECK: Div - -## CHECK: name "<>" -## CHECK: predecessors "<>" "<>" - -## CHECK: name "<>" -## CHECK: predecessors "<>" -## CHECK: successors "<>" -## CHECK: Div - -## CHECK: name "<>" -## CHECK: predecessors "<>" "<>" "<>" "<>" -## CHECK: successors "<>" -## CHECK: flags "catch_block" -## CHECK: StoreLocal [v0,<>] - -## CHECK: name "<>" -## CHECK: predecessors "B0" -## CHECK: successors "<>" -## CHECK: xhandlers "<>" -## CHECK: TryBoundary is_entry:true is_exit:false - -## CHECK: name "<>" -## CHECK: predecessors "<>" -## CHECK: successors "<>" -## CHECK: xhandlers "<>" -## CHECK: TryBoundary is_entry:false is_exit:true - -## CHECK: name "<>" -## CHECK: predecessors "<>" -## CHECK: successors "<>" -## CHECK: xhandlers "<>" -## CHECK: TryBoundary is_entry:true is_exit:false - -## CHECK: name "<>" -## CHECK: predecessors "<>" -## CHECK: successors "<>" -## CHECK: xhandlers "<>" -## CHECK: TryBoundary is_entry:false is_exit:true - -.method public static testIncontinuousTry(IIII)I - .registers 4 - - :try_start - div-int/2addr p0, p1 - goto :outside - - :inside - div-int/2addr p0, p3 - :try_end - .catchall {:try_start .. :try_end} :catch_all - - :return - return p0 - - :outside - div-int/2addr p0, p2 - goto :inside - - :catch_all - const/4 p0, -0x1 - goto :return -.end method - -# Test that a TryBoundary is inserted between a Throw instruction and the exit -# block when covered by a try range. - -## CHECK-START: int Builder.testThrow(java.lang.Exception) builder (after) - -## CHECK: name "B0" -## CHECK: successors "<>" -## CHECK: <> IntConstant -1 - -## CHECK: name "<>" -## CHECK: predecessors "<>" -## CHECK: successors "<>" -## CHECK: Throw - -## CHECK: name "<>" -## CHECK: predecessors "<>" "<>" -## CHECK: successors "<>" -## CHECK: flags "catch_block" -## CHECK: StoreLocal [v0,<>] - -## CHECK: name "<>" -## CHECK: predecessors "B0" -## CHECK: successors "<>" -## CHECK: xhandlers "<>" -## CHECK: TryBoundary is_entry:true is_exit:false - -## CHECK: name "<>" -## CHECK: predecessors "<>" -## CHECK: successors "<>" -## CHECK: xhandlers "<>" -## CHECK: TryBoundary is_entry:false is_exit:true - -## CHECK: name "<>" -## CHECK: predecessors "<>" "<>" -## CHECK: Exit - -.method public static testThrow(Ljava/lang/Exception;)I - .registers 2 - - :try_start - throw p0 - :try_end - .catchall {:try_start .. :try_end} :catch_all - - :catch_all - const/4 v0, -0x1 - return v0 -.end method - -# Test graph with a throw/catch loop. - -## CHECK-START: int Builder.testCatchLoop(int, int, int) builder (after) - -## CHECK: name "B0" -## CHECK: successors "<>" - -## CHECK: name "<>" -## CHECK: predecessors "<>" "<>" "<>" -## CHECK: successors "<>" -## CHECK: flags "catch_block" -## CHECK: Div - -## CHECK: name "<>" -## CHECK: predecessors "<>" - -## CHECK: name "<>" -## CHECK: predecessors "B0" -## CHECK: successors "<>" -## CHECK: xhandlers "<>" -## CHECK: TryBoundary is_entry:true is_exit:false - -## CHECK: name "<>" -## CHECK: predecessors "<>" -## CHECK: successors "<>" -## CHECK: xhandlers "<>" -## CHECK: TryBoundary is_entry:false is_exit:true - -.method public static testCatchLoop(III)I - .registers 4 - - :try_start - :catch_all - div-int/2addr p0, p2 - :try_end - .catchall {:try_start .. :try_end} :catch_all - - :return - return p0 -.end method - -# Test that handler edges are not split. In this scenario, the catch block is -# only the handler of the try block. - -## CHECK-START: int Builder.testHandlerEdge1(int, int, int) builder (after) - -## CHECK: name "B0" -## CHECK: successors "<>" - -## CHECK: name "<>" -## CHECK: predecessors "<>" -## CHECK: successors "<>" -## CHECK: Div - -## CHECK: name "<>" -## CHECK: predecessors "<>" "<>" "<>" -## CHECK: successors "<>" -## CHECK: flags "catch_block" -## CHECK: Div - -## CHECK: name "<>" -## CHECK: predecessors "<>" - -## CHECK: name "<>" -## CHECK: predecessors "B0" -## CHECK: successors "<>" -## CHECK: xhandlers "<>" -## CHECK: TryBoundary is_entry:true is_exit:false - -## CHECK: name "<>" -## CHECK: predecessors "<>" -## CHECK: successors "<>" -## CHECK: xhandlers "<>" -## CHECK: TryBoundary is_entry:false is_exit:true - -.method public static testHandlerEdge1(III)I - .registers 4 - - :try_start - div-int/2addr p0, p1 - - :catch_all - div-int/2addr p0, p2 - :try_end - .catchall {:try_start .. :try_end} :catch_all - - return p0 -.end method - -# Test that handler edges are not split. In this scenario, the catch block is -# the handler and also the successor of the try block. - -## CHECK-START: int Builder.testHandlerEdge2(int, int, int) builder (after) - -## CHECK: name "B0" -## CHECK: successors "<>" - -## CHECK: name "<>" -## CHECK: predecessors "<>" "<>" "<>" -## CHECK: successors "<>" -## CHECK: flags "catch_block" -## CHECK: Div - -## CHECK: name "<>" -## CHECK: predecessors "<>" "<>" "<>" -## CHECK: successors "<>" -## CHECK: flags "catch_block" -## CHECK: Div - -## CHECK: name "<>" -## CHECK: predecessors "<>" -## CHECK: Return - -## CHECK: name "<>" -## CHECK: predecessors "B0" -## CHECK: successors "<>" -## CHECK: xhandlers "<>" -## CHECK: TryBoundary is_entry:true is_exit:false - -## CHECK: name "<>" -## CHECK: predecessors "<>" -## CHECK: successors "<>" -## CHECK: xhandlers "<>" "<>" -## CHECK: TryBoundary is_entry:true is_exit:true - -## CHECK: name "<>" -## CHECK: predecessors "<>" -## CHECK: successors "<>" -## CHECK: xhandlers "<>" -## CHECK: TryBoundary is_entry:false is_exit:true - -.method public static testHandlerEdge2(III)I - .registers 4 - - :try_start_1 - :catch_all_1 - div-int/2addr p0, p1 - :try_end_1 - .catchall {:try_start_1 .. :try_end_1} :catch_all_2 - - :try_start_2 - :catch_all_2 - div-int/2addr p0, p2 - :try_end_2 - .catchall {:try_start_2 .. :try_end_2} :catch_all_1 - - return p0 -.end method - -# Test that a MOVE_RESULT instruction is placed into the same block as the -# INVOKE it follows, even if there is a try boundary between them. - -## CHECK-START: int Builder.testMoveResult_Invoke(int, int, int) builder (after) - -## CHECK: <> InvokeStaticOrDirect -## CHECK-NEXT: StoreLocal [v0,<>] - -.method public static testMoveResult_Invoke(III)I - .registers 3 - - :try_start - invoke-static {p0, p1, p2}, LBuilder;->testCatchLoop(III)I - :try_end - .catchall {:try_start .. :try_end} :catch_all - - move-result p0 - - :return - return p0 - - :catch_all - const/4 p0, -0x1 - goto :return -.end method - -# Test that a MOVE_RESULT instruction is placed into the same block as the -# FILLED_NEW_ARRAY it follows, even if there is a try boundary between them. - -## CHECK-START: int[] Builder.testMoveResult_FilledNewArray(int, int, int) builder (after) - -## CHECK: <> NewArray -## CHECK-NEXT: Temporary -## CHECK-NEXT: StoreLocal [v0,<>] -## CHECK: ArraySet [<>,{{i\d+}},{{i\d+}}] -## CHECK: ArraySet [<>,{{i\d+}},{{i\d+}}] -## CHECK: ArraySet [<>,{{i\d+}},{{i\d+}}] - -.method public static testMoveResult_FilledNewArray(III)[I - .registers 3 - - :try_start - filled-new-array {p0, p1, p2}, [I - :try_end - .catchall {:try_start .. :try_end} :catch_all - - move-result-object p0 - - :return - return-object p0 - - :catch_all - const/4 p0, 0x0 - goto :return -.end method - -# Test case for ReturnVoid inside a try block. Builder needs to move it outside -# the try block so as to not split the ReturnVoid-Exit edge. -# This invariant is enforced by GraphChecker. - -.method public static testReturnVoidInTry(II)V - .registers 2 - - :catch_all - :try_start - return-void - :try_end - .catchall {:try_start .. :try_end} :catch_all - -.end method - -# Test case for Return inside a try block. Builder needs to move it outside the -# try block so as to not split the Return-Exit edge. -# This invariant is enforced by GraphChecker. - -.method public static testReturnInTry(II)I - .registers 2 - - :try_start - div-int/2addr p0, p1 - return p0 - :try_end - .catchall {:try_start .. :try_end} :catch_all - - :catch_all - const/4 v0, 0x0 - return v0 -.end method - -# Test a (dead) try block which flows out of the method. The block will be -# removed by DCE but needs to pass post-builder GraphChecker. - -## CHECK-START: int Builder.testDeadEndTry(int) builder (after) -## CHECK-NOT: TryBoundary is_exit:true - -.method public static testDeadEndTry(I)I - .registers 1 - - return p0 - - :catch_all - nop - - :try_start - nop - :try_end - .catchall {:try_start .. :try_end} :catch_all -.end method diff --git a/test/510-checker-try-catch/src/Main.java b/test/510-checker-try-catch/src/Main.java deleted file mode 100644 index ae78ba0fd..000000000 --- a/test/510-checker-try-catch/src/Main.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * 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 { - - // Workaround for b/18051191. - class InnerClass {} - - public static void main(String[] args) {} -} -- 2.11.0