Fix node splitting in irreducible CFG

This commit is contained in:
Alexey Andreev 2023-09-19 20:15:51 +02:00
parent 7589eb23fc
commit 1cd635afa5
7 changed files with 216 additions and 165 deletions

View File

@ -484,13 +484,13 @@ public class CoroutineTransformation {
class SplittingBackend implements GraphSplittingBackend {
@Override
public int[] split(int[] domain, int[] nodes) {
int[] copies = new int[nodes.length];
public int[] split(int[] remaining, int[] toCopy) {
int[] copies = new int[toCopy.length];
var map = new IntIntHashMap();
IntSet nodeSet = IntHashSet.from(nodes);
IntSet nodeSet = IntHashSet.from(toCopy);
var outputs = ProgramUtils.getPhiOutputs(program);
for (int i = 0; i < nodes.length; ++i) {
int node = nodes[i];
for (int i = 0; i < toCopy.length; ++i) {
int node = toCopy[i];
BasicBlock block = program.basicBlockAt(node);
BasicBlock blockCopy = program.createBasicBlock();
ProgramUtils.copyBasicBlock(block, blockCopy);
@ -505,13 +505,13 @@ public class CoroutineTransformation {
for (int copy : copies) {
copyBlockMapper.transform(program.basicBlockAt(copy));
}
for (int domainNode : domain) {
for (int domainNode : remaining) {
copyBlockMapper.transformWithoutPhis(program.basicBlockAt(domainNode));
}
var domainSet = IntHashSet.from(domain);
for (int i = 0; i < nodes.length; ++i) {
int node = nodes[i];
var domainSet = IntHashSet.from(remaining);
for (int i = 0; i < toCopy.length; ++i) {
int node = toCopy[i];
BasicBlock blockCopy = program.basicBlockAt(copies[i]);
for (Incoming output : outputs.get(node)) {
if (!nodeSet.contains(output.getPhi().getBasicBlock().getIndex())) {

View File

@ -16,7 +16,6 @@
package org.teavm.common;
import com.carrotsearch.hppc.IntIntHashMap;
import com.carrotsearch.hppc.IntIntMap;
public class DefaultGraphSplittingBackend implements GraphSplittingBackend {
private MutableDirectedGraph graph;
@ -46,37 +45,36 @@ public class DefaultGraphSplittingBackend implements GraphSplittingBackend {
}
@Override
public int[] split(int[] domain, int[] nodes) {
int[] copies = new int[nodes.length];
IntIntMap map = new IntIntHashMap();
for (int i = 0; i < nodes.length; ++i) {
public int[] split(int[] remaining, int[] toCopy) {
var copies = new int[toCopy.length];
var map = new IntIntHashMap();
for (int i = 0; i < toCopy.length; ++i) {
copies[i] = index++;
map.put(nodes[i], copies[i] + 1);
int proto = prototypeNodes.get(nodes[i]);
map.put(toCopy[i], copies[i]);
int proto = prototypeNodes.get(toCopy[i]);
prototypeNodes.add(proto);
copyIndexes.add(++copyCount[proto]);
}
for (int i = 0; i < domain.length; ++i) {
int node = domain[i];
for (int i = 0; i < remaining.length; ++i) {
int node = remaining[i];
for (int succ : graph.outgoingEdges(node)) {
int succCopy = map.get(succ);
if (succCopy == 0) {
int succCopy = map.getOrDefault(succ, -1);
if (succCopy < 0) {
continue;
}
--succCopy;
graph.deleteEdge(node, succ);
graph.addEdge(node, succCopy);
}
}
for (int i = 0; i < nodes.length; ++i) {
int node = nodes[i];
for (int i = 0; i < toCopy.length; ++i) {
int node = toCopy[i];
int nodeCopy = copies[i];
for (int succ : graph.outgoingEdges(node)) {
int succCopy = map.get(succ);
if (succCopy != 0) {
graph.addEdge(nodeCopy, succCopy - 1);
int succCopy = map.getOrDefault(succ, -1);
if (succCopy >= 0) {
graph.addEdge(nodeCopy, succCopy);
} else {
graph.addEdge(nodeCopy, succ);
}

View File

@ -16,5 +16,5 @@
package org.teavm.common;
public interface GraphSplittingBackend {
int[] split(int[] domain, int[] nodes);
int[] split(int[] remaining, int[] toCopy);
}

View File

@ -29,14 +29,14 @@ import java.util.Arrays;
*/
class IrreducibleGraphSplitter {
private GraphSplittingBackend backend;
private int[] idom;
private DominatorTree dom;
private int[][] domNodes;
private MutableDirectedGraph cfg;
private int[] weights;
private IntArrayList[] realNodes;
private int[][] spBackEdges;
private int[] levels;
private int[] tmpArray;
private int[] tmpArray2;
private IntArrayList copiedRealNodes = new IntArrayList();
private int additionalWeight;
private int[] collapseMap;
@ -61,18 +61,14 @@ class IrreducibleGraphSplitter {
}
this.backend = backend;
tmpArray = new int[src.size()];
tmpArray2 = 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);
}
dom = GraphUtils.buildDominatorTree(src);
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) {
@ -84,20 +80,20 @@ class IrreducibleGraphSplitter {
// 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];
var domGraphCount = new int[size];
for (int i = 0; i < size; ++i) {
int j = idom[i];
int j = dom.immediateDominatorOf(i);
if (j >= 0) {
domGraphCount[j]++;
}
}
int[][] domGraph = new int[size][];
var 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];
int j = dom.immediateDominatorOf(i);
if (j >= 0) {
domGraph[j][domGraphCount[j]++] = i;
}
@ -105,34 +101,6 @@ class IrreducibleGraphSplitter {
this.domNodes = domGraph;
}
// n-th element of output array (levels) will contain length of the path from root to 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 || idom[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
@ -190,13 +158,13 @@ class IrreducibleGraphSplitter {
// 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
// Instead, it always starts with 0 node as top and assumes that all 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];
var cross = new boolean[size];
var stack = new int[size * 4];
int head = 0;
stack[head++] = 0;
@ -214,28 +182,28 @@ class IrreducibleGraphSplitter {
}
} else {
if (cross[node]) {
for (int successor : domNodes[node]) {
collapse(successor);
}
handleIrreducibleChildren(node);
}
int[] back = spBackEdges[node];
int parent = idom[node];
int parent = dom.immediateDominatorOf(node);
if (back != null && parent >= 0) {
for (int predecessor : back) {
if (!dominates(node, predecessor)) {
if (!dom.dominates(node, predecessor)) {
cross[parent] = true;
break;
}
}
}
if (domNodes[node].length > 1) {
collapse(node);
}
}
}
}
private void handleIrreducibleChildren(int top) {
Graph levelSubgraph = GraphUtils.subgraph(cfg, node -> node == top || idom[node] == top);
int[][] sccs = GraphUtils.findStronglyConnectedComponents(levelSubgraph);
var levelSubgraph = GraphUtils.subgraph(cfg, node -> node != top && dom.dominates(top, node));
var sccs = GraphUtils.findStronglyConnectedComponents(levelSubgraph);
for (int[] scc : sccs) {
if (scc.length > 1) {
handleStronglyConnectedComponent(top, scc);
@ -244,75 +212,100 @@ class IrreducibleGraphSplitter {
}
private void handleStronglyConnectedComponent(int top, int[] scc) {
var domainCount = 0;
for (var node : scc) {
if (dom.immediateDominatorOf(node) == top) {
++domainCount;
}
}
if (domainCount < 2) {
return;
}
// 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;
var domains = fillDomains(top, scc);
var localWeights = fillWeights(domains, scc);
int remaining = -1;
int maxWeight = 0;
for (var node : scc) {
if (domains[node] != node) {
continue;
}
if (remaining < 0 || localWeights[node] > maxWeight) {
maxWeight = localWeights[node];
remaining = node;
}
}
int[] realDomainNodes = realNodes[domain].toArray();
var realRemainingNodesCount = 0;
int realNodesToCopyCount = 0;
int copyWeight = 0;
int remainingNodeCount = 0;
for (int node : scc) {
if (node != domain) {
if (domains[node] != remaining) {
realNodesToCopyCount += realNodes[node].size();
copyWeight += weights[node];
} else {
remainingNodeCount++;
realRemainingNodesCount += realNodes[node].size();
}
}
int[] realNodesToCopy = new int[realNodesToCopyCount];
var realNodesToCopy = new int[realNodesToCopyCount];
var realRemainingNodes = new int[realRemainingNodesCount];
realNodesToCopyCount = 0;
realRemainingNodesCount = 0;
for (int node : scc) {
if (node != domain) {
int[] nodes = realNodes[node].toArray();
var nodes = realNodes[node].toArray();
if (domains[node] != remaining) {
System.arraycopy(nodes, 0, realNodesToCopy, realNodesToCopyCount, nodes.length);
realNodesToCopyCount += nodes.length;
} else {
System.arraycopy(nodes, 0, realRemainingNodes, realRemainingNodesCount, nodes.length);
realRemainingNodesCount += nodes.length;
}
}
int[] realNodesCopies = backend.split(realDomainNodes, realNodesToCopy);
var realNodesCopies = backend.split(realRemainingNodes, 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()];
int subgraphSize = scc.length * 2 + 1 - remainingNodeCount;
var subgraph = new GraphBuilder(subgraphSize);
var subgraphRealNodes = new int[subgraphSize][];
var subgraphWeights = new int[subgraphSize];
var map = new int[cfg.size()];
var 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];
subgraphRealNodes[0] = new int[0];
subgraphWeights[0] = 0;
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];
if (domains[node] == node) {
var hasPredecessorsOutsideLoop = false;
for (var pred : cfg.incomingEdges(node)) {
if (domains[pred] < 0) {
hasPredecessorsOutsideLoop = true;
break;
}
}
if (hasPredecessorsOutsideLoop) {
subgraph.addEdge(0, i + 1);
}
}
}
int copyIndex = scc.length + 1;
int realNodeCopiesIndex = 0;
for (int node : scc) {
if (node == domain) {
if (domains[node] == remaining) {
continue;
}
copyMap[node] = copyIndex;
int realNodeCount = realNodes[node].size();
if (node == top) {
realNodeCount -= realNodesCopies.length;
}
subgraphRealNodes[copyIndex] = Arrays.copyOfRange(realNodesCopies, realNodeCopiesIndex,
realNodeCopiesIndex + realNodeCount);
realNodeCopiesIndex += realNodeCount;
@ -320,13 +313,10 @@ class IrreducibleGraphSplitter {
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);
var successors = cfg.outgoingEdges(node);
for (int successor : successors) {
// (x, y) = (node, successor)
int subgraphSuccessor = map[successor];
@ -354,22 +344,60 @@ class IrreducibleGraphSplitter {
}
}
IrreducibleGraphSplitter subgraphSplitter = new IrreducibleGraphSplitter(backend, subgraph.build(),
var 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;
realNodes[top].add(realNodesCopies);
weights[top] += subgraphSplitter.additionalWeight + copyWeight;
copiedRealNodes.addAll(subgraphSplitter.copiedRealNodes);
additionalWeight += subgraphSplitter.additionalWeight + copyWeight;
}
private boolean dominates(int dominator, int node) {
int targetLevel = levels[dominator];
int level = levels[node];
while (level-- > targetLevel) {
node = idom[node];
private int[] fillDomains(int top, int[] nodes) {
var domains = tmpArray;
Arrays.fill(domains, -1);
for (var node : nodes) {
var domain = domains[node];
if (domain < 0) {
while (true) {
var parent = dom.immediateDominatorOf(node);
if (parent == top) {
domain = node;
break;
}
domain = domains[parent];
if (domain >= 0) {
break;
}
domains[parent] = node;
node = parent;
}
while (true) {
var next = domains[node];
domains[node] = domain;
if (next == -1) {
break;
}
node = next;
}
}
}
return node == dominator;
return domains;
}
private int[] fillWeights(int[] domains, int[] nodes) {
var localWeights = tmpArray2;
for (var node : nodes) {
if (domains[node] == node) {
localWeights[node] = 0;
}
}
for (var node : nodes) {
localWeights[domains[node]] += weights[node];
}
return localWeights;
}
private void collapse(int top) {
@ -377,9 +405,9 @@ class IrreducibleGraphSplitter {
return;
}
int count = findAllDominatedNodes(top);
int[] nodes = tmpArray;
var nodes = tmpArray;
IntArrayList topRealNodes = realNodes[top];
var topRealNodes = realNodes[top];
for (int i = 1; i < count; ++i) {
int node = nodes[i];
topRealNodes.addAll(realNodes[node]);
@ -410,13 +438,13 @@ class IrreducibleGraphSplitter {
}
private int findAllDominatedNodes(int top) {
int[] result = tmpArray;
var result = tmpArray;
int count = 0;
int head = 0;
result[count++] = top;
while (head < count) {
int[] successors = domNodes[result[head]];
var successors = domNodes[result[head]];
if (successors != null && successors.length > 0) {
System.arraycopy(successors, 0, result, count, successors.length);
count += successors.length;

View File

@ -338,11 +338,11 @@ public class AsyncProgramSplitter {
}
@Override
public int[] split(int[] domain, int[] nodes) {
int[] copies = inner.split(domain, nodes);
public int[] split(int[] remaining, int[] toCopy) {
int[] copies = inner.split(remaining, toCopy);
for (int i = 0; i < copies.length; ++i) {
int copy = copies[i];
int node = nodes[i];
int node = toCopy[i];
if (blockSuccessors.size() <= copy) {
blockSuccessors.add(-1);
splitPoints.add(null);

View File

@ -29,16 +29,16 @@ public class ProgramNodeSplittingBackend implements GraphSplittingBackend {
}
@Override
public int[] split(int[] domain, int[] nodes) {
int[] copies = new int[nodes.length];
public int[] split(int[] remaining, int[] toCopy) {
int[] copies = new int[toCopy.length];
IntIntMap map = new IntIntHashMap();
for (int i = 0; i < nodes.length; ++i) {
int node = nodes[i];
for (int i = 0; i < toCopy.length; ++i) {
int node = toCopy[i];
BasicBlock block = program.basicBlockAt(node);
BasicBlock blockCopy = program.createBasicBlock();
ProgramUtils.copyBasicBlock(block, blockCopy);
copies[i] = blockCopy.getIndex();
map.put(nodes[i], copies[i] + 1);
map.put(toCopy[i], copies[i] + 1);
}
BasicBlockMapper copyBlockMapper = new BasicBlockMapper((int block) -> {
int mappedIndex = map.get(block);
@ -47,7 +47,7 @@ public class ProgramNodeSplittingBackend implements GraphSplittingBackend {
for (int copy : copies) {
copyBlockMapper.transform(program.basicBlockAt(copy));
}
for (int domainNode : domain) {
for (int domainNode : remaining) {
copyBlockMapper.transform(program.basicBlockAt(domainNode));
}
return copies;

View File

@ -144,14 +144,12 @@ public class GraphTest {
builder.addEdge(5, 3);
Graph graph = builder.build();
DefaultGraphSplittingBackend backend = new DefaultGraphSplittingBackend(graph);
var backend = new DefaultGraphSplittingBackend(graph);
int[] weights = { 1, 4, 1, 10, 1, 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));
check(graph, result, backend);
}
@Test
@ -163,15 +161,13 @@ public class GraphTest {
builder.addEdge(2, 1);
Graph graph = builder.build();
DefaultGraphSplittingBackend backend = new DefaultGraphSplittingBackend(graph);
var 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));
check(graph, result, backend);
}
@Test
@ -195,15 +191,13 @@ public class GraphTest {
builder.addEdge(7, 8);
builder.addEdge(8, 7);
Graph graph = builder.build();
DefaultGraphSplittingBackend backend = new DefaultGraphSplittingBackend(graph);
var 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));
check(graph, result, backend);
}
@Test
@ -219,15 +213,13 @@ public class GraphTest {
builder.addEdge(4, 1);
Graph graph = builder.build();
DefaultGraphSplittingBackend backend = new DefaultGraphSplittingBackend(graph);
var 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));
check(graph, result, backend);
}
@Test
@ -287,15 +279,13 @@ public class GraphTest {
addEdges(builder, 69, 23);
Graph graph = builder.build();
DefaultGraphSplittingBackend backend = new DefaultGraphSplittingBackend(graph);
var 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));
check(graph, result, backend);
}
@Test
@ -316,15 +306,13 @@ public class GraphTest {
addEdges(builder, 12, 1);
Graph graph = builder.build();
DefaultGraphSplittingBackend backend = new DefaultGraphSplittingBackend(graph);
var 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));
check(graph, result, backend);
}
@Test
@ -371,9 +359,46 @@ public class GraphTest {
GraphUtils.splitIrreducibleGraph(graph, weights, backend);
var result = backend.getGraph();
assertTrue("Should be irreducible", GraphUtils.isIrreducible(graph));
check(graph, result, backend);
}
@Test
public void irreducibleGraphSplit8() {
var builder = new GraphBuilder();
addEdges(builder, 0, 2, 1);
addEdges(builder, 1, 2, 3);
addEdges(builder, 2, 4);
addEdges(builder, 3, 9);
addEdges(builder, 4, 5);
addEdges(builder, 5, 6, 7);
addEdges(builder, 6, 4);
addEdges(builder, 7, 8, 10);
addEdges(builder, 8, 7, 10, 9);
addEdges(builder, 9, 8, 4);
var graph = builder.build();
var backend = new DefaultGraphSplittingBackend(graph);
var weights = new int[] { 3, 2, 1, 9, 34, 6, 1, 6, 8, 24, 3 };
GraphUtils.splitIrreducibleGraph(graph, weights, backend);
var result = backend.getGraph();
check(graph, result, backend);
}
private void check(Graph original, Graph result, DefaultGraphSplittingBackend backend) {
assertTrue("Should be irreducible", GraphUtils.isIrreducible(original));
assertFalse("Should be reducible", GraphUtils.isIrreducible(result));
assertTrue("Should be equivalent", isEquivalent(backend, graph));
assertTrue("Should be equivalent", isEquivalent(backend, original));
assertFalse("Should not have disconnected nodes", hasDisconnectedNodes(result));
}
private boolean hasDisconnectedNodes(Graph graph) {
for (var i = 1; i < graph.size(); ++i) {
if (graph.incomingEdgesCount(i) == 0) {
return true;
}
}
return false;
}
private static void addEdges(GraphBuilder builder, int from, int... to) {