diff options
-rw-r--r-- | compiler/Android.bp | 1 | ||||
-rw-r--r-- | compiler/optimizing/block_namer.cc | 27 | ||||
-rw-r--r-- | compiler/optimizing/block_namer.h | 44 | ||||
-rw-r--r-- | compiler/optimizing/graph_visualizer.cc | 147 | ||||
-rw-r--r-- | compiler/optimizing/graph_visualizer.h | 18 | ||||
-rw-r--r-- | compiler/optimizing/nodes.cc | 53 | ||||
-rw-r--r-- | compiler/optimizing/nodes.h | 29 |
7 files changed, 272 insertions, 47 deletions
diff --git a/compiler/Android.bp b/compiler/Android.bp index 523aab6a25..f4217eb42a 100644 --- a/compiler/Android.bp +++ b/compiler/Android.bp @@ -37,6 +37,7 @@ art_cc_defaults { "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 0000000000..9936bf1c48 --- /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 0000000000..ed396b9bf8 --- /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 44a64c1d51..da34af28a5 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 @@ class HGraphVisualizerPrinter : public HGraphDelegateVisitor { 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 @@ class HGraphVisualizerPrinter : public HGraphDelegateVisitor { 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 @@ class HGraphVisualizerPrinter : public HGraphDelegateVisitor { 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 @@ class HGraphVisualizerPrinter : public HGraphDelegateVisitor { 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 @@ class HGraphVisualizerPrinter : public HGraphDelegateVisitor { 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 @@ class HGraphVisualizerPrinter : public HGraphDelegateVisitor { 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 @@ class HGraphVisualizerPrinter : public HGraphDelegateVisitor { 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 @@ class HGraphVisualizerPrinter : public HGraphDelegateVisitor { 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 @@ class HGraphVisualizerPrinter : public HGraphDelegateVisitor { } 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 @@ class HGraphVisualizerPrinter : public HGraphDelegateVisitor { 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 @@ class HGraphVisualizerPrinter : public HGraphDelegateVisitor { // 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 @@ class HGraphVisualizerPrinter : public HGraphDelegateVisitor { // 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 @@ class HGraphVisualizerPrinter : public HGraphDelegateVisitor { 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 @@ class HGraphVisualizerPrinter : public HGraphDelegateVisitor { 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 @@ class HGraphVisualizerPrinter : public HGraphDelegateVisitor { 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 @@ class HGraphVisualizerPrinter : public HGraphDelegateVisitor { 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 @@ std::string HGraphVisualizer::InsertMetaDataAsCompilationBlock(const std::string 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 @@ void HGraphVisualizer::DumpGraph(const char* pass_name, pass_name, is_after_pass, graph_in_bad_state, - codegen_); + codegen_, + namer_); printer.Run(); } } @@ -963,6 +1015,7 @@ void HGraphVisualizer::DumpGraphWithDisassembly() const { /* is_after_pass= */ true, /* graph_in_bad_state= */ false, codegen_, + namer_, codegen_->GetDisassemblyInformation()); printer.Run(); } @@ -971,12 +1024,14 @@ void HGraphVisualizer::DumpGraphWithDisassembly() const { 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 b83e88740c..3429c11cbd 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 @@ class HGraphVisualizer : public ValueObject { 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 @@ class HGraphVisualizer : public ValueObject { 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 d57eaf0b60..e815474b6e 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 @@ std::ostream& operator<<(std::ostream& os, HInstruction::InstructionKind rhs) { 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 125f86be81..73db7e541f 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 @@ class HGraph : public ArenaObject<kArenaAllocGraph> { 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 @@ class HGraph : public ArenaObject<kArenaAllocGraph> { 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 @@ class HEnvironment : public ArenaObject<kArenaAllocEnvironment> { 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 @@ class HInstruction : public ArenaObject<kArenaAllocInstruction> { 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 @@ class HInstruction : public ArenaObject<kArenaAllocInstruction> { 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 |