Index: llvm/include/llvm/Analysis/CFGPrinter.h =================================================================== --- llvm/include/llvm/Analysis/CFGPrinter.h +++ llvm/include/llvm/Analysis/CFGPrinter.h @@ -53,6 +53,9 @@ template<> struct DOTGraphTraits : public DefaultDOTGraphTraits { + // Cache for is hidden property + llvm::DenseMap isHiddenBasicBlock; + DOTGraphTraits (bool isSimple=false) : DefaultDOTGraphTraits(isSimple) {} static std::string getGraphName(const Function *F) { @@ -173,6 +176,8 @@ // profile count (due to scaling). return ("label=\"W:" + Twine(Weight->getZExtValue()) + "\"").str(); } + bool isNodeHidden(const BasicBlock *Node); + void computeHiddenNodes(const Function *F); }; } // End llvm namespace Index: llvm/lib/Analysis/CFGPrinter.cpp =================================================================== --- llvm/lib/Analysis/CFGPrinter.cpp +++ llvm/lib/Analysis/CFGPrinter.cpp @@ -17,11 +17,14 @@ // //===----------------------------------------------------------------------===// +#include "llvm/ADT/PostOrderIterator.h" #include "llvm/Analysis/CFGPrinter.h" #include "llvm/InitializePasses.h" #include "llvm/Pass.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/FileSystem.h" +#include + using namespace llvm; static cl::opt CFGFuncName( @@ -33,6 +36,12 @@ "cfg-dot-filename-prefix", cl::Hidden, cl::desc("The prefix used for the CFG dot file names.")); +static cl::opt HideUnreachablePaths("cfg-hide-unreachable-paths", + cl::init(false)); + +static cl::opt HideDeoptimizePaths("cfg-hide-deoptimize-paths", + cl::init(false)); + namespace { struct CFGViewerLegacyPass : public FunctionPass { static char ID; // Pass identifcation, replacement for typeid @@ -200,3 +209,30 @@ return new CFGOnlyPrinterLegacyPass(); } +void DOTGraphTraits::computeHiddenNodes(const Function *F) { + auto evaluateBB = [&](const BasicBlock *Node) { + if (succ_begin(Node) == succ_end(Node)) { + const Instruction *TI = Node->getTerminator(); + isHiddenBasicBlock[Node] = + (HideUnreachablePaths && isa(TI)) || + (HideDeoptimizePaths && Node->getTerminatingDeoptimizeCall()); + return; + } + isHiddenBasicBlock[Node] = std::all_of( + succ_begin(Node), succ_end(Node), + [this](const BasicBlock *BB) { return isHiddenBasicBlock[BB]; }); + }; + /// The post order traversal iteration is done to know the status of + /// isHiddenBasicBlock for all the successors on the current BB. + for_each(po_begin(&F->getEntryBlock()), po_end(&F->getEntryBlock()), + evaluateBB); +} + +bool DOTGraphTraits::isNodeHidden(const BasicBlock *Node) { + // If both restricting flags are false, all nodes are displayed. + if (!HideUnreachablePaths && !HideDeoptimizePaths) + return false; + if (isHiddenBasicBlock.find(Node) == isHiddenBasicBlock.end()) + computeHiddenNodes(Node->getParent()); + return isHiddenBasicBlock[Node]; +} Index: llvm/test/Transforms/Inline/cfg_deopt_unreach.ll =================================================================== --- /dev/null +++ llvm/test/Transforms/Inline/cfg_deopt_unreach.ll @@ -0,0 +1,33 @@ +; RUN: opt < %s -analyze -dot-cfg -cfg-hide-unreachable-paths -cfg-dot-filename-prefix=unreach 2>/dev/null +; RUN: FileCheck %s -input-file=unreach.callee.dot -check-prefix=UNREACH +; RUN: opt < %s -analyze -dot-cfg -cfg-hide-deoptimize-paths -cfg-dot-filename-prefix=deopt 2>/dev/null +; RUN: FileCheck %s -input-file=deopt.callee.dot -check-prefix=DEOPT +; RUN: opt < %s -analyze -dot-cfg -cfg-dot-filename-prefix=no-flags 2>/dev/null +; RUN: FileCheck %s -input-file=no-flags.callee.dot -check-prefix=NO-FLAGS +; RUN: opt < %s -analyze -dot-cfg -cfg-hide-unreachable-paths -cfg-hide-deoptimize-paths -cfg-dot-filename-prefix=both-flags 2>/dev/null +; RUN: FileCheck %s -input-file=both-flags.callee.dot -check-prefix=BOTH-FLAGS + +declare i8 @llvm.experimental.deoptimize.i8(...) + +define i8 @callee(i1* %c) alwaysinline { +; NO-FLAGS: [shape=record,label="{%0:\l %c0 = load volatile i1, i1* %c\l br i1 %c0, label %lleft, label %lright\l|{T|F}}"]; +; DEOPT: [shape=record,label="{%0:\l %c0 = load volatile i1, i1* %c\l br i1 %c0, label %lleft, label %lright\l|{T|F}}"]; +; UNREACH: [shape=record,label="{%0:\l %c0 = load volatile i1, i1* %c\l br i1 %c0, label %lleft, label %lright\l|{T|F}}"]; +; BOTH-FLAGS-NOT: [shape=record,label="{%0:\l %c0 = load volatile i1, i1* %c\l br i1 %c0, label %lleft, label %lright\l|{T|F}}"]; + %c0 = load volatile i1, i1* %c + br i1 %c0, label %lleft, label %lright +; NO-FLAGS: [shape=record,label="{lleft: \l %v0 = call i8 (...) @llvm.experimental.deoptimize.i8(i32 1) [ \"deopt\"(i32 1)\l... ]\l ret i8 %v0\l}"]; +; DEOPT-NOT: [shape=record,label="{lleft: \l %v0 = call i8 (...) @llvm.experimental.deoptimize.i8(i32 1) [ \"deopt\"(i32 1)\l... ]\l ret i8 %v0\l}"]; +; UNREACH: [shape=record,label="{lleft: \l %v0 = call i8 (...) @llvm.experimental.deoptimize.i8(i32 1) [ \"deopt\"(i32 1)\l... ]\l ret i8 %v0\l}"]; +; BOTH-FLAGS-NOT: [shape=record,label="{lleft: \l %v0 = call i8 (...) @llvm.experimental.deoptimize.i8(i32 1) [ \"deopt\"(i32 1)\l... ]\l ret i8 %v0\l}"]; +lleft: + %v0 = call i8(...) @llvm.experimental.deoptimize.i8(i32 1) [ "deopt"(i32 1) ] + ret i8 %v0 + +; NO-FLAGS: [shape=record,label="{lright: \l unreachable\l}"]; +; DEOPT: [shape=record,label="{lright: \l unreachable\l}"]; +; UNREACH-NOT: [shape=record,label="{lright: \l unreachable\l}"]; +; BOTH-FLAGS-NOT: [shape=record,label="{lright: \l unreachable\l}"]; +lright: + unreachable +}