summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--build/Android.gtest.mk1
-rw-r--r--compiler/Android.mk1
-rw-r--r--compiler/dex/quick/x86/codegen_x86.h4
-rwxr-xr-xcompiler/dex/quick/x86/target_x86.cc25
-rw-r--r--compiler/optimizing/graph_visualizer.h2
-rw-r--r--compiler/optimizing/gvn.cc186
-rw-r--r--compiler/optimizing/gvn.h230
-rw-r--r--compiler/optimizing/gvn_test.cc294
-rw-r--r--compiler/optimizing/nodes.cc3
-rw-r--r--compiler/optimizing/nodes.h64
-rw-r--r--compiler/optimizing/optimizing_compiler.cc6
-rw-r--r--oatdump/oatdump.cc11
-rw-r--r--patchoat/patchoat.cc7
-rw-r--r--runtime/class_linker.cc3
-rw-r--r--runtime/gc/space/image_space.cc57
-rw-r--r--runtime/gc/space/image_space.h3
-rw-r--r--runtime/jdwp/jdwp_event.cc9
-rw-r--r--runtime/native/dalvik_system_DexFile.cc4
-rw-r--r--runtime/runtime.cc4
-rw-r--r--runtime/thread_list.cc9
-rw-r--r--runtime/utils.cc6
-rw-r--r--runtime/utils.h3
22 files changed, 885 insertions, 47 deletions
diff --git a/build/Android.gtest.mk b/build/Android.gtest.mk
index 3f58527c45..d93d6dc10f 100644
--- a/build/Android.gtest.mk
+++ b/build/Android.gtest.mk
@@ -146,6 +146,7 @@ COMPILER_GTEST_COMMON_SRC_FILES := \
compiler/optimizing/find_loops_test.cc \
compiler/optimizing/graph_checker_test.cc \
compiler/optimizing/graph_test.cc \
+ compiler/optimizing/gvn_test.cc \
compiler/optimizing/linearize_test.cc \
compiler/optimizing/liveness_test.cc \
compiler/optimizing/live_interval_test.cc \
diff --git a/compiler/Android.mk b/compiler/Android.mk
index 35cddf9b01..7ac1c6b377 100644
--- a/compiler/Android.mk
+++ b/compiler/Android.mk
@@ -94,6 +94,7 @@ LIBART_COMPILER_SRC_FILES := \
optimizing/dead_code_elimination.cc \
optimizing/graph_checker.cc \
optimizing/graph_visualizer.cc \
+ optimizing/gvn.cc \
optimizing/locations.cc \
optimizing/nodes.cc \
optimizing/optimizing_compiler.cc \
diff --git a/compiler/dex/quick/x86/codegen_x86.h b/compiler/dex/quick/x86/codegen_x86.h
index dd4d66105c..6020e70f32 100644
--- a/compiler/dex/quick/x86/codegen_x86.h
+++ b/compiler/dex/quick/x86/codegen_x86.h
@@ -497,6 +497,8 @@ class X86Mir2Lir : public Mir2Lir {
void MaskVectorRegister(X86OpCode opcode, RegStorage rs_src1, uint32_t m1, uint32_t m2,
uint32_t m3, uint32_t m4);
void AppendOpcodeWithConst(X86OpCode opcode, int reg, MIR* mir);
+ virtual void LoadVectorRegister(RegStorage rs_dest, RegStorage rs_src, OpSize opsize,
+ int op_mov);
static bool ProvidesFullMemoryBarrier(X86OpCode opcode);
@@ -914,7 +916,7 @@ class X86Mir2Lir : public Mir2Lir {
* @param bb Basic block containing instruction.
* @param mir Instruction to analyze.
*/
- void AnalyzeFPInstruction(int opcode, BasicBlock * bb, MIR *mir);
+ virtual void AnalyzeFPInstruction(int opcode, BasicBlock * bb, MIR *mir);
/*
* @brief Analyze one use of a double operand.
diff --git a/compiler/dex/quick/x86/target_x86.cc b/compiler/dex/quick/x86/target_x86.cc
index aadb41a37a..bf088aac50 100755
--- a/compiler/dex/quick/x86/target_x86.cc
+++ b/compiler/dex/quick/x86/target_x86.cc
@@ -2268,6 +2268,20 @@ void X86Mir2Lir::GenReduceVector(BasicBlock *bb, MIR *mir) {
}
}
+void X86Mir2Lir::LoadVectorRegister(RegStorage rs_dest, RegStorage rs_src,
+ OpSize opsize, int op_mov) {
+ if (!cu_->target64 && opsize == k64) {
+ // Logic assumes that longs are loaded in GP register pairs.
+ NewLIR2(kX86MovdxrRR, rs_dest.GetReg(), rs_src.GetLowReg());
+ RegStorage r_tmp = AllocTempDouble();
+ NewLIR2(kX86MovdxrRR, r_tmp.GetReg(), rs_src.GetHighReg());
+ NewLIR2(kX86PunpckldqRR, rs_dest.GetReg(), r_tmp.GetReg());
+ FreeTemp(r_tmp);
+ } else {
+ NewLIR2(op_mov, rs_dest.GetReg(), rs_src.GetReg());
+ }
+}
+
void X86Mir2Lir::GenSetVector(BasicBlock *bb, MIR *mir) {
DCHECK_EQ(mir->dalvikInsn.vC & 0xFFFF, 128U);
OpSize opsize = static_cast<OpSize>(mir->dalvikInsn.vC >> 16);
@@ -2321,16 +2335,7 @@ void X86Mir2Lir::GenSetVector(BasicBlock *bb, MIR *mir) {
RegStorage reg_to_shuffle = rl_src.reg;
// Load the value into the XMM register.
- if (!cu_->target64 && opsize == k64) {
- // Logic assumes that longs are loaded in GP register pairs.
- NewLIR2(kX86MovdxrRR, rs_dest.GetReg(), reg_to_shuffle.GetLowReg());
- RegStorage r_tmp = AllocTempDouble();
- NewLIR2(kX86MovdxrRR, r_tmp.GetReg(), reg_to_shuffle.GetHighReg());
- NewLIR2(kX86PunpckldqRR, rs_dest.GetReg(), r_tmp.GetReg());
- FreeTemp(r_tmp);
- } else {
- NewLIR2(op_mov, rs_dest.GetReg(), reg_to_shuffle.GetReg());
- }
+ LoadVectorRegister(rs_dest, reg_to_shuffle, opsize, op_mov);
if (opsize == kSignedByte || opsize == kUnsignedByte) {
// In the byte case, first duplicate it to be a word
diff --git a/compiler/optimizing/graph_visualizer.h b/compiler/optimizing/graph_visualizer.h
index 7cd74e9b7a..6e2c6fd11f 100644
--- a/compiler/optimizing/graph_visualizer.h
+++ b/compiler/optimizing/graph_visualizer.h
@@ -25,8 +25,10 @@ class CodeGenerator;
class DexCompilationUnit;
class HGraph;
+// TODO: Create an analysis/optimization abstraction.
static const char* kLivenessPassName = "liveness";
static const char* kRegisterAllocatorPassName = "register";
+static const char* kGVNPassName = "gvn";
/**
* If enabled, emits compilation information suitable for the c1visualizer tool
diff --git a/compiler/optimizing/gvn.cc b/compiler/optimizing/gvn.cc
new file mode 100644
index 0000000000..027b3d4ff3
--- /dev/null
+++ b/compiler/optimizing/gvn.cc
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "gvn.h"
+
+namespace art {
+
+void GlobalValueNumberer::Run() {
+ ComputeSideEffects();
+
+ sets_.Put(graph_->GetEntryBlock()->GetBlockId(), new (allocator_) ValueSet(allocator_));
+
+ // Do reverse post order to ensure the non back-edge predecessors of a block are
+ // visited before the block itself.
+ for (HReversePostOrderIterator it(*graph_); !it.Done(); it.Advance()) {
+ VisitBasicBlock(it.Current());
+ }
+}
+
+void GlobalValueNumberer::UpdateLoopEffects(HLoopInformation* info, SideEffects effects) {
+ int id = info->GetHeader()->GetBlockId();
+ loop_effects_.Put(id, loop_effects_.Get(id).Union(effects));
+}
+
+void GlobalValueNumberer::ComputeSideEffects() {
+ if (kIsDebugBuild) {
+ for (HReversePostOrderIterator it(*graph_); !it.Done(); it.Advance()) {
+ HBasicBlock* block = it.Current();
+ SideEffects effects = GetBlockEffects(block);
+ DCHECK(!effects.HasSideEffects() && !effects.HasDependencies());
+ if (block->IsLoopHeader()) {
+ effects = GetLoopEffects(block);
+ DCHECK(!effects.HasSideEffects() && !effects.HasDependencies());
+ }
+ }
+ }
+
+ // Do a post order visit to ensure we visit a loop header after its loop body.
+ for (HPostOrderIterator it(*graph_); !it.Done(); it.Advance()) {
+ HBasicBlock* block = it.Current();
+
+ SideEffects effects = SideEffects::None();
+ // Update `effects` with the side effects of all instructions in this block.
+ for (HInstructionIterator it(block->GetInstructions()); !it.Done(); it.Advance()) {
+ HInstruction* instruction = it.Current();
+ effects = effects.Union(instruction->GetSideEffects());
+ if (effects.HasAllSideEffects()) {
+ break;
+ }
+ }
+
+ block_effects_.Put(block->GetBlockId(), effects);
+
+ if (block->IsLoopHeader()) {
+ // The side effects of the loop header are part of the loop.
+ UpdateLoopEffects(block->GetLoopInformation(), effects);
+ HBasicBlock* pre_header = block->GetLoopInformation()->GetPreHeader();
+ if (pre_header->IsInLoop()) {
+ // Update the side effects of the outer loop with the side effects of the inner loop.
+ // Note that this works because we know all the blocks of the inner loop are visited
+ // before the loop header of the outer loop.
+ UpdateLoopEffects(pre_header->GetLoopInformation(), GetLoopEffects(block));
+ }
+ } else if (block->IsInLoop()) {
+ // Update the side effects of the loop with the side effects of this block.
+ UpdateLoopEffects(block->GetLoopInformation(), effects);
+ }
+ }
+}
+
+SideEffects GlobalValueNumberer::GetLoopEffects(HBasicBlock* block) const {
+ DCHECK(block->IsLoopHeader());
+ return loop_effects_.Get(block->GetBlockId());
+}
+
+SideEffects GlobalValueNumberer::GetBlockEffects(HBasicBlock* block) const {
+ return block_effects_.Get(block->GetBlockId());
+}
+
+static bool IsLoopExit(HBasicBlock* block, HBasicBlock* successor) {
+ HLoopInformation* block_info = block->GetLoopInformation();
+ HLoopInformation* other_info = successor->GetLoopInformation();
+ return block_info != other_info && (other_info == nullptr || block_info->IsIn(*other_info));
+}
+
+void GlobalValueNumberer::VisitBasicBlock(HBasicBlock* block) {
+ if (kIsDebugBuild) {
+ // Check that all non back-edge processors have been visited.
+ for (size_t i = 0, e = block->GetPredecessors().Size(); i < e; ++i) {
+ HBasicBlock* predecessor = block->GetPredecessors().Get(i);
+ DCHECK(visited_.Get(predecessor->GetBlockId())
+ || (block->GetLoopInformation() != nullptr
+ && (block->GetLoopInformation()->GetBackEdges().Get(0) == predecessor)));
+ }
+ visited_.Put(block->GetBlockId(), true);
+ }
+
+ ValueSet* set = sets_.Get(block->GetBlockId());
+
+ if (block->IsLoopHeader()) {
+ set->Kill(GetLoopEffects(block));
+ }
+
+ HInstruction* current = block->GetFirstInstruction();
+ while (current != nullptr) {
+ set->Kill(current->GetSideEffects());
+ // Save the next instruction in case `current` is removed from the graph.
+ HInstruction* next = current->GetNext();
+ if (current->CanBeMoved()) {
+ HInstruction* existing = set->Lookup(current);
+ if (existing != nullptr) {
+ current->ReplaceWith(existing);
+ current->GetBlock()->RemoveInstruction(current);
+ } else {
+ set->Add(current);
+ }
+ }
+ current = next;
+ }
+
+ if (block == graph_->GetEntryBlock()) {
+ // The entry block should only accumulate constant instructions, and
+ // the builder puts constants only in the entry block.
+ // Therefore, there is no need to propagate the value set to the next block.
+ DCHECK_EQ(block->GetDominatedBlocks().Size(), 1u);
+ HBasicBlock* dominated = block->GetDominatedBlocks().Get(0);
+ sets_.Put(dominated->GetBlockId(), new (allocator_) ValueSet(allocator_));
+ return;
+ }
+
+ // Copy the value set to dominated blocks. We can re-use
+ // the current set for the last dominated block because we are done visiting
+ // this block.
+ for (size_t i = 0, e = block->GetDominatedBlocks().Size(); i < e; ++i) {
+ HBasicBlock* dominated = block->GetDominatedBlocks().Get(i);
+ sets_.Put(dominated->GetBlockId(), i == e - 1 ? set : set->Copy());
+ }
+
+ // Kill instructions in the value set of each successor. If the successor
+ // is a loop exit, then we use the side effects of the loop. If not, we use
+ // the side effects of this block.
+ for (size_t i = 0, e = block->GetSuccessors().Size(); i < e; ++i) {
+ HBasicBlock* successor = block->GetSuccessors().Get(i);
+ if (successor->IsLoopHeader()
+ && successor->GetLoopInformation()->GetBackEdges().Get(0) == block) {
+ // In case of a back edge, we already have visited the loop header.
+ // We should not update its value set, because the last dominated block
+ // of the loop header uses the same value set.
+ DCHECK(visited_.Get(successor->GetBlockId()));
+ continue;
+ }
+ DCHECK(!visited_.Get(successor->GetBlockId()));
+ ValueSet* successor_set = sets_.Get(successor->GetBlockId());
+ // The dominator sets the set, and we are guaranteed to have visited it already.
+ DCHECK(successor_set != nullptr);
+
+ // If this block dominates this successor there is nothing to do.
+ // Also if the set is empty, there is nothing to kill.
+ if (successor->GetDominator() != block && !successor_set->IsEmpty()) {
+ if (block->IsInLoop() && IsLoopExit(block, successor)) {
+ // All instructions killed in the loop must be killed for a loop exit.
+ SideEffects effects = GetLoopEffects(block->GetLoopInformation()->GetHeader());
+ sets_.Get(successor->GetBlockId())->Kill(effects);
+ } else {
+ // Following block (that might be in the same loop).
+ // Just kill instructions based on this block's side effects.
+ sets_.Get(successor->GetBlockId())->Kill(GetBlockEffects(block));
+ }
+ }
+ }
+}
+
+} // namespace art
diff --git a/compiler/optimizing/gvn.h b/compiler/optimizing/gvn.h
new file mode 100644
index 0000000000..41b3ceb509
--- /dev/null
+++ b/compiler/optimizing/gvn.h
@@ -0,0 +1,230 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ART_COMPILER_OPTIMIZING_GVN_H_
+#define ART_COMPILER_OPTIMIZING_GVN_H_
+
+#include <gtest/gtest.h>
+#include "nodes.h"
+
+namespace art {
+
+/**
+ * A node in the collision list of a ValueSet. Encodes the instruction,
+ * the hash code, and the next node in the collision list.
+ */
+class ValueSetNode : public ArenaObject {
+ public:
+ ValueSetNode(HInstruction* instruction, size_t hash_code, ValueSetNode* next)
+ : instruction_(instruction), hash_code_(hash_code), next_(next) {}
+
+ size_t GetHashCode() const { return hash_code_; }
+ HInstruction* GetInstruction() const { return instruction_; }
+ ValueSetNode* GetNext() const { return next_; }
+ void SetNext(ValueSetNode* node) { next_ = node; }
+
+ private:
+ HInstruction* const instruction_;
+ const size_t hash_code_;
+ ValueSetNode* next_;
+
+ DISALLOW_COPY_AND_ASSIGN(ValueSetNode);
+};
+
+/**
+ * A ValueSet holds instructions that can replace other instructions. It is updated
+ * through the `Add` method, and the `Kill` method. The `Kill` method removes
+ * instructions that are affected by the given side effect.
+ *
+ * The `Lookup` method returns an equivalent instruction to the given instruction
+ * if there is one in the set. In GVN, we would say those instructions have the
+ * same "number".
+ */
+class ValueSet : public ArenaObject {
+ public:
+ explicit ValueSet(ArenaAllocator* allocator)
+ : allocator_(allocator), number_of_entries_(0), collisions_(nullptr) {
+ for (size_t i = 0; i < kDefaultNumberOfEntries; ++i) {
+ table_[i] = nullptr;
+ }
+ }
+
+ // Adds an instruction in the set.
+ void Add(HInstruction* instruction) {
+ DCHECK(Lookup(instruction) == nullptr);
+ size_t hash_code = instruction->ComputeHashCode();
+ size_t index = hash_code % kDefaultNumberOfEntries;
+ if (table_[index] == nullptr) {
+ table_[index] = instruction;
+ } else {
+ collisions_ = new (allocator_) ValueSetNode(instruction, hash_code, collisions_);
+ }
+ ++number_of_entries_;
+ }
+
+ // If in the set, returns an equivalent instruction to the given instruction. Returns
+ // null otherwise.
+ HInstruction* Lookup(HInstruction* instruction) const {
+ size_t hash_code = instruction->ComputeHashCode();
+ size_t index = hash_code % kDefaultNumberOfEntries;
+ HInstruction* existing = table_[index];
+ if (existing != nullptr && existing->Equals(instruction)) {
+ return existing;
+ }
+
+ for (ValueSetNode* node = collisions_; node != nullptr; node = node->GetNext()) {
+ if (node->GetHashCode() == hash_code) {
+ existing = node->GetInstruction();
+ if (existing->Equals(instruction)) {
+ return existing;
+ }
+ }
+ }
+ return nullptr;
+ }
+
+ // Removes all instructions in the set that are affected by the given side effects.
+ void Kill(SideEffects side_effects) {
+ for (size_t i = 0; i < kDefaultNumberOfEntries; ++i) {
+ HInstruction* instruction = table_[i];
+ if (instruction != nullptr && instruction->GetSideEffects().DependsOn(side_effects)) {
+ table_[i] = nullptr;
+ --number_of_entries_;
+ }
+ }
+
+ ValueSetNode* current = collisions_;
+ ValueSetNode* previous = nullptr;
+ while (current != nullptr) {
+ HInstruction* instruction = current->GetInstruction();
+ if (instruction->GetSideEffects().DependsOn(side_effects)) {
+ if (previous == nullptr) {
+ collisions_ = current->GetNext();
+ } else {
+ previous->SetNext(current->GetNext());
+ }
+ --number_of_entries_;
+ } else {
+ previous = current;
+ }
+ current = current->GetNext();
+ }
+ }
+
+ // Returns a copy of this set.
+ ValueSet* Copy() const {
+ ValueSet* copy = new (allocator_) ValueSet(allocator_);
+
+ for (size_t i = 0; i < kDefaultNumberOfEntries; ++i) {
+ copy->table_[i] = table_[i];
+ }
+
+ // Note that the order will be inverted in the copy. This is fine, as the order is not
+ // relevant for a ValueSet.
+ for (ValueSetNode* node = collisions_; node != nullptr; node = node->GetNext()) {
+ copy->collisions_ = new (allocator_) ValueSetNode(
+ node->GetInstruction(), node->GetHashCode(), copy->collisions_);
+ }
+
+ copy->number_of_entries_ = number_of_entries_;
+ return copy;
+ }
+
+ bool IsEmpty() const { return number_of_entries_ == 0; }
+ size_t GetNumberOfEntries() const { return number_of_entries_; }
+
+ private:
+ static constexpr size_t kDefaultNumberOfEntries = 8;
+
+ ArenaAllocator* const allocator_;
+
+ // The number of entries in the set.
+ size_t number_of_entries_;
+
+ // The internal implementation of the set. It uses a combination of a hash code based
+ // fixed-size list, and a linked list to handle hash code collisions.
+ // TODO: Tune the fixed size list original size, and support growing it.
+ ValueSetNode* collisions_;
+ HInstruction* table_[kDefaultNumberOfEntries];
+
+ DISALLOW_COPY_AND_ASSIGN(ValueSet);
+};
+
+/**
+ * Optimization phase that removes redundant instruction.
+ */
+class GlobalValueNumberer : public ValueObject {
+ public:
+ GlobalValueNumberer(ArenaAllocator* allocator, HGraph* graph)
+ : allocator_(allocator),
+ graph_(graph),
+ block_effects_(allocator, graph->GetBlocks().Size()),
+ loop_effects_(allocator, graph->GetBlocks().Size()),
+ sets_(allocator, graph->GetBlocks().Size()),
+ visited_(allocator, graph->GetBlocks().Size()) {
+ size_t number_of_blocks = graph->GetBlocks().Size();
+ block_effects_.SetSize(number_of_blocks);
+ loop_effects_.SetSize(number_of_blocks);
+ sets_.SetSize(number_of_blocks);
+ visited_.SetSize(number_of_blocks);
+
+ for (size_t i = 0; i < number_of_blocks; ++i) {
+ block_effects_.Put(i, SideEffects::None());
+ loop_effects_.Put(i, SideEffects::None());
+ }
+ }
+
+ void Run();
+
+ private:
+ // Per-block GVN. Will also update the ValueSet of the dominated and
+ // successor blocks.
+ void VisitBasicBlock(HBasicBlock* block);
+
+ // Compute side effects of individual blocks and loops. The GVN algorithm
+ // will use these side effects to update the ValueSet of individual blocks.
+ void ComputeSideEffects();
+
+ void UpdateLoopEffects(HLoopInformation* info, SideEffects effects);
+ SideEffects GetLoopEffects(HBasicBlock* block) const;
+ SideEffects GetBlockEffects(HBasicBlock* block) const;
+
+ ArenaAllocator* const allocator_;
+ HGraph* const graph_;
+
+ // Side effects of individual blocks, that is the union of the side effects
+ // of the instructions in the block.
+ GrowableArray<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_;
+
+ // 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_;
+
+ // Mark visisted blocks. Only used for debugging.
+ GrowableArray<bool> visited_;
+
+ FRIEND_TEST(GVNTest, LoopSideEffects);
+ DISALLOW_COPY_AND_ASSIGN(GlobalValueNumberer);
+};
+
+} // namespace art
+
+#endif // ART_COMPILER_OPTIMIZING_GVN_H_
diff --git a/compiler/optimizing/gvn_test.cc b/compiler/optimizing/gvn_test.cc
new file mode 100644
index 0000000000..ad6e3382bc
--- /dev/null
+++ b/compiler/optimizing/gvn_test.cc
@@ -0,0 +1,294 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "builder.h"
+#include "gvn.h"
+#include "nodes.h"
+#include "optimizing_unit_test.h"
+#include "utils/arena_allocator.h"
+
+#include "gtest/gtest.h"
+
+namespace art {
+
+TEST(GVNTest, LocalFieldElimination) {
+ ArenaPool pool;
+ ArenaAllocator allocator(&pool);
+
+ HGraph* graph = new (&allocator) HGraph(&allocator);
+ HBasicBlock* entry = new (&allocator) HBasicBlock(graph);
+ graph->AddBlock(entry);
+ graph->SetEntryBlock(entry);
+ HInstruction* parameter = new (&allocator) HParameterValue(0, Primitive::kPrimNot);
+ entry->AddInstruction(parameter);
+
+ HBasicBlock* block = new (&allocator) HBasicBlock(graph);
+ graph->AddBlock(block);
+ entry->AddSuccessor(block);
+
+ block->AddInstruction(
+ new (&allocator) HInstanceFieldGet(parameter, Primitive::kPrimNot, MemberOffset(42)));
+ block->AddInstruction(
+ new (&allocator) HInstanceFieldGet(parameter, Primitive::kPrimNot, MemberOffset(42)));
+ HInstruction* to_remove = block->GetLastInstruction();
+ block->AddInstruction(
+ new (&allocator) HInstanceFieldGet(parameter, Primitive::kPrimNot, MemberOffset(43)));
+ HInstruction* different_offset = block->GetLastInstruction();
+ // Kill the value.
+ block->AddInstruction(new (&allocator) HInstanceFieldSet(
+ parameter, parameter, Primitive::kPrimNot, MemberOffset(42)));
+ block->AddInstruction(
+ new (&allocator) HInstanceFieldGet(parameter, Primitive::kPrimNot, MemberOffset(42)));
+ HInstruction* use_after_kill = block->GetLastInstruction();
+ block->AddInstruction(new (&allocator) HExit());
+
+ ASSERT_EQ(to_remove->GetBlock(), block);
+ ASSERT_EQ(different_offset->GetBlock(), block);
+ ASSERT_EQ(use_after_kill->GetBlock(), block);
+
+ graph->BuildDominatorTree();
+ graph->TransformToSSA();
+ GlobalValueNumberer(&allocator, graph).Run();
+
+ ASSERT_TRUE(to_remove->GetBlock() == nullptr);
+ ASSERT_EQ(different_offset->GetBlock(), block);
+ ASSERT_EQ(use_after_kill->GetBlock(), block);
+}
+
+TEST(GVNTest, GlobalFieldElimination) {
+ ArenaPool pool;
+ ArenaAllocator allocator(&pool);
+
+ HGraph* graph = new (&allocator) HGraph(&allocator);
+ HBasicBlock* entry = new (&allocator) HBasicBlock(graph);
+ graph->AddBlock(entry);
+ graph->SetEntryBlock(entry);
+ HInstruction* parameter = new (&allocator) HParameterValue(0, Primitive::kPrimNot);
+ entry->AddInstruction(parameter);
+
+ HBasicBlock* block = new (&allocator) HBasicBlock(graph);
+ graph->AddBlock(block);
+ entry->AddSuccessor(block);
+ block->AddInstruction(
+ new (&allocator) HInstanceFieldGet(parameter, Primitive::kPrimBoolean, MemberOffset(42)));
+
+ block->AddInstruction(new (&allocator) HIf(block->GetLastInstruction()));
+ HBasicBlock* then = new (&allocator) HBasicBlock(graph);
+ HBasicBlock* else_ = new (&allocator) HBasicBlock(graph);
+ HBasicBlock* join = new (&allocator) HBasicBlock(graph);
+ graph->AddBlock(then);
+ graph->AddBlock(else_);
+ graph->AddBlock(join);
+
+ block->AddSuccessor(then);
+ block->AddSuccessor(else_);
+ then->AddSuccessor(join);
+ else_->AddSuccessor(join);
+
+ then->AddInstruction(
+ new (&allocator) HInstanceFieldGet(parameter, Primitive::kPrimBoolean, MemberOffset(42)));
+ then->AddInstruction(new (&allocator) HGoto());
+ else_->AddInstruction(
+ new (&allocator) HInstanceFieldGet(parameter, Primitive::kPrimBoolean, MemberOffset(42)));
+ else_->AddInstruction(new (&allocator) HGoto());
+ join->AddInstruction(
+ new (&allocator) HInstanceFieldGet(parameter, Primitive::kPrimBoolean, MemberOffset(42)));
+ join->AddInstruction(new (&allocator) HExit());
+
+ graph->BuildDominatorTree();
+ graph->TransformToSSA();
+ GlobalValueNumberer(&allocator, graph).Run();
+
+ // Check that all field get instructions have been GVN'ed.
+ ASSERT_TRUE(then->GetFirstInstruction()->IsGoto());
+ ASSERT_TRUE(else_->GetFirstInstruction()->IsGoto());
+ ASSERT_TRUE(join->GetFirstInstruction()->IsExit());
+}
+
+TEST(GVNTest, LoopFieldElimination) {
+ ArenaPool pool;
+ ArenaAllocator allocator(&pool);
+
+ HGraph* graph = new (&allocator) HGraph(&allocator);
+ HBasicBlock* entry = new (&allocator) HBasicBlock(graph);
+ graph->AddBlock(entry);
+ graph->SetEntryBlock(entry);
+
+ HInstruction* parameter = new (&allocator) HParameterValue(0, Primitive::kPrimNot);
+ entry->AddInstruction(parameter);
+
+ HBasicBlock* block = new (&allocator) HBasicBlock(graph);
+ graph->AddBlock(block);
+ entry->AddSuccessor(block);
+ block->AddInstruction(
+ new (&allocator) HInstanceFieldGet(parameter, Primitive::kPrimBoolean, MemberOffset(42)));
+ block->AddInstruction(new (&allocator) HGoto());
+
+ HBasicBlock* loop_header = new (&allocator) HBasicBlock(graph);
+ HBasicBlock* loop_body = new (&allocator) HBasicBlock(graph);
+ HBasicBlock* exit = new (&allocator) HBasicBlock(graph);
+
+ graph->AddBlock(loop_header);
+ graph->AddBlock(loop_body);
+ graph->AddBlock(exit);
+ block->AddSuccessor(loop_header);
+ loop_header->AddSuccessor(loop_body);
+ loop_header->AddSuccessor(exit);
+ loop_body->AddSuccessor(loop_header);
+
+ loop_header->AddInstruction(
+ new (&allocator) HInstanceFieldGet(parameter, Primitive::kPrimBoolean, MemberOffset(42)));
+ HInstruction* field_get_in_loop_header = loop_header->GetLastInstruction();
+ loop_header->AddInstruction(new (&allocator) HIf(block->GetLastInstruction()));
+
+ // Kill inside the loop body to prevent field gets inside the loop header
+ // and the body to be GVN'ed.
+ loop_body->AddInstruction(new (&allocator) HInstanceFieldSet(
+ parameter, parameter, Primitive::kPrimNot, MemberOffset(42)));
+ HInstruction* field_set = loop_body->GetLastInstruction();
+ loop_body->AddInstruction(
+ new (&allocator) HInstanceFieldGet(parameter, Primitive::kPrimBoolean, MemberOffset(42)));
+ HInstruction* field_get_in_loop_body = loop_body->GetLastInstruction();
+ loop_body->AddInstruction(new (&allocator) HGoto());
+
+ exit->AddInstruction(
+ new (&allocator) HInstanceFieldGet(parameter, Primitive::kPrimBoolean, MemberOffset(42)));
+ HInstruction* field_get_in_exit = exit->GetLastInstruction();
+ exit->AddInstruction(new (&allocator) HExit());
+
+ ASSERT_EQ(field_get_in_loop_header->GetBlock(), loop_header);
+ ASSERT_EQ(field_get_in_loop_body->GetBlock(), loop_body);
+ ASSERT_EQ(field_get_in_exit->GetBlock(), exit);
+
+ graph->BuildDominatorTree();
+ graph->TransformToSSA();
+ graph->FindNaturalLoops();
+ GlobalValueNumberer(&allocator, graph).Run();
+
+ // Check that all field get instructions are still there.
+ ASSERT_EQ(field_get_in_loop_header->GetBlock(), loop_header);
+ ASSERT_EQ(field_get_in_loop_body->GetBlock(), loop_body);
+ // The exit block is dominated by the loop header, whose field get
+ // does not get killed by the loop flags.
+ ASSERT_TRUE(field_get_in_exit->GetBlock() == nullptr);
+
+ // Now remove the field set, and check that all field get instructions have been GVN'ed.
+ loop_body->RemoveInstruction(field_set);
+ GlobalValueNumberer(&allocator, graph).Run();
+
+ ASSERT_TRUE(field_get_in_loop_header->GetBlock() == nullptr);
+ ASSERT_TRUE(field_get_in_loop_body->GetBlock() == nullptr);
+ ASSERT_TRUE(field_get_in_exit->GetBlock() == nullptr);
+}
+
+// Test that inner loops affect the side effects of the outer loop.
+TEST(GVNTest, LoopSideEffects) {
+ ArenaPool pool;
+ ArenaAllocator allocator(&pool);
+
+ HGraph* graph = new (&allocator) HGraph(&allocator);
+ HBasicBlock* entry = new (&allocator) HBasicBlock(graph);
+ graph->AddBlock(entry);
+ graph->SetEntryBlock(entry);
+
+ HBasicBlock* outer_loop_header = new (&allocator) HBasicBlock(graph);
+ HBasicBlock* outer_loop_body = new (&allocator) HBasicBlock(graph);
+ HBasicBlock* outer_loop_exit = new (&allocator) HBasicBlock(graph);
+ HBasicBlock* inner_loop_header = new (&allocator) HBasicBlock(graph);
+ HBasicBlock* inner_loop_body = new (&allocator) HBasicBlock(graph);
+ HBasicBlock* inner_loop_exit = new (&allocator) HBasicBlock(graph);
+
+ graph->AddBlock(outer_loop_header);
+ graph->AddBlock(outer_loop_body);
+ graph->AddBlock(outer_loop_exit);
+ graph->AddBlock(inner_loop_header);
+ graph->AddBlock(inner_loop_body);
+ graph->AddBlock(inner_loop_exit);
+
+ entry->AddSuccessor(outer_loop_header);
+ outer_loop_header->AddSuccessor(outer_loop_body);
+ outer_loop_header->AddSuccessor(outer_loop_exit);
+ outer_loop_body->AddSuccessor(inner_loop_header);
+ inner_loop_header->AddSuccessor(inner_loop_body);
+ inner_loop_header->AddSuccessor(inner_loop_exit);
+ inner_loop_body->AddSuccessor(inner_loop_header);
+ inner_loop_exit->AddSuccessor(outer_loop_header);
+
+ HInstruction* parameter = new (&allocator) HParameterValue(0, Primitive::kPrimBoolean);
+ entry->AddInstruction(parameter);
+ entry->AddInstruction(new (&allocator) HGoto());
+ outer_loop_header->AddInstruction(new (&allocator) HIf(parameter));
+ outer_loop_body->AddInstruction(new (&allocator) HGoto());
+ inner_loop_header->AddInstruction(new (&allocator) HIf(parameter));
+ inner_loop_body->AddInstruction(new (&allocator) HGoto());
+ inner_loop_exit->AddInstruction(new (&allocator) HGoto());
+ outer_loop_exit->AddInstruction(new (&allocator) HExit());
+
+ graph->BuildDominatorTree();
+ graph->TransformToSSA();
+ graph->FindNaturalLoops();
+
+ ASSERT_TRUE(inner_loop_header->GetLoopInformation()->IsIn(
+ *outer_loop_header->GetLoopInformation()));
+
+ // Check that the loops don't have side effects.
+ {
+ // Make one block with a side effect.
+ entry->AddInstruction(new (&allocator) HInstanceFieldSet(
+ parameter, parameter, Primitive::kPrimNot, MemberOffset(42)));
+
+ GlobalValueNumberer gvn(&allocator, graph);
+ gvn.Run();
+
+ ASSERT_TRUE(gvn.GetBlockEffects(entry).HasSideEffects());
+ ASSERT_FALSE(gvn.GetLoopEffects(outer_loop_header).HasSideEffects());
+ ASSERT_FALSE(gvn.GetLoopEffects(inner_loop_header).HasSideEffects());
+ }
+
+ // Check that the side effects of the outer loop does not affect the inner loop.
+ {
+ outer_loop_body->InsertInstructionBefore(
+ new (&allocator) HInstanceFieldSet(
+ parameter, parameter, Primitive::kPrimNot, MemberOffset(42)),
+ outer_loop_body->GetLastInstruction());
+
+ GlobalValueNumberer gvn(&allocator, graph);
+ gvn.Run();
+
+ ASSERT_TRUE(gvn.GetBlockEffects(entry).HasSideEffects());
+ ASSERT_TRUE(gvn.GetBlockEffects(outer_loop_body).HasSideEffects());
+ ASSERT_TRUE(gvn.GetLoopEffects(outer_loop_header).HasSideEffects());
+ ASSERT_FALSE(gvn.GetLoopEffects(inner_loop_header).HasSideEffects());
+ }
+
+ // Check that the side effects of the inner loop affects the outer loop.
+ {
+ outer_loop_body->RemoveInstruction(outer_loop_body->GetFirstInstruction());
+ inner_loop_body->InsertInstructionBefore(
+ new (&allocator) HInstanceFieldSet(
+ parameter, parameter, Primitive::kPrimNot, MemberOffset(42)),
+ inner_loop_body->GetLastInstruction());
+
+ GlobalValueNumberer gvn(&allocator, graph);
+ gvn.Run();
+
+ ASSERT_TRUE(gvn.GetBlockEffects(entry).HasSideEffects());
+ ASSERT_FALSE(gvn.GetBlockEffects(outer_loop_body).HasSideEffects());
+ ASSERT_TRUE(gvn.GetLoopEffects(outer_loop_header).HasSideEffects());
+ ASSERT_TRUE(gvn.GetLoopEffects(inner_loop_header).HasSideEffects());
+ }
+}
+} // namespace art
diff --git a/compiler/optimizing/nodes.cc b/compiler/optimizing/nodes.cc
index 376d1af3ef..1a24677261 100644
--- a/compiler/optimizing/nodes.cc
+++ b/compiler/optimizing/nodes.cc
@@ -124,6 +124,7 @@ void HGraph::VisitBlockForDominatorTree(HBasicBlock* block,
// dominator of the block. We can then start visiting its successors.
if (visits->Get(block->GetBlockId()) ==
block->GetPredecessors().Size() - block->NumberOfBackEdges()) {
+ block->GetDominator()->AddDominatedBlock(block);
reverse_post_order_.Add(block);
for (size_t i = 0; i < block->GetSuccessors().Size(); i++) {
VisitBlockForDominatorTree(block->GetSuccessors().Get(i), block, visits);
@@ -543,6 +544,7 @@ bool HCondition::NeedsMaterialization() const {
bool HInstruction::Equals(HInstruction* other) const {
if (!InstructionTypeEquals(other)) return false;
+ DCHECK_EQ(GetKind(), other->GetKind());
if (!InstructionDataEquals(other)) return false;
if (GetType() != other->GetType()) return false;
if (InputCount() != other->InputCount()) return false;
@@ -550,6 +552,7 @@ bool HInstruction::Equals(HInstruction* other) const {
for (size_t i = 0, e = InputCount(); i < e; ++i) {
if (InputAt(i) != other->InputAt(i)) return false;
}
+ DCHECK_EQ(ComputeHashCode(), other->ComputeHashCode());
return true;
}
diff --git a/compiler/optimizing/nodes.h b/compiler/optimizing/nodes.h
index d98d2ad75f..af173c8087 100644
--- a/compiler/optimizing/nodes.h
+++ b/compiler/optimizing/nodes.h
@@ -38,6 +38,7 @@ class LocationSummary;
static const int kDefaultNumberOfBlocks = 8;
static const int kDefaultNumberOfSuccessors = 2;
static const int kDefaultNumberOfPredecessors = 2;
+static const int kDefaultNumberOfDominatedBlocks = 1;
static const int kDefaultNumberOfBackEdges = 1;
enum IfCondition {
@@ -272,6 +273,7 @@ class HBasicBlock : public ArenaObject {
successors_(graph->GetArena(), kDefaultNumberOfSuccessors),
loop_information_(nullptr),
dominator_(nullptr),
+ dominated_blocks_(graph->GetArena(), kDefaultNumberOfDominatedBlocks),
block_id_(-1),
lifetime_start_(kNoLifetime),
lifetime_end_(kNoLifetime) {}
@@ -284,6 +286,10 @@ class HBasicBlock : public ArenaObject {
return successors_;
}
+ const GrowableArray<HBasicBlock*>& GetDominatedBlocks() const {
+ return dominated_blocks_;
+ }
+
void AddBackEdge(HBasicBlock* back_edge) {
if (loop_information_ == nullptr) {
loop_information_ = new (graph_->GetArena()) HLoopInformation(this, graph_);
@@ -299,6 +305,7 @@ class HBasicBlock : public ArenaObject {
HBasicBlock* GetDominator() const { return dominator_; }
void SetDominator(HBasicBlock* dominator) { dominator_ = dominator; }
+ void AddDominatedBlock(HBasicBlock* block) { dominated_blocks_.Add(block); }
int NumberOfBackEdges() const {
return loop_information_ == nullptr
@@ -418,6 +425,7 @@ class HBasicBlock : public ArenaObject {
HInstructionList phis_;
HLoopInformation* loop_information_;
HBasicBlock* dominator_;
+ GrowableArray<HBasicBlock*> dominated_blocks_;
int block_id_;
size_t lifetime_start_;
size_t lifetime_end_;
@@ -473,6 +481,7 @@ FOR_EACH_INSTRUCTION(FORWARD_DECLARATION)
#undef FORWARD_DECLARATION
#define DECLARE_INSTRUCTION(type) \
+ virtual InstructionKind GetKind() const { return k##type; } \
virtual const char* DebugName() const { return #type; } \
virtual const H##type* As##type() const OVERRIDE { return this; } \
virtual H##type* As##type() OVERRIDE { return this; } \
@@ -504,6 +513,8 @@ class HUseListNode : public ArenaObject {
// Represents the side effects an instruction may have.
class SideEffects : public ValueObject {
public:
+ SideEffects() : flags_(0) {}
+
static SideEffects None() {
return SideEffects(0);
}
@@ -521,11 +532,31 @@ class SideEffects : public ValueObject {
return SideEffects(((1 << count) - 1) << kFlagChangesCount);
}
+ SideEffects Union(SideEffects other) const {
+ return SideEffects(flags_ | other.flags_);
+ }
+
bool HasSideEffects() const {
size_t all_bits_set = (1 << kFlagChangesCount) - 1;
return (flags_ & all_bits_set) != 0;
}
+ bool HasAllSideEffects() const {
+ size_t all_bits_set = (1 << kFlagChangesCount) - 1;
+ return all_bits_set == (flags_ & all_bits_set);
+ }
+
+ bool DependsOn(SideEffects other) const {
+ size_t depends_flags = other.ComputeDependsFlags();
+ return (flags_ & depends_flags) != 0;
+ }
+
+ bool HasDependencies() const {
+ int count = kFlagDependsOnCount - kFlagChangesCount;
+ size_t all_bits_set = (1 << count) - 1;
+ return ((flags_ >> kFlagChangesCount) & all_bits_set) != 0;
+ }
+
private:
static constexpr int kFlagChangesSomething = 0;
static constexpr int kFlagChangesCount = kFlagChangesSomething + 1;
@@ -533,10 +564,13 @@ class SideEffects : public ValueObject {
static constexpr int kFlagDependsOnSomething = kFlagChangesCount;
static constexpr int kFlagDependsOnCount = kFlagDependsOnSomething + 1;
- private:
explicit SideEffects(size_t flags) : flags_(flags) {}
- const size_t flags_;
+ size_t ComputeDependsFlags() const {
+ return flags_ << kFlagChangesCount;
+ }
+
+ size_t flags_;
};
class HInstruction : public ArenaObject {
@@ -557,6 +591,12 @@ class HInstruction : public ArenaObject {
virtual ~HInstruction() {}
+#define DECLARE_KIND(type) k##type,
+ enum InstructionKind {
+ FOR_EACH_INSTRUCTION(DECLARE_KIND)
+ };
+#undef DECLARE_KIND
+
HInstruction* GetNext() const { return next_; }
HInstruction* GetPrevious() const { return previous_; }
@@ -659,6 +699,18 @@ class HInstruction : public ArenaObject {
// 2) Their inputs are identical.
bool Equals(HInstruction* other) const;
+ virtual InstructionKind GetKind() const = 0;
+
+ virtual size_t ComputeHashCode() const {
+ size_t result = GetKind();
+ for (size_t i = 0, e = InputCount(); i < e; ++i) {
+ result = (result * 31) + InputAt(i)->GetId();
+ }
+ return result;
+ }
+
+ SideEffects GetSideEffects() const { return side_effects_; }
+
size_t GetLifetimePosition() const { return lifetime_position_; }
void SetLifetimePosition(size_t position) { lifetime_position_ = position; }
LiveInterval* GetLiveInterval() const { return live_interval_; }
@@ -1258,6 +1310,8 @@ class HIntConstant : public HConstant {
return other->AsIntConstant()->value_ == value_;
}
+ virtual size_t ComputeHashCode() const { return GetValue(); }
+
DECLARE_INSTRUCTION(IntConstant);
private:
@@ -1276,6 +1330,8 @@ class HLongConstant : public HConstant {
return other->AsLongConstant()->value_ == value_;
}
+ virtual size_t ComputeHashCode() const { return static_cast<size_t>(GetValue()); }
+
DECLARE_INSTRUCTION(LongConstant);
private:
@@ -1545,6 +1601,10 @@ class HInstanceFieldGet : public HExpression<1> {
return other_offset == GetFieldOffset().SizeValue();
}
+ virtual size_t ComputeHashCode() const {
+ return (HInstruction::ComputeHashCode() << 7) | GetFieldOffset().SizeValue();
+ }
+
MemberOffset GetFieldOffset() const { return field_info_.GetFieldOffset(); }
Primitive::Type GetFieldType() const { return field_info_.GetFieldType(); }
diff --git a/compiler/optimizing/optimizing_compiler.cc b/compiler/optimizing/optimizing_compiler.cc
index 3ce8e7797b..702eba183c 100644
--- a/compiler/optimizing/optimizing_compiler.cc
+++ b/compiler/optimizing/optimizing_compiler.cc
@@ -25,6 +25,7 @@
#include "driver/compiler_driver.h"
#include "driver/dex_compilation_unit.h"
#include "graph_visualizer.h"
+#include "gvn.h"
#include "nodes.h"
#include "register_allocator.h"
#include "ssa_phi_elimination.h"
@@ -260,6 +261,8 @@ CompiledMethod* OptimizingCompiler::TryCompile(const DexFile::CodeItem* code_ite
SsaRedundantPhiElimination(graph).Run();
SsaDeadPhiElimination(graph).Run();
+ GlobalValueNumberer(graph->GetArena(), graph).Run();
+ visualizer.DumpGraph(kGVNPassName);
SsaLivenessAnalysis liveness(*graph, codegen);
liveness.Analyze();
@@ -300,6 +303,9 @@ CompiledMethod* OptimizingCompiler::TryCompile(const DexFile::CodeItem* code_ite
graph->TransformToSSA();
visualizer.DumpGraph("ssa");
graph->FindNaturalLoops();
+ SsaRedundantPhiElimination(graph).Run();
+ SsaDeadPhiElimination(graph).Run();
+ GlobalValueNumberer(graph->GetArena(), graph).Run();
SsaLivenessAnalysis liveness(*graph, codegen);
liveness.Analyze();
visualizer.DumpGraph(kLivenessPassName);
diff --git a/oatdump/oatdump.cc b/oatdump/oatdump.cc
index bac3c33022..87de52988c 100644
--- a/oatdump/oatdump.cc
+++ b/oatdump/oatdump.cc
@@ -780,10 +780,7 @@ class OatDumper {
oat_method.GetVmapTableOffsetOffset());
success = false;
} else if (options_->dump_vmap_) {
- if (oat_method.GetNativeGcMap() != nullptr) {
- // The native GC map is null for methods compiled with the optimizing compiler.
- DumpVmap(*indent2_os, oat_method);
- }
+ DumpVmap(*indent2_os, oat_method);
}
}
{
@@ -894,6 +891,12 @@ class OatDumper {
}
void DumpVmap(std::ostream& os, const OatFile::OatMethod& oat_method) {
+ // If the native GC map is null, then this method has been compiled with the
+ // optimizing compiler. The optimizing compiler currently outputs its stack map
+ // in the vmap table, and the code below does not work with such a stack map.
+ if (oat_method.GetNativeGcMap() == nullptr) {
+ return;
+ }
const uint8_t* raw_table = oat_method.GetVmapTable();
if (raw_table != nullptr) {
const VmapTable vmap_table(raw_table);
diff --git a/patchoat/patchoat.cc b/patchoat/patchoat.cc
index f89a4f7122..50b4ece69f 100644
--- a/patchoat/patchoat.cc
+++ b/patchoat/patchoat.cc
@@ -79,9 +79,10 @@ static bool LocationToFilename(const std::string& location, InstructionSet isa,
bool have_android_data = false;
bool dalvik_cache_exists = false;
+ bool is_global_cache = false;
std::string dalvik_cache;
GetDalvikCache(GetInstructionSetString(isa), false, &dalvik_cache,
- &have_android_data, &dalvik_cache_exists);
+ &have_android_data, &dalvik_cache_exists, &is_global_cache);
std::string cache_filename;
if (have_android_data && dalvik_cache_exists) {
@@ -986,9 +987,11 @@ static int patchoat(int argc, char **argv) {
std::string cache_filename;
bool has_cache = false;
bool has_android_data_unused = false;
+ bool is_global_cache = false;
if (!gc::space::ImageSpace::FindImageFilename(patched_image_location.c_str(), isa,
&system_filename, &has_system, &cache_filename,
- &has_android_data_unused, &has_cache)) {
+ &has_android_data_unused, &has_cache,
+ &is_global_cache)) {
Usage("Unable to determine image file for location %s", patched_image_location.c_str());
}
if (has_cache) {
diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc
index 1686c27fd4..cb0fe0a7dc 100644
--- a/runtime/class_linker.cc
+++ b/runtime/class_linker.cc
@@ -1323,8 +1323,9 @@ const OatFile* ClassLinker::OpenOatFileFromDexLocation(const std::string& dex_lo
std::string dalvik_cache;
bool have_android_data = false;
bool have_dalvik_cache = false;
+ bool is_global_cache = false;
GetDalvikCache(GetInstructionSetString(kRuntimeISA), false, &dalvik_cache,
- &have_android_data, &have_dalvik_cache);
+ &have_android_data, &have_dalvik_cache, &is_global_cache);
std::string cache_filename;
if (have_dalvik_cache) {
cache_filename = GetDalvikCacheFilenameOrDie(dex_location.c_str(), dalvik_cache.c_str());
diff --git a/runtime/gc/space/image_space.cc b/runtime/gc/space/image_space.cc
index 41c34c9bf5..353d00c3dd 100644
--- a/runtime/gc/space/image_space.cc
+++ b/runtime/gc/space/image_space.cc
@@ -125,7 +125,7 @@ static bool GenerateImage(const std::string& image_filename, InstructionSet imag
}
// We should clean up so we are more likely to have room for the image.
if (Runtime::Current()->IsZygote()) {
- LOG(INFO) << "Pruning dalvik-cache since we are relocating an image and will need to recompile";
+ LOG(INFO) << "Pruning dalvik-cache since we are generating an image and will need to recompile";
PruneDexCache(image_isa);
}
@@ -177,7 +177,8 @@ bool ImageSpace::FindImageFilename(const char* image_location,
bool* has_system,
std::string* cache_filename,
bool* dalvik_cache_exists,
- bool* has_cache) {
+ bool* has_cache,
+ bool* is_global_cache) {
*has_system = false;
*has_cache = false;
// image_location = /system/framework/boot.art
@@ -192,7 +193,7 @@ bool ImageSpace::FindImageFilename(const char* image_location,
*dalvik_cache_exists = false;
std::string dalvik_cache;
GetDalvikCache(GetInstructionSetString(image_isa), true, &dalvik_cache,
- &have_android_data, dalvik_cache_exists);
+ &have_android_data, dalvik_cache_exists, is_global_cache);
if (have_android_data && *dalvik_cache_exists) {
// Always set output location even if it does not exist,
@@ -285,8 +286,9 @@ ImageHeader* ImageSpace::ReadImageHeaderOrDie(const char* image_location,
std::string cache_filename;
bool has_cache = false;
bool dalvik_cache_exists = false;
+ bool is_global_cache = false;
if (FindImageFilename(image_location, image_isa, &system_filename, &has_system,
- &cache_filename, &dalvik_cache_exists, &has_cache)) {
+ &cache_filename, &dalvik_cache_exists, &has_cache, &is_global_cache)) {
if (Runtime::Current()->ShouldRelocate()) {
if (has_system && has_cache) {
std::unique_ptr<ImageHeader> sys_hdr(new ImageHeader);
@@ -344,6 +346,21 @@ static bool ChecksumsMatch(const char* image_a, const char* image_b) {
&& hdr_a.GetOatChecksum() == hdr_b.GetOatChecksum();
}
+static bool ImageCreationAllowed(bool is_global_cache, std::string* error_msg) {
+ // Anyone can write into a "local" cache.
+ if (!is_global_cache) {
+ return true;
+ }
+
+ // Only the zygote is allowed to create the global boot image.
+ if (Runtime::Current()->IsZygote()) {
+ return true;
+ }
+
+ *error_msg = "Only the zygote can create the global boot image.";
+ return false;
+}
+
ImageSpace* ImageSpace::Create(const char* image_location,
const InstructionSet image_isa,
std::string* error_msg) {
@@ -352,9 +369,10 @@ ImageSpace* ImageSpace::Create(const char* image_location,
std::string cache_filename;
bool has_cache = false;
bool dalvik_cache_exists = false;
+ bool is_global_cache = true;
const bool found_image = FindImageFilename(image_location, image_isa, &system_filename,
&has_system, &cache_filename, &dalvik_cache_exists,
- &has_cache);
+ &has_cache, &is_global_cache);
ImageSpace* space;
bool relocate = Runtime::Current()->ShouldRelocate();
@@ -377,18 +395,27 @@ ImageSpace* ImageSpace::Create(const char* image_location,
relocated_version_used = true;
} else {
// We cannot have a relocated version, Relocate the system one and use it.
- if (can_compile && RelocateImage(image_location, cache_filename.c_str(), image_isa,
- error_msg)) {
+
+ std::string reason;
+ bool success;
+
+ // Check whether we are allowed to relocate.
+ if (!can_compile) {
+ reason = "Image dex2oat disabled by -Xnoimage-dex2oat.";
+ success = false;
+ } else if (!ImageCreationAllowed(is_global_cache, &reason)) {
+ // Whether we can write to the cache.
+ success = false;
+ } else {
+ // Try to relocate.
+ success = RelocateImage(image_location, cache_filename.c_str(), image_isa, &reason);
+ }
+
+ if (success) {
relocated_version_used = true;
image_filename = &cache_filename;
} else {
- std::string reason;
- if (can_compile) {
- reason = StringPrintf(": %s", error_msg->c_str());
- } else {
- reason = " because image dex2oat is disabled.";
- }
- *error_msg = StringPrintf("Unable to relocate image '%s' from '%s' to '%s'%s",
+ *error_msg = StringPrintf("Unable to relocate image '%s' from '%s' to '%s': %s",
image_location, system_filename.c_str(),
cache_filename.c_str(), reason.c_str());
return nullptr;
@@ -460,6 +487,8 @@ ImageSpace* ImageSpace::Create(const char* image_location,
} else if (!dalvik_cache_exists) {
*error_msg = StringPrintf("No place to put generated image.");
return nullptr;
+ } else if (!ImageCreationAllowed(is_global_cache, error_msg)) {
+ return nullptr;
} else if (!GenerateImage(cache_filename, image_isa, error_msg)) {
*error_msg = StringPrintf("Failed to generate image '%s': %s",
cache_filename.c_str(), error_msg->c_str());
diff --git a/runtime/gc/space/image_space.h b/runtime/gc/space/image_space.h
index 28ebca6e94..2586ece4ab 100644
--- a/runtime/gc/space/image_space.h
+++ b/runtime/gc/space/image_space.h
@@ -110,7 +110,8 @@ class ImageSpace : public MemMapSpace {
bool* has_system,
std::string* data_location,
bool* dalvik_cache_exists,
- bool* has_data);
+ bool* has_data,
+ bool *is_global_cache);
private:
// Tries to initialize an ImageSpace from the given image path,
diff --git a/runtime/jdwp/jdwp_event.cc b/runtime/jdwp/jdwp_event.cc
index 46db63ccd5..d61660bca7 100644
--- a/runtime/jdwp/jdwp_event.cc
+++ b/runtime/jdwp/jdwp_event.cc
@@ -297,9 +297,6 @@ void JdwpState::UnregisterEvent(JdwpEvent* pEvent) {
/*
* Remove the event with the given ID from the list.
*
- * Failure to find the event isn't really an error, but it is a little
- * weird. (It looks like Eclipse will try to be extra careful and will
- * explicitly remove one-off single-step events.)
*/
void JdwpState::UnregisterEventById(uint32_t requestId) {
bool found = false;
@@ -319,7 +316,11 @@ void JdwpState::UnregisterEventById(uint32_t requestId) {
if (found) {
Dbg::ManageDeoptimization();
} else {
- LOG(WARNING) << StringPrintf("Odd: no match when removing event reqId=0x%04x", requestId);
+ // Failure to find the event isn't really an error. For instance, it looks like Eclipse will
+ // try to be extra careful and will explicitly remove one-off single-step events (using a
+ // 'count' event modifier of 1). So the event may have already been removed as part of the
+ // event notification (see JdwpState::CleanupMatchList).
+ VLOG(jdwp) << StringPrintf("No match when removing event reqId=0x%04x", requestId);
}
}
diff --git a/runtime/native/dalvik_system_DexFile.cc b/runtime/native/dalvik_system_DexFile.cc
index ff9dc38e43..003815e7b9 100644
--- a/runtime/native/dalvik_system_DexFile.cc
+++ b/runtime/native/dalvik_system_DexFile.cc
@@ -504,7 +504,9 @@ static jbyte IsDexOptNeededInternal(JNIEnv* env, const char* filename,
std::string cache_dir;
bool have_android_data = false;
bool dalvik_cache_exists = false;
- GetDalvikCache(instruction_set, false, &cache_dir, &have_android_data, &dalvik_cache_exists);
+ bool is_global_cache = false;
+ GetDalvikCache(instruction_set, false, &cache_dir, &have_android_data, &dalvik_cache_exists,
+ &is_global_cache);
std::string cache_filename; // was cache_location
bool have_cache_filename = false;
if (dalvik_cache_exists) {
diff --git a/runtime/runtime.cc b/runtime/runtime.cc
index de8634fafe..8386cc0613 100644
--- a/runtime/runtime.cc
+++ b/runtime/runtime.cc
@@ -564,13 +564,15 @@ static bool OpenDexFilesFromImage(const std::vector<std::string>& dex_filenames,
std::string cache_filename_unused;
bool dalvik_cache_exists_unused;
bool has_cache_unused;
+ bool is_global_cache_unused;
bool found_image = gc::space::ImageSpace::FindImageFilename(image_location.c_str(),
kRuntimeISA,
&system_filename,
&has_system,
&cache_filename_unused,
&dalvik_cache_exists_unused,
- &has_cache_unused);
+ &has_cache_unused,
+ &is_global_cache_unused);
*failures = 0;
if (!found_image || !has_system) {
return false;
diff --git a/runtime/thread_list.cc b/runtime/thread_list.cc
index 08e66ea61d..ec5b7754d6 100644
--- a/runtime/thread_list.cc
+++ b/runtime/thread_list.cc
@@ -728,11 +728,14 @@ void ThreadList::SuspendSelfForDebugger() {
Thread::resume_cond_->Wait(self);
if (self->GetSuspendCount() != 0) {
// The condition was signaled but we're still suspended. This
- // can happen if the debugger lets go while a SIGQUIT thread
+ // can happen when we suspend then resume all threads to
+ // update instrumentation or compute monitor info. This can
+ // also happen if the debugger lets go while a SIGQUIT thread
// dump event is pending (assuming SignalCatcher was resumed for
// just long enough to try to grab the thread-suspend lock).
- LOG(WARNING) << *self << " still suspended after undo "
- << "(suspend count=" << self->GetSuspendCount() << ")";
+ VLOG(jdwp) << *self << " still suspended after undo "
+ << "(suspend count=" << self->GetSuspendCount() << ", "
+ << "debug suspend count=" << self->GetDebugSuspendCount() << ")";
}
}
CHECK_EQ(self->GetSuspendCount(), 0);
diff --git a/runtime/utils.cc b/runtime/utils.cc
index 6135e5d844..9157f6c9bf 100644
--- a/runtime/utils.cc
+++ b/runtime/utils.cc
@@ -1232,13 +1232,14 @@ const char* GetAndroidDataSafe(std::string* error_msg) {
}
void GetDalvikCache(const char* subdir, const bool create_if_absent, std::string* dalvik_cache,
- bool* have_android_data, bool* dalvik_cache_exists) {
+ bool* have_android_data, bool* dalvik_cache_exists, bool* is_global_cache) {
CHECK(subdir != nullptr);
std::string error_msg;
const char* android_data = GetAndroidDataSafe(&error_msg);
if (android_data == nullptr) {
*have_android_data = false;
*dalvik_cache_exists = false;
+ *is_global_cache = false;
return;
} else {
*have_android_data = true;
@@ -1246,7 +1247,8 @@ void GetDalvikCache(const char* subdir, const bool create_if_absent, std::string
const std::string dalvik_cache_root(StringPrintf("%s/dalvik-cache/", android_data));
*dalvik_cache = dalvik_cache_root + subdir;
*dalvik_cache_exists = OS::DirectoryExists(dalvik_cache->c_str());
- if (create_if_absent && !*dalvik_cache_exists && strcmp(android_data, "/data") != 0) {
+ *is_global_cache = strcmp(android_data, "/data") == 0;
+ if (create_if_absent && !*dalvik_cache_exists && !*is_global_cache) {
// Don't create the system's /data/dalvik-cache/... because it needs special permissions.
*dalvik_cache_exists = ((mkdir(dalvik_cache_root.c_str(), 0700) == 0 || errno == EEXIST) &&
(mkdir(dalvik_cache->c_str(), 0700) == 0 || errno == EEXIST));
diff --git a/runtime/utils.h b/runtime/utils.h
index 50462b11b3..9ec6db1e52 100644
--- a/runtime/utils.h
+++ b/runtime/utils.h
@@ -449,8 +449,9 @@ std::string GetDalvikCacheOrDie(const char* subdir, bool create_if_absent = true
// Return true if we found the dalvik cache and stored it in the dalvik_cache argument.
// have_android_data will be set to true if we have an ANDROID_DATA that exists,
// dalvik_cache_exists will be true if there is a dalvik-cache directory that is present.
+// The flag is_global_cache tells whether this cache is /data/dalvik-cache.
void GetDalvikCache(const char* subdir, bool create_if_absent, std::string* dalvik_cache,
- bool* have_android_data, bool* dalvik_cache_exists);
+ bool* have_android_data, bool* dalvik_cache_exists, bool* is_global_cache);
// Returns the absolute dalvik-cache path for a DexFile or OatFile. The path returned will be
// rooted at cache_location.