Merge "ART: Use unique_ptr for alloca-ed ShadowFrames"
diff --git a/compiler/Android.mk b/compiler/Android.mk
index 41e9744..96e13ac 100644
--- a/compiler/Android.mk
+++ b/compiler/Android.mk
@@ -158,6 +158,7 @@
$(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 b5ecf9c..1cd742a 100644
--- a/compiler/dex/quick/quick_compiler.cc
+++ b/compiler/dex/quick/quick_compiler.cc
@@ -391,9 +391,9 @@
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 9d70124..7ae405a 100644
--- a/compiler/optimizing/builder.cc
+++ b/compiler/optimizing/builder.cc
@@ -140,11 +140,11 @@
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 @@
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 @@
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::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);
+// 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;
+}
- // Link the TryBoundary to the handlers of `try_item`.
- for (CatchHandlerIterator it(code_item, try_item); it.HasNext(); it.Next()) {
+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 @@
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());
- // 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;
- }
- }
+ // 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];
- 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 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);
+ }
}
+
+ try_block_info.Put(throwing_block->GetBlockId(),
+ DexFile::GetTryItems(code_item, try_item_idx));
}
- 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;
+ // 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;
+ }
}
+ }
- // Catch blocks were split earlier and cannot throw.
- DCHECK(!try_block->IsCatchBlock());
+ // 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);
- // 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.
+ // 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 @@
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 @@
// 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 @@
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 @@
} 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 @@
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 7f87df6..4c8e3d0 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 @@
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 @@
// 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 @@
// 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 @@
// 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 8fdd56e..25ef388 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 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 LocationsBuilderMIPS64::VisitInvokeVirtual(HInvokeVirtual* invoke) {
- // TODO intrinsic function
+ IntrinsicLocationsBuilderMIPS64 intrinsic(codegen_);
+ if (intrinsic.TryDispatch(invoke)) {
+ return;
+ }
+
HandleInvoke(invoke);
}
@@ -2404,7 +2409,11 @@
// 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 @@
}
}
-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::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 345ff72..007d0e3 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);
+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());
- 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);
+ 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 @@
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 1ee8648..5050e15 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 @@
* 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 @@
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 @@
: 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 @@
// 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 @@
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 @@
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 @@
}
}
- 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 b71fdb8..9564622 100644
--- a/compiler/optimizing/intrinsics.cc
+++ b/compiler/optimizing/intrinsics.cc
@@ -90,7 +90,7 @@
}
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 0000000..52e2cbe
--- /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 0000000..1481d24
--- /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 d14dfc1..ebdf7a2 100644
--- a/compiler/optimizing/locations.cc
+++ b/compiler/optimizing/locations.cc
@@ -23,18 +23,15 @@
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 2162ab9..2eeba18 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 @@
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 @@
}
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 @@
}
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 @@
}
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 0128589..ef89932 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::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::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 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 @@
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 @@
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 @@
}
}
+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 52f6e23..26df241 100644
--- a/compiler/optimizing/nodes.h
+++ b/compiler/optimizing/nodes.h
@@ -370,13 +370,7 @@
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 @@
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 @@
// 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 @@
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 @@
// 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 @@
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 @@
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 af93438..c98f43e 100644
--- a/compiler/optimizing/primitive_type_propagation.cc
+++ b/compiler/optimizing/primitive_type_propagation.cc
@@ -108,8 +108,9 @@
}
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::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 6d370ed..212fcfc 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 @@
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 @@
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 a88c543..fe837e4 100644
--- a/compiler/optimizing/reference_type_propagation.cc
+++ b/compiler/optimizing/reference_type_propagation.cc
@@ -27,7 +27,7 @@
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 @@
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 @@
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 @@
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 @@
}
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::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 62f6ab8..5493601 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 @@
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 9594e3b..c43e58f 100644
--- a/compiler/optimizing/register_allocator.cc
+++ b/compiler/optimizing/register_allocator.cc
@@ -43,21 +43,21 @@
: 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
// 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::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 @@
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 @@
}
// 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 @@
// 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 @@
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 @@
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 @@
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 @@
// 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 @@
// 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 @@
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 @@
// 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 @@
// 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 @@
} 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 @@
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 @@
// 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 @@
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 @@
}
// 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 e030464..58600b7 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 @@
}
// 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 @@
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 @@
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 @@
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 @@
// 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 @@
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 b72df86..2bb5a8b 100644
--- a/compiler/optimizing/register_allocator_test.cc
+++ b/compiler/optimizing/register_allocator_test.cc
@@ -64,83 +64,83 @@
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 @@
// 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 @@
// 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 @@
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 @@
// 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 @@
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 @@
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 1956781..338a3aa 100644
--- a/compiler/optimizing/side_effects_analysis.cc
+++ b/compiler/optimizing/side_effects_analysis.cc
@@ -21,8 +21,8 @@
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 @@
}
}
- 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 @@
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 9888140..bac6088 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 @@
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 @@
// 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 0ef86d8..fb11d76 100644
--- a/compiler/optimizing/ssa_builder.cc
+++ b/compiler/optimizing/ssa_builder.cc
@@ -145,8 +145,14 @@
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 @@
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 @@
// 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 @@
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 1e9a813..b869d57 100644
--- a/compiler/optimizing/ssa_liveness_analysis.cc
+++ b/compiler/optimizing/ssa_liveness_analysis.cc
@@ -43,11 +43,11 @@
&& 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 @@
break;
}
}
- worklist->InsertAt(insert_at, block);
+ worklist->insert(insert_pos.base(), block);
}
void SsaLivenessAnalysis::LinearizeGraph() {
@@ -69,15 +69,15 @@
// 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 @@
// 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 @@
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 @@
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 @@
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::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 @@
// 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 @@
// 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 3aedaa5..414cc7d 100644
--- a/compiler/optimizing/ssa_liveness_analysis.h
+++ b/compiler/optimizing/ssa_liveness_analysis.h
@@ -206,7 +206,7 @@
* 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 @@
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 @@
}
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 @@
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 a9f04cd..72f9ddd 100644
--- a/compiler/optimizing/ssa_phi_elimination.cc
+++ b/compiler/optimizing/ssa_phi_elimination.cc
@@ -35,7 +35,7 @@
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 @@
}
// 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 @@
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 @@
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 67351f2..b48e820 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 @@
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 @@
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 @@
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 303e0d5..48f0328 100644
--- a/compiler/utils/array_ref.h
+++ b/compiler/utils/array_ref.h
@@ -161,6 +161,15 @@
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 a208ccf..ec2529e 100755
--- a/dexdump/Android.mk
+++ b/dexdump/Android.mk
@@ -34,8 +34,6 @@
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 d9b210d..4230cb2 100644
--- a/dexdump/dexdump_test.cc
+++ b/dexdump/dexdump_test.cc
@@ -43,12 +43,7 @@
// 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 995a1d5..059c4cd 100644
--- a/runtime/Android.mk
+++ b/runtime/Android.mk
@@ -104,6 +104,7 @@
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 6637c37..cc6dc7e 100644
--- a/runtime/arch/mips64/context_mips64.cc
+++ b/runtime/arch/mips64/context_mips64.cc
@@ -29,10 +29,10 @@
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 e4a144f..26fbcfe 100644
--- a/runtime/arch/mips64/context_mips64.h
+++ b/runtime/arch/mips64/context_mips64.h
@@ -41,7 +41,7 @@
}
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 @@
// 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 1b50b2e..ce1b2f3 100644
--- a/runtime/arch/mips64/quick_entrypoints_mips64.S
+++ b/runtime/arch/mips64/quick_entrypoints_mips64.S
@@ -431,7 +431,7 @@
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 38bc8f2..cd94d5e 100644
--- a/runtime/arch/mips64/registers_mips64.h
+++ b/runtime/arch/mips64/registers_mips64.h
@@ -52,6 +52,7 @@
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 4e51f55..c1a1088 100644
--- a/runtime/base/arena_allocator.cc
+++ b/runtime/base/arena_allocator.cc
@@ -55,6 +55,7 @@
"RegAlloc ",
"Data ",
"STL ",
+ "GraphBuilder ",
"Graph ",
"BasicBlock ",
"BlockList ",
@@ -74,12 +75,20 @@
"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 c5eb741..be96862 100644
--- a/runtime/base/arena_allocator.h
+++ b/runtime/base/arena_allocator.h
@@ -65,6 +65,7 @@
kArenaAllocRegAlloc,
kArenaAllocData,
kArenaAllocSTL,
+ kArenaAllocGraphBuilder,
kArenaAllocGraph,
kArenaAllocBasicBlock,
kArenaAllocBlockList,
@@ -84,12 +85,20 @@
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 6b9c8aa..8e42040 100644
--- a/runtime/class_linker.cc
+++ b/runtime/class_linker.cc
@@ -128,7 +128,11 @@
// 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 @@
// 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 @@
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 7344d13..e160a10 100644
--- a/runtime/dex_instruction-inl.h
+++ b/runtime/dex_instruction-inl.h
@@ -454,8 +454,8 @@
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 @@
*/
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 fc4df14..5250b0d 100644
--- a/runtime/dex_instruction.cc
+++ b/runtime/dex_instruction.cc
@@ -322,10 +322,10 @@
}
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 @@
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 df2d379..48a12e5 100644
--- a/runtime/dex_instruction.h
+++ b/runtime/dex_instruction.h
@@ -180,9 +180,11 @@
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 @@
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 @@
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 a176772..9d7e0c4 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 cfe7713..7d664fa 100644
--- a/runtime/gc/heap.cc
+++ b/runtime/gc/heap.cc
@@ -1963,6 +1963,10 @@
GrowForUtilization(semi_space_collector_);
LogGC(kGcCauseHomogeneousSpaceCompact, collector);
FinishGC(self, collector::kGcTypeFull);
+ {
+ ScopedObjectAccess soa(self);
+ soa.Vm()->UnloadNativeLibraries();
+ }
return HomogeneousSpaceCompactResult::kSuccess;
}
@@ -2104,6 +2108,10 @@
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 @@
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 7cfbb30..ad34c9a 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 @@
}
// 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 @@
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 @@
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 @@
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 @@
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 @@
// 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 7398778..f57bddb 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 @@
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 @@
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 @@
// 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 @@
// 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 @@
*
* - 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 @@
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 @@
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 @@
#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(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 72e2ba0..9677d79 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 @@
}
}
+ 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 @@
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 @@
}
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 @@
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 b5cc11e..083dfb5 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 @@
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 @@
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::UNUSED_F4:
- case Instruction::UNUSED_F5:
- case Instruction::UNUSED_F7: {
+ 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: {
if (!IsExperimentalInstructionEnabled(inst)) {
UnexpectedOpcode(inst, shadow_frame);
}
diff --git a/runtime/java_vm_ext.cc b/runtime/java_vm_ext.cc
index 531e039..ab70d02 100644
--- a/runtime/java_vm_ext.cc
+++ b/runtime/java_vm_ext.cc
@@ -60,7 +60,7 @@
: 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 @@
~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 @@
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 @@
// 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 @@
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 @@
}
}
- 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 @@
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,10 +253,45 @@
return nullptr;
}
- private:
- AllocationTrackingSafeMap<std::string, SharedLibrary*, kAllocatorTagJNILibraries> libraries_;
-};
+ // 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_
+ GUARDED_BY(Locks::jni_libraries_lock_);
+};
class JII {
public:
@@ -641,6 +680,10 @@
}
}
+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 @@
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 b539bbd..c1fbdc0 100644
--- a/runtime/java_vm_ext.h
+++ b/runtime/java_vm_ext.h
@@ -88,6 +88,11 @@
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 @@
// 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 643bc23..e73ba82 100644
--- a/runtime/jit/jit.h
+++ b/runtime/jit/jit.h
@@ -67,6 +67,9 @@
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 d437dd5..4f4a97f 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);
+ }
- virtual void Run(Thread* self) OVERRIDE {
+ ~JitCompileTask() {
+ ScopedObjectAccess soa(Thread::Current());
+ soa.Vm()->DeleteGlobalRef(soa.Self(), klass_);
+ }
+
+ 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 @@
}
}
- virtual void Finalize() OVERRIDE {
+ void Finalize() OVERRIDE {
delete this;
}
private:
ArtMethod* const method_;
+ jobject klass_;
DISALLOW_IMPLICIT_CONSTRUCTORS(JitCompileTask);
};
@@ -104,5 +115,31 @@
}
}
+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 6fdef65..9eb464b 100644
--- a/runtime/jit/jit_instrumentation.h
+++ b/runtime/jit/jit_instrumentation.h
@@ -50,6 +50,8 @@
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 892d8c6..ea13eb7 100644
--- a/runtime/lambda/art_lambda_method.h
+++ b/runtime/lambda/art_lambda_method.h
@@ -35,7 +35,7 @@
// (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 26575fd..8eef10b 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 @@
// 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 @@
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);
- ArtMethod* unboxed_closure = boxed_closure_as_method->GetArtMethod();
- DCHECK(unboxed_closure != nullptr);
+ const int8_t* unaligned_interior_closure = boxed_closure_as_array->GetData();
+
+ // 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 @@
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 @@
*/
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 @@
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 @@
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());
- // 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;
+ return lhs->ReferenceEquals(rhs);
+}
+
+size_t BoxTable::HashFn::operator()(const UnorderedMapKeyType& key) const {
+ const lambda::Closure* closure = key;
+ DCHECK_ALIGNED(closure, alignof(lambda::Closure));
+
+ // 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 9ffda66..adb7332 100644
--- a/runtime/lambda/box_table.h
+++ b/runtime/lambda/box_table.h
@@ -34,6 +34,7 @@
} // 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 @@
*/
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 @@
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 @@
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 95a17c6..179e4ee 100644
--- a/runtime/lambda/closure.cc
+++ b/runtime/lambda/closure.cc
@@ -124,6 +124,55 @@
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 60d117e..31ff194 100644
--- a/runtime/lambda/closure.h
+++ b/runtime/lambda/closure.h
@@ -49,6 +49,19 @@
// 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 41a803b..3cec21f 100644
--- a/runtime/lambda/closure_builder-inl.h
+++ b/runtime/lambda/closure_builder-inl.h
@@ -35,6 +35,8 @@
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 9c37db8..739e965 100644
--- a/runtime/lambda/closure_builder.cc
+++ b/runtime/lambda/closure_builder.cc
@@ -64,6 +64,8 @@
UNIMPLEMENTED(FATAL) << "can't yet safely capture objects with read barrier";
}
}
+
+ shorty_types_ += ShortyFieldType::kObject;
}
void ClosureBuilder::CaptureVariableLambda(Closure* closure) {
@@ -78,6 +80,8 @@
// 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::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 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 542e12a..23eb484 100644
--- a/runtime/lambda/closure_builder.h
+++ b/runtime/lambda/closure_builder.h
@@ -40,13 +40,12 @@
//
// 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 @@
// 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 @@
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 0000000..4910732
--- /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 0000000..c3222d0
--- /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 9938e90..eed3e22 100644
--- a/runtime/verifier/method_verifier.cc
+++ b/runtime/verifier/method_verifier.cc
@@ -1008,6 +1008,9 @@
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 @@
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 @@
<< ", 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 @@
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 @@
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 49d9cc0..86ab37e 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 db0dd32..be7888b 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 e69de29..6a5618e 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 fd5ec00..b3a0e1c 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 bde0024..5af68cd 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 e69de29..6a5618e 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 f86948a..052c8fa 100644
--- a/test/044-proxy/expected.txt
+++ b/test/044-proxy/expected.txt
@@ -93,4 +93,5 @@
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 54e34af..c6cd4f8 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 13b8c73..f252f6f 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 372ecd0..b003307 100644
--- a/test/115-native-bridge/expected.txt
+++ b/test/115-native-bridge/expected.txt
@@ -17,6 +17,7 @@
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 05b1c2f..157dfc4 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 5cc02d1..0cd4715 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 0103e89..166481e 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 ed13662..9b9db58 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 e69de29..6a5618e 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 e69de29..6a5618e 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 0000000..ff65a70
--- /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 0000000..d8dd381
--- /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 0000000..d913efe
--- /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 0000000..e4aa6b8
--- /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 0000000..105a2b9
--- /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 e69de29..6a5618e 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 e69de29..6a5618e 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 e69de29..6a5618e 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 e69de29..6a5618e 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 e69de29..6a5618e 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 0000000..e69de29
--- /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 0000000..6653526
--- /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 0000000..019d876
--- /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 f5b9fe0..905dbfd 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 2274ba4..1fde5ed 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 6568eac..17c1f00 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 6a3e5f0..95659fb 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 0000000..68f59d0
--- /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 ba4990a..f75747d 100644
--- a/test/800-smali/src/Main.java
+++ b/test/800-smali/src/Main.java
@@ -51,6 +51,10 @@
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 3637099..16381e4 100644
--- a/test/955-lambda-smali/expected.txt
+++ b/test/955-lambda-smali/expected.txt
@@ -16,3 +16,13 @@
(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 108b5fa..915de2d 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 0000000..f18b7ff
--- /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 5d2aabb..9892d61 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 1725da3..52f7ba3 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 38ee95a..3444b13 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 7f05a04..e43ea90 100644
--- a/test/Android.libarttest.mk
+++ b/test/Android.libarttest.mk
@@ -33,6 +33,7 @@
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 29e015f..4397ea4 100644
--- a/test/Android.run-test.mk
+++ b/test/Android.run-test.mk
@@ -370,6 +370,7 @@
# 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 a670fc7..972e827 100755
--- a/tools/buildbot-build.sh
+++ b/tools/buildbot-build.sh
@@ -68,20 +68,14 @@
echo "Executing $make_command"
$make_command
elif [[ $mode == "target" ]]; then
+ # Disable NINJA for building on target, it does not support setting environment variables
+ # within the make command.
+ env="$env USE_NINJA=false"
+ # Build extra tools that will be used by tests, so that
+ # they are compiled with our own linker.
# 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.
- 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
- # 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"
+ 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