From d7558daaa86decf5a38f4f9bcd82267ab6e3e17f Mon Sep 17 00:00:00 2001 From: David Brazdil Date: Tue, 22 Sep 2015 13:04:14 +0100 Subject: [PATCH] ART: Preserve loop headers with try/catch Algorithm for inserting HTryBoundary instructions would generate a non-natural loop when a loop header block was covered by a TryItem. This patch changes the approach to fix the issue. Bug: 23895756 Change-Id: I0e1ee6cf135cea326a96c97954907d202c9793cc --- compiler/optimizing/builder.cc | 247 ++++++++++--------------- compiler/optimizing/builder.h | 14 +- compiler/optimizing/nodes.cc | 26 +++ compiler/optimizing/nodes.h | 11 +- test/510-checker-try-catch/smali/Builder.smali | 209 +++++++++++++-------- 5 files changed, 276 insertions(+), 231 deletions(-) diff --git a/compiler/optimizing/builder.cc b/compiler/optimizing/builder.cc index 9d70124a4..d589f00d2 100644 --- a/compiler/optimizing/builder.cc +++ b/compiler/optimizing/builder.cc @@ -262,22 +262,6 @@ bool HGraphBuilder::SkipCompilation(const DexFile::CodeItem& code_item, return false; } -static const DexFile::TryItem* GetTryItem(HBasicBlock* block, - const DexFile::CodeItem& code_item, - const ArenaBitVector& can_block_throw) { - DCHECK(!block->IsSingleTryBoundary()); - - // Block does not contain throwing instructions. Even if it is covered by - // a TryItem, we will consider it not in a try block. - if (!can_block_throw.IsBitSet(block->GetBlockId())) { - return nullptr; - } - - // Instructions in the block may throw. Find a TryItem covering this block. - int32_t try_item_idx = DexFile::FindTryItem(code_item, block->GetDexPc()); - return (try_item_idx == -1) ? nullptr : DexFile::GetTryItems(code_item, try_item_idx); -} - void HGraphBuilder::CreateBlocksForTryCatch(const DexFile::CodeItem& code_item) { if (code_item.tries_size_ == 0) { return; @@ -316,18 +300,18 @@ void HGraphBuilder::CreateBlocksForTryCatch(const DexFile::CodeItem& code_item) } } -void HGraphBuilder::SplitTryBoundaryEdge(HBasicBlock* predecessor, - HBasicBlock* successor, - HTryBoundary::BoundaryKind kind, - const DexFile::CodeItem& code_item, - const DexFile::TryItem& try_item) { - // Split the edge with a single TryBoundary instruction. - HTryBoundary* try_boundary = new (arena_) HTryBoundary(kind, successor->GetDexPc()); - HBasicBlock* try_entry_block = graph_->SplitEdge(predecessor, successor); - try_entry_block->AddInstruction(try_boundary); - - // Link the TryBoundary to the handlers of `try_item`. - for (CatchHandlerIterator it(code_item, try_item); it.HasNext(); it.Next()) { +// Returns the TryItem stored for `block` or nullptr if there is no info for it. +static const DexFile::TryItem* GetTryItem( + HBasicBlock* block, + const ArenaSafeMap& try_block_info) { + auto iterator = try_block_info.find(block->GetBlockId()); + return (iterator == try_block_info.end()) ? nullptr : iterator->second; +} + +void HGraphBuilder::LinkToCatchBlocks(HTryBoundary* try_boundary, + const DexFile::CodeItem& code_item, + const DexFile::TryItem* try_item) { + for (CatchHandlerIterator it(code_item, *try_item); it.HasNext(); it.Next()) { try_boundary->AddExceptionHandler(FindBlockStartingAt(it.GetHandlerAddress())); } } @@ -337,132 +321,103 @@ void HGraphBuilder::InsertTryBoundaryBlocks(const DexFile::CodeItem& code_item) return; } - // Bit vector stores information on which blocks contain throwing instructions. - // Must be expandable because catch blocks may be split into two. - ArenaBitVector can_block_throw(arena_, graph_->GetBlocks().size(), /* expandable */ true); + // Keep a map of all try blocks and their respective TryItems. We do not use + // the block's pointer but rather its id to ensure deterministic iteration. + ArenaSafeMap try_block_info( + std::less(), arena_->Adapter()); + + // Obtain TryItem information for blocks with throwing instructions, and split + // blocks which are both try & catch to simplify the graph. + // NOTE: We are appending new blocks inside the loop, so we need to use index + // because iterators can be invalidated. We remember the initial size to avoid + // iterating over the new blocks which cannot throw. + for (size_t i = 0, e = graph_->GetBlocks().size(); i < e; ++i) { + HBasicBlock* block = graph_->GetBlocks()[i]; + + // Do not bother creating exceptional edges for try blocks which have no + // throwing instructions. In that case we simply assume that the block is + // not covered by a TryItem. This prevents us from creating a throw-catch + // loop for synchronized blocks. + if (block->HasThrowingInstructions()) { + // Try to find a TryItem covering the block. + DCHECK_NE(block->GetDexPc(), kNoDexPc) << "Block must have a dec_pc to find its TryItem."; + const int32_t try_item_idx = DexFile::FindTryItem(code_item, block->GetDexPc()); + if (try_item_idx != -1) { + // Block throwing and in a TryItem. Store the try block information. + HBasicBlock* throwing_block = block; + if (block->IsCatchBlock()) { + // Simplify blocks which are both try and catch, otherwise we would + // need a strategy for splitting exceptional edges. We split the block + // after the move-exception (if present) and mark the first part not + // throwing. The normal-flow edge between them will be split later. + HInstruction* first_insn = block->GetFirstInstruction(); + if (first_insn->IsLoadException()) { + // Catch block starts with a LoadException. Split the block after + // the StoreLocal and ClearException which must come after the load. + DCHECK(first_insn->GetNext()->IsStoreLocal()); + DCHECK(first_insn->GetNext()->GetNext()->IsClearException()); + throwing_block = block->SplitBefore(first_insn->GetNext()->GetNext()->GetNext()); + } else { + // Catch block does not load the exception. Split at the beginning + // to create an empty catch block. + throwing_block = block->SplitBefore(first_insn); + } + } - // Scan blocks and mark those which contain throwing instructions. - // NOTE: We're appending new blocks inside the loop, so we need to use index because iterators - // can be invalidated. We remember the initial size to avoid iterating over the new blocks. - for (size_t block_id = 0u, end = graph_->GetBlocks().size(); block_id != end; ++block_id) { - HBasicBlock* block = graph_->GetBlocks()[block_id]; - bool can_throw = false; - for (HInstructionIterator insn(block->GetInstructions()); !insn.Done(); insn.Advance()) { - if (insn.Current()->CanThrow()) { - can_throw = true; - break; + try_block_info.Put(throwing_block->GetBlockId(), + DexFile::GetTryItems(code_item, try_item_idx)); } } + } - if (can_throw) { - if (block->IsCatchBlock()) { - // Catch blocks are always considered an entry point into the TryItem in - // order to avoid splitting exceptional edges. We split the block after - // the move-exception (if present) and mark the first part non-throwing. - // Later on, a TryBoundary will be inserted between the two blocks. - HInstruction* first_insn = block->GetFirstInstruction(); - if (first_insn->IsLoadException()) { - // Catch block starts with a LoadException. Split the block after the - // StoreLocal and ClearException which must come after the load. - DCHECK(first_insn->GetNext()->IsStoreLocal()); - DCHECK(first_insn->GetNext()->GetNext()->IsClearException()); - block = block->SplitBefore(first_insn->GetNext()->GetNext()->GetNext()); - } else { - // Catch block does not load the exception. Split at the beginning to - // create an empty catch block. - block = block->SplitBefore(first_insn); - } + // Do a pass over the try blocks and insert entering TryBoundaries where at + // least one predecessor is not covered by the same TryItem as the try block. + // We do not split each edge separately, but rather create one boundary block + // that all predecessors are relinked to. This preserves loop headers (b/23895756). + for (auto entry : try_block_info) { + HBasicBlock* try_block = graph_->GetBlock(entry.first); + for (HBasicBlock* predecessor : try_block->GetPredecessors()) { + if (GetTryItem(predecessor, try_block_info) != entry.second) { + // Found a predecessor not covered by the same TryItem. Insert entering + // boundary block. + HTryBoundary* try_entry = + new (arena_) HTryBoundary(HTryBoundary::kEntry, try_block->GetDexPc()); + try_block->CreateImmediateDominator()->AddInstruction(try_entry); + LinkToCatchBlocks(try_entry, code_item, entry.second); + break; } - can_block_throw.SetBit(block->GetBlockId()); - } - } - - // Iterate over all blocks, find those covered by some 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. - // We cannot iterate only over blocks in `branch_targets_` because switch-case - // blocks share the same dex_pc. - // NOTE: We're appending new blocks inside the loop, so we need to use index because iterators - // can be invalidated. We remember the initial size to avoid iterating over the new blocks. - for (size_t block_id = 0u, end = graph_->GetBlocks().size(); block_id != end; ++block_id) { - HBasicBlock* try_block = graph_->GetBlocks()[block_id]; - // TryBoundary blocks are added at the end of the list and not iterated over. - DCHECK(!try_block->IsSingleTryBoundary()); - - // Find the TryItem for this block. - const DexFile::TryItem* try_item = GetTryItem(try_block, code_item, can_block_throw); - if (try_item == nullptr) { - continue; - } - - // Catch blocks were split earlier and cannot throw. - DCHECK(!try_block->IsCatchBlock()); - - // 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->GetPredecessor(i); - if (predecessor->IsSingleTryBoundary()) { - // The edge was already split because of an exit from a neighbouring - // TryItem. We split it again and insert an entry point. - if (kIsDebugBuild) { - HTryBoundary* last_insn = predecessor->GetLastInstruction()->AsTryBoundary(); - const DexFile::TryItem* predecessor_try_item = - GetTryItem(predecessor->GetSinglePredecessor(), code_item, can_block_throw); - DCHECK(!last_insn->IsEntry()); - DCHECK_EQ(last_insn->GetNormalFlowSuccessor(), try_block); - DCHECK(try_block->IsFirstIndexOfPredecessor(predecessor, i)); - DCHECK_NE(try_item, predecessor_try_item); - } - } else if (GetTryItem(predecessor, code_item, can_block_throw) != try_item) { - // This is an entry point into the TryItem and the edge has not been - // split yet. That means that `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 insert an entry point. - } else { - // Not an edge on the boundary of the try block. + } + } + + // Do a second pass over the try blocks and insert exit TryBoundaries where + // the successor is not in the same TryItem. + for (auto entry : try_block_info) { + HBasicBlock* try_block = graph_->GetBlock(entry.first); + // NOTE: Do not use iterators because SplitEdge would invalidate them. + for (size_t i = 0, e = try_block->GetSuccessors().size(); i < e; ++i) { + HBasicBlock* successor = try_block->GetSuccessor(i); + + // If the successor is a try block, all of its predecessors must be + // covered by the same TryItem. Otherwise the previous pass would have + // created a non-throwing boundary block. + if (GetTryItem(successor, try_block_info) != nullptr) { + DCHECK_EQ(entry.second, GetTryItem(successor, try_block_info)); continue; } - SplitTryBoundaryEdge(predecessor, try_block, HTryBoundary::kEntry, code_item, *try_item); - } - - // Find successors which are not covered by the same TryItem range. Such - // edges exit the try block and will have a TryBoundary inserted. - for (HBasicBlock* successor : try_block->GetSuccessors()) { - if (successor->IsCatchBlock()) { - // A catch block is always considered an entry point into its TryItem. - // We therefore assume this is an exit point, regardless of whether - // the catch block is in a different TryItem or not. - } else if (successor->IsSingleTryBoundary()) { - // The edge was already split because of an entry into a neighbouring - // TryItem. We split it again and insert an exit. - if (kIsDebugBuild) { - HTryBoundary* last_insn = successor->GetLastInstruction()->AsTryBoundary(); - const DexFile::TryItem* successor_try_item = - GetTryItem(last_insn->GetNormalFlowSuccessor(), code_item, can_block_throw); - DCHECK_EQ(try_block, successor->GetSinglePredecessor()); - DCHECK(last_insn->IsEntry()); - DCHECK_NE(try_item, successor_try_item); - } - } else if (GetTryItem(successor, code_item, can_block_throw) != try_item) { - // 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 insert an exit. - 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. - successor = try_block->SplitBefore(last_instruction); - } - } else { - // Not an edge on the boundary of the try block. - continue; + + // Preserve the invariant that Return(Void) always jumps to Exit by moving + // it outside the try block if necessary. + HInstruction* last_instruction = try_block->GetLastInstruction(); + if (last_instruction->IsReturn() || last_instruction->IsReturnVoid()) { + DCHECK_EQ(successor, exit_block_); + successor = try_block->SplitBefore(last_instruction); } - SplitTryBoundaryEdge(try_block, successor, HTryBoundary::kExit, code_item, *try_item); + + // Insert TryBoundary and link to catch blocks. + HTryBoundary* try_exit = + new (arena_) HTryBoundary(HTryBoundary::kExit, successor->GetDexPc()); + graph_->SplitEdge(try_block, successor)->AddInstruction(try_exit); + LinkToCatchBlocks(try_exit, code_item, entry.second); } } } diff --git a/compiler/optimizing/builder.h b/compiler/optimizing/builder.h index 7f87df6df..1691800ee 100644 --- a/compiler/optimizing/builder.h +++ b/compiler/optimizing/builder.h @@ -121,13 +121,13 @@ class HGraphBuilder : public ValueObject { // instructions and links them to the corresponding catch blocks. void InsertTryBoundaryBlocks(const DexFile::CodeItem& code_item); - // Splits a single edge, inserting a TryBoundary of given `kind` and linking - // it to exception handlers of `try_item`. - void SplitTryBoundaryEdge(HBasicBlock* predecessor, - HBasicBlock* successor, - HTryBoundary::BoundaryKind kind, - const DexFile::CodeItem& code_item, - const DexFile::TryItem& try_item); + // Iterates over the exception handlers of `try_item`, finds the corresponding + // catch blocks and makes them successors of `try_boundary`. The order of + // successors matches the order in which runtime exception delivery searches + // for a handler. + void LinkToCatchBlocks(HTryBoundary* try_boundary, + const DexFile::CodeItem& code_item, + const DexFile::TryItem* try_item); bool CanDecodeQuickenedInfo() const; uint16_t LookupQuickenedInfo(uint32_t dex_pc); diff --git a/compiler/optimizing/nodes.cc b/compiler/optimizing/nodes.cc index 1ca990700..ef89932e3 100644 --- a/compiler/optimizing/nodes.cc +++ b/compiler/optimizing/nodes.cc @@ -1170,6 +1170,23 @@ HBasicBlock* HBasicBlock::SplitBefore(HInstruction* cursor) { return new_block; } +HBasicBlock* HBasicBlock::CreateImmediateDominator() { + DCHECK(!graph_->IsInSsaForm()) << "Support for SSA form not implemented"; + DCHECK(!IsCatchBlock()) << "Support for updating try/catch information not implemented."; + + HBasicBlock* new_block = new (GetGraph()->GetArena()) HBasicBlock(GetGraph(), GetDexPc()); + + for (HBasicBlock* predecessor : GetPredecessors()) { + new_block->predecessors_.push_back(predecessor); + predecessor->successors_[predecessor->GetSuccessorIndexOf(this)] = new_block; + } + predecessors_.clear(); + AddPredecessor(new_block); + + GetGraph()->AddBlock(new_block); + return new_block; +} + HBasicBlock* HBasicBlock::SplitAfter(HInstruction* cursor) { DCHECK(!cursor->IsControlFlow()); DCHECK_NE(instructions_.last_instruction_, cursor); @@ -1215,6 +1232,15 @@ const HTryBoundary* HBasicBlock::ComputeTryEntryOfSuccessors() const { } } +bool HBasicBlock::HasThrowingInstructions() const { + for (HInstructionIterator it(GetInstructions()); !it.Done(); it.Advance()) { + if (it.Current()->CanThrow()) { + return true; + } + } + return false; +} + static bool HasOnlyOneInstruction(const HBasicBlock& block) { return block.GetPhis().IsEmpty() && !block.GetInstructions().IsEmpty() diff --git a/compiler/optimizing/nodes.h b/compiler/optimizing/nodes.h index 7498f4472..d79852c17 100644 --- a/compiler/optimizing/nodes.h +++ b/compiler/optimizing/nodes.h @@ -819,11 +819,17 @@ class HBasicBlock : public ArenaObject { return EndsWithTryBoundary() ? 1 : GetSuccessors().size(); } + // Create a new block between this block and its predecessors. The new block + // is added to the graph, all predecessor edges are relinked to it and an edge + // is created to `this`. Returns the new empty block. Reverse post order or + // loop and try/catch information are not updated. + HBasicBlock* CreateImmediateDominator(); + // Split the block into two blocks just before `cursor`. Returns the newly // created, latter block. Note that this method will add the block to the // graph, create a Goto at the end of the former block and will create an edge // between the blocks. It will not, however, update the reverse post order or - // loop information. + // loop and try/catch information. HBasicBlock* SplitBefore(HInstruction* cursor); // Split the block into two blocks just after `cursor`. Returns the newly @@ -934,6 +940,8 @@ class HBasicBlock : public ArenaObject { // the appropriate try entry will be returned. const HTryBoundary* ComputeTryEntryOfSuccessors() const; + bool HasThrowingInstructions() const; + // Returns whether this block dominates the blocked passed as parameter. bool Dominates(HBasicBlock* block) const; @@ -943,7 +951,6 @@ class HBasicBlock : public ArenaObject { void SetLifetimeStart(size_t start) { lifetime_start_ = start; } void SetLifetimeEnd(size_t end) { lifetime_end_ = end; } - bool EndsWithControlFlowInstruction() const; bool EndsWithIf() const; bool EndsWithTryBoundary() const; diff --git a/test/510-checker-try-catch/smali/Builder.smali b/test/510-checker-try-catch/smali/Builder.smali index 2274ba4d4..1fde5edc2 100644 --- a/test/510-checker-try-catch/smali/Builder.smali +++ b/test/510-checker-try-catch/smali/Builder.smali @@ -59,7 +59,7 @@ ## CHECK: StoreLocal [v0,<>] ## CHECK: name "<>" -## CHECK: predecessors "<>" "<>" "<>" "<>" +## CHECK: predecessors "<>" "<>" "<>" "<>" ## CHECK: successors "<>" ## CHECK: flags "catch_block" ## CHECK: StoreLocal [v0,<>] @@ -70,18 +70,18 @@ ## CHECK: xhandlers "<>" "<>" ## CHECK: TryBoundary kind:entry -## CHECK: name "<>" -## CHECK: predecessors "<>" -## CHECK: successors "<>" -## CHECK: xhandlers "<>" "<>" -## CHECK: TryBoundary kind:exit - ## CHECK: name "<>" ## CHECK: predecessors "<>" ## CHECK: successors "<>" ## CHECK: xhandlers "<>" "<>" ## CHECK: TryBoundary kind:entry +## CHECK: name "<>" +## CHECK: predecessors "<>" +## CHECK: successors "<>" +## CHECK: xhandlers "<>" "<>" +## CHECK: TryBoundary kind:exit + ## CHECK: name "<>" ## CHECK: predecessors "<>" ## CHECK: successors "<>" @@ -121,8 +121,7 @@ goto :return .end method -# Test that multiple try-entry blocks are generated if there are multiple entry -# points into the try block. +# Tests try-entry block when there are multiple entry points into the try block. ## CHECK-START: int Builder.testMultipleEntries(int, int, int, int) builder (after) @@ -142,20 +141,20 @@ ## CHECK: name "<>" ## CHECK: predecessors "<>" -## CHECK: successors "<>" +## CHECK: successors "<>" ## CHECK: Div -## CHECK: name "<>" -## CHECK: predecessors "<>" "<>" -## CHECK: successors "<>" +## CHECK: name "<>" +## CHECK: predecessors "<>" +## CHECK: successors "<>" ## CHECK: Div ## CHECK: name "<>" -## CHECK: predecessors "<>" "<>" +## CHECK: predecessors "<>" "<>" ## CHECK: Return ## CHECK: name "<>" -## CHECK: predecessors "<>" "<>" "<>" +## CHECK: predecessors "<>" "<>" "<>" "<>" ## CHECK: successors "<>" ## CHECK: flags "catch_block" ## CHECK: StoreLocal [v0,<>] @@ -167,12 +166,18 @@ ## CHECK: TryBoundary kind:entry ## CHECK: name "<>" -## CHECK: predecessors "<>" +## CHECK: predecessors "<>" "<>" ## CHECK: successors "<>" ## CHECK: xhandlers "<>" ## CHECK: TryBoundary kind:entry -## CHECK: name "<>" +## CHECK: name "<>" +## CHECK: predecessors "<>" +## CHECK: successors "<>" +## CHECK: xhandlers "<>" +## CHECK: TryBoundary kind:exit + +## CHECK: name "<>" ## CHECK: predecessors "<>" ## CHECK: successors "<>" ## CHECK: xhandlers "<>" @@ -314,18 +319,18 @@ ## CHECK: xhandlers "<>" ## CHECK: TryBoundary kind:entry -## CHECK: name "<>" -## CHECK: predecessors "<>" -## CHECK: successors "<>" -## CHECK: xhandlers "<>" -## CHECK: TryBoundary kind:exit - ## CHECK: name "<>" ## CHECK: predecessors "<>" ## CHECK: successors "<>" ## CHECK: xhandlers "<>" ## CHECK: TryBoundary kind:entry +## CHECK: name "<>" +## CHECK: predecessors "<>" +## CHECK: successors "<>" +## CHECK: xhandlers "<>" +## CHECK: TryBoundary kind:exit + ## CHECK: name "<>" ## CHECK: predecessors "<>" ## CHECK: successors "<>" @@ -402,18 +407,18 @@ ## CHECK: xhandlers "<>" ## CHECK: TryBoundary kind:entry -## CHECK: name "<>" -## CHECK: predecessors "<>" -## CHECK: successors "<>" -## CHECK: xhandlers "<>" -## CHECK: TryBoundary kind:exit - ## CHECK: name "<>" ## CHECK: predecessors "<>" ## CHECK: successors "<>" ## CHECK: xhandlers "<>" ## CHECK: TryBoundary kind:entry +## CHECK: name "<>" +## CHECK: predecessors "<>" +## CHECK: successors "<>" +## CHECK: xhandlers "<>" +## CHECK: TryBoundary kind:exit + ## CHECK: name "<>" ## CHECK: predecessors "<>" ## CHECK: successors "<>" @@ -483,7 +488,7 @@ ## CHECK: StoreLocal [v0,<>] ## CHECK: name "<>" -## CHECK: predecessors "<>" "<>" "<>" "<>" "<>" "<>" +## CHECK: predecessors "<>" "<>" "<>" "<>" "<>" "<>" ## CHECK: successors "<>" ## CHECK: flags "catch_block" ## CHECK: StoreLocal [v0,<>] @@ -494,30 +499,30 @@ ## CHECK: xhandlers "<>" ## CHECK: TryBoundary kind:entry -## CHECK: name "<>" -## CHECK: predecessors "<>" -## CHECK: successors "<>" -## CHECK: xhandlers "<>" -## CHECK: TryBoundary kind:exit - ## CHECK: name "<>" ## CHECK: predecessors "<>" ## CHECK: successors "<>" ## CHECK: xhandlers "<>" "<>" ## CHECK: TryBoundary kind:entry -## CHECK: name "<>" -## CHECK: predecessors "<>" -## CHECK: successors "<>" -## CHECK: xhandlers "<>" "<>" -## CHECK: TryBoundary kind:exit - ## CHECK: name "<>" ## CHECK: predecessors "<>" ## CHECK: successors "<>" ## CHECK: xhandlers "<>" ## CHECK: TryBoundary kind:entry +## CHECK: name "<>" +## CHECK: predecessors "<>" +## CHECK: successors "<>" +## CHECK: xhandlers "<>" +## CHECK: TryBoundary kind:exit + +## CHECK: name "<>" +## CHECK: predecessors "<>" +## CHECK: successors "<>" +## CHECK: xhandlers "<>" "<>" +## CHECK: TryBoundary kind:exit + ## CHECK: name "<>" ## CHECK: predecessors "<>" ## CHECK: successors "<>" @@ -577,7 +582,7 @@ ## CHECK: Div ## CHECK: name "<>" -## CHECK: predecessors "<>" "<>" "<>" "<>" +## CHECK: predecessors "<>" "<>" "<>" "<>" ## CHECK: successors "<>" ## CHECK: flags "catch_block" ## CHECK: StoreLocal [v0,<>] @@ -588,18 +593,18 @@ ## CHECK: xhandlers "<>" ## CHECK: TryBoundary kind:entry -## CHECK: name "<>" -## CHECK: predecessors "<>" -## CHECK: successors "<>" -## CHECK: xhandlers "<>" -## CHECK: TryBoundary kind:exit - ## CHECK: name "<>" ## CHECK: predecessors "<>" ## CHECK: successors "<>" ## CHECK: xhandlers "<>" ## CHECK: TryBoundary kind:entry +## CHECK: name "<>" +## CHECK: predecessors "<>" +## CHECK: successors "<>" +## CHECK: xhandlers "<>" +## CHECK: TryBoundary kind:exit + ## CHECK: name "<>" ## CHECK: predecessors "<>" ## CHECK: successors "<>" @@ -647,21 +652,21 @@ ## CHECK: name "<>" ## CHECK: predecessors "<>" -## CHECK: successors "<>" +## CHECK: successors "<>" ## CHECK: Div -## CHECK: name "<>" -## CHECK: predecessors "<>" "<>" -## CHECK: successors "<>" +## CHECK: name "<>" +## CHECK: predecessors "<>" +## CHECK: successors "<>" ## CHECK: Div ## CHECK: name "<>" -## CHECK: predecessors "<>" "<>" +## CHECK: predecessors "<>" "<>" ## CHECK: successors "<>" ## CHECK: Div ## CHECK: name "<>" -## CHECK: predecessors "<>" "<>" "<>" "<>" +## CHECK: predecessors "<>" "<>" "<>" "<>" "<>" ## CHECK: flags "catch_block" ## CHECK: Return @@ -677,7 +682,13 @@ ## CHECK: xhandlers "<>" ## CHECK: TryBoundary kind:entry -## CHECK: name "<>" +## CHECK: name "<>" +## CHECK: predecessors "<>" +## CHECK: successors "<>" +## CHECK: xhandlers "<>" +## CHECK: TryBoundary kind:exit + +## CHECK: name "<>" ## CHECK: predecessors "<>" ## CHECK: successors "<>" ## CHECK: xhandlers "<>" @@ -741,7 +752,7 @@ ## CHECK: Div ## CHECK: name "<>" -## CHECK: predecessors "<>" "<>" "<>" "<>" "<>" +## CHECK: predecessors "<>" "<>" "<>" "<>" "<>" ## CHECK: flags "catch_block" ## CHECK: Return @@ -751,18 +762,18 @@ ## CHECK: xhandlers "<>" ## CHECK: TryBoundary kind:entry -## CHECK: name "<>" -## CHECK: predecessors "<>" -## CHECK: successors "<>" -## CHECK: xhandlers "<>" -## CHECK: TryBoundary kind:exit - ## CHECK: name "<>" ## CHECK: predecessors "<>" ## CHECK: successors "<>" ## CHECK: xhandlers "<>" ## CHECK: TryBoundary kind:entry +## CHECK: name "<>" +## CHECK: predecessors "<>" +## CHECK: successors "<>" +## CHECK: xhandlers "<>" +## CHECK: TryBoundary kind:exit + ## CHECK: name "<>" ## CHECK: predecessors "<>" ## CHECK: successors "<>" @@ -907,7 +918,7 @@ ## CHECK: Div ## CHECK: name "<>" -## CHECK: predecessors "<>" "<>" "<>" "<>" "<>" +## CHECK: predecessors "<>" "<>" "<>" "<>" "<>" ## CHECK: successors "<>" ## CHECK: flags "catch_block" @@ -928,18 +939,18 @@ ## CHECK: xhandlers "<>" ## CHECK: TryBoundary kind:entry -## CHECK: name "<>" -## CHECK: predecessors "<>" -## CHECK: successors "<>" -## CHECK: xhandlers "<>" -## CHECK: TryBoundary kind:exit - ## CHECK: name "<>" ## CHECK: predecessors "<>" ## CHECK: successors "<>" ## CHECK: xhandlers "<>" ## CHECK: TryBoundary kind:entry +## CHECK: name "<>" +## CHECK: predecessors "<>" +## CHECK: successors "<>" +## CHECK: xhandlers "<>" +## CHECK: TryBoundary kind:exit + ## CHECK: name "<>" ## CHECK: predecessors "<>" ## CHECK: successors "<>" @@ -1001,18 +1012,18 @@ ## CHECK: xhandlers "<>" ## CHECK: TryBoundary kind:entry -## CHECK: name "<>" -## CHECK: predecessors "<>" -## CHECK: successors "<>" -## CHECK: xhandlers "<>" -## CHECK: TryBoundary kind:exit - ## CHECK: name "<>" ## CHECK: predecessors "<>" ## CHECK: successors "<>" ## CHECK: xhandlers "<>" ## CHECK: TryBoundary kind:entry +## CHECK: name "<>" +## CHECK: predecessors "<>" +## CHECK: successors "<>" +## CHECK: xhandlers "<>" +## CHECK: TryBoundary kind:exit + ## CHECK: name "<>" ## CHECK: predecessors "<>" ## CHECK: successors "<>" @@ -1037,6 +1048,52 @@ return p0 .end method +# Test graph with try/catch inside a loop. + +## CHECK-START: int Builder.testTryInLoop(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: name "<>" +## CHECK-NOT: predecessors "{{B\d+}}" +## CHECK: end_block + +## CHECK: name "<>" +## CHECK: predecessors "B0" +## CHECK: successors "<>" +## CHECK: xhandlers "<>" +## CHECK: TryBoundary kind:entry + +## CHECK: name "<>" +## CHECK: predecessors "<>" +## CHECK: successors "<>" +## CHECK: xhandlers "<>" +## CHECK: TryBoundary kind:exit + +.method public static testTryInLoop(II)I + .registers 3 + + :try_start + div-int/2addr p0, p1 + goto :try_start + :try_end + .catchall {:try_start .. :try_end} :catch_all + + :catch_all + goto :try_start +.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. -- 2.11.0