diff --git a/core/src/main/java/org/teavm/dependency/DependencyChecker.java b/core/src/main/java/org/teavm/dependency/DependencyChecker.java index eddf33d53..de1967928 100644 --- a/core/src/main/java/org/teavm/dependency/DependencyChecker.java +++ b/core/src/main/java/org/teavm/dependency/DependencyChecker.java @@ -15,6 +15,9 @@ */ package org.teavm.dependency; +import com.carrotsearch.hppc.IntOpenHashSet; +import com.carrotsearch.hppc.IntSet; +import com.carrotsearch.hppc.cursors.IntCursor; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collection; @@ -57,6 +60,7 @@ import org.teavm.model.util.ProgramUtils; import org.teavm.parsing.Parser; public class DependencyChecker implements DependencyInfo { + private static final int PROPAGATION_STACK_THRESHOLD = 50; static final boolean shouldLog = System.getProperty("org.teavm.logDependencies", "false").equals("true"); private int classNameSuffix; private DependencyClassSource classSource; @@ -68,6 +72,7 @@ public class DependencyChecker implements DependencyInfo { private CachedMapper classCache; private List listeners = new ArrayList<>(); private ServiceRepository services; + private Deque pendingTransitions = new ArrayDeque<>(); private Deque tasks = new ArrayDeque<>(); private Queue deferredTasks = new ArrayDeque<>(); List types = new ArrayList<>(); @@ -79,6 +84,7 @@ public class DependencyChecker implements DependencyInfo { private DependencyAgent agent; Map bootstrapMethodSubstitutors = new HashMap<>(); private boolean completing; + private Map superClassFilters = new HashMap<>(); public DependencyChecker(ClassReaderSource classSource, ClassLoader classLoader, ServiceRepository services, Diagnostics diagnostics) { @@ -237,7 +243,7 @@ public class DependencyChecker implements DependencyInfo { private int propagationDepth; void schedulePropagation(DependencyConsumer consumer, DependencyType type) { - if (propagationDepth < 50) { + if (propagationDepth < PROPAGATION_STACK_THRESHOLD) { ++propagationDepth; consumer.consume(type); --propagationDepth; @@ -246,6 +252,20 @@ public class DependencyChecker implements DependencyInfo { } } + void schedulePropagation(DependencyNodeToNodeTransition consumer, DependencyType type) { + if (consumer.pendingTypes == null && propagationDepth < PROPAGATION_STACK_THRESHOLD) { + ++propagationDepth; + consumer.consume(type); + --propagationDepth; + } else { + if (consumer.pendingTypes == null) { + pendingTransitions.add(consumer); + consumer.pendingTypes = new IntOpenHashSet(); + } + consumer.pendingTypes.add(type.index); + } + } + void schedulePropagation(DependencyNodeToNodeTransition consumer, DependencyType[] types) { if (types.length == 0) { return; @@ -255,14 +275,18 @@ public class DependencyChecker implements DependencyInfo { return; } - if (propagationDepth < 50) { + if (consumer.pendingTypes == null && propagationDepth < PROPAGATION_STACK_THRESHOLD) { ++propagationDepth; consumer.consume(types); --propagationDepth; } else { - tasks.add(() -> { - consumer.consume(types); - }); + if (consumer.pendingTypes == null) { + pendingTransitions.add(consumer); + consumer.pendingTypes = new IntOpenHashSet(); + } + for (DependencyType type : types) { + consumer.pendingTypes.add(type.index); + } } } @@ -275,7 +299,7 @@ public class DependencyChecker implements DependencyInfo { return; } - if (propagationDepth < 50) { + if (propagationDepth < PROPAGATION_STACK_THRESHOLD) { ++propagationDepth; for (DependencyType type : types) { consumer.consume(type); @@ -486,7 +510,7 @@ public class DependencyChecker implements DependencyInfo { } FieldDependency dep = new FieldDependency(node, field, fieldRef); if (!dep.isMissing()) { - tasks.add(() -> linkClass(fieldRef.getClassName(), null).initClass(null)); + deferredTasks.add(() -> linkClass(fieldRef.getClassName(), null).initClass(null)); } return dep; } @@ -538,9 +562,13 @@ public class DependencyChecker implements DependencyInfo { return; } int index = 0; - while (true) { - while (!tasks.isEmpty()) { - tasks.removeLast().run(); + while (!deferredTasks.isEmpty() || !tasks.isEmpty() || !pendingTransitions.isEmpty()) { + while (true) { + processNodeToNodeTransitionQueue(); + if (tasks.isEmpty()) { + break; + } + tasks.remove().run(); if (++index == 100) { if (interruptor != null && !interruptor.shouldContinue()) { interrupted = true; @@ -549,10 +577,31 @@ public class DependencyChecker implements DependencyInfo { index = 0; } } - if (deferredTasks.isEmpty()) { - break; + + propagationDepth = PROPAGATION_STACK_THRESHOLD; + while (!deferredTasks.isEmpty()) { + deferredTasks.remove().run(); + } + propagationDepth = 0; + } + } + + private void processNodeToNodeTransitionQueue() { + while (!pendingTransitions.isEmpty()) { + DependencyNodeToNodeTransition transition = pendingTransitions.remove(); + IntSet pendingTypes = transition.pendingTypes; + transition.pendingTypes = null; + if (pendingTypes.size() == 1) { + DependencyType type = types.get(pendingTypes.iterator().next().value); + transition.consume(type); + } else { + DependencyType[] typesToPropagate = new DependencyType[pendingTypes.size()]; + int index = 0; + for (IntCursor cursor : pendingTypes) { + typesToPropagate[index++] = types.get(cursor.value); + } + transition.consume(typesToPropagate); } - deferredTasks.poll().run(); } } @@ -611,4 +660,8 @@ public class DependencyChecker implements DependencyInfo { public void addBootstrapMethodSubstitutor(MethodReference method, BootstrapMethodSubstitutor substitutor) { bootstrapMethodSubstitutors.put(method, substitutor); } + + SuperClassFilter getSuperClassFilter(String superClass) { + return superClassFilters.computeIfAbsent(superClass, s -> new SuperClassFilter(classSource, s)); + } } \ No newline at end of file diff --git a/core/src/main/java/org/teavm/dependency/DependencyClassSource.java b/core/src/main/java/org/teavm/dependency/DependencyClassSource.java index ecccc9ac0..1c4bca016 100644 --- a/core/src/main/java/org/teavm/dependency/DependencyClassSource.java +++ b/core/src/main/java/org/teavm/dependency/DependencyClassSource.java @@ -37,7 +37,7 @@ class DependencyClassSource implements ClassHolderSource { private List transformers = new ArrayList<>(); private Map cache = new LinkedHashMap<>(); - public DependencyClassSource(ClassReaderSource innerSource, Diagnostics diagnostics) { + DependencyClassSource(ClassReaderSource innerSource, Diagnostics diagnostics) { this.innerSource = innerSource; this.diagnostics = diagnostics; } diff --git a/core/src/main/java/org/teavm/dependency/DependencyGraphBuilder.java b/core/src/main/java/org/teavm/dependency/DependencyGraphBuilder.java index 85fd26514..c3909b8df 100644 --- a/core/src/main/java/org/teavm/dependency/DependencyGraphBuilder.java +++ b/core/src/main/java/org/teavm/dependency/DependencyGraphBuilder.java @@ -17,6 +17,7 @@ package org.teavm.dependency; import java.util.ArrayList; import java.util.Arrays; +import java.util.BitSet; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -65,7 +66,7 @@ class DependencyGraphBuilder { private TextLocation currentLocation; private ExceptionConsumer currentExceptionConsumer; - public DependencyGraphBuilder(DependencyChecker dependencyChecker) { + DependencyGraphBuilder(DependencyChecker dependencyChecker) { this.dependencyChecker = dependencyChecker; } @@ -303,9 +304,8 @@ class DependencyGraphBuilder { } } - private static class VirtualCallConsumer implements DependencyConsumer { + static class VirtualCallConsumer implements DependencyConsumer { private final DependencyNode node; - private final String filterClass; private final MethodDescriptor methodDesc; private final DependencyChecker checker; private final DependencyNode[] parameters; @@ -313,14 +313,16 @@ class DependencyGraphBuilder { private final DefaultCallGraphNode caller; private final TextLocation location; private final Set knownMethods = new HashSet<>(); + private final BitSet knownTypes = new BitSet(); private ExceptionConsumer exceptionConsumer; + private SuperClassFilter filter; - public VirtualCallConsumer(DependencyNode node, String filterClass, + VirtualCallConsumer(DependencyNode node, String filterClass, MethodDescriptor methodDesc, DependencyChecker checker, DependencyNode[] parameters, DependencyNode result, DefaultCallGraphNode caller, TextLocation location, ExceptionConsumer exceptionConsumer) { this.node = node; - this.filterClass = filterClass; + this.filter = checker.getSuperClassFilter(filterClass); this.methodDesc = methodDesc; this.checker = checker; this.parameters = parameters; @@ -332,6 +334,11 @@ class DependencyGraphBuilder { @Override public void consume(DependencyType type) { + if (knownTypes.get(type.index)) { + return; + } + knownTypes.set(type.index); + String className = type.getName(); if (DependencyChecker.shouldLog) { System.out.println("Virtual call of " + methodDesc + " detected on " + node.getTag() + ". " @@ -339,10 +346,10 @@ class DependencyGraphBuilder { } if (className.startsWith("[")) { className = "java.lang.Object"; + type = checker.getType(className); } - ClassReaderSource classSource = checker.getClassSource(); - if (!classSource.isSuperType(filterClass, className).orElse(false)) { + if (!filter.match(type)) { return; } MethodReference methodRef = new MethodReference(className, methodDesc); @@ -351,8 +358,8 @@ class DependencyGraphBuilder { methodDep.use(); DependencyNode[] targetParams = methodDep.getVariables(); if (parameters[0] != null && targetParams[0] != null) { - parameters[0].connect(targetParams[0], thisType -> classSource.isSuperType( - methodDep.getMethod().getOwnerName(), thisType.getName()).orElse(false)); + parameters[0].connect(targetParams[0], + checker.getSuperClassFilter(methodDep.getMethod().getOwnerName())); } for (int i = 1; i < parameters.length; ++i) { if (parameters[i] != null && targetParams[i] != null) { @@ -429,14 +436,9 @@ class DependencyGraphBuilder { if (targetType instanceof ValueType.Object) { String targetClsName = ((ValueType.Object) targetType).getClassName(); final ClassReader targetClass = classSource.get(targetClsName); - if (targetClass != null) { + if (targetClass != null && !(targetClass.getName().equals("java.lang.Object"))) { if (valueNode != null && receiverNode != null) { - valueNode.connect(receiverNode, type -> { - if (targetClass.getName().equals("java.lang.Object")) { - return true; - } - return classSource.isSuperType(targetClass.getName(), type.getName()).orElse(false); - }); + valueNode.connect(receiverNode, dependencyChecker.getSuperClassFilter(targetClass.getName())); } return; } diff --git a/core/src/main/java/org/teavm/dependency/DependencyNode.java b/core/src/main/java/org/teavm/dependency/DependencyNode.java index 480153ad4..911e2bda8 100644 --- a/core/src/main/java/org/teavm/dependency/DependencyNode.java +++ b/core/src/main/java/org/teavm/dependency/DependencyNode.java @@ -19,6 +19,7 @@ import java.util.*; import org.teavm.model.MethodReference; public class DependencyNode implements ValueDependencyInfo { + private static final int SMALL_TYPES_THRESHOLD = 6; private DependencyChecker dependencyChecker; private List followers; private int[] smallTypes; @@ -57,8 +58,8 @@ public class DependencyNode implements ValueDependencyInfo { return false; } } - if (smallTypes.length == 5) { - types = new BitSet(); + if (smallTypes.length == SMALL_TYPES_THRESHOLD) { + types = new BitSet(dependencyChecker.types.size() * 2); for (int existingType : smallTypes) { types.set(existingType); } @@ -84,9 +85,6 @@ public class DependencyNode implements ValueDependencyInfo { } public void propagate(DependencyType type) { - if (type.getDependencyChecker() != dependencyChecker) { - throw new IllegalArgumentException("The given type does not belong to the same dependency checker"); - } if (degree > 2) { return; } @@ -94,29 +92,34 @@ public class DependencyNode implements ValueDependencyInfo { if (DependencyChecker.shouldLog) { System.out.println(tag + " -> " + type.getName()); } - if (followers != null) { - for (DependencyConsumer consumer : followers.toArray(new DependencyConsumer[followers.size()])) { - dependencyChecker.schedulePropagation(consumer, type); - } + scheduleSingleType(type); + } + } + + private void scheduleSingleType(DependencyType type) { + if (followers != null) { + for (DependencyConsumer consumer : followers.toArray(new DependencyConsumer[followers.size()])) { + dependencyChecker.schedulePropagation(consumer, type); } - if (transitions != null) { - for (DependencyConsumer consumer : transitions.toArray(new DependencyConsumer[transitions.size()])) { - dependencyChecker.schedulePropagation(consumer, type); - } + } + if (transitions != null) { + for (DependencyNodeToNodeTransition consumer : transitions.toArray( + new DependencyNodeToNodeTransition[transitions.size()])) { + dependencyChecker.schedulePropagation(consumer, type); } } } public void propagate(DependencyType[] newTypes) { - DependencyType[] types = new DependencyType[newTypes.length]; + if (degree > 2) { + return; + } + int j = 0; for (int i = 0; i < newTypes.length; ++i) { DependencyType type = newTypes[i]; - if (type.getDependencyChecker() != dependencyChecker) { - throw new IllegalArgumentException("The given type does not belong to the same dependency checker"); - } if (addType(type)) { - types[j++] = type; + newTypes[j++] = type; } } if (j == 0) { @@ -124,22 +127,29 @@ public class DependencyNode implements ValueDependencyInfo { } if (DependencyChecker.shouldLog) { for (int i = 0; i < j; ++i) { - System.out.println(tag + " -> " + types[i].getName()); + System.out.println(tag + " -> " + newTypes[i].getName()); } } - if (j < types.length && (followers != null || transitions != null)) { - types = Arrays.copyOf(types, j); + if (followers == null && transitions == null) { + return; + } + if (j < newTypes.length) { + if (j == 1) { + scheduleSingleType(newTypes[0]); + return; + } + newTypes = Arrays.copyOf(newTypes, j); } if (followers != null) { for (DependencyConsumer consumer : followers.toArray(new DependencyConsumer[followers.size()])) { - dependencyChecker.schedulePropagation(consumer, types); + dependencyChecker.schedulePropagation(consumer, newTypes); } } if (transitions != null) { for (DependencyNodeToNodeTransition consumer : transitions.toArray( new DependencyNodeToNodeTransition[transitions.size()])) { - dependencyChecker.schedulePropagation(consumer, types); + dependencyChecker.schedulePropagation(consumer, newTypes); } } } @@ -250,7 +260,7 @@ public class DependencyNode implements ValueDependencyInfo { } return false; } - return types != null && type.getDependencyChecker() == dependencyChecker && types.get(type.index); + return types != null && types.get(type.index); } @Override diff --git a/core/src/main/java/org/teavm/dependency/DependencyNodeToNodeTransition.java b/core/src/main/java/org/teavm/dependency/DependencyNodeToNodeTransition.java index 935d6169f..d5418a0c5 100644 --- a/core/src/main/java/org/teavm/dependency/DependencyNodeToNodeTransition.java +++ b/core/src/main/java/org/teavm/dependency/DependencyNodeToNodeTransition.java @@ -15,15 +15,18 @@ */ package org.teavm.dependency; +import com.carrotsearch.hppc.IntSet; import java.util.Arrays; +import java.util.BitSet; class DependencyNodeToNodeTransition implements DependencyConsumer { private DependencyNode source; DependencyNode destination; private DependencyTypeFilter filter; + private BitSet knownFilteredOffTypes; + IntSet pendingTypes; - public DependencyNodeToNodeTransition(DependencyNode source, DependencyNode destination, - DependencyTypeFilter filter) { + DependencyNodeToNodeTransition(DependencyNode source, DependencyNode destination, DependencyTypeFilter filter) { this.source = source; this.destination = destination; this.filter = filter; @@ -31,7 +34,7 @@ class DependencyNodeToNodeTransition implements DependencyConsumer { @Override public void consume(DependencyType type) { - if (filter != null && !filter.match(type)) { + if (!filterType(type)) { return; } if (type.getName().startsWith("[")) { @@ -47,18 +50,20 @@ class DependencyNodeToNodeTransition implements DependencyConsumer { } void consume(DependencyType[] types) { - DependencyType[] filtered = new DependencyType[types.length]; int j = 0; for (DependencyType type : types) { - if (type.getName().startsWith("[")) { - source.getArrayItem().connect(destination.getArrayItem()); - destination.getArrayItem().connect(source.getArrayItem()); - } - if (type.getName().equals("java.lang.Class")) { - source.getClassValueNode().connect(destination.getClassValueNode()); - } - if ((filter == null || filter.match(type)) && !destination.hasType(type)) { - filtered[j++] = type; + if (filterType(type)) { + if (!destination.hasType(type)) { + types[j++] = type; + } + + if (type.getName().startsWith("[")) { + source.getArrayItem().connect(destination.getArrayItem()); + destination.getArrayItem().connect(source.getArrayItem()); + } + if (type.getName().equals("java.lang.Class")) { + source.getClassValueNode().connect(destination.getClassValueNode()); + } } } @@ -67,13 +72,32 @@ class DependencyNodeToNodeTransition implements DependencyConsumer { } if (j == 1) { - destination.propagate(filtered[0]); + destination.propagate(types[0]); } else { - if (j < filtered.length) { - filtered = Arrays.copyOf(filtered, j); + if (j < types.length) { + types = Arrays.copyOf(types, j); } - destination.propagate(filtered); + destination.propagate(types); } } + + private boolean filterType(DependencyType type) { + if (filter == null) { + return true; + } + + if (knownFilteredOffTypes != null && knownFilteredOffTypes.get(type.index)) { + return false; + } + if (!filter.match(type)) { + if (knownFilteredOffTypes == null) { + knownFilteredOffTypes = new BitSet(64); + } + knownFilteredOffTypes.set(type.index); + return false; + } + + return true; + } } diff --git a/core/src/main/java/org/teavm/dependency/SuperClassFilter.java b/core/src/main/java/org/teavm/dependency/SuperClassFilter.java new file mode 100644 index 000000000..1ac6c6545 --- /dev/null +++ b/core/src/main/java/org/teavm/dependency/SuperClassFilter.java @@ -0,0 +1,41 @@ +/* + * Copyright 2017 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.dependency; + +import com.carrotsearch.hppc.IntIntMap; +import com.carrotsearch.hppc.IntIntOpenHashMap; +import org.teavm.model.ClassReaderSource; + +class SuperClassFilter implements DependencyTypeFilter { + private ClassReaderSource classSource; + private String superType; + private IntIntMap cache = new IntIntOpenHashMap(); + + SuperClassFilter(ClassReaderSource classSource, String superType) { + this.classSource = classSource; + this.superType = superType; + } + + @Override + public boolean match(DependencyType type) { + int result = cache.getOrDefault(type.index, -1); + if (result < 0) { + result = classSource.isSuperType(superType, type.getName()).orElse(false) ? 1 : 0; + cache.put(type.index, result); + } + return result != 0; + } +} diff --git a/core/src/main/java/org/teavm/model/ClassReaderSource.java b/core/src/main/java/org/teavm/model/ClassReaderSource.java index 49a48ff40..0617b05a7 100644 --- a/core/src/main/java/org/teavm/model/ClassReaderSource.java +++ b/core/src/main/java/org/teavm/model/ClassReaderSource.java @@ -113,24 +113,7 @@ public interface ClassReaderSource { } default Optional isSuperType(String superType, String subType) { - if (superType.equals(subType)) { - return Optional.of(true); - } - ClassReader cls = get(subType); - if (cls == null) { - return Optional.empty(); - } - if (cls.getParent() != null) { - if (isSuperType(superType, cls.getParent()).orElse(false)) { - return Optional.of(true); - } - } - for (String iface : cls.getInterfaces()) { - if (isSuperType(superType, iface).orElse(false)) { - return Optional.of(true); - } - } - return Optional.of(false); + return ClassReaderSourceHelper.isSuperType(this, superType, subType); } default Optional isSuperType(ValueType superType, ValueType subType) { diff --git a/core/src/main/java/org/teavm/model/ClassReaderSourceHelper.java b/core/src/main/java/org/teavm/model/ClassReaderSourceHelper.java index 3a678e198..dc6000c33 100644 --- a/core/src/main/java/org/teavm/model/ClassReaderSourceHelper.java +++ b/core/src/main/java/org/teavm/model/ClassReaderSourceHelper.java @@ -17,6 +17,7 @@ package org.teavm.model; import java.util.ArrayList; import java.util.List; +import java.util.Optional; import java.util.Set; final class ClassReaderSourceHelper { @@ -59,4 +60,56 @@ final class ClassReaderSourceHelper { return mostSpecificMethod; } + + static Optional isSuperType(ClassReaderSource classSource, String superType, String subType) { + if (superType.equals("java.lang.Object")) { + return Optional.of(true); + } + + ClassReader cls = classSource.get(superType); + if (cls != null && !cls.hasModifier(ElementModifier.INTERFACE)) { + return isSuperTypeSimple(classSource, superType, subType); + } + + return isSuperTypeInterface(classSource, superType, subType); + } + + private static Optional isSuperTypeSimple(ClassReaderSource classSource, + String superType, String subType) { + while (!superType.equals(subType)) { + ClassReader cls = classSource.get(subType); + if (cls == null) { + return Optional.empty(); + } + + subType = cls.getParent(); + if (subType == null) { + return Optional.of(false); + } + } + + return Optional.of(true); + } + + private static Optional isSuperTypeInterface(ClassReaderSource classSource, + String superType, String subType) { + if (superType.equals(subType)) { + return Optional.of(true); + } + ClassReader cls = classSource.get(subType); + if (cls == null) { + return Optional.empty(); + } + if (cls.getParent() != null) { + if (isSuperTypeInterface(classSource, superType, cls.getParent()).orElse(false)) { + return Optional.of(true); + } + } + for (String iface : cls.getInterfaces()) { + if (isSuperTypeInterface(classSource, superType, iface).orElse(false)) { + return Optional.of(true); + } + } + return Optional.of(false); + } }