Rewrite node splitting algorithm

This commit is contained in:
Alexey Andreev 2021-03-29 21:10:56 +03:00
parent b34e25414e
commit 9972fe0c29
7 changed files with 631 additions and 649 deletions

View File

@ -1,252 +0,0 @@
/*
* Copyright 2015 Alexey Andreev.
*
* 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.
*/
package org.teavm.common;
import com.carrotsearch.hppc.IntHashSet;
import com.carrotsearch.hppc.IntSet;
import com.carrotsearch.hppc.cursors.IntCursor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class DJGraph {
private DominatorTree domTree;
private MutableDirectedGraph cfg;
private MutableDirectedGraph graph;
private LCATree spanningTree;
private int[] spanningTreeNode;
private int[] spanningTreeIndex;
private int[][] levelContent;
private int[] mergeRoot;
private int[] weight;
private IntegerArray[] mergeClasses;
public DJGraph(Graph src, int[] weight) {
if (src.size() != weight.length) {
throw new IllegalArgumentException("Node count " + src.size() + " is not equal to weight array "
+ weight.length);
}
this.cfg = new MutableDirectedGraph(src);
domTree = GraphUtils.buildDominatorTree(src);
buildGraph(src);
buildLevels();
spanningTree = new LCATree(src.size());
dfs();
mergeRoot = new int[src.size()];
mergeClasses = new IntegerArray[src.size()];
for (int i = 0; i < mergeRoot.length; ++i) {
mergeRoot[i] = i;
mergeClasses[i] = IntegerArray.of(i);
}
this.weight = Arrays.copyOf(weight, weight.length);
}
private void buildGraph(Graph src) {
graph = new MutableDirectedGraph();
// Add join edges
for (int i = 0; i < src.size(); ++i) {
for (int j : src.outgoingEdges(i)) {
graph.addEdge(i, j);
}
}
// Add dom edges
for (int i = 0; i < graph.size(); ++i) {
int j = domTree.immediateDominatorOf(i);
if (j >= 0) {
graph.addEdge(j, i);
}
}
}
private void buildLevels() {
List<IntegerArray> builder = new ArrayList<>();
for (int i = 0; i < graph.size(); ++i) {
int level = domTree.levelOf(i);
while (level >= builder.size()) {
builder.add(new IntegerArray(1));
}
builder.get(level).add(i);
}
levelContent = new int[builder.size() - 1][];
for (int i = 1; i < builder.size(); ++i) {
levelContent[i - 1] = builder.get(i).getAll();
}
}
private void dfs() {
spanningTreeNode = new int[graph.size()];
spanningTreeIndex = new int[graph.size()];
Arrays.fill(spanningTreeIndex, -1);
Arrays.fill(spanningTreeNode, -1);
boolean[] visited = new boolean[graph.size()];
IntegerStack stack = new IntegerStack(graph.size() * 2);
stack.push(-1);
stack.push(0);
while (!stack.isEmpty()) {
int node = stack.pop();
int source = stack.pop();
if (visited[node]) {
continue;
}
int index = source >= 0 ? spanningTree.addNode(spanningTreeIndex[source]) : 0;
spanningTreeNode[index] = node;
spanningTreeIndex[node] = index;
visited[node] = true;
for (int succ : graph.outgoingEdges(node)) {
stack.push(node);
stack.push(succ);
}
}
}
public MutableDirectedGraph getCfg() {
return cfg;
}
public Graph getGraph() {
return graph;
}
public boolean isAncestorInSpanningTree(int anc, int node) {
anc = spanningTreeIndex[mergeRoot[anc]];
node = spanningTreeIndex[mergeRoot[node]];
if (anc < 0 || node < 0) {
return false;
}
return spanningTree.lcaOf(anc, node) == anc;
}
public boolean isDomEdge(int i, int j) {
return immediateDominatorOf(j) == mergeRoot[i];
}
public int immediateDominatorOf(int node) {
int dom = domTree.immediateDominatorOf(mergeRoot[node]);
return dom >= 0 ? mergeRoot[dom] : dom;
}
public int commonDominatorOf(int a, int b) {
int dom = domTree.commonDominatorOf(mergeRoot[a], mergeRoot[b]);
return dom >= 0 ? mergeRoot[dom] : dom;
}
public boolean isJoinEdge(int i, int j) {
return !isDomEdge(i, j);
}
public boolean isBackJoin(int i, int j) {
return isJoinEdge(i, j) && domTree.dominates(mergeRoot[j], mergeRoot[i]);
}
public boolean isCrossJoin(int i, int j) {
return isJoinEdge(i, j) && !domTree.dominates(mergeRoot[j], mergeRoot[i]);
}
public boolean isSpanningBack(int i, int j) {
i = spanningTreeIndex[mergeRoot[i]];
j = spanningTreeIndex[mergeRoot[j]];
return spanningTree.lcaOf(i, j) == j;
}
public boolean isSpanningCross(int i, int j) {
i = spanningTreeIndex[mergeRoot[i]];
j = spanningTreeIndex[mergeRoot[j]];
int c = spanningTree.lcaOf(i, j);
return c != i && c != j;
}
public int weightOf(int node) {
return weight[node];
}
public int weightOf(int... nodes) {
int result = 0;
for (int node : nodes) {
result += weight[node];
}
return result;
}
public int levelOf(int node) {
return domTree.levelOf(mergeRoot[node]) - 1;
}
public int[] level(int level) {
int[] result = levelContent[level];
return Arrays.copyOf(result, result.length);
}
public int levelCount() {
return levelContent.length;
}
public int[] classRepresentatives(int node) {
return mergeClasses[node].getAll();
}
public int classOf(int node) {
return mergeRoot[node];
}
public int collapse(int[] nodes) {
// Replace nodes with their classes and find common dominator among them
IntSet set = new IntHashSet();
int top = nodes[0];
for (int node : nodes) {
node = mergeRoot[node];
top = domTree.commonDominatorOf(top, node);
set.add(node);
}
if (!set.contains(top)) {
throw new IllegalArgumentException("All nodes must have one common dominator");
}
// Alter classes
IntegerArray cls = mergeClasses[top];
for (IntCursor node : set) {
mergeRoot[node.value] = top;
if (node.value != top) {
cls.addAll(mergeClasses[node.value].getAll());
mergeClasses[node.value].clear();
}
weight[top] += weight[node.value];
}
// Alter graphs
for (IntCursor node : set) {
if (node.value != top) {
for (int succ : graph.outgoingEdges(node.value)) {
graph.addEdge(top, succ);
}
for (int pred : graph.incomingEdges(node.value)) {
graph.addEdge(top, pred);
}
graph.detachNode(node.value);
for (int succ : cfg.outgoingEdges(node.value)) {
cfg.addEdge(top, succ);
}
for (int pred : cfg.incomingEdges(node.value)) {
cfg.addEdge(top, pred);
}
cfg.detachNode(node.value);
}
}
return top;
}
}

