llvm中的强连通算法使用的也是Tarjan算法,与我自己实现的那种粗糙的算法不同,llvm使用栈代替递归实现dfs,一方面防止运行栈空间不足的问题,另一方面借助这个栈,可以记住当前遍历的状态,从而实现了强连通分量上的迭代器。总之实现的十分的优雅。
代码文件在include\llvm\ADT\SCCIterator.h中。
现在我们来看一下具体实现。
首先定义了两个静态成员函数begin和end,就是类似容器里对应的头和尾。
static scc_iterator begin(const GraphT &G) {
return scc_iterator(GT::getEntryNode(G));
}
static scc_iterator end(const GraphT &) { return scc_iterator(); }
这里其实就是调用了个scc_iterator的构造函数,GraphT是对图的操作抽象出来的一个Trait类。
scc_iterator(NodeRef entryN) : visitNum(0) {
DFSVisitOne(entryN);
GetNextSCC();
}
DFSVisitOne用于处理当前正在访问的节点,在递归的TarjanSCC算法中,访问一个节点的操作是更新DFS序,并压入栈中,这里也是一样的。不同之处就是增加了个栈来记录DFS当前的状态。
template <class GraphT, class GT>
void scc_iterator<GraphT, GT>::DFSVisitOne(NodeRef N) {
// 增加DFS序
++visitNum;
nodeVisitNumbers[N] = visitNum;
// 节点入栈
SCCNodeStack.push_back(N);
// 记录DFS状态
VisitStack.push_back(StackElement(N, GT::child_begin(N), visitNum));
#if 0 // Enable if needed when debugging.
dbgs() << "TarjanSCC: Node " << N <<
" : visitNum = " << visitNum << "\n";
#endif
}
llvm使用VisitStack来记录当前的迭代状态,用于代替运行栈。
GetNextSCC获取了第一个SCC,实际上相当于递归版的TarjanSCC算法中遇到第一个强连通分量后就暂停程序。
template <class GraphT, class GT> void scc_iterator<GraphT, GT>::GetNextSCC() {
CurrentSCC.clear(); // Prepare to compute the next SCC
while (!VisitStack.empty()) {
DFSVisitChildren();
// Pop the leaf on top of the VisitStack.
// 取出栈顶的节点,相当于递归回退一层了
NodeRef visitingN = VisitStack.back().Node;
unsigned minVisitNum = VisitStack.back().MinVisited;
assert(VisitStack.back().NextChild == GT::child_end(visitingN));
VisitStack.pop_back();
// Propagate MinVisitNum to parent so we can detect the SCC starting node.
// 更新最小DFS序信息
if (!VisitStack.empty() && VisitStack.back().MinVisited > minVisitNum)
VisitStack.back().MinVisited = minVisitNum;
#if 0 // Enable if needed when debugging.
dbgs() << "TarjanSCC: Popped node " << visitingN <<
" : minVisitNum = " << minVisitNum << "; Node visit num = " <<
nodeVisitNumbers[visitingN] << "\n";
#endif
// 如果最小序不是当前节点,继续DFS
if (minVisitNum != nodeVisitNumbers[visitingN])
continue;
// A full SCC is on the SCCNodeStack! It includes all nodes below
// visitingN on the stack. Copy those nodes to CurrentSCC,
// reset their minVisit values, and return (this suspends
// the DFS traversal till the next ++).
// 这里说明遇到了一个回边或者是自身节点,则将栈中当前分量的节点弹出
do {
CurrentSCC.push_back(SCCNodeStack.back());
SCCNodeStack.pop_back();
nodeVisitNumbers[CurrentSCC.back()] = ~0U;
} while (CurrentSCC.back() != visitingN);
return;
}
}
在GetNextSCC一开始的地方,就会迭代遍历所有子结点,这里执行的操作其实和递归版是一样的,如果没有访问过,则继续向下遍历,否则和子节点的最小DFS序去比较,如果子节点较小,则更新到这个较小的DFS序:
template <class GraphT, class GT>
void scc_iterator<GraphT, GT>::DFSVisitChildren() {
assert(!VisitStack.empty());
// DFS遍历所有节点
while (VisitStack.back().NextChild != GT::child_end(VisitStack.back().Node)) {
// TOS has at least one more child so continue DFS
NodeRef childN = *VisitStack.back().NextChild++;
typename DenseMap<NodeRef, unsigned>::iterator Visited =
nodeVisitNumbers.find(childN);
// 如果之前没有被访问过,则记录在栈中
if (Visited == nodeVisitNumbers.end()) {
// this node has never been seen.
DFSVisitOne(childN);
continue;
}
// 如果子节点DFS序小于记录中的最小DFS序,则更新最小DFS序
unsigned childNum = Visited->second;
if (VisitStack.back().MinVisited > childNum)
VisitStack.back().MinVisited = childNum;
}
}
算法中出现了两个栈,其中SCCNodeStack对应的就是Tarjan算法中的栈,用于记录当前SCC中的所有节点,而VisitStack则是用于模拟递归调用并记录lowlink的。
注意这个算法中两处更新lowlink的地方。在GetNextSCC中的更新,是比较下一层的迭代的lowlink和当前迭代的lowlink,并取最小值,可以看作是递归版算法中,递归遍历子节点并返回到当前栈帧后对lowlink进行的更新。而在DFSVisitChildren中,则是将当前节点的DFS序和子节点的lowlink去比较,可以看作递归版中如果已经被visit后进行的更新策略。
即使有了强连通分量,也不能代表当前的节点就是循环。因为强连通分量的自反性,所以一个普通的基本块也是一个SCC,那么这是候需要根据循环的特征来判断SCC是否是一个loop。
// 如果节点有多余一个或者有指向自身的边,说明是个循环
template <class GraphT, class GT>
bool scc_iterator<GraphT, GT>::hasLoop() const {
assert(!CurrentSCC.empty() && "Dereferencing END SCC iterator!");
if (CurrentSCC.size() > 1)
return true;
NodeRef N = CurrentSCC.front();
for (ChildItTy CI = GT::child_begin(N), CE = GT::child_end(N); CI != CE;
++CI)
if (*CI == N)
return true;
return false;
}
还有一些迭代器的常规操作:
bool operator==(const scc_iterator &x) const {
return VisitStack == x.VisitStack && CurrentSCC == x.CurrentSCC;
}
scc_iterator &operator++() {
GetNextSCC();
return *this;
}
(由于博主水平有限,如有讲述错误之处,请留言指正。)