mirror of
https://github.com/RPCS3/llvm-mirror.git
synced 2024-11-25 20:23:11 +01:00
[DDG] Data Dependence Graph - Root Node
Summary: This patch adds Root Node to the DDG. The purpose of the root node is to create a single entry node that allows graph walk iterators to iterate through all nodes of the graph, making sure that no node is left unvisited during a graph walk (eg. SCC or DFS). Once the DDG is fully constructed it will have exactly one root node. Every node in the graph is reachable from the root. The algorithm for connecting the root node is based on depth-first-search that keeps track of visited nodes to try to avoid creating unnecessary edges. Authored By: bmahjour Reviewer: Meinersbur, fhahn, myhsu, xtian, dmgreen, kbarton, jdoerfert Reviewed By: Meinersbur Subscribers: ychen, arphaman, simoll, a.elovikov, mgorny, hiraditya, jfb, wuzish, llvm-commits, jsji, Whitney, etiotto, ppc-slack Tag: #llvm Differential Revision: https://reviews.llvm.org/D67970 llvm-svn: 373386
This commit is contained in:
parent
df5f9ba943
commit
a49fa03628
@ -33,6 +33,8 @@ class LPMUpdater;
|
|||||||
/// 1. Single instruction node containing just one instruction.
|
/// 1. Single instruction node containing just one instruction.
|
||||||
/// 2. Multiple instruction node where two or more instructions from
|
/// 2. Multiple instruction node where two or more instructions from
|
||||||
/// the same basic block are merged into one node.
|
/// the same basic block are merged into one node.
|
||||||
|
/// 3. Root node is a special node that connects to all components such that
|
||||||
|
/// there is always a path from it to any node in the graph.
|
||||||
class DDGNode : public DDGNodeBase {
|
class DDGNode : public DDGNodeBase {
|
||||||
public:
|
public:
|
||||||
using InstructionListType = SmallVectorImpl<Instruction *>;
|
using InstructionListType = SmallVectorImpl<Instruction *>;
|
||||||
@ -41,6 +43,7 @@ public:
|
|||||||
Unknown,
|
Unknown,
|
||||||
SingleInstruction,
|
SingleInstruction,
|
||||||
MultiInstruction,
|
MultiInstruction,
|
||||||
|
Root,
|
||||||
};
|
};
|
||||||
|
|
||||||
DDGNode() = delete;
|
DDGNode() = delete;
|
||||||
@ -78,6 +81,22 @@ private:
|
|||||||
NodeKind Kind;
|
NodeKind Kind;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Subclass of DDGNode representing the root node of the graph.
|
||||||
|
/// There should only be one such node in a given graph.
|
||||||
|
class RootDDGNode : public DDGNode {
|
||||||
|
public:
|
||||||
|
RootDDGNode() : DDGNode(NodeKind::Root) {}
|
||||||
|
RootDDGNode(const RootDDGNode &N) = delete;
|
||||||
|
RootDDGNode(RootDDGNode &&N) : DDGNode(std::move(N)) {}
|
||||||
|
~RootDDGNode() {}
|
||||||
|
|
||||||
|
/// Define classof to be able to use isa<>, cast<>, dyn_cast<>, etc.
|
||||||
|
static bool classof(const DDGNode *N) {
|
||||||
|
return N->getKind() == NodeKind::Root;
|
||||||
|
}
|
||||||
|
static bool classof(const RootDDGNode *N) { return true; }
|
||||||
|
};
|
||||||
|
|
||||||
/// Subclass of DDGNode representing single or multi-instruction nodes.
|
/// Subclass of DDGNode representing single or multi-instruction nodes.
|
||||||
class SimpleDDGNode : public DDGNode {
|
class SimpleDDGNode : public DDGNode {
|
||||||
public:
|
public:
|
||||||
@ -139,10 +158,12 @@ private:
|
|||||||
/// Data Dependency Graph Edge.
|
/// Data Dependency Graph Edge.
|
||||||
/// An edge in the DDG can represent a def-use relationship or
|
/// An edge in the DDG can represent a def-use relationship or
|
||||||
/// a memory dependence based on the result of DependenceAnalysis.
|
/// a memory dependence based on the result of DependenceAnalysis.
|
||||||
|
/// A rooted edge connects the root node to one of the components
|
||||||
|
/// of the graph.
|
||||||
class DDGEdge : public DDGEdgeBase {
|
class DDGEdge : public DDGEdgeBase {
|
||||||
public:
|
public:
|
||||||
/// The kind of edge in the DDG
|
/// The kind of edge in the DDG
|
||||||
enum class EdgeKind { Unknown, RegisterDefUse, MemoryDependence };
|
enum class EdgeKind { Unknown, RegisterDefUse, MemoryDependence, Rooted };
|
||||||
|
|
||||||
explicit DDGEdge(DDGNode &N) = delete;
|
explicit DDGEdge(DDGNode &N) = delete;
|
||||||
DDGEdge(DDGNode &N, EdgeKind K) : DDGEdgeBase(N), Kind(K) {}
|
DDGEdge(DDGNode &N, EdgeKind K) : DDGEdgeBase(N), Kind(K) {}
|
||||||
@ -169,6 +190,10 @@ public:
|
|||||||
/// Return true if this is a memory dependence edge, and false otherwise.
|
/// Return true if this is a memory dependence edge, and false otherwise.
|
||||||
bool isMemoryDependence() const { return Kind == EdgeKind::MemoryDependence; }
|
bool isMemoryDependence() const { return Kind == EdgeKind::MemoryDependence; }
|
||||||
|
|
||||||
|
/// Return true if this is an edge stemming from the root node, and false
|
||||||
|
/// otherwise.
|
||||||
|
bool isRooted() const { return Kind == EdgeKind::Rooted; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
EdgeKind Kind;
|
EdgeKind Kind;
|
||||||
};
|
};
|
||||||
@ -182,14 +207,21 @@ public:
|
|||||||
DependenceGraphInfo() = delete;
|
DependenceGraphInfo() = delete;
|
||||||
DependenceGraphInfo(const DependenceGraphInfo &G) = delete;
|
DependenceGraphInfo(const DependenceGraphInfo &G) = delete;
|
||||||
DependenceGraphInfo(const std::string &N, const DependenceInfo &DepInfo)
|
DependenceGraphInfo(const std::string &N, const DependenceInfo &DepInfo)
|
||||||
: Name(N), DI(DepInfo) {}
|
: Name(N), DI(DepInfo), Root(nullptr) {}
|
||||||
DependenceGraphInfo(DependenceGraphInfo &&G)
|
DependenceGraphInfo(DependenceGraphInfo &&G)
|
||||||
: Name(std::move(G.Name)), DI(std::move(G.DI)) {}
|
: Name(std::move(G.Name)), DI(std::move(G.DI)), Root(G.Root) {}
|
||||||
virtual ~DependenceGraphInfo() {}
|
virtual ~DependenceGraphInfo() {}
|
||||||
|
|
||||||
/// Return the label that is used to name this graph.
|
/// Return the label that is used to name this graph.
|
||||||
const StringRef getName() const { return Name; }
|
const StringRef getName() const { return Name; }
|
||||||
|
|
||||||
|
/// Return the root node of the graph.
|
||||||
|
NodeType &getRoot() const {
|
||||||
|
assert(Root && "Root node is not available yet. Graph construction may "
|
||||||
|
"still be in progress\n");
|
||||||
|
return *Root;
|
||||||
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
// Name of the graph.
|
// Name of the graph.
|
||||||
std::string Name;
|
std::string Name;
|
||||||
@ -198,6 +230,10 @@ protected:
|
|||||||
// dependencies don't need to be stored. Instead when the dependence is
|
// dependencies don't need to be stored. Instead when the dependence is
|
||||||
// queried it is recomputed using @DI.
|
// queried it is recomputed using @DI.
|
||||||
const DependenceInfo DI;
|
const DependenceInfo DI;
|
||||||
|
|
||||||
|
// A special node in the graph that has an edge to every connected component of
|
||||||
|
// the graph, to ensure all nodes are reachable in a graph walk.
|
||||||
|
NodeType *Root = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
using DDGInfo = DependenceGraphInfo<DDGNode>;
|
using DDGInfo = DependenceGraphInfo<DDGNode>;
|
||||||
@ -217,6 +253,12 @@ public:
|
|||||||
DataDependenceGraph(Function &F, DependenceInfo &DI);
|
DataDependenceGraph(Function &F, DependenceInfo &DI);
|
||||||
DataDependenceGraph(const Loop &L, DependenceInfo &DI);
|
DataDependenceGraph(const Loop &L, DependenceInfo &DI);
|
||||||
~DataDependenceGraph();
|
~DataDependenceGraph();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/// Add node \p N to the graph, if it's not added yet, and keep track of
|
||||||
|
/// the root node. Return true if node is successfully added.
|
||||||
|
bool addNode(NodeType &N);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Concrete implementation of a pure data dependence graph builder. This class
|
/// Concrete implementation of a pure data dependence graph builder. This class
|
||||||
@ -230,6 +272,12 @@ public:
|
|||||||
DDGBuilder(DataDependenceGraph &G, DependenceInfo &D,
|
DDGBuilder(DataDependenceGraph &G, DependenceInfo &D,
|
||||||
const BasicBlockListType &BBs)
|
const BasicBlockListType &BBs)
|
||||||
: AbstractDependenceGraphBuilder(G, D, BBs) {}
|
: AbstractDependenceGraphBuilder(G, D, BBs) {}
|
||||||
|
DDGNode &createRootNode() final override {
|
||||||
|
auto *RN = new RootDDGNode();
|
||||||
|
assert(RN && "Failed to allocate memory for DDG root node.");
|
||||||
|
Graph.addNode(*RN);
|
||||||
|
return *RN;
|
||||||
|
}
|
||||||
DDGNode &createFineGrainedNode(Instruction &I) final override {
|
DDGNode &createFineGrainedNode(Instruction &I) final override {
|
||||||
auto *SN = new SimpleDDGNode(I);
|
auto *SN = new SimpleDDGNode(I);
|
||||||
assert(SN && "Failed to allocate memory for simple DDG node.");
|
assert(SN && "Failed to allocate memory for simple DDG node.");
|
||||||
@ -248,6 +296,14 @@ public:
|
|||||||
Graph.connect(Src, Tgt, *E);
|
Graph.connect(Src, Tgt, *E);
|
||||||
return *E;
|
return *E;
|
||||||
}
|
}
|
||||||
|
DDGEdge &createRootedEdge(DDGNode &Src, DDGNode &Tgt) final override {
|
||||||
|
auto *E = new DDGEdge(Tgt, DDGEdge::EdgeKind::Rooted);
|
||||||
|
assert(E && "Failed to allocate memory for edge");
|
||||||
|
assert(isa<RootDDGNode>(Src) && "Expected root node");
|
||||||
|
Graph.connect(Src, Tgt, *E);
|
||||||
|
return *E;
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
raw_ostream &operator<<(raw_ostream &OS, const DDGNode &N);
|
raw_ostream &operator<<(raw_ostream &OS, const DDGNode &N);
|
||||||
@ -317,7 +373,9 @@ template <> struct GraphTraits<DDGNode *> {
|
|||||||
template <>
|
template <>
|
||||||
struct GraphTraits<DataDependenceGraph *> : public GraphTraits<DDGNode *> {
|
struct GraphTraits<DataDependenceGraph *> : public GraphTraits<DDGNode *> {
|
||||||
using nodes_iterator = DataDependenceGraph::iterator;
|
using nodes_iterator = DataDependenceGraph::iterator;
|
||||||
static NodeRef getEntryNode(DataDependenceGraph *DG) { return *DG->begin(); }
|
static NodeRef getEntryNode(DataDependenceGraph *DG) {
|
||||||
|
return &DG->getRoot();
|
||||||
|
}
|
||||||
static nodes_iterator nodes_begin(DataDependenceGraph *DG) {
|
static nodes_iterator nodes_begin(DataDependenceGraph *DG) {
|
||||||
return DG->begin();
|
return DG->begin();
|
||||||
}
|
}
|
||||||
@ -357,7 +415,7 @@ struct GraphTraits<const DataDependenceGraph *>
|
|||||||
: public GraphTraits<const DDGNode *> {
|
: public GraphTraits<const DDGNode *> {
|
||||||
using nodes_iterator = DataDependenceGraph::const_iterator;
|
using nodes_iterator = DataDependenceGraph::const_iterator;
|
||||||
static NodeRef getEntryNode(const DataDependenceGraph *DG) {
|
static NodeRef getEntryNode(const DataDependenceGraph *DG) {
|
||||||
return *DG->begin();
|
return &DG->getRoot();
|
||||||
}
|
}
|
||||||
static nodes_iterator nodes_begin(const DataDependenceGraph *DG) {
|
static nodes_iterator nodes_begin(const DataDependenceGraph *DG) {
|
||||||
return DG->begin();
|
return DG->begin();
|
||||||
|
@ -55,6 +55,7 @@ public:
|
|||||||
createFineGrainedNodes();
|
createFineGrainedNodes();
|
||||||
createDefUseEdges();
|
createDefUseEdges();
|
||||||
createMemoryDependencyEdges();
|
createMemoryDependencyEdges();
|
||||||
|
createAndConnectRootNode();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create fine grained nodes. These are typically atomic nodes that
|
/// Create fine grained nodes. These are typically atomic nodes that
|
||||||
@ -69,7 +70,14 @@ public:
|
|||||||
/// in the graph nodes and create edges between them.
|
/// in the graph nodes and create edges between them.
|
||||||
void createMemoryDependencyEdges();
|
void createMemoryDependencyEdges();
|
||||||
|
|
||||||
|
/// Create a root node and add edges such that each node in the graph is
|
||||||
|
/// reachable from the root.
|
||||||
|
void createAndConnectRootNode();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
/// Create the root node of the graph.
|
||||||
|
virtual NodeType &createRootNode() = 0;
|
||||||
|
|
||||||
/// Create an atomic node in the graph given a single instruction.
|
/// Create an atomic node in the graph given a single instruction.
|
||||||
virtual NodeType &createFineGrainedNode(Instruction &I) = 0;
|
virtual NodeType &createFineGrainedNode(Instruction &I) = 0;
|
||||||
|
|
||||||
@ -79,6 +87,9 @@ protected:
|
|||||||
/// Create a memory dependence edge going from \p Src to \p Tgt.
|
/// Create a memory dependence edge going from \p Src to \p Tgt.
|
||||||
virtual EdgeType &createMemoryEdge(NodeType &Src, NodeType &Tgt) = 0;
|
virtual EdgeType &createMemoryEdge(NodeType &Src, NodeType &Tgt) = 0;
|
||||||
|
|
||||||
|
/// Create a rooted edge going from \p Src to \p Tgt .
|
||||||
|
virtual EdgeType &createRootedEdge(NodeType &Src, NodeType &Tgt) = 0;
|
||||||
|
|
||||||
/// Deallocate memory of edge \p E.
|
/// Deallocate memory of edge \p E.
|
||||||
virtual void destroyEdge(EdgeType &E) { delete &E; }
|
virtual void destroyEdge(EdgeType &E) { delete &E; }
|
||||||
|
|
||||||
|
@ -46,6 +46,9 @@ raw_ostream &llvm::operator<<(raw_ostream &OS, const DDGNode::NodeKind K) {
|
|||||||
case DDGNode::NodeKind::MultiInstruction:
|
case DDGNode::NodeKind::MultiInstruction:
|
||||||
Out = "multi-instruction";
|
Out = "multi-instruction";
|
||||||
break;
|
break;
|
||||||
|
case DDGNode::NodeKind::Root:
|
||||||
|
Out = "root";
|
||||||
|
break;
|
||||||
case DDGNode::NodeKind::Unknown:
|
case DDGNode::NodeKind::Unknown:
|
||||||
Out = "??";
|
Out = "??";
|
||||||
break;
|
break;
|
||||||
@ -60,7 +63,7 @@ raw_ostream &llvm::operator<<(raw_ostream &OS, const DDGNode &N) {
|
|||||||
OS << " Instructions:\n";
|
OS << " Instructions:\n";
|
||||||
for (auto *I : cast<const SimpleDDGNode>(N).getInstructions())
|
for (auto *I : cast<const SimpleDDGNode>(N).getInstructions())
|
||||||
OS.indent(2) << *I << "\n";
|
OS.indent(2) << *I << "\n";
|
||||||
} else
|
} else if (!isa<RootDDGNode>(N))
|
||||||
llvm_unreachable("unimplemented type of node");
|
llvm_unreachable("unimplemented type of node");
|
||||||
|
|
||||||
OS << (N.getEdges().empty() ? " Edges:none!\n" : " Edges:\n");
|
OS << (N.getEdges().empty() ? " Edges:none!\n" : " Edges:\n");
|
||||||
@ -108,6 +111,9 @@ raw_ostream &llvm::operator<<(raw_ostream &OS, const DDGEdge::EdgeKind K) {
|
|||||||
case DDGEdge::EdgeKind::MemoryDependence:
|
case DDGEdge::EdgeKind::MemoryDependence:
|
||||||
Out = "memory";
|
Out = "memory";
|
||||||
break;
|
break;
|
||||||
|
case DDGEdge::EdgeKind::Rooted:
|
||||||
|
Out = "rooted";
|
||||||
|
break;
|
||||||
case DDGEdge::EdgeKind::Unknown:
|
case DDGEdge::EdgeKind::Unknown:
|
||||||
Out = "??";
|
Out = "??";
|
||||||
break;
|
break;
|
||||||
@ -153,6 +159,22 @@ DataDependenceGraph::~DataDependenceGraph() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool DataDependenceGraph::addNode(DDGNode &N) {
|
||||||
|
if (!DDGBase::addNode(N))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// In general, if the root node is already created and linked, it is not safe
|
||||||
|
// to add new nodes since they may be unreachable by the root.
|
||||||
|
// TODO: Allow adding Pi-block nodes after root is created. Pi-blocks are an
|
||||||
|
// exception because they represent components that are already reachable by
|
||||||
|
// root.
|
||||||
|
assert(!Root && "Root node is already added. No more nodes can be added.");
|
||||||
|
if (isa<RootDDGNode>(N))
|
||||||
|
Root = &N;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
raw_ostream &llvm::operator<<(raw_ostream &OS, const DataDependenceGraph &G) {
|
raw_ostream &llvm::operator<<(raw_ostream &OS, const DataDependenceGraph &G) {
|
||||||
for (auto *Node : G)
|
for (auto *Node : G)
|
||||||
OS << *Node << "\n";
|
OS << *Node << "\n";
|
||||||
|
@ -46,6 +46,34 @@ void AbstractDependenceGraphBuilder<G>::createFineGrainedNodes() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <class G>
|
||||||
|
void AbstractDependenceGraphBuilder<G>::createAndConnectRootNode() {
|
||||||
|
// Create a root node that connects to every connected component of the graph.
|
||||||
|
// This is done to allow graph iterators to visit all the disjoint components
|
||||||
|
// of the graph, in a single walk.
|
||||||
|
//
|
||||||
|
// This algorithm works by going through each node of the graph and for each
|
||||||
|
// node N, do a DFS starting from N. A rooted edge is established between the
|
||||||
|
// root node and N (if N is not yet visited). All the nodes reachable from N
|
||||||
|
// are marked as visited and are skipped in the DFS of subsequent nodes.
|
||||||
|
//
|
||||||
|
// Note: This algorithm tries to limit the number of edges out of the root
|
||||||
|
// node to some extent, but there may be redundant edges created depending on
|
||||||
|
// the iteration order. For example for a graph {A -> B}, an edge from the
|
||||||
|
// root node is added to both nodes if B is visited before A. While it does
|
||||||
|
// not result in minimal number of edges, this approach saves compile-time
|
||||||
|
// while keeping the number of edges in check.
|
||||||
|
auto &RootNode = createRootNode();
|
||||||
|
df_iterator_default_set<const NodeType *, 4> Visited;
|
||||||
|
for (auto *N : Graph) {
|
||||||
|
if (*N == RootNode)
|
||||||
|
continue;
|
||||||
|
for (auto I : depth_first_ext(N, Visited))
|
||||||
|
if (I == N)
|
||||||
|
createRootedEdge(RootNode, *N);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
template <class G> void AbstractDependenceGraphBuilder<G>::createDefUseEdges() {
|
template <class G> void AbstractDependenceGraphBuilder<G>::createDefUseEdges() {
|
||||||
for (NodeType *N : Graph) {
|
for (NodeType *N : Graph) {
|
||||||
InstructionListType SrcIList;
|
InstructionListType SrcIList;
|
||||||
|
52
test/Analysis/DDG/root-node.ll
Normal file
52
test/Analysis/DDG/root-node.ll
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
; RUN: opt < %s -disable-output "-passes=print<ddg>" 2>&1 | FileCheck %s
|
||||||
|
|
||||||
|
; CHECK-LABEL: 'DDG' for loop 'test1.for.body':
|
||||||
|
|
||||||
|
; CHECK: Node Address:[[N1:0x[0-9a-f]*]]:single-instruction
|
||||||
|
; CHECK-NEXT: Instructions:
|
||||||
|
; CHECK-NEXT: %i2.03 = phi i64 [ 0, %for.body.lr.ph ], [ %inc2, %test1.for.body ]
|
||||||
|
|
||||||
|
; CHECK: Node Address:[[N2:0x[0-9a-f]*]]:single-instruction
|
||||||
|
; CHECK-NEXT: Instructions:
|
||||||
|
; CHECK-NEXT: %i1.02 = phi i64 [ 0, %for.body.lr.ph ], [ %inc, %test1.for.body ]
|
||||||
|
|
||||||
|
; CHECK: Node Address:[[ROOT:0x[0-9a-f]*]]:root
|
||||||
|
; CHECK-NEXT: Edges:
|
||||||
|
; CHECK-NEXT: [rooted] to [[N1]]
|
||||||
|
; CHECK-NEXT: [rooted] to [[N2]]
|
||||||
|
|
||||||
|
|
||||||
|
;; // Two separate components in the graph. Root node must link to both.
|
||||||
|
;; void test1(unsigned long n, float * restrict a, float * restrict b) {
|
||||||
|
;; for (unsigned long i1 = 0, i2 = 0; i1 < n; i1++, i2++) {
|
||||||
|
;; a[i1] = 1;
|
||||||
|
;; b[i2] = -1;
|
||||||
|
;; }
|
||||||
|
;; }
|
||||||
|
|
||||||
|
define void @test1(i64 %n, float* noalias %a, float* noalias %b) {
|
||||||
|
entry:
|
||||||
|
%cmp1 = icmp ult i64 0, %n
|
||||||
|
br i1 %cmp1, label %for.body.lr.ph, label %for.end
|
||||||
|
|
||||||
|
for.body.lr.ph: ; preds = %entry
|
||||||
|
br label %test1.for.body
|
||||||
|
|
||||||
|
test1.for.body: ; preds = %for.body.lr.ph, %test1.for.body
|
||||||
|
%i2.03 = phi i64 [ 0, %for.body.lr.ph ], [ %inc2, %test1.for.body ]
|
||||||
|
%i1.02 = phi i64 [ 0, %for.body.lr.ph ], [ %inc, %test1.for.body ]
|
||||||
|
%arrayidx = getelementptr inbounds float, float* %a, i64 %i1.02
|
||||||
|
store float 1.000000e+00, float* %arrayidx, align 4
|
||||||
|
%arrayidx1 = getelementptr inbounds float, float* %b, i64 %i2.03
|
||||||
|
store float -1.000000e+00, float* %arrayidx1, align 4
|
||||||
|
%inc = add i64 %i1.02, 1
|
||||||
|
%inc2 = add i64 %i2.03, 1
|
||||||
|
%cmp = icmp ult i64 %inc, %n
|
||||||
|
br i1 %cmp, label %test1.for.body, label %for.cond.for.end_crit_edge
|
||||||
|
|
||||||
|
for.cond.for.end_crit_edge: ; preds = %test1.for.body
|
||||||
|
br label %for.end
|
||||||
|
|
||||||
|
for.end: ; preds = %for.cond.for.end_crit_edge, %entry
|
||||||
|
ret void
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user