diff options
108 files changed, 3734 insertions, 984 deletions
diff --git a/compiler/Android.mk b/compiler/Android.mk index 41e9744777..96e13ac9a3 100644 --- a/compiler/Android.mk +++ b/compiler/Android.mk @@ -158,6 +158,7 @@ LIBART_COMPILER_SRC_FILES_mips64 := \ $(LIBART_COMPILER_SRC_FILES_mips) \ jni/quick/mips64/calling_convention_mips64.cc \ optimizing/code_generator_mips64.cc \ + optimizing/intrinsics_mips64.cc \ utils/mips64/assembler_mips64.cc \ utils/mips64/managed_register_mips64.cc \ diff --git a/compiler/dex/quick/quick_compiler.cc b/compiler/dex/quick/quick_compiler.cc index b5ecf9c418..1cd742abac 100644 --- a/compiler/dex/quick/quick_compiler.cc +++ b/compiler/dex/quick/quick_compiler.cc @@ -391,9 +391,9 @@ static int kAllOpcodes[] = { Instruction::IGET_SHORT_QUICK, Instruction::INVOKE_LAMBDA, Instruction::UNUSED_F4, - Instruction::UNUSED_F5, + Instruction::CAPTURE_VARIABLE, Instruction::CREATE_LAMBDA, - Instruction::UNUSED_F7, + Instruction::LIBERATE_VARIABLE, Instruction::BOX_LAMBDA, Instruction::UNBOX_LAMBDA, Instruction::UNUSED_FA, diff --git a/compiler/optimizing/builder.cc b/compiler/optimizing/builder.cc index 9d70124a4c..7ae405ab3a 100644 --- a/compiler/optimizing/builder.cc +++ b/compiler/optimizing/builder.cc @@ -140,11 +140,11 @@ class SwitchTable : public ValueObject { void HGraphBuilder::InitializeLocals(uint16_t count) { graph_->SetNumberOfVRegs(count); - locals_.SetSize(count); + locals_.resize(count); for (int i = 0; i < count; i++) { HLocal* local = new (arena_) HLocal(i); entry_block_->AddInstruction(local); - locals_.Put(i, local); + locals_[i] = local; } } @@ -156,7 +156,7 @@ void HGraphBuilder::InitializeParameters(uint16_t number_of_parameters) { graph_->SetNumberOfInVRegs(number_of_parameters); const char* shorty = dex_compilation_unit_->GetShorty(); - int locals_index = locals_.Size() - number_of_parameters; + int locals_index = locals_.size() - number_of_parameters; int parameter_index = 0; if (!dex_compilation_unit_->IsStatic()) { @@ -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<uint32_t, const DexFile::TryItem*>& 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<uint32_t, const DexFile::TryItem*> try_block_info( + std::less<uint32_t>(), 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); } } } @@ -554,11 +509,11 @@ void HGraphBuilder::MaybeUpdateCurrentBlock(size_t dex_pc) { bool HGraphBuilder::ComputeBranchTargets(const uint16_t* code_ptr, const uint16_t* code_end, size_t* number_of_branches) { - branch_targets_.SetSize(code_end - code_ptr); + branch_targets_.resize(code_end - code_ptr, nullptr); // Create the first block for the dex instructions, single successor of the entry block. HBasicBlock* block = new (arena_) HBasicBlock(graph_, 0); - branch_targets_.Put(0, block); + branch_targets_[0] = block; entry_block_->AddSuccessor(block); // Iterate over all instructions and find branching instructions. Create blocks for @@ -602,7 +557,7 @@ bool HGraphBuilder::ComputeBranchTargets(const uint16_t* code_ptr, // Create a block for the switch-case logic. The block gets the dex_pc // of the SWITCH instruction because it is part of its semantics. block = new (arena_) HBasicBlock(graph_, dex_pc); - branch_targets_.Put(table.GetDexPcForIndex(i), block); + branch_targets_[table.GetDexPcForIndex(i)] = block; } // Fall-through. Add a block if there is more code afterwards. @@ -626,15 +581,15 @@ bool HGraphBuilder::ComputeBranchTargets(const uint16_t* code_ptr, HBasicBlock* HGraphBuilder::FindBlockStartingAt(int32_t dex_pc) const { DCHECK_GE(dex_pc, 0); - DCHECK_LT(static_cast<size_t>(dex_pc), branch_targets_.Size()); - return branch_targets_.Get(dex_pc); + DCHECK_LT(static_cast<size_t>(dex_pc), branch_targets_.size()); + return branch_targets_[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); + branch_targets_[dex_pc] = block; } return block; } @@ -1738,8 +1693,14 @@ void HGraphBuilder::BuildPackedSwitch(const Instruction& instruction, uint32_t d } else { // Chained cmp-and-branch, starting from starting_key. for (size_t i = 1; i <= num_entries; i++) { - BuildSwitchCaseHelper(instruction, i, i == num_entries, table, value, - starting_key + i - 1, table.GetEntryAt(i), dex_pc); + BuildSwitchCaseHelper(instruction, + i, + i == num_entries, + table, + value, + starting_key + i - 1, + table.GetEntryAt(i), + dex_pc); } } } @@ -2874,18 +2835,19 @@ bool HGraphBuilder::AnalyzeDexInstruction(const Instruction& instruction, uint32 return true; } // NOLINT(readability/fn_size) -HLocal* HGraphBuilder::GetLocalAt(int register_index) const { - return locals_.Get(register_index); +HLocal* HGraphBuilder::GetLocalAt(uint32_t register_index) const { + DCHECK_LT(register_index, locals_.size()); + return locals_[register_index]; } -void HGraphBuilder::UpdateLocal(int register_index, +void HGraphBuilder::UpdateLocal(uint32_t register_index, HInstruction* instruction, uint32_t dex_pc) const { HLocal* local = GetLocalAt(register_index); current_block_->AddInstruction(new (arena_) HStoreLocal(local, instruction, dex_pc)); } -HInstruction* HGraphBuilder::LoadLocal(int register_index, +HInstruction* HGraphBuilder::LoadLocal(uint32_t register_index, Primitive::Type type, uint32_t dex_pc) const { HLocal* local = GetLocalAt(register_index); diff --git a/compiler/optimizing/builder.h b/compiler/optimizing/builder.h index 7f87df6df2..4c8e3d0442 100644 --- a/compiler/optimizing/builder.h +++ b/compiler/optimizing/builder.h @@ -17,6 +17,7 @@ #ifndef ART_COMPILER_OPTIMIZING_BUILDER_H_ #define ART_COMPILER_OPTIMIZING_BUILDER_H_ +#include "base/arena_containers.h" #include "base/arena_object.h" #include "dex_file.h" #include "dex_file-inl.h" @@ -24,7 +25,6 @@ #include "driver/dex_compilation_unit.h" #include "optimizing_compiler_stats.h" #include "primitive.h" -#include "utils/growable_array.h" #include "nodes.h" namespace art { @@ -43,8 +43,8 @@ class HGraphBuilder : public ValueObject { const uint8_t* interpreter_metadata, Handle<mirror::DexCache> dex_cache) : arena_(graph->GetArena()), - branch_targets_(graph->GetArena(), 0), - locals_(graph->GetArena(), 0), + branch_targets_(graph->GetArena()->Adapter(kArenaAllocGraphBuilder)), + locals_(graph->GetArena()->Adapter(kArenaAllocGraphBuilder)), entry_block_(nullptr), exit_block_(nullptr), current_block_(nullptr), @@ -64,8 +64,8 @@ class HGraphBuilder : public ValueObject { // Only for unit testing. HGraphBuilder(HGraph* graph, Primitive::Type return_type = Primitive::kPrimInt) : arena_(graph->GetArena()), - branch_targets_(graph->GetArena(), 0), - locals_(graph->GetArena(), 0), + branch_targets_(graph->GetArena()->Adapter(kArenaAllocGraphBuilder)), + locals_(graph->GetArena()->Adapter(kArenaAllocGraphBuilder)), entry_block_(nullptr), exit_block_(nullptr), current_block_(nullptr), @@ -121,21 +121,21 @@ 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); void InitializeLocals(uint16_t count); - HLocal* GetLocalAt(int register_index) const; - void UpdateLocal(int register_index, HInstruction* instruction, uint32_t dex_pc) const; - HInstruction* LoadLocal(int register_index, Primitive::Type type, uint32_t dex_pc) const; + HLocal* GetLocalAt(uint32_t register_index) const; + void UpdateLocal(uint32_t register_index, HInstruction* instruction, uint32_t dex_pc) const; + HInstruction* LoadLocal(uint32_t register_index, Primitive::Type type, uint32_t dex_pc) const; void PotentiallyAddSuspendCheck(HBasicBlock* target, uint32_t dex_pc); void InitializeParameters(uint16_t number_of_parameters); bool NeedsAccessCheck(uint32_t type_index) const; @@ -313,9 +313,9 @@ class HGraphBuilder : public ValueObject { // A list of the size of the dex code holding block information for // the method. If an entry contains a block, then the dex instruction // starting at that entry is the first instruction of a new block. - GrowableArray<HBasicBlock*> branch_targets_; + ArenaVector<HBasicBlock*> branch_targets_; - GrowableArray<HLocal*> locals_; + ArenaVector<HLocal*> locals_; HBasicBlock* entry_block_; HBasicBlock* exit_block_; diff --git a/compiler/optimizing/code_generator_mips64.cc b/compiler/optimizing/code_generator_mips64.cc index 8fdd56e0bc..25ef3880bd 100644 --- a/compiler/optimizing/code_generator_mips64.cc +++ b/compiler/optimizing/code_generator_mips64.cc @@ -20,7 +20,9 @@ #include "entrypoints/quick/quick_entrypoints_enum.h" #include "gc/accounting/card_table.h" #include "intrinsics.h" +#include "intrinsics_mips64.h" #include "art_method.h" +#include "code_generator_utils.h" #include "mirror/array-inl.h" #include "mirror/class-inl.h" #include "offsets.h" @@ -36,7 +38,6 @@ static constexpr int kCurrentMethodStackOffset = 0; static constexpr GpuRegister kMethodRegisterArgument = A0; // We need extra temporary/scratch registers (in addition to AT) in some cases. -static constexpr GpuRegister TMP = T8; static constexpr FpuRegister FTMP = F8; // ART Thread Register. @@ -2395,7 +2396,11 @@ void InstructionCodeGeneratorMIPS64::VisitInvokeInterface(HInvokeInterface* invo } void LocationsBuilderMIPS64::VisitInvokeVirtual(HInvokeVirtual* invoke) { - // TODO intrinsic function + IntrinsicLocationsBuilderMIPS64 intrinsic(codegen_); + if (intrinsic.TryDispatch(invoke)) { + return; + } + HandleInvoke(invoke); } @@ -2404,7 +2409,11 @@ void LocationsBuilderMIPS64::VisitInvokeStaticOrDirect(HInvokeStaticOrDirect* in // invokes must have been pruned by art::PrepareForRegisterAllocation. DCHECK(codegen_->IsBaseline() || !invoke->IsStaticWithExplicitClinitCheck()); - // TODO - intrinsic function + IntrinsicLocationsBuilderMIPS64 intrinsic(codegen_); + if (intrinsic.TryDispatch(invoke)) { + return; + } + HandleInvoke(invoke); // While SetupBlockedRegisters() blocks registers S2-S8 due to their @@ -2419,10 +2428,10 @@ void LocationsBuilderMIPS64::VisitInvokeStaticOrDirect(HInvokeStaticOrDirect* in } } -static bool TryGenerateIntrinsicCode(HInvoke* invoke, - CodeGeneratorMIPS64* codegen ATTRIBUTE_UNUSED) { +static bool TryGenerateIntrinsicCode(HInvoke* invoke, CodeGeneratorMIPS64* codegen) { if (invoke->GetLocations()->Intrinsified()) { - // TODO - intrinsic function + IntrinsicCodeGeneratorMIPS64 intrinsic(codegen); + intrinsic.Dispatch(invoke); return true; } return false; @@ -2531,7 +2540,10 @@ void InstructionCodeGeneratorMIPS64::VisitInvokeStaticOrDirect(HInvokeStaticOrDi } void InstructionCodeGeneratorMIPS64::VisitInvokeVirtual(HInvokeVirtual* invoke) { - // TODO: Try to generate intrinsics code. + if (TryGenerateIntrinsicCode(invoke, codegen_)) { + return; + } + LocationSummary* locations = invoke->GetLocations(); Location receiver = locations->InAt(0); GpuRegister temp = invoke->GetLocations()->GetTemp(0).AsRegister<GpuRegister>(); diff --git a/compiler/optimizing/dead_code_elimination.cc b/compiler/optimizing/dead_code_elimination.cc index 345ff72148..007d0e3332 100644 --- a/compiler/optimizing/dead_code_elimination.cc +++ b/compiler/optimizing/dead_code_elimination.cc @@ -16,49 +16,67 @@ #include "dead_code_elimination.h" +#include "utils/array_ref.h" #include "base/bit_vector-inl.h" #include "ssa_phi_elimination.h" namespace art { -static void MarkReachableBlocks(HBasicBlock* block, ArenaBitVector* visited) { - int block_id = block->GetBlockId(); - if (visited->IsBitSet(block_id)) { - return; - } - visited->SetBit(block_id); - - HInstruction* last_instruction = block->GetLastInstruction(); - if (last_instruction->IsIf()) { - HIf* if_instruction = last_instruction->AsIf(); - HInstruction* condition = if_instruction->InputAt(0); - if (!condition->IsIntConstant()) { - MarkReachableBlocks(if_instruction->IfTrueSuccessor(), visited); - MarkReachableBlocks(if_instruction->IfFalseSuccessor(), visited); - } else if (condition->AsIntConstant()->IsOne()) { - MarkReachableBlocks(if_instruction->IfTrueSuccessor(), visited); - } else { - DCHECK(condition->AsIntConstant()->IsZero()); - MarkReachableBlocks(if_instruction->IfFalseSuccessor(), visited); - } - } else if (last_instruction->IsPackedSwitch() && - last_instruction->AsPackedSwitch()->InputAt(0)->IsIntConstant()) { - HPackedSwitch* switch_instruction = last_instruction->AsPackedSwitch(); - int32_t switch_value = switch_instruction->InputAt(0)->AsIntConstant()->GetValue(); - int32_t start_value = switch_instruction->GetStartValue(); - int32_t last_value = start_value + switch_instruction->GetNumEntries(); - for (int32_t case_value = start_value; case_value <= last_value; case_value++) { - if (case_value == last_value) { - MarkReachableBlocks(switch_instruction->GetDefaultBlock(), visited); +static void MarkReachableBlocks(HGraph* graph, ArenaBitVector* visited) { + ArenaVector<HBasicBlock*> worklist(graph->GetArena()->Adapter()); + constexpr size_t kDefaultWorlistSize = 8; + worklist.reserve(kDefaultWorlistSize); + visited->SetBit(graph->GetEntryBlock()->GetBlockId()); + worklist.push_back(graph->GetEntryBlock()); + + while (!worklist.empty()) { + HBasicBlock* block = worklist.back(); + worklist.pop_back(); + int block_id = block->GetBlockId(); + DCHECK(visited->IsBitSet(block_id)); + + ArrayRef<HBasicBlock* const> live_successors(block->GetSuccessors()); + HInstruction* last_instruction = block->GetLastInstruction(); + if (last_instruction->IsIf()) { + HIf* if_instruction = last_instruction->AsIf(); + HInstruction* condition = if_instruction->InputAt(0); + if (condition->IsIntConstant()) { + if (condition->AsIntConstant()->IsOne()) { + live_successors = live_successors.SubArray(0u, 1u); + DCHECK_EQ(live_successors[0], if_instruction->IfTrueSuccessor()); + } else { + DCHECK(condition->AsIntConstant()->IsZero()); + live_successors = live_successors.SubArray(1u, 1u); + DCHECK_EQ(live_successors[0], if_instruction->IfFalseSuccessor()); + } } - if (case_value == switch_value) { - MarkReachableBlocks(block->GetSuccessor(case_value - start_value), visited); - break; + } else if (last_instruction->IsPackedSwitch()) { + HPackedSwitch* switch_instruction = last_instruction->AsPackedSwitch(); + HInstruction* switch_input = switch_instruction->InputAt(0); + if (switch_input->IsIntConstant()) { + int32_t switch_value = switch_input->AsIntConstant()->GetValue(); + int32_t start_value = switch_instruction->GetStartValue(); + // Note: Though the spec forbids packed-switch values to wrap around, we leave + // that task to the verifier and use unsigned arithmetic with it's "modulo 2^32" + // semantics to check if the value is in range, wrapped or not. + uint32_t switch_index = + static_cast<uint32_t>(switch_value) - static_cast<uint32_t>(start_value); + if (switch_index < switch_instruction->GetNumEntries()) { + live_successors = live_successors.SubArray(switch_index, 1u); + DCHECK_EQ(live_successors[0], block->GetSuccessor(switch_index)); + } else { + live_successors = live_successors.SubArray(switch_instruction->GetNumEntries(), 1u); + DCHECK_EQ(live_successors[0], switch_instruction->GetDefaultBlock()); + } } } - } else { - for (HBasicBlock* successor : block->GetSuccessors()) { - MarkReachableBlocks(successor, visited); + + for (HBasicBlock* successor : live_successors) { + // Add only those successors that have not been visited yet. + if (!visited->IsBitSet(successor->GetBlockId())) { + visited->SetBit(successor->GetBlockId()); + worklist.push_back(successor); + } } } } @@ -82,7 +100,7 @@ void HDeadCodeElimination::RemoveDeadBlocks() { ArenaBitVector live_blocks(allocator, graph_->GetBlocks().size(), false); ArenaBitVector affected_loops(allocator, graph_->GetBlocks().size(), false); - MarkReachableBlocks(graph_->GetEntryBlock(), &live_blocks); + MarkReachableBlocks(graph_, &live_blocks); bool removed_one_or_more_blocks = false; // Remove all dead blocks. Iterate in post order because removal needs the diff --git a/compiler/optimizing/gvn.cc b/compiler/optimizing/gvn.cc index 1ee8648533..5050e155f5 100644 --- a/compiler/optimizing/gvn.cc +++ b/compiler/optimizing/gvn.cc @@ -15,11 +15,12 @@ */ #include "gvn.h" + +#include "base/arena_containers.h" +#include "base/bit_vector-inl.h" #include "side_effects_analysis.h" #include "utils.h" - #include "utils/arena_bit_vector.h" -#include "base/bit_vector-inl.h" namespace art { @@ -32,7 +33,7 @@ namespace art { * if there is one in the set. In GVN, we would say those instructions have the * same "number". */ -class ValueSet : public ArenaObject<kArenaAllocMisc> { +class ValueSet : public ArenaObject<kArenaAllocGvn> { public: // Constructs an empty ValueSet which owns all its buckets. explicit ValueSet(ArenaAllocator* allocator) @@ -143,7 +144,7 @@ class ValueSet : public ArenaObject<kArenaAllocMisc> { size_t GetNumberOfEntries() const { return num_entries_; } private: - class Node : public ArenaObject<kArenaAllocMisc> { + class Node : public ArenaObject<kArenaAllocGvn> { public: Node(HInstruction* instruction, size_t hash_code, Node* next) : instruction_(instruction), hash_code_(hash_code), next_(next) {} @@ -306,7 +307,7 @@ class GlobalValueNumberer : public ValueObject { : graph_(graph), allocator_(allocator), side_effects_(side_effects), - sets_(allocator, graph->GetBlocks().size(), nullptr) {} + sets_(graph->GetBlocks().size(), nullptr, allocator->Adapter(kArenaAllocGvn)) {} void Run(); @@ -322,14 +323,14 @@ class GlobalValueNumberer : public ValueObject { // ValueSet for blocks. Initially null, but for an individual block they // are allocated and populated by the dominator, and updated by all blocks // in the path from the dominator to the block. - GrowableArray<ValueSet*> sets_; + ArenaVector<ValueSet*> sets_; DISALLOW_COPY_AND_ASSIGN(GlobalValueNumberer); }; void GlobalValueNumberer::Run() { DCHECK(side_effects_.HasRun()); - sets_.Put(graph_->GetEntryBlock()->GetBlockId(), new (allocator_) ValueSet(allocator_)); + sets_[graph_->GetEntryBlock()->GetBlockId()] = new (allocator_) ValueSet(allocator_); // Use the reverse post order to ensure the non back-edge predecessors of a block are // visited before the block itself. @@ -348,7 +349,7 @@ void GlobalValueNumberer::VisitBasicBlock(HBasicBlock* block) { set = new (allocator_) ValueSet(allocator_); } else { HBasicBlock* dominator = block->GetDominator(); - ValueSet* dominator_set = sets_.Get(dominator->GetBlockId()); + ValueSet* dominator_set = sets_[dominator->GetBlockId()]; if (dominator->GetSuccessors().size() == 1) { DCHECK_EQ(dominator->GetSuccessor(0), block); set = dominator_set; @@ -363,7 +364,7 @@ void GlobalValueNumberer::VisitBasicBlock(HBasicBlock* block) { set->Kill(side_effects_.GetLoopEffects(block)); } else if (predecessors.size() > 1) { for (HBasicBlock* predecessor : predecessors) { - set->IntersectWith(sets_.Get(predecessor->GetBlockId())); + set->IntersectWith(sets_[predecessor->GetBlockId()]); if (set->IsEmpty()) { break; } @@ -372,7 +373,7 @@ void GlobalValueNumberer::VisitBasicBlock(HBasicBlock* block) { } } - sets_.Put(block->GetBlockId(), set); + sets_[block->GetBlockId()] = set; HInstruction* current = block->GetFirstInstruction(); while (current != nullptr) { diff --git a/compiler/optimizing/intrinsics.cc b/compiler/optimizing/intrinsics.cc index b71fdb8f1d..95646222ef 100644 --- a/compiler/optimizing/intrinsics.cc +++ b/compiler/optimizing/intrinsics.cc @@ -90,7 +90,7 @@ static Primitive::Type GetType(uint64_t data, bool is_op_size) { } static Intrinsics GetIntrinsic(InlineMethod method, InstructionSet instruction_set) { - if (instruction_set == kMips || instruction_set == kMips64) { + if (instruction_set == kMips) { return Intrinsics::kNone; } switch (method.opcode) { diff --git a/compiler/optimizing/intrinsics_mips64.cc b/compiler/optimizing/intrinsics_mips64.cc new file mode 100644 index 0000000000..52e2cbec34 --- /dev/null +++ b/compiler/optimizing/intrinsics_mips64.cc @@ -0,0 +1,782 @@ +/* + * 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. + */ + +#include "intrinsics_mips64.h" + +#include "arch/mips64/instruction_set_features_mips64.h" +#include "art_method.h" +#include "code_generator_mips64.h" +#include "entrypoints/quick/quick_entrypoints.h" +#include "intrinsics.h" +#include "mirror/array-inl.h" +#include "mirror/string.h" +#include "thread.h" +#include "utils/mips64/assembler_mips64.h" +#include "utils/mips64/constants_mips64.h" + +namespace art { + +namespace mips64 { + +IntrinsicLocationsBuilderMIPS64::IntrinsicLocationsBuilderMIPS64(CodeGeneratorMIPS64* codegen) + : arena_(codegen->GetGraph()->GetArena()) { +} + +Mips64Assembler* IntrinsicCodeGeneratorMIPS64::GetAssembler() { + return reinterpret_cast<Mips64Assembler*>(codegen_->GetAssembler()); +} + +ArenaAllocator* IntrinsicCodeGeneratorMIPS64::GetAllocator() { + return codegen_->GetGraph()->GetArena(); +} + +bool IntrinsicLocationsBuilderMIPS64::TryDispatch(HInvoke* invoke) { + Dispatch(invoke); + LocationSummary* res = invoke->GetLocations(); + return res != nullptr && res->Intrinsified(); +} + +#define __ assembler-> + +static void CreateFPToIntLocations(ArenaAllocator* arena, HInvoke* invoke) { + LocationSummary* locations = new (arena) LocationSummary(invoke, + LocationSummary::kNoCall, + kIntrinsified); + locations->SetInAt(0, Location::RequiresFpuRegister()); + locations->SetOut(Location::RequiresRegister()); +} + +static void MoveFPToInt(LocationSummary* locations, bool is64bit, Mips64Assembler* assembler) { + FpuRegister in = locations->InAt(0).AsFpuRegister<FpuRegister>(); + GpuRegister out = locations->Out().AsRegister<GpuRegister>(); + + if (is64bit) { + __ Dmfc1(out, in); + } else { + __ Mfc1(out, in); + } +} + +// long java.lang.Double.doubleToRawLongBits(double) +void IntrinsicLocationsBuilderMIPS64::VisitDoubleDoubleToRawLongBits(HInvoke* invoke) { + CreateFPToIntLocations(arena_, invoke); +} + +void IntrinsicCodeGeneratorMIPS64::VisitDoubleDoubleToRawLongBits(HInvoke* invoke) { + MoveFPToInt(invoke->GetLocations(), true, GetAssembler()); +} + +// int java.lang.Float.floatToRawIntBits(float) +void IntrinsicLocationsBuilderMIPS64::VisitFloatFloatToRawIntBits(HInvoke* invoke) { + CreateFPToIntLocations(arena_, invoke); +} + +void IntrinsicCodeGeneratorMIPS64::VisitFloatFloatToRawIntBits(HInvoke* invoke) { + MoveFPToInt(invoke->GetLocations(), false, GetAssembler()); +} + +static void CreateIntToFPLocations(ArenaAllocator* arena, HInvoke* invoke) { + LocationSummary* locations = new (arena) LocationSummary(invoke, + LocationSummary::kNoCall, + kIntrinsified); + locations->SetInAt(0, Location::RequiresRegister()); + locations->SetOut(Location::RequiresFpuRegister()); +} + +static void MoveIntToFP(LocationSummary* locations, bool is64bit, Mips64Assembler* assembler) { + GpuRegister in = locations->InAt(0).AsRegister<GpuRegister>(); + FpuRegister out = locations->Out().AsFpuRegister<FpuRegister>(); + + if (is64bit) { + __ Dmtc1(in, out); + } else { + __ Mtc1(in, out); + } +} + +// double java.lang.Double.longBitsToDouble(long) +void IntrinsicLocationsBuilderMIPS64::VisitDoubleLongBitsToDouble(HInvoke* invoke) { + CreateIntToFPLocations(arena_, invoke); +} + +void IntrinsicCodeGeneratorMIPS64::VisitDoubleLongBitsToDouble(HInvoke* invoke) { + MoveIntToFP(invoke->GetLocations(), true, GetAssembler()); +} + +// float java.lang.Float.intBitsToFloat(int) +void IntrinsicLocationsBuilderMIPS64::VisitFloatIntBitsToFloat(HInvoke* invoke) { + CreateIntToFPLocations(arena_, invoke); +} + +void IntrinsicCodeGeneratorMIPS64::VisitFloatIntBitsToFloat(HInvoke* invoke) { + MoveIntToFP(invoke->GetLocations(), false, GetAssembler()); +} + +static void CreateIntToIntLocations(ArenaAllocator* arena, HInvoke* invoke) { + LocationSummary* locations = new (arena) LocationSummary(invoke, + LocationSummary::kNoCall, + kIntrinsified); + locations->SetInAt(0, Location::RequiresRegister()); + locations->SetOut(Location::RequiresRegister(), Location::kNoOutputOverlap); +} + +static void GenReverseBytes(LocationSummary* locations, + Primitive::Type type, + Mips64Assembler* assembler) { + GpuRegister in = locations->InAt(0).AsRegister<GpuRegister>(); + GpuRegister out = locations->Out().AsRegister<GpuRegister>(); + + switch (type) { + case Primitive::kPrimShort: + __ Dsbh(out, in); + __ Seh(out, out); + break; + case Primitive::kPrimInt: + __ Rotr(out, in, 16); + __ Wsbh(out, out); + break; + case Primitive::kPrimLong: + __ Dsbh(out, in); + __ Dshd(out, out); + break; + default: + LOG(FATAL) << "Unexpected size for reverse-bytes: " << type; + UNREACHABLE(); + } +} + +// int java.lang.Integer.reverseBytes(int) +void IntrinsicLocationsBuilderMIPS64::VisitIntegerReverseBytes(HInvoke* invoke) { + CreateIntToIntLocations(arena_, invoke); +} + +void IntrinsicCodeGeneratorMIPS64::VisitIntegerReverseBytes(HInvoke* invoke) { + GenReverseBytes(invoke->GetLocations(), Primitive::kPrimInt, GetAssembler()); +} + +// long java.lang.Long.reverseBytes(long) +void IntrinsicLocationsBuilderMIPS64::VisitLongReverseBytes(HInvoke* invoke) { + CreateIntToIntLocations(arena_, invoke); +} + +void IntrinsicCodeGeneratorMIPS64::VisitLongReverseBytes(HInvoke* invoke) { + GenReverseBytes(invoke->GetLocations(), Primitive::kPrimLong, GetAssembler()); +} + +// short java.lang.Short.reverseBytes(short) +void IntrinsicLocationsBuilderMIPS64::VisitShortReverseBytes(HInvoke* invoke) { + CreateIntToIntLocations(arena_, invoke); +} + +void IntrinsicCodeGeneratorMIPS64::VisitShortReverseBytes(HInvoke* invoke) { + GenReverseBytes(invoke->GetLocations(), Primitive::kPrimShort, GetAssembler()); +} + +static void GenCountZeroes(LocationSummary* locations, bool is64bit, Mips64Assembler* assembler) { + GpuRegister in = locations->InAt(0).AsRegister<GpuRegister>(); + GpuRegister out = locations->Out().AsRegister<GpuRegister>(); + + if (is64bit) { + __ Dclz(out, in); + } else { + __ Clz(out, in); + } +} + +// int java.lang.Integer.numberOfLeadingZeros(int i) +void IntrinsicLocationsBuilderMIPS64::VisitIntegerNumberOfLeadingZeros(HInvoke* invoke) { + CreateIntToIntLocations(arena_, invoke); +} + +void IntrinsicCodeGeneratorMIPS64::VisitIntegerNumberOfLeadingZeros(HInvoke* invoke) { + GenCountZeroes(invoke->GetLocations(), false, GetAssembler()); +} + +// int java.lang.Long.numberOfLeadingZeros(long i) +void IntrinsicLocationsBuilderMIPS64::VisitLongNumberOfLeadingZeros(HInvoke* invoke) { + CreateIntToIntLocations(arena_, invoke); +} + +void IntrinsicCodeGeneratorMIPS64::VisitLongNumberOfLeadingZeros(HInvoke* invoke) { + GenCountZeroes(invoke->GetLocations(), true, GetAssembler()); +} + +static void GenReverse(LocationSummary* locations, + Primitive::Type type, + Mips64Assembler* assembler) { + DCHECK(type == Primitive::kPrimInt || type == Primitive::kPrimLong); + + GpuRegister in = locations->InAt(0).AsRegister<GpuRegister>(); + GpuRegister out = locations->Out().AsRegister<GpuRegister>(); + + if (type == Primitive::kPrimInt) { + __ Rotr(out, in, 16); + __ Wsbh(out, out); + __ Bitswap(out, out); + } else { + __ Dsbh(out, in); + __ Dshd(out, out); + __ Dbitswap(out, out); + } +} + +// int java.lang.Integer.reverse(int) +void IntrinsicLocationsBuilderMIPS64::VisitIntegerReverse(HInvoke* invoke) { + CreateIntToIntLocations(arena_, invoke); +} + +void IntrinsicCodeGeneratorMIPS64::VisitIntegerReverse(HInvoke* invoke) { + GenReverse(invoke->GetLocations(), Primitive::kPrimInt, GetAssembler()); +} + +// long java.lang.Long.reverse(long) +void IntrinsicLocationsBuilderMIPS64::VisitLongReverse(HInvoke* invoke) { + CreateIntToIntLocations(arena_, invoke); +} + +void IntrinsicCodeGeneratorMIPS64::VisitLongReverse(HInvoke* invoke) { + GenReverse(invoke->GetLocations(), Primitive::kPrimLong, GetAssembler()); +} + +static void CreateFPToFPLocations(ArenaAllocator* arena, HInvoke* invoke) { + LocationSummary* locations = new (arena) LocationSummary(invoke, + LocationSummary::kNoCall, + kIntrinsified); + locations->SetInAt(0, Location::RequiresFpuRegister()); + locations->SetOut(Location::RequiresFpuRegister(), Location::kNoOutputOverlap); +} + +static void MathAbsFP(LocationSummary* locations, bool is64bit, Mips64Assembler* assembler) { + FpuRegister in = locations->InAt(0).AsFpuRegister<FpuRegister>(); + FpuRegister out = locations->Out().AsFpuRegister<FpuRegister>(); + + if (is64bit) { + __ AbsD(out, in); + } else { + __ AbsS(out, in); + } +} + +// double java.lang.Math.abs(double) +void IntrinsicLocationsBuilderMIPS64::VisitMathAbsDouble(HInvoke* invoke) { + CreateFPToFPLocations(arena_, invoke); +} + +void IntrinsicCodeGeneratorMIPS64::VisitMathAbsDouble(HInvoke* invoke) { + MathAbsFP(invoke->GetLocations(), true, GetAssembler()); +} + +// float java.lang.Math.abs(float) +void IntrinsicLocationsBuilderMIPS64::VisitMathAbsFloat(HInvoke* invoke) { + CreateFPToFPLocations(arena_, invoke); +} + +void IntrinsicCodeGeneratorMIPS64::VisitMathAbsFloat(HInvoke* invoke) { + MathAbsFP(invoke->GetLocations(), false, GetAssembler()); +} + +static void CreateIntToInt(ArenaAllocator* arena, HInvoke* invoke) { + LocationSummary* locations = new (arena) LocationSummary(invoke, + LocationSummary::kNoCall, + kIntrinsified); + locations->SetInAt(0, Location::RequiresRegister()); + locations->SetOut(Location::RequiresRegister(), Location::kNoOutputOverlap); +} + +static void GenAbsInteger(LocationSummary* locations, bool is64bit, Mips64Assembler* assembler) { + GpuRegister in = locations->InAt(0).AsRegister<GpuRegister>(); + GpuRegister out = locations->Out().AsRegister<GpuRegister>(); + + if (is64bit) { + __ Dsra32(AT, in, 31); + __ Xor(out, in, AT); + __ Dsubu(out, out, AT); + } else { + __ Sra(AT, in, 31); + __ Xor(out, in, AT); + __ Subu(out, out, AT); + } +} + +// int java.lang.Math.abs(int) +void IntrinsicLocationsBuilderMIPS64::VisitMathAbsInt(HInvoke* invoke) { + CreateIntToInt(arena_, invoke); +} + +void IntrinsicCodeGeneratorMIPS64::VisitMathAbsInt(HInvoke* invoke) { + GenAbsInteger(invoke->GetLocations(), false, GetAssembler()); +} + +// long java.lang.Math.abs(long) +void IntrinsicLocationsBuilderMIPS64::VisitMathAbsLong(HInvoke* invoke) { + CreateIntToInt(arena_, invoke); +} + +void IntrinsicCodeGeneratorMIPS64::VisitMathAbsLong(HInvoke* invoke) { + GenAbsInteger(invoke->GetLocations(), true, GetAssembler()); +} + +static void GenMinMaxFP(LocationSummary* locations, + bool is_min, + bool is_double, + Mips64Assembler* assembler) { + FpuRegister lhs = locations->InAt(0).AsFpuRegister<FpuRegister>(); + FpuRegister rhs = locations->InAt(1).AsFpuRegister<FpuRegister>(); + FpuRegister out = locations->Out().AsFpuRegister<FpuRegister>(); + + if (is_double) { + if (is_min) { + __ MinD(out, lhs, rhs); + } else { + __ MaxD(out, lhs, rhs); + } + } else { + if (is_min) { + __ MinS(out, lhs, rhs); + } else { + __ MaxS(out, lhs, rhs); + } + } +} + +static void CreateFPFPToFPLocations(ArenaAllocator* arena, HInvoke* invoke) { + LocationSummary* locations = new (arena) LocationSummary(invoke, + LocationSummary::kNoCall, + kIntrinsified); + locations->SetInAt(0, Location::RequiresFpuRegister()); + locations->SetInAt(1, Location::RequiresFpuRegister()); + locations->SetOut(Location::RequiresFpuRegister(), Location::kNoOutputOverlap); +} + +// double java.lang.Math.min(double, double) +void IntrinsicLocationsBuilderMIPS64::VisitMathMinDoubleDouble(HInvoke* invoke) { + CreateFPFPToFPLocations(arena_, invoke); +} + +void IntrinsicCodeGeneratorMIPS64::VisitMathMinDoubleDouble(HInvoke* invoke) { + GenMinMaxFP(invoke->GetLocations(), true, true, GetAssembler()); +} + +// float java.lang.Math.min(float, float) +void IntrinsicLocationsBuilderMIPS64::VisitMathMinFloatFloat(HInvoke* invoke) { + CreateFPFPToFPLocations(arena_, invoke); +} + +void IntrinsicCodeGeneratorMIPS64::VisitMathMinFloatFloat(HInvoke* invoke) { + GenMinMaxFP(invoke->GetLocations(), true, false, GetAssembler()); +} + +// double java.lang.Math.max(double, double) +void IntrinsicLocationsBuilderMIPS64::VisitMathMaxDoubleDouble(HInvoke* invoke) { + CreateFPFPToFPLocations(arena_, invoke); +} + +void IntrinsicCodeGeneratorMIPS64::VisitMathMaxDoubleDouble(HInvoke* invoke) { + GenMinMaxFP(invoke->GetLocations(), false, true, GetAssembler()); +} + +// float java.lang.Math.max(float, float) +void IntrinsicLocationsBuilderMIPS64::VisitMathMaxFloatFloat(HInvoke* invoke) { + CreateFPFPToFPLocations(arena_, invoke); +} + +void IntrinsicCodeGeneratorMIPS64::VisitMathMaxFloatFloat(HInvoke* invoke) { + GenMinMaxFP(invoke->GetLocations(), false, false, GetAssembler()); +} + +static void GenMinMax(LocationSummary* locations, + bool is_min, + Mips64Assembler* assembler) { + GpuRegister lhs = locations->InAt(0).AsRegister<GpuRegister>(); + GpuRegister rhs = locations->InAt(1).AsRegister<GpuRegister>(); + GpuRegister out = locations->Out().AsRegister<GpuRegister>(); + + if (out == lhs) { + __ Slt(AT, rhs, lhs); + if (is_min) { + __ Seleqz(out, lhs, AT); + __ Selnez(AT, rhs, AT); + } else { + __ Selnez(out, lhs, AT); + __ Seleqz(AT, rhs, AT); + } + } else { + __ Slt(AT, lhs, rhs); + if (is_min) { + __ Seleqz(out, rhs, AT); + __ Selnez(AT, lhs, AT); + } else { + __ Selnez(out, rhs, AT); + __ Seleqz(AT, lhs, AT); + } + } + __ Or(out, out, AT); +} + +static void CreateIntIntToIntLocations(ArenaAllocator* arena, HInvoke* invoke) { + LocationSummary* locations = new (arena) LocationSummary(invoke, + LocationSummary::kNoCall, + kIntrinsified); + locations->SetInAt(0, Location::RequiresRegister()); + locations->SetInAt(1, Location::RequiresRegister()); + locations->SetOut(Location::RequiresRegister(), Location::kNoOutputOverlap); +} + +// int java.lang.Math.min(int, int) +void IntrinsicLocationsBuilderMIPS64::VisitMathMinIntInt(HInvoke* invoke) { + CreateIntIntToIntLocations(arena_, invoke); +} + +void IntrinsicCodeGeneratorMIPS64::VisitMathMinIntInt(HInvoke* invoke) { + GenMinMax(invoke->GetLocations(), true, GetAssembler()); +} + +// long java.lang.Math.min(long, long) +void IntrinsicLocationsBuilderMIPS64::VisitMathMinLongLong(HInvoke* invoke) { + CreateIntIntToIntLocations(arena_, invoke); +} + +void IntrinsicCodeGeneratorMIPS64::VisitMathMinLongLong(HInvoke* invoke) { + GenMinMax(invoke->GetLocations(), true, GetAssembler()); +} + +// int java.lang.Math.max(int, int) +void IntrinsicLocationsBuilderMIPS64::VisitMathMaxIntInt(HInvoke* invoke) { + CreateIntIntToIntLocations(arena_, invoke); +} + +void IntrinsicCodeGeneratorMIPS64::VisitMathMaxIntInt(HInvoke* invoke) { + GenMinMax(invoke->GetLocations(), false, GetAssembler()); +} + +// long java.lang.Math.max(long, long) +void IntrinsicLocationsBuilderMIPS64::VisitMathMaxLongLong(HInvoke* invoke) { + CreateIntIntToIntLocations(arena_, invoke); +} + +void IntrinsicCodeGeneratorMIPS64::VisitMathMaxLongLong(HInvoke* invoke) { + GenMinMax(invoke->GetLocations(), false, GetAssembler()); +} + +// double java.lang.Math.sqrt(double) +void IntrinsicLocationsBuilderMIPS64::VisitMathSqrt(HInvoke* invoke) { + CreateFPToFPLocations(arena_, invoke); +} + +void IntrinsicCodeGeneratorMIPS64::VisitMathSqrt(HInvoke* invoke) { + LocationSummary* locations = invoke->GetLocations(); + Mips64Assembler* assembler = GetAssembler(); + FpuRegister in = locations->InAt(0).AsFpuRegister<FpuRegister>(); + FpuRegister out = locations->Out().AsFpuRegister<FpuRegister>(); + + __ SqrtD(out, in); +} + +static void CreateFPToFP(ArenaAllocator* arena, HInvoke* invoke) { + LocationSummary* locations = new (arena) LocationSummary(invoke, + LocationSummary::kNoCall, + kIntrinsified); + locations->SetInAt(0, Location::RequiresFpuRegister()); + locations->SetOut(Location::RequiresFpuRegister(), Location::kNoOutputOverlap); +} + +// double java.lang.Math.rint(double) +void IntrinsicLocationsBuilderMIPS64::VisitMathRint(HInvoke* invoke) { + CreateFPToFP(arena_, invoke); +} + +void IntrinsicCodeGeneratorMIPS64::VisitMathRint(HInvoke* invoke) { + LocationSummary* locations = invoke->GetLocations(); + Mips64Assembler* assembler = GetAssembler(); + FpuRegister in = locations->InAt(0).AsFpuRegister<FpuRegister>(); + FpuRegister out = locations->Out().AsFpuRegister<FpuRegister>(); + + __ RintD(out, in); +} + +// double java.lang.Math.floor(double) +void IntrinsicLocationsBuilderMIPS64::VisitMathFloor(HInvoke* invoke) { + CreateFPToFP(arena_, invoke); +} + +// 0x200 - +zero +// 0x040 - +infinity +// 0x020 - -zero +// 0x004 - -infinity +// 0x002 - quiet NaN +// 0x001 - signaling NaN +const constexpr uint16_t CLASS_MASK = 0x267; + +void IntrinsicCodeGeneratorMIPS64::VisitMathFloor(HInvoke* invoke) { + LocationSummary* locations = invoke->GetLocations(); + Mips64Assembler* assembler = GetAssembler(); + FpuRegister in = locations->InAt(0).AsFpuRegister<FpuRegister>(); + FpuRegister out = locations->Out().AsFpuRegister<FpuRegister>(); + + Label done; + + // double floor(double in) { + // if in.isNaN || in.isInfinite || in.isZero { + // return in; + // } + __ ClassD(out, in); + __ Dmfc1(AT, out); + __ Andi(AT, AT, CLASS_MASK); // +0.0 | +Inf | -0.0 | -Inf | qNaN | sNaN + __ MovD(out, in); + __ Bnezc(AT, &done); + + // Long outLong = floor(in); + // if outLong == Long.MAX_VALUE { + // // floor() has almost certainly returned a value which + // // can't be successfully represented as a signed 64-bit + // // number. Java expects that the input value will be + // // returned in these cases. + // // There is also a small probability that floor(in) + // // correctly truncates the input value to Long.MAX_VALUE. In + // // that case, this exception handling code still does the + // // correct thing. + // return in; + // } + __ FloorLD(out, in); + __ Dmfc1(AT, out); + __ MovD(out, in); + __ LoadConst64(TMP, kPrimLongMax); + __ Beqc(AT, TMP, &done); + + // double out = outLong; + // return out; + __ Dmtc1(AT, out); + __ Cvtdl(out, out); + __ Bind(&done); + // } +} + +// double java.lang.Math.ceil(double) +void IntrinsicLocationsBuilderMIPS64::VisitMathCeil(HInvoke* invoke) { + CreateFPToFP(arena_, invoke); +} + +void IntrinsicCodeGeneratorMIPS64::VisitMathCeil(HInvoke* invoke) { + LocationSummary* locations = invoke->GetLocations(); + Mips64Assembler* assembler = GetAssembler(); + FpuRegister in = locations->InAt(0).AsFpuRegister<FpuRegister>(); + FpuRegister out = locations->Out().AsFpuRegister<FpuRegister>(); + + Label done; + + // double ceil(double in) { + // if in.isNaN || in.isInfinite || in.isZero { + // return in; + // } + __ ClassD(out, in); + __ Dmfc1(AT, out); + __ Andi(AT, AT, CLASS_MASK); // +0.0 | +Inf | -0.0 | -Inf | qNaN | sNaN + __ MovD(out, in); + __ Bnezc(AT, &done); + + // Long outLong = ceil(in); + // if outLong == Long.MAX_VALUE { + // // ceil() has almost certainly returned a value which + // // can't be successfully represented as a signed 64-bit + // // number. Java expects that the input value will be + // // returned in these cases. + // // There is also a small probability that ceil(in) + // // correctly rounds up the input value to Long.MAX_VALUE. In + // // that case, this exception handling code still does the + // // correct thing. + // return in; + // } + __ CeilLD(out, in); + __ Dmfc1(AT, out); + __ MovD(out, in); + __ LoadConst64(TMP, kPrimLongMax); + __ Beqc(AT, TMP, &done); + + // double out = outLong; + // return out; + __ Dmtc1(AT, out); + __ Cvtdl(out, out); + __ Bind(&done); + // } +} + +// byte libcore.io.Memory.peekByte(long address) +void IntrinsicLocationsBuilderMIPS64::VisitMemoryPeekByte(HInvoke* invoke) { + CreateIntToIntLocations(arena_, invoke); +} + +void IntrinsicCodeGeneratorMIPS64::VisitMemoryPeekByte(HInvoke* invoke) { + Mips64Assembler* assembler = GetAssembler(); + GpuRegister adr = invoke->GetLocations()->InAt(0).AsRegister<GpuRegister>(); + GpuRegister out = invoke->GetLocations()->Out().AsRegister<GpuRegister>(); + + __ Lb(out, adr, 0); +} + +// short libcore.io.Memory.peekShort(long address) +void IntrinsicLocationsBuilderMIPS64::VisitMemoryPeekShortNative(HInvoke* invoke) { + CreateIntToIntLocations(arena_, invoke); +} + +void IntrinsicCodeGeneratorMIPS64::VisitMemoryPeekShortNative(HInvoke* invoke) { + Mips64Assembler* assembler = GetAssembler(); + GpuRegister adr = invoke->GetLocations()->InAt(0).AsRegister<GpuRegister>(); + GpuRegister out = invoke->GetLocations()->Out().AsRegister<GpuRegister>(); + + __ Lh(out, adr, 0); +} + +// int libcore.io.Memory.peekInt(long address) +void IntrinsicLocationsBuilderMIPS64::VisitMemoryPeekIntNative(HInvoke* invoke) { + CreateIntToIntLocations(arena_, invoke); +} + +void IntrinsicCodeGeneratorMIPS64::VisitMemoryPeekIntNative(HInvoke* invoke) { + Mips64Assembler* assembler = GetAssembler(); + GpuRegister adr = invoke->GetLocations()->InAt(0).AsRegister<GpuRegister>(); + GpuRegister out = invoke->GetLocations()->Out().AsRegister<GpuRegister>(); + + __ Lw(out, adr, 0); +} + +// long libcore.io.Memory.peekLong(long address) +void IntrinsicLocationsBuilderMIPS64::VisitMemoryPeekLongNative(HInvoke* invoke) { + CreateIntToIntLocations(arena_, invoke); +} + +void IntrinsicCodeGeneratorMIPS64::VisitMemoryPeekLongNative(HInvoke* invoke) { + Mips64Assembler* assembler = GetAssembler(); + GpuRegister adr = invoke->GetLocations()->InAt(0).AsRegister<GpuRegister>(); + GpuRegister out = invoke->GetLocations()->Out().AsRegister<GpuRegister>(); + + __ Ld(out, adr, 0); +} + +static void CreateIntIntToVoidLocations(ArenaAllocator* arena, HInvoke* invoke) { + LocationSummary* locations = new (arena) LocationSummary(invoke, + LocationSummary::kNoCall, + kIntrinsified); + locations->SetInAt(0, Location::RequiresRegister()); + locations->SetInAt(1, Location::RequiresRegister()); +} + +// void libcore.io.Memory.pokeByte(long address, byte value) +void IntrinsicLocationsBuilderMIPS64::VisitMemoryPokeByte(HInvoke* invoke) { + CreateIntIntToVoidLocations(arena_, invoke); +} + +void IntrinsicCodeGeneratorMIPS64::VisitMemoryPokeByte(HInvoke* invoke) { + Mips64Assembler* assembler = GetAssembler(); + GpuRegister adr = invoke->GetLocations()->InAt(0).AsRegister<GpuRegister>(); + GpuRegister val = invoke->GetLocations()->InAt(1).AsRegister<GpuRegister>(); + + __ Sb(val, adr, 0); +} + +// void libcore.io.Memory.pokeShort(long address, short value) +void IntrinsicLocationsBuilderMIPS64::VisitMemoryPokeShortNative(HInvoke* invoke) { + CreateIntIntToVoidLocations(arena_, invoke); +} + +void IntrinsicCodeGeneratorMIPS64::VisitMemoryPokeShortNative(HInvoke* invoke) { + Mips64Assembler* assembler = GetAssembler(); + GpuRegister adr = invoke->GetLocations()->InAt(0).AsRegister<GpuRegister>(); + GpuRegister val = invoke->GetLocations()->InAt(1).AsRegister<GpuRegister>(); + + __ Sh(val, adr, 0); +} + +// void libcore.io.Memory.pokeInt(long address, int value) +void IntrinsicLocationsBuilderMIPS64::VisitMemoryPokeIntNative(HInvoke* invoke) { + CreateIntIntToVoidLocations(arena_, invoke); +} + +void IntrinsicCodeGeneratorMIPS64::VisitMemoryPokeIntNative(HInvoke* invoke) { + Mips64Assembler* assembler = GetAssembler(); + GpuRegister adr = invoke->GetLocations()->InAt(0).AsRegister<GpuRegister>(); + GpuRegister val = invoke->GetLocations()->InAt(1).AsRegister<GpuRegister>(); + + __ Sw(val, adr, 00); +} + +// void libcore.io.Memory.pokeLong(long address, long value) +void IntrinsicLocationsBuilderMIPS64::VisitMemoryPokeLongNative(HInvoke* invoke) { + CreateIntIntToVoidLocations(arena_, invoke); +} + +void IntrinsicCodeGeneratorMIPS64::VisitMemoryPokeLongNative(HInvoke* invoke) { + Mips64Assembler* assembler = GetAssembler(); + GpuRegister adr = invoke->GetLocations()->InAt(0).AsRegister<GpuRegister>(); + GpuRegister val = invoke->GetLocations()->InAt(1).AsRegister<GpuRegister>(); + + __ Sd(val, adr, 0); +} + +// Unimplemented intrinsics. + +#define UNIMPLEMENTED_INTRINSIC(Name) \ +void IntrinsicLocationsBuilderMIPS64::Visit ## Name(HInvoke* invoke ATTRIBUTE_UNUSED) { \ +} \ +void IntrinsicCodeGeneratorMIPS64::Visit ## Name(HInvoke* invoke ATTRIBUTE_UNUSED) { \ +} + +UNIMPLEMENTED_INTRINSIC(MathRoundDouble) +UNIMPLEMENTED_INTRINSIC(MathRoundFloat) + +UNIMPLEMENTED_INTRINSIC(ThreadCurrentThread) +UNIMPLEMENTED_INTRINSIC(UnsafeGet) +UNIMPLEMENTED_INTRINSIC(UnsafeGetVolatile) +UNIMPLEMENTED_INTRINSIC(UnsafeGetLong) +UNIMPLEMENTED_INTRINSIC(UnsafeGetLongVolatile) +UNIMPLEMENTED_INTRINSIC(UnsafeGetObject) +UNIMPLEMENTED_INTRINSIC(UnsafeGetObjectVolatile) +UNIMPLEMENTED_INTRINSIC(UnsafePut) +UNIMPLEMENTED_INTRINSIC(UnsafePutOrdered) +UNIMPLEMENTED_INTRINSIC(UnsafePutVolatile) +UNIMPLEMENTED_INTRINSIC(UnsafePutObject) +UNIMPLEMENTED_INTRINSIC(UnsafePutObjectOrdered) +UNIMPLEMENTED_INTRINSIC(UnsafePutObjectVolatile) +UNIMPLEMENTED_INTRINSIC(UnsafePutLong) +UNIMPLEMENTED_INTRINSIC(UnsafePutLongOrdered) +UNIMPLEMENTED_INTRINSIC(UnsafePutLongVolatile) +UNIMPLEMENTED_INTRINSIC(UnsafeCASInt) +UNIMPLEMENTED_INTRINSIC(UnsafeCASLong) +UNIMPLEMENTED_INTRINSIC(UnsafeCASObject) +UNIMPLEMENTED_INTRINSIC(StringCharAt) +UNIMPLEMENTED_INTRINSIC(StringCompareTo) +UNIMPLEMENTED_INTRINSIC(StringEquals) +UNIMPLEMENTED_INTRINSIC(StringIndexOf) +UNIMPLEMENTED_INTRINSIC(StringIndexOfAfter) +UNIMPLEMENTED_INTRINSIC(StringNewStringFromBytes) +UNIMPLEMENTED_INTRINSIC(StringNewStringFromChars) +UNIMPLEMENTED_INTRINSIC(StringNewStringFromString) +UNIMPLEMENTED_INTRINSIC(LongRotateLeft) +UNIMPLEMENTED_INTRINSIC(LongRotateRight) +UNIMPLEMENTED_INTRINSIC(LongNumberOfTrailingZeros) +UNIMPLEMENTED_INTRINSIC(IntegerRotateLeft) +UNIMPLEMENTED_INTRINSIC(IntegerRotateRight) +UNIMPLEMENTED_INTRINSIC(IntegerNumberOfTrailingZeros) + +UNIMPLEMENTED_INTRINSIC(ReferenceGetReferent) +UNIMPLEMENTED_INTRINSIC(StringGetCharsNoCheck) +UNIMPLEMENTED_INTRINSIC(SystemArrayCopyChar) + +#undef UNIMPLEMENTED_INTRINSIC + +#undef __ + +} // namespace mips64 +} // namespace art diff --git a/compiler/optimizing/intrinsics_mips64.h b/compiler/optimizing/intrinsics_mips64.h new file mode 100644 index 0000000000..1481d24c9e --- /dev/null +++ b/compiler/optimizing/intrinsics_mips64.h @@ -0,0 +1,83 @@ +/* + * 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. + */ + +#ifndef ART_COMPILER_OPTIMIZING_INTRINSICS_MIPS64_H_ +#define ART_COMPILER_OPTIMIZING_INTRINSICS_MIPS64_H_ + +#include "intrinsics.h" + +namespace art { + +class ArenaAllocator; +class HInvokeStaticOrDirect; +class HInvokeVirtual; + +namespace mips64 { + +class CodeGeneratorMIPS64; +class Mips64Assembler; + +class IntrinsicLocationsBuilderMIPS64 FINAL : public IntrinsicVisitor { + public: + explicit IntrinsicLocationsBuilderMIPS64(CodeGeneratorMIPS64* codegen); + + // Define visitor methods. + +#define OPTIMIZING_INTRINSICS(Name, IsStatic, NeedsEnvironmentOrCache) \ + void Visit ## Name(HInvoke* invoke) OVERRIDE; +#include "intrinsics_list.h" +INTRINSICS_LIST(OPTIMIZING_INTRINSICS) +#undef INTRINSICS_LIST +#undef OPTIMIZING_INTRINSICS + + // Check whether an invoke is an intrinsic, and if so, create a location summary. Returns whether + // a corresponding LocationSummary with the intrinsified_ flag set was generated and attached to + // the invoke. + bool TryDispatch(HInvoke* invoke); + + private: + ArenaAllocator* arena_; + + DISALLOW_COPY_AND_ASSIGN(IntrinsicLocationsBuilderMIPS64); +}; + +class IntrinsicCodeGeneratorMIPS64 FINAL : public IntrinsicVisitor { + public: + explicit IntrinsicCodeGeneratorMIPS64(CodeGeneratorMIPS64* codegen) : codegen_(codegen) {} + + // Define visitor methods. + +#define OPTIMIZING_INTRINSICS(Name, IsStatic, NeedsEnvironmentOrCache) \ + void Visit ## Name(HInvoke* invoke) OVERRIDE; +#include "intrinsics_list.h" +INTRINSICS_LIST(OPTIMIZING_INTRINSICS) +#undef INTRINSICS_LIST +#undef OPTIMIZING_INTRINSICS + + private: + Mips64Assembler* GetAssembler(); + + ArenaAllocator* GetAllocator(); + + CodeGeneratorMIPS64* codegen_; + + DISALLOW_COPY_AND_ASSIGN(IntrinsicCodeGeneratorMIPS64); +}; + +} // namespace mips64 +} // namespace art + +#endif // ART_COMPILER_OPTIMIZING_INTRINSICS_MIPS64_H_ diff --git a/compiler/optimizing/locations.cc b/compiler/optimizing/locations.cc index d14dfc190f..ebdf7a2f65 100644 --- a/compiler/optimizing/locations.cc +++ b/compiler/optimizing/locations.cc @@ -23,18 +23,15 @@ namespace art { LocationSummary::LocationSummary(HInstruction* instruction, CallKind call_kind, bool intrinsified) - : inputs_(instruction->GetBlock()->GetGraph()->GetArena(), instruction->InputCount()), - temps_(instruction->GetBlock()->GetGraph()->GetArena(), 0), + : inputs_(instruction->InputCount(), + instruction->GetBlock()->GetGraph()->GetArena()->Adapter(kArenaAllocLocationSummary)), + temps_(instruction->GetBlock()->GetGraph()->GetArena()->Adapter(kArenaAllocLocationSummary)), output_overlaps_(Location::kOutputOverlap), call_kind_(call_kind), stack_mask_(nullptr), register_mask_(0), live_registers_(), intrinsified_(intrinsified) { - inputs_.SetSize(instruction->InputCount()); - for (size_t i = 0; i < instruction->InputCount(); ++i) { - inputs_.Put(i, Location()); - } instruction->SetLocations(this); if (NeedsSafepoint()) { diff --git a/compiler/optimizing/locations.h b/compiler/optimizing/locations.h index 2162ab928b..2eeba18a4d 100644 --- a/compiler/optimizing/locations.h +++ b/compiler/optimizing/locations.h @@ -17,6 +17,7 @@ #ifndef ART_COMPILER_OPTIMIZING_LOCATIONS_H_ #define ART_COMPILER_OPTIMIZING_LOCATIONS_H_ +#include "base/arena_containers.h" #include "base/arena_object.h" #include "base/bit_field.h" #include "base/bit_vector.h" @@ -481,15 +482,17 @@ class LocationSummary : public ArenaObject<kArenaAllocMisc> { bool intrinsified = false); void SetInAt(uint32_t at, Location location) { - inputs_.Put(at, location); + DCHECK_LT(at, GetInputCount()); + inputs_[at] = location; } Location InAt(uint32_t at) const { - return inputs_.Get(at); + DCHECK_LT(at, GetInputCount()); + return inputs_[at]; } size_t GetInputCount() const { - return inputs_.Size(); + return inputs_.size(); } void SetOut(Location location, Location::OutputOverlap overlaps = Location::kOutputOverlap) { @@ -508,23 +511,25 @@ class LocationSummary : public ArenaObject<kArenaAllocMisc> { } void AddTemp(Location location) { - temps_.Add(location); + temps_.push_back(location); } Location GetTemp(uint32_t at) const { - return temps_.Get(at); + DCHECK_LT(at, GetTempCount()); + return temps_[at]; } void SetTempAt(uint32_t at, Location location) { - DCHECK(temps_.Get(at).IsUnallocated() || temps_.Get(at).IsInvalid()); - temps_.Put(at, location); + DCHECK_LT(at, GetTempCount()); + DCHECK(temps_[at].IsUnallocated() || temps_[at].IsInvalid()); + temps_[at] = location; } size_t GetTempCount() const { - return temps_.Size(); + return temps_.size(); } - bool HasTemps() const { return !temps_.IsEmpty(); } + bool HasTemps() const { return !temps_.empty(); } Location Out() const { return output_; } @@ -576,7 +581,7 @@ class LocationSummary : public ArenaObject<kArenaAllocMisc> { } bool IsFixedInput(uint32_t input_index) const { - Location input = inputs_.Get(input_index); + Location input = inputs_[input_index]; return input.IsRegister() || input.IsFpuRegister() || input.IsPair() @@ -593,8 +598,8 @@ class LocationSummary : public ArenaObject<kArenaAllocMisc> { } private: - GrowableArray<Location> inputs_; - GrowableArray<Location> temps_; + ArenaVector<Location> inputs_; + ArenaVector<Location> temps_; // Whether the output overlaps with any of the inputs. If it overlaps, then it cannot // share the same register as the inputs. Location::OutputOverlap output_overlaps_; diff --git a/compiler/optimizing/nodes.cc b/compiler/optimizing/nodes.cc index 012858920f..ef89932e3b 100644 --- a/compiler/optimizing/nodes.cc +++ b/compiler/optimizing/nodes.cc @@ -20,6 +20,7 @@ #include "ssa_builder.h" #include "base/bit_vector-inl.h" #include "base/bit_utils.h" +#include "base/stl_util.h" #include "mirror/class-inl.h" #include "utils/growable_array.h" #include "scoped_thread_state_change.h" @@ -32,8 +33,41 @@ void HGraph::AddBlock(HBasicBlock* block) { } void HGraph::FindBackEdges(ArenaBitVector* visited) { + // "visited" must be empty on entry, it's an output argument for all visited (i.e. live) blocks. + DCHECK_EQ(visited->GetHighestBitSet(), -1); + + // Nodes that we're currently visiting, indexed by block id. ArenaBitVector visiting(arena_, blocks_.size(), false); - VisitBlockForBackEdges(entry_block_, visited, &visiting); + // Number of successors visited from a given node, indexed by block id. + ArenaVector<size_t> successors_visited(blocks_.size(), 0u, arena_->Adapter()); + // Stack of nodes that we're currently visiting (same as marked in "visiting" above). + ArenaVector<HBasicBlock*> worklist(arena_->Adapter()); + constexpr size_t kDefaultWorklistSize = 8; + worklist.reserve(kDefaultWorklistSize); + visited->SetBit(entry_block_->GetBlockId()); + visiting.SetBit(entry_block_->GetBlockId()); + worklist.push_back(entry_block_); + + while (!worklist.empty()) { + HBasicBlock* current = worklist.back(); + uint32_t current_id = current->GetBlockId(); + if (successors_visited[current_id] == current->GetSuccessors().size()) { + visiting.ClearBit(current_id); + worklist.pop_back(); + } else { + DCHECK_LT(successors_visited[current_id], current->GetSuccessors().size()); + HBasicBlock* successor = current->GetSuccessors()[successors_visited[current_id]++]; + uint32_t successor_id = successor->GetBlockId(); + if (visiting.IsBitSet(successor_id)) { + DCHECK(ContainsElement(worklist, successor)); + successor->AddBackEdge(current); + } else if (!visited->IsBitSet(successor_id)) { + visited->SetBit(successor_id); + visiting.SetBit(successor_id); + worklist.push_back(successor); + } + } + } } static void RemoveAsUser(HInstruction* instruction) { @@ -79,24 +113,6 @@ void HGraph::RemoveDeadBlocks(const ArenaBitVector& visited) { } } -void HGraph::VisitBlockForBackEdges(HBasicBlock* block, - ArenaBitVector* visited, - ArenaBitVector* visiting) { - int id = block->GetBlockId(); - if (visited->IsBitSet(id)) return; - - visited->SetBit(id); - visiting->SetBit(id); - for (HBasicBlock* successor : block->GetSuccessors()) { - if (visiting->IsBitSet(successor->GetBlockId())) { - successor->AddBackEdge(block); - } else { - VisitBlockForBackEdges(successor, visited, visiting); - } - } - visiting->ClearBit(id); -} - void HGraph::BuildDominatorTree() { // (1) Simplify the CFG so that catch blocks have only exceptional incoming // edges. This invariant simplifies building SSA form because Phis cannot @@ -141,10 +157,43 @@ void HBasicBlock::ClearDominanceInformation() { void HGraph::ComputeDominanceInformation() { DCHECK(reverse_post_order_.empty()); reverse_post_order_.reserve(blocks_.size()); - ArenaVector<size_t> visits(blocks_.size(), 0u, arena_->Adapter()); reverse_post_order_.push_back(entry_block_); - for (HBasicBlock* successor : entry_block_->GetSuccessors()) { - VisitBlockForDominatorTree(successor, entry_block_, &visits); + + // Number of visits of a given node, indexed by block id. + ArenaVector<size_t> visits(blocks_.size(), 0u, arena_->Adapter()); + // Number of successors visited from a given node, indexed by block id. + ArenaVector<size_t> successors_visited(blocks_.size(), 0u, arena_->Adapter()); + // Nodes for which we need to visit successors. + ArenaVector<HBasicBlock*> worklist(arena_->Adapter()); + constexpr size_t kDefaultWorklistSize = 8; + worklist.reserve(kDefaultWorklistSize); + worklist.push_back(entry_block_); + + while (!worklist.empty()) { + HBasicBlock* current = worklist.back(); + uint32_t current_id = current->GetBlockId(); + if (successors_visited[current_id] == current->GetSuccessors().size()) { + worklist.pop_back(); + } else { + DCHECK_LT(successors_visited[current_id], current->GetSuccessors().size()); + HBasicBlock* successor = current->GetSuccessors()[successors_visited[current_id]++]; + + if (successor->GetDominator() == nullptr) { + successor->SetDominator(current); + } else { + successor->SetDominator(FindCommonDominator(successor->GetDominator(), current)); + } + + // Once all the forward edges have been visited, we know the immediate + // dominator of the block. We can then start visiting its successors. + DCHECK_LT(successor->GetBlockId(), visits.size()); + if (++visits[successor->GetBlockId()] == + successor->GetPredecessors().size() - successor->NumberOfBackEdges()) { + successor->GetDominator()->AddDominatedBlock(successor); + reverse_post_order_.push_back(successor); + worklist.push_back(successor); + } + } } } @@ -166,28 +215,6 @@ HBasicBlock* HGraph::FindCommonDominator(HBasicBlock* first, HBasicBlock* second return nullptr; } -void HGraph::VisitBlockForDominatorTree(HBasicBlock* block, - HBasicBlock* predecessor, - ArenaVector<size_t>* visits) { - if (block->GetDominator() == nullptr) { - block->SetDominator(predecessor); - } else { - block->SetDominator(FindCommonDominator(block->GetDominator(), predecessor)); - } - - // Once all the forward edges have been visited, we know the immediate - // dominator of the block. We can then start visiting its successors. - DCHECK_LT(block->GetBlockId(), visits->size()); - if (++(*visits)[block->GetBlockId()] == - block->GetPredecessors().size() - block->NumberOfBackEdges()) { - block->GetDominator()->AddDominatedBlock(block); - reverse_post_order_.push_back(block); - for (HBasicBlock* successor : block->GetSuccessors()) { - VisitBlockForDominatorTree(successor, block, visits); - } - } -} - void HGraph::TransformToSsa() { DCHECK(!reverse_post_order_.empty()); SsaBuilder ssa_builder(this); @@ -1143,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); @@ -1188,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 52f6e232ea..26df2419a7 100644 --- a/compiler/optimizing/nodes.h +++ b/compiler/optimizing/nodes.h @@ -370,13 +370,7 @@ class HGraph : public ArenaObject<kArenaAllocGraph> { void SetHasTryCatch(bool value) { has_try_catch_ = value; } private: - void VisitBlockForDominatorTree(HBasicBlock* block, - HBasicBlock* predecessor, - ArenaVector<size_t>* visits); void FindBackEdges(ArenaBitVector* visited); - void VisitBlockForBackEdges(HBasicBlock* block, - ArenaBitVector* visited, - ArenaBitVector* visiting); void RemoveInstructionsAsUsersFromDeadBlocks(const ArenaBitVector& visited) const; void RemoveDeadBlocks(const ArenaBitVector& visited); @@ -825,11 +819,17 @@ class HBasicBlock : public ArenaObject<kArenaAllocBasicBlock> { 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 @@ -940,6 +940,8 @@ class HBasicBlock : public ArenaObject<kArenaAllocBasicBlock> { // 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; @@ -949,7 +951,6 @@ class HBasicBlock : public ArenaObject<kArenaAllocBasicBlock> { 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; @@ -2408,7 +2409,9 @@ class HCurrentMethod : public HExpression<0> { // will be the block containing the next Dex opcode. class HPackedSwitch : public HTemplateInstruction<1> { public: - HPackedSwitch(int32_t start_value, int32_t num_entries, HInstruction* input, + HPackedSwitch(int32_t start_value, + uint32_t num_entries, + HInstruction* input, uint32_t dex_pc = kNoDexPc) : HTemplateInstruction(SideEffects::None(), dex_pc), start_value_(start_value), @@ -2420,7 +2423,7 @@ class HPackedSwitch : public HTemplateInstruction<1> { int32_t GetStartValue() const { return start_value_; } - int32_t GetNumEntries() const { return num_entries_; } + uint32_t GetNumEntries() const { return num_entries_; } HBasicBlock* GetDefaultBlock() const { // Last entry is the default block. @@ -2429,8 +2432,8 @@ class HPackedSwitch : public HTemplateInstruction<1> { DECLARE_INSTRUCTION(PackedSwitch); private: - int32_t start_value_; - int32_t num_entries_; + const int32_t start_value_; + const uint32_t num_entries_; DISALLOW_COPY_AND_ASSIGN(HPackedSwitch); }; diff --git a/compiler/optimizing/primitive_type_propagation.cc b/compiler/optimizing/primitive_type_propagation.cc index af93438c9a..c98f43e461 100644 --- a/compiler/optimizing/primitive_type_propagation.cc +++ b/compiler/optimizing/primitive_type_propagation.cc @@ -108,8 +108,9 @@ void PrimitiveTypePropagation::VisitBasicBlock(HBasicBlock* block) { } void PrimitiveTypePropagation::ProcessWorklist() { - while (!worklist_.IsEmpty()) { - HPhi* instruction = worklist_.Pop(); + while (!worklist_.empty()) { + HPhi* instruction = worklist_.back(); + worklist_.pop_back(); if (UpdateType(instruction)) { AddDependentInstructionsToWorklist(instruction); } @@ -118,7 +119,7 @@ void PrimitiveTypePropagation::ProcessWorklist() { void PrimitiveTypePropagation::AddToWorklist(HPhi* instruction) { DCHECK(instruction->IsLive()); - worklist_.Add(instruction); + worklist_.push_back(instruction); } void PrimitiveTypePropagation::AddDependentInstructionsToWorklist(HInstruction* instruction) { diff --git a/compiler/optimizing/primitive_type_propagation.h b/compiler/optimizing/primitive_type_propagation.h index 6d370ed2ab..212fcfc69f 100644 --- a/compiler/optimizing/primitive_type_propagation.h +++ b/compiler/optimizing/primitive_type_propagation.h @@ -17,6 +17,7 @@ #ifndef ART_COMPILER_OPTIMIZING_PRIMITIVE_TYPE_PROPAGATION_H_ #define ART_COMPILER_OPTIMIZING_PRIMITIVE_TYPE_PROPAGATION_H_ +#include "base/arena_containers.h" #include "nodes.h" namespace art { @@ -25,7 +26,9 @@ namespace art { class PrimitiveTypePropagation : public ValueObject { public: explicit PrimitiveTypePropagation(HGraph* graph) - : graph_(graph), worklist_(graph->GetArena(), kDefaultWorklistSize) {} + : graph_(graph), worklist_(graph->GetArena()->Adapter(kArenaAllocPrimitiveTypePropagation)) { + worklist_.reserve(kDefaultWorklistSize); + } void Run(); @@ -37,7 +40,7 @@ class PrimitiveTypePropagation : public ValueObject { bool UpdateType(HPhi* phi); HGraph* const graph_; - GrowableArray<HPhi*> worklist_; + ArenaVector<HPhi*> worklist_; static constexpr size_t kDefaultWorklistSize = 8; diff --git a/compiler/optimizing/reference_type_propagation.cc b/compiler/optimizing/reference_type_propagation.cc index a88c5431c5..fe837e4545 100644 --- a/compiler/optimizing/reference_type_propagation.cc +++ b/compiler/optimizing/reference_type_propagation.cc @@ -27,7 +27,7 @@ class RTPVisitor : public HGraphDelegateVisitor { public: RTPVisitor(HGraph* graph, StackHandleScopeCollection* handles, - GrowableArray<HInstruction*>* worklist, + ArenaVector<HInstruction*>* worklist, ReferenceTypeInfo::TypeHandle object_class_handle, ReferenceTypeInfo::TypeHandle class_class_handle, ReferenceTypeInfo::TypeHandle string_class_handle, @@ -68,7 +68,7 @@ class RTPVisitor : public HGraphDelegateVisitor { ReferenceTypeInfo::TypeHandle class_class_handle_; ReferenceTypeInfo::TypeHandle string_class_handle_; ReferenceTypeInfo::TypeHandle throwable_class_handle_; - GrowableArray<HInstruction*>* worklist_; + ArenaVector<HInstruction*>* worklist_; static constexpr size_t kDefaultWorklistSize = 8; }; @@ -78,7 +78,8 @@ ReferenceTypePropagation::ReferenceTypePropagation(HGraph* graph, const char* name) : HOptimization(graph, name), handles_(handles), - worklist_(graph->GetArena(), kDefaultWorklistSize) { + worklist_(graph->GetArena()->Adapter(kArenaAllocReferenceTypePropagation)) { + worklist_.reserve(kDefaultWorklistSize); // Mutator lock is required for NewHandle, but annotalysis ignores constructors. ScopedObjectAccess soa(Thread::Current()); ClassLinker* linker = Runtime::Current()->GetClassLinker(); @@ -649,7 +650,7 @@ void RTPVisitor::VisitArrayGet(HArrayGet* instr) { ScopedObjectAccess soa(Thread::Current()); UpdateArrayGet(instr, handles_, object_class_handle_); if (!instr->GetReferenceTypeInfo().IsValid()) { - worklist_->Add(instr); + worklist_->push_back(instr); } } @@ -718,8 +719,9 @@ bool ReferenceTypePropagation::UpdateNullability(HInstruction* instr) { } void ReferenceTypePropagation::ProcessWorklist() { - while (!worklist_.IsEmpty()) { - HInstruction* instruction = worklist_.Pop(); + while (!worklist_.empty()) { + HInstruction* instruction = worklist_.back(); + worklist_.pop_back(); if (UpdateNullability(instruction) || UpdateReferenceTypeInfo(instruction)) { AddDependentInstructionsToWorklist(instruction); } @@ -729,7 +731,7 @@ void ReferenceTypePropagation::ProcessWorklist() { void ReferenceTypePropagation::AddToWorklist(HInstruction* instruction) { DCHECK_EQ(instruction->GetType(), Primitive::kPrimNot) << instruction->DebugName() << ":" << instruction->GetType(); - worklist_.Add(instruction); + worklist_.push_back(instruction); } void ReferenceTypePropagation::AddDependentInstructionsToWorklist(HInstruction* instruction) { diff --git a/compiler/optimizing/reference_type_propagation.h b/compiler/optimizing/reference_type_propagation.h index 62f6ab80b3..5493601adc 100644 --- a/compiler/optimizing/reference_type_propagation.h +++ b/compiler/optimizing/reference_type_propagation.h @@ -17,6 +17,7 @@ #ifndef ART_COMPILER_OPTIMIZING_REFERENCE_TYPE_PROPAGATION_H_ #define ART_COMPILER_OPTIMIZING_REFERENCE_TYPE_PROPAGATION_H_ +#include "base/arena_containers.h" #include "driver/dex_compilation_unit.h" #include "handle_scope-inl.h" #include "nodes.h" @@ -57,7 +58,7 @@ class ReferenceTypePropagation : public HOptimization { StackHandleScopeCollection* handles_; - GrowableArray<HInstruction*> worklist_; + ArenaVector<HInstruction*> worklist_; ReferenceTypeInfo::TypeHandle object_class_handle_; ReferenceTypeInfo::TypeHandle class_class_handle_; diff --git a/compiler/optimizing/register_allocator.cc b/compiler/optimizing/register_allocator.cc index 9594e3b8e1..c43e58ffc4 100644 --- a/compiler/optimizing/register_allocator.cc +++ b/compiler/optimizing/register_allocator.cc @@ -43,21 +43,21 @@ RegisterAllocator::RegisterAllocator(ArenaAllocator* allocator, : allocator_(allocator), codegen_(codegen), liveness_(liveness), - unhandled_core_intervals_(allocator, 0), - unhandled_fp_intervals_(allocator, 0), + unhandled_core_intervals_(allocator->Adapter(kArenaAllocRegisterAllocator)), + unhandled_fp_intervals_(allocator->Adapter(kArenaAllocRegisterAllocator)), unhandled_(nullptr), - handled_(allocator, 0), - active_(allocator, 0), - inactive_(allocator, 0), - physical_core_register_intervals_(allocator, codegen->GetNumberOfCoreRegisters()), - physical_fp_register_intervals_(allocator, codegen->GetNumberOfFloatingPointRegisters()), - temp_intervals_(allocator, 4), - int_spill_slots_(allocator, kDefaultNumberOfSpillSlots), - long_spill_slots_(allocator, kDefaultNumberOfSpillSlots), - float_spill_slots_(allocator, kDefaultNumberOfSpillSlots), - double_spill_slots_(allocator, kDefaultNumberOfSpillSlots), + handled_(allocator->Adapter(kArenaAllocRegisterAllocator)), + active_(allocator->Adapter(kArenaAllocRegisterAllocator)), + inactive_(allocator->Adapter(kArenaAllocRegisterAllocator)), + physical_core_register_intervals_(allocator->Adapter(kArenaAllocRegisterAllocator)), + physical_fp_register_intervals_(allocator->Adapter(kArenaAllocRegisterAllocator)), + temp_intervals_(allocator->Adapter(kArenaAllocRegisterAllocator)), + int_spill_slots_(allocator->Adapter(kArenaAllocRegisterAllocator)), + long_spill_slots_(allocator->Adapter(kArenaAllocRegisterAllocator)), + float_spill_slots_(allocator->Adapter(kArenaAllocRegisterAllocator)), + double_spill_slots_(allocator->Adapter(kArenaAllocRegisterAllocator)), catch_phi_spill_slots_(0), - safepoints_(allocator, 0), + safepoints_(allocator->Adapter(kArenaAllocRegisterAllocator)), processing_core_registers_(false), number_of_registers_(-1), registers_array_(nullptr), @@ -66,10 +66,16 @@ RegisterAllocator::RegisterAllocator(ArenaAllocator* allocator, reserved_out_slots_(0), maximum_number_of_live_core_registers_(0), maximum_number_of_live_fp_registers_(0) { + temp_intervals_.reserve(4); + int_spill_slots_.reserve(kDefaultNumberOfSpillSlots); + long_spill_slots_.reserve(kDefaultNumberOfSpillSlots); + float_spill_slots_.reserve(kDefaultNumberOfSpillSlots); + double_spill_slots_.reserve(kDefaultNumberOfSpillSlots); + static constexpr bool kIsBaseline = false; codegen->SetupBlockedRegisters(kIsBaseline); - physical_core_register_intervals_.SetSize(codegen->GetNumberOfCoreRegisters()); - physical_fp_register_intervals_.SetSize(codegen->GetNumberOfFloatingPointRegisters()); + physical_core_register_intervals_.resize(codegen->GetNumberOfCoreRegisters(), nullptr); + physical_fp_register_intervals_.resize(codegen->GetNumberOfFloatingPointRegisters(), nullptr); // Always reserve for the current method and the graph's max out registers. // TODO: compute it instead. // ArtMethod* takes 2 vregs for 64 bits. @@ -129,17 +135,17 @@ void RegisterAllocator::BlockRegister(Location location, size_t start, size_t en int reg = location.reg(); DCHECK(location.IsRegister() || location.IsFpuRegister()); LiveInterval* interval = location.IsRegister() - ? physical_core_register_intervals_.Get(reg) - : physical_fp_register_intervals_.Get(reg); + ? physical_core_register_intervals_[reg] + : physical_fp_register_intervals_[reg]; Primitive::Type type = location.IsRegister() ? Primitive::kPrimInt : Primitive::kPrimFloat; if (interval == nullptr) { interval = LiveInterval::MakeFixedInterval(allocator_, reg, type); if (location.IsRegister()) { - physical_core_register_intervals_.Put(reg, interval); + physical_core_register_intervals_[reg] = interval; } else { - physical_fp_register_intervals_.Put(reg, interval); + physical_fp_register_intervals_[reg] = interval; } } DCHECK(interval->GetRegister() == reg); @@ -184,34 +190,32 @@ void RegisterAllocator::AllocateRegistersInternal() { registers_array_ = allocator_->AllocArray<size_t>(number_of_registers_); processing_core_registers_ = true; unhandled_ = &unhandled_core_intervals_; - for (size_t i = 0, e = physical_core_register_intervals_.Size(); i < e; ++i) { - LiveInterval* fixed = physical_core_register_intervals_.Get(i); + for (LiveInterval* fixed : physical_core_register_intervals_) { if (fixed != nullptr) { // Fixed interval is added to inactive_ instead of unhandled_. // It's also the only type of inactive interval whose start position // can be after the current interval during linear scan. // Fixed interval is never split and never moves to unhandled_. - inactive_.Add(fixed); + inactive_.push_back(fixed); } } LinearScan(); - inactive_.Reset(); - active_.Reset(); - handled_.Reset(); + inactive_.clear(); + active_.clear(); + handled_.clear(); number_of_registers_ = codegen_->GetNumberOfFloatingPointRegisters(); registers_array_ = allocator_->AllocArray<size_t>(number_of_registers_); processing_core_registers_ = false; unhandled_ = &unhandled_fp_intervals_; - for (size_t i = 0, e = physical_fp_register_intervals_.Size(); i < e; ++i) { - LiveInterval* fixed = physical_fp_register_intervals_.Get(i); + for (LiveInterval* fixed : physical_fp_register_intervals_) { if (fixed != nullptr) { // Fixed interval is added to inactive_ instead of unhandled_. // It's also the only type of inactive interval whose start position // can be after the current interval during linear scan. // Fixed interval is never split and never moves to unhandled_. - inactive_.Add(fixed); + inactive_.push_back(fixed); } } LinearScan(); @@ -236,24 +240,24 @@ void RegisterAllocator::ProcessInstruction(HInstruction* instruction) { case Location::kRequiresRegister: { LiveInterval* interval = LiveInterval::MakeTempInterval(allocator_, Primitive::kPrimInt); - temp_intervals_.Add(interval); + temp_intervals_.push_back(interval); interval->AddTempUse(instruction, i); - unhandled_core_intervals_.Add(interval); + unhandled_core_intervals_.push_back(interval); break; } case Location::kRequiresFpuRegister: { LiveInterval* interval = LiveInterval::MakeTempInterval(allocator_, Primitive::kPrimDouble); - temp_intervals_.Add(interval); + temp_intervals_.push_back(interval); interval->AddTempUse(instruction, i); if (codegen_->NeedsTwoRegisters(Primitive::kPrimDouble)) { interval->AddHighInterval(/* is_temp */ true); LiveInterval* high = interval->GetHighInterval(); - temp_intervals_.Add(high); - unhandled_fp_intervals_.Add(high); + temp_intervals_.push_back(high); + unhandled_fp_intervals_.push_back(high); } - unhandled_fp_intervals_.Add(interval); + unhandled_fp_intervals_.push_back(interval); break; } @@ -276,7 +280,7 @@ void RegisterAllocator::ProcessInstruction(HInstruction* instruction) { instruction->GetBlock()->RemoveInstruction(instruction); return; } - safepoints_.Add(instruction); + safepoints_.push_back(instruction); if (locations->OnlyCallsOnSlowPath()) { // We add a synthesized range at this position to record the live registers // at this position. Ideally, we could just update the safepoints when locations @@ -310,28 +314,28 @@ void RegisterAllocator::ProcessInstruction(HInstruction* instruction) { LiveInterval* current = instruction->GetLiveInterval(); if (current == nullptr) return; - GrowableArray<LiveInterval*>& unhandled = core_register + ArenaVector<LiveInterval*>& unhandled = core_register ? unhandled_core_intervals_ : unhandled_fp_intervals_; - DCHECK(unhandled.IsEmpty() || current->StartsBeforeOrAt(unhandled.Peek())); + DCHECK(unhandled.empty() || current->StartsBeforeOrAt(unhandled.back())); if (codegen_->NeedsTwoRegisters(current->GetType())) { current->AddHighInterval(); } - for (size_t safepoint_index = safepoints_.Size(); safepoint_index > 0; --safepoint_index) { - HInstruction* safepoint = safepoints_.Get(safepoint_index - 1); + for (size_t safepoint_index = safepoints_.size(); safepoint_index > 0; --safepoint_index) { + HInstruction* safepoint = safepoints_[safepoint_index - 1u]; size_t safepoint_position = safepoint->GetLifetimePosition(); // Test that safepoints are ordered in the optimal way. - DCHECK(safepoint_index == safepoints_.Size() - || safepoints_.Get(safepoint_index)->GetLifetimePosition() < safepoint_position); + DCHECK(safepoint_index == safepoints_.size() || + safepoints_[safepoint_index]->GetLifetimePosition() < safepoint_position); if (safepoint_position == current->GetStart()) { // The safepoint is for this instruction, so the location of the instruction // does not need to be saved. - DCHECK_EQ(safepoint_index, safepoints_.Size()); + DCHECK_EQ(safepoint_index, safepoints_.size()); DCHECK_EQ(safepoint, instruction); continue; } else if (current->IsDeadAt(safepoint_position)) { @@ -437,34 +441,26 @@ class AllRangesIterator : public ValueObject { bool RegisterAllocator::ValidateInternal(bool log_fatal_on_failure) const { // To simplify unit testing, we eagerly create the array of intervals, and // call the helper method. - GrowableArray<LiveInterval*> intervals(allocator_, 0); + ArenaVector<LiveInterval*> intervals(allocator_->Adapter(kArenaAllocRegisterAllocator)); for (size_t i = 0; i < liveness_.GetNumberOfSsaValues(); ++i) { HInstruction* instruction = liveness_.GetInstructionFromSsaIndex(i); if (ShouldProcess(processing_core_registers_, instruction->GetLiveInterval())) { - intervals.Add(instruction->GetLiveInterval()); + intervals.push_back(instruction->GetLiveInterval()); } } - if (processing_core_registers_) { - for (size_t i = 0, e = physical_core_register_intervals_.Size(); i < e; ++i) { - LiveInterval* fixed = physical_core_register_intervals_.Get(i); - if (fixed != nullptr) { - intervals.Add(fixed); - } - } - } else { - for (size_t i = 0, e = physical_fp_register_intervals_.Size(); i < e; ++i) { - LiveInterval* fixed = physical_fp_register_intervals_.Get(i); - if (fixed != nullptr) { - intervals.Add(fixed); - } + const ArenaVector<LiveInterval*>* physical_register_intervals = processing_core_registers_ + ? &physical_core_register_intervals_ + : &physical_fp_register_intervals_; + for (LiveInterval* fixed : *physical_register_intervals) { + if (fixed != nullptr) { + intervals.push_back(fixed); } } - for (size_t i = 0, e = temp_intervals_.Size(); i < e; ++i) { - LiveInterval* temp = temp_intervals_.Get(i); + for (LiveInterval* temp : temp_intervals_) { if (ShouldProcess(processing_core_registers_, temp)) { - intervals.Add(temp); + intervals.push_back(temp); } } @@ -472,7 +468,7 @@ bool RegisterAllocator::ValidateInternal(bool log_fatal_on_failure) const { allocator_, processing_core_registers_, log_fatal_on_failure); } -bool RegisterAllocator::ValidateIntervals(const GrowableArray<LiveInterval*>& intervals, +bool RegisterAllocator::ValidateIntervals(const ArenaVector<LiveInterval*>& intervals, size_t number_of_spill_slots, size_t number_of_out_slots, const CodeGenerator& codegen, @@ -482,26 +478,27 @@ bool RegisterAllocator::ValidateIntervals(const GrowableArray<LiveInterval*>& in size_t number_of_registers = processing_core_registers ? codegen.GetNumberOfCoreRegisters() : codegen.GetNumberOfFloatingPointRegisters(); - GrowableArray<ArenaBitVector*> liveness_of_values( - allocator, number_of_registers + number_of_spill_slots); + ArenaVector<ArenaBitVector*> liveness_of_values( + allocator->Adapter(kArenaAllocRegisterAllocator)); + liveness_of_values.reserve(number_of_registers + number_of_spill_slots); // Allocate a bit vector per register. A live interval that has a register // allocated will populate the associated bit vector based on its live ranges. for (size_t i = 0; i < number_of_registers + number_of_spill_slots; ++i) { - liveness_of_values.Add(new (allocator) ArenaBitVector(allocator, 0, true)); + liveness_of_values.push_back(new (allocator) ArenaBitVector(allocator, 0, true)); } - for (size_t i = 0, e = intervals.Size(); i < e; ++i) { - for (AllRangesIterator it(intervals.Get(i)); !it.Done(); it.Advance()) { + for (LiveInterval* start_interval : intervals) { + for (AllRangesIterator it(start_interval); !it.Done(); it.Advance()) { LiveInterval* current = it.CurrentInterval(); HInstruction* defined_by = current->GetParent()->GetDefinedBy(); if (current->GetParent()->HasSpillSlot() // Parameters and current method have their own stack slot. && !(defined_by != nullptr && (defined_by->IsParameterValue() || defined_by->IsCurrentMethod()))) { - BitVector* liveness_of_spill_slot = liveness_of_values.Get(number_of_registers + BitVector* liveness_of_spill_slot = liveness_of_values[number_of_registers + current->GetParent()->GetSpillSlot() / kVRegSize - - number_of_out_slots); + - number_of_out_slots]; for (size_t j = it.CurrentRange()->GetStart(); j < it.CurrentRange()->GetEnd(); ++j) { if (liveness_of_spill_slot->IsBitSet(j)) { if (log_fatal_on_failure) { @@ -523,7 +520,7 @@ bool RegisterAllocator::ValidateIntervals(const GrowableArray<LiveInterval*>& in // and test code may not properly fill the right information to the code generator. CHECK(codegen.HasAllocatedRegister(processing_core_registers, current->GetRegister())); } - BitVector* liveness_of_register = liveness_of_values.Get(current->GetRegister()); + BitVector* liveness_of_register = liveness_of_values[current->GetRegister()]; for (size_t j = it.CurrentRange()->GetStart(); j < it.CurrentRange()->GetEnd(); ++j) { if (liveness_of_register->IsBitSet(j)) { if (current->IsUsingInputRegister() && current->CanUseInputRegister()) { @@ -572,93 +569,101 @@ void RegisterAllocator::DumpInterval(std::ostream& stream, LiveInterval* interva void RegisterAllocator::DumpAllIntervals(std::ostream& stream) const { stream << "inactive: " << std::endl; - for (size_t i = 0; i < inactive_.Size(); i ++) { - DumpInterval(stream, inactive_.Get(i)); + for (LiveInterval* inactive_interval : inactive_) { + DumpInterval(stream, inactive_interval); } stream << "active: " << std::endl; - for (size_t i = 0; i < active_.Size(); i ++) { - DumpInterval(stream, active_.Get(i)); + for (LiveInterval* active_interval : active_) { + DumpInterval(stream, active_interval); } stream << "unhandled: " << std::endl; auto unhandled = (unhandled_ != nullptr) ? unhandled_ : &unhandled_core_intervals_; - for (size_t i = 0; i < unhandled->Size(); i ++) { - DumpInterval(stream, unhandled->Get(i)); + for (LiveInterval* unhandled_interval : *unhandled) { + DumpInterval(stream, unhandled_interval); } stream << "handled: " << std::endl; - for (size_t i = 0; i < handled_.Size(); i ++) { - DumpInterval(stream, handled_.Get(i)); + for (LiveInterval* handled_interval : handled_) { + DumpInterval(stream, handled_interval); } } // By the book implementation of a linear scan register allocator. void RegisterAllocator::LinearScan() { - while (!unhandled_->IsEmpty()) { + while (!unhandled_->empty()) { // (1) Remove interval with the lowest start position from unhandled. - LiveInterval* current = unhandled_->Pop(); + LiveInterval* current = unhandled_->back(); + unhandled_->pop_back(); // Make sure the interval is an expected state. DCHECK(!current->IsFixed() && !current->HasSpillSlot()); // Make sure we are going in the right order. - DCHECK(unhandled_->IsEmpty() || unhandled_->Peek()->GetStart() >= current->GetStart()); + DCHECK(unhandled_->empty() || unhandled_->back()->GetStart() >= current->GetStart()); // Make sure a low interval is always with a high. - DCHECK(!current->IsLowInterval() || unhandled_->Peek()->IsHighInterval()); + DCHECK(!current->IsLowInterval() || unhandled_->back()->IsHighInterval()); // Make sure a high interval is always with a low. DCHECK(current->IsLowInterval() || - unhandled_->IsEmpty() || - !unhandled_->Peek()->IsHighInterval()); + unhandled_->empty() || + !unhandled_->back()->IsHighInterval()); size_t position = current->GetStart(); // Remember the inactive_ size here since the ones moved to inactive_ from // active_ below shouldn't need to be re-checked. - size_t inactive_intervals_to_handle = inactive_.Size(); + size_t inactive_intervals_to_handle = inactive_.size(); // (2) Remove currently active intervals that are dead at this position. // Move active intervals that have a lifetime hole at this position // to inactive. - for (size_t i = 0; i < active_.Size(); ++i) { - LiveInterval* interval = active_.Get(i); + // Note: Copy elements we keep to the beginning, just like + // v.erase(std::remove(v.begin(), v.end(), value), v.end()); + auto active_kept_end = active_.begin(); + for (auto it = active_.begin(), end = active_.end(); it != end; ++it) { + LiveInterval* interval = *it; if (interval->IsDeadAt(position)) { - active_.Delete(interval); - --i; - handled_.Add(interval); + handled_.push_back(interval); } else if (!interval->Covers(position)) { - active_.Delete(interval); - --i; - inactive_.Add(interval); + inactive_.push_back(interval); + } else { + *active_kept_end++ = interval; // Keep this interval. } } + // We have copied what we want to keep to [active_.begin(), active_kept_end), + // the rest of the data in active_ is junk - drop it. + active_.erase(active_kept_end, active_.end()); // (3) Remove currently inactive intervals that are dead at this position. // Move inactive intervals that cover this position to active. - for (size_t i = 0; i < inactive_intervals_to_handle; ++i) { - LiveInterval* interval = inactive_.Get(i); + // Note: Copy elements we keep to the beginning, just like + // v.erase(std::remove(v.begin(), v.begin() + num, value), v.begin() + num); + auto inactive_kept_end = inactive_.begin(); + auto inactive_to_handle_end = inactive_.begin() + inactive_intervals_to_handle; + for (auto it = inactive_.begin(); it != inactive_to_handle_end; ++it) { + LiveInterval* interval = *it; DCHECK(interval->GetStart() < position || interval->IsFixed()); if (interval->IsDeadAt(position)) { - inactive_.Delete(interval); - --i; - --inactive_intervals_to_handle; - handled_.Add(interval); + handled_.push_back(interval); } else if (interval->Covers(position)) { - inactive_.Delete(interval); - --i; - --inactive_intervals_to_handle; - active_.Add(interval); + active_.push_back(interval); + } else { + *inactive_kept_end++ = interval; // Keep this interval. } } + // We have copied what we want to keep to [inactive_.begin(), inactive_kept_end), + // the rest of the data in the processed interval is junk - drop it. + inactive_.erase(inactive_kept_end, inactive_to_handle_end); if (current->IsSlowPathSafepoint()) { // Synthesized interval to record the maximum number of live registers // at safepoints. No need to allocate a register for it. if (processing_core_registers_) { maximum_number_of_live_core_registers_ = - std::max(maximum_number_of_live_core_registers_, active_.Size()); + std::max(maximum_number_of_live_core_registers_, active_.size()); } else { maximum_number_of_live_fp_registers_ = - std::max(maximum_number_of_live_fp_registers_, active_.Size()); + std::max(maximum_number_of_live_fp_registers_, active_.size()); } - DCHECK(unhandled_->IsEmpty() || unhandled_->Peek()->GetStart() > current->GetStart()); + DCHECK(unhandled_->empty() || unhandled_->back()->GetStart() > current->GetStart()); continue; } @@ -683,7 +688,7 @@ void RegisterAllocator::LinearScan() { codegen_->AddAllocatedRegister(processing_core_registers_ ? Location::RegisterLocation(current->GetRegister()) : Location::FpuRegisterLocation(current->GetRegister())); - active_.Add(current); + active_.push_back(current); if (current->HasHighInterval() && !current->GetHighInterval()->HasRegister()) { current->GetHighInterval()->SetRegister(GetHighForLowRegister(current->GetRegister())); } @@ -726,8 +731,7 @@ bool RegisterAllocator::TryAllocateFreeReg(LiveInterval* current) { } // For each active interval, set its register to not free. - for (size_t i = 0, e = active_.Size(); i < e; ++i) { - LiveInterval* interval = active_.Get(i); + for (LiveInterval* interval : active_) { DCHECK(interval->HasRegister()); free_until[interval->GetRegister()] = 0; } @@ -762,8 +766,7 @@ bool RegisterAllocator::TryAllocateFreeReg(LiveInterval* current) { // For each inactive interval, set its register to be free until // the next intersection with `current`. - for (size_t i = 0, e = inactive_.Size(); i < e; ++i) { - LiveInterval* inactive = inactive_.Get(i); + for (LiveInterval* inactive : inactive_) { // Temp/Slow-path-safepoint interval has no holes. DCHECK(!inactive->IsTemp() && !inactive->IsSlowPathSafepoint()); if (!current->IsSplit() && !inactive->IsFixed()) { @@ -923,11 +926,29 @@ int RegisterAllocator::FindAvailableRegister(size_t* next_use, LiveInterval* cur return reg; } +// Remove interval and its other half if any. Return iterator to the following element. +static ArenaVector<LiveInterval*>::iterator RemoveIntervalAndPotentialOtherHalf( + ArenaVector<LiveInterval*>* intervals, ArenaVector<LiveInterval*>::iterator pos) { + DCHECK(intervals->begin() <= pos && pos < intervals->end()); + LiveInterval* interval = *pos; + if (interval->IsLowInterval()) { + DCHECK(pos + 1 < intervals->end()); + DCHECK_EQ(*(pos + 1), interval->GetHighInterval()); + return intervals->erase(pos, pos + 2); + } else if (interval->IsHighInterval()) { + DCHECK(intervals->begin() < pos); + DCHECK_EQ(*(pos - 1), interval->GetLowInterval()); + return intervals->erase(pos - 1, pos + 1); + } else { + return intervals->erase(pos); + } +} + bool RegisterAllocator::TrySplitNonPairOrUnalignedPairIntervalAt(size_t position, size_t first_register_use, size_t* next_use) { - for (size_t i = 0, e = active_.Size(); i < e; ++i) { - LiveInterval* active = active_.Get(i); + for (auto it = active_.begin(), end = active_.end(); it != end; ++it) { + LiveInterval* active = *it; DCHECK(active->HasRegister()); if (active->IsFixed()) continue; if (active->IsHighInterval()) continue; @@ -941,11 +962,10 @@ bool RegisterAllocator::TrySplitNonPairOrUnalignedPairIntervalAt(size_t position IsLowOfUnalignedPairInterval(active) || !IsLowRegister(active->GetRegister())) { LiveInterval* split = Split(active, position); - active_.DeleteAt(i); if (split != active) { - handled_.Add(active); + handled_.push_back(active); } - PotentiallyRemoveOtherHalf(active, &active_, i); + RemoveIntervalAndPotentialOtherHalf(&active_, it); AddSorted(unhandled_, split); return true; } @@ -953,23 +973,6 @@ bool RegisterAllocator::TrySplitNonPairOrUnalignedPairIntervalAt(size_t position return false; } -bool RegisterAllocator::PotentiallyRemoveOtherHalf(LiveInterval* interval, - GrowableArray<LiveInterval*>* intervals, - size_t index) { - if (interval->IsLowInterval()) { - DCHECK_EQ(intervals->Get(index), interval->GetHighInterval()); - intervals->DeleteAt(index); - return true; - } else if (interval->IsHighInterval()) { - DCHECK_GT(index, 0u); - DCHECK_EQ(intervals->Get(index - 1), interval->GetLowInterval()); - intervals->DeleteAt(index - 1); - return true; - } else { - return false; - } -} - // Find the register that is used the last, and spill the interval // that holds it. If the first use of `current` is after that register // we spill `current` instead. @@ -1001,8 +1004,7 @@ bool RegisterAllocator::AllocateBlockedReg(LiveInterval* current) { // For each active interval, find the next use of its register after the // start of current. - for (size_t i = 0, e = active_.Size(); i < e; ++i) { - LiveInterval* active = active_.Get(i); + for (LiveInterval* active : active_) { DCHECK(active->HasRegister()); if (active->IsFixed()) { next_use[active->GetRegister()] = current->GetStart(); @@ -1016,8 +1018,7 @@ bool RegisterAllocator::AllocateBlockedReg(LiveInterval* current) { // For each inactive interval, find the next use of its register after the // start of current. - for (size_t i = 0, e = inactive_.Size(); i < e; ++i) { - LiveInterval* inactive = inactive_.Get(i); + for (LiveInterval* inactive : inactive_) { // Temp/Slow-path-safepoint interval has no holes. DCHECK(!inactive->IsTemp() && !inactive->IsSlowPathSafepoint()); if (!current->IsSplit() && !inactive->IsFixed()) { @@ -1087,10 +1088,10 @@ bool RegisterAllocator::AllocateBlockedReg(LiveInterval* current) { first_register_use, next_use); DCHECK(success); - LiveInterval* existing = unhandled_->Peek(); + LiveInterval* existing = unhandled_->back(); DCHECK(existing->IsHighInterval()); DCHECK_EQ(existing->GetLowInterval(), current); - unhandled_->Add(current); + unhandled_->push_back(current); } else { // If the first use of that instruction is after the last use of the found // register, we split this interval just before its first register use. @@ -1105,23 +1106,24 @@ bool RegisterAllocator::AllocateBlockedReg(LiveInterval* current) { // have that register. current->SetRegister(reg); - for (size_t i = 0, e = active_.Size(); i < e; ++i) { - LiveInterval* active = active_.Get(i); + for (auto it = active_.begin(), end = active_.end(); it != end; ++it) { + LiveInterval* active = *it; if (active->GetRegister() == reg) { DCHECK(!active->IsFixed()); LiveInterval* split = Split(active, current->GetStart()); if (split != active) { - handled_.Add(active); + handled_.push_back(active); } - active_.DeleteAt(i); - PotentiallyRemoveOtherHalf(active, &active_, i); + RemoveIntervalAndPotentialOtherHalf(&active_, it); AddSorted(unhandled_, split); break; } } - for (size_t i = 0; i < inactive_.Size(); ++i) { - LiveInterval* inactive = inactive_.Get(i); + // NOTE: Retrieve end() on each iteration because we're removing elements in the loop body. + for (auto it = inactive_.begin(); it != inactive_.end(); ) { + LiveInterval* inactive = *it; + bool erased = false; if (inactive->GetRegister() == reg) { if (!current->IsSplit() && !inactive->IsFixed()) { // Neither current nor inactive are fixed. @@ -1129,43 +1131,43 @@ bool RegisterAllocator::AllocateBlockedReg(LiveInterval* current) { // inactive interval should never intersect with that inactive interval. // Only if it's not fixed though, because fixed intervals don't come from SSA. DCHECK_EQ(inactive->FirstIntersectionWith(current), kNoLifetime); - continue; - } - size_t next_intersection = inactive->FirstIntersectionWith(current); - if (next_intersection != kNoLifetime) { - if (inactive->IsFixed()) { - LiveInterval* split = Split(current, next_intersection); - DCHECK_NE(split, current); - AddSorted(unhandled_, split); - } else { - // Split at the start of `current`, which will lead to splitting - // at the end of the lifetime hole of `inactive`. - LiveInterval* split = Split(inactive, current->GetStart()); - // If it's inactive, it must start before the current interval. - DCHECK_NE(split, inactive); - inactive_.DeleteAt(i); - if (PotentiallyRemoveOtherHalf(inactive, &inactive_, i) && inactive->IsHighInterval()) { - // We have removed an entry prior to `inactive`. So we need to decrement. - --i; + } else { + size_t next_intersection = inactive->FirstIntersectionWith(current); + if (next_intersection != kNoLifetime) { + if (inactive->IsFixed()) { + LiveInterval* split = Split(current, next_intersection); + DCHECK_NE(split, current); + AddSorted(unhandled_, split); + } else { + // Split at the start of `current`, which will lead to splitting + // at the end of the lifetime hole of `inactive`. + LiveInterval* split = Split(inactive, current->GetStart()); + // If it's inactive, it must start before the current interval. + DCHECK_NE(split, inactive); + it = RemoveIntervalAndPotentialOtherHalf(&inactive_, it); + erased = true; + handled_.push_back(inactive); + AddSorted(unhandled_, split); } - // Decrement because we have removed `inactive` from the list. - --i; - handled_.Add(inactive); - AddSorted(unhandled_, split); } } } + // If we have erased the element, `it` already points to the next element. + // Otherwise we need to move to the next element. + if (!erased) { + ++it; + } } return true; } } -void RegisterAllocator::AddSorted(GrowableArray<LiveInterval*>* array, LiveInterval* interval) { +void RegisterAllocator::AddSorted(ArenaVector<LiveInterval*>* array, LiveInterval* interval) { DCHECK(!interval->IsFixed() && !interval->HasSpillSlot()); size_t insert_at = 0; - for (size_t i = array->Size(); i > 0; --i) { - LiveInterval* current = array->Get(i - 1); + for (size_t i = array->size(); i > 0; --i) { + LiveInterval* current = (*array)[i - 1u]; // High intervals must be processed right after their low equivalent. if (current->StartsAfter(interval) && !current->IsHighInterval()) { insert_at = i; @@ -1173,18 +1175,20 @@ void RegisterAllocator::AddSorted(GrowableArray<LiveInterval*>* array, LiveInter } else if ((current->GetStart() == interval->GetStart()) && current->IsSlowPathSafepoint()) { // Ensure the slow path interval is the last to be processed at its location: we want the // interval to know all live registers at this location. - DCHECK(i == 1 || array->Get(i - 2)->StartsAfter(current)); + DCHECK(i == 1 || (*array)[i - 2u]->StartsAfter(current)); insert_at = i; break; } } - array->InsertAt(insert_at, interval); // Insert the high interval before the low, to ensure the low is processed before. + auto insert_pos = array->begin() + insert_at; if (interval->HasHighInterval()) { - array->InsertAt(insert_at, interval->GetHighInterval()); + array->insert(insert_pos, { interval->GetHighInterval(), interval }); } else if (interval->HasLowInterval()) { - array->InsertAt(insert_at + 1, interval->GetLowInterval()); + array->insert(insert_pos, { interval, interval->GetLowInterval() }); + } else { + array->insert(insert_pos, interval); } } @@ -1309,7 +1313,7 @@ void RegisterAllocator::AllocateSpillSlotFor(LiveInterval* interval) { return; } - GrowableArray<size_t>* spill_slots = nullptr; + ArenaVector<size_t>* spill_slots = nullptr; switch (interval->GetType()) { case Primitive::kPrimDouble: spill_slots = &double_spill_slots_; @@ -1334,32 +1338,27 @@ void RegisterAllocator::AllocateSpillSlotFor(LiveInterval* interval) { // Find an available spill slot. size_t slot = 0; - for (size_t e = spill_slots->Size(); slot < e; ++slot) { - if (spill_slots->Get(slot) <= parent->GetStart() - && (slot == (e - 1) || spill_slots->Get(slot + 1) <= parent->GetStart())) { + for (size_t e = spill_slots->size(); slot < e; ++slot) { + if ((*spill_slots)[slot] <= parent->GetStart() + && (slot == (e - 1) || (*spill_slots)[slot + 1] <= parent->GetStart())) { break; } } size_t end = interval->GetLastSibling()->GetEnd(); if (parent->NeedsTwoSpillSlots()) { - if (slot == spill_slots->Size()) { + if (slot + 2u > spill_slots->size()) { // We need a new spill slot. - spill_slots->Add(end); - spill_slots->Add(end); - } else if (slot == spill_slots->Size() - 1) { - spill_slots->Put(slot, end); - spill_slots->Add(end); - } else { - spill_slots->Put(slot, end); - spill_slots->Put(slot + 1, end); + spill_slots->resize(slot + 2u, end); } + (*spill_slots)[slot] = end; + (*spill_slots)[slot + 1] = end; } else { - if (slot == spill_slots->Size()) { + if (slot == spill_slots->size()) { // We need a new spill slot. - spill_slots->Add(end); + spill_slots->push_back(end); } else { - spill_slots->Put(slot, end); + (*spill_slots)[slot] = end; } } @@ -1817,13 +1816,13 @@ void RegisterAllocator::Resolve() { size_t slot = current->GetSpillSlot(); switch (current->GetType()) { case Primitive::kPrimDouble: - slot += long_spill_slots_.Size(); + slot += long_spill_slots_.size(); FALLTHROUGH_INTENDED; case Primitive::kPrimLong: - slot += float_spill_slots_.Size(); + slot += float_spill_slots_.size(); FALLTHROUGH_INTENDED; case Primitive::kPrimFloat: - slot += int_spill_slots_.Size(); + slot += int_spill_slots_.size(); FALLTHROUGH_INTENDED; case Primitive::kPrimNot: case Primitive::kPrimInt: @@ -1906,8 +1905,7 @@ void RegisterAllocator::Resolve() { } // Assign temp locations. - for (size_t i = 0; i < temp_intervals_.Size(); ++i) { - LiveInterval* temp = temp_intervals_.Get(i); + for (LiveInterval* temp : temp_intervals_) { if (temp->IsHighInterval()) { // High intervals can be skipped, they are already handled by the low interval. continue; diff --git a/compiler/optimizing/register_allocator.h b/compiler/optimizing/register_allocator.h index e0304643e6..58600b789b 100644 --- a/compiler/optimizing/register_allocator.h +++ b/compiler/optimizing/register_allocator.h @@ -18,9 +18,9 @@ #define ART_COMPILER_OPTIMIZING_REGISTER_ALLOCATOR_H_ #include "arch/instruction_set.h" +#include "base/arena_containers.h" #include "base/macros.h" #include "primitive.h" -#include "utils/growable_array.h" namespace art { @@ -59,7 +59,7 @@ class RegisterAllocator { } // Helper method for validation. Used by unit testing. - static bool ValidateIntervals(const GrowableArray<LiveInterval*>& intervals, + static bool ValidateIntervals(const ArenaVector<LiveInterval*>& intervals, size_t number_of_spill_slots, size_t number_of_out_slots, const CodeGenerator& codegen, @@ -70,10 +70,10 @@ class RegisterAllocator { static bool CanAllocateRegistersFor(const HGraph& graph, InstructionSet instruction_set); size_t GetNumberOfSpillSlots() const { - return int_spill_slots_.Size() - + long_spill_slots_.Size() - + float_spill_slots_.Size() - + double_spill_slots_.Size() + return int_spill_slots_.size() + + long_spill_slots_.size() + + float_spill_slots_.size() + + double_spill_slots_.size() + catch_phi_spill_slots_; } @@ -87,7 +87,7 @@ class RegisterAllocator { void Resolve(); // Add `interval` in the given sorted list. - static void AddSorted(GrowableArray<LiveInterval*>* array, LiveInterval* interval); + static void AddSorted(ArenaVector<LiveInterval*>* array, LiveInterval* interval); // Split `interval` at the position `position`. The new interval starts at `position`. LiveInterval* Split(LiveInterval* interval, size_t position); @@ -159,13 +159,6 @@ class RegisterAllocator { size_t first_register_use, size_t* next_use); - // If `interval` has another half, remove it from the list of `intervals`. - // `index` holds the index at which `interval` is in `intervals`. - // Returns whether there is another half. - bool PotentiallyRemoveOtherHalf(LiveInterval* interval, - GrowableArray<LiveInterval*>* intervals, - size_t index); - ArenaAllocator* const allocator_; CodeGenerator* const codegen_; const SsaLivenessAnalysis& liveness_; @@ -173,43 +166,43 @@ class RegisterAllocator { // List of intervals for core registers that must be processed, ordered by start // position. Last entry is the interval that has the lowest start position. // This list is initially populated before doing the linear scan. - GrowableArray<LiveInterval*> unhandled_core_intervals_; + ArenaVector<LiveInterval*> unhandled_core_intervals_; // List of intervals for floating-point registers. Same comments as above. - GrowableArray<LiveInterval*> unhandled_fp_intervals_; + ArenaVector<LiveInterval*> unhandled_fp_intervals_; // Currently processed list of unhandled intervals. Either `unhandled_core_intervals_` // or `unhandled_fp_intervals_`. - GrowableArray<LiveInterval*>* unhandled_; + ArenaVector<LiveInterval*>* unhandled_; // List of intervals that have been processed. - GrowableArray<LiveInterval*> handled_; + ArenaVector<LiveInterval*> handled_; // List of intervals that are currently active when processing a new live interval. // That is, they have a live range that spans the start of the new interval. - GrowableArray<LiveInterval*> active_; + ArenaVector<LiveInterval*> active_; // List of intervals that are currently inactive when processing a new live interval. // That is, they have a lifetime hole that spans the start of the new interval. - GrowableArray<LiveInterval*> inactive_; + ArenaVector<LiveInterval*> inactive_; // Fixed intervals for physical registers. Such intervals cover the positions // where an instruction requires a specific register. - GrowableArray<LiveInterval*> physical_core_register_intervals_; - GrowableArray<LiveInterval*> physical_fp_register_intervals_; + ArenaVector<LiveInterval*> physical_core_register_intervals_; + ArenaVector<LiveInterval*> physical_fp_register_intervals_; // Intervals for temporaries. Such intervals cover the positions // where an instruction requires a temporary. - GrowableArray<LiveInterval*> temp_intervals_; + ArenaVector<LiveInterval*> temp_intervals_; // The spill slots allocated for live intervals. We ensure spill slots // are typed to avoid (1) doing moves and swaps between two different kinds // of registers, and (2) swapping between a single stack slot and a double // stack slot. This simplifies the parallel move resolver. - GrowableArray<size_t> int_spill_slots_; - GrowableArray<size_t> long_spill_slots_; - GrowableArray<size_t> float_spill_slots_; - GrowableArray<size_t> double_spill_slots_; + ArenaVector<size_t> int_spill_slots_; + ArenaVector<size_t> long_spill_slots_; + ArenaVector<size_t> float_spill_slots_; + ArenaVector<size_t> double_spill_slots_; // Spill slots allocated to catch phis. This category is special-cased because // (1) slots are allocated prior to linear scan and in reverse linear order, @@ -217,7 +210,7 @@ class RegisterAllocator { size_t catch_phi_spill_slots_; // Instructions that need a safepoint. - GrowableArray<HInstruction*> safepoints_; + ArenaVector<HInstruction*> safepoints_; // True if processing core registers. False if processing floating // point registers. diff --git a/compiler/optimizing/register_allocator_test.cc b/compiler/optimizing/register_allocator_test.cc index b72df868d3..2bb5a8bb08 100644 --- a/compiler/optimizing/register_allocator_test.cc +++ b/compiler/optimizing/register_allocator_test.cc @@ -64,83 +64,83 @@ TEST(RegisterAllocatorTest, ValidateIntervals) { std::unique_ptr<const X86InstructionSetFeatures> features_x86( X86InstructionSetFeatures::FromCppDefines()); x86::CodeGeneratorX86 codegen(graph, *features_x86.get(), CompilerOptions()); - GrowableArray<LiveInterval*> intervals(&allocator, 0); + ArenaVector<LiveInterval*> intervals(allocator.Adapter()); // Test with two intervals of the same range. { static constexpr size_t ranges[][2] = {{0, 42}}; - intervals.Add(BuildInterval(ranges, arraysize(ranges), &allocator, 0)); - intervals.Add(BuildInterval(ranges, arraysize(ranges), &allocator, 1)); + intervals.push_back(BuildInterval(ranges, arraysize(ranges), &allocator, 0)); + intervals.push_back(BuildInterval(ranges, arraysize(ranges), &allocator, 1)); ASSERT_TRUE(RegisterAllocator::ValidateIntervals( intervals, 0, 0, codegen, &allocator, true, false)); - intervals.Get(1)->SetRegister(0); + intervals[1]->SetRegister(0); ASSERT_FALSE(RegisterAllocator::ValidateIntervals( intervals, 0, 0, codegen, &allocator, true, false)); - intervals.Reset(); + intervals.clear(); } // Test with two non-intersecting intervals. { static constexpr size_t ranges1[][2] = {{0, 42}}; - intervals.Add(BuildInterval(ranges1, arraysize(ranges1), &allocator, 0)); + intervals.push_back(BuildInterval(ranges1, arraysize(ranges1), &allocator, 0)); static constexpr size_t ranges2[][2] = {{42, 43}}; - intervals.Add(BuildInterval(ranges2, arraysize(ranges2), &allocator, 1)); + intervals.push_back(BuildInterval(ranges2, arraysize(ranges2), &allocator, 1)); ASSERT_TRUE(RegisterAllocator::ValidateIntervals( intervals, 0, 0, codegen, &allocator, true, false)); - intervals.Get(1)->SetRegister(0); + intervals[1]->SetRegister(0); ASSERT_TRUE(RegisterAllocator::ValidateIntervals( intervals, 0, 0, codegen, &allocator, true, false)); - intervals.Reset(); + intervals.clear(); } // Test with two non-intersecting intervals, with one with a lifetime hole. { static constexpr size_t ranges1[][2] = {{0, 42}, {45, 48}}; - intervals.Add(BuildInterval(ranges1, arraysize(ranges1), &allocator, 0)); + intervals.push_back(BuildInterval(ranges1, arraysize(ranges1), &allocator, 0)); static constexpr size_t ranges2[][2] = {{42, 43}}; - intervals.Add(BuildInterval(ranges2, arraysize(ranges2), &allocator, 1)); + intervals.push_back(BuildInterval(ranges2, arraysize(ranges2), &allocator, 1)); ASSERT_TRUE(RegisterAllocator::ValidateIntervals( intervals, 0, 0, codegen, &allocator, true, false)); - intervals.Get(1)->SetRegister(0); + intervals[1]->SetRegister(0); ASSERT_TRUE(RegisterAllocator::ValidateIntervals( intervals, 0, 0, codegen, &allocator, true, false)); - intervals.Reset(); + intervals.clear(); } // Test with intersecting intervals. { static constexpr size_t ranges1[][2] = {{0, 42}, {44, 48}}; - intervals.Add(BuildInterval(ranges1, arraysize(ranges1), &allocator, 0)); + intervals.push_back(BuildInterval(ranges1, arraysize(ranges1), &allocator, 0)); static constexpr size_t ranges2[][2] = {{42, 47}}; - intervals.Add(BuildInterval(ranges2, arraysize(ranges2), &allocator, 1)); + intervals.push_back(BuildInterval(ranges2, arraysize(ranges2), &allocator, 1)); ASSERT_TRUE(RegisterAllocator::ValidateIntervals( intervals, 0, 0, codegen, &allocator, true, false)); - intervals.Get(1)->SetRegister(0); + intervals[1]->SetRegister(0); ASSERT_FALSE(RegisterAllocator::ValidateIntervals( intervals, 0, 0, codegen, &allocator, true, false)); - intervals.Reset(); + intervals.clear(); } // Test with siblings. { static constexpr size_t ranges1[][2] = {{0, 42}, {44, 48}}; - intervals.Add(BuildInterval(ranges1, arraysize(ranges1), &allocator, 0)); - intervals.Get(0)->SplitAt(43); + intervals.push_back(BuildInterval(ranges1, arraysize(ranges1), &allocator, 0)); + intervals[0]->SplitAt(43); static constexpr size_t ranges2[][2] = {{42, 47}}; - intervals.Add(BuildInterval(ranges2, arraysize(ranges2), &allocator, 1)); + intervals.push_back(BuildInterval(ranges2, arraysize(ranges2), &allocator, 1)); ASSERT_TRUE(RegisterAllocator::ValidateIntervals( intervals, 0, 0, codegen, &allocator, true, false)); - intervals.Get(1)->SetRegister(0); + intervals[1]->SetRegister(0); // Sibling of the first interval has no register allocated to it. ASSERT_TRUE(RegisterAllocator::ValidateIntervals( intervals, 0, 0, codegen, &allocator, true, false)); - intervals.Get(0)->GetNextSibling()->SetRegister(0); + intervals[0]->GetNextSibling()->SetRegister(0); ASSERT_FALSE(RegisterAllocator::ValidateIntervals( intervals, 0, 0, codegen, &allocator, true, false)); } @@ -429,7 +429,7 @@ TEST(RegisterAllocatorTest, FreeUntil) { // Populate the instructions in the liveness object, to please the register allocator. for (size_t i = 0; i < 60; ++i) { - liveness.instructions_from_lifetime_position_.Add( + liveness.instructions_from_lifetime_position_.push_back( graph->GetEntryBlock()->GetFirstInstruction()); } @@ -442,15 +442,15 @@ TEST(RegisterAllocatorTest, FreeUntil) { // we do not depend on an order. LiveInterval* interval = LiveInterval::MakeFixedInterval(&allocator, 0, Primitive::kPrimInt); interval->AddRange(40, 50); - register_allocator.inactive_.Add(interval); + register_allocator.inactive_.push_back(interval); interval = LiveInterval::MakeFixedInterval(&allocator, 0, Primitive::kPrimInt); interval->AddRange(20, 30); - register_allocator.inactive_.Add(interval); + register_allocator.inactive_.push_back(interval); interval = LiveInterval::MakeFixedInterval(&allocator, 0, Primitive::kPrimInt); interval->AddRange(60, 70); - register_allocator.inactive_.Add(interval); + register_allocator.inactive_.push_back(interval); register_allocator.number_of_registers_ = 1; register_allocator.registers_array_ = allocator.AllocArray<size_t>(1); @@ -460,10 +460,10 @@ TEST(RegisterAllocatorTest, FreeUntil) { ASSERT_TRUE(register_allocator.TryAllocateFreeReg(unhandled)); // Check that we have split the interval. - ASSERT_EQ(1u, register_allocator.unhandled_->Size()); + ASSERT_EQ(1u, register_allocator.unhandled_->size()); // Check that we know need to find a new register where the next interval // that uses the register starts. - ASSERT_EQ(20u, register_allocator.unhandled_->Get(0)->GetStart()); + ASSERT_EQ(20u, register_allocator.unhandled_->front()->GetStart()); } static HGraph* BuildIfElseWithPhi(ArenaAllocator* allocator, @@ -678,7 +678,7 @@ TEST(RegisterAllocatorTest, ExpectedInRegisterHint) { // Check that the field gets put in the register expected by its use. // Don't use SetInAt because we are overriding an already allocated location. - ret->GetLocations()->inputs_.Put(0, Location::RegisterLocation(2)); + ret->GetLocations()->inputs_[0] = Location::RegisterLocation(2); RegisterAllocator register_allocator(&allocator, &codegen, liveness); register_allocator.AllocateRegisters(); @@ -885,14 +885,14 @@ TEST(RegisterAllocatorTest, SpillInactive) { SsaLivenessAnalysis liveness(graph, &codegen); // Populate the instructions in the liveness object, to please the register allocator. for (size_t i = 0; i < 32; ++i) { - liveness.instructions_from_lifetime_position_.Add(user); + liveness.instructions_from_lifetime_position_.push_back(user); } RegisterAllocator register_allocator(&allocator, &codegen, liveness); - register_allocator.unhandled_core_intervals_.Add(fourth); - register_allocator.unhandled_core_intervals_.Add(third); - register_allocator.unhandled_core_intervals_.Add(second); - register_allocator.unhandled_core_intervals_.Add(first); + register_allocator.unhandled_core_intervals_.push_back(fourth); + register_allocator.unhandled_core_intervals_.push_back(third); + register_allocator.unhandled_core_intervals_.push_back(second); + register_allocator.unhandled_core_intervals_.push_back(first); // Set just one register available to make all intervals compete for the same. register_allocator.number_of_registers_ = 1; @@ -902,11 +902,11 @@ TEST(RegisterAllocatorTest, SpillInactive) { register_allocator.LinearScan(); // Test that there is no conflicts between intervals. - GrowableArray<LiveInterval*> intervals(&allocator, 0); - intervals.Add(first); - intervals.Add(second); - intervals.Add(third); - intervals.Add(fourth); + ArenaVector<LiveInterval*> intervals(allocator.Adapter()); + intervals.push_back(first); + intervals.push_back(second); + intervals.push_back(third); + intervals.push_back(fourth); ASSERT_TRUE(RegisterAllocator::ValidateIntervals( intervals, 0, 0, codegen, &allocator, true, false)); } diff --git a/compiler/optimizing/side_effects_analysis.cc b/compiler/optimizing/side_effects_analysis.cc index 1956781b79..338a3aaad0 100644 --- a/compiler/optimizing/side_effects_analysis.cc +++ b/compiler/optimizing/side_effects_analysis.cc @@ -21,8 +21,8 @@ namespace art { void SideEffectsAnalysis::Run() { // Inlining might have created more blocks, so we need to increase the size // if needed. - block_effects_.SetSize(graph_->GetBlocks().size()); - loop_effects_.SetSize(graph_->GetBlocks().size()); + block_effects_.resize(graph_->GetBlocks().size()); + loop_effects_.resize(graph_->GetBlocks().size()); // In DEBUG mode, ensure side effects are properly initialized to empty. if (kIsDebugBuild) { @@ -54,7 +54,7 @@ void SideEffectsAnalysis::Run() { } } - block_effects_.Put(block->GetBlockId(), effects); + block_effects_[block->GetBlockId()] = effects; if (block->IsLoopHeader()) { // The side effects of the loop header are part of the loop. @@ -76,16 +76,19 @@ void SideEffectsAnalysis::Run() { SideEffects SideEffectsAnalysis::GetLoopEffects(HBasicBlock* block) const { DCHECK(block->IsLoopHeader()); - return loop_effects_.Get(block->GetBlockId()); + DCHECK_LT(block->GetBlockId(), loop_effects_.size()); + return loop_effects_[block->GetBlockId()]; } SideEffects SideEffectsAnalysis::GetBlockEffects(HBasicBlock* block) const { - return block_effects_.Get(block->GetBlockId()); + DCHECK_LT(block->GetBlockId(), block_effects_.size()); + return block_effects_[block->GetBlockId()]; } void SideEffectsAnalysis::UpdateLoopEffects(HLoopInformation* info, SideEffects effects) { - int id = info->GetHeader()->GetBlockId(); - loop_effects_.Put(id, loop_effects_.Get(id).Union(effects)); + uint32_t id = info->GetHeader()->GetBlockId(); + DCHECK_LT(id, loop_effects_.size()); + loop_effects_[id] = loop_effects_[id].Union(effects); } } // namespace art diff --git a/compiler/optimizing/side_effects_analysis.h b/compiler/optimizing/side_effects_analysis.h index 9888140fb6..bac6088bf7 100644 --- a/compiler/optimizing/side_effects_analysis.h +++ b/compiler/optimizing/side_effects_analysis.h @@ -17,6 +17,7 @@ #ifndef ART_COMPILER_OPTIMIZING_SIDE_EFFECTS_ANALYSIS_H_ #define ART_COMPILER_OPTIMIZING_SIDE_EFFECTS_ANALYSIS_H_ +#include "base/arena_containers.h" #include "nodes.h" #include "optimization.h" @@ -27,8 +28,10 @@ class SideEffectsAnalysis : public HOptimization { explicit SideEffectsAnalysis(HGraph* graph) : HOptimization(graph, kSideEffectsAnalysisPassName), graph_(graph), - block_effects_(graph->GetArena(), graph->GetBlocks().size(), SideEffects::None()), - loop_effects_(graph->GetArena(), graph->GetBlocks().size(), SideEffects::None()) {} + block_effects_(graph->GetBlocks().size(), + graph->GetArena()->Adapter(kArenaAllocSideEffectsAnalysis)), + loop_effects_(graph->GetBlocks().size(), + graph->GetArena()->Adapter(kArenaAllocSideEffectsAnalysis)) {} SideEffects GetLoopEffects(HBasicBlock* block) const; SideEffects GetBlockEffects(HBasicBlock* block) const; @@ -51,11 +54,11 @@ class SideEffectsAnalysis : public HOptimization { // Side effects of individual blocks, that is the union of the side effects // of the instructions in the block. - GrowableArray<SideEffects> block_effects_; + ArenaVector<SideEffects> block_effects_; // Side effects of loops, that is the union of the side effects of the // blocks contained in that loop. - GrowableArray<SideEffects> loop_effects_; + ArenaVector<SideEffects> loop_effects_; ART_FRIEND_TEST(GVNTest, LoopSideEffects); DISALLOW_COPY_AND_ASSIGN(SideEffectsAnalysis); diff --git a/compiler/optimizing/ssa_builder.cc b/compiler/optimizing/ssa_builder.cc index 0ef86d80ed..fb11d76320 100644 --- a/compiler/optimizing/ssa_builder.cc +++ b/compiler/optimizing/ssa_builder.cc @@ -145,8 +145,14 @@ void DeadPhiHandling::VisitBasicBlock(HBasicBlock* block) { if (phi->IsDead() && phi->HasEnvironmentUses()) { phi->SetLive(); if (block->IsLoopHeader()) { - // Give a type to the loop phi, to guarantee convergence of the algorithm. - phi->SetType(phi->InputAt(0)->GetType()); + // Give a type to the loop phi to guarantee convergence of the algorithm. + // Note that the dead phi may already have a type if it is an equivalent + // generated for a typed LoadLocal. In that case we do not change the + // type because it could lead to an unsupported PrimNot/Float/Double -> + // PrimInt/Long transition and create same type equivalents. + if (phi->GetType() == Primitive::kPrimVoid) { + phi->SetType(phi->InputAt(0)->GetType()); + } AddToWorklist(phi); } else { // Because we are doing a reverse post order visit, all inputs of @@ -191,12 +197,6 @@ void DeadPhiHandling::Run() { ProcessWorklist(); } -static bool IsPhiEquivalentOf(HInstruction* instruction, HPhi* phi) { - return instruction != nullptr - && instruction->IsPhi() - && instruction->AsPhi()->GetRegNumber() == phi->GetRegNumber(); -} - void SsaBuilder::FixNullConstantType() { // The order doesn't matter here. for (HReversePostOrderIterator itb(*GetGraph()); !itb.Done(); itb.Advance()) { @@ -324,13 +324,13 @@ void SsaBuilder::BuildSsa() { // If the phi is not dead, or has no environment uses, there is nothing to do. if (!phi->IsDead() || !phi->HasEnvironmentUses()) continue; HInstruction* next = phi->GetNext(); - if (!IsPhiEquivalentOf(next, phi)) continue; + if (!phi->IsVRegEquivalentOf(next)) continue; if (next->AsPhi()->IsDead()) { // If the phi equivalent is dead, check if there is another one. next = next->GetNext(); - if (!IsPhiEquivalentOf(next, phi)) continue; + if (!phi->IsVRegEquivalentOf(next)) continue; // There can be at most two phi equivalents. - DCHECK(!IsPhiEquivalentOf(next->GetNext(), phi)); + DCHECK(!phi->IsVRegEquivalentOf(next->GetNext())); if (next->AsPhi()->IsDead()) continue; } // We found a live phi equivalent. Update the environment uses of `phi` with it. @@ -403,6 +403,24 @@ void SsaBuilder::VisitBasicBlock(HBasicBlock* block) { if (block->IsCatchBlock()) { // Catch phis were already created and inputs collected from throwing sites. + if (kIsDebugBuild) { + // Make sure there was at least one throwing instruction which initialized + // locals (guaranteed by HGraphBuilder) and that all try blocks have been + // visited already (from HTryBoundary scoping and reverse post order). + bool throwing_instruction_found = false; + bool catch_block_visited = false; + for (HReversePostOrderIterator it(*GetGraph()); !it.Done(); it.Advance()) { + HBasicBlock* current = it.Current(); + if (current == block) { + catch_block_visited = true; + } else if (current->IsTryBlock() && + current->GetTryCatchInformation()->GetTryEntry().HasExceptionHandler(*block)) { + DCHECK(!catch_block_visited) << "Catch block visited before its try block."; + throwing_instruction_found |= current->HasThrowingInstructions(); + } + } + DCHECK(throwing_instruction_found) << "No instructions throwing into a live catch block."; + } } else if (block->IsLoopHeader()) { // If the block is a loop header, we know we only have visited the pre header // because we are visiting in reverse post order. We create phis for all initialized diff --git a/compiler/optimizing/ssa_liveness_analysis.cc b/compiler/optimizing/ssa_liveness_analysis.cc index 1e9a813be9..b869d57be8 100644 --- a/compiler/optimizing/ssa_liveness_analysis.cc +++ b/compiler/optimizing/ssa_liveness_analysis.cc @@ -43,11 +43,11 @@ static bool IsInnerLoop(HLoopInformation* outer, HLoopInformation* inner) { && inner->IsIn(*outer); } -static void AddToListForLinearization(GrowableArray<HBasicBlock*>* worklist, HBasicBlock* block) { - size_t insert_at = worklist->Size(); +static void AddToListForLinearization(ArenaVector<HBasicBlock*>* worklist, HBasicBlock* block) { HLoopInformation* block_loop = block->GetLoopInformation(); - for (; insert_at > 0; --insert_at) { - HBasicBlock* current = worklist->Get(insert_at - 1); + auto insert_pos = worklist->rbegin(); // insert_pos.base() will be the actual position. + for (auto end = worklist->rend(); insert_pos != end; ++insert_pos) { + HBasicBlock* current = *insert_pos; HLoopInformation* current_loop = current->GetLoopInformation(); if (InSameLoop(block_loop, current_loop) || !IsLoop(current_loop) @@ -56,7 +56,7 @@ static void AddToListForLinearization(GrowableArray<HBasicBlock*>* worklist, HBa break; } } - worklist->InsertAt(insert_at, block); + worklist->insert(insert_pos.base(), block); } void SsaLivenessAnalysis::LinearizeGraph() { @@ -69,15 +69,15 @@ void SsaLivenessAnalysis::LinearizeGraph() { // current reverse post order in the graph, but it would require making // order queries to a GrowableArray, which is not the best data structure // for it. - GrowableArray<uint32_t> forward_predecessors(graph_->GetArena(), graph_->GetBlocks().size()); - forward_predecessors.SetSize(graph_->GetBlocks().size()); + ArenaVector<uint32_t> forward_predecessors(graph_->GetBlocks().size(), + graph_->GetArena()->Adapter(kArenaAllocSsaLiveness)); for (HReversePostOrderIterator it(*graph_); !it.Done(); it.Advance()) { HBasicBlock* block = it.Current(); size_t number_of_forward_predecessors = block->GetPredecessors().size(); if (block->IsLoopHeader()) { number_of_forward_predecessors -= block->GetLoopInformation()->NumberOfBackEdges(); } - forward_predecessors.Put(block->GetBlockId(), number_of_forward_predecessors); + forward_predecessors[block->GetBlockId()] = number_of_forward_predecessors; } // (2): Following a worklist approach, first start with the entry block, and @@ -85,20 +85,21 @@ void SsaLivenessAnalysis::LinearizeGraph() { // successor block are visited, the successor block is added in the worklist // following an order that satisfies the requirements to build our linear graph. graph_->linear_order_.reserve(graph_->GetReversePostOrder().size()); - GrowableArray<HBasicBlock*> worklist(graph_->GetArena(), 1); - worklist.Add(graph_->GetEntryBlock()); + ArenaVector<HBasicBlock*> worklist(graph_->GetArena()->Adapter(kArenaAllocSsaLiveness)); + worklist.push_back(graph_->GetEntryBlock()); do { - HBasicBlock* current = worklist.Pop(); + HBasicBlock* current = worklist.back(); + worklist.pop_back(); graph_->linear_order_.push_back(current); for (HBasicBlock* successor : current->GetSuccessors()) { int block_id = successor->GetBlockId(); - size_t number_of_remaining_predecessors = forward_predecessors.Get(block_id); + size_t number_of_remaining_predecessors = forward_predecessors[block_id]; if (number_of_remaining_predecessors == 1) { AddToListForLinearization(&worklist, successor); } - forward_predecessors.Put(block_id, number_of_remaining_predecessors - 1); + forward_predecessors[block_id] = number_of_remaining_predecessors - 1; } - } while (!worklist.IsEmpty()); + } while (!worklist.empty()); } void SsaLivenessAnalysis::NumberInstructions() { @@ -122,7 +123,7 @@ void SsaLivenessAnalysis::NumberInstructions() { codegen_->AllocateLocations(current); LocationSummary* locations = current->GetLocations(); if (locations != nullptr && locations->Out().IsValid()) { - instructions_from_ssa_index_.Add(current); + instructions_from_ssa_index_.push_back(current); current->SetSsaIndex(ssa_index++); current->SetLiveInterval( LiveInterval::MakeInterval(graph_->GetArena(), current->GetType(), current)); @@ -132,7 +133,7 @@ void SsaLivenessAnalysis::NumberInstructions() { lifetime_position += 2; // Add a null marker to notify we are starting a block. - instructions_from_lifetime_position_.Add(nullptr); + instructions_from_lifetime_position_.push_back(nullptr); for (HInstructionIterator inst_it(block->GetInstructions()); !inst_it.Done(); inst_it.Advance()) { @@ -140,12 +141,12 @@ void SsaLivenessAnalysis::NumberInstructions() { codegen_->AllocateLocations(current); LocationSummary* locations = current->GetLocations(); if (locations != nullptr && locations->Out().IsValid()) { - instructions_from_ssa_index_.Add(current); + instructions_from_ssa_index_.push_back(current); current->SetSsaIndex(ssa_index++); current->SetLiveInterval( LiveInterval::MakeInterval(graph_->GetArena(), current->GetType(), current)); } - instructions_from_lifetime_position_.Add(current); + instructions_from_lifetime_position_.push_back(current); current->SetLifetimePosition(lifetime_position); lifetime_position += 2; } @@ -158,9 +159,9 @@ void SsaLivenessAnalysis::NumberInstructions() { void SsaLivenessAnalysis::ComputeLiveness() { for (HLinearOrderIterator it(*graph_); !it.Done(); it.Advance()) { HBasicBlock* block = it.Current(); - block_infos_.Put( - block->GetBlockId(), - new (graph_->GetArena()) BlockInfo(graph_->GetArena(), *block, number_of_ssa_values_)); + DCHECK_LT(block->GetBlockId(), block_infos_.size()); + block_infos_[block->GetBlockId()] = + new (graph_->GetArena()) BlockInfo(graph_->GetArena(), *block, number_of_ssa_values_); } // Compute the live ranges, as well as the initial live_in, live_out, and kill sets. @@ -212,7 +213,7 @@ void SsaLivenessAnalysis::ComputeLiveRanges() { // Add a range that covers this block to all instructions live_in because of successors. // Instructions defined in this block will have their start of the range adjusted. for (uint32_t idx : live_in->Indexes()) { - HInstruction* current = instructions_from_ssa_index_.Get(idx); + HInstruction* current = GetInstructionFromSsaIndex(idx); current->GetLiveInterval()->AddRange(block->GetLifetimeStart(), block->GetLifetimeEnd()); } @@ -277,7 +278,7 @@ void SsaLivenessAnalysis::ComputeLiveRanges() { // For all live_in instructions at the loop header, we need to create a range // that covers the full loop. for (uint32_t idx : live_in->Indexes()) { - HInstruction* current = instructions_from_ssa_index_.Get(idx); + HInstruction* current = GetInstructionFromSsaIndex(idx); current->GetLiveInterval()->AddLoopRange(block->GetLifetimeStart(), last_position); } } diff --git a/compiler/optimizing/ssa_liveness_analysis.h b/compiler/optimizing/ssa_liveness_analysis.h index 3aedaa56a2..414cc7d951 100644 --- a/compiler/optimizing/ssa_liveness_analysis.h +++ b/compiler/optimizing/ssa_liveness_analysis.h @@ -206,7 +206,7 @@ class SafepointPosition : public ArenaObject<kArenaAllocMisc> { * An interval is a list of disjoint live ranges where an instruction is live. * Each instruction that has uses gets an interval. */ -class LiveInterval : public ArenaObject<kArenaAllocMisc> { +class LiveInterval : public ArenaObject<kArenaAllocSsaLiveness> { public: static LiveInterval* MakeInterval(ArenaAllocator* allocator, Primitive::Type type, @@ -1106,33 +1106,39 @@ class SsaLivenessAnalysis : public ValueObject { SsaLivenessAnalysis(HGraph* graph, CodeGenerator* codegen) : graph_(graph), codegen_(codegen), - block_infos_(graph->GetArena(), graph->GetBlocks().size()), - instructions_from_ssa_index_(graph->GetArena(), 0), - instructions_from_lifetime_position_(graph->GetArena(), 0), + block_infos_(graph->GetBlocks().size(), + nullptr, + graph->GetArena()->Adapter(kArenaAllocSsaLiveness)), + instructions_from_ssa_index_(graph->GetArena()->Adapter(kArenaAllocSsaLiveness)), + instructions_from_lifetime_position_(graph->GetArena()->Adapter(kArenaAllocSsaLiveness)), number_of_ssa_values_(0) { - block_infos_.SetSize(graph->GetBlocks().size()); } void Analyze(); BitVector* GetLiveInSet(const HBasicBlock& block) const { - return &block_infos_.Get(block.GetBlockId())->live_in_; + DCHECK_LT(block.GetBlockId(), block_infos_.size()); + return &block_infos_[block.GetBlockId()]->live_in_; } BitVector* GetLiveOutSet(const HBasicBlock& block) const { - return &block_infos_.Get(block.GetBlockId())->live_out_; + DCHECK_LT(block.GetBlockId(), block_infos_.size()); + return &block_infos_[block.GetBlockId()]->live_out_; } BitVector* GetKillSet(const HBasicBlock& block) const { - return &block_infos_.Get(block.GetBlockId())->kill_; + DCHECK_LT(block.GetBlockId(), block_infos_.size()); + return &block_infos_[block.GetBlockId()]->kill_; } HInstruction* GetInstructionFromSsaIndex(size_t index) const { - return instructions_from_ssa_index_.Get(index); + DCHECK_LT(index, instructions_from_ssa_index_.size()); + return instructions_from_ssa_index_[index]; } HInstruction* GetInstructionFromPosition(size_t index) const { - return instructions_from_lifetime_position_.Get(index); + DCHECK_LT(index, instructions_from_lifetime_position_.size()); + return instructions_from_lifetime_position_[index]; } HBasicBlock* GetBlockFromPosition(size_t index) const { @@ -1163,7 +1169,7 @@ class SsaLivenessAnalysis : public ValueObject { } size_t GetMaxLifetimePosition() const { - return instructions_from_lifetime_position_.Size() * 2 - 1; + return instructions_from_lifetime_position_.size() * 2 - 1; } size_t GetNumberOfSsaValues() const { @@ -1218,13 +1224,13 @@ class SsaLivenessAnalysis : public ValueObject { HGraph* const graph_; CodeGenerator* const codegen_; - GrowableArray<BlockInfo*> block_infos_; + ArenaVector<BlockInfo*> block_infos_; // Temporary array used when computing live_in, live_out, and kill sets. - GrowableArray<HInstruction*> instructions_from_ssa_index_; + ArenaVector<HInstruction*> instructions_from_ssa_index_; // Temporary array used when inserting moves in the graph. - GrowableArray<HInstruction*> instructions_from_lifetime_position_; + ArenaVector<HInstruction*> instructions_from_lifetime_position_; size_t number_of_ssa_values_; ART_FRIEND_TEST(RegisterAllocatorTest, SpillInactive); diff --git a/compiler/optimizing/ssa_phi_elimination.cc b/compiler/optimizing/ssa_phi_elimination.cc index a9f04cd201..72f9ddd506 100644 --- a/compiler/optimizing/ssa_phi_elimination.cc +++ b/compiler/optimizing/ssa_phi_elimination.cc @@ -35,7 +35,7 @@ void SsaDeadPhiElimination::MarkDeadPhis() { HUseListNode<HInstruction*>* current = use_it.Current(); HInstruction* user = current->GetUser(); if (!user->IsPhi()) { - worklist_.Add(phi); + worklist_.push_back(phi); phi->SetLive(); break; } @@ -44,12 +44,13 @@ void SsaDeadPhiElimination::MarkDeadPhis() { } // Process the worklist by propagating liveness to phi inputs. - while (!worklist_.IsEmpty()) { - HPhi* phi = worklist_.Pop(); + while (!worklist_.empty()) { + HPhi* phi = worklist_.back(); + worklist_.pop_back(); for (HInputIterator it(phi); !it.Done(); it.Advance()) { HInstruction* input = it.Current(); if (input->IsPhi() && input->AsPhi()->IsDead()) { - worklist_.Add(input->AsPhi()); + worklist_.push_back(input->AsPhi()); input->AsPhi()->SetLive(); } } @@ -103,12 +104,13 @@ void SsaRedundantPhiElimination::Run() { for (HReversePostOrderIterator it(*graph_); !it.Done(); it.Advance()) { HBasicBlock* block = it.Current(); for (HInstructionIterator inst_it(block->GetPhis()); !inst_it.Done(); inst_it.Advance()) { - worklist_.Add(inst_it.Current()->AsPhi()); + worklist_.push_back(inst_it.Current()->AsPhi()); } } - while (!worklist_.IsEmpty()) { - HPhi* phi = worklist_.Pop(); + while (!worklist_.empty()) { + HPhi* phi = worklist_.back(); + worklist_.pop_back(); // If the phi has already been processed, continue. if (!phi->IsInBlock()) { @@ -155,7 +157,7 @@ void SsaRedundantPhiElimination::Run() { HUseListNode<HInstruction*>* current = it.Current(); HInstruction* user = current->GetUser(); if (user->IsPhi()) { - worklist_.Add(user->AsPhi()); + worklist_.push_back(user->AsPhi()); } } diff --git a/compiler/optimizing/ssa_phi_elimination.h b/compiler/optimizing/ssa_phi_elimination.h index 67351f277b..b48e8200d5 100644 --- a/compiler/optimizing/ssa_phi_elimination.h +++ b/compiler/optimizing/ssa_phi_elimination.h @@ -17,6 +17,7 @@ #ifndef ART_COMPILER_OPTIMIZING_SSA_PHI_ELIMINATION_H_ #define ART_COMPILER_OPTIMIZING_SSA_PHI_ELIMINATION_H_ +#include "base/arena_containers.h" #include "nodes.h" #include "optimization.h" @@ -30,7 +31,9 @@ class SsaDeadPhiElimination : public HOptimization { public: explicit SsaDeadPhiElimination(HGraph* graph) : HOptimization(graph, kSsaDeadPhiEliminationPassName), - worklist_(graph->GetArena(), kDefaultWorklistSize) {} + worklist_(graph->GetArena()->Adapter(kArenaAllocSsaPhiElimination)) { + worklist_.reserve(kDefaultWorklistSize); + } void Run() OVERRIDE; @@ -40,7 +43,7 @@ class SsaDeadPhiElimination : public HOptimization { static constexpr const char* kSsaDeadPhiEliminationPassName = "dead_phi_elimination"; private: - GrowableArray<HPhi*> worklist_; + ArenaVector<HPhi*> worklist_; static constexpr size_t kDefaultWorklistSize = 8; @@ -57,14 +60,16 @@ class SsaRedundantPhiElimination : public HOptimization { public: explicit SsaRedundantPhiElimination(HGraph* graph) : HOptimization(graph, kSsaRedundantPhiEliminationPassName), - worklist_(graph->GetArena(), kDefaultWorklistSize) {} + worklist_(graph->GetArena()->Adapter(kArenaAllocSsaPhiElimination)) { + worklist_.reserve(kDefaultWorklistSize); + } void Run() OVERRIDE; static constexpr const char* kSsaRedundantPhiEliminationPassName = "redundant_phi_elimination"; private: - GrowableArray<HPhi*> worklist_; + ArenaVector<HPhi*> worklist_; static constexpr size_t kDefaultWorklistSize = 8; diff --git a/compiler/utils/array_ref.h b/compiler/utils/array_ref.h index 303e0d5ad4..48f0328dce 100644 --- a/compiler/utils/array_ref.h +++ b/compiler/utils/array_ref.h @@ -161,6 +161,15 @@ class ArrayRef { value_type* data() { return array_; } const value_type* data() const { return array_; } + ArrayRef SubArray(size_type pos) const { + return SubArray(pos, size_ - pos); + } + ArrayRef SubArray(size_type pos, size_type length) const { + DCHECK_LE(pos, size()); + DCHECK_LE(length, size() - pos); + return ArrayRef(array_ + pos, length); + } + private: T* array_; size_t size_; diff --git a/dexdump/Android.mk b/dexdump/Android.mk index a208ccf89b..ec2529e18f 100755 --- a/dexdump/Android.mk +++ b/dexdump/Android.mk @@ -34,8 +34,6 @@ LOCAL_C_INCLUDES := $(dexdump_c_includes) LOCAL_CFLAGS += -Wall LOCAL_SHARED_LIBRARIES += $(dexdump_libraries) LOCAL_MODULE := dexdump2 -LOCAL_MODULE_TAGS := optional -LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES) include $(BUILD_EXECUTABLE) endif # !SDK_ONLY diff --git a/dexdump/dexdump_test.cc b/dexdump/dexdump_test.cc index d9b210d767..4230cb26b7 100644 --- a/dexdump/dexdump_test.cc +++ b/dexdump/dexdump_test.cc @@ -43,12 +43,7 @@ class DexDumpTest : public CommonRuntimeTest { // Runs test with given arguments. bool Exec(const std::vector<std::string>& args, std::string* error_msg) { // TODO(ajcbik): dexdump2 -> dexdump - std::string file_path = GetTestAndroidRoot(); - if (IsHost()) { - file_path += "/bin/dexdump2"; - } else { - file_path += "/xbin/dexdump2"; - } + std::string file_path = GetTestAndroidRoot() + "/bin/dexdump2"; EXPECT_TRUE(OS::FileExists(file_path.c_str())) << file_path << " should be a valid file path"; std::vector<std::string> exec_argv = { file_path }; exec_argv.insert(exec_argv.end(), args.begin(), args.end()); diff --git a/runtime/Android.mk b/runtime/Android.mk index 995a1d5c0d..059c4cdd57 100644 --- a/runtime/Android.mk +++ b/runtime/Android.mk @@ -104,6 +104,7 @@ LIBART_COMMON_SRC_FILES := \ lambda/box_table.cc \ lambda/closure.cc \ lambda/closure_builder.cc \ + lambda/leaking_allocator.cc \ jni_internal.cc \ jobject_comparator.cc \ linear_alloc.cc \ diff --git a/runtime/arch/mips64/context_mips64.cc b/runtime/arch/mips64/context_mips64.cc index 6637c371d2..cc6dc7e17c 100644 --- a/runtime/arch/mips64/context_mips64.cc +++ b/runtime/arch/mips64/context_mips64.cc @@ -29,10 +29,10 @@ void Mips64Context::Reset() { std::fill_n(gprs_, arraysize(gprs_), nullptr); std::fill_n(fprs_, arraysize(fprs_), nullptr); gprs_[SP] = &sp_; - gprs_[RA] = &ra_; + gprs_[T9] = &t9_; // Initialize registers with easy to spot debug values. sp_ = Mips64Context::kBadGprBase + SP; - ra_ = Mips64Context::kBadGprBase + RA; + t9_ = Mips64Context::kBadGprBase + T9; } void Mips64Context::FillCalleeSaves(const StackVisitor& fr) { diff --git a/runtime/arch/mips64/context_mips64.h b/runtime/arch/mips64/context_mips64.h index e4a144f420..26fbcfe9d4 100644 --- a/runtime/arch/mips64/context_mips64.h +++ b/runtime/arch/mips64/context_mips64.h @@ -41,7 +41,7 @@ class Mips64Context : public Context { } void SetPC(uintptr_t new_pc) OVERRIDE { - SetGPR(RA, new_pc); + SetGPR(T9, new_pc); } bool IsAccessibleGPR(uint32_t reg) OVERRIDE { @@ -82,8 +82,9 @@ class Mips64Context : public Context { // Pointers to registers in the stack, initialized to null except for the special cases below. uintptr_t* gprs_[kNumberOfGpuRegisters]; uint64_t* fprs_[kNumberOfFpuRegisters]; - // Hold values for sp and ra (return address) if they are not located within a stack frame. - uintptr_t sp_, ra_; + // Hold values for sp and t9 if they are not located within a stack frame. We use t9 for the + // PC (as ra is required to be valid for single-frame deopt and must not be clobbered). + uintptr_t sp_, t9_; }; } // namespace mips64 } // namespace art diff --git a/runtime/arch/mips64/quick_entrypoints_mips64.S b/runtime/arch/mips64/quick_entrypoints_mips64.S index 1b50b2e246..ce1b2f3d24 100644 --- a/runtime/arch/mips64/quick_entrypoints_mips64.S +++ b/runtime/arch/mips64/quick_entrypoints_mips64.S @@ -431,7 +431,7 @@ ENTRY_NO_GP art_quick_do_long_jump ld $ra, 248($a0) ld $a0, 32($a0) move $v0, $zero # clear result registers v0 and v1 - jalr $zero, $ra # do long jump + jalr $zero, $t9 # do long jump (do not use ra, it must not be clobbered) move $v1, $zero END art_quick_do_long_jump diff --git a/runtime/arch/mips64/registers_mips64.h b/runtime/arch/mips64/registers_mips64.h index 38bc8f2687..cd94d5ec66 100644 --- a/runtime/arch/mips64/registers_mips64.h +++ b/runtime/arch/mips64/registers_mips64.h @@ -52,6 +52,7 @@ enum GpuRegister { S6 = 22, S7 = 23, T8 = 24, // More temporaries. + TMP = T8, // scratch register (in addition to AT) T9 = 25, K0 = 26, // Reserved for trap handler. K1 = 27, diff --git a/runtime/base/arena_allocator.cc b/runtime/base/arena_allocator.cc index 4e51f5555d..c1a108839b 100644 --- a/runtime/base/arena_allocator.cc +++ b/runtime/base/arena_allocator.cc @@ -55,6 +55,7 @@ const char* const ArenaAllocatorStatsImpl<kCount>::kAllocNames[] = { "RegAlloc ", "Data ", "STL ", + "GraphBuilder ", "Graph ", "BasicBlock ", "BlockList ", @@ -74,12 +75,20 @@ const char* const ArenaAllocatorStatsImpl<kCount>::kAllocNames[] = { "Environment ", "EnvVRegs ", "EnvLocations ", + "LocSummary ", "SsaBuilder ", "MoveOperands ", "CodeBuffer ", "StackMaps ", "BaselineMaps ", "Optimization ", + "GVN ", + "SsaLiveness ", + "SsaPhiElim ", + "RefTypeProp ", + "PrimTypeProp ", + "SideEffects ", + "RegAllocator ", }; template <bool kCount> diff --git a/runtime/base/arena_allocator.h b/runtime/base/arena_allocator.h index c5eb741b76..be9686287a 100644 --- a/runtime/base/arena_allocator.h +++ b/runtime/base/arena_allocator.h @@ -65,6 +65,7 @@ enum ArenaAllocKind { kArenaAllocRegAlloc, kArenaAllocData, kArenaAllocSTL, + kArenaAllocGraphBuilder, kArenaAllocGraph, kArenaAllocBasicBlock, kArenaAllocBlockList, @@ -84,12 +85,20 @@ enum ArenaAllocKind { kArenaAllocEnvironment, kArenaAllocEnvironmentVRegs, kArenaAllocEnvironmentLocations, + kArenaAllocLocationSummary, kArenaAllocSsaBuilder, kArenaAllocMoveOperands, kArenaAllocCodeBuffer, kArenaAllocStackMaps, kArenaAllocBaselineMaps, kArenaAllocOptimization, + kArenaAllocGvn, + kArenaAllocSsaLiveness, + kArenaAllocSsaPhiElimination, + kArenaAllocReferenceTypePropagation, + kArenaAllocPrimitiveTypePropagation, + kArenaAllocSideEffectsAnalysis, + kArenaAllocRegisterAllocator, kNumArenaAllocKinds }; diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc index 6b9c8aa353..8e42040c1d 100644 --- a/runtime/class_linker.cc +++ b/runtime/class_linker.cc @@ -128,7 +128,11 @@ void ClassLinker::ThrowEarlierClassFailure(mirror::Class* c) { // the previous error. Runtime* const runtime = Runtime::Current(); if (!runtime->IsAotCompiler()) { // Give info if this occurs at runtime. - LOG(INFO) << "Rejecting re-init on previously-failed class " << PrettyClass(c); + std::string extra; + if (c->GetVerifyErrorClass() != nullptr) { + extra = PrettyDescriptor(c->GetVerifyErrorClass()); + } + LOG(INFO) << "Rejecting re-init on previously-failed class " << PrettyClass(c) << ": " << extra; } CHECK(c->IsErroneous()) << PrettyClass(c) << " " << c->GetStatus(); @@ -1316,13 +1320,6 @@ void ClassLinker::VisitClassRoots(RootVisitor* visitor, VisitRootFlags flags) { // Need to make sure to not copy ArtMethods without doing read barriers since the roots are // marked concurrently and we don't hold the classlinker_classes_lock_ when we do the copy. boot_class_table_.VisitRoots(buffered_visitor); - // TODO: Avoid marking these to enable class unloading. - JavaVMExt* const vm = Runtime::Current()->GetJavaVM(); - for (const ClassLoaderData& data : class_loaders_) { - mirror::Object* class_loader = vm->DecodeWeakGlobal(self, data.weak_root); - // Don't need to update anything since the class loaders will be updated by SweepSystemWeaks. - visitor->VisitRootIfNonNull(&class_loader, RootInfo(kRootVMInternal)); - } } else if ((flags & kVisitRootFlagNewRoots) != 0) { for (auto& root : new_class_roots_) { mirror::Class* old_ref = root.Read<kWithoutReadBarrier>(); @@ -4266,6 +4263,11 @@ bool ClassLinker::LinkClass(Thread* self, ClassTable* const table = InsertClassTableForClassLoader(class_loader); mirror::Class* existing = table->UpdateClass(descriptor, h_new_class.Get(), ComputeModifiedUtf8Hash(descriptor)); + if (class_loader != nullptr) { + // We updated the class in the class table, perform the write barrier so that the GC knows + // about the change. + Runtime::Current()->GetHeap()->WriteBarrierEveryFieldOf(class_loader); + } CHECK_EQ(existing, klass.Get()); if (kIsDebugBuild && class_loader == nullptr && dex_cache_image_class_lookup_required_) { // Check a class loaded with the system class loader matches one in the image if the class diff --git a/runtime/dex_instruction-inl.h b/runtime/dex_instruction-inl.h index 7344d13805..e160a103d9 100644 --- a/runtime/dex_instruction-inl.h +++ b/runtime/dex_instruction-inl.h @@ -454,8 +454,8 @@ inline bool Instruction::HasVarArgs25x() const { return FormatOf(Opcode()) == k25x; } -// Copies all of the parameter registers into the arg array. Check the length with VRegB_25x()+1. -inline void Instruction::GetAllArgs25x(uint32_t arg[kMaxVarArgRegs]) const { +// Copies all of the parameter registers into the arg array. Check the length with VRegB_25x()+2. +inline void Instruction::GetAllArgs25x(uint32_t (&arg)[kMaxVarArgRegs25x]) const { DCHECK_EQ(FormatOf(Opcode()), k25x); /* @@ -500,19 +500,21 @@ inline void Instruction::GetAllArgs25x(uint32_t arg[kMaxVarArgRegs]) const { */ switch (count) { case 4: - arg[4] = (Fetch16(0) >> 8) & 0x0f; // vG + arg[5] = (Fetch16(0) >> 8) & 0x0f; // vG FALLTHROUGH_INTENDED; case 3: - arg[3] = (reg_list >> 12) & 0x0f; // vF + arg[4] = (reg_list >> 12) & 0x0f; // vF FALLTHROUGH_INTENDED; case 2: - arg[2] = (reg_list >> 8) & 0x0f; // vE + arg[3] = (reg_list >> 8) & 0x0f; // vE FALLTHROUGH_INTENDED; case 1: - arg[1] = (reg_list >> 4) & 0x0f; // vD + arg[2] = (reg_list >> 4) & 0x0f; // vD FALLTHROUGH_INTENDED; default: // case 0 + // The required lambda 'this' is actually a pair, but the pair is implicit. arg[0] = VRegC_25x(); // vC + arg[1] = arg[0] + 1; // vC + 1 break; } } diff --git a/runtime/dex_instruction.cc b/runtime/dex_instruction.cc index fc4df1475a..5250b0d79b 100644 --- a/runtime/dex_instruction.cc +++ b/runtime/dex_instruction.cc @@ -322,10 +322,10 @@ std::string Instruction::DumpString(const DexFile* file) const { } case k25x: { if (Opcode() == INVOKE_LAMBDA) { - uint32_t arg[kMaxVarArgRegs]; + uint32_t arg[kMaxVarArgRegs25x]; GetAllArgs25x(arg); const size_t num_extra_var_args = VRegB_25x(); - DCHECK_LE(num_extra_var_args + 1, kMaxVarArgRegs); + DCHECK_LE(num_extra_var_args + 2, arraysize(arg)); // invoke-lambda vC, {vD, vE, vF, vG} os << opcode << " v" << arg[0] << ", {"; @@ -333,7 +333,7 @@ std::string Instruction::DumpString(const DexFile* file) const { if (i != 0) { os << ", "; } - os << "v" << arg[i+1]; + os << "v" << arg[i+2]; // Don't print the pair of vC registers. Pair is implicit. } os << "}"; break; diff --git a/runtime/dex_instruction.h b/runtime/dex_instruction.h index df2d3799ab..48a12e53af 100644 --- a/runtime/dex_instruction.h +++ b/runtime/dex_instruction.h @@ -180,9 +180,11 @@ class Instruction { kVerifyVarArgRangeNonZero = 0x100000, kVerifyRuntimeOnly = 0x200000, kVerifyError = 0x400000, + kVerifyRegCString = 0x800000, }; static constexpr uint32_t kMaxVarArgRegs = 5; + static constexpr uint32_t kMaxVarArgRegs25x = 6; // lambdas are 2 registers. // Returns the size (in 2 byte code units) of this instruction. size_t SizeInCodeUnits() const { @@ -408,7 +410,7 @@ class Instruction { void GetVarArgs(uint32_t args[kMaxVarArgRegs]) const { return GetVarArgs(args, Fetch16(0)); } - void GetAllArgs25x(uint32_t args[kMaxVarArgRegs]) const; + void GetAllArgs25x(uint32_t (&args)[kMaxVarArgRegs25x]) const; // Returns the opcode field of the instruction. The given "inst_data" parameter must be the first // 16 bits of instruction. @@ -536,7 +538,7 @@ class Instruction { int GetVerifyTypeArgumentC() const { return (kInstructionVerifyFlags[Opcode()] & (kVerifyRegC | kVerifyRegCField | - kVerifyRegCNewArray | kVerifyRegCType | kVerifyRegCWide)); + kVerifyRegCNewArray | kVerifyRegCType | kVerifyRegCWide | kVerifyRegCString)); } int GetVerifyExtraFlags() const { diff --git a/runtime/dex_instruction_list.h b/runtime/dex_instruction_list.h index a176772a84..9d7e0c4409 100644 --- a/runtime/dex_instruction_list.h +++ b/runtime/dex_instruction_list.h @@ -263,10 +263,10 @@ V(0xF2, IGET_SHORT_QUICK, "iget-short-quick", k22c, true, kIndexFieldOffset, kContinue | kThrow | kLoad | kRegCFieldOrConstant, kVerifyRegA | kVerifyRegB | kVerifyRuntimeOnly) \ V(0xF3, INVOKE_LAMBDA, "invoke-lambda", k25x, false, kIndexNone, kContinue | kThrow | kInvoke | kExperimental, kVerifyRegC /*TODO: | kVerifyVarArg*/) \ V(0xF4, UNUSED_F4, "unused-f4", k10x, false, kIndexUnknown, 0, kVerifyError) \ - V(0xF5, UNUSED_F5, "unused-f5", k10x, false, kIndexUnknown, 0, kVerifyError) \ + V(0xF5, CAPTURE_VARIABLE, "capture-variable", k21c, false, kIndexStringRef, kExperimental, kVerifyRegA | kVerifyRegBString) \ /* TODO(iam): get rid of the unused 'false' column */ \ V(0xF6, CREATE_LAMBDA, "create-lambda", k21c, false_UNUSED, kIndexMethodRef, kContinue | kThrow | kExperimental, kVerifyRegA | kVerifyRegBMethod) \ - V(0xF7, UNUSED_F7, "unused-f7", k10x, false, kIndexUnknown, 0, kVerifyError) \ + V(0xF7, LIBERATE_VARIABLE, "liberate-variable", k22c, false, kIndexStringRef, kExperimental, kVerifyRegA | kVerifyRegB | kVerifyRegCString) \ V(0xF8, BOX_LAMBDA, "box-lambda", k22x, true, kIndexNone, kContinue | kExperimental, kVerifyRegA | kVerifyRegB) \ V(0xF9, UNBOX_LAMBDA, "unbox-lambda", k22c, true, kIndexTypeRef, kContinue | kThrow | kExperimental, kVerifyRegA | kVerifyRegB | kVerifyRegCType) \ V(0xFA, UNUSED_FA, "unused-fa", k10x, false, kIndexUnknown, 0, kVerifyError) \ diff --git a/runtime/gc/heap.cc b/runtime/gc/heap.cc index cfe77135b7..7d664faa40 100644 --- a/runtime/gc/heap.cc +++ b/runtime/gc/heap.cc @@ -1963,6 +1963,10 @@ HomogeneousSpaceCompactResult Heap::PerformHomogeneousSpaceCompact() { GrowForUtilization(semi_space_collector_); LogGC(kGcCauseHomogeneousSpaceCompact, collector); FinishGC(self, collector::kGcTypeFull); + { + ScopedObjectAccess soa(self); + soa.Vm()->UnloadNativeLibraries(); + } return HomogeneousSpaceCompactResult::kSuccess; } @@ -2104,6 +2108,10 @@ void Heap::TransitionCollector(CollectorType collector_type) { DCHECK(collector != nullptr); LogGC(kGcCauseCollectorTransition, collector); FinishGC(self, collector::kGcTypeFull); + { + ScopedObjectAccess soa(self); + soa.Vm()->UnloadNativeLibraries(); + } int32_t after_allocated = num_bytes_allocated_.LoadSequentiallyConsistent(); int32_t delta_allocated = before_allocated - after_allocated; std::string saved_str; @@ -2588,6 +2596,12 @@ collector::GcType Heap::CollectGarbageInternal(collector::GcType gc_type, FinishGC(self, gc_type); // Inform DDMS that a GC completed. Dbg::GcDidFinish(); + // Unload native libraries for class unloading. We do this after calling FinishGC to prevent + // deadlocks in case the JNI_OnUnload function does allocations. + { + ScopedObjectAccess soa(self); + soa.Vm()->UnloadNativeLibraries(); + } return gc_type; } diff --git a/runtime/interpreter/interpreter_common.cc b/runtime/interpreter/interpreter_common.cc index 7cfbb30d4d..ad34c9ad9e 100644 --- a/runtime/interpreter/interpreter_common.cc +++ b/runtime/interpreter/interpreter_common.cc @@ -28,6 +28,9 @@ namespace art { namespace interpreter { +// All lambda closures have to be a consecutive pair of virtual registers. +static constexpr size_t kLambdaVirtualRegisterWidth = 2; + void ThrowNullPointerExceptionFromInterpreter() { ThrowNullPointerExceptionFromDexPC(); } @@ -484,13 +487,16 @@ void AbortTransactionV(Thread* self, const char* fmt, va_list args) { } // Separate declaration is required solely for the attributes. -template<bool is_range, bool do_assignability_check> SHARED_REQUIRES(Locks::mutator_lock_) +template <bool is_range, + bool do_assignability_check, + size_t kVarArgMax> + SHARED_REQUIRES(Locks::mutator_lock_) static inline bool DoCallCommon(ArtMethod* called_method, Thread* self, ShadowFrame& shadow_frame, JValue* result, uint16_t number_of_inputs, - uint32_t arg[Instruction::kMaxVarArgRegs], + uint32_t (&arg)[kVarArgMax], uint32_t vregC) ALWAYS_INLINE; SHARED_REQUIRES(Locks::mutator_lock_) @@ -510,13 +516,15 @@ static inline bool NeedsInterpreter(Thread* self, ShadowFrame* new_shadow_frame) Dbg::IsForcedInterpreterNeededForCalling(self, target); } -template<bool is_range, bool do_assignability_check> +template <bool is_range, + bool do_assignability_check, + size_t kVarArgMax> static inline bool DoCallCommon(ArtMethod* called_method, Thread* self, ShadowFrame& shadow_frame, JValue* result, uint16_t number_of_inputs, - uint32_t arg[Instruction::kMaxVarArgRegs], + uint32_t (&arg)[kVarArgMax], uint32_t vregC) { bool string_init = false; // Replace calls to String.<init> with equivalent StringFactory call. @@ -561,10 +569,10 @@ static inline bool DoCallCommon(ArtMethod* called_method, number_of_inputs--; // Rewrite the var-args, dropping the 0th argument ("this") - for (uint32_t i = 1; i < Instruction::kMaxVarArgRegs; ++i) { + for (uint32_t i = 1; i < arraysize(arg); ++i) { arg[i - 1] = arg[i]; } - arg[Instruction::kMaxVarArgRegs - 1] = 0; + arg[arraysize(arg) - 1] = 0; // Rewrite the non-var-arg case vregC++; // Skips the 0th vreg in the range ("this"). @@ -670,7 +678,7 @@ static inline bool DoCallCommon(ArtMethod* called_method, AssignRegister(new_shadow_frame, shadow_frame, dest_reg, src_reg); } } else { - DCHECK_LE(number_of_inputs, Instruction::kMaxVarArgRegs); + DCHECK_LE(number_of_inputs, arraysize(arg)); for (; arg_index < number_of_inputs; ++arg_index) { AssignRegister(new_shadow_frame, shadow_frame, first_dest_reg + arg_index, arg[arg_index]); @@ -737,12 +745,13 @@ bool DoLambdaCall(ArtMethod* called_method, Thread* self, ShadowFrame& shadow_fr const Instruction* inst, uint16_t inst_data, JValue* result) { const uint4_t num_additional_registers = inst->VRegB_25x(); // Argument word count. - const uint16_t number_of_inputs = num_additional_registers + 1; - // The first input register is always present and is not encoded in the count. + const uint16_t number_of_inputs = num_additional_registers + kLambdaVirtualRegisterWidth; + // The lambda closure register is always present and is not encoded in the count. + // Furthermore, the lambda closure register is always wide, so it counts as 2 inputs. // TODO: find a cleaner way to separate non-range and range information without duplicating // code. - uint32_t arg[Instruction::kMaxVarArgRegs]; // only used in invoke-XXX. + uint32_t arg[Instruction::kMaxVarArgRegs25x]; // only used in invoke-XXX. uint32_t vregC = 0; // only used in invoke-XXX-range. if (is_range) { vregC = inst->VRegC_3rc(); @@ -768,7 +777,7 @@ bool DoCall(ArtMethod* called_method, Thread* self, ShadowFrame& shadow_frame, // TODO: find a cleaner way to separate non-range and range information without duplicating // code. - uint32_t arg[Instruction::kMaxVarArgRegs]; // only used in invoke-XXX. + uint32_t arg[Instruction::kMaxVarArgRegs] = {}; // only used in invoke-XXX. uint32_t vregC = 0; if (is_range) { vregC = inst->VRegC_3rc(); diff --git a/runtime/interpreter/interpreter_common.h b/runtime/interpreter/interpreter_common.h index 7398778d15..f57bddbb4f 100644 --- a/runtime/interpreter/interpreter_common.h +++ b/runtime/interpreter/interpreter_common.h @@ -34,7 +34,12 @@ #include "dex_instruction-inl.h" #include "entrypoints/entrypoint_utils-inl.h" #include "handle_scope-inl.h" +#include "lambda/art_lambda_method.h" #include "lambda/box_table.h" +#include "lambda/closure.h" +#include "lambda/closure_builder-inl.h" +#include "lambda/leaking_allocator.h" +#include "lambda/shorty_field_type.h" #include "mirror/class-inl.h" #include "mirror/method.h" #include "mirror/object-inl.h" @@ -133,32 +138,44 @@ static inline bool IsValidLambdaTargetOrThrow(ArtMethod* called_method) return success; } -// Write out the 'ArtMethod*' into vreg and vreg+1 +// Write out the 'Closure*' into vreg and vreg+1, as if it was a jlong. static inline void WriteLambdaClosureIntoVRegs(ShadowFrame& shadow_frame, - const ArtMethod& called_method, + const lambda::Closure* lambda_closure, uint32_t vreg) { // Split the method into a lo and hi 32 bits so we can encode them into 2 virtual registers. - uint32_t called_method_lo = static_cast<uint32_t>(reinterpret_cast<uintptr_t>(&called_method)); - uint32_t called_method_hi = static_cast<uint32_t>(reinterpret_cast<uint64_t>(&called_method) + uint32_t closure_lo = static_cast<uint32_t>(reinterpret_cast<uintptr_t>(lambda_closure)); + uint32_t closure_hi = static_cast<uint32_t>(reinterpret_cast<uint64_t>(lambda_closure) >> BitSizeOf<uint32_t>()); // Use uint64_t instead of uintptr_t to allow shifting past the max on 32-bit. static_assert(sizeof(uint64_t) >= sizeof(uintptr_t), "Impossible"); - DCHECK_NE(called_method_lo | called_method_hi, 0u); + DCHECK_NE(closure_lo | closure_hi, 0u); - shadow_frame.SetVReg(vreg, called_method_lo); - shadow_frame.SetVReg(vreg + 1, called_method_hi); + shadow_frame.SetVReg(vreg, closure_lo); + shadow_frame.SetVReg(vreg + 1, closure_hi); } // Handles create-lambda instructions. // Returns true on success, otherwise throws an exception and returns false. // (Exceptions are thrown by creating a new exception and then being put in the thread TLS) // +// The closure must be allocated big enough to hold the data, and should not be +// pre-initialized. It is initialized with the actual captured variables as a side-effect, +// although this should be unimportant to the caller since this function also handles storing it to +// the ShadowFrame. +// // As a work-in-progress implementation, this shoves the ArtMethod object corresponding // to the target dex method index into the target register vA and vA + 1. template<bool do_access_check> -static inline bool DoCreateLambda(Thread* self, ShadowFrame& shadow_frame, - const Instruction* inst) { +static inline bool DoCreateLambda(Thread* self, + const Instruction* inst, + /*inout*/ShadowFrame& shadow_frame, + /*inout*/lambda::ClosureBuilder* closure_builder, + /*inout*/lambda::Closure* uninitialized_closure) { + DCHECK(closure_builder != nullptr); + DCHECK(uninitialized_closure != nullptr); + DCHECK_ALIGNED(uninitialized_closure, alignof(lambda::Closure)); + /* * create-lambda is opcode 0x21c * - vA is the target register where the closure will be stored into @@ -171,16 +188,69 @@ static inline bool DoCreateLambda(Thread* self, ShadowFrame& shadow_frame, ArtMethod* const called_method = FindMethodFromCode<kStatic, do_access_check>( method_idx, &receiver, sf_method, self); - uint32_t vregA = inst->VRegA_21c(); + uint32_t vreg_dest_closure = inst->VRegA_21c(); if (UNLIKELY(!IsValidLambdaTargetOrThrow(called_method))) { CHECK(self->IsExceptionPending()); - shadow_frame.SetVReg(vregA, 0u); - shadow_frame.SetVReg(vregA + 1, 0u); + shadow_frame.SetVReg(vreg_dest_closure, 0u); + shadow_frame.SetVReg(vreg_dest_closure + 1, 0u); return false; } - WriteLambdaClosureIntoVRegs(shadow_frame, *called_method, vregA); + lambda::ArtLambdaMethod* initialized_lambda_method; + // Initialize the ArtLambdaMethod with the right data. + { + lambda::ArtLambdaMethod* uninitialized_lambda_method = + reinterpret_cast<lambda::ArtLambdaMethod*>( + lambda::LeakingAllocator::AllocateMemory(self, sizeof(lambda::ArtLambdaMethod))); + + std::string captured_variables_shorty = closure_builder->GetCapturedVariableShortyTypes(); + std::string captured_variables_long_type_desc; + + // Synthesize a long type descriptor from the short one. + for (char shorty : captured_variables_shorty) { + lambda::ShortyFieldType shorty_field_type(shorty); + if (shorty_field_type.IsObject()) { + // Not the true type, but good enough until we implement verifier support. + captured_variables_long_type_desc += "Ljava/lang/Object;"; + UNIMPLEMENTED(FATAL) << "create-lambda with an object captured variable"; + } else if (shorty_field_type.IsLambda()) { + // Not the true type, but good enough until we implement verifier support. + captured_variables_long_type_desc += "Ljava/lang/Runnable;"; + UNIMPLEMENTED(FATAL) << "create-lambda with a lambda captured variable"; + } else { + // The primitive types have the same length shorty or not, so this is always correct. + DCHECK(shorty_field_type.IsPrimitive()); + captured_variables_long_type_desc += shorty_field_type; + } + } + + // Copy strings to dynamically allocated storage. This leaks, but that's ok. Fix it later. + // TODO: Strings need to come from the DexFile, so they won't need their own allocations. + char* captured_variables_type_desc = lambda::LeakingAllocator::MakeFlexibleInstance<char>( + self, + captured_variables_long_type_desc.size() + 1); + strcpy(captured_variables_type_desc, captured_variables_long_type_desc.c_str()); + char* captured_variables_shorty_copy = lambda::LeakingAllocator::MakeFlexibleInstance<char>( + self, + captured_variables_shorty.size() + 1); + strcpy(captured_variables_shorty_copy, captured_variables_shorty.c_str()); + + new (uninitialized_lambda_method) lambda::ArtLambdaMethod(called_method, + captured_variables_type_desc, + captured_variables_shorty_copy, + true); // innate lambda + initialized_lambda_method = uninitialized_lambda_method; + } + + // Write all the closure captured variables and the closure header into the closure. + lambda::Closure* initialized_closure; + { + initialized_closure = + closure_builder->CreateInPlace(uninitialized_closure, initialized_lambda_method); + } + + WriteLambdaClosureIntoVRegs(/*inout*/shadow_frame, initialized_closure, vreg_dest_closure); return true; } @@ -189,13 +259,11 @@ static inline bool DoCreateLambda(Thread* self, ShadowFrame& shadow_frame, // Validates that the art method points to a valid lambda function, otherwise throws // an exception and returns null. // (Exceptions are thrown by creating a new exception and then being put in the thread TLS) -static inline ArtMethod* ReadLambdaClosureFromVRegsOrThrow(ShadowFrame& shadow_frame, - uint32_t vreg) +static inline lambda::Closure* ReadLambdaClosureFromVRegsOrThrow(ShadowFrame& shadow_frame, + uint32_t vreg) SHARED_REQUIRES(Locks::mutator_lock_) { - // TODO(iam): Introduce a closure abstraction that will contain the captured variables - // instead of just an ArtMethod. - // This is temporarily using 2 vregs because a native ArtMethod can be up to 64-bit, - // but once proper variable capture is implemented it will only use 1 vreg. + // Lambda closures take up a consecutive pair of 2 virtual registers. + // On 32-bit the high bits are always 0. uint32_t vc_value_lo = shadow_frame.GetVReg(vreg); uint32_t vc_value_hi = shadow_frame.GetVReg(vreg + 1); @@ -204,17 +272,285 @@ static inline ArtMethod* ReadLambdaClosureFromVRegsOrThrow(ShadowFrame& shadow_f // Use uint64_t instead of uintptr_t to allow left-shifting past the max on 32-bit. static_assert(sizeof(uint64_t) >= sizeof(uintptr_t), "Impossible"); - ArtMethod* const called_method = reinterpret_cast<ArtMethod* const>(vc_value_ptr); + lambda::Closure* const lambda_closure = reinterpret_cast<lambda::Closure*>(vc_value_ptr); + DCHECK_ALIGNED(lambda_closure, alignof(lambda::Closure)); // Guard against the user passing a null closure, which is odd but (sadly) semantically valid. - if (UNLIKELY(called_method == nullptr)) { + if (UNLIKELY(lambda_closure == nullptr)) { ThrowNullPointerExceptionFromInterpreter(); return nullptr; - } else if (UNLIKELY(!IsValidLambdaTargetOrThrow(called_method))) { + } else if (UNLIKELY(!IsValidLambdaTargetOrThrow(lambda_closure->GetTargetMethod()))) { + // Sanity check against data corruption. return nullptr; } - return called_method; + return lambda_closure; +} + +// Forward declaration for lock annotations. See below for documentation. +template <bool do_access_check> +static inline const char* GetStringDataByDexStringIndexOrThrow(ShadowFrame& shadow_frame, + uint32_t string_idx) + SHARED_REQUIRES(Locks::mutator_lock_); + +// Find the c-string data corresponding to a dex file's string index. +// Otherwise, returns null if not found and throws a VerifyError. +// +// Note that with do_access_check=false, we never return null because the verifier +// must guard against invalid string indices. +// (Exceptions are thrown by creating a new exception and then being put in the thread TLS) +template <bool do_access_check> +static inline const char* GetStringDataByDexStringIndexOrThrow(ShadowFrame& shadow_frame, + uint32_t string_idx) { + ArtMethod* method = shadow_frame.GetMethod(); + const DexFile* dex_file = method->GetDexFile(); + + mirror::Class* declaring_class = method->GetDeclaringClass(); + if (!do_access_check) { + // MethodVerifier refuses methods with string_idx out of bounds. + DCHECK_LT(string_idx, declaring_class->GetDexCache()->NumStrings()); + } else { + // Access checks enabled: perform string index bounds ourselves. + if (string_idx >= dex_file->GetHeader().string_ids_size_) { + ThrowVerifyError(declaring_class, "String index '%" PRIu32 "' out of bounds", + string_idx); + return nullptr; + } + } + + const char* type_string = dex_file->StringDataByIdx(string_idx); + + if (UNLIKELY(type_string == nullptr)) { + CHECK_EQ(false, do_access_check) + << " verifier should've caught invalid string index " << string_idx; + CHECK_EQ(true, do_access_check) + << " string idx size check should've caught invalid string index " << string_idx; + } + + return type_string; +} + +// Handles capture-variable instructions. +// Returns true on success, otherwise throws an exception and returns false. +// (Exceptions are thrown by creating a new exception and then being put in the thread TLS) +template<bool do_access_check> +static inline bool DoCaptureVariable(Thread* self, + const Instruction* inst, + /*inout*/ShadowFrame& shadow_frame, + /*inout*/lambda::ClosureBuilder* closure_builder) { + DCHECK(closure_builder != nullptr); + using lambda::ShortyFieldType; + /* + * capture-variable is opcode 0xf6, fmt 0x21c + * - vA is the source register of the variable that will be captured + * - vB is the string ID of the variable's type that will be captured + */ + const uint32_t source_vreg = inst->VRegA_21c(); + const uint32_t string_idx = inst->VRegB_21c(); + // TODO: this should be a proper [type id] instead of a [string ID] pointing to a type. + + const char* type_string = GetStringDataByDexStringIndexOrThrow<do_access_check>(shadow_frame, + string_idx); + if (UNLIKELY(type_string == nullptr)) { + CHECK(self->IsExceptionPending()); + return false; + } + + char type_first_letter = type_string[0]; + ShortyFieldType shorty_type; + if (do_access_check && + UNLIKELY(!ShortyFieldType::MaybeCreate(type_first_letter, /*out*/&shorty_type))) { // NOLINT: [whitespace/comma] [3] + ThrowVerifyError(shadow_frame.GetMethod()->GetDeclaringClass(), + "capture-variable vB must be a valid type"); + return false; + } else { + // Already verified that the type is valid. + shorty_type = ShortyFieldType(type_first_letter); + } + + const size_t captured_variable_count = closure_builder->GetCaptureCount(); + + // Note: types are specified explicitly so that the closure is packed tightly. + switch (shorty_type) { + case ShortyFieldType::kBoolean: { + uint32_t primitive_narrow_value = shadow_frame.GetVReg(source_vreg); + closure_builder->CaptureVariablePrimitive<bool>(primitive_narrow_value); + break; + } + case ShortyFieldType::kByte: { + uint32_t primitive_narrow_value = shadow_frame.GetVReg(source_vreg); + closure_builder->CaptureVariablePrimitive<int8_t>(primitive_narrow_value); + break; + } + case ShortyFieldType::kChar: { + uint32_t primitive_narrow_value = shadow_frame.GetVReg(source_vreg); + closure_builder->CaptureVariablePrimitive<uint16_t>(primitive_narrow_value); + break; + } + case ShortyFieldType::kShort: { + uint32_t primitive_narrow_value = shadow_frame.GetVReg(source_vreg); + closure_builder->CaptureVariablePrimitive<int16_t>(primitive_narrow_value); + break; + } + case ShortyFieldType::kInt: { + uint32_t primitive_narrow_value = shadow_frame.GetVReg(source_vreg); + closure_builder->CaptureVariablePrimitive<int32_t>(primitive_narrow_value); + break; + } + case ShortyFieldType::kDouble: { + closure_builder->CaptureVariablePrimitive(shadow_frame.GetVRegDouble(source_vreg)); + break; + } + case ShortyFieldType::kFloat: { + closure_builder->CaptureVariablePrimitive(shadow_frame.GetVRegFloat(source_vreg)); + break; + } + case ShortyFieldType::kLambda: { + UNIMPLEMENTED(FATAL) << " capture-variable with type kLambda"; + // TODO: Capturing lambdas recursively will be done at a later time. + UNREACHABLE(); + } + case ShortyFieldType::kLong: { + closure_builder->CaptureVariablePrimitive(shadow_frame.GetVRegLong(source_vreg)); + break; + } + case ShortyFieldType::kObject: { + closure_builder->CaptureVariableObject(shadow_frame.GetVRegReference(source_vreg)); + UNIMPLEMENTED(FATAL) << " capture-variable with type kObject"; + // TODO: finish implementing this. disabled for now since we can't track lambda refs for GC. + UNREACHABLE(); + } + + default: + LOG(FATAL) << "Invalid shorty type value " << shorty_type; + UNREACHABLE(); + } + + DCHECK_EQ(captured_variable_count + 1, closure_builder->GetCaptureCount()); + + return true; +} + +// Handles capture-variable instructions. +// Returns true on success, otherwise throws an exception and returns false. +// (Exceptions are thrown by creating a new exception and then being put in the thread TLS) +template<bool do_access_check> +static inline bool DoLiberateVariable(Thread* self, + const Instruction* inst, + size_t captured_variable_index, + /*inout*/ShadowFrame& shadow_frame) { + using lambda::ShortyFieldType; + /* + * liberate-variable is opcode 0xf7, fmt 0x22c + * - vA is the destination register + * - vB is the register with the lambda closure in it + * - vC is the string ID which needs to be a valid field type descriptor + */ + + const uint32_t dest_vreg = inst->VRegA_22c(); + const uint32_t closure_vreg = inst->VRegB_22c(); + const uint32_t string_idx = inst->VRegC_22c(); + // TODO: this should be a proper [type id] instead of a [string ID] pointing to a type. + + + // Synthesize a long type descriptor from a shorty type descriptor list. + // TODO: Fix the dex encoding to contain the long and short type descriptors. + const char* type_string = GetStringDataByDexStringIndexOrThrow<do_access_check>(shadow_frame, + string_idx); + if (UNLIKELY(do_access_check && type_string == nullptr)) { + CHECK(self->IsExceptionPending()); + shadow_frame.SetVReg(dest_vreg, 0); + return false; + } + + char type_first_letter = type_string[0]; + ShortyFieldType shorty_type; + if (do_access_check && + UNLIKELY(!ShortyFieldType::MaybeCreate(type_first_letter, /*out*/&shorty_type))) { // NOLINT: [whitespace/comma] [3] + ThrowVerifyError(shadow_frame.GetMethod()->GetDeclaringClass(), + "liberate-variable vC must be a valid type"); + shadow_frame.SetVReg(dest_vreg, 0); + return false; + } else { + // Already verified that the type is valid. + shorty_type = ShortyFieldType(type_first_letter); + } + + // Check for closure being null *after* the type check. + // This way we can access the type info in case we fail later, to know how many vregs to clear. + const lambda::Closure* lambda_closure = + ReadLambdaClosureFromVRegsOrThrow(/*inout*/shadow_frame, closure_vreg); + + // Failed lambda target runtime check, an exception was raised. + if (UNLIKELY(lambda_closure == nullptr)) { + CHECK(self->IsExceptionPending()); + + // Clear the destination vreg(s) to be safe. + shadow_frame.SetVReg(dest_vreg, 0); + if (shorty_type.IsPrimitiveWide() || shorty_type.IsLambda()) { + shadow_frame.SetVReg(dest_vreg + 1, 0); + } + return false; + } + + if (do_access_check && + UNLIKELY(captured_variable_index >= lambda_closure->GetNumberOfCapturedVariables())) { + ThrowVerifyError(shadow_frame.GetMethod()->GetDeclaringClass(), + "liberate-variable captured variable index %zu out of bounds", + lambda_closure->GetNumberOfCapturedVariables()); + // Clear the destination vreg(s) to be safe. + shadow_frame.SetVReg(dest_vreg, 0); + if (shorty_type.IsPrimitiveWide() || shorty_type.IsLambda()) { + shadow_frame.SetVReg(dest_vreg + 1, 0); + } + return false; + } + + // Verify that the runtime type of the captured-variable matches the requested dex type. + if (do_access_check) { + ShortyFieldType actual_type = lambda_closure->GetCapturedShortyType(captured_variable_index); + if (actual_type != shorty_type) { + ThrowVerifyError(shadow_frame.GetMethod()->GetDeclaringClass(), + "cannot liberate-variable of runtime type '%c' to dex type '%c'", + static_cast<char>(actual_type), + static_cast<char>(shorty_type)); + + shadow_frame.SetVReg(dest_vreg, 0); + if (shorty_type.IsPrimitiveWide() || shorty_type.IsLambda()) { + shadow_frame.SetVReg(dest_vreg + 1, 0); + } + return false; + } + + if (actual_type.IsLambda() || actual_type.IsObject()) { + UNIMPLEMENTED(FATAL) << "liberate-variable type checks needs to " + << "parse full type descriptor for objects and lambdas"; + } + } + + // Unpack the captured variable from the closure into the correct type, then save it to the vreg. + if (shorty_type.IsPrimitiveNarrow()) { + uint32_t primitive_narrow_value = + lambda_closure->GetCapturedPrimitiveNarrow(captured_variable_index); + shadow_frame.SetVReg(dest_vreg, primitive_narrow_value); + } else if (shorty_type.IsPrimitiveWide()) { + uint64_t primitive_wide_value = + lambda_closure->GetCapturedPrimitiveWide(captured_variable_index); + shadow_frame.SetVRegLong(dest_vreg, static_cast<int64_t>(primitive_wide_value)); + } else if (shorty_type.IsObject()) { + mirror::Object* unpacked_object = + lambda_closure->GetCapturedObject(captured_variable_index); + shadow_frame.SetVRegReference(dest_vreg, unpacked_object); + + UNIMPLEMENTED(FATAL) << "liberate-variable cannot unpack objects yet"; + } else if (shorty_type.IsLambda()) { + UNIMPLEMENTED(FATAL) << "liberate-variable cannot unpack lambdas yet"; + } else { + LOG(FATAL) << "unreachable"; + UNREACHABLE(); + } + + return true; } template<bool do_access_check> @@ -229,22 +565,24 @@ static inline bool DoInvokeLambda(Thread* self, ShadowFrame& shadow_frame, const * * - reading var-args for 0x25 gets us vD,vE,vF,vG (but not vB) */ - uint32_t vC = inst->VRegC_25x(); - ArtMethod* const called_method = ReadLambdaClosureFromVRegsOrThrow(shadow_frame, vC); + uint32_t vreg_closure = inst->VRegC_25x(); + const lambda::Closure* lambda_closure = + ReadLambdaClosureFromVRegsOrThrow(shadow_frame, vreg_closure); // Failed lambda target runtime check, an exception was raised. - if (UNLIKELY(called_method == nullptr)) { + if (UNLIKELY(lambda_closure == nullptr)) { CHECK(self->IsExceptionPending()); result->SetJ(0); return false; } + ArtMethod* const called_method = lambda_closure->GetTargetMethod(); // Invoke a non-range lambda return DoLambdaCall<false, do_access_check>(called_method, self, shadow_frame, inst, inst_data, result); } -// Handles invoke-XXX/range instructions. +// Handles invoke-XXX/range instructions (other than invoke-lambda[-range]). // Returns true on success, otherwise throws an exception and returns false. template<InvokeType type, bool is_range, bool do_access_check> static inline bool DoInvoke(Thread* self, ShadowFrame& shadow_frame, const Instruction* inst, @@ -521,17 +859,17 @@ static inline bool DoBoxLambda(Thread* self, ShadowFrame& shadow_frame, const In uint32_t vreg_target_object = inst->VRegA_22x(inst_data); uint32_t vreg_source_closure = inst->VRegB_22x(); - ArtMethod* closure_method = ReadLambdaClosureFromVRegsOrThrow(shadow_frame, - vreg_source_closure); + lambda::Closure* lambda_closure = ReadLambdaClosureFromVRegsOrThrow(shadow_frame, + vreg_source_closure); // Failed lambda target runtime check, an exception was raised. - if (UNLIKELY(closure_method == nullptr)) { + if (UNLIKELY(lambda_closure == nullptr)) { CHECK(self->IsExceptionPending()); return false; } mirror::Object* closure_as_object = - Runtime::Current()->GetLambdaBoxTable()->BoxLambda(closure_method); + Runtime::Current()->GetLambdaBoxTable()->BoxLambda(lambda_closure); // Failed to box the lambda, an exception was raised. if (UNLIKELY(closure_as_object == nullptr)) { @@ -564,16 +902,16 @@ static inline bool DoUnboxLambda(Thread* self, return false; } - ArtMethod* unboxed_closure = nullptr; + lambda::Closure* unboxed_closure = nullptr; // Raise an exception if unboxing fails. if (!Runtime::Current()->GetLambdaBoxTable()->UnboxLambda(boxed_closure_object, - &unboxed_closure)) { + /*out*/&unboxed_closure)) { CHECK(self->IsExceptionPending()); return false; } DCHECK(unboxed_closure != nullptr); - WriteLambdaClosureIntoVRegs(shadow_frame, *unboxed_closure, vreg_target_closure); + WriteLambdaClosureIntoVRegs(/*inout*/shadow_frame, unboxed_closure, vreg_target_closure); return true; } @@ -650,10 +988,13 @@ EXPLICIT_DO_INVOKE_VIRTUAL_QUICK_TEMPLATE_DECL(true); // invoke-virtual-quick- #undef EXPLICIT_INSTANTIATION_DO_INVOKE_VIRTUAL_QUICK // Explicitly instantiate all DoCreateLambda functions. -#define EXPLICIT_DO_CREATE_LAMBDA_DECL(_do_check) \ -template SHARED_REQUIRES(Locks::mutator_lock_) \ -bool DoCreateLambda<_do_check>(Thread* self, ShadowFrame& shadow_frame, \ - const Instruction* inst) +#define EXPLICIT_DO_CREATE_LAMBDA_DECL(_do_check) \ +template SHARED_REQUIRES(Locks::mutator_lock_) \ +bool DoCreateLambda<_do_check>(Thread* self, \ + const Instruction* inst, \ + /*inout*/ShadowFrame& shadow_frame, \ + /*inout*/lambda::ClosureBuilder* closure_builder, \ + /*inout*/lambda::Closure* uninitialized_closure); EXPLICIT_DO_CREATE_LAMBDA_DECL(false); // create-lambda EXPLICIT_DO_CREATE_LAMBDA_DECL(true); // create-lambda @@ -689,7 +1030,29 @@ EXPLICIT_DO_UNBOX_LAMBDA_DECL(false); // unbox-lambda EXPLICIT_DO_UNBOX_LAMBDA_DECL(true); // unbox-lambda #undef EXPLICIT_DO_BOX_LAMBDA_DECL +// Explicitly instantiate all DoCaptureVariable functions. +#define EXPLICIT_DO_CAPTURE_VARIABLE_DECL(_do_check) \ +template SHARED_REQUIRES(Locks::mutator_lock_) \ +bool DoCaptureVariable<_do_check>(Thread* self, \ + const Instruction* inst, \ + ShadowFrame& shadow_frame, \ + lambda::ClosureBuilder* closure_builder); + +EXPLICIT_DO_CAPTURE_VARIABLE_DECL(false); // capture-variable +EXPLICIT_DO_CAPTURE_VARIABLE_DECL(true); // capture-variable +#undef EXPLICIT_DO_CREATE_LAMBDA_DECL +// Explicitly instantiate all DoLiberateVariable functions. +#define EXPLICIT_DO_LIBERATE_VARIABLE_DECL(_do_check) \ +template SHARED_REQUIRES(Locks::mutator_lock_) \ +bool DoLiberateVariable<_do_check>(Thread* self, \ + const Instruction* inst, \ + size_t captured_variable_index, \ + ShadowFrame& shadow_frame); \ + +EXPLICIT_DO_LIBERATE_VARIABLE_DECL(false); // liberate-variable +EXPLICIT_DO_LIBERATE_VARIABLE_DECL(true); // liberate-variable +#undef EXPLICIT_DO_LIBERATE_LAMBDA_DECL } // namespace interpreter } // namespace art diff --git a/runtime/interpreter/interpreter_goto_table_impl.cc b/runtime/interpreter/interpreter_goto_table_impl.cc index 72e2ba0e7b..9677d79de3 100644 --- a/runtime/interpreter/interpreter_goto_table_impl.cc +++ b/runtime/interpreter/interpreter_goto_table_impl.cc @@ -17,9 +17,13 @@ #if !defined(__clang__) // Clang 3.4 fails to build the goto interpreter implementation. + +#include "base/stl_util.h" // MakeUnique #include "interpreter_common.h" #include "safe_math.h" +#include <memory> // std::unique_ptr + namespace art { namespace interpreter { @@ -179,6 +183,9 @@ JValue ExecuteGotoImpl(Thread* self, const DexFile::CodeItem* code_item, ShadowF } } + std::unique_ptr<lambda::ClosureBuilder> lambda_closure_builder; + size_t lambda_captured_variable_index = 0; + // Jump to first instruction. ADVANCE(0); UNREACHABLE_CODE_CHECK(); @@ -2412,7 +2419,20 @@ JValue ExecuteGotoImpl(Thread* self, const DexFile::CodeItem* code_item, ShadowF HANDLE_INSTRUCTION_END(); HANDLE_EXPERIMENTAL_INSTRUCTION_START(CREATE_LAMBDA) { - bool success = DoCreateLambda<true>(self, shadow_frame, inst); + if (lambda_closure_builder == nullptr) { + // DoCreateLambda always needs a ClosureBuilder, even if it has 0 captured variables. + lambda_closure_builder = MakeUnique<lambda::ClosureBuilder>(); + } + + // TODO: these allocations should not leak, and the lambda method should not be local. + lambda::Closure* lambda_closure = + reinterpret_cast<lambda::Closure*>(alloca(lambda_closure_builder->GetSize())); + bool success = DoCreateLambda<do_access_check>(self, + inst, + /*inout*/shadow_frame, + /*inout*/lambda_closure_builder.get(), + /*inout*/lambda_closure); + lambda_closure_builder.reset(nullptr); // reset state of variables captured POSSIBLY_HANDLE_PENDING_EXCEPTION(!success, 2); } HANDLE_EXPERIMENTAL_INSTRUCTION_END(); @@ -2429,6 +2449,31 @@ JValue ExecuteGotoImpl(Thread* self, const DexFile::CodeItem* code_item, ShadowF } HANDLE_EXPERIMENTAL_INSTRUCTION_END(); + HANDLE_EXPERIMENTAL_INSTRUCTION_START(CAPTURE_VARIABLE) { + if (lambda_closure_builder == nullptr) { + lambda_closure_builder = MakeUnique<lambda::ClosureBuilder>(); + } + + bool success = DoCaptureVariable<do_access_check>(self, + inst, + /*inout*/shadow_frame, + /*inout*/lambda_closure_builder.get()); + + POSSIBLY_HANDLE_PENDING_EXCEPTION(!success, 2); + } + HANDLE_EXPERIMENTAL_INSTRUCTION_END(); + + HANDLE_EXPERIMENTAL_INSTRUCTION_START(LIBERATE_VARIABLE) { + bool success = DoLiberateVariable<do_access_check>(self, + inst, + lambda_captured_variable_index, + /*inout*/shadow_frame); + // Temporarily only allow sequences of 'liberate-variable, liberate-variable, ...' + lambda_captured_variable_index++; + POSSIBLY_HANDLE_PENDING_EXCEPTION(!success, 2); + } + HANDLE_EXPERIMENTAL_INSTRUCTION_END(); + HANDLE_INSTRUCTION_START(UNUSED_3E) UnexpectedOpcode(inst, shadow_frame); HANDLE_INSTRUCTION_END(); @@ -2465,14 +2510,6 @@ JValue ExecuteGotoImpl(Thread* self, const DexFile::CodeItem* code_item, ShadowF UnexpectedOpcode(inst, shadow_frame); HANDLE_INSTRUCTION_END(); - HANDLE_INSTRUCTION_START(UNUSED_F5) - UnexpectedOpcode(inst, shadow_frame); - HANDLE_INSTRUCTION_END(); - - HANDLE_INSTRUCTION_START(UNUSED_F7) - UnexpectedOpcode(inst, shadow_frame); - HANDLE_INSTRUCTION_END(); - HANDLE_INSTRUCTION_START(UNUSED_FA) UnexpectedOpcode(inst, shadow_frame); HANDLE_INSTRUCTION_END(); diff --git a/runtime/interpreter/interpreter_switch_impl.cc b/runtime/interpreter/interpreter_switch_impl.cc index b5cc11e070..083dfb5267 100644 --- a/runtime/interpreter/interpreter_switch_impl.cc +++ b/runtime/interpreter/interpreter_switch_impl.cc @@ -14,9 +14,12 @@ * limitations under the License. */ +#include "base/stl_util.h" // MakeUnique #include "interpreter_common.h" #include "safe_math.h" +#include <memory> // std::unique_ptr + namespace art { namespace interpreter { @@ -82,6 +85,11 @@ JValue ExecuteSwitchImpl(Thread* self, const DexFile::CodeItem* code_item, const uint16_t* const insns = code_item->insns_; const Instruction* inst = Instruction::At(insns + dex_pc); uint16_t inst_data; + + // TODO: collapse capture-variable+create-lambda into one opcode, then we won't need + // to keep this live for the scope of the entire function call. + std::unique_ptr<lambda::ClosureBuilder> lambda_closure_builder; + size_t lambda_captured_variable_index = 0; while (true) { dex_pc = inst->GetDexPc(insns); shadow_frame.SetDexPC(dex_pc); @@ -2235,19 +2243,63 @@ JValue ExecuteSwitchImpl(Thread* self, const DexFile::CodeItem* code_item, POSSIBLY_HANDLE_PENDING_EXCEPTION(!success, Next_2xx); break; } + case Instruction::CAPTURE_VARIABLE: { + if (!IsExperimentalInstructionEnabled(inst)) { + UnexpectedOpcode(inst, shadow_frame); + } + + if (lambda_closure_builder == nullptr) { + lambda_closure_builder = MakeUnique<lambda::ClosureBuilder>(); + } + + PREAMBLE(); + bool success = DoCaptureVariable<do_access_check>(self, + inst, + /*inout*/shadow_frame, + /*inout*/lambda_closure_builder.get()); + POSSIBLY_HANDLE_PENDING_EXCEPTION(!success, Next_2xx); + break; + } case Instruction::CREATE_LAMBDA: { if (!IsExperimentalInstructionEnabled(inst)) { UnexpectedOpcode(inst, shadow_frame); } PREAMBLE(); - bool success = DoCreateLambda<do_access_check>(self, shadow_frame, inst); + + if (lambda_closure_builder == nullptr) { + // DoCreateLambda always needs a ClosureBuilder, even if it has 0 captured variables. + lambda_closure_builder = MakeUnique<lambda::ClosureBuilder>(); + } + + // TODO: these allocations should not leak, and the lambda method should not be local. + lambda::Closure* lambda_closure = + reinterpret_cast<lambda::Closure*>(alloca(lambda_closure_builder->GetSize())); + bool success = DoCreateLambda<do_access_check>(self, + inst, + /*inout*/shadow_frame, + /*inout*/lambda_closure_builder.get(), + /*inout*/lambda_closure); + lambda_closure_builder.reset(nullptr); // reset state of variables captured + POSSIBLY_HANDLE_PENDING_EXCEPTION(!success, Next_2xx); + break; + } + case Instruction::LIBERATE_VARIABLE: { + if (!IsExperimentalInstructionEnabled(inst)) { + UnexpectedOpcode(inst, shadow_frame); + } + + PREAMBLE(); + bool success = DoLiberateVariable<do_access_check>(self, + inst, + lambda_captured_variable_index, + /*inout*/shadow_frame); + // Temporarily only allow sequences of 'liberate-variable, liberate-variable, ...' + lambda_captured_variable_index++; POSSIBLY_HANDLE_PENDING_EXCEPTION(!success, Next_2xx); break; } - case Instruction::UNUSED_F4: - case Instruction::UNUSED_F5: - case Instruction::UNUSED_F7: { + case Instruction::UNUSED_F4: { if (!IsExperimentalInstructionEnabled(inst)) { UnexpectedOpcode(inst, shadow_frame); } diff --git a/runtime/java_vm_ext.cc b/runtime/java_vm_ext.cc index 531e03926a..ab70d02349 100644 --- a/runtime/java_vm_ext.cc +++ b/runtime/java_vm_ext.cc @@ -60,7 +60,7 @@ class SharedLibrary { : path_(path), handle_(handle), needs_native_bridge_(false), - class_loader_(env->NewGlobalRef(class_loader)), + class_loader_(env->NewWeakGlobalRef(class_loader)), jni_on_load_lock_("JNI_OnLoad lock"), jni_on_load_cond_("JNI_OnLoad condition variable", jni_on_load_lock_), jni_on_load_thread_id_(self->GetThreadId()), @@ -70,11 +70,11 @@ class SharedLibrary { ~SharedLibrary() { Thread* self = Thread::Current(); if (self != nullptr) { - self->GetJniEnv()->DeleteGlobalRef(class_loader_); + self->GetJniEnv()->DeleteWeakGlobalRef(class_loader_); } } - jobject GetClassLoader() const { + jweak GetClassLoader() const { return class_loader_; } @@ -131,7 +131,13 @@ class SharedLibrary { return needs_native_bridge_; } - void* FindSymbol(const std::string& symbol_name) { + void* FindSymbol(const std::string& symbol_name, const char* shorty = nullptr) { + return NeedsNativeBridge() + ? FindSymbolWithNativeBridge(symbol_name.c_str(), shorty) + : FindSymbolWithoutNativeBridge(symbol_name.c_str()); + } + + void* FindSymbolWithoutNativeBridge(const std::string& symbol_name) { CHECK(!NeedsNativeBridge()); return dlsym(handle_, symbol_name.c_str()); @@ -160,9 +166,9 @@ class SharedLibrary { // True if a native bridge is required. bool needs_native_bridge_; - // The ClassLoader this library is associated with, a global JNI reference that is + // The ClassLoader this library is associated with, a weak global JNI reference that is // created/deleted with the scope of the library. - const jobject class_loader_; + const jweak class_loader_; // Guards remaining items. Mutex jni_on_load_lock_ DEFAULT_MUTEX_ACQUIRED_AFTER; @@ -184,7 +190,10 @@ class Libraries { STLDeleteValues(&libraries_); } - void Dump(std::ostream& os) const { + // NO_THREAD_SAFETY_ANALYSIS since this may be called from Dumpable. Dumpable can't be annotated + // properly due to the template. The caller should be holding the jni_libraries_lock_. + void Dump(std::ostream& os) const NO_THREAD_SAFETY_ANALYSIS { + Locks::jni_libraries_lock_->AssertHeld(Thread::Current()); bool first = true; for (const auto& library : libraries_) { if (!first) { @@ -195,16 +204,17 @@ class Libraries { } } - size_t size() const { + size_t size() const REQUIRES(Locks::jni_libraries_lock_) { return libraries_.size(); } - SharedLibrary* Get(const std::string& path) { + SharedLibrary* Get(const std::string& path) REQUIRES(Locks::jni_libraries_lock_) { auto it = libraries_.find(path); return (it == libraries_.end()) ? nullptr : it->second; } - void Put(const std::string& path, SharedLibrary* library) { + void Put(const std::string& path, SharedLibrary* library) + REQUIRES(Locks::jni_libraries_lock_) { libraries_.Put(path, library); } @@ -217,24 +227,18 @@ class Libraries { const mirror::ClassLoader* declaring_class_loader = m->GetDeclaringClass()->GetClassLoader(); ScopedObjectAccessUnchecked soa(Thread::Current()); for (const auto& lib : libraries_) { - SharedLibrary* library = lib.second; + SharedLibrary* const library = lib.second; if (soa.Decode<mirror::ClassLoader*>(library->GetClassLoader()) != declaring_class_loader) { // We only search libraries loaded by the appropriate ClassLoader. continue; } // Try the short name then the long name... - void* fn; - if (library->NeedsNativeBridge()) { - const char* shorty = m->GetShorty(); - fn = library->FindSymbolWithNativeBridge(jni_short_name, shorty); - if (fn == nullptr) { - fn = library->FindSymbolWithNativeBridge(jni_long_name, shorty); - } - } else { - fn = library->FindSymbol(jni_short_name); - if (fn == nullptr) { - fn = library->FindSymbol(jni_long_name); - } + const char* shorty = library->NeedsNativeBridge() + ? m->GetShorty() + : nullptr; + void* fn = library->FindSymbol(jni_short_name, shorty); + if (fn == nullptr) { + fn = library->FindSymbol(jni_long_name, shorty); } if (fn != nullptr) { VLOG(jni) << "[Found native code for " << PrettyMethod(m) @@ -249,11 +253,46 @@ class Libraries { return nullptr; } + // Unload native libraries with cleared class loaders. + void UnloadNativeLibraries() + REQUIRES(!Locks::jni_libraries_lock_) + SHARED_REQUIRES(Locks::mutator_lock_) { + ScopedObjectAccessUnchecked soa(Thread::Current()); + typedef void (*JNI_OnUnloadFn)(JavaVM*, void*); + std::vector<JNI_OnUnloadFn> unload_functions; + { + MutexLock mu(soa.Self(), *Locks::jni_libraries_lock_); + for (auto it = libraries_.begin(); it != libraries_.end(); ) { + SharedLibrary* const library = it->second; + // If class loader is null then it was unloaded, call JNI_OnUnload. + if (soa.Decode<mirror::ClassLoader*>(library->GetClassLoader()) == nullptr) { + void* const sym = library->FindSymbol("JNI_OnUnload", nullptr); + if (sym == nullptr) { + VLOG(jni) << "[No JNI_OnUnload found in \"" << library->GetPath() << "\"]"; + } else { + VLOG(jni) << "[JNI_OnUnload found for \"" << library->GetPath() << "\"]"; + JNI_OnUnloadFn jni_on_unload = reinterpret_cast<JNI_OnUnloadFn>(sym); + unload_functions.push_back(jni_on_unload); + } + delete library; + it = libraries_.erase(it); + } else { + ++it; + } + } + } + // Do this without holding the jni libraries lock to prevent possible deadlocks. + for (JNI_OnUnloadFn fn : unload_functions) { + VLOG(jni) << "Calling JNI_OnUnload"; + (*fn)(soa.Vm(), nullptr); + } + } + private: - AllocationTrackingSafeMap<std::string, SharedLibrary*, kAllocatorTagJNILibraries> libraries_; + AllocationTrackingSafeMap<std::string, SharedLibrary*, kAllocatorTagJNILibraries> libraries_ + GUARDED_BY(Locks::jni_libraries_lock_); }; - class JII { public: static jint DestroyJavaVM(JavaVM* vm) { @@ -641,6 +680,10 @@ void JavaVMExt::DumpReferenceTables(std::ostream& os) { } } +void JavaVMExt::UnloadNativeLibraries() { + libraries_.get()->UnloadNativeLibraries(); +} + bool JavaVMExt::LoadNativeLibrary(JNIEnv* env, const std::string& path, jobject class_loader, std::string* error_msg) { error_msg->clear(); @@ -738,10 +781,8 @@ bool JavaVMExt::LoadNativeLibrary(JNIEnv* env, const std::string& path, jobject void* sym; if (needs_native_bridge) { library->SetNeedsNativeBridge(); - sym = library->FindSymbolWithNativeBridge("JNI_OnLoad", nullptr); - } else { - sym = dlsym(handle, "JNI_OnLoad"); } + sym = library->FindSymbol("JNI_OnLoad", nullptr); if (sym == nullptr) { VLOG(jni) << "[No JNI_OnLoad found in \"" << path << "\"]"; was_successful = true; diff --git a/runtime/java_vm_ext.h b/runtime/java_vm_ext.h index b539bbdba3..c1fbdc0389 100644 --- a/runtime/java_vm_ext.h +++ b/runtime/java_vm_ext.h @@ -88,6 +88,11 @@ class JavaVMExt : public JavaVM { bool LoadNativeLibrary(JNIEnv* env, const std::string& path, jobject javaLoader, std::string* error_msg); + // Unload native libraries with cleared class loaders. + void UnloadNativeLibraries() + REQUIRES(!Locks::jni_libraries_lock_) + SHARED_REQUIRES(Locks::mutator_lock_); + /** * Returns a pointer to the code for the native method 'm', found * using dlsym(3) on every native library that's been loaded so far. @@ -184,7 +189,9 @@ class JavaVMExt : public JavaVM { // Not guarded by globals_lock since we sometimes use SynchronizedGet in Thread::DecodeJObject. IndirectReferenceTable globals_; - std::unique_ptr<Libraries> libraries_ GUARDED_BY(Locks::jni_libraries_lock_); + // No lock annotation since UnloadNativeLibraries is called on libraries_ but locks the + // jni_libraries_lock_ internally. + std::unique_ptr<Libraries> libraries_; // Used by -Xcheck:jni. const JNIInvokeInterface* const unchecked_functions_; diff --git a/runtime/jit/jit.h b/runtime/jit/jit.h index 643bc23da3..e73ba82278 100644 --- a/runtime/jit/jit.h +++ b/runtime/jit/jit.h @@ -67,6 +67,9 @@ class Jit { void DumpInfo(std::ostream& os); // Add a timing logger to cumulative_timings_. void AddTimingLogger(const TimingLogger& logger); + JitInstrumentationCache* GetInstrumentationCache() const { + return instrumentation_cache_.get(); + } private: Jit(); diff --git a/runtime/jit/jit_instrumentation.cc b/runtime/jit/jit_instrumentation.cc index d437dd5d56..4f4a97f455 100644 --- a/runtime/jit/jit_instrumentation.cc +++ b/runtime/jit/jit_instrumentation.cc @@ -24,11 +24,21 @@ namespace art { namespace jit { -class JitCompileTask : public Task { +class JitCompileTask FINAL : public Task { public: - explicit JitCompileTask(ArtMethod* method) : method_(method) {} + explicit JitCompileTask(ArtMethod* method) : method_(method) { + ScopedObjectAccess soa(Thread::Current()); + // Add a global ref to the class to prevent class unloading until compilation is done. + klass_ = soa.Vm()->AddGlobalRef(soa.Self(), method_->GetDeclaringClass()); + CHECK(klass_ != nullptr); + } + + ~JitCompileTask() { + ScopedObjectAccess soa(Thread::Current()); + soa.Vm()->DeleteGlobalRef(soa.Self(), klass_); + } - virtual void Run(Thread* self) OVERRIDE { + void Run(Thread* self) OVERRIDE { ScopedObjectAccess soa(self); VLOG(jit) << "JitCompileTask compiling method " << PrettyMethod(method_); if (!Runtime::Current()->GetJit()->CompileMethod(method_, self)) { @@ -36,12 +46,13 @@ class JitCompileTask : public Task { } } - virtual void Finalize() OVERRIDE { + void Finalize() OVERRIDE { delete this; } private: ArtMethod* const method_; + jobject klass_; DISALLOW_IMPLICIT_CONSTRUCTORS(JitCompileTask); }; @@ -104,5 +115,31 @@ void JitInstrumentationListener::InvokeVirtualOrInterface(Thread* thread, } } +class WaitForCompilationToFinishTask FINAL : public Task { + public: + WaitForCompilationToFinishTask() : barrier_(0) {} + + void Wait(Thread* self) { + barrier_.Increment(self, 1); + } + + void Run(Thread* self ATTRIBUTE_UNUSED) OVERRIDE {} + + void Finalize() OVERRIDE { + // Do this in Finalize since Finalize is called after Run by the thread pool. + barrier_.Pass(Thread::Current()); + } + + private: + Barrier barrier_; + DISALLOW_COPY_AND_ASSIGN(WaitForCompilationToFinishTask); +}; + +void JitInstrumentationCache::WaitForCompilationToFinish(Thread* self) { + std::unique_ptr<WaitForCompilationToFinishTask> task(new WaitForCompilationToFinishTask); + thread_pool_->AddTask(self, task.get()); + task->Wait(self); +} + } // namespace jit } // namespace art diff --git a/runtime/jit/jit_instrumentation.h b/runtime/jit/jit_instrumentation.h index 6fdef6585d..9eb464b841 100644 --- a/runtime/jit/jit_instrumentation.h +++ b/runtime/jit/jit_instrumentation.h @@ -50,6 +50,8 @@ class JitInstrumentationCache { SHARED_REQUIRES(Locks::mutator_lock_); void CreateThreadPool(); void DeleteThreadPool(); + // Wait until there is no more pending compilation tasks. + void WaitForCompilationToFinish(Thread* self); private: size_t hot_method_threshold_; diff --git a/runtime/lambda/art_lambda_method.h b/runtime/lambda/art_lambda_method.h index 892d8c6f6b..ea13eb7af6 100644 --- a/runtime/lambda/art_lambda_method.h +++ b/runtime/lambda/art_lambda_method.h @@ -35,7 +35,7 @@ class ArtLambdaMethod { // (Ownership of strings is retained by the caller and the lifetime should exceed this class). ArtLambdaMethod(ArtMethod* target_method, const char* captured_variables_type_descriptor, - const char* captured_variables_shorty_, + const char* captured_variables_shorty, bool innate_lambda = true); // Get the target method for this lambda that would be used by the invoke-lambda dex instruction. diff --git a/runtime/lambda/box_table.cc b/runtime/lambda/box_table.cc index 26575fd995..8eef10bbad 100644 --- a/runtime/lambda/box_table.cc +++ b/runtime/lambda/box_table.cc @@ -18,6 +18,8 @@ #include "base/mutex.h" #include "common_throws.h" #include "gc_root-inl.h" +#include "lambda/closure.h" +#include "lambda/leaking_allocator.h" #include "mirror/method.h" #include "mirror/object-inl.h" #include "thread.h" @@ -26,11 +28,53 @@ namespace art { namespace lambda { +// Temporarily represent the lambda Closure as its raw bytes in an array. +// TODO: Generate a proxy class for the closure when boxing the first time. +using BoxedClosurePointerType = mirror::ByteArray*; + +static mirror::Class* GetBoxedClosureClass() SHARED_REQUIRES(Locks::mutator_lock_) { + return mirror::ByteArray::GetArrayClass(); +} + +namespace { + // Convenience functions to allocating/deleting box table copies of the closures. + struct ClosureAllocator { + // Deletes a Closure that was allocated through ::Allocate. + static void Delete(Closure* ptr) { + delete[] reinterpret_cast<char*>(ptr); + } + + // Returns a well-aligned pointer to a newly allocated Closure on the 'new' heap. + static Closure* Allocate(size_t size) { + DCHECK_GE(size, sizeof(Closure)); + + // TODO: Maybe point to the interior of the boxed closure object after we add proxy support? + Closure* closure = reinterpret_cast<Closure*>(new char[size]); + DCHECK_ALIGNED(closure, alignof(Closure)); + return closure; + } + }; +} // namespace BoxTable::BoxTable() : allow_new_weaks_(true), new_weaks_condition_("lambda box table allowed weaks", *Locks::lambda_table_lock_) {} +BoxTable::~BoxTable() { + // Free all the copies of our closures. + for (auto map_iterator = map_.begin(); map_iterator != map_.end(); ++map_iterator) { + std::pair<UnorderedMapKeyType, ValueType>& key_value_pair = *map_iterator; + + Closure* closure = key_value_pair.first; + + // Remove from the map first, so that it doesn't try to access dangling pointer. + map_iterator = map_.Erase(map_iterator); + + // Safe to delete, no dangling pointers. + ClosureAllocator::Delete(closure); + } +} + mirror::Object* BoxTable::BoxLambda(const ClosureType& closure) { Thread* self = Thread::Current(); @@ -58,22 +102,29 @@ mirror::Object* BoxTable::BoxLambda(const ClosureType& closure) { // Release the lambda table lock here, so that thread suspension is allowed. - // Convert the ArtMethod into a java.lang.reflect.Method which will serve + // Convert the Closure into a managed byte[] which will serve // as the temporary 'boxed' version of the lambda. This is good enough // to check all the basic object identities that a boxed lambda must retain. + // It's also good enough to contain all the captured primitive variables. // TODO: Boxing an innate lambda (i.e. made with create-lambda) should make a proxy class // TODO: Boxing a learned lambda (i.e. made with unbox-lambda) should return the original object - mirror::Method* method_as_object = - mirror::Method::CreateFromArtMethod(self, closure); + BoxedClosurePointerType closure_as_array_object = + mirror::ByteArray::Alloc(self, closure->GetSize()); + // There are no thread suspension points after this, so we don't need to put it into a handle. - if (UNLIKELY(method_as_object == nullptr)) { + if (UNLIKELY(closure_as_array_object == nullptr)) { // Most likely an OOM has occurred. CHECK(self->IsExceptionPending()); return nullptr; } + // Write the raw closure data into the byte[]. + closure->CopyTo(closure_as_array_object->GetRawData(sizeof(uint8_t), // component size + 0 /*index*/), // index + closure_as_array_object->GetLength()); + // The method has been successfully boxed into an object, now insert it into the hash map. { MutexLock mu(self, *Locks::lambda_table_lock_); @@ -87,38 +138,56 @@ mirror::Object* BoxTable::BoxLambda(const ClosureType& closure) { return value.Read(); } - // Otherwise we should insert it into the hash map in this thread. - map_.Insert(std::make_pair(closure, ValueType(method_as_object))); + // Otherwise we need to insert it into the hash map in this thread. + + // Make a copy for the box table to keep, in case the closure gets collected from the stack. + // TODO: GC may need to sweep for roots in the box table's copy of the closure. + Closure* closure_table_copy = ClosureAllocator::Allocate(closure->GetSize()); + closure->CopyTo(closure_table_copy, closure->GetSize()); + + // The closure_table_copy needs to be deleted by us manually when we erase it from the map. + + // Actually insert into the table. + map_.Insert({closure_table_copy, ValueType(closure_as_array_object)}); } - return method_as_object; + return closure_as_array_object; } bool BoxTable::UnboxLambda(mirror::Object* object, ClosureType* out_closure) { DCHECK(object != nullptr); *out_closure = nullptr; + Thread* self = Thread::Current(); + // Note that we do not need to access lambda_table_lock_ here // since we don't need to look at the map. mirror::Object* boxed_closure_object = object; - // Raise ClassCastException if object is not instanceof java.lang.reflect.Method - if (UNLIKELY(!boxed_closure_object->InstanceOf(mirror::Method::StaticClass()))) { - ThrowClassCastException(mirror::Method::StaticClass(), boxed_closure_object->GetClass()); + // Raise ClassCastException if object is not instanceof byte[] + if (UNLIKELY(!boxed_closure_object->InstanceOf(GetBoxedClosureClass()))) { + ThrowClassCastException(GetBoxedClosureClass(), boxed_closure_object->GetClass()); return false; } // TODO(iam): We must check that the closure object extends/implements the type - // specified in [type id]. This is not currently implemented since it's always a Method. + // specified in [type id]. This is not currently implemented since it's always a byte[]. // If we got this far, the inputs are valid. - // Write out the java.lang.reflect.Method's embedded ArtMethod* into the vreg target. - mirror::AbstractMethod* boxed_closure_as_method = - down_cast<mirror::AbstractMethod*>(boxed_closure_object); + // Shuffle the byte[] back into a raw closure, then allocate it, copy, and return it. + BoxedClosurePointerType boxed_closure_as_array = + down_cast<BoxedClosurePointerType>(boxed_closure_object); + + const int8_t* unaligned_interior_closure = boxed_closure_as_array->GetData(); - ArtMethod* unboxed_closure = boxed_closure_as_method->GetArtMethod(); - DCHECK(unboxed_closure != nullptr); + // Allocate a copy that can "escape" and copy the closure data into that. + Closure* unboxed_closure = + LeakingAllocator::MakeFlexibleInstance<Closure>(self, boxed_closure_as_array->GetLength()); + // TODO: don't just memcpy the closure, it's unsafe when we add references to the mix. + memcpy(unboxed_closure, unaligned_interior_closure, boxed_closure_as_array->GetLength()); + + DCHECK_EQ(unboxed_closure->GetSize(), static_cast<size_t>(boxed_closure_as_array->GetLength())); *out_closure = unboxed_closure; return true; @@ -127,7 +196,7 @@ bool BoxTable::UnboxLambda(mirror::Object* object, ClosureType* out_closure) { BoxTable::ValueType BoxTable::FindBoxedLambda(const ClosureType& closure) const { auto map_iterator = map_.Find(closure); if (map_iterator != map_.end()) { - const std::pair<ClosureType, ValueType>& key_value_pair = *map_iterator; + const std::pair<UnorderedMapKeyType, ValueType>& key_value_pair = *map_iterator; const ValueType& value = key_value_pair.second; DCHECK(!value.IsNull()); // Never store null boxes. @@ -157,7 +226,7 @@ void BoxTable::SweepWeakBoxedLambdas(IsMarkedVisitor* visitor) { */ std::vector<ClosureType> remove_list; for (auto map_iterator = map_.begin(); map_iterator != map_.end(); ) { - std::pair<ClosureType, ValueType>& key_value_pair = *map_iterator; + std::pair<UnorderedMapKeyType, ValueType>& key_value_pair = *map_iterator; const ValueType& old_value = key_value_pair.second; @@ -166,10 +235,15 @@ void BoxTable::SweepWeakBoxedLambdas(IsMarkedVisitor* visitor) { mirror::Object* new_value = visitor->IsMarked(old_value_raw); if (new_value == nullptr) { - const ClosureType& closure = key_value_pair.first; // The object has been swept away. + const ClosureType& closure = key_value_pair.first; + // Delete the entry from the map. - map_iterator = map_.Erase(map_.Find(closure)); + map_iterator = map_.Erase(map_iterator); + + // Clean up the memory by deleting the closure. + ClosureAllocator::Delete(closure); + } else { // The object has been moved. // Update the map. @@ -208,16 +282,33 @@ void BoxTable::BroadcastForNewWeakBoxedLambdas() { new_weaks_condition_.Broadcast(self); } -bool BoxTable::EqualsFn::operator()(const ClosureType& lhs, const ClosureType& rhs) const { +void BoxTable::EmptyFn::MakeEmpty(std::pair<UnorderedMapKeyType, ValueType>& item) const { + item.first = nullptr; + + Locks::mutator_lock_->AssertSharedHeld(Thread::Current()); + item.second = ValueType(); // Also clear the GC root. +} + +bool BoxTable::EmptyFn::IsEmpty(const std::pair<UnorderedMapKeyType, ValueType>& item) const { + return item.first == nullptr; +} + +bool BoxTable::EqualsFn::operator()(const UnorderedMapKeyType& lhs, + const UnorderedMapKeyType& rhs) const { // Nothing needs this right now, but leave this assertion for later when // we need to look at the references inside of the closure. - if (kIsDebugBuild) { - Locks::mutator_lock_->AssertSharedHeld(Thread::Current()); - } + Locks::mutator_lock_->AssertSharedHeld(Thread::Current()); + + return lhs->ReferenceEquals(rhs); +} + +size_t BoxTable::HashFn::operator()(const UnorderedMapKeyType& key) const { + const lambda::Closure* closure = key; + DCHECK_ALIGNED(closure, alignof(lambda::Closure)); - // TODO: Need rework to use read barriers once closures have references inside of them that can - // move. Until then, it's safe to just compare the data inside of it directly. - return lhs == rhs; + // Need to hold mutator_lock_ before calling into Closure::GetHashCode. + Locks::mutator_lock_->AssertSharedHeld(Thread::Current()); + return closure->GetHashCode(); } } // namespace lambda diff --git a/runtime/lambda/box_table.h b/runtime/lambda/box_table.h index 9ffda6658f..adb733271e 100644 --- a/runtime/lambda/box_table.h +++ b/runtime/lambda/box_table.h @@ -34,6 +34,7 @@ class Object; // forward declaration } // namespace mirror namespace lambda { +struct Closure; // forward declaration /* * Store a table of boxed lambdas. This is required to maintain object referential equality @@ -44,7 +45,7 @@ namespace lambda { */ class BoxTable FINAL { public: - using ClosureType = art::ArtMethod*; + using ClosureType = art::lambda::Closure*; // Boxes a closure into an object. Returns null and throws an exception on failure. mirror::Object* BoxLambda(const ClosureType& closure) @@ -72,10 +73,9 @@ class BoxTable FINAL { REQUIRES(!Locks::lambda_table_lock_); BoxTable(); - ~BoxTable() = default; + ~BoxTable(); private: - // FIXME: This needs to be a GcRoot. // Explanation: // - After all threads are suspended (exclusive mutator lock), // the concurrent-copying GC can move objects from the "from" space to the "to" space. @@ -97,30 +97,30 @@ class BoxTable FINAL { void BlockUntilWeaksAllowed() SHARED_REQUIRES(Locks::lambda_table_lock_); + // Wrap the Closure into a unique_ptr so that the HashMap can delete its memory automatically. + using UnorderedMapKeyType = ClosureType; + // EmptyFn implementation for art::HashMap struct EmptyFn { - void MakeEmpty(std::pair<ClosureType, ValueType>& item) const { - item.first = nullptr; - } - bool IsEmpty(const std::pair<ClosureType, ValueType>& item) const { - return item.first == nullptr; - } + void MakeEmpty(std::pair<UnorderedMapKeyType, ValueType>& item) const + NO_THREAD_SAFETY_ANALYSIS; // SHARED_REQUIRES(Locks::mutator_lock_) + + bool IsEmpty(const std::pair<UnorderedMapKeyType, ValueType>& item) const; }; // HashFn implementation for art::HashMap struct HashFn { - size_t operator()(const ClosureType& key) const { - // TODO(iam): Rewrite hash function when ClosureType is no longer an ArtMethod* - return static_cast<size_t>(reinterpret_cast<uintptr_t>(key)); - } + size_t operator()(const UnorderedMapKeyType& key) const + NO_THREAD_SAFETY_ANALYSIS; // SHARED_REQUIRES(Locks::mutator_lock_) }; // EqualsFn implementation for art::HashMap struct EqualsFn { - bool operator()(const ClosureType& lhs, const ClosureType& rhs) const; + bool operator()(const UnorderedMapKeyType& lhs, const UnorderedMapKeyType& rhs) const + NO_THREAD_SAFETY_ANALYSIS; // SHARED_REQUIRES(Locks::mutator_lock_) }; - using UnorderedMap = art::HashMap<ClosureType, + using UnorderedMap = art::HashMap<UnorderedMapKeyType, ValueType, EmptyFn, HashFn, diff --git a/runtime/lambda/closure.cc b/runtime/lambda/closure.cc index 95a17c660c..179e4ee7f2 100644 --- a/runtime/lambda/closure.cc +++ b/runtime/lambda/closure.cc @@ -124,6 +124,55 @@ void Closure::CopyTo(void* target, size_t target_size) const { memcpy(target, this, GetSize()); } +ArtMethod* Closure::GetTargetMethod() const { + return const_cast<ArtMethod*>(lambda_info_->GetArtMethod()); +} + +uint32_t Closure::GetHashCode() const { + // Start with a non-zero constant, a prime number. + uint32_t result = 17; + + // Include the hash with the ArtMethod. + { + uintptr_t method = reinterpret_cast<uintptr_t>(GetTargetMethod()); + result = 31 * result + Low32Bits(method); + if (sizeof(method) == sizeof(uint64_t)) { + result = 31 * result + High32Bits(method); + } + } + + // Include a hash for each captured variable. + for (size_t i = 0; i < GetCapturedVariablesSize(); ++i) { + // TODO: not safe for GC-able values since the address can move and the hash code would change. + uint8_t captured_variable_raw_value; + CopyUnsafeAtOffset<uint8_t>(i, /*out*/&captured_variable_raw_value); // NOLINT: [whitespace/comma] [3] + + result = 31 * result + captured_variable_raw_value; + } + + // TODO: Fix above loop to work for objects and lambdas. + static_assert(kClosureSupportsGarbageCollection == false, + "Need to update above loop to read the hash code from the " + "objects and lambdas recursively"); + + return result; +} + +bool Closure::ReferenceEquals(const Closure* other) const { + DCHECK(other != nullptr); + + // TODO: Need rework to use read barriers once closures have references inside of them that can + // move. Until then, it's safe to just compare the data inside of it directly. + static_assert(kClosureSupportsReferences == false, + "Unsafe to use memcmp in read barrier collector"); + + if (GetSize() != other->GetSize()) { + return false; + } + + return memcmp(this, other, GetSize()); +} + size_t Closure::GetNumberOfCapturedVariables() const { // TODO: refactor into art_lambda_method.h. Parsing should only be required here as a DCHECK. VariableInfo variable_info = diff --git a/runtime/lambda/closure.h b/runtime/lambda/closure.h index 60d117e9e2..31ff1944d2 100644 --- a/runtime/lambda/closure.h +++ b/runtime/lambda/closure.h @@ -49,6 +49,19 @@ struct PACKED(sizeof(ArtLambdaMethod*)) Closure { // The target_size must be at least as large as GetSize(). void CopyTo(void* target, size_t target_size) const; + // Get the target method, i.e. the method that will be dispatched into with invoke-lambda. + ArtMethod* GetTargetMethod() const; + + // Calculates the hash code. Value is recomputed each time. + uint32_t GetHashCode() const SHARED_REQUIRES(Locks::mutator_lock_); + + // Is this the same closure as other? e.g. same target method, same variables captured. + // + // Determines whether the two Closures are interchangeable instances. + // Does *not* call Object#equals recursively. If two Closures compare ReferenceEquals true that + // means that they are interchangeable values (usually for the purpose of boxing/unboxing). + bool ReferenceEquals(const Closure* other) const SHARED_REQUIRES(Locks::mutator_lock_); + // How many variables were captured? size_t GetNumberOfCapturedVariables() const; diff --git a/runtime/lambda/closure_builder-inl.h b/runtime/lambda/closure_builder-inl.h index 41a803baf2..3cec21f3ba 100644 --- a/runtime/lambda/closure_builder-inl.h +++ b/runtime/lambda/closure_builder-inl.h @@ -35,6 +35,8 @@ void ClosureBuilder::CaptureVariablePrimitive(T value) { values_.push_back(value_storage); size_ += sizeof(T); + + shorty_types_ += kShortyType; } } // namespace lambda diff --git a/runtime/lambda/closure_builder.cc b/runtime/lambda/closure_builder.cc index 9c37db8fcc..739e965238 100644 --- a/runtime/lambda/closure_builder.cc +++ b/runtime/lambda/closure_builder.cc @@ -64,6 +64,8 @@ void ClosureBuilder::CaptureVariableObject(mirror::Object* object) { UNIMPLEMENTED(FATAL) << "can't yet safely capture objects with read barrier"; } } + + shorty_types_ += ShortyFieldType::kObject; } void ClosureBuilder::CaptureVariableLambda(Closure* closure) { @@ -78,6 +80,8 @@ void ClosureBuilder::CaptureVariableLambda(Closure* closure) { // A closure may be sized dynamically, so always query it for the true size. size_ += closure->GetSize(); + + shorty_types_ += ShortyFieldType::kLambda; } size_t ClosureBuilder::GetSize() const { @@ -85,9 +89,15 @@ size_t ClosureBuilder::GetSize() const { } size_t ClosureBuilder::GetCaptureCount() const { + DCHECK_EQ(values_.size(), shorty_types_.size()); return values_.size(); } +const std::string& ClosureBuilder::GetCapturedVariableShortyTypes() const { + DCHECK_EQ(values_.size(), shorty_types_.size()); + return shorty_types_; +} + Closure* ClosureBuilder::CreateInPlace(void* memory, ArtLambdaMethod* target_method) const { DCHECK(memory != nullptr); DCHECK(target_method != nullptr); @@ -138,11 +148,14 @@ size_t ClosureBuilder::WriteValues(ArtLambdaMethod* target_method, size_t variables_size) const { size_t total_size = header_size; const char* shorty_types = target_method->GetCapturedVariablesShortyTypeDescriptor(); + DCHECK_STREQ(shorty_types, shorty_types_.c_str()); size_t variables_offset = 0; size_t remaining_size = variables_size; const size_t shorty_count = target_method->GetNumberOfCapturedVariables(); + DCHECK_EQ(shorty_count, GetCaptureCount()); + for (size_t i = 0; i < shorty_count; ++i) { ShortyFieldType shorty{shorty_types[i]}; // NOLINT [readability/braces] [4] diff --git a/runtime/lambda/closure_builder.h b/runtime/lambda/closure_builder.h index 542e12afaa..23eb484529 100644 --- a/runtime/lambda/closure_builder.h +++ b/runtime/lambda/closure_builder.h @@ -40,13 +40,12 @@ class ArtLambdaMethod; // forward declaration // // The mutator lock must be held for the duration of the lifetime of this object, // since it needs to temporarily store heap references into an internal list. -class ClosureBuilder : ValueObject { +class ClosureBuilder { public: using ShortyTypeEnum = decltype(ShortyFieldType::kByte); - // Mark this primitive value to be captured as the specified type. - template <typename T, ShortyTypeEnum kShortyType> + template <typename T, ShortyTypeEnum kShortyType = ShortyFieldTypeSelectEnum<T>::value> void CaptureVariablePrimitive(T value); // Mark this object reference to be captured. @@ -63,6 +62,9 @@ class ClosureBuilder : ValueObject { // Returns how many variables have been captured so far. size_t GetCaptureCount() const; + // Get the list of captured variables' shorty field types. + const std::string& GetCapturedVariableShortyTypes() const; + // Creates a closure in-place and writes out the data into 'memory'. // Memory must be at least 'GetSize' bytes large. // All previously marked data to be captured is now written out. @@ -93,6 +95,7 @@ class ClosureBuilder : ValueObject { size_t size_ = kInitialSize; bool is_dynamic_size_ = false; std::vector<ShortyFieldTypeTraits::MaxType> values_; + std::string shorty_types_; }; } // namespace lambda diff --git a/runtime/lambda/leaking_allocator.cc b/runtime/lambda/leaking_allocator.cc new file mode 100644 index 0000000000..4910732a6c --- /dev/null +++ b/runtime/lambda/leaking_allocator.cc @@ -0,0 +1,30 @@ +/* + * 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. + */ + +#include "lambda/leaking_allocator.h" +#include "linear_alloc.h" +#include "runtime.h" + +namespace art { +namespace lambda { + +void* LeakingAllocator::AllocateMemory(Thread* self, size_t byte_size) { + // TODO: use GetAllocatorForClassLoader to allocate lambda ArtMethod data. + return Runtime::Current()->GetLinearAlloc()->Alloc(self, byte_size); +} + +} // namespace lambda +} // namespace art diff --git a/runtime/lambda/leaking_allocator.h b/runtime/lambda/leaking_allocator.h new file mode 100644 index 0000000000..c3222d0485 --- /dev/null +++ b/runtime/lambda/leaking_allocator.h @@ -0,0 +1,55 @@ +/* + * 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. + */ +#ifndef ART_RUNTIME_LAMBDA_LEAKING_ALLOCATOR_H_ +#define ART_RUNTIME_LAMBDA_LEAKING_ALLOCATOR_H_ + +#include <utility> // std::forward + +namespace art { +class Thread; // forward declaration + +namespace lambda { + +// Temporary class to centralize all the leaking allocations. +// Allocations made through this class are never freed, but it is a placeholder +// that means that the calling code needs to be rewritten to properly: +// +// (a) Have a lifetime scoped to some other entity. +// (b) Not be allocated over and over again if it was already allocated once (immutable data). +// +// TODO: do all of the above a/b for each callsite, and delete this class. +class LeakingAllocator { + public: + // Allocate byte_size bytes worth of memory. Never freed. + static void* AllocateMemory(Thread* self, size_t byte_size); + + // Make a new instance of T, flexibly sized, in-place at newly allocated memory. Never freed. + template <typename T, typename... Args> + static T* MakeFlexibleInstance(Thread* self, size_t byte_size, Args&&... args) { + return new (AllocateMemory(self, byte_size)) T(std::forward<Args>(args)...); + } + + // Make a new instance of T in-place at newly allocated memory. Never freed. + template <typename T, typename... Args> + static T* MakeInstance(Thread* self, Args&&... args) { + return new (AllocateMemory(self, sizeof(T))) T(std::forward<Args>(args)...); + } +}; + +} // namespace lambda +} // namespace art + +#endif // ART_RUNTIME_LAMBDA_LEAKING_ALLOCATOR_H_ diff --git a/runtime/verifier/method_verifier.cc b/runtime/verifier/method_verifier.cc index 9938e907e9..eed3e22a72 100644 --- a/runtime/verifier/method_verifier.cc +++ b/runtime/verifier/method_verifier.cc @@ -1008,6 +1008,9 @@ bool MethodVerifier::VerifyInstruction(const Instruction* inst, uint32_t code_of case Instruction::kVerifyRegCWide: result = result && CheckWideRegisterIndex(inst->VRegC()); break; + case Instruction::kVerifyRegCString: + result = result && CheckStringIndex(inst->VRegC()); + break; } switch (inst->GetVerifyExtraFlags()) { case Instruction::kVerifyArrayData: @@ -1300,17 +1303,17 @@ bool MethodVerifier::CheckSwitchTargets(uint32_t cur_offset) { return false; } + bool is_packed_switch = (*insns & 0xff) == Instruction::PACKED_SWITCH; + uint32_t switch_count = switch_insns[1]; - int32_t keys_offset, targets_offset; + int32_t targets_offset; uint16_t expected_signature; - if ((*insns & 0xff) == Instruction::PACKED_SWITCH) { + if (is_packed_switch) { /* 0=sig, 1=count, 2/3=firstKey */ targets_offset = 4; - keys_offset = -1; expected_signature = Instruction::kPackedSwitchSignature; } else { /* 0=sig, 1=count, 2..count*2 = keys */ - keys_offset = 2; targets_offset = 2 + 2 * switch_count; expected_signature = Instruction::kSparseSwitchSignature; } @@ -1329,19 +1332,33 @@ bool MethodVerifier::CheckSwitchTargets(uint32_t cur_offset) { << ", count " << insn_count; return false; } - /* for a sparse switch, verify the keys are in ascending order */ - if (keys_offset > 0 && switch_count > 1) { - int32_t last_key = switch_insns[keys_offset] | (switch_insns[keys_offset + 1] << 16); - for (uint32_t targ = 1; targ < switch_count; targ++) { - int32_t key = - static_cast<int32_t>(switch_insns[keys_offset + targ * 2]) | - static_cast<int32_t>(switch_insns[keys_offset + targ * 2 + 1] << 16); - if (key <= last_key) { - Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "invalid packed switch: last key=" << last_key - << ", this=" << key; + + constexpr int32_t keys_offset = 2; + if (switch_count > 1) { + if (is_packed_switch) { + /* for a packed switch, verify that keys do not overflow int32 */ + int32_t first_key = switch_insns[keys_offset] | (switch_insns[keys_offset + 1] << 16); + int32_t max_first_key = + std::numeric_limits<int32_t>::max() - (static_cast<int32_t>(switch_count) - 1); + if (first_key > max_first_key) { + Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "invalid packed switch: first_key=" << first_key + << ", switch_count=" << switch_count; return false; } - last_key = key; + } else { + /* for a sparse switch, verify the keys are in ascending order */ + int32_t last_key = switch_insns[keys_offset] | (switch_insns[keys_offset + 1] << 16); + for (uint32_t targ = 1; targ < switch_count; targ++) { + int32_t key = + static_cast<int32_t>(switch_insns[keys_offset + targ * 2]) | + static_cast<int32_t>(switch_insns[keys_offset + targ * 2 + 1] << 16); + if (key <= last_key) { + Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "invalid sparse switch: last key=" << last_key + << ", this=" << key; + return false; + } + last_key = key; + } } } /* verify each switch target */ @@ -3149,6 +3166,13 @@ bool MethodVerifier::CodeFlowVerifyInstruction(uint32_t* start_guess) { Fail(VERIFY_ERROR_FORCE_INTERPRETER); // TODO(iam): implement invoke-lambda verification break; } + case Instruction::CAPTURE_VARIABLE: { + // Don't bother verifying, instead the interpreter will take the slow path with access checks. + // If the code would've normally hard-failed, then the interpreter will throw the + // appropriate verification errors at runtime. + Fail(VERIFY_ERROR_FORCE_INTERPRETER); // TODO(iam): implement capture-variable verification + break; + } case Instruction::CREATE_LAMBDA: { // Don't bother verifying, instead the interpreter will take the slow path with access checks. // If the code would've normally hard-failed, then the interpreter will throw the @@ -3156,10 +3180,15 @@ bool MethodVerifier::CodeFlowVerifyInstruction(uint32_t* start_guess) { Fail(VERIFY_ERROR_FORCE_INTERPRETER); // TODO(iam): implement create-lambda verification break; } + case Instruction::LIBERATE_VARIABLE: { + // Don't bother verifying, instead the interpreter will take the slow path with access checks. + // If the code would've normally hard-failed, then the interpreter will throw the + // appropriate verification errors at runtime. + Fail(VERIFY_ERROR_FORCE_INTERPRETER); // TODO(iam): implement liberate-variable verification + break; + } - case Instruction::UNUSED_F4: - case Instruction::UNUSED_F5: - case Instruction::UNUSED_F7: { + case Instruction::UNUSED_F4: { DCHECK(false); // TODO(iam): Implement opcodes for lambdas // Conservatively fail verification on release builds. Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "Unexpected opcode " << inst->DumpString(dex_file_); diff --git a/test/004-JniTest/expected.txt b/test/004-JniTest/expected.txt index 49d9cc0d5a..86ab37e1e5 100644 --- a/test/004-JniTest/expected.txt +++ b/test/004-JniTest/expected.txt @@ -1,3 +1,4 @@ +JNI_OnLoad called Super.<init> Super.<init> Subclass.<init> diff --git a/test/004-JniTest/jni_test.cc b/test/004-JniTest/jni_test.cc index db0dd32771..be7888b04a 100644 --- a/test/004-JniTest/jni_test.cc +++ b/test/004-JniTest/jni_test.cc @@ -15,8 +15,9 @@ */ #include <assert.h> -#include <stdio.h> +#include <iostream> #include <pthread.h> +#include <stdio.h> #include <vector> #include "jni.h" @@ -27,13 +28,21 @@ static JavaVM* jvm = nullptr; -extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *) { +extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void*) { assert(vm != nullptr); assert(jvm == nullptr); jvm = vm; + std::cout << "JNI_OnLoad called" << std::endl; return JNI_VERSION_1_6; } +extern "C" JNIEXPORT void JNI_OnUnload(JavaVM*, void*) { + // std::cout since LOG(INFO) adds extra stuff like pid. + std::cout << "JNI_OnUnload called" << std::endl; + // Clear jvm for assert in test 004-JniTest. + jvm = nullptr; +} + static void* AttachHelper(void* arg) { assert(jvm != nullptr); diff --git a/test/004-ReferenceMap/expected.txt b/test/004-ReferenceMap/expected.txt index e69de29bb2..6a5618ebc6 100644 --- a/test/004-ReferenceMap/expected.txt +++ b/test/004-ReferenceMap/expected.txt @@ -0,0 +1 @@ +JNI_OnLoad called diff --git a/test/004-SignalTest/expected.txt b/test/004-SignalTest/expected.txt index fd5ec00067..b3a0e1cbe0 100644 --- a/test/004-SignalTest/expected.txt +++ b/test/004-SignalTest/expected.txt @@ -1,3 +1,4 @@ +JNI_OnLoad called init signal test Caught NullPointerException Caught StackOverflowError diff --git a/test/004-StackWalk/expected.txt b/test/004-StackWalk/expected.txt index bde00246a3..5af68cd85d 100644 --- a/test/004-StackWalk/expected.txt +++ b/test/004-StackWalk/expected.txt @@ -1,3 +1,4 @@ +JNI_OnLoad called 1st call 172001234567891011121314151617181920652310201919 2nd call diff --git a/test/004-UnsafeTest/expected.txt b/test/004-UnsafeTest/expected.txt index e69de29bb2..6a5618ebc6 100644 --- a/test/004-UnsafeTest/expected.txt +++ b/test/004-UnsafeTest/expected.txt @@ -0,0 +1 @@ +JNI_OnLoad called diff --git a/test/044-proxy/expected.txt b/test/044-proxy/expected.txt index f86948ad6c..052c8faf1b 100644 --- a/test/044-proxy/expected.txt +++ b/test/044-proxy/expected.txt @@ -93,4 +93,5 @@ Invocation of public abstract java.lang.String NarrowingTest$I2.foo() Got expected exception Proxy narrowed invocation return type passed 5.8 +JNI_OnLoad called callback diff --git a/test/051-thread/expected.txt b/test/051-thread/expected.txt index 54e34af3aa..c6cd4f8bea 100644 --- a/test/051-thread/expected.txt +++ b/test/051-thread/expected.txt @@ -1,3 +1,4 @@ +JNI_OnLoad called thread test starting testThreadCapacity thread count: 512 testThreadDaemons starting thread 'TestDaemonThread' diff --git a/test/088-monitor-verification/expected.txt b/test/088-monitor-verification/expected.txt index 13b8c73970..f252f6f2ee 100644 --- a/test/088-monitor-verification/expected.txt +++ b/test/088-monitor-verification/expected.txt @@ -1,3 +1,4 @@ +JNI_OnLoad called recursiveSync ok nestedMayThrow ok constantLock ok diff --git a/test/115-native-bridge/expected.txt b/test/115-native-bridge/expected.txt index 372ecd0484..b003307ab7 100644 --- a/test/115-native-bridge/expected.txt +++ b/test/115-native-bridge/expected.txt @@ -17,6 +17,7 @@ Test ART callbacks: all JNI function number is 11. name:testSignal, signature:()I, shorty:I. name:testZeroLengthByteBuffers, signature:()V, shorty:V. trampoline_JNI_OnLoad called! +JNI_OnLoad called Getting trampoline for Java_Main_testFindClassOnAttachedNativeThread with shorty V. trampoline_Java_Main_testFindClassOnAttachedNativeThread called! Getting trampoline for Java_Main_testFindFieldOnAttachedNativeThreadNative with shorty V. diff --git a/test/116-nodex2oat/expected.txt b/test/116-nodex2oat/expected.txt index 05b1c2f387..157dfc4ea4 100644 --- a/test/116-nodex2oat/expected.txt +++ b/test/116-nodex2oat/expected.txt @@ -1,6 +1,9 @@ Run -Xnodex2oat +JNI_OnLoad called Has oat is false, is dex2oat enabled is false. Run -Xdex2oat +JNI_OnLoad called Has oat is true, is dex2oat enabled is true. Run default +JNI_OnLoad called Has oat is true, is dex2oat enabled is true. diff --git a/test/117-nopatchoat/expected.txt b/test/117-nopatchoat/expected.txt index 5cc02d1662..0cd4715d09 100644 --- a/test/117-nopatchoat/expected.txt +++ b/test/117-nopatchoat/expected.txt @@ -1,9 +1,12 @@ Run without dex2oat/patchoat +JNI_OnLoad called dex2oat & patchoat are disabled, has oat is true, has executable oat is expected. This is a function call Run with dexoat/patchoat +JNI_OnLoad called dex2oat & patchoat are enabled, has oat is true, has executable oat is expected. This is a function call Run default +JNI_OnLoad called dex2oat & patchoat are enabled, has oat is true, has executable oat is expected. This is a function call diff --git a/test/118-noimage-dex2oat/expected.txt b/test/118-noimage-dex2oat/expected.txt index 0103e899f6..166481e96a 100644 --- a/test/118-noimage-dex2oat/expected.txt +++ b/test/118-noimage-dex2oat/expected.txt @@ -1,11 +1,14 @@ Run -Xnoimage-dex2oat +JNI_OnLoad called Has image is false, is image dex2oat enabled is false, is BOOTCLASSPATH on disk is false. testB18485243 PASS Run -Xnoimage-dex2oat -Xno-dex-file-fallback Failed to initialize runtime (check log for details) Run -Ximage-dex2oat +JNI_OnLoad called Has image is true, is image dex2oat enabled is true, is BOOTCLASSPATH on disk is true. testB18485243 PASS Run default +JNI_OnLoad called Has image is true, is image dex2oat enabled is true, is BOOTCLASSPATH on disk is true. testB18485243 PASS diff --git a/test/119-noimage-patchoat/expected.txt b/test/119-noimage-patchoat/expected.txt index ed136621c3..9b9db58fcd 100644 --- a/test/119-noimage-patchoat/expected.txt +++ b/test/119-noimage-patchoat/expected.txt @@ -1,8 +1,11 @@ Run -Xnoimage-dex2oat -Xpatchoat:/system/bin/false +JNI_OnLoad called Has image is false, is image dex2oat enabled is false. Run -Xnoimage-dex2oat -Xpatchoat:/system/bin/false -Xno-dex-file-fallback Failed to initialize runtime (check log for details) Run -Ximage-dex2oat +JNI_OnLoad called Has image is true, is image dex2oat enabled is true. Run default +JNI_OnLoad called Has image is true, is image dex2oat enabled is true. diff --git a/test/137-cfi/expected.txt b/test/137-cfi/expected.txt index e69de29bb2..6a5618ebc6 100644 --- a/test/137-cfi/expected.txt +++ b/test/137-cfi/expected.txt @@ -0,0 +1 @@ +JNI_OnLoad called diff --git a/test/139-register-natives/expected.txt b/test/139-register-natives/expected.txt index e69de29bb2..6a5618ebc6 100644 --- a/test/139-register-natives/expected.txt +++ b/test/139-register-natives/expected.txt @@ -0,0 +1 @@ +JNI_OnLoad called diff --git a/test/141-class-unload/expected.txt b/test/141-class-unload/expected.txt new file mode 100644 index 0000000000..ff65a70b12 --- /dev/null +++ b/test/141-class-unload/expected.txt @@ -0,0 +1,18 @@ +1 +2 +JNI_OnLoad called +JNI_OnUnload called +1 +2 +JNI_OnLoad called +JNI_OnUnload called +null +null +JNI_OnLoad called +JNI_OnUnload called +null +loader null false +loader null false +JNI_OnLoad called +JNI_OnUnload called +null diff --git a/test/141-class-unload/info.txt b/test/141-class-unload/info.txt new file mode 100644 index 0000000000..d8dd381dc7 --- /dev/null +++ b/test/141-class-unload/info.txt @@ -0,0 +1 @@ +Test that classes get freed after they are no longer reachable. diff --git a/test/141-class-unload/jni_unload.cc b/test/141-class-unload/jni_unload.cc new file mode 100644 index 0000000000..d913efe53e --- /dev/null +++ b/test/141-class-unload/jni_unload.cc @@ -0,0 +1,37 @@ +/* + * 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. + */ + +#include "jni.h" + +#include <iostream> + +#include "jit/jit.h" +#include "jit/jit_instrumentation.h" +#include "runtime.h" +#include "thread-inl.h" + +namespace art { +namespace { + +extern "C" JNIEXPORT void JNICALL Java_IntHolder_waitForCompilation(JNIEnv*, jclass) { + jit::Jit* jit = Runtime::Current()->GetJit(); + if (jit != nullptr) { + jit->GetInstrumentationCache()->WaitForCompilationToFinish(Thread::Current()); + } +} + +} // namespace +} // namespace art diff --git a/test/141-class-unload/src-ex/IntHolder.java b/test/141-class-unload/src-ex/IntHolder.java new file mode 100644 index 0000000000..e4aa6b8949 --- /dev/null +++ b/test/141-class-unload/src-ex/IntHolder.java @@ -0,0 +1,39 @@ +/* + * 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. + */ + +// Simple class that holds a static int for testing that class unloading works +// and re-runs the class initializer. +public class IntHolder { + private static int value = 1; + + public static void setValue(int newValue) { + value = newValue; + } + + public static int getValue() { + return value; + } + + public static void runGC() { + Runtime.getRuntime().gc(); + } + + public static void loadLibrary(String name) { + System.loadLibrary(name); + } + + public static native void waitForCompilation(); +} diff --git a/test/141-class-unload/src/Main.java b/test/141-class-unload/src/Main.java new file mode 100644 index 0000000000..105a2b981c --- /dev/null +++ b/test/141-class-unload/src/Main.java @@ -0,0 +1,154 @@ +/* + * 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. + */ + +import java.lang.ref.WeakReference; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; + +public class Main { + static final String DEX_FILE = System.getenv("DEX_LOCATION") + "/141-class-unload-ex.jar"; + static String nativeLibraryName; + + public static void main(String[] args) throws Exception { + nativeLibraryName = args[0]; + Class pathClassLoader = Class.forName("dalvik.system.PathClassLoader"); + if (pathClassLoader == null) { + throw new AssertionError("Couldn't find path class loader class"); + } + Constructor constructor = + pathClassLoader.getDeclaredConstructor(String.class, ClassLoader.class); + try { + testUnloadClass(constructor); + testUnloadLoader(constructor); + // Test that we don't unload if we have a Method keeping the class live. + testNoUnloadInvoke(constructor); + // Test that we don't unload if we have an instance. + testNoUnloadInstance(constructor); + // Test JNI_OnLoad and JNI_OnUnload. + testLoadAndUnloadLibrary(constructor); + // Stress test to make sure we dont leak memory. + stressTest(constructor); + } catch (Exception e) { + System.out.println(e); + } + } + + private static void stressTest(Constructor constructor) throws Exception { + for (int i = 0; i <= 100; ++i) { + setUpUnloadLoader(constructor, false); + if (i % 10 == 0) { + Runtime.getRuntime().gc(); + } + } + } + + private static void testUnloadClass(Constructor constructor) throws Exception { + WeakReference<Class> klass = setUpUnloadClass(constructor); + // No strong refernces to class loader, should get unloaded. + Runtime.getRuntime().gc(); + WeakReference<Class> klass2 = setUpUnloadClass(constructor); + Runtime.getRuntime().gc(); + // If the weak reference is cleared, then it was unloaded. + System.out.println(klass.get()); + System.out.println(klass2.get()); + } + + private static void testUnloadLoader(Constructor constructor) + throws Exception { + WeakReference<ClassLoader> loader = setUpUnloadLoader(constructor, true); + // No strong refernces to class loader, should get unloaded. + Runtime.getRuntime().gc(); + // If the weak reference is cleared, then it was unloaded. + System.out.println(loader.get()); + } + + private static void testLoadAndUnloadLibrary(Constructor constructor) throws Exception { + WeakReference<ClassLoader> loader = setUpLoadLibrary(constructor); + // No strong refernces to class loader, should get unloaded. + Runtime.getRuntime().gc(); + // If the weak reference is cleared, then it was unloaded. + System.out.println(loader.get()); + } + + private static void testNoUnloadInvoke(Constructor constructor) throws Exception { + WeakReference<ClassLoader> loader = + new WeakReference((ClassLoader) constructor.newInstance( + DEX_FILE, ClassLoader.getSystemClassLoader())); + WeakReference<Class> intHolder = new WeakReference(loader.get().loadClass("IntHolder")); + intHolder.get().getDeclaredMethod("runGC").invoke(intHolder.get()); + boolean isNull = loader.get() == null; + System.out.println("loader null " + isNull); + } + + private static void testNoUnloadInstance(Constructor constructor) throws Exception { + WeakReference<ClassLoader> loader = + new WeakReference((ClassLoader) constructor.newInstance( + DEX_FILE, ClassLoader.getSystemClassLoader())); + WeakReference<Class> intHolder = new WeakReference(loader.get().loadClass("IntHolder")); + Object o = intHolder.get().newInstance(); + Runtime.getRuntime().gc(); + boolean isNull = loader.get() == null; + System.out.println("loader null " + isNull); + } + + private static WeakReference<Class> setUpUnloadClass(Constructor constructor) throws Exception { + ClassLoader loader = (ClassLoader) constructor.newInstance( + DEX_FILE, ClassLoader.getSystemClassLoader()); + Class intHolder = loader.loadClass("IntHolder"); + Method getValue = intHolder.getDeclaredMethod("getValue"); + Method setValue = intHolder.getDeclaredMethod("setValue", Integer.TYPE); + // Make sure we don't accidentally preserve the value in the int holder, the class + // initializer should be re-run. + System.out.println((int) getValue.invoke(intHolder)); + setValue.invoke(intHolder, 2); + System.out.println((int) getValue.invoke(intHolder)); + waitForCompilation(intHolder); + return new WeakReference(intHolder); + } + + private static WeakReference<ClassLoader> setUpUnloadLoader(Constructor constructor, + boolean waitForCompilation) + throws Exception { + ClassLoader loader = (ClassLoader) constructor.newInstance( + DEX_FILE, ClassLoader.getSystemClassLoader()); + Class intHolder = loader.loadClass("IntHolder"); + Method setValue = intHolder.getDeclaredMethod("setValue", Integer.TYPE); + setValue.invoke(intHolder, 2); + if (waitForCompilation) { + waitForCompilation(intHolder); + } + return new WeakReference(loader); + } + + private static void waitForCompilation(Class intHolder) throws Exception { + // Load the native library so that we can call waitForCompilation. + Method loadLibrary = intHolder.getDeclaredMethod("loadLibrary", String.class); + loadLibrary.invoke(intHolder, nativeLibraryName); + // Wait for JIT compilation to finish since the async threads may prevent unloading. + Method waitForCompilation = intHolder.getDeclaredMethod("waitForCompilation"); + waitForCompilation.invoke(intHolder); + } + + private static WeakReference<ClassLoader> setUpLoadLibrary(Constructor constructor) + throws Exception { + ClassLoader loader = (ClassLoader) constructor.newInstance( + DEX_FILE, ClassLoader.getSystemClassLoader()); + Class intHolder = loader.loadClass("IntHolder"); + Method loadLibrary = intHolder.getDeclaredMethod("loadLibrary", String.class); + loadLibrary.invoke(intHolder, nativeLibraryName); + return new WeakReference(loader); + } +} diff --git a/test/454-get-vreg/expected.txt b/test/454-get-vreg/expected.txt index e69de29bb2..6a5618ebc6 100644 --- a/test/454-get-vreg/expected.txt +++ b/test/454-get-vreg/expected.txt @@ -0,0 +1 @@ +JNI_OnLoad called diff --git a/test/455-set-vreg/expected.txt b/test/455-set-vreg/expected.txt index e69de29bb2..6a5618ebc6 100644 --- a/test/455-set-vreg/expected.txt +++ b/test/455-set-vreg/expected.txt @@ -0,0 +1 @@ +JNI_OnLoad called diff --git a/test/457-regs/expected.txt b/test/457-regs/expected.txt index e69de29bb2..6a5618ebc6 100644 --- a/test/457-regs/expected.txt +++ b/test/457-regs/expected.txt @@ -0,0 +1 @@ +JNI_OnLoad called diff --git a/test/461-get-reference-vreg/expected.txt b/test/461-get-reference-vreg/expected.txt index e69de29bb2..6a5618ebc6 100644 --- a/test/461-get-reference-vreg/expected.txt +++ b/test/461-get-reference-vreg/expected.txt @@ -0,0 +1 @@ +JNI_OnLoad called diff --git a/test/466-get-live-vreg/expected.txt b/test/466-get-live-vreg/expected.txt index e69de29bb2..6a5618ebc6 100644 --- a/test/466-get-live-vreg/expected.txt +++ b/test/466-get-live-vreg/expected.txt @@ -0,0 +1 @@ +JNI_OnLoad called diff --git a/test/485-checker-dce-switch/expected.txt b/test/485-checker-dce-switch/expected.txt new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/test/485-checker-dce-switch/expected.txt diff --git a/test/485-checker-dce-switch/info.txt b/test/485-checker-dce-switch/info.txt new file mode 100644 index 0000000000..6653526827 --- /dev/null +++ b/test/485-checker-dce-switch/info.txt @@ -0,0 +1 @@ +Tests that DCE can remove a packed switch. diff --git a/test/485-checker-dce-switch/src/Main.java b/test/485-checker-dce-switch/src/Main.java new file mode 100644 index 0000000000..019d876ec8 --- /dev/null +++ b/test/485-checker-dce-switch/src/Main.java @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +public class Main { + + public static int $inline$method() { + return 5; + } + + /// CHECK-START: int Main.wholeSwitchDead(int) dead_code_elimination_final (before) + /// CHECK-DAG: PackedSwitch + + /// CHECK-START: int Main.wholeSwitchDead(int) dead_code_elimination_final (after) + /// CHECK-DAG: <<Const100:i\d+>> IntConstant 100 + /// CHECK-DAG: Return [<<Const100>>] + + /// CHECK-START: int Main.wholeSwitchDead(int) dead_code_elimination_final (after) + /// CHECK-NOT: PackedSwitch + + public static int wholeSwitchDead(int j) { + int i = $inline$method(); + int l = 100; + if (i > 100) { + switch(j) { + case 1: + i++; + break; + case 2: + i = 99; + break; + case 3: + i = 100; + break; + case 4: + i = -100; + break; + case 5: + i = 7; + break; + case 6: + i = -9; + break; + } + l += i; + } + + return l; + } + + /// CHECK-START: int Main.constantSwitch_InRange() dead_code_elimination_final (before) + /// CHECK-DAG: PackedSwitch + + /// CHECK-START: int Main.constantSwitch_InRange() dead_code_elimination_final (after) + /// CHECK-DAG: <<Const7:i\d+>> IntConstant 7 + /// CHECK-DAG: Return [<<Const7>>] + + /// CHECK-START: int Main.constantSwitch_InRange() dead_code_elimination_final (after) + /// CHECK-NOT: PackedSwitch + + public static int constantSwitch_InRange() { + int i = $inline$method(); + switch(i) { + case 1: + i++; + break; + case 2: + i = 99; + break; + case 3: + i = 100; + break; + case 4: + i = -100; + break; + case 5: + i = 7; + break; + case 6: + i = -9; + break; + } + + return i; + } + + /// CHECK-START: int Main.constantSwitch_AboveRange() dead_code_elimination_final (before) + /// CHECK-DAG: PackedSwitch + + /// CHECK-START: int Main.constantSwitch_AboveRange() dead_code_elimination_final (after) + /// CHECK-DAG: <<Const15:i\d+>> IntConstant 15 + /// CHECK-DAG: Return [<<Const15>>] + + /// CHECK-START: int Main.constantSwitch_AboveRange() dead_code_elimination_final (after) + /// CHECK-NOT: PackedSwitch + + public static int constantSwitch_AboveRange() { + int i = $inline$method() + 10; + switch(i) { + case 1: + i++; + break; + case 2: + i = 99; + break; + case 3: + i = 100; + break; + case 4: + i = -100; + break; + case 5: + i = 7; + break; + case 6: + i = -9; + break; + } + + return i; + } + + /// CHECK-START: int Main.constantSwitch_BelowRange() dead_code_elimination_final (before) + /// CHECK-DAG: PackedSwitch + + /// CHECK-START: int Main.constantSwitch_BelowRange() dead_code_elimination_final (after) + /// CHECK-DAG: <<ConstM5:i\d+>> IntConstant -5 + /// CHECK-DAG: Return [<<ConstM5>>] + + /// CHECK-START: int Main.constantSwitch_BelowRange() dead_code_elimination_final (after) + /// CHECK-NOT: PackedSwitch + + public static int constantSwitch_BelowRange() { + int i = $inline$method() - 10; + switch(i) { + case 1: + i++; + break; + case 2: + i = 99; + break; + case 3: + i = 100; + break; + case 4: + i = -100; + break; + case 5: + i = 7; + break; + case 6: + i = -9; + break; + } + + return i; + } + + public static void main(String[] args) throws Exception { + int ret_val = wholeSwitchDead(10); + if (ret_val != 100) { + throw new Error("Incorrect return value from wholeSwitchDead:" + ret_val); + } + + ret_val = constantSwitch_InRange(); + if (ret_val != 7) { + throw new Error("Incorrect return value from constantSwitch_InRange:" + ret_val); + } + + ret_val = constantSwitch_AboveRange(); + if (ret_val != 15) { + throw new Error("Incorrect return value from constantSwitch_AboveRange:" + ret_val); + } + + ret_val = constantSwitch_BelowRange(); + if (ret_val != -5) { + throw new Error("Incorrect return value from constantSwitch_BelowRange:" + ret_val); + } + } +} diff --git a/test/497-inlining-and-class-loader/expected.txt b/test/497-inlining-and-class-loader/expected.txt index f5b9fe07de..905dbfd2cb 100644 --- a/test/497-inlining-and-class-loader/expected.txt +++ b/test/497-inlining-and-class-loader/expected.txt @@ -1,3 +1,4 @@ +JNI_OnLoad called java.lang.Exception at Main.$noinline$bar(Main.java:124) at Level2.$inline$bar(Level1.java:25) diff --git a/test/510-checker-try-catch/smali/Builder.smali b/test/510-checker-try-catch/smali/Builder.smali index 2274ba4d43..1fde5edc23 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,<<Minus2>>] ## CHECK: name "<<BCatch3>>" -## CHECK: predecessors "<<BEnterTry1>>" "<<BExitTry1>>" "<<BEnterTry2>>" "<<BExitTry2>>" +## CHECK: predecessors "<<BEnterTry1>>" "<<BEnterTry2>>" "<<BExitTry1>>" "<<BExitTry2>>" ## CHECK: successors "<<BReturn>>" ## CHECK: flags "catch_block" ## CHECK: StoreLocal [v0,<<Minus3>>] @@ -70,18 +70,18 @@ ## CHECK: xhandlers "<<BCatch1>>" "<<BCatch3>>" ## CHECK: TryBoundary kind:entry -## CHECK: name "<<BExitTry1>>" -## CHECK: predecessors "<<BTry1>>" -## CHECK: successors "<<BAdd>>" -## CHECK: xhandlers "<<BCatch1>>" "<<BCatch3>>" -## CHECK: TryBoundary kind:exit - ## CHECK: name "<<BEnterTry2>>" ## CHECK: predecessors "<<BAdd>>" ## CHECK: successors "<<BTry2>>" ## CHECK: xhandlers "<<BCatch2>>" "<<BCatch3>>" ## CHECK: TryBoundary kind:entry +## CHECK: name "<<BExitTry1>>" +## CHECK: predecessors "<<BTry1>>" +## CHECK: successors "<<BAdd>>" +## CHECK: xhandlers "<<BCatch1>>" "<<BCatch3>>" +## CHECK: TryBoundary kind:exit + ## CHECK: name "<<BExitTry2>>" ## CHECK: predecessors "<<BTry2>>" ## CHECK: successors "<<BReturn>>" @@ -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 "<<BTry1:B\d+>>" ## CHECK: predecessors "<<BEnterTry1>>" -## CHECK: successors "<<BTry2:B\d+>>" +## CHECK: successors "<<BExitTry1:B\d+>>" ## CHECK: Div -## CHECK: name "<<BTry2>>" -## CHECK: predecessors "<<BEnterTry2>>" "<<BTry1>>" -## CHECK: successors "<<BExitTry:B\d+>>" +## CHECK: name "<<BTry2:B\d+>>" +## CHECK: predecessors "<<BEnterTry2>>" +## CHECK: successors "<<BExitTry2:B\d+>>" ## CHECK: Div ## CHECK: name "<<BReturn:B\d+>>" -## CHECK: predecessors "<<BExitTry>>" "<<BCatch:B\d+>>" +## CHECK: predecessors "<<BExitTry2>>" "<<BCatch:B\d+>>" ## CHECK: Return ## CHECK: name "<<BCatch>>" -## CHECK: predecessors "<<BEnterTry1>>" "<<BEnterTry2>>" "<<BExitTry>>" +## CHECK: predecessors "<<BEnterTry1>>" "<<BEnterTry2>>" "<<BExitTry1>>" "<<BExitTry2>>" ## CHECK: successors "<<BReturn>>" ## CHECK: flags "catch_block" ## CHECK: StoreLocal [v0,<<Minus1>>] @@ -167,12 +166,18 @@ ## CHECK: TryBoundary kind:entry ## CHECK: name "<<BEnterTry2>>" -## CHECK: predecessors "<<BIf>>" +## CHECK: predecessors "<<BIf>>" "<<BExitTry1>>" ## CHECK: successors "<<BTry2>>" ## CHECK: xhandlers "<<BCatch>>" ## CHECK: TryBoundary kind:entry -## CHECK: name "<<BExitTry>>" +## CHECK: name "<<BExitTry1>>" +## CHECK: predecessors "<<BTry1>>" +## CHECK: successors "<<BEnterTry2>>" +## CHECK: xhandlers "<<BCatch>>" +## CHECK: TryBoundary kind:exit + +## CHECK: name "<<BExitTry2>>" ## CHECK: predecessors "<<BTry2>>" ## CHECK: successors "<<BReturn>>" ## CHECK: xhandlers "<<BCatch>>" @@ -314,18 +319,18 @@ ## CHECK: xhandlers "<<BCatch1>>" ## CHECK: TryBoundary kind:entry -## CHECK: name "<<BExit1>>" -## CHECK: predecessors "<<BTry1>>" -## CHECK: successors "<<BEnter2>>" -## CHECK: xhandlers "<<BCatch1>>" -## CHECK: TryBoundary kind:exit - ## CHECK: name "<<BEnter2>>" ## CHECK: predecessors "<<BExit1>>" ## CHECK: successors "<<BTry2>>" ## CHECK: xhandlers "<<BCatch2>>" ## CHECK: TryBoundary kind:entry +## CHECK: name "<<BExit1>>" +## CHECK: predecessors "<<BTry1>>" +## CHECK: successors "<<BEnter2>>" +## CHECK: xhandlers "<<BCatch1>>" +## CHECK: TryBoundary kind:exit + ## CHECK: name "<<BExit2>>" ## CHECK: predecessors "<<BTry2>>" ## CHECK: successors "<<BReturn>>" @@ -402,18 +407,18 @@ ## CHECK: xhandlers "<<BCatch1>>" ## CHECK: TryBoundary kind:entry -## CHECK: name "<<BExit1>>" -## CHECK: predecessors "<<BTry1>>" -## CHECK: successors "<<BReturn>>" -## CHECK: xhandlers "<<BCatch1>>" -## CHECK: TryBoundary kind:exit - ## CHECK: name "<<BEnter2>>" ## CHECK: predecessors "<<BGoto>>" ## CHECK: successors "<<BTry2>>" ## CHECK: xhandlers "<<BCatch2>>" ## CHECK: TryBoundary kind:entry +## CHECK: name "<<BExit1>>" +## CHECK: predecessors "<<BTry1>>" +## CHECK: successors "<<BReturn>>" +## CHECK: xhandlers "<<BCatch1>>" +## CHECK: TryBoundary kind:exit + ## CHECK: name "<<BExit2>>" ## CHECK: predecessors "<<BTry2>>" ## CHECK: successors "<<BEnter1>>" @@ -483,7 +488,7 @@ ## CHECK: StoreLocal [v0,<<Minus1>>] ## CHECK: name "<<BCatchAll>>" -## CHECK: predecessors "<<BEnter1>>" "<<BExit1>>" "<<BEnter2>>" "<<BExit2>>" "<<BEnter3>>" "<<BExit3>>" +## CHECK: predecessors "<<BEnter1>>" "<<BEnter2>>" "<<BEnter3>>" "<<BExit1>>" "<<BExit2>>" "<<BExit3>>" ## CHECK: successors "<<BReturn>>" ## CHECK: flags "catch_block" ## CHECK: StoreLocal [v0,<<Minus2>>] @@ -494,30 +499,30 @@ ## CHECK: xhandlers "<<BCatchAll>>" ## CHECK: TryBoundary kind:entry -## CHECK: name "<<BExit1>>" -## CHECK: predecessors "<<BTry1>>" -## CHECK: successors "<<BEnter2>>" -## CHECK: xhandlers "<<BCatchAll>>" -## CHECK: TryBoundary kind:exit - ## CHECK: name "<<BEnter2>>" ## CHECK: predecessors "<<BExit1>>" ## CHECK: successors "<<BTry2>>" ## CHECK: xhandlers "<<BCatchArith>>" "<<BCatchAll>>" ## CHECK: TryBoundary kind:entry -## CHECK: name "<<BExit2>>" -## CHECK: predecessors "<<BTry2>>" -## CHECK: successors "<<BEnter3>>" -## CHECK: xhandlers "<<BCatchArith>>" "<<BCatchAll>>" -## CHECK: TryBoundary kind:exit - ## CHECK: name "<<BEnter3>>" ## CHECK: predecessors "<<BExit2>>" ## CHECK: successors "<<BTry3>>" ## CHECK: xhandlers "<<BCatchAll>>" ## CHECK: TryBoundary kind:entry +## CHECK: name "<<BExit1>>" +## CHECK: predecessors "<<BTry1>>" +## CHECK: successors "<<BEnter2>>" +## CHECK: xhandlers "<<BCatchAll>>" +## CHECK: TryBoundary kind:exit + +## CHECK: name "<<BExit2>>" +## CHECK: predecessors "<<BTry2>>" +## CHECK: successors "<<BEnter3>>" +## CHECK: xhandlers "<<BCatchArith>>" "<<BCatchAll>>" +## CHECK: TryBoundary kind:exit + ## CHECK: name "<<BExit3>>" ## CHECK: predecessors "<<BTry3>>" ## CHECK: successors "<<BReturn>>" @@ -577,7 +582,7 @@ ## CHECK: Div ## CHECK: name "<<BCatch>>" -## CHECK: predecessors "<<BEnterTry1>>" "<<BExitTry1>>" "<<BEnterTry2>>" "<<BExitTry2>>" +## CHECK: predecessors "<<BEnterTry1>>" "<<BEnterTry2>>" "<<BExitTry1>>" "<<BExitTry2>>" ## CHECK: successors "<<BReturn>>" ## CHECK: flags "catch_block" ## CHECK: StoreLocal [v0,<<Minus1>>] @@ -588,18 +593,18 @@ ## CHECK: xhandlers "<<BCatch>>" ## CHECK: TryBoundary kind:entry -## CHECK: name "<<BExitTry1>>" -## CHECK: predecessors "<<BTry1>>" -## CHECK: successors "<<BOutside>>" -## CHECK: xhandlers "<<BCatch>>" -## CHECK: TryBoundary kind:exit - ## CHECK: name "<<BEnterTry2>>" ## CHECK: predecessors "<<BOutside>>" ## CHECK: successors "<<BTry2>>" ## CHECK: xhandlers "<<BCatch>>" ## CHECK: TryBoundary kind:entry +## CHECK: name "<<BExitTry1>>" +## CHECK: predecessors "<<BTry1>>" +## CHECK: successors "<<BOutside>>" +## CHECK: xhandlers "<<BCatch>>" +## CHECK: TryBoundary kind:exit + ## CHECK: name "<<BExitTry2>>" ## CHECK: predecessors "<<BTry2>>" ## CHECK: successors "<<BReturn>>" @@ -647,21 +652,21 @@ ## CHECK: name "<<BTry1:B\d+>>" ## CHECK: predecessors "<<BEnterTry1>>" -## CHECK: successors "<<BTry2:B\d+>>" +## CHECK: successors "<<BExitTry1:B\d+>>" ## CHECK: Div -## CHECK: name "<<BTry2>>" -## CHECK: predecessors "<<BEnterTry2>>" "<<BTry1>>" -## CHECK: successors "<<BExitTry:B\d+>>" +## CHECK: name "<<BTry2:B\d+>>" +## CHECK: predecessors "<<BEnterTry2>>" +## CHECK: successors "<<BExitTry2:B\d+>>" ## CHECK: Div ## CHECK: name "<<BOutside>>" -## CHECK: predecessors "<<BPSwitch1>>" "<<BExitTry>>" +## CHECK: predecessors "<<BPSwitch1>>" "<<BExitTry2>>" ## CHECK: successors "<<BCatchReturn:B\d+>>" ## CHECK: Div ## CHECK: name "<<BCatchReturn>>" -## CHECK: predecessors "<<BOutside>>" "<<BEnterTry1>>" "<<BEnterTry2>>" "<<BExitTry>>" +## CHECK: predecessors "<<BOutside>>" "<<BEnterTry1>>" "<<BEnterTry2>>" "<<BExitTry1>>" "<<BExitTry2>>" ## CHECK: flags "catch_block" ## CHECK: Return @@ -677,7 +682,13 @@ ## CHECK: xhandlers "<<BCatchReturn>>" ## CHECK: TryBoundary kind:entry -## CHECK: name "<<BExitTry>>" +## CHECK: name "<<BExitTry1>>" +## CHECK: predecessors "<<BTry1>>" +## CHECK: successors "<<BEnterTry2>>" +## CHECK: xhandlers "<<BCatchReturn>>" +## CHECK: TryBoundary kind:exit + +## CHECK: name "<<BExitTry2>>" ## CHECK: predecessors "<<BTry2>>" ## CHECK: successors "<<BOutside>>" ## CHECK: xhandlers "<<BCatchReturn>>" @@ -741,7 +752,7 @@ ## CHECK: Div ## CHECK: name "<<BCatchReturn>>" -## CHECK: predecessors "<<BOutside>>" "<<BEnterTry1>>" "<<BExitTry1>>" "<<BEnterTry2>>" "<<BExitTry2>>" +## CHECK: predecessors "<<BOutside>>" "<<BEnterTry1>>" "<<BEnterTry2>>" "<<BExitTry1>>" "<<BExitTry2>>" ## CHECK: flags "catch_block" ## CHECK: Return @@ -751,18 +762,18 @@ ## CHECK: xhandlers "<<BCatchReturn>>" ## CHECK: TryBoundary kind:entry -## CHECK: name "<<BExitTry1>>" -## CHECK: predecessors "<<BPSwitch0>>" -## CHECK: successors "<<BPSwitch1>>" -## CHECK: xhandlers "<<BCatchReturn>>" -## CHECK: TryBoundary kind:exit - ## CHECK: name "<<BEnterTry2>>" ## CHECK: predecessors "<<BPSwitch1>>" ## CHECK: successors "<<BTry1>>" ## CHECK: xhandlers "<<BCatchReturn>>" ## CHECK: TryBoundary kind:entry +## CHECK: name "<<BExitTry1>>" +## CHECK: predecessors "<<BPSwitch0>>" +## CHECK: successors "<<BPSwitch1>>" +## CHECK: xhandlers "<<BCatchReturn>>" +## CHECK: TryBoundary kind:exit + ## CHECK: name "<<BExitTry2>>" ## CHECK: predecessors "<<BTry2>>" ## CHECK: successors "<<BOutside>>" @@ -907,7 +918,7 @@ ## CHECK: Div ## CHECK: name "<<BCatch:B\d+>>" -## CHECK: predecessors "<<BExitTry1>>" "<<BEnterTry1>>" "<<BExitTry1>>" "<<BEnterTry2:B\d+>>" "<<BExitTry2:B\d+>>" +## CHECK: predecessors "<<BExitTry1>>" "<<BEnterTry1>>" "<<BEnterTry2:B\d+>>" "<<BExitTry1>>" "<<BExitTry2:B\d+>>" ## CHECK: successors "<<BEnterTry2>>" ## CHECK: flags "catch_block" @@ -928,18 +939,18 @@ ## CHECK: xhandlers "<<BCatch>>" ## CHECK: TryBoundary kind:entry -## CHECK: name "<<BExitTry1>>" -## CHECK: predecessors "<<BTry1>>" -## CHECK: successors "<<BCatch>>" -## CHECK: xhandlers "<<BCatch>>" -## CHECK: TryBoundary kind:exit - ## CHECK: name "<<BEnterTry2>>" ## CHECK: predecessors "<<BCatch>>" ## CHECK: successors "<<BTry2>>" ## CHECK: xhandlers "<<BCatch>>" ## CHECK: TryBoundary kind:entry +## CHECK: name "<<BExitTry1>>" +## CHECK: predecessors "<<BTry1>>" +## CHECK: successors "<<BCatch>>" +## CHECK: xhandlers "<<BCatch>>" +## CHECK: TryBoundary kind:exit + ## CHECK: name "<<BExitTry2>>" ## CHECK: predecessors "<<BTry2>>" ## CHECK: successors "<<BReturn>>" @@ -1001,18 +1012,18 @@ ## CHECK: xhandlers "<<BCatch2>>" ## CHECK: TryBoundary kind:entry -## CHECK: name "<<BExitTry1>>" -## CHECK: predecessors "<<BTry1>>" -## CHECK: successors "<<BCatch2>>" -## CHECK: xhandlers "<<BCatch2>>" -## CHECK: TryBoundary kind:exit - ## CHECK: name "<<BEnterTry2>>" ## CHECK: predecessors "<<BCatch2>>" ## CHECK: successors "<<BTry2>>" ## CHECK: xhandlers "<<BCatch1>>" ## CHECK: TryBoundary kind:entry +## CHECK: name "<<BExitTry1>>" +## CHECK: predecessors "<<BTry1>>" +## CHECK: successors "<<BCatch2>>" +## CHECK: xhandlers "<<BCatch2>>" +## CHECK: TryBoundary kind:exit + ## CHECK: name "<<BExitTry2>>" ## CHECK: predecessors "<<BTry2>>" ## CHECK: successors "<<BReturn>>" @@ -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 "<<BEnterTry:B\d+>>" + +## CHECK: name "<<BTry:B\d+>>" +## CHECK: predecessors "<<BEnterTry>>" +## CHECK: successors "<<BExitTry:B\d+>>" +## CHECK: Div + +## CHECK: name "<<BCatch:B\d+>>" +## CHECK: predecessors "<<BEnterTry>>" "<<BExitTry>>" +## CHECK: successors "<<BEnterTry>>" +## CHECK: flags "catch_block" + +## CHECK: name "<<BExit:B\d+>>" +## CHECK-NOT: predecessors "{{B\d+}}" +## CHECK: end_block + +## CHECK: name "<<BEnterTry>>" +## CHECK: predecessors "B0" +## CHECK: successors "<<BTry>>" +## CHECK: xhandlers "<<BCatch>>" +## CHECK: TryBoundary kind:entry + +## CHECK: name "<<BExitTry>>" +## CHECK: predecessors "<<BTry>>" +## CHECK: successors "<<BEnterTry>>" +## CHECK: xhandlers "<<BCatch>>" +## 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. diff --git a/test/800-smali/expected.txt b/test/800-smali/expected.txt index 6568eac29f..17c1f00c41 100644 --- a/test/800-smali/expected.txt +++ b/test/800-smali/expected.txt @@ -1,4 +1,6 @@ PackedSwitch +PackedSwitch key INT_MAX +PackedSwitch key overflow b/17790197 FloatBadArgReg negLong diff --git a/test/800-smali/smali/PackedSwitch.smali b/test/800-smali/smali/PackedSwitch.smali index 6a3e5f00ba..95659fb16f 100644 --- a/test/800-smali/smali/PackedSwitch.smali +++ b/test/800-smali/smali/PackedSwitch.smali @@ -24,3 +24,29 @@ goto :return .end method + +.method public static packedSwitch_INT_MAX(I)I + .registers 2 + + const/4 v0, 0 + packed-switch v0, :switch_data + goto :default + + :switch_data + .packed-switch 0x7FFFFFFE + :case1 # key = INT_MAX - 1 + :case2 # key = INT_MAX + .end packed-switch + + :return + return v1 + + :default + goto :return + + :case1 + goto :return + :case2 + goto :return + +.end method diff --git a/test/800-smali/smali/b_24399945.smali b/test/800-smali/smali/b_24399945.smali new file mode 100644 index 0000000000..68f59d0387 --- /dev/null +++ b/test/800-smali/smali/b_24399945.smali @@ -0,0 +1,32 @@ +.class public Lb_24399945; + +.super Ljava/lang/Object; + +.method public static packedSwitch_overflow(I)I + .registers 2 + + const/4 v0, 0 + packed-switch v0, :switch_data + goto :default + + :switch_data + .packed-switch 0x7FFFFFFE + :case1 # key = INT_MAX - 1 + :case2 # key = INT_MAX + :case3 # key = INT_MIN (overflow!) + .end packed-switch + + :return + return v1 + + :default + goto :return + + :case1 + goto :return + :case2 + goto :return + :case3 + goto :return + +.end method diff --git a/test/800-smali/src/Main.java b/test/800-smali/src/Main.java index ba4990a76e..f75747d5c5 100644 --- a/test/800-smali/src/Main.java +++ b/test/800-smali/src/Main.java @@ -51,6 +51,10 @@ public class Main { testCases = new LinkedList<TestCase>(); testCases.add(new TestCase("PackedSwitch", "PackedSwitch", "packedSwitch", new Object[]{123}, null, 123)); + testCases.add(new TestCase("PackedSwitch key INT_MAX", "PackedSwitch", + "packedSwitch_INT_MAX", new Object[]{123}, null, 123)); + testCases.add(new TestCase("PackedSwitch key overflow", "b_24399945", + "packedSwitch_overflow", new Object[]{123}, new VerifyError(), null)); testCases.add(new TestCase("b/17790197", "B17790197", "getInt", null, null, 100)); testCases.add(new TestCase("FloatBadArgReg", "FloatBadArgReg", "getInt", diff --git a/test/955-lambda-smali/expected.txt b/test/955-lambda-smali/expected.txt index 36370998f4..16381e4b46 100644 --- a/test/955-lambda-smali/expected.txt +++ b/test/955-lambda-smali/expected.txt @@ -16,3 +16,13 @@ Caught NPE (MoveResult) testF success (MoveResult) testD success (MoveResult) testL success +(CaptureVariables) (0-args, 1 captured variable 'Z'): value is true +(CaptureVariables) (0-args, 1 captured variable 'B'): value is R +(CaptureVariables) (0-args, 1 captured variable 'C'): value is ∂ +(CaptureVariables) (0-args, 1 captured variable 'S'): value is 1000 +(CaptureVariables) (0-args, 1 captured variable 'I'): value is 12345678 +(CaptureVariables) (0-args, 1 captured variable 'J'): value is 3287471278325742 +(CaptureVariables) (0-args, 1 captured variable 'F'): value is Infinity +(CaptureVariables) (0-args, 1 captured variable 'D'): value is -Infinity +(CaptureVariables) (0-args, 8 captured variable 'ZBCSIJFD'): value is true,R,∂,1000,12345678,3287471278325742,Infinity,-Infinity +(CaptureVariables) Caught NPE diff --git a/test/955-lambda-smali/smali/BoxUnbox.smali b/test/955-lambda-smali/smali/BoxUnbox.smali index 108b5fafbc..915de2d55d 100644 --- a/test/955-lambda-smali/smali/BoxUnbox.smali +++ b/test/955-lambda-smali/smali/BoxUnbox.smali @@ -1,4 +1,3 @@ -# # Copyright (C) 2015 The Android Open Source Project # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -36,8 +35,8 @@ .end method #TODO: should use a closure type instead of ArtMethod. -.method public static doHelloWorld(Ljava/lang/reflect/ArtMethod;)V - .registers 3 # 1 parameters, 2 locals +.method public static doHelloWorld(J)V + .registers 4 # 1 wide parameters, 2 locals const-string v0, "(BoxUnbox) Hello boxing world! (0-args, no closure)" @@ -51,9 +50,9 @@ .method private static testBox()V .registers 3 - create-lambda v0, LBoxUnbox;->doHelloWorld(Ljava/lang/reflect/ArtMethod;)V + create-lambda v0, LBoxUnbox;->doHelloWorld(J)V box-lambda v2, v0 # v2 = box(v0) - unbox-lambda v0, v2, Ljava/lang/reflect/ArtMethod; # v0 = unbox(v2) + unbox-lambda v0, v2, J # v0 = unbox(v2) invoke-lambda v0, {} return-void @@ -63,7 +62,7 @@ .method private static testBoxEquality()V .registers 6 # 0 parameters, 6 locals - create-lambda v0, LBoxUnbox;->doHelloWorld(Ljava/lang/reflect/ArtMethod;)V + create-lambda v0, LBoxUnbox;->doHelloWorld(J)V box-lambda v2, v0 # v2 = box(v0) box-lambda v3, v0 # v3 = box(v0) @@ -95,7 +94,7 @@ const v0, 0 # v0 = null const v1, 0 # v1 = null :start - unbox-lambda v2, v0, Ljava/lang/reflect/ArtMethod; + unbox-lambda v2, v0, J # attempting to unbox a null lambda will throw NPE :end return-void @@ -140,7 +139,7 @@ const-string v0, "This is not a boxed lambda" :start # TODO: use \FunctionalType; here instead - unbox-lambda v2, v0, Ljava/lang/reflect/ArtMethod; + unbox-lambda v2, v0, J # can't use a string, expects a lambda object here. throws ClassCastException. :end return-void diff --git a/test/955-lambda-smali/smali/CaptureVariables.smali b/test/955-lambda-smali/smali/CaptureVariables.smali new file mode 100644 index 0000000000..f18b7ff741 --- /dev/null +++ b/test/955-lambda-smali/smali/CaptureVariables.smali @@ -0,0 +1,311 @@ +# +# 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 LCaptureVariables; +.super Ljava/lang/Object; + +.method public constructor <init>()V +.registers 1 + invoke-direct {p0}, Ljava/lang/Object;-><init>()V + return-void +.end method + +.method public static run()V +.registers 8 + # Test boolean capture + const v2, 1 # v2 = true + capture-variable v2, "Z" + create-lambda v0, LCaptureVariables;->printCapturedVariable_Z(J)V + # TODO: create-lambda should not write to both v0 and v1 + invoke-lambda v0, {} + + # Test byte capture + const v2, 82 # v2 = 82, 'R' + capture-variable v2, "B" + create-lambda v0, LCaptureVariables;->printCapturedVariable_B(J)V + # TODO: create-lambda should not write to both v0 and v1 + invoke-lambda v0, {} + + # Test char capture + const v2, 0x2202 # v2 = 0x2202, '∂' + capture-variable v2, "C" + create-lambda v0, LCaptureVariables;->printCapturedVariable_C(J)V + # TODO: create-lambda should not write to both v0 and v1 + invoke-lambda v0, {} + + # Test short capture + const v2, 1000 # v2 = 1000 + capture-variable v2, "S" + create-lambda v0, LCaptureVariables;->printCapturedVariable_S(J)V + # TODO: create-lambda should not write to both v0 and v1 + invoke-lambda v0, {} + + # Test int capture + const v2, 12345678 + capture-variable v2, "I" + create-lambda v0, LCaptureVariables;->printCapturedVariable_I(J)V + # TODO: create-lambda should not write to both v0 and v1 + invoke-lambda v0, {} + + # Test long capture + const-wide v2, 0x0badf00dc0ffeeL # v2 = 3287471278325742 + capture-variable v2, "J" + create-lambda v0, LCaptureVariables;->printCapturedVariable_J(J)V + # TODO: create-lambda should not write to both v0 and v1 + invoke-lambda v0, {} + + # Test float capture + const v2, infinityf + capture-variable v2, "F" + create-lambda v0, LCaptureVariables;->printCapturedVariable_F(J)V + # TODO: create-lambda should not write to both v0 and v1 + invoke-lambda v0, {} + + # Test double capture + const-wide v2, -infinity + capture-variable v2, "D" + create-lambda v0, LCaptureVariables;->printCapturedVariable_D(J)V + # TODO: create-lambda should not write to both v0 and v1 + invoke-lambda v0, {} + + #TODO: capture objects and lambdas once we have support for it + + # Test capturing multiple variables + invoke-static {}, LCaptureVariables;->testMultipleCaptures()V + + # Test failures + invoke-static {}, LCaptureVariables;->testFailures()V + + return-void +.end method + +#TODO: should use a closure type instead of a long +.method public static printCapturedVariable_Z(J)V + .registers 5 # 1 wide parameter, 3 locals + + const-string v0, "(CaptureVariables) (0-args, 1 captured variable 'Z'): value is " + + sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream; + invoke-virtual {v1, v0}, Ljava/io/PrintStream;->print(Ljava/lang/String;)V + + liberate-variable v2, p0, "Z" + invoke-virtual {v1, v2}, Ljava/io/PrintStream;->println(Z)V + + return-void +.end method + +#TODO: should use a closure type instead of a long +.method public static printCapturedVariable_B(J)V + .registers 5 # 1 wide parameter, 3 locals + + const-string v0, "(CaptureVariables) (0-args, 1 captured variable 'B'): value is " + + sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream; + invoke-virtual {v1, v0}, Ljava/io/PrintStream;->print(Ljava/lang/String;)V + + liberate-variable v2, p0, "B" + invoke-virtual {v1, v2}, Ljava/io/PrintStream;->println(C)V # no println(B), use char instead. + + return-void +.end method + +#TODO: should use a closure type instead of a long +.method public static printCapturedVariable_C(J)V + .registers 5 # 1 wide parameter, 3 locals + + const-string v0, "(CaptureVariables) (0-args, 1 captured variable 'C'): value is " + + sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream; + invoke-virtual {v1, v0}, Ljava/io/PrintStream;->print(Ljava/lang/String;)V + + liberate-variable v2, p0, "C" + invoke-virtual {v1, v2}, Ljava/io/PrintStream;->println(C)V + + return-void +.end method + +#TODO: should use a closure type instead of a long +.method public static printCapturedVariable_S(J)V + .registers 5 # 1 wide parameter, 3 locals + + const-string v0, "(CaptureVariables) (0-args, 1 captured variable 'S'): value is " + + sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream; + invoke-virtual {v1, v0}, Ljava/io/PrintStream;->print(Ljava/lang/String;)V + + liberate-variable v2, p0, "S" + invoke-virtual {v1, v2}, Ljava/io/PrintStream;->println(I)V # no println(S), use int instead + + return-void +.end method + +#TODO: should use a closure type instead of a long +.method public static printCapturedVariable_I(J)V + .registers 5 # 1 wide parameter, 3 locals + + const-string v0, "(CaptureVariables) (0-args, 1 captured variable 'I'): value is " + + sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream; + invoke-virtual {v1, v0}, Ljava/io/PrintStream;->print(Ljava/lang/String;)V + + liberate-variable v2, p0, "I" + invoke-virtual {v1, v2}, Ljava/io/PrintStream;->println(I)V + + return-void +.end method + +#TODO: should use a closure type instead of a long +.method public static printCapturedVariable_J(J)V + .registers 6 # 1 wide parameter, 4 locals + + const-string v0, "(CaptureVariables) (0-args, 1 captured variable 'J'): value is " + + sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream; + invoke-virtual {v1, v0}, Ljava/io/PrintStream;->print(Ljava/lang/String;)V + + liberate-variable v2, p0, "J" + invoke-virtual {v1, v2, v3}, Ljava/io/PrintStream;->println(J)V + + return-void +.end method + +#TODO: should use a closure type instead of a long +.method public static printCapturedVariable_F(J)V + .registers 5 # 1 parameter, 4 locals + + const-string v0, "(CaptureVariables) (0-args, 1 captured variable 'F'): value is " + + sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream; + invoke-virtual {v1, v0}, Ljava/io/PrintStream;->print(Ljava/lang/String;)V + + liberate-variable v2, p0, "F" + invoke-virtual {v1, v2}, Ljava/io/PrintStream;->println(F)V + + return-void +.end method + +#TODO: should use a closure type instead of a long +.method public static printCapturedVariable_D(J)V + .registers 6 # 1 wide parameter, 4 locals + + const-string v0, "(CaptureVariables) (0-args, 1 captured variable 'D'): value is " + + sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream; + invoke-virtual {v1, v0}, Ljava/io/PrintStream;->print(Ljava/lang/String;)V + + liberate-variable v2, p0, "D" + invoke-virtual {v1, v2, v3}, Ljava/io/PrintStream;->println(D)V + + return-void +.end method + +# Test capturing more than one variable. +.method private static testMultipleCaptures()V + .registers 4 # 0 parameters, 4 locals + + const v2, 1 # v2 = true + capture-variable v2, "Z" + + const v2, 82 # v2 = 82, 'R' + capture-variable v2, "B" + + const v2, 0x2202 # v2 = 0x2202, '∂' + capture-variable v2, "C" + + const v2, 1000 # v2 = 1000 + capture-variable v2, "S" + + const v2, 12345678 + capture-variable v2, "I" + + const-wide v2, 0x0badf00dc0ffeeL # v2 = 3287471278325742 + capture-variable v2, "J" + + const v2, infinityf + capture-variable v2, "F" + + const-wide v2, -infinity + capture-variable v2, "D" + + create-lambda v0, LCaptureVariables;->printCapturedVariable_ZBCSIJFD(J)V + # TODO: create-lambda should not write to both v0 and v1 + invoke-lambda v0, {} + +.end method + +#TODO: should use a closure type instead of a long +.method public static printCapturedVariable_ZBCSIJFD(J)V + .registers 7 # 1 wide parameter, 5 locals + + const-string v0, "(CaptureVariables) (0-args, 8 captured variable 'ZBCSIJFD'): value is " + const-string v4, "," + + sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream; + invoke-virtual {v1, v0}, Ljava/io/PrintStream;->print(Ljava/lang/String;)V + + liberate-variable v2, p0, "Z" + invoke-virtual {v1, v2}, Ljava/io/PrintStream;->print(Z)V + invoke-virtual {v1, v4}, Ljava/io/PrintStream;->print(Ljava/lang/String;)V + + liberate-variable v2, p0, "B" + invoke-virtual {v1, v2}, Ljava/io/PrintStream;->print(C)V + invoke-virtual {v1, v4}, Ljava/io/PrintStream;->print(Ljava/lang/String;)V + + liberate-variable v2, p0, "C" + invoke-virtual {v1, v2}, Ljava/io/PrintStream;->print(C)V + invoke-virtual {v1, v4}, Ljava/io/PrintStream;->print(Ljava/lang/String;)V + + liberate-variable v2, p0, "S" + invoke-virtual {v1, v2}, Ljava/io/PrintStream;->print(I)V + invoke-virtual {v1, v4}, Ljava/io/PrintStream;->print(Ljava/lang/String;)V + + liberate-variable v2, p0, "I" + invoke-virtual {v1, v2}, Ljava/io/PrintStream;->print(I)V + invoke-virtual {v1, v4}, Ljava/io/PrintStream;->print(Ljava/lang/String;)V + + liberate-variable v2, p0, "J" + invoke-virtual {v1, v2, v3}, Ljava/io/PrintStream;->print(J)V + invoke-virtual {v1, v4}, Ljava/io/PrintStream;->print(Ljava/lang/String;)V + + liberate-variable v2, p0, "F" + invoke-virtual {v1, v2}, Ljava/io/PrintStream;->print(F)V + invoke-virtual {v1, v4}, Ljava/io/PrintStream;->print(Ljava/lang/String;)V + + liberate-variable v2, p0, "D" + invoke-virtual {v1, v2, v3}, Ljava/io/PrintStream;->println(D)V + + return-void +.end method + +# Test exceptions are thrown as expected when used opcodes incorrectly +.method private static testFailures()V + .registers 4 # 0 parameters, 4 locals + + const v0, 0 # v0 = null + const v1, 0 # v1 = null +:start + liberate-variable v0, v2, "Z" # invoking a null lambda shall raise an NPE +:end + return-void + +:handler + const-string v2, "(CaptureVariables) Caught NPE" + sget-object v3, Ljava/lang/System;->out:Ljava/io/PrintStream; + invoke-virtual {v3, v2}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V + + return-void + + .catch Ljava/lang/NullPointerException; {:start .. :end} :handler +.end method diff --git a/test/955-lambda-smali/smali/Main.smali b/test/955-lambda-smali/smali/Main.smali index 5d2aabb386..9892d6124e 100644 --- a/test/955-lambda-smali/smali/Main.smali +++ b/test/955-lambda-smali/smali/Main.smali @@ -24,6 +24,7 @@ invoke-static {}, LTrivialHelloWorld;->run()V invoke-static {}, LBoxUnbox;->run()V invoke-static {}, LMoveResult;->run()V + invoke-static {}, LCaptureVariables;->run()V # TODO: add tests when verification fails diff --git a/test/955-lambda-smali/smali/MoveResult.smali b/test/955-lambda-smali/smali/MoveResult.smali index 1725da3044..52f7ba363b 100644 --- a/test/955-lambda-smali/smali/MoveResult.smali +++ b/test/955-lambda-smali/smali/MoveResult.smali @@ -41,7 +41,7 @@ .method public static testZ()V .registers 6 - create-lambda v0, LMoveResult;->lambdaZ(Ljava/lang/reflect/ArtMethod;)Z + create-lambda v0, LMoveResult;->lambdaZ(J)Z invoke-lambda v0, {} move-result v2 const v3, 1 @@ -61,7 +61,7 @@ .end method # Lambda target for testZ. Always returns "true". -.method public static lambdaZ(Ljava/lang/reflect/ArtMethod;)Z +.method public static lambdaZ(J)Z .registers 3 const v0, 1 @@ -73,7 +73,7 @@ .method public static testB()V .registers 6 - create-lambda v0, LMoveResult;->lambdaB(Ljava/lang/reflect/ArtMethod;)B + create-lambda v0, LMoveResult;->lambdaB(J)B invoke-lambda v0, {} move-result v2 const v3, 15 @@ -93,7 +93,7 @@ .end method # Lambda target for testB. Always returns "15". -.method public static lambdaB(Ljava/lang/reflect/ArtMethod;)B +.method public static lambdaB(J)B .registers 3 # 1 parameters, 2 locals const v0, 15 @@ -105,7 +105,7 @@ .method public static testS()V .registers 6 - create-lambda v0, LMoveResult;->lambdaS(Ljava/lang/reflect/ArtMethod;)S + create-lambda v0, LMoveResult;->lambdaS(J)S invoke-lambda v0, {} move-result v2 const/16 v3, 31000 @@ -125,7 +125,7 @@ .end method # Lambda target for testS. Always returns "31000". -.method public static lambdaS(Ljava/lang/reflect/ArtMethod;)S +.method public static lambdaS(J)S .registers 3 const/16 v0, 31000 @@ -137,7 +137,7 @@ .method public static testI()V .registers 6 - create-lambda v0, LMoveResult;->lambdaI(Ljava/lang/reflect/ArtMethod;)I + create-lambda v0, LMoveResult;->lambdaI(J)I invoke-lambda v0, {} move-result v2 const v3, 128000 @@ -157,7 +157,7 @@ .end method # Lambda target for testI. Always returns "128000". -.method public static lambdaI(Ljava/lang/reflect/ArtMethod;)I +.method public static lambdaI(J)I .registers 3 const v0, 128000 @@ -167,9 +167,9 @@ # Test that chars are returned correctly via move-result. .method public static testC()V - .registers 6 + .registers 7 - create-lambda v0, LMoveResult;->lambdaC(Ljava/lang/reflect/ArtMethod;)C + create-lambda v0, LMoveResult;->lambdaC(J)C invoke-lambda v0, {} move-result v2 const v3, 65535 @@ -189,7 +189,7 @@ .end method # Lambda target for testC. Always returns "65535". -.method public static lambdaC(Ljava/lang/reflect/ArtMethod;)C +.method public static lambdaC(J)C .registers 3 const v0, 65535 @@ -199,12 +199,12 @@ # Test that longs are returned correctly via move-result. .method public static testJ()V - .registers 8 + .registers 9 - create-lambda v0, LMoveResult;->lambdaJ(Ljava/lang/reflect/ArtMethod;)J + create-lambda v0, LMoveResult;->lambdaJ(J)J invoke-lambda v0, {} move-result v2 - const-wide v4, 0xdeadf00dc0ffee + const-wide v4, 0xdeadf00dc0ffeeL if-ne v4, v2, :is_not_equal const-string v6, "(MoveResult) testJ success" @@ -220,11 +220,11 @@ .end method -# Lambda target for testC. Always returns "0xdeadf00dc0ffee". -.method public static lambdaJ(Ljava/lang/reflect/ArtMethod;)J - .registers 4 +# Lambda target for testC. Always returns "0xdeadf00dc0ffeeL". +.method public static lambdaJ(J)J + .registers 5 - const-wide v0, 0xdeadf00dc0ffee + const-wide v0, 0xdeadf00dc0ffeeL return-wide v0 .end method @@ -233,7 +233,7 @@ .method public static testF()V .registers 6 - create-lambda v0, LMoveResult;->lambdaF(Ljava/lang/reflect/ArtMethod;)F + create-lambda v0, LMoveResult;->lambdaF(J)F invoke-lambda v0, {} move-result v2 const v3, infinityf @@ -253,8 +253,8 @@ .end method # Lambda target for testF. Always returns "infinityf". -.method public static lambdaF(Ljava/lang/reflect/ArtMethod;)F - .registers 3 +.method public static lambdaF(J)F + .registers 4 const v0, infinityf return v0 @@ -265,10 +265,10 @@ .method public static testD()V .registers 8 - create-lambda v0, LMoveResult;->lambdaD(Ljava/lang/reflect/ArtMethod;)D + create-lambda v0, LMoveResult;->lambdaD(J)D invoke-lambda v0, {} move-result-wide v2 - const-wide v4, infinity + const-wide v4, -infinity if-ne v4, v2, :is_not_equal const-string v6, "(MoveResult) testD success" @@ -285,10 +285,10 @@ .end method # Lambda target for testD. Always returns "infinity". -.method public static lambdaD(Ljava/lang/reflect/ArtMethod;)D - .registers 4 +.method public static lambdaD(J)D + .registers 5 - const-wide v0, infinity # 123.456789 + const-wide v0, -infinity return-wide v0 .end method @@ -298,7 +298,7 @@ .method public static testL()V .registers 8 - create-lambda v0, LMoveResult;->lambdaL(Ljava/lang/reflect/ArtMethod;)Ljava/lang/String; + create-lambda v0, LMoveResult;->lambdaL(J)Ljava/lang/String; invoke-lambda v0, {} move-result-object v2 const-string v4, "Interned string" @@ -319,8 +319,8 @@ .end method # Lambda target for testL. Always returns "Interned string" (string). -.method public static lambdaL(Ljava/lang/reflect/ArtMethod;)Ljava/lang/String; - .registers 4 +.method public static lambdaL(J)Ljava/lang/String; + .registers 5 const-string v0, "Interned string" return-object v0 diff --git a/test/955-lambda-smali/smali/TrivialHelloWorld.smali b/test/955-lambda-smali/smali/TrivialHelloWorld.smali index 38ee95ac7e..3444b13a65 100644 --- a/test/955-lambda-smali/smali/TrivialHelloWorld.smali +++ b/test/955-lambda-smali/smali/TrivialHelloWorld.smali @@ -25,12 +25,12 @@ .method public static run()V .registers 8 # Trivial 0-arg hello world - create-lambda v0, LTrivialHelloWorld;->doHelloWorld(Ljava/lang/reflect/ArtMethod;)V + create-lambda v0, LTrivialHelloWorld;->doHelloWorld(J)V # TODO: create-lambda should not write to both v0 and v1 invoke-lambda v0, {} # Slightly more interesting 4-arg hello world - create-lambda v2, doHelloWorldArgs(Ljava/lang/reflect/ArtMethod;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + create-lambda v2, doHelloWorldArgs(JLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V # TODO: create-lambda should not write to both v2 and v3 const-string v4, "A" const-string v5, "B" @@ -43,9 +43,9 @@ return-void .end method -#TODO: should use a closure type instead of ArtMethod. -.method public static doHelloWorld(Ljava/lang/reflect/ArtMethod;)V - .registers 3 # 1 parameters, 2 locals +#TODO: should use a closure type instead of jlong. +.method public static doHelloWorld(J)V + .registers 5 # 1 wide parameters, 3 locals const-string v0, "Hello world! (0-args, no closure)" @@ -55,17 +55,17 @@ return-void .end method -#TODO: should use a closure type instead of ArtMethod. -.method public static doHelloWorldArgs(Ljava/lang/reflect/ArtMethod;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V - .registers 7 # 5 parameters, 2 locals +#TODO: should use a closure type instead of jlong. +.method public static doHelloWorldArgs(JLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + .registers 9 # 1 wide parameter, 4 narrow parameters, 3 locals const-string v0, " Hello world! (4-args, no closure)" sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream; - invoke-virtual {v1, p1}, Ljava/io/PrintStream;->print(Ljava/lang/String;)V invoke-virtual {v1, p2}, Ljava/io/PrintStream;->print(Ljava/lang/String;)V invoke-virtual {v1, p3}, Ljava/io/PrintStream;->print(Ljava/lang/String;)V invoke-virtual {v1, p4}, Ljava/io/PrintStream;->print(Ljava/lang/String;)V + invoke-virtual {v1, p5}, Ljava/io/PrintStream;->print(Ljava/lang/String;)V invoke-virtual {v1, v0}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V diff --git a/test/Android.libarttest.mk b/test/Android.libarttest.mk index 7f05a043d8..e43ea90ba6 100644 --- a/test/Android.libarttest.mk +++ b/test/Android.libarttest.mk @@ -33,6 +33,7 @@ LIBARTTEST_COMMON_SRC_FILES := \ 1337-gc-coverage/gc_coverage.cc \ 137-cfi/cfi.cc \ 139-register-natives/regnative.cc \ + 141-class-unload/jni_unload.cc \ 454-get-vreg/get_vreg_jni.cc \ 455-set-vreg/set_vreg_jni.cc \ 457-regs/regs_jni.cc \ diff --git a/test/Android.run-test.mk b/test/Android.run-test.mk index 29e015f534..4397ea4b52 100644 --- a/test/Android.run-test.mk +++ b/test/Android.run-test.mk @@ -370,6 +370,7 @@ TEST_ART_BROKEN_FALLBACK_RUN_TESTS := # when already tracing, and writes an error message that we do not want to check for. TEST_ART_BROKEN_TRACING_RUN_TESTS := \ 137-cfi \ + 141-class-unload \ 802-deoptimization ifneq (,$(filter trace stream,$(TRACE_TYPES))) diff --git a/tools/buildbot-build.sh b/tools/buildbot-build.sh index a670fc7738..972e827667 100755 --- a/tools/buildbot-build.sh +++ b/tools/buildbot-build.sh @@ -68,20 +68,14 @@ if [[ $mode == "host" ]]; then echo "Executing $make_command" $make_command elif [[ $mode == "target" ]]; then - # We need to provide our own linker in case the linker on the device - # is out of date. - env="TARGET_GLOBAL_LDFLAGS=-Wl,-dynamic-linker=$android_root/bin/$linker" - # gcc gives a linker error, so compile with clang. - # TODO: investigate and fix? - if [[ $TARGET_PRODUCT == "mips32r2_fp" ]]; then - env="$env USE_CLANG_PLATFORM_BUILD=true" - fi - # Disable NINJA for building on target, it does not support the -e option to Makefile. + # Disable NINJA for building on target, it does not support setting environment variables + # within the make command. env="$env USE_NINJA=false" - # Use '-e' to force the override of TARGET_GLOBAL_LDFLAGS. - # Also, we build extra tools that will be used by tests, so that + # Build extra tools that will be used by tests, so that # they are compiled with our own linker. - make_command="make -e $j_arg $showcommands build-art-target-tests $common_targets libjavacrypto libjavacoretests linker toybox toolbox sh out/host/linux-x86/bin/adb" + # We need to provide our own linker in case the linker on the device + # is out of date. + make_command="make TARGET_LINKER=$android_root/bin/$linker $j_arg $showcommands build-art-target-tests $common_targets libjavacrypto libjavacoretests linker toybox toolbox sh out/host/linux-x86/bin/adb" echo "Executing env $env $make_command" env $env $make_command fi |