Add operator<< for HGraph and HInstructions.
Include helpers for printing arguments as well.
Test: ./test.py --host
Change-Id: I692fd5bd32a8a39da0defd9454d56ccf2480f229
diff --git a/compiler/Android.bp b/compiler/Android.bp
index 523aab6..f4217eb 100644
--- a/compiler/Android.bp
+++ b/compiler/Android.bp
@@ -37,6 +37,7 @@
"jni/quick/calling_convention.cc",
"jni/quick/jni_compiler.cc",
"optimizing/block_builder.cc",
+ "optimizing/block_namer.cc",
"optimizing/bounds_check_elimination.cc",
"optimizing/builder.cc",
"optimizing/cha_guard_optimization.cc",
diff --git a/compiler/optimizing/block_namer.cc b/compiler/optimizing/block_namer.cc
new file mode 100644
index 0000000..9936bf1
--- /dev/null
+++ b/compiler/optimizing/block_namer.cc
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2021 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 "block_namer.h"
+
+#include "nodes.h"
+
+namespace art {
+
+std::ostream& BlockNamer::PrintName(std::ostream& os, HBasicBlock* blk) const {
+ return os << "B" << blk->GetBlockId();
+}
+
+} // namespace art
diff --git a/compiler/optimizing/block_namer.h b/compiler/optimizing/block_namer.h
new file mode 100644
index 0000000..ed396b9
--- /dev/null
+++ b/compiler/optimizing/block_namer.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2021 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_BLOCK_NAMER_H_
+#define ART_COMPILER_OPTIMIZING_BLOCK_NAMER_H_
+
+#include <ostream>
+
+namespace art {
+class HBasicBlock;
+
+struct BlockNamer {
+ public:
+ struct NameWrapper {
+ HBasicBlock* blk_;
+ const BlockNamer& namer_;
+ };
+ virtual ~BlockNamer() {}
+ virtual std::ostream& PrintName(std::ostream& os, HBasicBlock* blk) const;
+ NameWrapper GetName(HBasicBlock* blk) const {
+ return NameWrapper{blk, *this};
+ }
+};
+
+inline std::ostream& operator<<(std::ostream& os, const BlockNamer::NameWrapper& blk) {
+ return blk.namer_.PrintName(os, blk.blk_);
+}
+
+} // namespace art
+
+#endif // ART_COMPILER_OPTIMIZING_BLOCK_NAMER_H_
diff --git a/compiler/optimizing/graph_visualizer.cc b/compiler/optimizing/graph_visualizer.cc
index 44a64c1..da34af2 100644
--- a/compiler/optimizing/graph_visualizer.cc
+++ b/compiler/optimizing/graph_visualizer.cc
@@ -44,6 +44,10 @@
namespace art {
+// Unique pass-name to identify that the dump is for printing to log.
+constexpr const char* kDebugDumpName = "debug";
+constexpr const char* kDebugDumpGraphName = "debug_graph";
+
using android::base::StringPrintf;
static bool HasWhitespace(const char* str) {
@@ -196,6 +200,7 @@
bool is_after_pass,
bool graph_in_bad_state,
const CodeGenerator* codegen,
+ const BlockNamer& namer,
const DisassemblyInformation* disasm_info = nullptr)
: HGraphDelegateVisitor(graph),
output_(output),
@@ -204,6 +209,7 @@
graph_in_bad_state_(graph_in_bad_state),
codegen_(codegen),
disasm_info_(disasm_info),
+ namer_(namer),
disassembler_(disasm_info_ != nullptr
? new HGraphVisualizerDisassembler(
codegen_->GetInstructionSet(),
@@ -231,6 +237,11 @@
output_ << "end_" << name << "\n";
}
+ void PrintProperty(const char* name, HBasicBlock* blk) {
+ AddIndent();
+ output_ << name << " \"" << namer_.GetName(blk) << "\"\n";
+ }
+
void PrintProperty(const char* name, const char* property) {
AddIndent();
output_ << name << " \"" << property << "\"\n";
@@ -266,7 +277,7 @@
AddIndent();
output_ << "predecessors";
for (HBasicBlock* predecessor : block->GetPredecessors()) {
- output_ << " \"B" << predecessor->GetBlockId() << "\" ";
+ output_ << " \"" << namer_.GetName(predecessor) << "\" ";
}
if (block->IsEntryBlock() && (disasm_info_ != nullptr)) {
output_ << " \"" << kDisassemblyBlockFrameEntry << "\" ";
@@ -278,20 +289,24 @@
AddIndent();
output_ << "successors";
for (HBasicBlock* successor : block->GetNormalSuccessors()) {
- output_ << " \"B" << successor->GetBlockId() << "\" ";
+ output_ << " \"" << namer_.GetName(successor) << "\" ";
}
output_<< "\n";
}
void PrintExceptionHandlers(HBasicBlock* block) {
+ bool has_slow_paths = block->IsExitBlock() &&
+ (disasm_info_ != nullptr) &&
+ !disasm_info_->GetSlowPathIntervals().empty();
+ if (IsDebugDump() && block->GetExceptionalSuccessors().empty() && !has_slow_paths) {
+ return;
+ }
AddIndent();
output_ << "xhandlers";
for (HBasicBlock* handler : block->GetExceptionalSuccessors()) {
- output_ << " \"B" << handler->GetBlockId() << "\" ";
+ output_ << " \"" << namer_.GetName(handler) << "\" ";
}
- if (block->IsExitBlock() &&
- (disasm_info_ != nullptr) &&
- !disasm_info_->GetSlowPathIntervals().empty()) {
+ if (has_slow_paths) {
output_ << " \"" << kDisassemblyBlockSlowPaths << "\" ";
}
output_<< "\n";
@@ -393,11 +408,10 @@
void VisitLoadClass(HLoadClass* load_class) override {
StartAttributeStream("load_kind") << load_class->GetLoadKind();
- const char* descriptor = load_class->GetDexFile().GetTypeDescriptor(
- load_class->GetDexFile().GetTypeId(load_class->GetTypeIndex()));
- StartAttributeStream("class_name") << PrettyDescriptor(descriptor);
- StartAttributeStream("gen_clinit_check") << std::boolalpha
- << load_class->MustGenerateClinitCheck() << std::noboolalpha;
+ StartAttributeStream("class_name")
+ << load_class->GetDexFile().PrettyType(load_class->GetTypeIndex());
+ StartAttributeStream("gen_clinit_check")
+ << std::boolalpha << load_class->MustGenerateClinitCheck() << std::noboolalpha;
StartAttributeStream("needs_access_check") << std::boolalpha
<< load_class->NeedsAccessCheck() << std::noboolalpha;
}
@@ -410,8 +424,13 @@
void VisitLoadMethodType(HLoadMethodType* load_method_type) override {
StartAttributeStream("load_kind") << "RuntimeCall";
const DexFile& dex_file = load_method_type->GetDexFile();
- const dex::ProtoId& proto_id = dex_file.GetProtoId(load_method_type->GetProtoIndex());
- StartAttributeStream("method_type") << dex_file.GetProtoSignature(proto_id);
+ if (dex_file.NumProtoIds() >= load_method_type->GetProtoIndex().index_) {
+ const dex::ProtoId& proto_id = dex_file.GetProtoId(load_method_type->GetProtoIndex());
+ StartAttributeStream("method_type") << dex_file.GetProtoSignature(proto_id);
+ } else {
+ StartAttributeStream("method_type")
+ << "<<Unknown proto-idx: " << load_method_type->GetProtoIndex() << ">>";
+ }
}
void VisitLoadString(HLoadString* load_string) override {
@@ -609,6 +628,10 @@
return strcmp(pass_name_, name) == 0;
}
+ bool IsDebugDump() {
+ return IsPass(kDebugDumpGraphName) || IsPass(kDebugDumpName);
+ }
+
void PrintInstruction(HInstruction* instruction) {
output_ << instruction->DebugName();
HConstInputsRef inputs = instruction->GetInputs();
@@ -624,6 +647,10 @@
} else {
StartAttributeStream("dex_pc") << "n/a";
}
+ if (IsPass(kDebugDumpName)) {
+ // Include block name for logcat use.
+ StartAttributeStream("block") << namer_.GetName(instruction->GetBlock());
+ }
instruction->Accept(this);
if (instruction->HasEnvironment()) {
StringList envs;
@@ -678,10 +705,10 @@
if (loop_info == nullptr) {
StartAttributeStream("loop") << "none";
} else {
- StartAttributeStream("loop") << "B" << loop_info->GetHeader()->GetBlockId();
+ StartAttributeStream("loop") << namer_.GetName(loop_info->GetHeader());
HLoopInformation* outer = loop_info->GetPreHeader()->GetLoopInformation();
if (outer != nullptr) {
- StartAttributeStream("outer_loop") << "B" << outer->GetHeader()->GetBlockId();
+ StartAttributeStream("outer_loop") << namer_.GetName(outer->GetHeader());
} else {
StartAttributeStream("outer_loop") << "none";
}
@@ -692,7 +719,8 @@
// For the builder and the inliner, we want to add extra information on HInstructions
// that have reference types, and also HInstanceOf/HCheckcast.
if ((IsPass(HGraphBuilder::kBuilderPassName)
- || IsPass(HInliner::kInlinerPassName))
+ || IsPass(HInliner::kInlinerPassName)
+ || IsDebugDump())
&& (instruction->GetType() == DataType::Type::kReference ||
instruction->IsInstanceOf() ||
instruction->IsCheckCast())) {
@@ -722,6 +750,7 @@
// doesn't run or doesn't inline anything, the NullConstant remains untyped.
// So we should check NullConstants for validity only after reference type propagation.
DCHECK(graph_in_bad_state_ ||
+ IsDebugDump() ||
(!is_after_pass_ && IsPass(HGraphBuilder::kBuilderPassName)))
<< instruction->DebugName() << instruction->GetId() << " has invalid rti "
<< (is_after_pass_ ? "after" : "before") << " pass " << pass_name_;
@@ -821,12 +850,13 @@
void Run() {
StartTag("cfg");
- std::string pass_desc = std::string(pass_name_)
- + " ("
- + (is_after_pass_ ? "after" : "before")
- + (graph_in_bad_state_ ? ", bad_state" : "")
- + ")";
- PrintProperty("name", pass_desc.c_str());
+ std::ostringstream oss;
+ oss << pass_name_;
+ if (!IsDebugDump()) {
+ oss << " (" << (is_after_pass_ ? "after" : "before")
+ << (graph_in_bad_state_ ? ", bad_state" : "") << ")";
+ }
+ PrintProperty("name", oss.str().c_str());
if (disasm_info_ != nullptr) {
DumpDisassemblyBlockForFrameEntry();
}
@@ -846,12 +876,13 @@
void VisitBasicBlock(HBasicBlock* block) override {
StartTag("block");
- PrintProperty("name", "B", block->GetBlockId());
+ PrintProperty("name", block);
if (block->GetLifetimeStart() != kNoLifetime) {
// Piggy back on these fields to show the lifetime of the block.
PrintInt("from_bci", block->GetLifetimeStart());
PrintInt("to_bci", block->GetLifetimeEnd());
- } else {
+ } else if (!IsDebugDump()) {
+ // Don't print useless information to logcat.
PrintInt("from_bci", -1);
PrintInt("to_bci", -1);
}
@@ -861,30 +892,33 @@
if (block->IsCatchBlock()) {
PrintProperty("flags", "catch_block");
- } else {
+ } else if (!IsDebugDump()) {
+ // Don't print useless information to logcat
PrintEmptyProperty("flags");
}
if (block->GetDominator() != nullptr) {
- PrintProperty("dominator", "B", block->GetDominator()->GetBlockId());
+ PrintProperty("dominator", block->GetDominator());
}
- StartTag("states");
- StartTag("locals");
- PrintInt("size", 0);
- PrintProperty("method", "None");
- for (HInstructionIterator it(block->GetPhis()); !it.Done(); it.Advance()) {
- AddIndent();
- HInstruction* instruction = it.Current();
- output_ << instruction->GetId() << " " << DataType::TypeId(instruction->GetType())
- << instruction->GetId() << "[ ";
- for (const HInstruction* input : instruction->GetInputs()) {
- output_ << input->GetId() << " ";
+ if (!IsDebugDump() || !block->GetPhis().IsEmpty()) {
+ StartTag("states");
+ StartTag("locals");
+ PrintInt("size", 0);
+ PrintProperty("method", "None");
+ for (HInstructionIterator it(block->GetPhis()); !it.Done(); it.Advance()) {
+ AddIndent();
+ HInstruction* instruction = it.Current();
+ output_ << instruction->GetId() << " " << DataType::TypeId(instruction->GetType())
+ << instruction->GetId() << "[ ";
+ for (const HInstruction* input : instruction->GetInputs()) {
+ output_ << input->GetId() << " ";
+ }
+ output_ << "]\n";
}
- output_ << "]\n";
+ EndTag("locals");
+ EndTag("states");
}
- EndTag("locals");
- EndTag("states");
StartTag("HIR");
PrintInstructions(block->GetPhis());
@@ -904,20 +938,31 @@
const bool graph_in_bad_state_;
const CodeGenerator* codegen_;
const DisassemblyInformation* disasm_info_;
+ const BlockNamer& namer_;
std::unique_ptr<HGraphVisualizerDisassembler> disassembler_;
size_t indent_;
DISALLOW_COPY_AND_ASSIGN(HGraphVisualizerPrinter);
};
+std::ostream& HGraphVisualizer::OptionalDefaultNamer::PrintName(std::ostream& os,
+ HBasicBlock* blk) const {
+ if (namer_) {
+ return namer_->get().PrintName(os, blk);
+ } else {
+ return BlockNamer::PrintName(os, blk);
+ }
+}
+
HGraphVisualizer::HGraphVisualizer(std::ostream* output,
HGraph* graph,
- const CodeGenerator* codegen)
- : output_(output), graph_(graph), codegen_(codegen) {}
+ const CodeGenerator* codegen,
+ std::optional<std::reference_wrapper<const BlockNamer>> namer)
+ : output_(output), graph_(graph), codegen_(codegen), namer_(namer) {}
void HGraphVisualizer::PrintHeader(const char* method_name) const {
DCHECK(output_ != nullptr);
- HGraphVisualizerPrinter printer(graph_, *output_, "", true, false, codegen_);
+ HGraphVisualizerPrinter printer(graph_, *output_, "", true, false, codegen_, namer_);
printer.StartTag("compilation");
printer.PrintProperty("name", method_name);
printer.PrintProperty("method", method_name);
@@ -939,6 +984,12 @@
time_str.c_str());
}
+void HGraphVisualizer::DumpGraphDebug() const {
+ DumpGraph(/* pass_name= */ kDebugDumpGraphName,
+ /* is_after_pass= */ false,
+ /* graph_in_bad_state= */ true);
+}
+
void HGraphVisualizer::DumpGraph(const char* pass_name,
bool is_after_pass,
bool graph_in_bad_state) const {
@@ -949,7 +1000,8 @@
pass_name,
is_after_pass,
graph_in_bad_state,
- codegen_);
+ codegen_,
+ namer_);
printer.Run();
}
}
@@ -963,6 +1015,7 @@
/* is_after_pass= */ true,
/* graph_in_bad_state= */ false,
codegen_,
+ namer_,
codegen_->GetDisassemblyInformation());
printer.Run();
}
@@ -971,12 +1024,14 @@
void HGraphVisualizer::DumpInstruction(std::ostream* output,
HGraph* graph,
HInstruction* instruction) {
+ BlockNamer namer;
HGraphVisualizerPrinter printer(graph,
*output,
- /* pass_name= */ "debug",
+ /* pass_name= */ kDebugDumpName,
/* is_after_pass= */ false,
/* graph_in_bad_state= */ false,
- /* codegen= */ nullptr);
+ /* codegen= */ nullptr,
+ /* namer= */ namer);
printer.Run(instruction);
}
diff --git a/compiler/optimizing/graph_visualizer.h b/compiler/optimizing/graph_visualizer.h
index b83e887..3429c11 100644
--- a/compiler/optimizing/graph_visualizer.h
+++ b/compiler/optimizing/graph_visualizer.h
@@ -17,11 +17,13 @@
#ifndef ART_COMPILER_OPTIMIZING_GRAPH_VISUALIZER_H_
#define ART_COMPILER_OPTIMIZING_GRAPH_VISUALIZER_H_
+#include <functional>
#include <ostream>
#include "arch/instruction_set.h"
#include "base/arena_containers.h"
#include "base/value_object.h"
+#include "block_namer.h"
namespace art {
@@ -101,10 +103,12 @@
public:
HGraphVisualizer(std::ostream* output,
HGraph* graph,
- const CodeGenerator* codegen);
+ const CodeGenerator* codegen,
+ std::optional<std::reference_wrapper<const BlockNamer>> namer = std::nullopt);
void PrintHeader(const char* method_name) const;
void DumpGraph(const char* pass_name, bool is_after_pass, bool graph_in_bad_state) const;
+ void DumpGraphDebug() const;
void DumpGraphWithDisassembly() const;
// C1visualizer file format does not support inserting arbitrary metadata into a cfg
@@ -115,9 +119,21 @@
static void DumpInstruction(std::ostream* output, HGraph* graph, HInstruction* instruction);
private:
+ class OptionalDefaultNamer final : public BlockNamer {
+ public:
+ explicit OptionalDefaultNamer(std::optional<std::reference_wrapper<const BlockNamer>> inner)
+ : namer_(inner) {}
+
+ std::ostream& PrintName(std::ostream& os, HBasicBlock* blk) const override;
+
+ private:
+ std::optional<std::reference_wrapper<const BlockNamer>> namer_;
+ };
+
std::ostream* const output_;
HGraph* const graph_;
const CodeGenerator* codegen_;
+ OptionalDefaultNamer namer_;
DISALLOW_COPY_AND_ASSIGN(HGraphVisualizer);
};
diff --git a/compiler/optimizing/nodes.cc b/compiler/optimizing/nodes.cc
index d57eaf0..e815474 100644
--- a/compiler/optimizing/nodes.cc
+++ b/compiler/optimizing/nodes.cc
@@ -17,6 +17,7 @@
#include <algorithm>
#include <cfloat>
+#include <functional>
#include "art_method-inl.h"
#include "base/arena_allocator.h"
@@ -1955,6 +1956,58 @@
return os;
}
+std::ostream& operator<<(std::ostream& os, const HInstruction::NoArgsDump rhs) {
+ // TODO Really this should be const but that would require const-ifying
+ // graph-visualizer and HGraphVisitor which are tangled up everywhere.
+ return const_cast<HInstruction*>(rhs.ins)->Dump(os, /* dump_args= */ false);
+}
+
+std::ostream& operator<<(std::ostream& os, const HInstruction::ArgsDump rhs) {
+ // TODO Really this should be const but that would require const-ifying
+ // graph-visualizer and HGraphVisitor which are tangled up everywhere.
+ return const_cast<HInstruction*>(rhs.ins)->Dump(os, /* dump_args= */ true);
+}
+
+std::ostream& operator<<(std::ostream& os, const HInstruction& rhs) {
+ return os << rhs.DumpWithoutArgs();
+}
+
+std::ostream& operator<<(std::ostream& os, const HUseList<HInstruction*>& lst) {
+ os << "Instructions[";
+ bool first = true;
+ for (const auto& hi : lst) {
+ if (!first) {
+ os << ", ";
+ }
+ first = false;
+ os << hi.GetUser()->DebugName() << "[id: " << hi.GetUser()->GetId()
+ << ", blk: " << hi.GetUser()->GetBlock()->GetBlockId() << "]@" << hi.GetIndex();
+ }
+ os << "]";
+ return os;
+}
+
+std::ostream& operator<<(std::ostream& os, const HUseList<HEnvironment*>& lst) {
+ os << "Environments[";
+ bool first = true;
+ for (const auto& hi : lst) {
+ if (!first) {
+ os << ", ";
+ }
+ first = false;
+ os << *hi.GetUser()->GetHolder() << "@" << hi.GetIndex();
+ }
+ os << "]";
+ return os;
+}
+
+std::ostream& HGraph::Dump(std::ostream& os,
+ std::optional<std::reference_wrapper<const BlockNamer>> namer) {
+ HGraphVisualizer vis(&os, this, nullptr, namer);
+ vis.DumpGraphDebug();
+ return os;
+}
+
void HInstruction::MoveBefore(HInstruction* cursor, bool do_checks) {
if (do_checks) {
DCHECK(!IsPhi());
diff --git a/compiler/optimizing/nodes.h b/compiler/optimizing/nodes.h
index 125f86b..73db7e5 100644
--- a/compiler/optimizing/nodes.h
+++ b/compiler/optimizing/nodes.h
@@ -33,6 +33,7 @@
#include "base/stl_util.h"
#include "base/transform_array_ref.h"
#include "art_method.h"
+#include "block_namer.h"
#include "class_root.h"
#include "compilation_kind.h"
#include "data_type.h"
@@ -423,6 +424,9 @@
blocks_.reserve(kDefaultNumberOfBlocks);
}
+ std::ostream& Dump(std::ostream& os,
+ std::optional<std::reference_wrapper<const BlockNamer>> namer = std::nullopt);
+
ArenaAllocator* GetAllocator() const { return allocator_; }
ArenaStack* GetArenaStack() const { return arena_stack_; }
@@ -881,6 +885,10 @@
DISALLOW_COPY_AND_ASSIGN(HGraph);
};
+inline std::ostream& operator<<(std::ostream& os, HGraph& graph) {
+ return graph.Dump(os);
+}
+
class HLoopInformation : public ArenaObject<kArenaAllocLoopInfo> {
public:
HLoopInformation(HBasicBlock* header, HGraph* graph)
@@ -2112,6 +2120,8 @@
DISALLOW_COPY_AND_ASSIGN(HEnvironment);
};
+std::ostream& operator<<(std::ostream& os, const HInstruction& rhs);
+
class HInstruction : public ArenaObject<kArenaAllocInstruction> {
public:
#define DECLARE_KIND(type, super) k##type,
@@ -2147,6 +2157,21 @@
std::ostream& Dump(std::ostream& os, bool dump_args = false);
+ // Helper for dumping without argument information using operator<<
+ struct NoArgsDump {
+ const HInstruction* ins;
+ };
+ NoArgsDump DumpWithoutArgs() const {
+ return NoArgsDump{this};
+ }
+ // Helper for dumping with argument information using operator<<
+ struct ArgsDump {
+ const HInstruction* ins;
+ };
+ ArgsDump DumpWithArgs() const {
+ return ArgsDump{this};
+ }
+
HInstruction* GetNext() const { return next_; }
HInstruction* GetPrevious() const { return previous_; }
@@ -2672,6 +2697,10 @@
friend class HInstructionList;
};
std::ostream& operator<<(std::ostream& os, HInstruction::InstructionKind rhs);
+std::ostream& operator<<(std::ostream& os, const HInstruction::NoArgsDump rhs);
+std::ostream& operator<<(std::ostream& os, const HInstruction::ArgsDump rhs);
+std::ostream& operator<<(std::ostream& os, const HUseList<HInstruction*>& lst);
+std::ostream& operator<<(std::ostream& os, const HUseList<HEnvironment*>& lst);
// Iterates over the instructions, while preserving the next instruction
// in case the current instruction gets removed from the list by the user