From bab0cd59a6f4f38460d45425e65b17d10101f093 Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Thu, 13 Dec 2018 18:45:44 +0300 Subject: [PATCH] Improve performance and stability of development server and incremental compilation --- .../classlib/impl/PlatformMarkerSupport.java | 4 +- .../main/java/org/teavm/ast/FieldNode.java | 13 ++- .../teavm/ast/decompilation/Decompiler.java | 2 +- .../codegen/DefaultNamingStrategy.java | 64 +++++------ .../cache/AnnotationAwareCacheStatus.java | 19 +++- .../teavm/cache/InMemoryMethodNodeCache.java | 18 +++- .../org/teavm/cache/InMemoryProgramCache.java | 16 ++- .../org/teavm/dependency/DependencyAgent.java | 1 - .../teavm/dependency/DependencyAnalyzer.java | 1 - .../org/teavm/dependency/DependencyInfo.java | 3 - .../dependency/FastInstructionAnalyzer.java | 24 +++-- .../dependency/FastVirtualCallConsumer.java | 2 +- .../java/org/teavm/model/ClassHierarchy.java | 64 +++++++++++ .../teavm/model/analysis/ClassInference.java | 8 +- .../org/teavm/model/emit/ProgramEmitter.java | 4 +- .../teavm/model/optimization/Inlining.java | 31 ++++-- .../teavm/model/util/AsyncMethodFinder.java | 29 ++--- .../model/util/MissingItemsProcessor.java | 6 +- core/src/main/java/org/teavm/vm/TeaVM.java | 30 ++++-- .../MetaprogrammingDependencyListener.java | 1 + .../impl/MetaprogrammingImpl.java | 8 +- .../org/teavm/dependency/DependencyTest.java | 4 +- .../teavm/incremental/IncrementalTest.java | 2 + .../org/teavm/cli/TeaVMDevServerRunner.java | 1 + .../teavm/tooling/util/FileSystemWatcher.java | 35 ++++-- .../java/org/teavm/devserver/CodeServlet.java | 102 +++++++++++++++--- .../org/teavm/devserver/CodeWsEndpoint.java | 31 +++--- .../java/org/teavm/devserver/DevServer.java | 15 ++- 28 files changed, 388 insertions(+), 150 deletions(-) diff --git a/classlib/src/main/java/org/teavm/classlib/impl/PlatformMarkerSupport.java b/classlib/src/main/java/org/teavm/classlib/impl/PlatformMarkerSupport.java index 19adfc23d..e618d1635 100644 --- a/classlib/src/main/java/org/teavm/classlib/impl/PlatformMarkerSupport.java +++ b/classlib/src/main/java/org/teavm/classlib/impl/PlatformMarkerSupport.java @@ -70,7 +70,7 @@ public class PlatformMarkerSupport implements ClassHolderTransformer { MarkerKind kind; if (instruction instanceof InvokeInstruction) { MethodReference methodRef = ((InvokeInstruction) instruction).getMethod(); - MethodReader method = hierarchy.getClassSource().resolveImplementation(methodRef); + MethodReader method = hierarchy.resolve(methodRef); if (method == null) { continue; } @@ -94,7 +94,7 @@ public class PlatformMarkerSupport implements ClassHolderTransformer { receiver = ((InvokeInstruction) instruction).getReceiver(); } else if (instruction instanceof GetFieldInstruction) { FieldReference fieldRef = ((GetFieldInstruction) instruction).getField(); - FieldReader field = hierarchy.getClassSource().resolve(fieldRef); + FieldReader field = hierarchy.resolve(fieldRef); if (field == null) { continue; } diff --git a/core/src/main/java/org/teavm/ast/FieldNode.java b/core/src/main/java/org/teavm/ast/FieldNode.java index f02393edd..7113f86c1 100644 --- a/core/src/main/java/org/teavm/ast/FieldNode.java +++ b/core/src/main/java/org/teavm/ast/FieldNode.java @@ -18,21 +18,26 @@ package org.teavm.ast; import java.util.EnumSet; import java.util.Set; import org.teavm.model.ElementModifier; +import org.teavm.model.FieldReference; import org.teavm.model.ValueType; public class FieldNode { - private String name; + private FieldReference reference; private ValueType type; private Set modifiers = EnumSet.noneOf(ElementModifier.class); private Object initialValue; - public FieldNode(String name, ValueType type) { - this.name = name; + public FieldNode(FieldReference reference, ValueType type) { + this.reference = reference; this.type = type; } + public FieldReference getReference() { + return reference; + } + public String getName() { - return name; + return reference.getFieldName(); } public Set getModifiers() { diff --git a/core/src/main/java/org/teavm/ast/decompilation/Decompiler.java b/core/src/main/java/org/teavm/ast/decompilation/Decompiler.java index 7c7db9474..fb2c8f157 100644 --- a/core/src/main/java/org/teavm/ast/decompilation/Decompiler.java +++ b/core/src/main/java/org/teavm/ast/decompilation/Decompiler.java @@ -199,7 +199,7 @@ public class Decompiler { public ClassNode decompile(ClassHolder cls) { ClassNode clsNode = new ClassNode(cls.getName(), cls.getParent()); for (FieldHolder field : cls.getFields()) { - FieldNode fieldNode = new FieldNode(field.getName(), field.getType()); + FieldNode fieldNode = new FieldNode(field.getReference(), field.getType()); fieldNode.getModifiers().addAll(field.getModifiers()); fieldNode.setInitialValue(field.getInitialValue()); clsNode.getFields().add(fieldNode); diff --git a/core/src/main/java/org/teavm/backend/javascript/codegen/DefaultNamingStrategy.java b/core/src/main/java/org/teavm/backend/javascript/codegen/DefaultNamingStrategy.java index 352af216c..ca5e01ef0 100644 --- a/core/src/main/java/org/teavm/backend/javascript/codegen/DefaultNamingStrategy.java +++ b/core/src/main/java/org/teavm/backend/javascript/codegen/DefaultNamingStrategy.java @@ -25,8 +25,8 @@ public class DefaultNamingStrategy implements NamingStrategy { private final Map aliases = new HashMap<>(); private final Map privateAliases = new HashMap<>(); private final Map classAliases = new HashMap<>(); - private final Map fieldAliases = new HashMap<>(); - private final Map staticFieldAliases = new HashMap<>(); + private final Map fieldAliases = new HashMap<>(); + private final Map staticFieldAliases = new HashMap<>(); private final Map functionAliases = new HashMap<>(); private final Map classInitAliases = new HashMap<>(); @@ -75,27 +75,32 @@ public class DefaultNamingStrategy implements NamingStrategy { @Override public String getNameFor(FieldReference field) { - String realCls = getRealFieldOwner(field.getClassName(), field.getFieldName()); - if (!realCls.equals(field.getClassName())) { - String alias = getNameFor(new FieldReference(realCls, field.getFieldName())); - fieldAliases.put(field.getClassName() + "#" + field, alias); - return alias; - } else { - return fieldAliases.computeIfAbsent(realCls + "#" + field, key -> aliasProvider.getFieldAlias(field)); + String alias = fieldAliases.get(field); + if (alias == null) { + FieldReference realField = getRealField(field); + if (realField.equals(field)) { + alias = aliasProvider.getFieldAlias(realField); + } else { + alias = getNameFor(realField); + } + fieldAliases.put(field, alias); } + return alias; } @Override public String getFullNameFor(FieldReference field) { - String realCls = getRealFieldOwner(field.getClassName(), field.getFieldName()); - if (!realCls.equals(field.getClassName())) { - String alias = getNameFor(new FieldReference(realCls, field.getFieldName())); - staticFieldAliases.put(field.getClassName() + "#" + field, alias); - return alias; - } else { - return staticFieldAliases.computeIfAbsent(realCls + "#" + field, - key -> aliasProvider.getStaticFieldAlias(field)); + String alias = staticFieldAliases.get(field); + if (alias == null) { + FieldReference realField = getRealField(field); + if (realField.equals(field)) { + alias = aliasProvider.getStaticFieldAlias(realField); + } else { + alias = getNameFor(realField); + } + staticFieldAliases.put(field, alias); } + return alias; } @Override @@ -127,20 +132,19 @@ public class DefaultNamingStrategy implements NamingStrategy { return null; } - private String getRealFieldOwner(String cls, String field) { - String initialCls = cls; - while (!fieldExists(cls, field)) { - ClassReader clsHolder = classSource.get(cls); - if (clsHolder == null || clsHolder.getParent() == null) { - return initialCls; + private FieldReference getRealField(FieldReference fieldRef) { + String initialCls = fieldRef.getClassName(); + String cls = fieldRef.getClassName(); + while (cls != null) { + ClassReader clsReader = classSource.get(cls); + if (clsReader != null) { + FieldReader fieldReader = clsReader.getField(fieldRef.getFieldName()); + if (fieldReader != null) { + return fieldReader.getReference(); + } } - cls = clsHolder.getParent(); + cls = clsReader.getParent(); } - return cls; - } - - private boolean fieldExists(String cls, String field) { - ClassReader classHolder = classSource.get(cls); - return classHolder != null && classHolder.getField(field) != null; + return fieldRef; } } diff --git a/core/src/main/java/org/teavm/cache/AnnotationAwareCacheStatus.java b/core/src/main/java/org/teavm/cache/AnnotationAwareCacheStatus.java index 2beabe74f..d07ce195a 100644 --- a/core/src/main/java/org/teavm/cache/AnnotationAwareCacheStatus.java +++ b/core/src/main/java/org/teavm/cache/AnnotationAwareCacheStatus.java @@ -20,6 +20,8 @@ import com.carrotsearch.hppc.ObjectByteMap; import java.util.ArrayList; import java.util.List; import java.util.function.Predicate; +import org.teavm.model.ClassReader; +import org.teavm.model.ClassReaderSource; import org.teavm.model.MethodReference; public final class AnnotationAwareCacheStatus implements CacheStatus { @@ -32,10 +34,13 @@ public final class AnnotationAwareCacheStatus implements CacheStatus { private IncrementalDependencyProvider dependencyProvider; private List> synthesizedClasses = new ArrayList<>(); private ObjectByteMap classStatusCache = new ObjectByteHashMap<>(); + private ClassReaderSource classSource; - public AnnotationAwareCacheStatus(CacheStatus underlyingStatus, IncrementalDependencyProvider dependencyProvider) { + public AnnotationAwareCacheStatus(CacheStatus underlyingStatus, IncrementalDependencyProvider dependencyProvider, + ClassReaderSource classSource) { this.underlyingStatus = underlyingStatus; this.dependencyProvider = dependencyProvider; + this.classSource = classSource; } public void addSynthesizedClasses(Predicate synthesizedClasses) { @@ -72,6 +77,18 @@ public final class AnnotationAwareCacheStatus implements CacheStatus { return STALE; } + ClassReader cls = classSource.get(className); + if (cls != null) { + if (cls.getParent() != null && getClassStatus(cls.getParent()) == STALE) { + return STALE; + } + for (String itf : cls.getInterfaces()) { + if (getClassStatus(cls.getParent()) == STALE) { + return STALE; + } + } + } + return FRESH; } diff --git a/core/src/main/java/org/teavm/cache/InMemoryMethodNodeCache.java b/core/src/main/java/org/teavm/cache/InMemoryMethodNodeCache.java index f1a78daee..1fe47c283 100644 --- a/core/src/main/java/org/teavm/cache/InMemoryMethodNodeCache.java +++ b/core/src/main/java/org/teavm/cache/InMemoryMethodNodeCache.java @@ -25,7 +25,9 @@ import org.teavm.model.MethodReference; public class InMemoryMethodNodeCache implements MethodNodeCache { private Map cache = new HashMap<>(); + private Map newItems = new HashMap<>(); private Map asyncCache = new HashMap<>(); + private Map newAsyncItems = new HashMap<>(); @Override public RegularMethodNode get(MethodReference methodReference, CacheStatus cacheStatus) { @@ -43,7 +45,7 @@ public class InMemoryMethodNodeCache implements MethodNodeCache { @Override public void store(MethodReference methodReference, RegularMethodNode node, Supplier dependencies) { - cache.put(methodReference, new RegularItem(node, dependencies.get().clone())); + newItems.put(methodReference, new RegularItem(node, dependencies.get().clone())); } @Override @@ -62,7 +64,19 @@ public class InMemoryMethodNodeCache implements MethodNodeCache { @Override public void storeAsync(MethodReference methodReference, AsyncMethodNode node, Supplier dependencies) { - asyncCache.put(methodReference, new AsyncItem(node, dependencies.get().clone())); + newAsyncItems.put(methodReference, new AsyncItem(node, dependencies.get().clone())); + } + + public void commit() { + cache.putAll(newItems); + asyncCache.putAll(newAsyncItems); + newItems.clear(); + newAsyncItems.clear(); + } + + public void discard() { + newItems.clear(); + newAsyncItems.clear(); } static final class RegularItem { diff --git a/core/src/main/java/org/teavm/cache/InMemoryProgramCache.java b/core/src/main/java/org/teavm/cache/InMemoryProgramCache.java index 919714072..ee3f4c26a 100644 --- a/core/src/main/java/org/teavm/cache/InMemoryProgramCache.java +++ b/core/src/main/java/org/teavm/cache/InMemoryProgramCache.java @@ -25,6 +25,7 @@ import org.teavm.model.ProgramCache; public class InMemoryProgramCache implements ProgramCache { private Map cache = new HashMap<>(); + private Map newItems = new HashMap<>(); @Override public Program get(MethodReference method, CacheStatus cacheStatus) { @@ -42,7 +43,20 @@ public class InMemoryProgramCache implements ProgramCache { @Override public void store(MethodReference method, Program program, Supplier dependencies) { - cache.put(method, new Item(program, dependencies.get().clone())); + newItems.put(method, new Item(program, dependencies.get().clone())); + } + + public void commit() { + cache.putAll(newItems); + newItems.clear(); + } + + public int getPendingItemsCount() { + return newItems.size(); + } + + public void discard() { + newItems.clear(); } static final class Item { diff --git a/core/src/main/java/org/teavm/dependency/DependencyAgent.java b/core/src/main/java/org/teavm/dependency/DependencyAgent.java index bed54fac3..4d844537d 100644 --- a/core/src/main/java/org/teavm/dependency/DependencyAgent.java +++ b/core/src/main/java/org/teavm/dependency/DependencyAgent.java @@ -88,7 +88,6 @@ public class DependencyAgent implements DependencyInfo, ServiceRepository { return analyzer.getClassLoader(); } - @Override public ClassHierarchy getClassHierarchy() { return analyzer.getClassHierarchy(); } diff --git a/core/src/main/java/org/teavm/dependency/DependencyAnalyzer.java b/core/src/main/java/org/teavm/dependency/DependencyAnalyzer.java index f742334eb..ff9ae1abe 100644 --- a/core/src/main/java/org/teavm/dependency/DependencyAnalyzer.java +++ b/core/src/main/java/org/teavm/dependency/DependencyAnalyzer.java @@ -214,7 +214,6 @@ public abstract class DependencyAnalyzer implements DependencyInfo { return classSource.isGeneratedClass(className); } - @Override public ClassHierarchy getClassHierarchy() { return classHierarchy; } diff --git a/core/src/main/java/org/teavm/dependency/DependencyInfo.java b/core/src/main/java/org/teavm/dependency/DependencyInfo.java index e63c6b88b..a4f41dfb0 100644 --- a/core/src/main/java/org/teavm/dependency/DependencyInfo.java +++ b/core/src/main/java/org/teavm/dependency/DependencyInfo.java @@ -17,7 +17,6 @@ package org.teavm.dependency; import java.util.Collection; import org.teavm.callgraph.CallGraph; -import org.teavm.model.ClassHierarchy; import org.teavm.model.ClassReaderSource; import org.teavm.model.FieldReference; import org.teavm.model.MethodReference; @@ -27,8 +26,6 @@ public interface DependencyInfo { ClassLoader getClassLoader(); - ClassHierarchy getClassHierarchy(); - Collection getReachableMethods(); Collection getReachableFields(); diff --git a/core/src/main/java/org/teavm/dependency/FastInstructionAnalyzer.java b/core/src/main/java/org/teavm/dependency/FastInstructionAnalyzer.java index 24ad64673..6ae2fc6dd 100644 --- a/core/src/main/java/org/teavm/dependency/FastInstructionAnalyzer.java +++ b/core/src/main/java/org/teavm/dependency/FastInstructionAnalyzer.java @@ -22,7 +22,7 @@ import org.teavm.model.VariableReader; class FastInstructionAnalyzer extends AbstractInstructionAnalyzer { private FastDependencyAnalyzer dependencyAnalyzer; - private MethodReference callerMethod; + private CallLocation impreciseLocation; FastInstructionAnalyzer(FastDependencyAnalyzer dependencyAnalyzer) { this.dependencyAnalyzer = dependencyAnalyzer; @@ -31,7 +31,7 @@ class FastInstructionAnalyzer extends AbstractInstructionAnalyzer { @Override protected void invokeSpecial(VariableReader receiver, VariableReader instance, MethodReference method, List arguments) { - CallLocation callLocation = getCallLocation(); + CallLocation callLocation = impreciseLocation; if (instance == null) { dependencyAnalyzer.linkClass(method.getClassName()).initClass(callLocation); } @@ -43,19 +43,14 @@ class FastInstructionAnalyzer extends AbstractInstructionAnalyzer { @Override protected void invokeVirtual(VariableReader receiver, VariableReader instance, MethodReference method, List arguments) { - dependencyAnalyzer.getVirtualCallConsumer(method).addLocation(getCallLocation()); - - dependencyAnalyzer.getClassSource().overriddenMethods(method).forEach(methodImpl -> { - dependencyAnalyzer.linkMethod(methodImpl.getReference()).addLocation(getCallLocation()); - }); + dependencyAnalyzer.getVirtualCallConsumer(method).addLocation(impreciseLocation); } - @Override public void cloneArray(VariableReader receiver, VariableReader array) { DependencyNode arrayNode = getNode(array); MethodDependency cloneDep = getAnalyzer().linkMethod(CLONE_METHOD); - cloneDep.addLocation(getCallLocation()); + cloneDep.addLocation(impreciseLocation); cloneDep.use(); } @@ -68,4 +63,15 @@ class FastInstructionAnalyzer extends AbstractInstructionAnalyzer { protected DependencyAnalyzer getAnalyzer() { return dependencyAnalyzer; } + + @Override + protected CallLocation getCallLocation() { + return impreciseLocation; + } + + @Override + public void setCaller(MethodReference caller) { + super.setCaller(caller); + impreciseLocation = new CallLocation(caller); + } } diff --git a/core/src/main/java/org/teavm/dependency/FastVirtualCallConsumer.java b/core/src/main/java/org/teavm/dependency/FastVirtualCallConsumer.java index 276ff68b4..124227fa1 100644 --- a/core/src/main/java/org/teavm/dependency/FastVirtualCallConsumer.java +++ b/core/src/main/java/org/teavm/dependency/FastVirtualCallConsumer.java @@ -28,7 +28,7 @@ class FastVirtualCallConsumer implements DependencyConsumer { private final MethodDescriptor methodDesc; private final DependencyAnalyzer analyzer; private final Map callLocations = new LinkedHashMap<>(); - private final Set methods = new LinkedHashSet<>(); + private final Set methods = new LinkedHashSet<>(100, 0.5f); FastVirtualCallConsumer(DependencyNode node, MethodDescriptor methodDesc, DependencyAnalyzer analyzer) { this.node = node; diff --git a/core/src/main/java/org/teavm/model/ClassHierarchy.java b/core/src/main/java/org/teavm/model/ClassHierarchy.java index 506d77559..4013423cc 100644 --- a/core/src/main/java/org/teavm/model/ClassHierarchy.java +++ b/core/src/main/java/org/teavm/model/ClassHierarchy.java @@ -19,11 +19,14 @@ import com.carrotsearch.hppc.ObjectByteHashMap; import com.carrotsearch.hppc.ObjectByteMap; import java.util.HashMap; import java.util.Map; +import java.util.Optional; import org.teavm.common.OptionalPredicate; public class ClassHierarchy { private final ClassReaderSource classSource; private final Map> superclassPredicateCache = new HashMap<>(); + private final Map>> resolveMethodCache = new HashMap<>(); + private final Map>> resolveFieldCache = new HashMap<>(); public ClassHierarchy(ClassReaderSource classSource) { this.classSource = classSource; @@ -62,6 +65,67 @@ public class ClassHierarchy { return getSuperclassPredicate(superType).test(subType, defaultValue); } + public MethodReader resolve(MethodReference method) { + return resolve(method.getClassName(), method.getDescriptor()); + } + + public MethodReader resolve(String className, MethodDescriptor method) { + Map> cache = resolveMethodCache.computeIfAbsent(className, + k -> new HashMap<>()); + Optional opt = cache.get(method); + if (opt == null) { + MethodReader reader = null; + ClassReader cls = classSource.get(className); + if (cls != null) { + reader = cls.getMethod(method); + if (reader == null && cls.getParent() != null) { + reader = resolve(cls.getParent(), method); + } + if (reader == null) { + for (String itf : cls.getInterfaces()) { + reader = resolve(itf, method); + if (reader != null) { + break; + } + } + } + } + opt = Optional.ofNullable(reader); + cache.put(method, opt); + } + return opt.orElse(null); + } + + public FieldReader resolve(FieldReference field) { + return resolve(field.getClassName(), field.getFieldName()); + } + + public FieldReader resolve(String className, String fieldName) { + Map> cache = resolveFieldCache.computeIfAbsent(className, + k -> new HashMap<>()); + Optional opt = cache.get(fieldName); + if (opt == null) { + FieldReader reader = null; + ClassReader cls = classSource.get(className); + if (cls != null) { + if (cls.getParent() != null) { + reader = resolve(cls.getParent(), fieldName); + } + if (reader == null) { + for (String itf : cls.getInterfaces()) { + reader = resolve(itf, fieldName); + if (reader != null) { + break; + } + } + } + } + opt = Optional.ofNullable(reader); + cache.put(fieldName, opt); + } + return opt.orElse(null); + } + public OptionalPredicate getSuperclassPredicate(String superclass) { return superclassPredicateCache.computeIfAbsent(superclass, SuperclassPredicate::new); } 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 f00e0bc88..8d250dded 100644 --- a/core/src/main/java/org/teavm/model/analysis/ClassInference.java +++ b/core/src/main/java/org/teavm/model/analysis/ClassInference.java @@ -67,6 +67,7 @@ import org.teavm.model.instructions.UnwrapArrayInstruction; public class ClassInference { private DependencyInfo dependencyInfo; + private ClassHierarchy hierarchy; private Graph assignmentGraph; private Graph cloneGraph; private Graph arrayGraph; @@ -89,8 +90,9 @@ public class ClassInference { private static final int MAX_DEGREE = 3; - public ClassInference(DependencyInfo dependencyInfo) { + public ClassInference(DependencyInfo dependencyInfo, ClassHierarchy hierarchy) { this.dependencyInfo = dependencyInfo; + this.hierarchy = hierarchy; } public void infer(Program program, MethodReference methodReference) { @@ -403,8 +405,6 @@ public class ClassInference { } private void propagateAlongCasts() { - ClassHierarchy hierarchy = dependencyInfo.getClassHierarchy(); - for (ValueCast cast : casts) { int fromNode = nodeMapping[packNodeAndDegree(cast.fromVariable, 0)]; if (!formerNodeChanged[fromNode] && !nodeChanged[fromNode]) { @@ -500,8 +500,6 @@ public class ClassInference { } private void propagateException(String thrownTypeName, BasicBlock block) { - ClassHierarchy hierarchy = dependencyInfo.getClassHierarchy(); - for (TryCatchBlock tryCatch : block.getTryCatchBlocks()) { String expectedType = tryCatch.getExceptionType(); if (expectedType == null || hierarchy.isSuperType(expectedType, thrownTypeName, false)) { diff --git a/core/src/main/java/org/teavm/model/emit/ProgramEmitter.java b/core/src/main/java/org/teavm/model/emit/ProgramEmitter.java index 07c46e26b..2e7dad5a3 100644 --- a/core/src/main/java/org/teavm/model/emit/ProgramEmitter.java +++ b/core/src/main/java/org/teavm/model/emit/ProgramEmitter.java @@ -175,7 +175,7 @@ public final class ProgramEmitter { } public ValueEmitter getField(FieldReference field, ValueType type) { - FieldReader resolvedField = classSource.resolve(field); + FieldReader resolvedField = hierarchy.resolve(field); if (resolvedField != null) { field = resolvedField.getReference(); } @@ -198,7 +198,7 @@ public final class ProgramEmitter { } public ProgramEmitter setField(FieldReference field, ValueEmitter value) { - FieldReader resolvedField = classSource.resolve(field); + FieldReader resolvedField = hierarchy.resolve(field); if (resolvedField != null) { field = resolvedField.getReference(); } diff --git a/core/src/main/java/org/teavm/model/optimization/Inlining.java b/core/src/main/java/org/teavm/model/optimization/Inlining.java index 30c12f6ca..7e94a2fa2 100644 --- a/core/src/main/java/org/teavm/model/optimization/Inlining.java +++ b/core/src/main/java/org/teavm/model/optimization/Inlining.java @@ -25,6 +25,7 @@ import java.util.Set; import java.util.stream.Collectors; import org.teavm.dependency.DependencyInfo; import org.teavm.model.BasicBlock; +import org.teavm.model.ClassHierarchy; import org.teavm.model.ClassReader; import org.teavm.model.ClassReaderSource; import org.teavm.model.Incoming; @@ -55,16 +56,24 @@ public class Inlining { private static final int MAX_DEPTH = 7; private IntArrayList depthsByBlock; private Set instructionsToSkip; + private ClassHierarchy hierarchy; + private ClassReaderSource classes; + private DependencyInfo dependencyInfo; - public void apply(Program program, MethodReference method, ClassReaderSource classes, - DependencyInfo dependencyInfo) { + public Inlining(ClassHierarchy hierarchy, DependencyInfo dependencyInfo) { + this.hierarchy = hierarchy; + this.classes = hierarchy.getClassSource(); + this.dependencyInfo = dependencyInfo; + } + + public void apply(Program program, MethodReference method) { depthsByBlock = new IntArrayList(program.basicBlockCount()); for (int i = 0; i < program.basicBlockCount(); ++i) { depthsByBlock.add(0); } instructionsToSkip = new HashSet<>(); - while (applyOnce(program, classes)) { + while (applyOnce(program)) { devirtualize(program, method, dependencyInfo); } depthsByBlock = null; @@ -73,8 +82,8 @@ public class Inlining { new UnreachableBasicBlockEliminator().optimize(program); } - private boolean applyOnce(Program program, ClassReaderSource classSource) { - List plan = buildPlan(program, classSource, 0); + private boolean applyOnce(Program program) { + List plan = buildPlan(program, 0); if (plan.isEmpty()) { return false; } @@ -210,7 +219,7 @@ public class Inlining { execPlan(program, planEntry.innerPlan, firstInlineBlock.getIndex()); } - private List buildPlan(Program program, ClassReaderSource classSource, int depth) { + private List buildPlan(Program program, int depth) { if (depth >= MAX_DEPTH) { return Collections.emptyList(); } @@ -243,7 +252,7 @@ public class Inlining { continue; } - MethodReader invokedMethod = getMethod(classSource, invoke.getMethod()); + MethodReader invokedMethod = getMethod(invoke.getMethod()); if (invokedMethod == null || invokedMethod.getProgram() == null || invokedMethod.getProgram().basicBlockCount() == 0) { instructionsToSkip.add(insn); @@ -264,7 +273,7 @@ public class Inlining { entry.targetBlock = block.getIndex(); entry.targetInstruction = insn; entry.program = invokedProgram; - entry.innerPlan.addAll(buildPlan(invokedProgram, classSource, depth + 1)); + entry.innerPlan.addAll(buildPlan(invokedProgram, depth + 1)); entry.depth = depth; plan.add(entry); } @@ -274,8 +283,8 @@ public class Inlining { return plan; } - private MethodReader getMethod(ClassReaderSource classSource, MethodReference methodRef) { - ClassReader cls = classSource.get(methodRef.getClassName()); + private MethodReader getMethod(MethodReference methodRef) { + ClassReader cls = classes.get(methodRef.getClassName()); return cls != null ? cls.getMethod(methodRef.getDescriptor()) : null; } @@ -308,7 +317,7 @@ public class Inlining { } private void devirtualize(Program program, MethodReference method, DependencyInfo dependencyInfo) { - ClassInference inference = new ClassInference(dependencyInfo); + ClassInference inference = new ClassInference(dependencyInfo, hierarchy); inference.infer(program, method); for (BasicBlock block : program.getBasicBlocks()) { diff --git a/core/src/main/java/org/teavm/model/util/AsyncMethodFinder.java b/core/src/main/java/org/teavm/model/util/AsyncMethodFinder.java index 70ed8c28b..5f5d3a47d 100644 --- a/core/src/main/java/org/teavm/model/util/AsyncMethodFinder.java +++ b/core/src/main/java/org/teavm/model/util/AsyncMethodFinder.java @@ -46,6 +46,7 @@ public class AsyncMethodFinder { private Map asyncFamilyMethods = new HashMap<>(); private Set readonlyAsyncMethods = Collections.unmodifiableSet(asyncMethods); private Set readonlyAsyncFamilyMethods = Collections.unmodifiableSet(asyncFamilyMethods.keySet()); + private Map> overiddenMethodsCache = new HashMap<>(); private CallGraph callGraph; private Diagnostics diagnostics; private ListableClassReaderSource classSource; @@ -209,7 +210,7 @@ public class AsyncMethodFinder { if (cls == null) { return; } - for (MethodReference overriddenMethod : findOverriddenMethods(cls, methodRef)) { + for (MethodReference overriddenMethod : findOverriddenMethods(cls, methodRef.getDescriptor())) { addOverriddenToFamily(overriddenMethod); } } @@ -232,7 +233,7 @@ public class AsyncMethodFinder { if (cls == null) { return false; } - for (MethodReference overriddenMethod : findOverriddenMethods(cls, methodRef)) { + for (MethodReference overriddenMethod : findOverriddenMethods(cls, methodRef.getDescriptor())) { if (addToFamily(overriddenMethod)) { return true; } @@ -240,44 +241,44 @@ public class AsyncMethodFinder { return false; } - private Set findOverriddenMethods(ClassReader cls, MethodReference methodRef) { + private Set findOverriddenMethods(ClassReader cls, MethodDescriptor methodDesc) { List parents = new ArrayList<>(); if (cls.getParent() != null) { parents.add(cls.getParent()); } parents.addAll(cls.getInterfaces()); - Set visited = new HashSet<>(); + Set visited = new HashSet<>(); Set overridden = new HashSet<>(); for (String parent : parents) { - findOverriddenMethods(new MethodReference(parent, methodRef.getDescriptor()), overridden, visited); + findOverriddenMethods(parent, methodDesc, overridden, visited); } return overridden; } - private void findOverriddenMethods(MethodReference methodRef, Set result, - Set visited) { - if (!visited.add(methodRef)) { + private void findOverriddenMethods(String className, MethodDescriptor methodDesc, Set result, + Set visited) { + if (!visited.add(className)) { return; } - if (methodRef.getName().equals("") || methodRef.getName().equals("")) { + if (methodDesc.getName().equals("") || methodDesc.getName().equals("")) { return; } - ClassReader cls = classSource.get(methodRef.getClassName()); + ClassReader cls = classSource.get(className); if (cls == null) { return; } - MethodReader method = cls.getMethod(methodRef.getDescriptor()); + MethodReader method = cls.getMethod(methodDesc); if (method != null) { if (!method.hasModifier(ElementModifier.STATIC) && !method.hasModifier(ElementModifier.FINAL)) { - result.add(methodRef); + result.add(method.getReference()); } } else { if (cls.getParent() != null) { - findOverriddenMethods(new MethodReference(cls.getParent(), methodRef.getDescriptor()), result, visited); + findOverriddenMethods(cls.getParent(), methodDesc, result, visited); } for (String iface : cls.getInterfaces()) { - findOverriddenMethods(new MethodReference(iface, methodRef.getDescriptor()), result, visited); + findOverriddenMethods(iface, methodDesc, result, visited); } } } diff --git a/core/src/main/java/org/teavm/model/util/MissingItemsProcessor.java b/core/src/main/java/org/teavm/model/util/MissingItemsProcessor.java index bcd95a3e0..1ec9f645f 100644 --- a/core/src/main/java/org/teavm/model/util/MissingItemsProcessor.java +++ b/core/src/main/java/org/teavm/model/util/MissingItemsProcessor.java @@ -27,6 +27,7 @@ import org.teavm.model.optimization.UnreachableBasicBlockEliminator; public class MissingItemsProcessor { private DependencyInfo dependencyInfo; + private ClassHierarchy hierarchy; private Diagnostics diagnostics; private List instructionsToAdd = new ArrayList<>(); private MethodHolder methodHolder; @@ -35,9 +36,10 @@ public class MissingItemsProcessor { private Collection reachableMethods; private Collection reachableFields; - public MissingItemsProcessor(DependencyInfo dependencyInfo, Diagnostics diagnostics) { + public MissingItemsProcessor(DependencyInfo dependencyInfo, ClassHierarchy hierarchy, Diagnostics diagnostics) { this.dependencyInfo = dependencyInfo; this.diagnostics = diagnostics; + this.hierarchy = hierarchy; reachableClasses = dependencyInfo.getReachableClasses(); reachableMethods = dependencyInfo.getReachableMethods(); reachableFields = dependencyInfo.getReachableFields(); @@ -171,7 +173,7 @@ public class MissingItemsProcessor { return true; } - if (dependencyInfo.getClassSource().resolve(method) != null) { + if (hierarchy.resolve(method) != null) { return true; } diff --git a/core/src/main/java/org/teavm/vm/TeaVM.java b/core/src/main/java/org/teavm/vm/TeaVM.java index 40d8ad006..76442bc03 100644 --- a/core/src/main/java/org/teavm/vm/TeaVM.java +++ b/core/src/main/java/org/teavm/vm/TeaVM.java @@ -47,6 +47,7 @@ import org.teavm.dependency.MethodDependency; import org.teavm.diagnostics.AccumulationDiagnostics; import org.teavm.diagnostics.Diagnostics; import org.teavm.diagnostics.ProblemProvider; +import org.teavm.model.ClassHierarchy; import org.teavm.model.ClassHolder; import org.teavm.model.ClassHolderSource; import org.teavm.model.ClassHolderTransformer; @@ -372,7 +373,8 @@ public class TeaVM implements TeaVMHost, ServiceRepository { return; } - cacheStatus = new AnnotationAwareCacheStatus(rawCacheStatus, dependencyAnalyzer.getIncrementalDependencies()); + cacheStatus = new AnnotationAwareCacheStatus(rawCacheStatus, dependencyAnalyzer.getIncrementalDependencies(), + dependencyAnalyzer.getClassSource()); cacheStatus.addSynthesizedClasses(dependencyAnalyzer::isSynthesizedClass); // Link @@ -423,11 +425,12 @@ public class TeaVM implements TeaVMHost, ServiceRepository { } @SuppressWarnings("WeakerAccess") - public ListableClassHolderSource link(DependencyInfo dependency) { + public ListableClassHolderSource link(DependencyAnalyzer dependency) { reportPhase(TeaVMPhase.LINKING, dependency.getReachableClasses().size()); Linker linker = new Linker(); MutableClassHolderSource cutClasses = new MutableClassHolderSource(); - MissingItemsProcessor missingItemsProcessor = new MissingItemsProcessor(dependency, diagnostics); + MissingItemsProcessor missingItemsProcessor = new MissingItemsProcessor(dependency, + dependency.getClassHierarchy(), diagnostics); if (wasCancelled()) { return cutClasses; } @@ -446,7 +449,10 @@ public class TeaVM implements TeaVMHost, ServiceRepository { cutClasses.putClassHolder(cls); missingItemsProcessor.processClass(cls); linker.link(dependency, cls); - progressListener.progressReached(++index); + reportProgress(++index); + if (wasCancelled()) { + break; + } } return cutClasses; } @@ -457,6 +463,12 @@ public class TeaVM implements TeaVMHost, ServiceRepository { } } + private void reportProgress(int progress) { + if (progressListener.progressReached(progress) == TeaVMProgressFeedback.CANCEL) { + cancelled = true; + } + } + private int devirtualize(int progress, ListableClassHolderSource classes, DependencyInfo dependency) { if (wasCancelled()) { return progress; @@ -470,7 +482,7 @@ public class TeaVM implements TeaVMHost, ServiceRepository { devirtualization.apply(method); } } - progressListener.progressReached(++index); + reportProgress(++index); if (wasCancelled()) { break; } @@ -485,19 +497,19 @@ public class TeaVM implements TeaVMHost, ServiceRepository { } Map inlinedPrograms = new HashMap<>(); - Inlining inlining = new Inlining(); + Inlining inlining = new Inlining(new ClassHierarchy(classes), dependencyInfo); for (String className : classes.getClassNames()) { ClassHolder cls = classes.get(className); for (MethodHolder method : cls.getMethods()) { if (method.getProgram() != null) { Program program = ProgramUtils.copy(method.getProgram()); MethodOptimizationContextImpl context = new MethodOptimizationContextImpl(method, classes); - inlining.apply(program, method.getReference(), classes, dependencyInfo); + inlining.apply(program, method.getReference()); new UnusedVariableElimination().optimize(context, program); inlinedPrograms.put(method.getReference(), program); } } - progressListener.progressReached(++progress); + reportProgress(++progress); if (wasCancelled()) { break; } @@ -521,7 +533,7 @@ public class TeaVM implements TeaVMHost, ServiceRepository { for (MethodHolder method : cls.getMethods()) { processMethod(method, classSource); } - progressListener.progressReached(++progress); + reportProgress(++progress); if (wasCancelled()) { break; } diff --git a/metaprogramming/impl/src/main/java/org/teavm/metaprogramming/impl/MetaprogrammingDependencyListener.java b/metaprogramming/impl/src/main/java/org/teavm/metaprogramming/impl/MetaprogrammingDependencyListener.java index d84eb1186..b2c02989b 100644 --- a/metaprogramming/impl/src/main/java/org/teavm/metaprogramming/impl/MetaprogrammingDependencyListener.java +++ b/metaprogramming/impl/src/main/java/org/teavm/metaprogramming/impl/MetaprogrammingDependencyListener.java @@ -46,6 +46,7 @@ public class MetaprogrammingDependencyListener extends AbstractDependencyListene MetaprogrammingImpl.classLoader = proxyClassLoader; MetaprogrammingImpl.classSource = agent.getClassSource(); + MetaprogrammingImpl.hierarchy = agent.getClassHierarchy(); MetaprogrammingImpl.incrementaDependencies = agent.getIncrementalCache(); MetaprogrammingImpl.agent = agent; MetaprogrammingImpl.reflectContext = new ReflectContext(agent.getClassHierarchy(), proxyClassLoader); diff --git a/metaprogramming/impl/src/main/java/org/teavm/metaprogramming/impl/MetaprogrammingImpl.java b/metaprogramming/impl/src/main/java/org/teavm/metaprogramming/impl/MetaprogrammingImpl.java index 54c005448..597997e3e 100644 --- a/metaprogramming/impl/src/main/java/org/teavm/metaprogramming/impl/MetaprogrammingImpl.java +++ b/metaprogramming/impl/src/main/java/org/teavm/metaprogramming/impl/MetaprogrammingImpl.java @@ -36,6 +36,7 @@ import org.teavm.metaprogramming.reflect.ReflectMethod; import org.teavm.model.AccessLevel; import org.teavm.model.BasicBlock; import org.teavm.model.CallLocation; +import org.teavm.model.ClassHierarchy; import org.teavm.model.ClassHolder; import org.teavm.model.ClassReaderSource; import org.teavm.model.ElementModifier; @@ -63,6 +64,7 @@ public final class MetaprogrammingImpl { static Map proxySuffixGenerators = new HashMap<>(); static ClassLoader classLoader; static ClassReaderSource classSource; + static ClassHierarchy hierarchy; static IncrementalDependencyRegistration incrementaDependencies; static ReflectContext reflectContext; static DependencyAgent agent; @@ -87,7 +89,7 @@ public final class MetaprogrammingImpl { return var != null ? new ValueImpl<>(var, varContext, valueImpl.type) : null; } else { Fragment fragment = (Fragment) computation; - MethodReader method = classSource.resolve(fragment.method); + MethodReader method = hierarchy.resolve(fragment.method); generator.addProgram(method.getProgram(), fragment.capturedValues); return new ValueImpl<>(generator.getResultVar(), varContext, fragment.method.getReturnType()); } @@ -95,7 +97,7 @@ public final class MetaprogrammingImpl { public static void emit(Action action) { Fragment fragment = (Fragment) action; - MethodReader method = classSource.resolve(fragment.method); + MethodReader method = hierarchy.resolve(fragment.method); generator.addProgram(method.getProgram(), fragment.capturedValues); } @@ -121,7 +123,7 @@ public final class MetaprogrammingImpl { if (value instanceof Fragment) { Fragment fragment = (Fragment) value; - MethodReader method = classSource.resolve(fragment.method); + MethodReader method = hierarchy.resolve(fragment.method); generator.addProgram(method.getProgram(), fragment.capturedValues); generator.blockIndex = generator.returnBlockIndex; diff --git a/tests/src/test/java/org/teavm/dependency/DependencyTest.java b/tests/src/test/java/org/teavm/dependency/DependencyTest.java index 86577dedb..852363bb5 100644 --- a/tests/src/test/java/org/teavm/dependency/DependencyTest.java +++ b/tests/src/test/java/org/teavm/dependency/DependencyTest.java @@ -33,6 +33,7 @@ import org.teavm.backend.javascript.JavaScriptTarget; import org.teavm.common.DisjointSet; import org.teavm.diagnostics.Problem; import org.teavm.model.BasicBlock; +import org.teavm.model.ClassHierarchy; import org.teavm.model.ClassHolderSource; import org.teavm.model.Instruction; import org.teavm.model.MethodHolder; @@ -145,7 +146,8 @@ public class DependencyTest { private void processAssertions(List assertions, MethodDependencyInfo methodDep, DependencyInfo dependencyInfo, Program program) { - ClassInference classInference = new ClassInference(dependencyInfo); + ClassInference classInference = new ClassInference(dependencyInfo, new ClassHierarchy( + dependencyInfo.getClassSource())); classInference.infer(program, methodDep.getReference()); for (Assertion assertion : assertions) { diff --git a/tests/src/test/java/org/teavm/incremental/IncrementalTest.java b/tests/src/test/java/org/teavm/incremental/IncrementalTest.java index dfebb8a0a..4f00b576f 100644 --- a/tests/src/test/java/org/teavm/incremental/IncrementalTest.java +++ b/tests/src/test/java/org/teavm/incremental/IncrementalTest.java @@ -213,6 +213,8 @@ public class IncrementalTest { if (!problems.isEmpty()) { fail("Compiler error generating file '" + name + "'\n" + buildErrorMessage(vm)); } + astCache.commit(); + programCache.commit(); } private String buildErrorMessage(TeaVM vm) { diff --git a/tools/cli/src/main/java/org/teavm/cli/TeaVMDevServerRunner.java b/tools/cli/src/main/java/org/teavm/cli/TeaVMDevServerRunner.java index 8073ac924..123d6f837 100644 --- a/tools/cli/src/main/java/org/teavm/cli/TeaVMDevServerRunner.java +++ b/tools/cli/src/main/java/org/teavm/cli/TeaVMDevServerRunner.java @@ -114,6 +114,7 @@ public final class TeaVMDevServerRunner { devServer.setIndicator(commandLine.hasOption("indicator")); devServer.setReloadedAutomatically(commandLine.hasOption("auto-reload")); + devServer.setVerbose(commandLine.hasOption('v')); if (commandLine.hasOption("port")) { try { devServer.setPort(Integer.parseInt(commandLine.getOptionValue("port"))); diff --git a/tools/core/src/main/java/org/teavm/tooling/util/FileSystemWatcher.java b/tools/core/src/main/java/org/teavm/tooling/util/FileSystemWatcher.java index d37482961..6676206f8 100644 --- a/tools/core/src/main/java/org/teavm/tooling/util/FileSystemWatcher.java +++ b/tools/core/src/main/java/org/teavm/tooling/util/FileSystemWatcher.java @@ -43,6 +43,7 @@ public class FileSystemWatcher { private WatchService watchService; private Map keysToPath = new HashMap<>(); private Map pathsToKey = new HashMap<>(); + private Map refCount = new HashMap<>(); private Set changedFiles = new LinkedHashSet<>(); public FileSystemWatcher(String[] classPath) throws IOException { @@ -51,9 +52,7 @@ public class FileSystemWatcher { Path path = Paths.get(entry); File file = path.toFile(); if (file.exists()) { - if (!file.isDirectory()) { - registerSingle(path.getParent()); - } else { + if (file.isDirectory()) { register(path); } } @@ -74,6 +73,13 @@ public class FileSystemWatcher { .collect(Collectors.toList())); return FileVisitResult.CONTINUE; } + + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { + Path parent = file.getParent(); + refCount.put(parent, refCount.getOrDefault(parent, 0) + 1); + return FileVisitResult.CONTINUE; + } }); return files; } @@ -83,6 +89,9 @@ public class FileSystemWatcher { StandardWatchEventKinds.ENTRY_DELETE, StandardWatchEventKinds.ENTRY_MODIFY); keysToPath.put(key, path); pathsToKey.put(path, key); + refCount.put(path, refCount.getOrDefault(path, 0) + 1); + Path parent = path.getParent(); + refCount.put(parent, refCount.getOrDefault(parent, 0) + 1); } public boolean hasChanges() throws IOException { @@ -159,9 +168,6 @@ public class FileSystemWatcher { return Collections.emptyList(); } Path basePath = keysToPath.get(baseKey); - if (basePath == null) { - return Collections.emptyList(); - } Path path = basePath.resolve((Path) event.context()); WatchKey key = pathsToKey.get(path); @@ -170,9 +176,8 @@ public class FileSystemWatcher { if (event.kind() == StandardWatchEventKinds.ENTRY_DELETE) { if (key != null) { - pathsToKey.remove(path); - keysToPath.remove(key); key.cancel(); + releasePath(path); } } else if (event.kind() == StandardWatchEventKinds.ENTRY_CREATE) { if (Files.isDirectory(path)) { @@ -181,4 +186,18 @@ public class FileSystemWatcher { } return result; } + + private void releasePath(Path path) { + refCount.put(path, refCount.getOrDefault(path, 0) - 1); + while (refCount.getOrDefault(path, 0) <= 0) { + WatchKey key = pathsToKey.get(path); + if (key == null) { + break; + } + pathsToKey.remove(path); + keysToPath.remove(key); + path = path.getParent(); + refCount.put(path, refCount.getOrDefault(path, 0) - 1); + } + } } diff --git a/tools/devserver/src/main/java/org/teavm/devserver/CodeServlet.java b/tools/devserver/src/main/java/org/teavm/devserver/CodeServlet.java index 37530efcc..1d0e1d026 100644 --- a/tools/devserver/src/main/java/org/teavm/devserver/CodeServlet.java +++ b/tools/devserver/src/main/java/org/teavm/devserver/CodeServlet.java @@ -32,8 +32,10 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.function.Supplier; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; @@ -92,7 +94,10 @@ public class CodeServlet extends HttpServlet { private final Map content = new HashMap<>(); private MemoryBuildTarget buildTarget = new MemoryBuildTarget(); - private CodeWsEndpoint wsEndpoint; + private final Set wsEndpoints = new LinkedHashSet<>(); + private final Object statusLock = new Object(); + private boolean compiling; + private double progress; public CodeServlet(String mainClass, String[] classPath) { this.mainClass = mainClass; @@ -129,14 +134,32 @@ public class CodeServlet extends HttpServlet { this.port = port; } - public void setWsEndpoint(CodeWsEndpoint wsEndpoint) { - this.wsEndpoint = wsEndpoint; - } - public void setAutomaticallyReloaded(boolean automaticallyReloaded) { this.automaticallyReloaded = automaticallyReloaded; } + public void addWsEndpoint(CodeWsEndpoint endpoint) { + synchronized (wsEndpoints) { + wsEndpoints.add(endpoint); + } + + double progress; + synchronized (statusLock) { + if (!compiling) { + return; + } + progress = this.progress; + } + + endpoint.progress(progress); + } + + public void removeWsEndpoint(CodeWsEndpoint endpoint) { + synchronized (wsEndpoints) { + wsEndpoints.remove(endpoint); + } + } + @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { String path = req.getPathInfo(); @@ -289,8 +312,17 @@ public class CodeServlet extends HttpServlet { break; } log.info("Changes detected. Recompiling."); + List staleClasses = getChangedClasses(watcher.grabChangedFiles()); - log.debug("Following classes changed: " + staleClasses); + if (staleClasses.size() > 15) { + List displayedStaleClasses = staleClasses.subList(0, 10); + log.debug("Following classes changed (" + staleClasses.size() + "): " + + String.join(", ", displayedStaleClasses) + " and more..."); + } else { + log.debug("Following classes changed (" + staleClasses.size() + "): " + + String.join(", ", staleClasses)); + } + classSource.evict(staleClasses); } } catch (Throwable e) { @@ -358,9 +390,7 @@ public class CodeServlet extends HttpServlet { log.info("Starting build"); progressListener.last = 0; progressListener.lastTime = System.currentTimeMillis(); - if (wsEndpoint != null) { - wsEndpoint.progress(0); - } + reportProgress(0); vm.build(buildTarget, fileName); addIndicator(); generateDebug(debugInformationBuilder); @@ -419,19 +449,18 @@ public class CodeServlet extends HttpServlet { private void postBuild(TeaVM vm, long startTime) { if (!vm.wasCancelled()) { + log.info("Recompiled stale methods: " + programCache.getPendingItemsCount()); if (vm.getProblemProvider().getSevereProblems().isEmpty()) { log.info("Build complete successfully"); saveNewResult(); lastReachedClasses = vm.getDependencyInfo().getReachableClasses().size(); classSource.commit(); - if (wsEndpoint != null) { - wsEndpoint.complete(true); - } + programCache.commit(); + astCache.commit(); + reportCompilationComplete(true); } else { log.info("Build complete with errors"); - if (wsEndpoint != null) { - wsEndpoint.complete(false); - } + reportCompilationComplete(false); } printStats(vm, startTime); TeaVMProblemRenderer.describeProblems(vm, log); @@ -439,6 +468,8 @@ public class CodeServlet extends HttpServlet { log.info("Build cancelled"); } + astCache.discard(); + programCache.discard(); buildTarget.clear(); } @@ -505,6 +536,43 @@ public class CodeServlet extends HttpServlet { return new URLClassLoader(urls, CodeServlet.class.getClassLoader()); } + private void reportProgress(double progress) { + synchronized (statusLock) { + if (compiling && this.progress == progress) { + return; + } + compiling = true; + this.progress = progress; + } + + CodeWsEndpoint[] endpoints; + synchronized (wsEndpoints) { + endpoints = wsEndpoints.toArray(new CodeWsEndpoint[0]); + } + + for (CodeWsEndpoint endpoint : endpoints) { + endpoint.progress(progress); + } + } + + private void reportCompilationComplete(boolean success) { + synchronized (statusLock) { + if (!compiling) { + return; + } + compiling = false; + } + + CodeWsEndpoint[] endpoints; + synchronized (wsEndpoints) { + endpoints = wsEndpoints.toArray(new CodeWsEndpoint[0]); + } + + for (CodeWsEndpoint endpoint : endpoints) { + endpoint.complete(success); + } + } + private final ProgressListenerImpl progressListener = new ProgressListenerImpl(); class ProgressListenerImpl implements TeaVMProgressListener { @@ -540,13 +608,13 @@ public class CodeServlet extends HttpServlet { @Override public TeaVMProgressFeedback progressReached(int progress) { - if (wsEndpoint != null && indicator) { + if (indicator) { int current = start + Math.min(progress, phaseLimit) * (end - start) / phaseLimit; if (current != last) { if (current - last > 10 || System.currentTimeMillis() - lastTime > 100) { lastTime = System.currentTimeMillis(); last = current; - wsEndpoint.progress(current / 10.0); + reportProgress(current / 10.0); } } } diff --git a/tools/devserver/src/main/java/org/teavm/devserver/CodeWsEndpoint.java b/tools/devserver/src/main/java/org/teavm/devserver/CodeWsEndpoint.java index 76bc95151..278cad802 100644 --- a/tools/devserver/src/main/java/org/teavm/devserver/CodeWsEndpoint.java +++ b/tools/devserver/src/main/java/org/teavm/devserver/CodeWsEndpoint.java @@ -15,7 +15,7 @@ */ package org.teavm.devserver; -import java.util.function.Consumer; +import javax.websocket.OnClose; import javax.websocket.OnOpen; import javax.websocket.Session; import javax.websocket.server.ServerEndpoint; @@ -23,38 +23,35 @@ import javax.websocket.server.ServerEndpoint; @ServerEndpoint("/") public class CodeWsEndpoint { private Session session; - private boolean compiling; - private double progress; + private CodeServlet servlet; @OnOpen public void open(Session session) { this.session = session; - @SuppressWarnings("unchecked") - Consumer consumer = (Consumer) session.getUserProperties().get("ws.consumer"); - if (consumer != null) { - consumer.accept(this); + servlet = (CodeServlet) session.getUserProperties().get("teavm.servlet"); + if (servlet != null) { + servlet.addWsEndpoint(this); } - if (compiling) { - sendProgress(progress); + } + + @OnClose + public void close() { + if (servlet != null) { + servlet.removeWsEndpoint(this); } + servlet = null; + session = null; } public void progress(double value) { if (session != null) { - sendProgress(value); + session.getAsyncRemote().sendText("{ \"command\": \"compiling\", \"progress\": " + value + " }"); } - compiling = true; - progress = value; } public void complete(boolean success) { if (session != null) { session.getAsyncRemote().sendText("{ \"command\": \"complete\", \"success\": " + success + " }"); } - compiling = false; - } - - private void sendProgress(double value) { - session.getAsyncRemote().sendText("{ \"command\": \"compiling\", \"progress\": " + value + " }"); } } diff --git a/tools/devserver/src/main/java/org/teavm/devserver/DevServer.java b/tools/devserver/src/main/java/org/teavm/devserver/DevServer.java index 8aa43a639..675e7c1dd 100644 --- a/tools/devserver/src/main/java/org/teavm/devserver/DevServer.java +++ b/tools/devserver/src/main/java/org/teavm/devserver/DevServer.java @@ -20,7 +20,6 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.function.Consumer; import javax.websocket.Decoder; import javax.websocket.Encoder; import javax.websocket.Extension; @@ -42,7 +41,8 @@ public class DevServer { private List sourcePath = new ArrayList<>(); private boolean indicator; private boolean reloadedAutomatically; - private TeaVMToolLog log = new ConsoleTeaVMToolLog(false); + private boolean verbose; + private TeaVMToolLog log; private Server server; private int port = 9090; @@ -85,11 +85,16 @@ public class DevServer { this.reloadedAutomatically = reloadedAutomatically; } + public void setVerbose(boolean verbose) { + this.verbose = verbose; + } + public List getSourcePath() { return sourcePath; } public void start() { + log = new ConsoleTeaVMToolLog(verbose); server = new Server(); ServerConnector connector = new ServerConnector(server); connector.setPort(port); @@ -110,7 +115,7 @@ public class DevServer { try { ServerContainer wscontainer = WebSocketServerContainerInitializer.configureContext(context); - wscontainer.addEndpoint(new DevServerEndpointConfig(servlet::setWsEndpoint)); + wscontainer.addEndpoint(new DevServerEndpointConfig(servlet)); server.start(); server.join(); } catch (Exception e) { @@ -129,8 +134,8 @@ public class DevServer { private class DevServerEndpointConfig implements ServerEndpointConfig { private Map userProperties = new HashMap<>(); - public DevServerEndpointConfig(Consumer consumer) { - userProperties.put("ws.consumer", consumer); + public DevServerEndpointConfig(CodeServlet servlet) { + userProperties.put("teavm.servlet", servlet); } @Override