diff --git a/core/src/main/java/org/teavm/common/DJGraph.java b/core/src/main/java/org/teavm/common/DJGraph.java deleted file mode 100644 index 7cdbbff5e..000000000 --- a/core/src/main/java/org/teavm/common/DJGraph.java +++ /dev/null @@ -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 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; - } -} diff --git a/core/src/main/java/org/teavm/common/GraphBuilder.java b/core/src/main/java/org/teavm/common/GraphBuilder.java index f5d20a6b9..7b43f67e8 100644 --- a/core/src/main/java/org/teavm/common/GraphBuilder.java +++ b/core/src/main/java/org/teavm/common/GraphBuilder.java @@ -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); } } } diff --git a/core/src/main/java/org/teavm/common/GraphUtils.java b/core/src/main/java/org/teavm/common/GraphUtils.java index 8c88a1c17..b4a29dac6 100644 --- a/core/src/main/java/org/teavm/common/GraphUtils.java +++ b/core/src/main/java/org/teavm/common/GraphUtils.java @@ -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) { diff --git a/core/src/main/java/org/teavm/common/IrreducibleGraphConverter.java b/core/src/main/java/org/teavm/common/IrreducibleGraphConverter.java deleted file mode 100644 index b1eb1ad4f..000000000 --- a/core/src/main/java/org/teavm/common/IrreducibleGraphConverter.java +++ /dev/null @@ -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; - -/** - *

Converts irreducible graph to reducible one using node splitting algorithm described at - * the paper “Handling irreducible loops: optimized node splitting vs. DJ-graphs” by - * Sebastian Unger and Frank Mueller.

- * - * @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; - } - } -} diff --git a/core/src/main/java/org/teavm/common/IrreducibleGraphSplitter.java b/core/src/main/java/org/teavm/common/IrreducibleGraphSplitter.java new file mode 100644 index 000000000..023d83f08 --- /dev/null +++ b/core/src/main/java/org/teavm/common/IrreducibleGraphSplitter.java @@ -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; + +/** + *

Converts irreducible graph to reducible one using node splitting algorithm described at + * the paper “Handling irreducible loops: optimized node splitting vs. DJ-graphs” by + * Sebastian Unger and Frank Mueller.

+ * + *

Appendix A of the paper contains pseudocode. We refer to this pseudocode below.

+ * + * @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; + } +} diff --git a/core/src/main/java/org/teavm/common/MutableDirectedGraph.java b/core/src/main/java/org/teavm/common/MutableDirectedGraph.java index 1366aa5c1..63d4d9da7 100644 --- a/core/src/main/java/org/teavm/common/MutableDirectedGraph.java +++ b/core/src/main/java/org/teavm/common/MutableDirectedGraph.java @@ -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 successors = new ArrayList<>(); - private List predecessors = new ArrayList<>(); + private List successors = new ArrayList<>(); + private List 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); } } diff --git a/core/src/test/java/org/teavm/common/GraphTest.java b/core/src/test/java/org/teavm/common/GraphTest.java index fa40f1a81..734558ff8 100644 --- a/core/src/test/java/org/teavm/common/GraphTest.java +++ b/core/src/test/java/org/teavm/common/GraphTest.java @@ -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);