View File

@ -151,24 +151,7 @@ public class GraphBuilder {
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("digraph {\n");
for (int i = 0; i < size(); ++i) {
if (outgoingEdgesCount(i) > 0) {
sb.append(" ").append(i).append(" -> { ");
int[] outgoingEdges = outgoingEdges(i);
sb.append(outgoingEdges[0]);
for (int j = 1; j < outgoingEdges.length; ++j) {
sb.append(", ").append(outgoingEdges[j]);
}
sb.append(" }\n");
}
}
sb.append("}");
return sb.toString();
return GraphUtils.printToDot(this);
}
}
}

View File

@ -126,11 +126,11 @@ public final class GraphUtils {
}
}
private static class FilteredGraph implements Graph {
static class FilteredGraph implements Graph {
final Graph innerGraph;
final IntPredicate filter;
public FilteredGraph(Graph innerGraph, IntPredicate filter) {
FilteredGraph(Graph innerGraph, IntPredicate filter) {
this.innerGraph = innerGraph;
this.filter = filter;
}
@ -198,6 +198,11 @@ public final class GraphUtils {
public int outgoingEdgesCount(int node) {
return outgoingEdges(node).length;
}
@Override
public String toString() {
return GraphUtils.printToDot(this);
}
}
/*
@ -349,7 +354,7 @@ public final class GraphUtils {
}
public static void splitIrreducibleGraph(Graph graph, int[] weights, GraphSplittingBackend backend) {
new IrreducibleGraphConverter().convertToReducible(graph, weights, backend);
new IrreducibleGraphSplitter(backend, graph, weights).splitLoops();
}
public static int[][] findDominanceFrontiers(Graph cfg, DominatorTree domTree) {

View File

@ -1,349 +0,0 @@
/*
* Copyright 2015 Alexey Andreev.
*
* 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.
*/
package org.teavm.common;
import com.carrotsearch.hppc.IntHashSet;
import com.carrotsearch.hppc.IntSet;
import com.carrotsearch.hppc.cursors.IntCursor;
import java.util.Arrays;
import java.util.function.IntPredicate;
/**
* <p>Converts irreducible graph to reducible one using node splitting algorithm described at
* the paper &ldquo;Handling irreducible loops: optimized node splitting vs. DJ-graphs&rdquo; by
* Sebastian Unger and Frank Mueller.</p>
*
* @author Alexey Andreev
*/
class IrreducibleGraphConverter {
private Graph cfg;
private int totalNodeCount;
private GraphSplittingBackend backend;
private IntSet[] nodeCopies;
private IntegerArray nodeOriginals;
void convertToReducible(Graph cfg, int[] weight, GraphSplittingBackend backend) {
this.backend = backend;
nodeCopies = new IntHashSet[cfg.size()];
nodeOriginals = new IntegerArray(cfg.size());
for (int i = 0; i < cfg.size(); ++i) {
nodeCopies[i] = new IntHashSet();
nodeOriginals.add(i);
}
int[][] identityNodeMap = new int[cfg.size()][];
for (int i = 0; i < identityNodeMap.length; ++i) {
identityNodeMap[i] = new int[] { i };
}
this.cfg = cfg;
totalNodeCount = cfg.size();
handleLoops(new DJGraph(cfg, weight), identityNodeMap);
this.backend = null;
}
private void handleLoops(DJGraph djGraph, int[][] nodeMap) {
for (int level = djGraph.levelCount() - 1; level >= 0; --level) {
boolean irreducible = false;
levelScan:
for (int node : djGraph.level(level)) {
for (int pred : djGraph.getGraph().incomingEdges(node)) {
if (djGraph.isCrossJoin(pred, node) && djGraph.isSpanningBack(node, pred)) {
irreducible = true;
break levelScan;
}
}
}
if (irreducible) {
DJGraphNodeFilter filter = new DJGraphNodeFilter(djGraph, level);
Graph graph = GraphUtils.subgraph(djGraph.getGraph(), filter);
int[][] sccs = GraphUtils.findStronglyConnectedComponents(graph);
for (int[] scc : sccs) {
if (scc.length > 1) {
handleStronglyConnectedComponent(djGraph, scc, nodeMap);
}
}
}
}
}
private void handleStronglyConnectedComponent(DJGraph djGraph, int[] scc, int[][] nodeMap) {
// Find shared dominator
int sharedDom = scc[0];
for (int i = 1; i < scc.length; ++i) {
sharedDom = djGraph.commonDominatorOf(sharedDom, scc[i]);
}
for (int i = 0; i < scc.length; ++i) {
if (scc[i] == sharedDom) {
collapse(djGraph, scc, nodeMap);
return;
}
}
// Partition SCC into domains
DisjointSet partitions = new DisjointSet();
int[] sccBack = new int[djGraph.getGraph().size()];
for (int i = 0; i < scc.length; ++i) {
partitions.create();
sccBack[scc[i]] = i;
}
for (int i = 0; i < scc.length; ++i) {
int node = scc[i];
int idom = djGraph.immediateDominatorOf(node);
if (idom != sharedDom) {
partitions.union(i, sccBack[idom]);
}
}
int[] domains = partitions.pack(scc.length);
int domainCount = 0;
for (int domain : domains) {
domainCount = Math.max(domainCount, domain + 1);
}
// For each domain calculate its weight
int[] domainWeight = new int[domainCount];
for (int i = 0; i < scc.length; ++i) {
int node = scc[i];
domainWeight[domains[i]] += djGraph.weightOf(node);
}
// Find domain to split around
int domain = 0;
int maxWeight = domainWeight[0];
for (int i = 1; i < domainWeight.length; ++i) {
if (domainWeight[i] > maxWeight) {
domain = i;
maxWeight = domainWeight[i];
}
}
// Find header of this domain
IntSet domainNodes = new IntHashSet(scc.length);
for (int i = 0; i < scc.length; ++i) {
int node = scc[i];
if (domains[i] == domain) {
domainNodes.add(node);
}
}
// Split
splitStronglyConnectedComponent(djGraph, domainNodes, sharedDom, scc, nodeMap);
// Collapse
int[] sccAndTop = Arrays.copyOf(scc, scc.length + 1);
sccAndTop[scc.length] = sharedDom;
collapse(djGraph, sccAndTop, nodeMap);
}
private void splitStronglyConnectedComponent(DJGraph djGraph, IntSet domain, int sharedDom,
int[] scc, int[][] nodeMap) {
Arrays.sort(scc);
// Find SCC \ domain
int[][] mappedNonDomain = new int[scc.length - domain.size()][];
int[] domainNodes = new int[domain.size()];
int[] nonDomainNodes = new int[mappedNonDomain.length];
int index = 0;
for (int node : scc) {
if (!domain.contains(node)) {
mappedNonDomain[index] = nodeMap[node];
nonDomainNodes[index] = node;
++index;
}
}
int[][] mappedDomain = new int[domain.size()][];
index = 0;
for (IntCursor cursor : domain) {
mappedDomain[index] = nodeMap[cursor.value];
domainNodes[index] = cursor.value;
++index;
}
// Delegate splitting to domain
int[] nodesToCopy = withCopies(flatten(mappedNonDomain));
int[] copies = backend.split(withCopies(flatten(mappedDomain)), nodesToCopy);
registerCopies(nodesToCopy, copies);
int[][] newNodes = unflatten(withoutCopies(copies), mappedNonDomain);
for (int[] nodes : newNodes) {
totalNodeCount += nodes.length;
}
// Calculate mappings
int[][] newNodeMap = new int[1 + scc.length + newNodes.length][];
int[] newNodeBackMap = new int[totalNodeCount];
int[] mappedWeight = new int[newNodeMap.length];
Arrays.fill(newNodeBackMap, -1);
newNodeMap[0] = nodeMap[sharedDom];
newNodeBackMap[sharedDom] = 0;
mappedWeight[0] = djGraph.weightOf(sharedDom);
index = 1;
for (int i = 0; i < mappedDomain.length; ++i) {
newNodeMap[index] = mappedDomain[i];
newNodeBackMap[domainNodes[i]] = index;
mappedWeight[index] = djGraph.weightOf(domainNodes[i]);
++index;
}
for (int i = 0; i < mappedNonDomain.length; ++i) {
newNodeMap[index] = mappedNonDomain[i];
newNodeBackMap[nonDomainNodes[i]] = index;
mappedWeight[index] = djGraph.weightOf(nonDomainNodes[i]);
++index;
}
for (int i = 0; i < mappedNonDomain.length; ++i) {
newNodeMap[index] = newNodes[i];
mappedWeight[index] = djGraph.weightOf(nonDomainNodes[i]);
++index;
}
// Build subgraph with new nodes
GraphBuilder builder = new GraphBuilder(newNodeMap.length);
for (int succ : cfg.outgoingEdges(sharedDom)) {
int j = newNodeBackMap[succ];
if (j >= 0) {
builder.addEdge(0, j);
}
}
for (int i = 1; i <= mappedDomain.length; ++i) {
for (int succ : djGraph.getCfg().outgoingEdges(domainNodes[i - 1])) {
int j = newNodeBackMap[succ];
if (j > mappedDomain.length) {
builder.addEdge(i, j + mappedNonDomain.length);
} else if (j >= 0) {
builder.addEdge(i, j);
}
}
}
index = 0;
for (int i = mappedDomain.length + 1; i <= scc.length; ++i) {
for (int succ : djGraph.getCfg().outgoingEdges(nonDomainNodes[index++])) {
int j = newNodeBackMap[succ];
if (j >= 0) {
builder.addEdge(i, j);
if (j > mappedDomain.length) {
builder.addEdge(i + mappedNonDomain.length, j + mappedNonDomain.length);
} else {
builder.addEdge(i + mappedNonDomain.length, j);
}
}
}
}
handleLoops(new DJGraph(builder.build(), mappedWeight), newNodeMap);
}
private int[] withCopies(int[] nodes) {
IntegerArray nodesWithCopies = new IntegerArray(nodes.length);
for (int node : nodes) {
nodesWithCopies.add(node);
IntSet copies = nodeCopies[node];
if (copies != null) {
nodesWithCopies.addAll(copies.toArray());
}
}
return nodesWithCopies.getAll();
}
private int[] withoutCopies(int[] nodesWithCopies) {
IntSet visited = new IntHashSet();
int[] nodes = new int[nodesWithCopies.length];
int sz = 0;
for (int node : nodesWithCopies) {
node = nodeOriginals.get(node);
if (visited.add(node)) {
nodes[sz++] = node;
}
}
return Arrays.copyOf(nodes, sz);
}
private void registerCopies(int[] originalNodes, int[] copies) {
for (int i = 0; i < originalNodes.length; ++i) {
int original = nodeOriginals.get(originalNodes[i]);
int copy = copies[i];
IntSet knownCopies = nodeCopies[original];
if (knownCopies == null) {
knownCopies = new IntHashSet();
nodeCopies[original] = knownCopies;
}
if (knownCopies.add(copy)) {
while (nodeOriginals.size() <= copy) {
nodeOriginals.add(-1);
}
nodeOriginals.set(copy, original);
}
}
}
private void collapse(DJGraph djGraph, int[] scc, int[][] nodeMap) {
int cls = djGraph.collapse(scc);
IntegerArray nodes = new IntegerArray(djGraph.getGraph().size());
for (int representative : djGraph.classRepresentatives(cls)) {
for (int node : nodeMap[representative]) {
nodes.add(node);
}
}
for (int representative : djGraph.classRepresentatives(cls)) {
nodeMap[representative] = new int[0];
}
nodeMap[cls] = nodes.getAll();
}
private static int[] flatten(int[][] array) {
int count = 0;
for (int i = 0; i < array.length; ++i) {
count += array[i].length;
}
int[] flat = new int[count];
int index = 0;
for (int i = 0; i < array.length; ++i) {
int[] part = array[i];
for (int j = 0; j < part.length; ++j) {
flat[index++] = part[j];
}
}
return flat;
}
private static int[][] unflatten(int[] flat, int[][] pattern) {
int[][] rough = new int[pattern.length][];
int index = 0;
for (int i = 0; i < rough.length; ++i) {
int[] part = new int[pattern[i].length];
for (int j = 0; j < part.length; ++j) {
part[j] = flat[index++];
}
rough[i] = part;
}
return rough;
}
static class DJGraphNodeFilter implements IntPredicate {
private DJGraph graph;
private int level;
public DJGraphNodeFilter(DJGraph graph, int level) {
this.graph = graph;
this.level = level;
}
@Override
public boolean test(int node) {
return graph.levelOf(node) >= level;
}
}
}

View File

@ -0,0 +1,426 @@
/*
* Copyright 2021 Alexey Andreev.
*
* 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.
*/
package org.teavm.common;
import com.carrotsearch.hppc.IntArrayList;
import java.util.Arrays;
/**
* <p>Converts irreducible graph to reducible one using node splitting algorithm described at
* the paper &ldquo;Handling irreducible loops: optimized node splitting vs. DJ-graphs&rdquo; by
* Sebastian Unger and Frank Mueller.</p>
*
* <p>Appendix A of the paper contains pseudocode. We refer to this pseudocode below.</p>
*
* @author Alexey Andreev
*/
class IrreducibleGraphSplitter {
private GraphSplittingBackend backend;
private int[] idom;
private int[][] domNodes;
private MutableDirectedGraph cfg;
private int[] weights;
private IntArrayList[] realNodes;
private int[][] spBackEdges;
private int[] levels;
private int[] tmpArray;
private IntArrayList copiedRealNodes = new IntArrayList();
private int additionalWeight;
private int[] collapseMap;
IrreducibleGraphSplitter(GraphSplittingBackend backend, Graph src, int[] weights) {
this(backend, src, weights, initRealNodes(src.size()));
}
private static int[][] initRealNodes(int size) {
int[][] result = new int[size][];
for (int i = 0; i < size; ++i) {
result[i] = new int[] { i };
}
return result;
}
private IrreducibleGraphSplitter(GraphSplittingBackend backend, Graph src, int[] weights, int[][] realNodes) {
int size = src.size();
if (size != weights.length || size != realNodes.length) {
throw new IllegalArgumentException("Node count " + src.size() + " is not equal to weight array "
+ weights.length);
}
this.backend = backend;
tmpArray = new int[src.size()];
cfg = new MutableDirectedGraph(src);
DominatorTree domTree = GraphUtils.buildDominatorTree(src);
idom = new int[size];
for (int i = 0; i < size; ++i) {
idom[i] = domTree.immediateDominatorOf(i);
}
collapseMap = new int[size];
for (int i = 0; i < size; ++i) {
collapseMap[i] = i;
}
buildDomGraph();
buildLevels();
dfs();
this.realNodes = new IntArrayList[realNodes.length];
for (int i = 0; i < cfg.size(); ++i) {
this.realNodes[i] = IntArrayList.from(realNodes[i]);
}
this.weights = weights.clone();
}
// n-th element of output array (domGraph) will contain nodes, directly dominated by node n.
private void buildDomGraph() {
int size = cfg.size();
int[] domGraphCount = new int[size];
for (int i = 0; i < size; ++i) {
int j = idom[i];
if (j >= 0) {
domGraphCount[j]++;
}
}
int[][] domGraph = new int[size][];
for (int i = 0; i < size; ++i) {
domGraph[i] = new int[domGraphCount[i]];
domGraphCount[i] = 0;
}
for (int i = 0; i < size; ++i) {
int j = idom[i];
if (j >= 0) {
domGraph[j][domGraphCount[j]++] = i;
}
}
this.domNodes = domGraph;
}
// n-th element of output array (levels) will contain length of the path from root to node node N
// (paper calls this 'level').
private void buildLevels() {
int size = cfg.size();
levels = new int[size];
Arrays.fill(levels, -1);
levels[0] = 0;
for (int i = 1; i < size; ++i) {
if (levels[i] >= 0) {
continue;
}
int node = i;
int depth = 0;
while (levels[node] < 0) {
node = idom[node];
depth++;
}
int level = depth + levels[node];
node = i;
while (levels[node] < 0) {
levels[node] = level--;
node = idom[node];
}
}
}
// Find back edges.
// The n-th element of output array (sbBackEdges) will contain null if there is no back edges leading to n,
// or array of nodes m_i, where each edge m_i -> n is a back edge in spanning tree
// (m_i -> n is called 'SB back edge' in the paper).
private void dfs() {
int size = cfg.size();
spBackEdges = new int[size][];
int[] spBackEdgeCount = new int[size];
for (int i = 0; i < size; ++i) {
int count = cfg.incomingEdgesCount(i);
if (count > 0) {
spBackEdges[i] = new int[cfg.incomingEdgesCount(i)];
}
}
int[] state = new int[size];
int[] stack = new int[size * 2];
int top = 0;
stack[top++] = 0;
while (top > 0) {
int node = stack[--top];
switch (state[node]) {
case 0:
state[node] = 1;
stack[top++] = node;
for (int successor : cfg.outgoingEdges(node)) {
if (state[successor] == 0) {
stack[top++] = successor;
} else if (state[successor] == 1) {
spBackEdges[successor][spBackEdgeCount[successor]++] = node;
}
}
break;
case 1:
state[node] = 2;
break;
}
}
for (int i = 0; i < size; ++i) {
int[] back = spBackEdges[i];
if (back == null) {
continue;
}
int count = spBackEdgeCount[i];
if (count == 0) {
spBackEdges[i] = null;
} else if (count < spBackEdges[i].length) {
spBackEdges[i] = Arrays.copyOf(back, count);
}
}
}
// This is an implementation of 'split_loop' function from the paper.
// It does not take 'top' and 'set' parameter.
// Instead, it always starts with 0 node as top and assumes that all of the 'set' nodes are in the graph
// We rewrote this method to use stack instead of recursion. The only place where we need recursion
// is handleScc. We build a new instance of this class with corresponding subgraph.
void splitLoops() {
int size = cfg.size();
boolean[] cross = new boolean[size];
int[] stack = new int[size * 4];
int head = 0;
stack[head++] = 0;
stack[head++] = 0;
while (head > 0) {
int state = stack[--head];
int node = stack[--head];
if (state == 0) {
stack[head++] = node;
stack[head++] = 1;
int[] successors = domNodes[node];
for (int i = successors.length - 1; i >= 0; --i) {
stack[head++] = successors[i];
stack[head++] = 0;
}
} else {
if (cross[node]) {
for (int successor : domNodes[node]) {
collapse(successor);
}
handleIrreducibleChildren(node);
}
int[] back = spBackEdges[node];
int parent = idom[node];
if (back != null && parent >= 0) {
for (int predecessor : back) {
if (!dominates(node, predecessor)) {
cross[parent] = true;
break;
}
}
}
}
}
}
private void handleIrreducibleChildren(int top) {
Graph levelSubgraph = GraphUtils.subgraph(cfg, node -> node == top || idom[node] == top);
int[][] sccs = GraphUtils.findStronglyConnectedComponents(levelSubgraph);
for (int[] scc : sccs) {
if (scc.length > 1) {
handleStronglyConnectedComponent(top, scc);
}
}
}
private void handleStronglyConnectedComponent(int top, int[] scc) {
// Find header node
int domain = scc[0];
int maxWeight = weights[domain];
for (int i = 1; i < scc.length; ++i) {
int node = scc[i];
if (weights[node] > maxWeight) {
maxWeight = weights[node];
domain = node;
}
}
int[] realDomainNodes = realNodes[domain].toArray();
int realNodesToCopyCount = 0;
for (int node : scc) {
if (node != domain) {
realNodesToCopyCount += realNodes[node].size();
}
}
int[] realNodesToCopy = new int[realNodesToCopyCount];
realNodesToCopyCount = 0;
for (int node : scc) {
if (node != domain) {
int[] nodes = realNodes[node].toArray();
System.arraycopy(nodes, 0, realNodesToCopy, realNodesToCopyCount, nodes.length);
realNodesToCopyCount += nodes.length;
}
}
int[] realNodesCopies = backend.split(realDomainNodes, realNodesToCopy);
copiedRealNodes.add(realNodesCopies);
realNodes[top].add(realNodesCopies);
int copyWeight = 0;
for (int node : scc) {
if (node != domain) {
copyWeight += weights[node];
}
}
this.additionalWeight += copyWeight;
weights[top] += copyWeight;
int subgraphSize = scc.length * 2;
GraphBuilder subgraph = new GraphBuilder(subgraphSize);
int[][] subgraphRealNodes = new int[subgraphSize][];
int[] subgraphWeights = new int[subgraphSize];
int[] map = new int[cfg.size()];
int[] copyMap = new int[cfg.size()];
Arrays.fill(map, -1);
Arrays.fill(copyMap, -1);
map[top] = 0;
subgraphRealNodes[0] = realNodes[top].toArray();
subgraphWeights[0] = weights[top];
for (int i = 0; i < scc.length; ++i) {
int node = scc[i];
map[node] = i + 1;
subgraphRealNodes[i + 1] = realNodes[node].toArray();
subgraphWeights[i + 1] = weights[node];
}
int copyIndex = scc.length + 1;
int realNodeCopiesIndex = 0;
for (int node : scc) {
if (node == domain) {
continue;
}
copyMap[node] = copyIndex;
int realNodeCount = realNodes[node].size();
subgraphRealNodes[copyIndex] = Arrays.copyOfRange(realNodesCopies, realNodeCopiesIndex,
realNodeCopiesIndex + realNodeCount);
realNodeCopiesIndex += realNodeCount;
subgraphWeights[copyIndex] = weights[node];
copyIndex++;
}
for (int i = 0; i < scc.length; ++i) {
subgraph.addEdge(0, i + 1);
}
for (int node : scc) {
int subgraphNode = map[node];
int subgraphNodeCopy = copyMap[node];
int[] successors = cfg.outgoingEdges(node);
for (int successor : successors) {
// (x, y) = (node, successor)
int subgraphSuccessor = map[successor];
int subgraphSuccessorCopy = copyMap[successor];
if (subgraphSuccessorCopy >= 0) {
// y in S
if (subgraphNodeCopy >= 0) {
// x in S
subgraph.addEdge(subgraphNodeCopy, subgraphSuccessorCopy); // 8.4
if (subgraphSuccessor >= 0) {
subgraph.addEdge(subgraphNode, subgraphSuccessor); // 8.1
}
} else {
// x !in S (x in domain(h))
subgraph.addEdge(subgraphNode, subgraphSuccessorCopy); // 8.2
}
} else if (subgraphSuccessor >= 0) {
// y !in S (y in N\S)
if (subgraphNodeCopy >= 0) {
subgraph.addEdge(subgraphNodeCopy, subgraphSuccessor); // 8.3
}
subgraph.addEdge(subgraphNode, subgraphSuccessor); // 8.1
}
}
}
IrreducibleGraphSplitter subgraphSplitter = new IrreducibleGraphSplitter(backend, subgraph.build(),
subgraphWeights, subgraphRealNodes);
subgraphSplitter.splitLoops();
copiedRealNodes.addAll(subgraphSplitter.copiedRealNodes);
realNodes[top].addAll(subgraphSplitter.copiedRealNodes);
additionalWeight += subgraphSplitter.additionalWeight;
weights[top] += subgraphSplitter.additionalWeight;
}
private boolean dominates(int dominator, int node) {
int targetLevel = levels[dominator];
int level = levels[node];
while (level-- > targetLevel) {
node = idom[node];
}
return node == dominator;
}
private void collapse(int top) {
if (domNodes[top] == null || domNodes[top].length == 0) {
return;
}
int count = findAllDominatedNodes(top);
int[] nodes = tmpArray;
IntArrayList topRealNodes = realNodes[top];
for (int i = 1; i < count; ++i) {
int node = nodes[i];
topRealNodes.addAll(realNodes[node]);
realNodes[node] = null;
weights[top] += weights[node];
collapseMap[node] = top;
}
// Alter graphs
for (int i = 1; i < count; ++i) {
int node = nodes[i];
for (int succ : cfg.outgoingEdges(node)) {
int mappedSucc = collapseMap[succ];
if (mappedSucc != top || succ == top) {
cfg.addEdge(top, mappedSucc);
}
}
for (int pred : cfg.incomingEdges(node)) {
int mappedPred = collapseMap[pred];
if (mappedPred != top) {
cfg.addEdge(mappedPred, top);
}
}
cfg.detachNode(node);
}
domNodes[top] = null;
}
private int findAllDominatedNodes(int top) {
int[] result = tmpArray;
int count = 0;
int head = 0;
result[count++] = top;
while (head < count) {
int[] successors = domNodes[result[head]];
if (successors != null && successors.length > 0) {
System.arraycopy(successors, 0, result, count, successors.length);
count += successors.length;
}
++head;
}
return count;
}
}

View File

@ -15,15 +15,15 @@
*/
package org.teavm.common;
import com.carrotsearch.hppc.IntArrayList;
import com.carrotsearch.hppc.IntHashSet;
import com.carrotsearch.hppc.IntSet;
import com.carrotsearch.hppc.cursors.IntCursor;
import java.util.ArrayList;
import java.util.List;
public class MutableDirectedGraph implements Graph {
private List<IntSet> successors = new ArrayList<>();
private List<IntSet> predecessors = new ArrayList<>();
private List<NodeSet> successors = new ArrayList<>();
private List<NodeSet> predecessors = new ArrayList<>();
public MutableDirectedGraph() {
}
@ -36,18 +36,29 @@ public class MutableDirectedGraph implements Graph {
addEdge(i, data[j]);
}
}
while (successors.size() < graph.size()) {
successors.add(new NodeSet());
predecessors.add(new NodeSet());
}
}
public Graph copyToImmutable() {
GraphBuilder builder = new GraphBuilder(successors.size());
for (int i = 0; i < successors.size(); ++i) {
for (IntCursor cursor : successors.get(i)) {
builder.addEdge(i, cursor.value);
for (IntCursor successor : successors.get(i).list) {
builder.addEdge(i, successor.value);
}
}
return builder.build();
}
public int addNode() {
int index = successors.size();
successors.add(new NodeSet());
predecessors.add(new NodeSet());
return index;
}
@Override
public int size() {
return successors.size();
@ -56,41 +67,62 @@ public class MutableDirectedGraph implements Graph {
public void addEdge(int from, int to) {
int max = Math.max(from, to);
while (max >= successors.size()) {
successors.add(new IntHashSet(1));
predecessors.add(new IntHashSet(1));
successors.add(new NodeSet());
predecessors.add(new NodeSet());
}
NodeSet successorNodes = successors.get(from);
if (successorNodes.set.add(to)) {
successorNodes.list.add(to);
NodeSet predecessorNodes = predecessors.get(to);
predecessorNodes.set.add(from);
predecessorNodes.list.add(from);
}
successors.get(from).add(to);
predecessors.get(to).add(from);
}
public void deleteEdge(int from, int to) {
if (from >= successors.size() || to >= successors.size()) {
return;
}
successors.get(from).removeAll(to);
predecessors.get(to).removeAll(from);
NodeSet successorNodes = successors.get(from);
if (successorNodes.set.removeAll(to) > 0) {
successorNodes.list.removeAll(to);
NodeSet predecessorNodes = predecessors.get(to);
predecessorNodes.set.removeAll(from);
predecessorNodes.list.removeAll(from);
}
}
public void detachNode(int node) {
for (IntCursor succ : successors.get(node)) {
predecessors.get(succ.value).removeAll(node);
for (IntCursor succ : successors.get(node).list) {
NodeSet predecessorNodes = predecessors.get(succ.value);
predecessorNodes.set.removeAll(node);
predecessorNodes.list.removeAll(node);
}
for (IntCursor pred : predecessors.get(node)) {
successors.get(pred.value).removeAll(node);
for (IntCursor pred : predecessors.get(node).list) {
NodeSet successorNodes = successors.get(pred.value);
successorNodes.set.removeAll(node);
successorNodes.list.removeAll(node);
}
predecessors.get(node).clear();
successors.get(node).clear();
NodeSet predecessorNodes = predecessors.get(node);
predecessorNodes.list.clear();
predecessorNodes.set.clear();
NodeSet successorNodes = successors.get(node);
successorNodes.list.clear();
predecessorNodes.list.clear();
}
@Override
public int[] incomingEdges(int node) {
return predecessors.get(node).toArray();
return predecessors.get(node).list.toArray();
}
@Override
public int copyIncomingEdges(int node, int[] target) {
int index = 0;
for (IntCursor cursor : predecessors.get(node)) {
for (IntCursor cursor : predecessors.get(node).list) {
target[index++] = cursor.value;
}
return index;
@ -98,13 +130,13 @@ public class MutableDirectedGraph implements Graph {
@Override
public int[] outgoingEdges(int node) {
return successors.get(node).toArray();
return successors.get(node).list.toArray();
}
@Override
public int copyOutgoingEdges(int node, int[] target) {
int index = 0;
for (IntCursor cursor : successors.get(node)) {
for (IntCursor cursor : successors.get(node).list) {
target[index++] = cursor.value;
}
return index;
@ -112,11 +144,21 @@ public class MutableDirectedGraph implements Graph {
@Override
public int incomingEdgesCount(int node) {
return predecessors.get(node).size();
return predecessors.get(node).list.size();
}
@Override
public int outgoingEdgesCount(int node) {
return successors.get(node).size();
return successors.get(node).list.size();
}
@Override
public String toString() {
return GraphUtils.printToDot(this);
}
static class NodeSet {
IntHashSet set = new IntHashSet(1);
IntArrayList list = new IntArrayList(1);
}
}

View File

@ -151,7 +151,7 @@ public class GraphTest {
assertTrue("Should be irreducible", GraphUtils.isIrreducible(graph));
assertFalse("Should be reducible", GraphUtils.isIrreducible(result));
assertTrue("Should be equialent", isEquialent(backend, graph));
assertTrue("Should be equivalent", isEquivalent(backend, graph));
}
@Test
@ -171,7 +171,7 @@ public class GraphTest {
assertTrue("Should be irreducible", GraphUtils.isIrreducible(graph));
assertFalse("Should be reducible", GraphUtils.isIrreducible(result));
assertTrue("Should be equivalent", isEquialent(backend, graph));
assertTrue("Should be equivalent", isEquivalent(backend, graph));
}
@Test
@ -203,10 +203,137 @@ public class GraphTest {
assertTrue("Should be irreducible", GraphUtils.isIrreducible(graph));
assertFalse("Should be reducible", GraphUtils.isIrreducible(result));
assertTrue("Should be equivalent", isEquialent(backend, graph));
assertTrue("Should be equivalent", isEquivalent(backend, graph));
}
private boolean isEquialent(DefaultGraphSplittingBackend backend, Graph proto) {
@Test
public void irreducibleGraphSplit4() {
GraphBuilder builder = new GraphBuilder();
builder.addEdge(0, 1);
builder.addEdge(0, 2);
builder.addEdge(0, 4);
builder.addEdge(1, 2);
builder.addEdge(2, 3);
builder.addEdge(3, 4);
builder.addEdge(4, 1);
Graph graph = builder.build();
DefaultGraphSplittingBackend backend = new DefaultGraphSplittingBackend(graph);
int[] weights = new int[graph.size()];
Arrays.fill(weights, 1);
GraphUtils.splitIrreducibleGraph(graph, weights, backend);
Graph result = backend.getGraph();
assertTrue("Should be irreducible", GraphUtils.isIrreducible(graph));
assertFalse("Should be reducible", GraphUtils.isIrreducible(result));
assertTrue("Should be equivalent", isEquivalent(backend, graph));
}
@Test
public void irreducibleGraphSplit5() {
GraphBuilder builder = new GraphBuilder();
addEdges(builder, 0, 1);
addEdges(builder, 1, 2, 3);
addEdges(builder, 2, 5);
addEdges(builder, 3, 2, 4);
addEdges(builder, 4, 5);
addEdges(builder, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22);
addEdges(builder, 6, 69, 70);
addEdges(builder, 7, 69);
addEdges(builder, 8, 32);
addEdges(builder, 9, 63);
addEdges(builder, 10, 61);
addEdges(builder, 11, 59);
addEdges(builder, 12, 57);
addEdges(builder, 13, 55);
addEdges(builder, 14, 53);
addEdges(builder, 15, 42);
addEdges(builder, 16, 44);
addEdges(builder, 17, 46);
addEdges(builder, 18, 48);
addEdges(builder, 19, 50);
addEdges(builder, 20, 65);
addEdges(builder, 21, 67);
addEdges(builder, 23, 24, 25);
addEdges(builder, 24, 67, 68);
addEdges(builder, 25, 26, 27);
addEdges(builder, 26, 28, 29);
addEdges(builder, 28, 30, 31);
addEdges(builder, 30, 65, 66);
addEdges(builder, 31, 32, 33);
addEdges(builder, 32, 34, 35, 36, 37, 38, 39, 40, 41);
addEdges(builder, 34, 63, 64);
addEdges(builder, 35, 61, 62);
addEdges(builder, 36, 59, 60);
addEdges(builder, 37, 57, 58);
addEdges(builder, 38, 55, 56);
addEdges(builder, 39, 53, 54);
addEdges(builder, 40, 42, 43);
addEdges(builder, 42, 44, 45);
addEdges(builder, 44, 46, 47);
addEdges(builder, 46, 48, 49);
addEdges(builder, 48, 50, 51);
addEdges(builder, 50, 52);
addEdges(builder, 52, 23);
addEdges(builder, 53, 52);
addEdges(builder, 55, 52);
addEdges(builder, 57, 52);
addEdges(builder, 59, 52);
addEdges(builder, 61, 52);
addEdges(builder, 63, 52);
addEdges(builder, 65, 52);
addEdges(builder, 69, 23);
Graph graph = builder.build();
DefaultGraphSplittingBackend backend = new DefaultGraphSplittingBackend(graph);
int[] weights = new int[graph.size()];
Arrays.fill(weights, 1);
GraphUtils.splitIrreducibleGraph(graph, weights, backend);
Graph result = backend.getGraph();
assertTrue("Should be irreducible", GraphUtils.isIrreducible(graph));
assertFalse("Should be reducible", GraphUtils.isIrreducible(result));
assertTrue("Should be equivalent", isEquivalent(backend, graph));
}
@Test
public void irreducibleGraphSplit6() {
GraphBuilder builder = new GraphBuilder();
addEdges(builder, 0, 1, 3, 6, 9);
addEdges(builder, 1, 2);
addEdges(builder, 2, 3);
addEdges(builder, 3, 4);
addEdges(builder, 4, 5, 8);
addEdges(builder, 5, 6);
addEdges(builder, 6, 7);
addEdges(builder, 7, 11);
addEdges(builder, 8, 9);
addEdges(builder, 9, 10);
addEdges(builder, 10, 11);
addEdges(builder, 11, 12);
addEdges(builder, 12, 1);
Graph graph = builder.build();
DefaultGraphSplittingBackend backend = new DefaultGraphSplittingBackend(graph);
int[] weights = new int[graph.size()];
Arrays.fill(weights, 1);
GraphUtils.splitIrreducibleGraph(graph, weights, backend);
Graph result = backend.getGraph();
assertTrue("Should be irreducible", GraphUtils.isIrreducible(graph));
assertFalse("Should be reducible", GraphUtils.isIrreducible(result));
assertTrue("Should be equivalent", isEquivalent(backend, graph));
}
private static void addEdges(GraphBuilder builder, int from, int... to) {
for (int target : to) {
builder.addEdge(from, target);
}
}
private boolean isEquivalent(DefaultGraphSplittingBackend backend, Graph proto) {
Graph graph = backend.getGraph();
for (int node = 0; node < graph.size(); ++node) {
int nodeProto = backend.prototype(node);