diff --git a/core/src/main/java/org/teavm/common/GraphUtils.java b/core/src/main/java/org/teavm/common/GraphUtils.java index 31731e9fa..6f121effb 100644 --- a/core/src/main/java/org/teavm/common/GraphUtils.java +++ b/core/src/main/java/org/teavm/common/GraphUtils.java @@ -15,6 +15,7 @@ */ package org.teavm.common; +import com.carrotsearch.hppc.IntStack; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -164,64 +165,98 @@ public final class GraphUtils { /* * Tarjan's algorithm + * See pseudocode at https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm + * This is a stackless version. */ - public static int[][] findStronglyConnectedComponents(Graph graph, int[] start) { + public static int[][] findStronglyConnectedComponents(Graph graph) { List components = new ArrayList<>(); - int[] visitIndex = new int[graph.size()]; - int[] headerIndex = new int[graph.size()]; - IntegerStack currentComponent = new IntegerStack(1); - boolean[] inCurrentComponent = new boolean[graph.size()]; - int lastIndex = 1; - IntegerStack stack = new IntegerStack(graph.size()); - IntegerStack modeStack = new IntegerStack(graph.size()); - int[] expectedMode = new int[graph.size()]; - for (int startNode : start) { - stack.push(startNode); - modeStack.push(0); + int index = 0; + IntStack procStack = new IntStack(); + IntStack stack = new IntStack(); + int[] nodeIndex = new int[graph.size()]; + int[] nodeLowLink = new int[graph.size()]; + boolean[] nodeOnStack = new boolean[graph.size()]; + + Arrays.fill(nodeIndex, -1); + Arrays.fill(nodeLowLink, -2); + + for (int i = 0; i < graph.size(); ++i) { + procStack.push(i); + procStack.push(0); } - while (!stack.isEmpty()) { - int node = stack.pop(); - int mode = modeStack.pop(); - if (expectedMode[node] != mode) { - continue; - } - expectedMode[node]++; - if (mode == 1) { - expectedMode[node] = 2; - int hdr = headerIndex[node]; - for (int successor : graph.outgoingEdges(node)) { - if (visitIndex[node] < visitIndex[successor]) { - hdr = Math.min(hdr, headerIndex[successor]); - } else if (inCurrentComponent[successor]) { - hdr = Math.min(hdr, visitIndex[successor]); + while (!procStack.isEmpty()) { + int state = procStack.pop(); + int v = procStack.pop(); + + switch (state) { + case 0: { + if (nodeIndex[v] >= 0) { + break; } - } - headerIndex[node] = hdr; + nodeIndex[v] = index; + nodeLowLink[v] = index; + index++; + stack.push(v); + nodeOnStack[v] = true; - if (hdr == visitIndex[node]) { - IntegerArray componentMembers = new IntegerArray(graph.size()); - int componentMember; - do { - componentMember = currentComponent.pop(); - inCurrentComponent[componentMember] = false; - componentMembers.add(componentMember); - } while (componentMember != node); - components.add(componentMembers.getAll()); - } - } else if (mode == 0) { - visitIndex[node] = lastIndex; - headerIndex[node] = lastIndex; - lastIndex++; + procStack.push(v); + procStack.push(3); - currentComponent.push(node); - inCurrentComponent[node] = true; - stack.push(node); - modeStack.push(1); - for (int successor : graph.outgoingEdges(node)) { - stack.push(successor); - modeStack.push(0); + for (int w : graph.outgoingEdges(v)) { + procStack.push(w); + procStack.push(v); + procStack.push(1); + } + break; + } + + case 1: { + int w = procStack.pop(); + + if (nodeIndex[w] < 0) { + procStack.push(w); + procStack.push(v); + procStack.push(2); + + procStack.push(w); + procStack.push(0); + } else if (nodeOnStack[w]) { + nodeLowLink[v] = Math.min(nodeLowLink[v], nodeIndex[w]); + } + + break; + } + + case 2: { + int w = procStack.pop(); + nodeLowLink[v] = Math.min(nodeLowLink[v], nodeLowLink[w]); + break; + } + + case 3: { + if (nodeLowLink[v] == nodeIndex[v]) { + IntegerArray scc = new IntegerArray(4); + int w; + do { + w = stack.pop(); + nodeOnStack[w] = false; + scc.add(w); + } while (w != v); + + if (scc.size() > 1) { + components.add(scc.getAll()); + } else { + for (int successor : graph.outgoingEdges(v)) { + if (successor == v) { + components.add(scc.getAll()); + break; + } + } + } + } + break; } } } diff --git a/core/src/main/java/org/teavm/common/IrreducibleGraphConverter.java b/core/src/main/java/org/teavm/common/IrreducibleGraphConverter.java index fb043184b..942ad429e 100644 --- a/core/src/main/java/org/teavm/common/IrreducibleGraphConverter.java +++ b/core/src/main/java/org/teavm/common/IrreducibleGraphConverter.java @@ -70,7 +70,7 @@ class IrreducibleGraphConverter { if (irreducible) { DJGraphNodeFilter filter = new DJGraphNodeFilter(djGraph, level); Graph graph = GraphUtils.subgraph(djGraph.getGraph(), filter); - int[][] sccs = GraphUtils.findStronglyConnectedComponents(graph, djGraph.level(level)); + int[][] sccs = GraphUtils.findStronglyConnectedComponents(graph); for (int[] scc : sccs) { if (scc.length > 1) { handleStronglyConnectedComponent(djGraph, scc, nodeMap); diff --git a/core/src/main/java/org/teavm/model/analysis/ClassInference.java b/core/src/main/java/org/teavm/model/analysis/ClassInference.java index 234fb927a..89003ed1e 100644 --- a/core/src/main/java/org/teavm/model/analysis/ClassInference.java +++ b/core/src/main/java/org/teavm/model/analysis/ClassInference.java @@ -15,21 +15,21 @@ */ package org.teavm.model.analysis; -import com.carrotsearch.hppc.IntObjectMap; -import com.carrotsearch.hppc.IntObjectOpenHashMap; import com.carrotsearch.hppc.IntOpenHashSet; import com.carrotsearch.hppc.IntSet; -import java.util.ArrayDeque; +import com.carrotsearch.hppc.IntStack; +import com.carrotsearch.hppc.ObjectIntMap; +import com.carrotsearch.hppc.ObjectIntOpenHashMap; +import com.carrotsearch.hppc.cursors.IntCursor; import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; +import java.util.Arrays; import java.util.HashSet; -import java.util.LinkedHashSet; import java.util.List; -import java.util.Queue; import java.util.Set; import org.teavm.common.Graph; import org.teavm.common.GraphBuilder; +import org.teavm.common.GraphUtils; +import org.teavm.common.IntegerArray; import org.teavm.dependency.DependencyInfo; import org.teavm.dependency.FieldDependencyInfo; import org.teavm.dependency.MethodDependencyInfo; @@ -70,28 +70,64 @@ public class ClassInference { private Graph cloneGraph; private Graph arrayGraph; private Graph itemGraph; - private List> casts; - private IntObjectMap exceptionMap; - private VirtualCallSite[][] virtualCallSites; - private List initialTasks; - private List>> types; - private List> finalTypes; + private Graph graph; + private ValueCast[] casts; + private int[] exceptions; + private VirtualCallSite[] virtualCallSites; + + private int[] propagationPath; + private int[] nodeMapping; + + private IntOpenHashSet[] types; + private ObjectIntMap typeMap = new ObjectIntOpenHashMap<>(); + private List typeList = new ArrayList<>(); + + private boolean changed = true; + private boolean[] nodeChanged; + private boolean[] formerNodeChanged; + + private static final int MAX_DEGREE = 3; public ClassInference(DependencyInfo dependencyInfo) { this.dependencyInfo = dependencyInfo; } public void infer(Program program, MethodReference methodReference) { - MethodDependencyInfo thisMethodDep = dependencyInfo.getMethod(methodReference); - buildGraphs(program, thisMethodDep); + /* + The idea behind this algorithm + 1. Build preliminary graphs that represent different connection types between variables. + See `assignmentGraph`, `cloneGraph`, `arrayGraph`, `itemGraph`. + 2. Build initial type sets where possible. See `types`. + 3. Build additional info: casts, virtual invocations, exceptions. + See `casts`, `exceptions`, `virtualCallSites`. + 4. Build graph from set of preliminary paths + 5. Find strongly connected components, collapse then into one nodes. + 6. Calculate topological order of the DAG (let it be propagation path). + Let resulting order be `propagationPath`. + 7. Propagate types along calculated path; then propagate types using additional info. + 8. Repeat 7 until it changes anything (i.e. calculate fixed point). + */ + types = new IntOpenHashSet[program.variableCount() << 3]; + nodeChanged = new boolean[types.length]; + formerNodeChanged = new boolean[nodeChanged.length]; + nodeMapping = new int[types.length]; + for (int i = 0; i < types.length; ++i) { + nodeMapping[i] = i; + } + + // See 1, 2, 3 + MethodDependencyInfo thisMethodDep = dependencyInfo.getMethod(methodReference); + buildPreliminaryGraphs(program, thisMethodDep); + + // Augment (2) with input types of method for (int i = 0; i <= methodReference.parameterCount(); ++i) { ValueDependencyInfo paramDep = thisMethodDep.getVariable(i); if (paramDep != null) { int degree = 0; - while (true) { + while (degree <= MAX_DEGREE) { for (String paramType : paramDep.getTypes()) { - initialTasks.add(new Task(i, degree, paramType)); + addType(i, degree, paramType); } if (!paramDep.hasArrayType()) { break; @@ -102,37 +138,46 @@ public class ClassInference { } } - types = new ArrayList<>(program.variableCount()); - for (int i = 0; i < program.variableCount(); ++i) { - List> variableTypes = new ArrayList<>(); - types.add(variableTypes); - for (int j = 0; j < 3; ++j) { - variableTypes.add(new LinkedHashSet<>()); - } - } + // See 4 + buildPropagationGraph(); + // See 5 + collapseSCCs(); + + // See 6 + buildPropagationPath(); + + // See 7, 8 propagate(program); + + // Cleanup assignmentGraph = null; + graph = null; cloneGraph = null; arrayGraph = null; itemGraph = null; casts = null; - exceptionMap = null; + exceptions = null; virtualCallSites = null; - - finalTypes = new ArrayList<>(program.variableCount()); - for (int i = 0; i < program.variableCount(); ++i) { - finalTypes.add(types.get(i).get(0)); - } - - types = null; + propagationPath = null; + nodeChanged = null; } public String[] classesOf(int variableIndex) { - return finalTypes.get(variableIndex).toArray(new String[0]); + IntOpenHashSet typeSet = types[nodeMapping[packNodeAndDegree(variableIndex, 0)]]; + if (typeSet == null) { + return new String[0]; + } + + int[] typeIndexes = typeSet.toArray(); + String[] types = new String[typeIndexes.length]; + for (int i = 0; i < typeIndexes.length; ++i) { + types[i] = typeList.get(typeIndexes[i]); + } + return types; } - private void buildGraphs(Program program, MethodDependencyInfo thisMethodDep) { + private void buildPreliminaryGraphs(Program program, MethodDependencyInfo thisMethodDep) { GraphBuildingVisitor visitor = new GraphBuildingVisitor(program.variableCount(), dependencyInfo); visitor.thisMethodDep = thisMethodDep; for (BasicBlock block : program.getBasicBlocks()) { @@ -143,161 +188,367 @@ public class ClassInference { for (Instruction insn : block) { insn.acceptVisitor(visitor); } + + if (block.getExceptionVariable() != null) { + getNodeTypes(packNodeAndDegree(block.getExceptionVariable().getIndex(), 0)); + } } assignmentGraph = visitor.assignmentGraphBuilder.build(); cloneGraph = visitor.cloneGraphBuilder.build(); arrayGraph = visitor.arrayGraphBuilder.build(); itemGraph = visitor.itemGraphBuilder.build(); - casts = visitor.casts; - exceptionMap = visitor.exceptionMap; - initialTasks = visitor.tasks; + casts = visitor.casts.toArray(new ValueCast[0]); + exceptions = visitor.exceptions.getAll(); + virtualCallSites = visitor.virtualCallSites.toArray(new VirtualCallSite[0]); + } - virtualCallSites = new VirtualCallSite[program.variableCount()][]; - for (int i = 0; i < virtualCallSites.length; ++i) { - List buildCallSites = visitor.virtualCallSites.get(i); - if (buildCallSites != null) { - virtualCallSites[i] = buildCallSites.toArray(new VirtualCallSite[0]); + private void buildPropagationGraph() { + IntStack stack = new IntStack(); + + for (int i = 0; i < types.length; ++i) { + if (types[i] != null) { + stack.push(i); + } + } + + boolean[] visited = new boolean[types.length]; + GraphBuilder graphBuilder = new GraphBuilder(types.length); + + while (!stack.isEmpty()) { + int entry = stack.pop(); + + if (visited[entry]) { + continue; + } + + visited[entry] = true; + + int degree = extractDegree(entry); + int variable = extractNode(entry); + + // Actually, successor nodes in resulting graph + IntSet nextEntries = new IntOpenHashSet(); + + // Start: calculating successor nodes in resulting DAG along different paths + // + + for (int successor : assignmentGraph.outgoingEdges(variable)) { + nextEntries.add(packNodeAndDegree(successor, degree)); + } + + for (int successor : cloneGraph.outgoingEdges(variable)) { + nextEntries.add(packNodeAndDegree(successor, degree)); + } + + if (degree > 0) { + for (int predecessor : assignmentGraph.incomingEdges(variable)) { + nextEntries.add(packNodeAndDegree(predecessor, degree)); + } + + for (int successor : itemGraph.outgoingEdges(variable)) { + nextEntries.add(packNodeAndDegree(successor, degree - 1)); + } + } + + for (int successor : arrayGraph.outgoingEdges(variable)) { + nextEntries.add(packNodeAndDegree(successor, degree + 1)); + } + + // + // End: calculating successor nodes in resulting graph + + for (IntCursor next : nextEntries) { + graphBuilder.addEdge(entry, next.value); + if (!visited[next.value]) { + stack.push(next.value); + } + } + } + + graph = graphBuilder.build(); + } + + private void collapseSCCs() { + int[][] sccs = GraphUtils.findStronglyConnectedComponents(graph); + if (sccs.length == 0) { + return; + } + + for (int[] scc : sccs) { + for (int i = 1; i < scc.length; ++i) { + nodeMapping[scc[i]] = scc[0]; + } + } + + boolean[] nodeChangedBackup = nodeChanged.clone(); + IntOpenHashSet[] typesBackup = types.clone(); + Arrays.fill(nodeChanged, false); + Arrays.fill(types, null); + + GraphBuilder graphBuilder = new GraphBuilder(graph.size()); + for (int i = 0; i < graph.size(); ++i) { + for (int j : graph.outgoingEdges(i)) { + int from = nodeMapping[i]; + int to = nodeMapping[j]; + if (from != to) { + graphBuilder.addEdge(from, to); + } + } + + int node = nodeMapping[i]; + if (typesBackup[i] != null) { + getNodeTypes(node).addAll(typesBackup[i]); + } + + if (nodeChangedBackup[i]) { + nodeChanged[node] = true; + } + } + + graph = graphBuilder.build(); + } + + private static final byte FRESH = 0; + private static final byte VISITING = 1; + private static final byte VISITED = 2; + + private void buildPropagationPath() { + byte[] state = new byte[types.length]; + int[] path = new int[types.length]; + int pathSize = 0; + IntStack stack = new IntStack(); + + for (int i = 0; i < graph.size(); ++i) { + if (graph.incomingEdgesCount(i) == 0 && types[i] != null) { + stack.push(i); + } + } + + while (!stack.isEmpty()) { + int node = stack.pop(); + if (state[node] == FRESH) { + state[node] = VISITING; + + stack.push(node); + + for (int successor : graph.outgoingEdges(node)) { + if (state[successor] == FRESH) { + stack.push(successor); + } + } + + } else if (state[node] == VISITING) { + path[pathSize++] = node; + state[node] = VISITED; + } + } + + propagationPath = Arrays.copyOf(path, pathSize); + } + + private void propagate(Program program) { + changed = false; + + while (true) { + System.arraycopy(nodeChanged, 0, formerNodeChanged, 0, nodeChanged.length); + Arrays.fill(nodeChanged, false); + + propagateAlongDAG(); + boolean outerChanged = changed; + + do { + changed = false; + propagateAlongCasts(); + propagateAlongVirtualCalls(program); + propagateAlongExceptions(program); + if (changed) { + outerChanged = true; + } + } while (changed); + + if (!outerChanged) { + break; + } + + changed = false; + } + } + + private void propagateAlongDAG() { + for (int i = propagationPath.length - 1; i >= 0; --i) { + int node = propagationPath[i]; + boolean predecessorsChanged = false; + for (int predecessor : graph.incomingEdges(node)) { + if (formerNodeChanged[predecessor] || nodeChanged[predecessor]) { + predecessorsChanged = true; + break; + } + } + if (!predecessorsChanged) { + continue; + } + + IntOpenHashSet nodeTypes = getNodeTypes(node); + for (int predecessor : graph.incomingEdges(node)) { + if (formerNodeChanged[predecessor] || nodeChanged[predecessor]) { + if (nodeTypes.addAll(types[predecessor]) > 0) { + nodeChanged[node] = true; + changed = true; + } + } } } } - private void propagate(Program program) { + private void propagateAlongCasts() { ClassReaderSource classSource = dependencyInfo.getClassSource(); - Queue queue = new ArrayDeque<>(initialTasks); - initialTasks = null; - - while (!queue.isEmpty()) { - Task task = queue.remove(); - - if (task.degree < 0) { - BasicBlock block = program.basicBlockAt(task.variable); - for (TryCatchBlock tryCatch : block.getTryCatchBlocks()) { - if (tryCatch.getExceptionType() == null - || classSource.isSuperType(tryCatch.getExceptionType(), task.className).orElse(false)) { - Variable exception = tryCatch.getHandler().getExceptionVariable(); - if (exception != null) { - queue.add(new Task(exception.getIndex(), 0, task.className)); - } - break; - } - } - + for (ValueCast cast : casts) { + int fromNode = nodeMapping[packNodeAndDegree(cast.fromVariable, 0)]; + if (!formerNodeChanged[fromNode] && !nodeChanged[fromNode]) { continue; } - List> variableTypes = types.get(task.variable); - if (task.degree >= variableTypes.size()) { - continue; - } + int toNode = nodeMapping[packNodeAndDegree(cast.toVariable, 0)]; + IntOpenHashSet targetTypes = getNodeTypes(toNode); - Set typeSet = variableTypes.get(task.degree); - if (!typeSet.add(task.className)) { - continue; - } - - for (int successor : assignmentGraph.outgoingEdges(task.variable)) { - queue.add(new Task(successor, task.degree, task.className)); - int itemDegree = task.degree + 1; - if (itemDegree < variableTypes.size()) { - for (String type : variableTypes.get(itemDegree)) { - queue.add(new Task(successor, itemDegree, type)); - } + for (IntCursor cursor : types[fromNode]) { + if (targetTypes.contains(cursor.value)) { + continue; } - List> successorVariableTypes = types.get(successor); - if (itemDegree < successorVariableTypes.size()) { - for (String type : successorVariableTypes.get(itemDegree)) { - queue.add(new Task(task.variable, itemDegree, type)); - } - } - } + String className = typeList.get(cursor.value); - if (task.degree > 0) { - for (int predecessor : assignmentGraph.incomingEdges(task.variable)) { - queue.add(new Task(predecessor, task.degree, task.className)); - } - - for (int successor : itemGraph.outgoingEdges(task.variable)) { - queue.add(new Task(successor, task.degree - 1, task.className)); - } - } else { - for (int successor : cloneGraph.outgoingEdges(task.variable)) { - queue.add(new Task(successor, 0, task.className)); - } - - IntSet blocks = exceptionMap.get(task.variable); - if (blocks != null) { - for (int block : blocks.toArray()) { - queue.add(new Task(block, -1, task.className)); - } - } - - VirtualCallSite[] callSites = virtualCallSites[task.variable]; - if (callSites != null) { - for (VirtualCallSite callSite : callSites) { - MethodReference rawMethod = new MethodReference(task.className, - callSite.method.getDescriptor()); - MethodReader resolvedMethod = classSource.resolveImplementation(rawMethod); - if (resolvedMethod == null) { - continue; - } - MethodReference resolvedMethodRef = resolvedMethod.getReference(); - if (callSite.resolvedMethods.add(resolvedMethodRef)) { - MethodDependencyInfo methodDep = dependencyInfo.getMethod(resolvedMethodRef); - if (methodDep != null) { - if (callSite.receiver >= 0) { - readValue(methodDep.getResult(), program.variableAt(callSite.receiver), queue); - } - for (int i = 0; i < callSite.arguments.length; ++i) { - writeValue(methodDep.getVariable(i + 1), program.variableAt(callSite.arguments[i]), - queue); - } - for (String type : methodDep.getThrown().getTypes()) { - queue.add(new Task(callSite.block, -1, type)); - } - } - } - } - } - } - - for (int successor : arrayGraph.outgoingEdges(task.variable)) { - queue.add(new Task(successor, task.degree + 1, task.className)); - } - - IntObjectMap variableCasts = casts.get(task.variable); - if (variableCasts != null) { ValueType type; - if (task.className.startsWith("[")) { - type = ValueType.parseIfPossible(task.className); + if (className.startsWith("[")) { + type = ValueType.parseIfPossible(className); if (type == null) { type = ValueType.arrayOf(ValueType.object("java.lang.Object")); } } else { - type = ValueType.object(task.className); + type = ValueType.object(className); } - for (int target : variableCasts.keys().toArray()) { - ValueType targetType = variableCasts.get(target); - if (classSource.isSuperType(targetType, type).orElse(false)) { - queue.add(new Task(target, 0, task.className)); - } + + if (classSource.isSuperType(cast.targetType, type).orElse(false)) { + changed = true; + nodeChanged[toNode] = true; + targetTypes.add(cursor.value); } } } } - static class GraphBuildingVisitor extends AbstractInstructionVisitor { + private void propagateAlongVirtualCalls(Program program) { + ClassReaderSource classSource = dependencyInfo.getClassSource(); + + for (VirtualCallSite callSite : virtualCallSites) { + int instanceNode = nodeMapping[packNodeAndDegree(callSite.instance, 0)]; + if (!formerNodeChanged[instanceNode] && !nodeChanged[instanceNode]) { + continue; + } + + for (IntCursor type : types[instanceNode]) { + if (!callSite.knownClasses.add(type.value)) { + continue; + } + + String className = typeList.get(type.value); + MethodReference rawMethod = new MethodReference(className, callSite.method.getDescriptor()); + MethodReader resolvedMethod = classSource.resolveImplementation(rawMethod); + + if (resolvedMethod == null) { + continue; + } + + MethodReference resolvedMethodRef = resolvedMethod.getReference(); + if (!callSite.resolvedMethods.add(resolvedMethodRef)) { + continue; + } + + MethodDependencyInfo methodDep = dependencyInfo.getMethod(resolvedMethodRef); + if (methodDep == null) { + continue; + } + if (callSite.receiver >= 0) { + readValue(methodDep.getResult(), program.variableAt(callSite.receiver)); + } + for (int i = 0; i < callSite.arguments.length; ++i) { + writeValue(methodDep.getVariable(i + 1), program.variableAt(callSite.arguments[i])); + } + + for (String thrownTypeName : methodDep.getThrown().getTypes()) { + propagateException(thrownTypeName, program.basicBlockAt(callSite.block)); + } + } + } + } + + private void propagateAlongExceptions(Program program) { + for (int i = 0; i < exceptions.length; i += 2) { + int variable = nodeMapping[packNodeAndDegree(exceptions[i], 0)]; + if (!formerNodeChanged[variable] && !nodeChanged[variable]) { + continue; + } + + BasicBlock block = program.basicBlockAt(exceptions[i + 1]); + for (IntCursor type : types[variable]) { + String typeName = typeList.get(type.value); + propagateException(typeName, block); + } + } + } + + private void propagateException(String thrownTypeName, BasicBlock block) { + ClassReaderSource classSource = dependencyInfo.getClassSource(); + + for (TryCatchBlock tryCatch : block.getTryCatchBlocks()) { + String expectedType = tryCatch.getExceptionType(); + if (expectedType == null || classSource.isSuperType(expectedType, thrownTypeName).orElse(false)) { + if (tryCatch.getHandler().getExceptionVariable() == null) { + break; + } + int exceptionNode = packNodeAndDegree(tryCatch.getHandler().getExceptionVariable().getIndex(), 0); + exceptionNode = nodeMapping[exceptionNode]; + int thrownType = getTypeByName(thrownTypeName); + if (getNodeTypes(exceptionNode).add(thrownType)) { + nodeChanged[exceptionNode] = true; + changed = true; + } + + break; + } + } + } + + IntOpenHashSet getNodeTypes(int node) { + IntOpenHashSet result = types[node]; + if (result == null) { + result = new IntOpenHashSet(); + types[node] = result; + } + return result; + } + + int getTypeByName(String typeName) { + int type = typeMap.getOrDefault(typeName, -1); + if (type < 0) { + type = typeList.size(); + typeMap.put(typeName, type); + typeList.add(typeName); + } + return type; + } + + class GraphBuildingVisitor extends AbstractInstructionVisitor { DependencyInfo dependencyInfo; GraphBuilder assignmentGraphBuilder; GraphBuilder cloneGraphBuilder; GraphBuilder arrayGraphBuilder; GraphBuilder itemGraphBuilder; MethodDependencyInfo thisMethodDep; - List> casts; - IntObjectMap exceptionMap = new IntObjectOpenHashMap<>(); - List tasks = new ArrayList<>(); - List> virtualCallSites; + List casts = new ArrayList<>(); + IntegerArray exceptions = new IntegerArray(2); + List virtualCallSites = new ArrayList<>(); BasicBlock currentBlock; GraphBuildingVisitor(int variableCount, DependencyInfo dependencyInfo) { @@ -306,12 +557,6 @@ public class ClassInference { cloneGraphBuilder = new GraphBuilder(variableCount); arrayGraphBuilder = new GraphBuilder(variableCount); itemGraphBuilder = new GraphBuilder(variableCount); - casts = new ArrayList<>(variableCount); - for (int i = 0; i < variableCount; ++i) { - casts.add(new IntObjectOpenHashMap<>()); - } - - virtualCallSites = new ArrayList<>(Collections.nCopies(variableCount, null)); } public void visit(Phi phi) { @@ -322,12 +567,12 @@ public class ClassInference { @Override public void visit(ClassConstantInstruction insn) { - tasks.add(new Task(insn.getReceiver().getIndex(), 0, "java.lang.Class")); + addType(insn.getReceiver().getIndex(), 0, "java.lang.Class"); } @Override public void visit(StringConstantInstruction insn) { - tasks.add(new Task(insn.getReceiver().getIndex(), 0, "java.lang.String")); + addType(insn.getReceiver().getIndex(), 0, "java.lang.String"); } @Override @@ -337,27 +582,24 @@ public class ClassInference { @Override public void visit(CastInstruction insn) { - casts.get(insn.getValue().getIndex()).put(insn.getReceiver().getIndex(), insn.getTargetType()); + casts.add(new ValueCast(insn.getValue().getIndex(), insn.getReceiver().getIndex(), insn.getTargetType())); + getNodeTypes(packNodeAndDegree(insn.getReceiver().getIndex(), 0)); } @Override public void visit(RaiseInstruction insn) { - IntSet blockIndexes = exceptionMap.get(insn.getException().getIndex()); - if (blockIndexes == null) { - blockIndexes = new IntOpenHashSet(); - exceptionMap.put(insn.getException().getIndex(), blockIndexes); - } - blockIndexes.add(currentBlock.getIndex()); + exceptions.add(insn.getException().getIndex()); + exceptions.add(currentBlock.getIndex()); } @Override public void visit(ConstructArrayInstruction insn) { - tasks.add(new Task(insn.getReceiver().getIndex(), 0, ValueType.arrayOf(insn.getItemType()).toString())); + addType(insn.getReceiver().getIndex(), 0, ValueType.arrayOf(insn.getItemType()).toString()); } @Override public void visit(ConstructInstruction insn) { - tasks.add(new Task(insn.getReceiver().getIndex(), 0, insn.getType())); + addType(insn.getReceiver().getIndex(), 0, insn.getType()); } @Override @@ -366,21 +608,21 @@ public class ClassInference { for (int i = 0; i < insn.getDimensions().size(); ++i) { type = ValueType.arrayOf(type); } - tasks.add(new Task(insn.getReceiver().getIndex(), 0, type.toString())); + addType(insn.getReceiver().getIndex(), 0, type.toString()); } @Override public void visit(GetFieldInstruction insn) { FieldDependencyInfo fieldDep = dependencyInfo.getField(insn.getField()); ValueDependencyInfo valueDep = fieldDep.getValue(); - readValue(valueDep, insn.getReceiver(), tasks); + readValue(valueDep, insn.getReceiver()); } @Override public void visit(PutFieldInstruction insn) { FieldDependencyInfo fieldDep = dependencyInfo.getField(insn.getField()); ValueDependencyInfo valueDep = fieldDep.getValue(); - writeValue(valueDep, insn.getValue(), tasks); + writeValue(valueDep, insn.getValue()); } @Override @@ -408,11 +650,12 @@ public class ClassInference { if (insn.getValueToReturn() != null) { ValueDependencyInfo resultDependency = thisMethodDep.getResult(); int resultDegree = 0; - while (resultDependency.hasArrayType()) { + while (resultDependency.hasArrayType() && resultDegree <= MAX_DEGREE) { resultDependency = resultDependency.getArrayItem(); for (String paramType : resultDependency.getTypes()) { - tasks.add(new Task(insn.getValueToReturn().getIndex(), ++resultDegree, paramType)); + addType(insn.getValueToReturn().getIndex(), resultDegree, paramType); } + ++resultDegree; } } } @@ -421,21 +664,26 @@ public class ClassInference { public void visit(InvokeInstruction insn) { if (insn.getType() == InvocationType.VIRTUAL) { int instance = insn.getInstance().getIndex(); - List callSites = virtualCallSites.get(instance); - if (callSites == null) { - callSites = new ArrayList<>(); - virtualCallSites.set(instance, callSites); - } VirtualCallSite callSite = new VirtualCallSite(); + callSite.instance = instance; callSite.method = insn.getMethod(); callSite.arguments = new int[insn.getArguments().size()]; for (int i = 0; i < insn.getArguments().size(); ++i) { callSite.arguments[i] = insn.getArguments().get(i).getIndex(); + for (int j = 0; j <= MAX_DEGREE; ++j) { + getNodeTypes(packNodeAndDegree(callSite.arguments[i], j)); + } } callSite.receiver = insn.getReceiver() != null ? insn.getReceiver().getIndex() : -1; callSite.block = currentBlock.getIndex(); - callSites.add(callSite); + virtualCallSites.add(callSite); + + if (insn.getReceiver() != null) { + for (int j = 1; j <= MAX_DEGREE; ++j) { + getNodeTypes(packNodeAndDegree(callSite.receiver, j)); + } + } return; } @@ -443,61 +691,95 @@ public class ClassInference { MethodDependencyInfo methodDep = dependencyInfo.getMethod(insn.getMethod()); if (methodDep != null) { if (insn.getReceiver() != null) { - readValue(methodDep.getResult(), insn.getReceiver(), tasks); + readValue(methodDep.getResult(), insn.getReceiver()); } for (int i = 0; i < insn.getArguments().size(); ++i) { - writeValue(methodDep.getVariable(i + 1), insn.getArguments().get(i), tasks); + writeValue(methodDep.getVariable(i + 1), insn.getArguments().get(i)); } for (String type : methodDep.getThrown().getTypes()) { - tasks.add(new Task(currentBlock.getIndex(), -1, type)); + propagateException(type, currentBlock); } } } } - private static void readValue(ValueDependencyInfo valueDep, Variable receiver, Collection tasks) { - int depth = 0; - boolean hasArrayType; - do { - for (String type : valueDep.getTypes()) { - tasks.add(new Task(receiver.getIndex(), depth, type)); - } - depth++; - hasArrayType = valueDep.hasArrayType(); - valueDep = valueDep.getArrayItem(); - } while (hasArrayType); - } - - private static void writeValue(ValueDependencyInfo valueDep, Variable source, Collection tasks) { - int depth = 0; - while (valueDep.hasArrayType()) { - depth++; - valueDep = valueDep.getArrayItem(); - for (String type : valueDep.getTypes()) { - tasks.add(new Task(source.getIndex(), depth, type)); - } - } - } - - static class Task { - int variable; - int degree; - String className; - - Task(int variable, int degree, String className) { - this.variable = variable; - this.degree = degree; - this.className = className; - } - } - static class VirtualCallSite { + int instance; + IntSet knownClasses = new IntOpenHashSet(); Set resolvedMethods = new HashSet<>(); MethodReference method; int[] arguments; int receiver; int block; } + + void readValue(ValueDependencyInfo valueDep, Variable receiver) { + int depth = 0; + boolean hasArrayType; + do { + for (String type : valueDep.getTypes()) { + addType(receiver.getIndex(), depth, type); + } + depth++; + hasArrayType = valueDep.hasArrayType(); + valueDep = valueDep.getArrayItem(); + } while (hasArrayType && depth <= MAX_DEGREE); + } + + void writeValue(ValueDependencyInfo valueDep, Variable source) { + int depth = 0; + while (valueDep.hasArrayType() && depth < MAX_DEGREE) { + depth++; + valueDep = valueDep.getArrayItem(); + for (String type : valueDep.getTypes()) { + addType(source.getIndex(), depth, type); + } + } + } + + void addType(int variable, int degree, String typeName) { + int entry = nodeMapping[packNodeAndDegree(variable, degree)]; + if (getNodeTypes(entry).add(getTypeByName(typeName))) { + nodeChanged[entry] = true; + changed = true; + } + } + + static int extractNode(int nodeAndDegree) { + return nodeAndDegree >>> 3; + } + + static int extractDegree(int nodeAndDegree) { + return nodeAndDegree & 7; + } + + static int packNodeAndDegree(int node, int degree) { + return (node << 3) | degree; + } + + static long packTwoIntegers(int a, int b) { + return ((long) a << 32) | b; + } + + static int unpackFirst(long pair) { + return (int) (pair >>> 32); + } + + static int unpackSecond(long pair) { + return (int) pair; + } + + static final class ValueCast { + final int fromVariable; + final int toVariable; + final ValueType targetType; + + ValueCast(int fromVariable, int toVariable, ValueType targetType) { + this.fromVariable = fromVariable; + this.toVariable = toVariable; + this.targetType = targetType; + } + } } diff --git a/core/src/main/java/org/teavm/model/analysis/NullnessInformationBuilder.java b/core/src/main/java/org/teavm/model/analysis/NullnessInformationBuilder.java index eb3612e5b..4a4871f0f 100644 --- a/core/src/main/java/org/teavm/model/analysis/NullnessInformationBuilder.java +++ b/core/src/main/java/org/teavm/model/analysis/NullnessInformationBuilder.java @@ -137,7 +137,7 @@ class NullnessInformationBuilder { sccIndexes = new int[program.variableCount()]; if (assignmentGraph.size() > 0) { - int[][] sccs = GraphUtils.findStronglyConnectedComponents(assignmentGraph, new int[]{0}); + int[][] sccs = GraphUtils.findStronglyConnectedComponents(assignmentGraph); for (int i = 0; i < sccs.length; ++i) { for (int sccNode : sccs[i]) { sccIndexes[sccNode] = i + 1; diff --git a/core/src/test/java/org/teavm/common/GraphTest.java b/core/src/test/java/org/teavm/common/GraphTest.java index ffddbf5bd..0b619ce4a 100644 --- a/core/src/test/java/org/teavm/common/GraphTest.java +++ b/core/src/test/java/org/teavm/common/GraphTest.java @@ -49,16 +49,12 @@ public class GraphTest { builder.addEdge(12, 13); Graph graph = builder.build(); - int[][] sccs = GraphUtils.findStronglyConnectedComponents(graph, new int[] { 0 }); + int[][] sccs = GraphUtils.findStronglyConnectedComponents(graph); sortSccs(sccs); - assertThat(sccs.length, is(6)); - assertThat(sccs[0], is(new int[] { 0 })); - assertThat(sccs[1], is(new int[] { 1, 2, 3, 4, 5, 6, 7, 8 })); - assertThat(sccs[2], is(new int[] { 9 })); - assertThat(sccs[3], is(new int[] { 10 })); - assertThat(sccs[4], is(new int[] { 11, 12 })); - assertThat(sccs[5], is(new int[] { 13 })); + assertThat(sccs.length, is(2)); + assertThat(sccs[0], is(new int[] { 1, 2, 3, 4, 5, 6, 7, 8 })); + assertThat(sccs[1], is(new int[] { 11, 12 })); } @Test @@ -77,7 +73,7 @@ public class GraphTest { Graph graph = builder.build(); graph = GraphUtils.subgraph(graph, node -> node != 0); - int[][] sccs = GraphUtils.findStronglyConnectedComponents(graph, new int[] { 1, 2, 3 }); + int[][] sccs = GraphUtils.findStronglyConnectedComponents(graph); sortSccs(sccs); assertThat(sccs.length, is(1)); @@ -95,13 +91,11 @@ public class GraphTest { Graph graph = builder.build(); graph = GraphUtils.subgraph(graph, filter); - int[][] sccs = GraphUtils.findStronglyConnectedComponents(graph, new int[] { 0 }); + int[][] sccs = GraphUtils.findStronglyConnectedComponents(graph); sortSccs(sccs); - assertThat(sccs.length, is(3)); - assertThat(sccs[0], is(new int[] { 0 })); - assertThat(sccs[1], is(new int[] { 1, 3 })); - assertThat(sccs[2], is(new int[] { 2 })); + assertThat(sccs.length, is(1)); + assertThat(sccs[0], is(new int[] { 1, 3 })); } @Test @@ -127,7 +121,7 @@ public class GraphTest { Graph graph = builder.build(); graph = GraphUtils.subgraph(graph, node -> node != 0); - int[][] sccs = GraphUtils.findStronglyConnectedComponents(graph, new int[] { 1, 2, 3, 4 }); + int[][] sccs = GraphUtils.findStronglyConnectedComponents(graph); sortSccs(sccs); assertThat(sccs.length, is(2));