diff --git a/teavm-core/src/main/java/org/teavm/common/GraphUtils.java b/teavm-core/src/main/java/org/teavm/common/GraphUtils.java index d181d8e98..ec8fcf277 100644 --- a/teavm-core/src/main/java/org/teavm/common/GraphUtils.java +++ b/teavm-core/src/main/java/org/teavm/common/GraphUtils.java @@ -31,37 +31,8 @@ public final class GraphUtils { private GraphUtils() { } - public static Graph invert(Graph graph) { + public static int[] findBackEdges(Graph graph) { int sz = graph.size(); - GraphBuilder result = new GraphBuilder(); - int[] sourceEdges = new int[sz]; - for (int node = 0; node < sz; ++node) { - int sourceCount = graph.copyIncomingEdges(node, sourceEdges); - for (int i = 0; i < sourceCount; ++i) { - int source = sourceEdges[i]; - result.addEdge(node, source); - } - } - return result.build(); - } - - public static Graph close(Graph graph) { - GraphBuilder result = new GraphBuilder(); - for (int node = 0; node < graph.size(); ++node) { - int[] next = graph.outgoingEdges(node); - for (int target : next) { - result.addEdge(node, target); - } - if (next.length == 0) { - result.addEdge(node, graph.size()); - } - } - return result.build(); - } - - public static Graph removeLoops(Graph graph) { - int sz = graph.size(); - GraphBuilder result = new GraphBuilder(); int[] stack = new int[sz * 2]; int stackSize = 0; byte[] state = new byte[sz]; @@ -70,6 +41,7 @@ public final class GraphUtils { stack[stackSize++] = i; } } + IntegerArray result = new IntegerArray(2); while (stackSize > 0) { int node = stack[--stackSize]; switch (state[node]) { @@ -79,11 +51,11 @@ public final class GraphUtils { for (int next : graph.outgoingEdges(node)) { switch (state[next]) { case NONE: - result.addEdge(node, next); stack[stackSize++] = next; break; - case VISITED: - result.addEdge(node, next); + case VISITING: + result.add(node); + result.add(next); break; } } @@ -93,22 +65,25 @@ public final class GraphUtils { break; } } - return result.build(); + return result.getAll(); } - public static int edgeCount(Graph graph) { - int cnt = 0; - int sz = graph.size(); - for (int node = 0; node < sz; ++node) { - cnt += graph.outgoingEdgesCount(node); + public static boolean isIrreducible(Graph graph) { + DominatorTree dom = buildDominatorTree(graph); + int[] backEdges = findBackEdges(graph); + for (int i = 0; i < backEdges.length; i += 2) { + if (!dom.dominates(backEdges[i + 1], backEdges[i])) { + return true; + } } - return cnt; + return false; } /* * Tarjan's algorithm */ public static int[][] findStronglyConnectedComponents(Graph graph, int[] start, GraphNodeFilter filter) { + // TODO: can show incorrect behaviour sometimes List components = new ArrayList<>(); int[] visitIndex = new int[graph.size()]; int[] headerIndex = new int[graph.size()]; diff --git a/teavm-core/src/test/java/org/teavm/common/GraphTest.java b/teavm-core/src/test/java/org/teavm/common/GraphTest.java index bfb8911c6..4ae932305 100644 --- a/teavm-core/src/test/java/org/teavm/common/GraphTest.java +++ b/teavm-core/src/test/java/org/teavm/common/GraphTest.java @@ -17,6 +17,8 @@ package org.teavm.common; import static org.hamcrest.CoreMatchers.*; import static org.junit.Assert.*; +import com.carrotsearch.hppc.IntOpenHashSet; +import com.carrotsearch.hppc.IntSet; import java.util.Arrays; import java.util.Comparator; import org.junit.Test; @@ -86,6 +88,71 @@ public class GraphTest { assertThat(sccs[0], is(new int[] { 1, 2, 3, 4, 5 })); } + @Test + public void irreducibleGraphSplit() { + GraphBuilder builder = new GraphBuilder(); + builder.addEdge(0, 1); + builder.addEdge(0, 2); + builder.addEdge(0, 3); + builder.addEdge(1, 2); + builder.addEdge(2, 1); + builder.addEdge(3, 2); + builder.addEdge(2, 4); + builder.addEdge(4, 5); + builder.addEdge(4, 1); + builder.addEdge(5, 3); + + Graph graph = builder.build(); + DefaultGraphSplittingBackend 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 equialent", isEquialent(backend, graph)); + } + + @Test + public void irreducibleGraphSplit2() { + GraphBuilder builder = new GraphBuilder(); + builder.addEdge(0, 1); + builder.addEdge(0, 2); + builder.addEdge(1, 2); + builder.addEdge(2, 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 equialent", isEquialent(backend, graph)); + } + + private boolean isEquialent(DefaultGraphSplittingBackend backend, Graph proto) { + Graph graph = backend.getGraph(); + for (int node = 0; node < graph.size(); ++node) { + int nodeProto = backend.prototype(node); + IntSet succProto = new IntOpenHashSet(); + for (int succ : graph.outgoingEdges(node)) { + succProto.add(backend.prototype(succ)); + } + if (succProto.size() != proto.outgoingEdgesCount(nodeProto)) { + return false; + } + for (int succ : proto.outgoingEdges(nodeProto)) { + if (!succProto.contains(succ)) { + return false; + } + } + } + return true; + } + private GraphNodeFilter filter = new GraphNodeFilter() { @Override public boolean match(int node) { return true;