Improve performance and stability of development server and incremental compilation

This commit is contained in:
Alexey Andreev 2018-12-13 18:45:44 +03:00
parent c9c9c15dfe
commit bab0cd59a6
28 changed files with 388 additions and 150 deletions

View File

@ -70,7 +70,7 @@ public class PlatformMarkerSupport implements ClassHolderTransformer {
MarkerKind kind; MarkerKind kind;
if (instruction instanceof InvokeInstruction) { if (instruction instanceof InvokeInstruction) {
MethodReference methodRef = ((InvokeInstruction) instruction).getMethod(); MethodReference methodRef = ((InvokeInstruction) instruction).getMethod();
MethodReader method = hierarchy.getClassSource().resolveImplementation(methodRef); MethodReader method = hierarchy.resolve(methodRef);
if (method == null) { if (method == null) {
continue; continue;
} }
@ -94,7 +94,7 @@ public class PlatformMarkerSupport implements ClassHolderTransformer {
receiver = ((InvokeInstruction) instruction).getReceiver(); receiver = ((InvokeInstruction) instruction).getReceiver();
} else if (instruction instanceof GetFieldInstruction) { } else if (instruction instanceof GetFieldInstruction) {
FieldReference fieldRef = ((GetFieldInstruction) instruction).getField(); FieldReference fieldRef = ((GetFieldInstruction) instruction).getField();
FieldReader field = hierarchy.getClassSource().resolve(fieldRef); FieldReader field = hierarchy.resolve(fieldRef);
if (field == null) { if (field == null) {
continue; continue;
} }

View File

@ -18,21 +18,26 @@ package org.teavm.ast;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.Set; import java.util.Set;
import org.teavm.model.ElementModifier; import org.teavm.model.ElementModifier;
import org.teavm.model.FieldReference;
import org.teavm.model.ValueType; import org.teavm.model.ValueType;
public class FieldNode { public class FieldNode {
private String name; private FieldReference reference;
private ValueType type; private ValueType type;
private Set<ElementModifier> modifiers = EnumSet.noneOf(ElementModifier.class); private Set<ElementModifier> modifiers = EnumSet.noneOf(ElementModifier.class);
private Object initialValue; private Object initialValue;
public FieldNode(String name, ValueType type) { public FieldNode(FieldReference reference, ValueType type) {
this.name = name; this.reference = reference;
this.type = type; this.type = type;
} }
public FieldReference getReference() {
return reference;
}
public String getName() { public String getName() {
return name; return reference.getFieldName();
} }
public Set<ElementModifier> getModifiers() { public Set<ElementModifier> getModifiers() {

View File

@ -199,7 +199,7 @@ public class Decompiler {
public ClassNode decompile(ClassHolder cls) { public ClassNode decompile(ClassHolder cls) {
ClassNode clsNode = new ClassNode(cls.getName(), cls.getParent()); ClassNode clsNode = new ClassNode(cls.getName(), cls.getParent());
for (FieldHolder field : cls.getFields()) { 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.getModifiers().addAll(field.getModifiers());
fieldNode.setInitialValue(field.getInitialValue()); fieldNode.setInitialValue(field.getInitialValue());
clsNode.getFields().add(fieldNode); clsNode.getFields().add(fieldNode);

View File

@ -25,8 +25,8 @@ public class DefaultNamingStrategy implements NamingStrategy {
private final Map<String, String> aliases = new HashMap<>(); private final Map<String, String> aliases = new HashMap<>();
private final Map<String, String> privateAliases = new HashMap<>(); private final Map<String, String> privateAliases = new HashMap<>();
private final Map<String, String> classAliases = new HashMap<>(); private final Map<String, String> classAliases = new HashMap<>();
private final Map<String, String> fieldAliases = new HashMap<>(); private final Map<FieldReference, String> fieldAliases = new HashMap<>();
private final Map<String, String> staticFieldAliases = new HashMap<>(); private final Map<FieldReference, String> staticFieldAliases = new HashMap<>();
private final Map<String, String> functionAliases = new HashMap<>(); private final Map<String, String> functionAliases = new HashMap<>();
private final Map<String, String> classInitAliases = new HashMap<>(); private final Map<String, String> classInitAliases = new HashMap<>();
@ -75,27 +75,32 @@ public class DefaultNamingStrategy implements NamingStrategy {
@Override @Override
public String getNameFor(FieldReference field) { public String getNameFor(FieldReference field) {
String realCls = getRealFieldOwner(field.getClassName(), field.getFieldName()); String alias = fieldAliases.get(field);
if (!realCls.equals(field.getClassName())) { if (alias == null) {
String alias = getNameFor(new FieldReference(realCls, field.getFieldName())); FieldReference realField = getRealField(field);
fieldAliases.put(field.getClassName() + "#" + field, alias); if (realField.equals(field)) {
return alias; alias = aliasProvider.getFieldAlias(realField);
} else { } else {
return fieldAliases.computeIfAbsent(realCls + "#" + field, key -> aliasProvider.getFieldAlias(field)); alias = getNameFor(realField);
}
fieldAliases.put(field, alias);
} }
return alias;
} }
@Override @Override
public String getFullNameFor(FieldReference field) { public String getFullNameFor(FieldReference field) {
String realCls = getRealFieldOwner(field.getClassName(), field.getFieldName()); String alias = staticFieldAliases.get(field);
if (!realCls.equals(field.getClassName())) { if (alias == null) {
String alias = getNameFor(new FieldReference(realCls, field.getFieldName())); FieldReference realField = getRealField(field);
staticFieldAliases.put(field.getClassName() + "#" + field, alias); if (realField.equals(field)) {
return alias; alias = aliasProvider.getStaticFieldAlias(realField);
} else { } else {
return staticFieldAliases.computeIfAbsent(realCls + "#" + field, alias = getNameFor(realField);
key -> aliasProvider.getStaticFieldAlias(field)); }
staticFieldAliases.put(field, alias);
} }
return alias;
} }
@Override @Override
@ -127,20 +132,19 @@ public class DefaultNamingStrategy implements NamingStrategy {
return null; return null;
} }
private String getRealFieldOwner(String cls, String field) { private FieldReference getRealField(FieldReference fieldRef) {
String initialCls = cls; String initialCls = fieldRef.getClassName();
while (!fieldExists(cls, field)) { String cls = fieldRef.getClassName();
ClassReader clsHolder = classSource.get(cls); while (cls != null) {
if (clsHolder == null || clsHolder.getParent() == null) { ClassReader clsReader = classSource.get(cls);
return initialCls; if (clsReader != null) {
FieldReader fieldReader = clsReader.getField(fieldRef.getFieldName());
if (fieldReader != null) {
return fieldReader.getReference();
}
} }
cls = clsHolder.getParent(); cls = clsReader.getParent();
} }
return cls; return fieldRef;
}
private boolean fieldExists(String cls, String field) {
ClassReader classHolder = classSource.get(cls);
return classHolder != null && classHolder.getField(field) != null;
} }
} }

View File

@ -20,6 +20,8 @@ import com.carrotsearch.hppc.ObjectByteMap;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.function.Predicate; import java.util.function.Predicate;
import org.teavm.model.ClassReader;
import org.teavm.model.ClassReaderSource;
import org.teavm.model.MethodReference; import org.teavm.model.MethodReference;
public final class AnnotationAwareCacheStatus implements CacheStatus { public final class AnnotationAwareCacheStatus implements CacheStatus {
@ -32,10 +34,13 @@ public final class AnnotationAwareCacheStatus implements CacheStatus {
private IncrementalDependencyProvider dependencyProvider; private IncrementalDependencyProvider dependencyProvider;
private List<Predicate<String>> synthesizedClasses = new ArrayList<>(); private List<Predicate<String>> synthesizedClasses = new ArrayList<>();
private ObjectByteMap<String> classStatusCache = new ObjectByteHashMap<>(); private ObjectByteMap<String> classStatusCache = new ObjectByteHashMap<>();
private ClassReaderSource classSource;
public AnnotationAwareCacheStatus(CacheStatus underlyingStatus, IncrementalDependencyProvider dependencyProvider) { public AnnotationAwareCacheStatus(CacheStatus underlyingStatus, IncrementalDependencyProvider dependencyProvider,
ClassReaderSource classSource) {
this.underlyingStatus = underlyingStatus; this.underlyingStatus = underlyingStatus;
this.dependencyProvider = dependencyProvider; this.dependencyProvider = dependencyProvider;
this.classSource = classSource;
} }
public void addSynthesizedClasses(Predicate<String> synthesizedClasses) { public void addSynthesizedClasses(Predicate<String> synthesizedClasses) {
@ -72,6 +77,18 @@ public final class AnnotationAwareCacheStatus implements CacheStatus {
return STALE; 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; return FRESH;
} }

View File

@ -25,7 +25,9 @@ import org.teavm.model.MethodReference;
public class InMemoryMethodNodeCache implements MethodNodeCache { public class InMemoryMethodNodeCache implements MethodNodeCache {
private Map<MethodReference, RegularItem> cache = new HashMap<>(); private Map<MethodReference, RegularItem> cache = new HashMap<>();
private Map<MethodReference, RegularItem> newItems = new HashMap<>();
private Map<MethodReference, AsyncItem> asyncCache = new HashMap<>(); private Map<MethodReference, AsyncItem> asyncCache = new HashMap<>();
private Map<MethodReference, AsyncItem> newAsyncItems = new HashMap<>();
@Override @Override
public RegularMethodNode get(MethodReference methodReference, CacheStatus cacheStatus) { public RegularMethodNode get(MethodReference methodReference, CacheStatus cacheStatus) {
@ -43,7 +45,7 @@ public class InMemoryMethodNodeCache implements MethodNodeCache {
@Override @Override
public void store(MethodReference methodReference, RegularMethodNode node, Supplier<String[]> dependencies) { public void store(MethodReference methodReference, RegularMethodNode node, Supplier<String[]> dependencies) {
cache.put(methodReference, new RegularItem(node, dependencies.get().clone())); newItems.put(methodReference, new RegularItem(node, dependencies.get().clone()));
} }
@Override @Override
@ -62,7 +64,19 @@ public class InMemoryMethodNodeCache implements MethodNodeCache {
@Override @Override
public void storeAsync(MethodReference methodReference, AsyncMethodNode node, Supplier<String[]> dependencies) { public void storeAsync(MethodReference methodReference, AsyncMethodNode node, Supplier<String[]> 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 { static final class RegularItem {

View File

@ -25,6 +25,7 @@ import org.teavm.model.ProgramCache;
public class InMemoryProgramCache implements ProgramCache { public class InMemoryProgramCache implements ProgramCache {
private Map<MethodReference, Item> cache = new HashMap<>(); private Map<MethodReference, Item> cache = new HashMap<>();
private Map<MethodReference, Item> newItems = new HashMap<>();
@Override @Override
public Program get(MethodReference method, CacheStatus cacheStatus) { public Program get(MethodReference method, CacheStatus cacheStatus) {
@ -42,7 +43,20 @@ public class InMemoryProgramCache implements ProgramCache {
@Override @Override
public void store(MethodReference method, Program program, Supplier<String[]> dependencies) { public void store(MethodReference method, Program program, Supplier<String[]> 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 { static final class Item {

View File

@ -88,7 +88,6 @@ public class DependencyAgent implements DependencyInfo, ServiceRepository {
return analyzer.getClassLoader(); return analyzer.getClassLoader();
} }
@Override
public ClassHierarchy getClassHierarchy() { public ClassHierarchy getClassHierarchy() {
return analyzer.getClassHierarchy(); return analyzer.getClassHierarchy();
} }

View File

@ -214,7 +214,6 @@ public abstract class DependencyAnalyzer implements DependencyInfo {
return classSource.isGeneratedClass(className); return classSource.isGeneratedClass(className);
} }
@Override
public ClassHierarchy getClassHierarchy() { public ClassHierarchy getClassHierarchy() {
return classHierarchy; return classHierarchy;
} }

View File

@ -17,7 +17,6 @@ package org.teavm.dependency;
import java.util.Collection; import java.util.Collection;
import org.teavm.callgraph.CallGraph; import org.teavm.callgraph.CallGraph;
import org.teavm.model.ClassHierarchy;
import org.teavm.model.ClassReaderSource; import org.teavm.model.ClassReaderSource;
import org.teavm.model.FieldReference; import org.teavm.model.FieldReference;
import org.teavm.model.MethodReference; import org.teavm.model.MethodReference;
@ -27,8 +26,6 @@ public interface DependencyInfo {
ClassLoader getClassLoader(); ClassLoader getClassLoader();
ClassHierarchy getClassHierarchy();
Collection<MethodReference> getReachableMethods(); Collection<MethodReference> getReachableMethods();
Collection<FieldReference> getReachableFields(); Collection<FieldReference> getReachableFields();

View File

@ -22,7 +22,7 @@ import org.teavm.model.VariableReader;
class FastInstructionAnalyzer extends AbstractInstructionAnalyzer { class FastInstructionAnalyzer extends AbstractInstructionAnalyzer {
private FastDependencyAnalyzer dependencyAnalyzer; private FastDependencyAnalyzer dependencyAnalyzer;
private MethodReference callerMethod; private CallLocation impreciseLocation;
FastInstructionAnalyzer(FastDependencyAnalyzer dependencyAnalyzer) { FastInstructionAnalyzer(FastDependencyAnalyzer dependencyAnalyzer) {
this.dependencyAnalyzer = dependencyAnalyzer; this.dependencyAnalyzer = dependencyAnalyzer;
@ -31,7 +31,7 @@ class FastInstructionAnalyzer extends AbstractInstructionAnalyzer {
@Override @Override
protected void invokeSpecial(VariableReader receiver, VariableReader instance, MethodReference method, protected void invokeSpecial(VariableReader receiver, VariableReader instance, MethodReference method,
List<? extends VariableReader> arguments) { List<? extends VariableReader> arguments) {
CallLocation callLocation = getCallLocation(); CallLocation callLocation = impreciseLocation;
if (instance == null) { if (instance == null) {
dependencyAnalyzer.linkClass(method.getClassName()).initClass(callLocation); dependencyAnalyzer.linkClass(method.getClassName()).initClass(callLocation);
} }
@ -43,19 +43,14 @@ class FastInstructionAnalyzer extends AbstractInstructionAnalyzer {
@Override @Override
protected void invokeVirtual(VariableReader receiver, VariableReader instance, MethodReference method, protected void invokeVirtual(VariableReader receiver, VariableReader instance, MethodReference method,
List<? extends VariableReader> arguments) { List<? extends VariableReader> arguments) {
dependencyAnalyzer.getVirtualCallConsumer(method).addLocation(getCallLocation()); dependencyAnalyzer.getVirtualCallConsumer(method).addLocation(impreciseLocation);
dependencyAnalyzer.getClassSource().overriddenMethods(method).forEach(methodImpl -> {
dependencyAnalyzer.linkMethod(methodImpl.getReference()).addLocation(getCallLocation());
});
} }
@Override @Override
public void cloneArray(VariableReader receiver, VariableReader array) { public void cloneArray(VariableReader receiver, VariableReader array) {
DependencyNode arrayNode = getNode(array); DependencyNode arrayNode = getNode(array);
MethodDependency cloneDep = getAnalyzer().linkMethod(CLONE_METHOD); MethodDependency cloneDep = getAnalyzer().linkMethod(CLONE_METHOD);
cloneDep.addLocation(getCallLocation()); cloneDep.addLocation(impreciseLocation);
cloneDep.use(); cloneDep.use();
} }
@ -68,4 +63,15 @@ class FastInstructionAnalyzer extends AbstractInstructionAnalyzer {
protected DependencyAnalyzer getAnalyzer() { protected DependencyAnalyzer getAnalyzer() {
return dependencyAnalyzer; return dependencyAnalyzer;
} }
@Override
protected CallLocation getCallLocation() {
return impreciseLocation;
}
@Override
public void setCaller(MethodReference caller) {
super.setCaller(caller);
impreciseLocation = new CallLocation(caller);
}
} }

View File

@ -28,7 +28,7 @@ class FastVirtualCallConsumer implements DependencyConsumer {
private final MethodDescriptor methodDesc; private final MethodDescriptor methodDesc;
private final DependencyAnalyzer analyzer; private final DependencyAnalyzer analyzer;
private final Map<MethodReference, CallLocation> callLocations = new LinkedHashMap<>(); private final Map<MethodReference, CallLocation> callLocations = new LinkedHashMap<>();
private final Set<MethodDependency> methods = new LinkedHashSet<>(); private final Set<MethodDependency> methods = new LinkedHashSet<>(100, 0.5f);
FastVirtualCallConsumer(DependencyNode node, MethodDescriptor methodDesc, DependencyAnalyzer analyzer) { FastVirtualCallConsumer(DependencyNode node, MethodDescriptor methodDesc, DependencyAnalyzer analyzer) {
this.node = node; this.node = node;

View File

@ -19,11 +19,14 @@ import com.carrotsearch.hppc.ObjectByteHashMap;
import com.carrotsearch.hppc.ObjectByteMap; import com.carrotsearch.hppc.ObjectByteMap;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import org.teavm.common.OptionalPredicate; import org.teavm.common.OptionalPredicate;
public class ClassHierarchy { public class ClassHierarchy {
private final ClassReaderSource classSource; private final ClassReaderSource classSource;
private final Map<String, OptionalPredicate<String>> superclassPredicateCache = new HashMap<>(); private final Map<String, OptionalPredicate<String>> superclassPredicateCache = new HashMap<>();
private final Map<String, Map<MethodDescriptor, Optional<MethodReader>>> resolveMethodCache = new HashMap<>();
private final Map<String, Map<String, Optional<FieldReader>>> resolveFieldCache = new HashMap<>();
public ClassHierarchy(ClassReaderSource classSource) { public ClassHierarchy(ClassReaderSource classSource) {
this.classSource = classSource; this.classSource = classSource;
@ -62,6 +65,67 @@ public class ClassHierarchy {
return getSuperclassPredicate(superType).test(subType, defaultValue); 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<MethodDescriptor, Optional<MethodReader>> cache = resolveMethodCache.computeIfAbsent(className,
k -> new HashMap<>());
Optional<MethodReader> 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<String, Optional<FieldReader>> cache = resolveFieldCache.computeIfAbsent(className,
k -> new HashMap<>());
Optional<FieldReader> 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<String> getSuperclassPredicate(String superclass) { public OptionalPredicate<String> getSuperclassPredicate(String superclass) {
return superclassPredicateCache.computeIfAbsent(superclass, SuperclassPredicate::new); return superclassPredicateCache.computeIfAbsent(superclass, SuperclassPredicate::new);
} }

View File

@ -67,6 +67,7 @@ import org.teavm.model.instructions.UnwrapArrayInstruction;
public class ClassInference { public class ClassInference {
private DependencyInfo dependencyInfo; private DependencyInfo dependencyInfo;
private ClassHierarchy hierarchy;
private Graph assignmentGraph; private Graph assignmentGraph;
private Graph cloneGraph; private Graph cloneGraph;
private Graph arrayGraph; private Graph arrayGraph;
@ -89,8 +90,9 @@ public class ClassInference {
private static final int MAX_DEGREE = 3; private static final int MAX_DEGREE = 3;
public ClassInference(DependencyInfo dependencyInfo) { public ClassInference(DependencyInfo dependencyInfo, ClassHierarchy hierarchy) {
this.dependencyInfo = dependencyInfo; this.dependencyInfo = dependencyInfo;
this.hierarchy = hierarchy;
} }
public void infer(Program program, MethodReference methodReference) { public void infer(Program program, MethodReference methodReference) {
@ -403,8 +405,6 @@ public class ClassInference {
} }
private void propagateAlongCasts() { private void propagateAlongCasts() {
ClassHierarchy hierarchy = dependencyInfo.getClassHierarchy();
for (ValueCast cast : casts) { for (ValueCast cast : casts) {
int fromNode = nodeMapping[packNodeAndDegree(cast.fromVariable, 0)]; int fromNode = nodeMapping[packNodeAndDegree(cast.fromVariable, 0)];
if (!formerNodeChanged[fromNode] && !nodeChanged[fromNode]) { if (!formerNodeChanged[fromNode] && !nodeChanged[fromNode]) {
@ -500,8 +500,6 @@ public class ClassInference {
} }
private void propagateException(String thrownTypeName, BasicBlock block) { private void propagateException(String thrownTypeName, BasicBlock block) {
ClassHierarchy hierarchy = dependencyInfo.getClassHierarchy();
for (TryCatchBlock tryCatch : block.getTryCatchBlocks()) { for (TryCatchBlock tryCatch : block.getTryCatchBlocks()) {
String expectedType = tryCatch.getExceptionType(); String expectedType = tryCatch.getExceptionType();
if (expectedType == null || hierarchy.isSuperType(expectedType, thrownTypeName, false)) { if (expectedType == null || hierarchy.isSuperType(expectedType, thrownTypeName, false)) {

View File

@ -175,7 +175,7 @@ public final class ProgramEmitter {
} }
public ValueEmitter getField(FieldReference field, ValueType type) { public ValueEmitter getField(FieldReference field, ValueType type) {
FieldReader resolvedField = classSource.resolve(field); FieldReader resolvedField = hierarchy.resolve(field);
if (resolvedField != null) { if (resolvedField != null) {
field = resolvedField.getReference(); field = resolvedField.getReference();
} }
@ -198,7 +198,7 @@ public final class ProgramEmitter {
} }
public ProgramEmitter setField(FieldReference field, ValueEmitter value) { public ProgramEmitter setField(FieldReference field, ValueEmitter value) {
FieldReader resolvedField = classSource.resolve(field); FieldReader resolvedField = hierarchy.resolve(field);
if (resolvedField != null) { if (resolvedField != null) {
field = resolvedField.getReference(); field = resolvedField.getReference();
} }

View File

@ -25,6 +25,7 @@ import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.teavm.dependency.DependencyInfo; import org.teavm.dependency.DependencyInfo;
import org.teavm.model.BasicBlock; import org.teavm.model.BasicBlock;
import org.teavm.model.ClassHierarchy;
import org.teavm.model.ClassReader; import org.teavm.model.ClassReader;
import org.teavm.model.ClassReaderSource; import org.teavm.model.ClassReaderSource;
import org.teavm.model.Incoming; import org.teavm.model.Incoming;
@ -55,16 +56,24 @@ public class Inlining {
private static final int MAX_DEPTH = 7; private static final int MAX_DEPTH = 7;
private IntArrayList depthsByBlock; private IntArrayList depthsByBlock;
private Set<Instruction> instructionsToSkip; private Set<Instruction> instructionsToSkip;
private ClassHierarchy hierarchy;
private ClassReaderSource classes;
private DependencyInfo dependencyInfo;
public void apply(Program program, MethodReference method, ClassReaderSource classes, public Inlining(ClassHierarchy hierarchy, DependencyInfo dependencyInfo) {
DependencyInfo dependencyInfo) { this.hierarchy = hierarchy;
this.classes = hierarchy.getClassSource();
this.dependencyInfo = dependencyInfo;
}
public void apply(Program program, MethodReference method) {
depthsByBlock = new IntArrayList(program.basicBlockCount()); depthsByBlock = new IntArrayList(program.basicBlockCount());
for (int i = 0; i < program.basicBlockCount(); ++i) { for (int i = 0; i < program.basicBlockCount(); ++i) {
depthsByBlock.add(0); depthsByBlock.add(0);
} }
instructionsToSkip = new HashSet<>(); instructionsToSkip = new HashSet<>();
while (applyOnce(program, classes)) { while (applyOnce(program)) {
devirtualize(program, method, dependencyInfo); devirtualize(program, method, dependencyInfo);
} }
depthsByBlock = null; depthsByBlock = null;
@ -73,8 +82,8 @@ public class Inlining {
new UnreachableBasicBlockEliminator().optimize(program); new UnreachableBasicBlockEliminator().optimize(program);
} }
private boolean applyOnce(Program program, ClassReaderSource classSource) { private boolean applyOnce(Program program) {
List<PlanEntry> plan = buildPlan(program, classSource, 0); List<PlanEntry> plan = buildPlan(program, 0);
if (plan.isEmpty()) { if (plan.isEmpty()) {
return false; return false;
} }
@ -210,7 +219,7 @@ public class Inlining {
execPlan(program, planEntry.innerPlan, firstInlineBlock.getIndex()); execPlan(program, planEntry.innerPlan, firstInlineBlock.getIndex());
} }
private List<PlanEntry> buildPlan(Program program, ClassReaderSource classSource, int depth) { private List<PlanEntry> buildPlan(Program program, int depth) {
if (depth >= MAX_DEPTH) { if (depth >= MAX_DEPTH) {
return Collections.emptyList(); return Collections.emptyList();
} }
@ -243,7 +252,7 @@ public class Inlining {
continue; continue;
} }
MethodReader invokedMethod = getMethod(classSource, invoke.getMethod()); MethodReader invokedMethod = getMethod(invoke.getMethod());
if (invokedMethod == null || invokedMethod.getProgram() == null if (invokedMethod == null || invokedMethod.getProgram() == null
|| invokedMethod.getProgram().basicBlockCount() == 0) { || invokedMethod.getProgram().basicBlockCount() == 0) {
instructionsToSkip.add(insn); instructionsToSkip.add(insn);
@ -264,7 +273,7 @@ public class Inlining {
entry.targetBlock = block.getIndex(); entry.targetBlock = block.getIndex();
entry.targetInstruction = insn; entry.targetInstruction = insn;
entry.program = invokedProgram; entry.program = invokedProgram;
entry.innerPlan.addAll(buildPlan(invokedProgram, classSource, depth + 1)); entry.innerPlan.addAll(buildPlan(invokedProgram, depth + 1));
entry.depth = depth; entry.depth = depth;
plan.add(entry); plan.add(entry);
} }
@ -274,8 +283,8 @@ public class Inlining {
return plan; return plan;
} }
private MethodReader getMethod(ClassReaderSource classSource, MethodReference methodRef) { private MethodReader getMethod(MethodReference methodRef) {
ClassReader cls = classSource.get(methodRef.getClassName()); ClassReader cls = classes.get(methodRef.getClassName());
return cls != null ? cls.getMethod(methodRef.getDescriptor()) : null; return cls != null ? cls.getMethod(methodRef.getDescriptor()) : null;
} }
@ -308,7 +317,7 @@ public class Inlining {
} }
private void devirtualize(Program program, MethodReference method, DependencyInfo dependencyInfo) { private void devirtualize(Program program, MethodReference method, DependencyInfo dependencyInfo) {
ClassInference inference = new ClassInference(dependencyInfo); ClassInference inference = new ClassInference(dependencyInfo, hierarchy);
inference.infer(program, method); inference.infer(program, method);
for (BasicBlock block : program.getBasicBlocks()) { for (BasicBlock block : program.getBasicBlocks()) {

View File

@ -46,6 +46,7 @@ public class AsyncMethodFinder {
private Map<MethodReference, Boolean> asyncFamilyMethods = new HashMap<>(); private Map<MethodReference, Boolean> asyncFamilyMethods = new HashMap<>();
private Set<MethodReference> readonlyAsyncMethods = Collections.unmodifiableSet(asyncMethods); private Set<MethodReference> readonlyAsyncMethods = Collections.unmodifiableSet(asyncMethods);
private Set<MethodReference> readonlyAsyncFamilyMethods = Collections.unmodifiableSet(asyncFamilyMethods.keySet()); private Set<MethodReference> readonlyAsyncFamilyMethods = Collections.unmodifiableSet(asyncFamilyMethods.keySet());
private Map<MethodReference, Set<MethodReference>> overiddenMethodsCache = new HashMap<>();
private CallGraph callGraph; private CallGraph callGraph;
private Diagnostics diagnostics; private Diagnostics diagnostics;
private ListableClassReaderSource classSource; private ListableClassReaderSource classSource;
@ -209,7 +210,7 @@ public class AsyncMethodFinder {
if (cls == null) { if (cls == null) {
return; return;
} }
for (MethodReference overriddenMethod : findOverriddenMethods(cls, methodRef)) { for (MethodReference overriddenMethod : findOverriddenMethods(cls, methodRef.getDescriptor())) {
addOverriddenToFamily(overriddenMethod); addOverriddenToFamily(overriddenMethod);
} }
} }
@ -232,7 +233,7 @@ public class AsyncMethodFinder {
if (cls == null) { if (cls == null) {
return false; return false;
} }
for (MethodReference overriddenMethod : findOverriddenMethods(cls, methodRef)) { for (MethodReference overriddenMethod : findOverriddenMethods(cls, methodRef.getDescriptor())) {
if (addToFamily(overriddenMethod)) { if (addToFamily(overriddenMethod)) {
return true; return true;
} }
@ -240,44 +241,44 @@ public class AsyncMethodFinder {
return false; return false;
} }
private Set<MethodReference> findOverriddenMethods(ClassReader cls, MethodReference methodRef) { private Set<MethodReference> findOverriddenMethods(ClassReader cls, MethodDescriptor methodDesc) {
List<String> parents = new ArrayList<>(); List<String> parents = new ArrayList<>();
if (cls.getParent() != null) { if (cls.getParent() != null) {
parents.add(cls.getParent()); parents.add(cls.getParent());
} }
parents.addAll(cls.getInterfaces()); parents.addAll(cls.getInterfaces());
Set<MethodReference> visited = new HashSet<>(); Set<String> visited = new HashSet<>();
Set<MethodReference> overridden = new HashSet<>(); Set<MethodReference> overridden = new HashSet<>();
for (String parent : parents) { for (String parent : parents) {
findOverriddenMethods(new MethodReference(parent, methodRef.getDescriptor()), overridden, visited); findOverriddenMethods(parent, methodDesc, overridden, visited);
} }
return overridden; return overridden;
} }
private void findOverriddenMethods(MethodReference methodRef, Set<MethodReference> result, private void findOverriddenMethods(String className, MethodDescriptor methodDesc, Set<MethodReference> result,
Set<MethodReference> visited) { Set<String> visited) {
if (!visited.add(methodRef)) { if (!visited.add(className)) {
return; return;
} }
if (methodRef.getName().equals("<init>") || methodRef.getName().equals("<clinit>")) { if (methodDesc.getName().equals("<init>") || methodDesc.getName().equals("<clinit>")) {
return; return;
} }
ClassReader cls = classSource.get(methodRef.getClassName()); ClassReader cls = classSource.get(className);
if (cls == null) { if (cls == null) {
return; return;
} }
MethodReader method = cls.getMethod(methodRef.getDescriptor()); MethodReader method = cls.getMethod(methodDesc);
if (method != null) { if (method != null) {
if (!method.hasModifier(ElementModifier.STATIC) && !method.hasModifier(ElementModifier.FINAL)) { if (!method.hasModifier(ElementModifier.STATIC) && !method.hasModifier(ElementModifier.FINAL)) {
result.add(methodRef); result.add(method.getReference());
} }
} else { } else {
if (cls.getParent() != null) { if (cls.getParent() != null) {
findOverriddenMethods(new MethodReference(cls.getParent(), methodRef.getDescriptor()), result, visited); findOverriddenMethods(cls.getParent(), methodDesc, result, visited);
} }
for (String iface : cls.getInterfaces()) { for (String iface : cls.getInterfaces()) {
findOverriddenMethods(new MethodReference(iface, methodRef.getDescriptor()), result, visited); findOverriddenMethods(iface, methodDesc, result, visited);
} }
} }
} }

View File

@ -27,6 +27,7 @@ import org.teavm.model.optimization.UnreachableBasicBlockEliminator;
public class MissingItemsProcessor { public class MissingItemsProcessor {
private DependencyInfo dependencyInfo; private DependencyInfo dependencyInfo;
private ClassHierarchy hierarchy;
private Diagnostics diagnostics; private Diagnostics diagnostics;
private List<Instruction> instructionsToAdd = new ArrayList<>(); private List<Instruction> instructionsToAdd = new ArrayList<>();
private MethodHolder methodHolder; private MethodHolder methodHolder;
@ -35,9 +36,10 @@ public class MissingItemsProcessor {
private Collection<MethodReference> reachableMethods; private Collection<MethodReference> reachableMethods;
private Collection<FieldReference> reachableFields; private Collection<FieldReference> reachableFields;
public MissingItemsProcessor(DependencyInfo dependencyInfo, Diagnostics diagnostics) { public MissingItemsProcessor(DependencyInfo dependencyInfo, ClassHierarchy hierarchy, Diagnostics diagnostics) {
this.dependencyInfo = dependencyInfo; this.dependencyInfo = dependencyInfo;
this.diagnostics = diagnostics; this.diagnostics = diagnostics;
this.hierarchy = hierarchy;
reachableClasses = dependencyInfo.getReachableClasses(); reachableClasses = dependencyInfo.getReachableClasses();
reachableMethods = dependencyInfo.getReachableMethods(); reachableMethods = dependencyInfo.getReachableMethods();
reachableFields = dependencyInfo.getReachableFields(); reachableFields = dependencyInfo.getReachableFields();
@ -171,7 +173,7 @@ public class MissingItemsProcessor {
return true; return true;
} }
if (dependencyInfo.getClassSource().resolve(method) != null) { if (hierarchy.resolve(method) != null) {
return true; return true;
} }

View File

@ -47,6 +47,7 @@ import org.teavm.dependency.MethodDependency;
import org.teavm.diagnostics.AccumulationDiagnostics; import org.teavm.diagnostics.AccumulationDiagnostics;
import org.teavm.diagnostics.Diagnostics; import org.teavm.diagnostics.Diagnostics;
import org.teavm.diagnostics.ProblemProvider; import org.teavm.diagnostics.ProblemProvider;
import org.teavm.model.ClassHierarchy;
import org.teavm.model.ClassHolder; import org.teavm.model.ClassHolder;
import org.teavm.model.ClassHolderSource; import org.teavm.model.ClassHolderSource;
import org.teavm.model.ClassHolderTransformer; import org.teavm.model.ClassHolderTransformer;
@ -372,7 +373,8 @@ public class TeaVM implements TeaVMHost, ServiceRepository {
return; return;
} }
cacheStatus = new AnnotationAwareCacheStatus(rawCacheStatus, dependencyAnalyzer.getIncrementalDependencies()); cacheStatus = new AnnotationAwareCacheStatus(rawCacheStatus, dependencyAnalyzer.getIncrementalDependencies(),
dependencyAnalyzer.getClassSource());
cacheStatus.addSynthesizedClasses(dependencyAnalyzer::isSynthesizedClass); cacheStatus.addSynthesizedClasses(dependencyAnalyzer::isSynthesizedClass);
// Link // Link
@ -423,11 +425,12 @@ public class TeaVM implements TeaVMHost, ServiceRepository {
} }
@SuppressWarnings("WeakerAccess") @SuppressWarnings("WeakerAccess")
public ListableClassHolderSource link(DependencyInfo dependency) { public ListableClassHolderSource link(DependencyAnalyzer dependency) {
reportPhase(TeaVMPhase.LINKING, dependency.getReachableClasses().size()); reportPhase(TeaVMPhase.LINKING, dependency.getReachableClasses().size());
Linker linker = new Linker(); Linker linker = new Linker();
MutableClassHolderSource cutClasses = new MutableClassHolderSource(); MutableClassHolderSource cutClasses = new MutableClassHolderSource();
MissingItemsProcessor missingItemsProcessor = new MissingItemsProcessor(dependency, diagnostics); MissingItemsProcessor missingItemsProcessor = new MissingItemsProcessor(dependency,
dependency.getClassHierarchy(), diagnostics);
if (wasCancelled()) { if (wasCancelled()) {
return cutClasses; return cutClasses;
} }
@ -446,7 +449,10 @@ public class TeaVM implements TeaVMHost, ServiceRepository {
cutClasses.putClassHolder(cls); cutClasses.putClassHolder(cls);
missingItemsProcessor.processClass(cls); missingItemsProcessor.processClass(cls);
linker.link(dependency, cls); linker.link(dependency, cls);
progressListener.progressReached(++index); reportProgress(++index);
if (wasCancelled()) {
break;
}
} }
return cutClasses; 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) { private int devirtualize(int progress, ListableClassHolderSource classes, DependencyInfo dependency) {
if (wasCancelled()) { if (wasCancelled()) {
return progress; return progress;
@ -470,7 +482,7 @@ public class TeaVM implements TeaVMHost, ServiceRepository {
devirtualization.apply(method); devirtualization.apply(method);
} }
} }
progressListener.progressReached(++index); reportProgress(++index);
if (wasCancelled()) { if (wasCancelled()) {
break; break;
} }
@ -485,19 +497,19 @@ public class TeaVM implements TeaVMHost, ServiceRepository {
} }
Map<MethodReference, Program> inlinedPrograms = new HashMap<>(); Map<MethodReference, Program> inlinedPrograms = new HashMap<>();
Inlining inlining = new Inlining(); Inlining inlining = new Inlining(new ClassHierarchy(classes), dependencyInfo);
for (String className : classes.getClassNames()) { for (String className : classes.getClassNames()) {
ClassHolder cls = classes.get(className); ClassHolder cls = classes.get(className);
for (MethodHolder method : cls.getMethods()) { for (MethodHolder method : cls.getMethods()) {
if (method.getProgram() != null) { if (method.getProgram() != null) {
Program program = ProgramUtils.copy(method.getProgram()); Program program = ProgramUtils.copy(method.getProgram());
MethodOptimizationContextImpl context = new MethodOptimizationContextImpl(method, classes); MethodOptimizationContextImpl context = new MethodOptimizationContextImpl(method, classes);
inlining.apply(program, method.getReference(), classes, dependencyInfo); inlining.apply(program, method.getReference());
new UnusedVariableElimination().optimize(context, program); new UnusedVariableElimination().optimize(context, program);
inlinedPrograms.put(method.getReference(), program); inlinedPrograms.put(method.getReference(), program);
} }
} }
progressListener.progressReached(++progress); reportProgress(++progress);
if (wasCancelled()) { if (wasCancelled()) {
break; break;
} }
@ -521,7 +533,7 @@ public class TeaVM implements TeaVMHost, ServiceRepository {
for (MethodHolder method : cls.getMethods()) { for (MethodHolder method : cls.getMethods()) {
processMethod(method, classSource); processMethod(method, classSource);
} }
progressListener.progressReached(++progress); reportProgress(++progress);
if (wasCancelled()) { if (wasCancelled()) {
break; break;
} }

View File

@ -46,6 +46,7 @@ public class MetaprogrammingDependencyListener extends AbstractDependencyListene
MetaprogrammingImpl.classLoader = proxyClassLoader; MetaprogrammingImpl.classLoader = proxyClassLoader;
MetaprogrammingImpl.classSource = agent.getClassSource(); MetaprogrammingImpl.classSource = agent.getClassSource();
MetaprogrammingImpl.hierarchy = agent.getClassHierarchy();
MetaprogrammingImpl.incrementaDependencies = agent.getIncrementalCache(); MetaprogrammingImpl.incrementaDependencies = agent.getIncrementalCache();
MetaprogrammingImpl.agent = agent; MetaprogrammingImpl.agent = agent;
MetaprogrammingImpl.reflectContext = new ReflectContext(agent.getClassHierarchy(), proxyClassLoader); MetaprogrammingImpl.reflectContext = new ReflectContext(agent.getClassHierarchy(), proxyClassLoader);

View File

@ -36,6 +36,7 @@ import org.teavm.metaprogramming.reflect.ReflectMethod;
import org.teavm.model.AccessLevel; import org.teavm.model.AccessLevel;
import org.teavm.model.BasicBlock; import org.teavm.model.BasicBlock;
import org.teavm.model.CallLocation; import org.teavm.model.CallLocation;
import org.teavm.model.ClassHierarchy;
import org.teavm.model.ClassHolder; import org.teavm.model.ClassHolder;
import org.teavm.model.ClassReaderSource; import org.teavm.model.ClassReaderSource;
import org.teavm.model.ElementModifier; import org.teavm.model.ElementModifier;
@ -63,6 +64,7 @@ public final class MetaprogrammingImpl {
static Map<String, Integer> proxySuffixGenerators = new HashMap<>(); static Map<String, Integer> proxySuffixGenerators = new HashMap<>();
static ClassLoader classLoader; static ClassLoader classLoader;
static ClassReaderSource classSource; static ClassReaderSource classSource;
static ClassHierarchy hierarchy;
static IncrementalDependencyRegistration incrementaDependencies; static IncrementalDependencyRegistration incrementaDependencies;
static ReflectContext reflectContext; static ReflectContext reflectContext;
static DependencyAgent agent; static DependencyAgent agent;
@ -87,7 +89,7 @@ public final class MetaprogrammingImpl {
return var != null ? new ValueImpl<>(var, varContext, valueImpl.type) : null; return var != null ? new ValueImpl<>(var, varContext, valueImpl.type) : null;
} else { } else {
Fragment fragment = (Fragment) computation; Fragment fragment = (Fragment) computation;
MethodReader method = classSource.resolve(fragment.method); MethodReader method = hierarchy.resolve(fragment.method);
generator.addProgram(method.getProgram(), fragment.capturedValues); generator.addProgram(method.getProgram(), fragment.capturedValues);
return new ValueImpl<>(generator.getResultVar(), varContext, fragment.method.getReturnType()); return new ValueImpl<>(generator.getResultVar(), varContext, fragment.method.getReturnType());
} }
@ -95,7 +97,7 @@ public final class MetaprogrammingImpl {
public static void emit(Action action) { public static void emit(Action action) {
Fragment fragment = (Fragment) action; Fragment fragment = (Fragment) action;
MethodReader method = classSource.resolve(fragment.method); MethodReader method = hierarchy.resolve(fragment.method);
generator.addProgram(method.getProgram(), fragment.capturedValues); generator.addProgram(method.getProgram(), fragment.capturedValues);
} }
@ -121,7 +123,7 @@ public final class MetaprogrammingImpl {
if (value instanceof Fragment) { if (value instanceof Fragment) {
Fragment fragment = (Fragment) value; Fragment fragment = (Fragment) value;
MethodReader method = classSource.resolve(fragment.method); MethodReader method = hierarchy.resolve(fragment.method);
generator.addProgram(method.getProgram(), fragment.capturedValues); generator.addProgram(method.getProgram(), fragment.capturedValues);
generator.blockIndex = generator.returnBlockIndex; generator.blockIndex = generator.returnBlockIndex;

View File

@ -33,6 +33,7 @@ import org.teavm.backend.javascript.JavaScriptTarget;
import org.teavm.common.DisjointSet; import org.teavm.common.DisjointSet;
import org.teavm.diagnostics.Problem; import org.teavm.diagnostics.Problem;
import org.teavm.model.BasicBlock; import org.teavm.model.BasicBlock;
import org.teavm.model.ClassHierarchy;
import org.teavm.model.ClassHolderSource; import org.teavm.model.ClassHolderSource;
import org.teavm.model.Instruction; import org.teavm.model.Instruction;
import org.teavm.model.MethodHolder; import org.teavm.model.MethodHolder;
@ -145,7 +146,8 @@ public class DependencyTest {
private void processAssertions(List<Assertion> assertions, MethodDependencyInfo methodDep, private void processAssertions(List<Assertion> assertions, MethodDependencyInfo methodDep,
DependencyInfo dependencyInfo, Program program) { DependencyInfo dependencyInfo, Program program) {
ClassInference classInference = new ClassInference(dependencyInfo); ClassInference classInference = new ClassInference(dependencyInfo, new ClassHierarchy(
dependencyInfo.getClassSource()));
classInference.infer(program, methodDep.getReference()); classInference.infer(program, methodDep.getReference());
for (Assertion assertion : assertions) { for (Assertion assertion : assertions) {

View File

@ -213,6 +213,8 @@ public class IncrementalTest {
if (!problems.isEmpty()) { if (!problems.isEmpty()) {
fail("Compiler error generating file '" + name + "'\n" + buildErrorMessage(vm)); fail("Compiler error generating file '" + name + "'\n" + buildErrorMessage(vm));
} }
astCache.commit();
programCache.commit();
} }
private String buildErrorMessage(TeaVM vm) { private String buildErrorMessage(TeaVM vm) {

View File

@ -114,6 +114,7 @@ public final class TeaVMDevServerRunner {
devServer.setIndicator(commandLine.hasOption("indicator")); devServer.setIndicator(commandLine.hasOption("indicator"));
devServer.setReloadedAutomatically(commandLine.hasOption("auto-reload")); devServer.setReloadedAutomatically(commandLine.hasOption("auto-reload"));
devServer.setVerbose(commandLine.hasOption('v'));
if (commandLine.hasOption("port")) { if (commandLine.hasOption("port")) {
try { try {
devServer.setPort(Integer.parseInt(commandLine.getOptionValue("port"))); devServer.setPort(Integer.parseInt(commandLine.getOptionValue("port")));

View File

@ -43,6 +43,7 @@ public class FileSystemWatcher {
private WatchService watchService; private WatchService watchService;
private Map<WatchKey, Path> keysToPath = new HashMap<>(); private Map<WatchKey, Path> keysToPath = new HashMap<>();
private Map<Path, WatchKey> pathsToKey = new HashMap<>(); private Map<Path, WatchKey> pathsToKey = new HashMap<>();
private Map<Path, Integer> refCount = new HashMap<>();
private Set<File> changedFiles = new LinkedHashSet<>(); private Set<File> changedFiles = new LinkedHashSet<>();
public FileSystemWatcher(String[] classPath) throws IOException { public FileSystemWatcher(String[] classPath) throws IOException {
@ -51,9 +52,7 @@ public class FileSystemWatcher {
Path path = Paths.get(entry); Path path = Paths.get(entry);
File file = path.toFile(); File file = path.toFile();
if (file.exists()) { if (file.exists()) {
if (!file.isDirectory()) { if (file.isDirectory()) {
registerSingle(path.getParent());
} else {
register(path); register(path);
} }
} }
@ -74,6 +73,13 @@ public class FileSystemWatcher {
.collect(Collectors.toList())); .collect(Collectors.toList()));
return FileVisitResult.CONTINUE; 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; return files;
} }
@ -83,6 +89,9 @@ public class FileSystemWatcher {
StandardWatchEventKinds.ENTRY_DELETE, StandardWatchEventKinds.ENTRY_MODIFY); StandardWatchEventKinds.ENTRY_DELETE, StandardWatchEventKinds.ENTRY_MODIFY);
keysToPath.put(key, path); keysToPath.put(key, path);
pathsToKey.put(path, key); 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 { public boolean hasChanges() throws IOException {
@ -159,9 +168,6 @@ public class FileSystemWatcher {
return Collections.emptyList(); return Collections.emptyList();
} }
Path basePath = keysToPath.get(baseKey); Path basePath = keysToPath.get(baseKey);
if (basePath == null) {
return Collections.emptyList();
}
Path path = basePath.resolve((Path) event.context()); Path path = basePath.resolve((Path) event.context());
WatchKey key = pathsToKey.get(path); WatchKey key = pathsToKey.get(path);
@ -170,9 +176,8 @@ public class FileSystemWatcher {
if (event.kind() == StandardWatchEventKinds.ENTRY_DELETE) { if (event.kind() == StandardWatchEventKinds.ENTRY_DELETE) {
if (key != null) { if (key != null) {
pathsToKey.remove(path);
keysToPath.remove(key);
key.cancel(); key.cancel();
releasePath(path);
} }
} else if (event.kind() == StandardWatchEventKinds.ENTRY_CREATE) { } else if (event.kind() == StandardWatchEventKinds.ENTRY_CREATE) {
if (Files.isDirectory(path)) { if (Files.isDirectory(path)) {
@ -181,4 +186,18 @@ public class FileSystemWatcher {
} }
return result; 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);
}
}
} }

View File

@ -32,8 +32,10 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.function.Supplier; import java.util.function.Supplier;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipFile; import java.util.zip.ZipFile;
@ -92,7 +94,10 @@ public class CodeServlet extends HttpServlet {
private final Map<String, byte[]> content = new HashMap<>(); private final Map<String, byte[]> content = new HashMap<>();
private MemoryBuildTarget buildTarget = new MemoryBuildTarget(); private MemoryBuildTarget buildTarget = new MemoryBuildTarget();
private CodeWsEndpoint wsEndpoint; private final Set<CodeWsEndpoint> wsEndpoints = new LinkedHashSet<>();
private final Object statusLock = new Object();
private boolean compiling;
private double progress;
public CodeServlet(String mainClass, String[] classPath) { public CodeServlet(String mainClass, String[] classPath) {
this.mainClass = mainClass; this.mainClass = mainClass;
@ -129,14 +134,32 @@ public class CodeServlet extends HttpServlet {
this.port = port; this.port = port;
} }
public void setWsEndpoint(CodeWsEndpoint wsEndpoint) {
this.wsEndpoint = wsEndpoint;
}
public void setAutomaticallyReloaded(boolean automaticallyReloaded) { public void setAutomaticallyReloaded(boolean automaticallyReloaded) {
this.automaticallyReloaded = 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 @Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
String path = req.getPathInfo(); String path = req.getPathInfo();
@ -289,8 +312,17 @@ public class CodeServlet extends HttpServlet {
break; break;
} }
log.info("Changes detected. Recompiling."); log.info("Changes detected. Recompiling.");
List<String> staleClasses = getChangedClasses(watcher.grabChangedFiles()); List<String> staleClasses = getChangedClasses(watcher.grabChangedFiles());
log.debug("Following classes changed: " + staleClasses); if (staleClasses.size() > 15) {
List<String> 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); classSource.evict(staleClasses);
} }
} catch (Throwable e) { } catch (Throwable e) {
@ -358,9 +390,7 @@ public class CodeServlet extends HttpServlet {
log.info("Starting build"); log.info("Starting build");
progressListener.last = 0; progressListener.last = 0;
progressListener.lastTime = System.currentTimeMillis(); progressListener.lastTime = System.currentTimeMillis();
if (wsEndpoint != null) { reportProgress(0);
wsEndpoint.progress(0);
}
vm.build(buildTarget, fileName); vm.build(buildTarget, fileName);
addIndicator(); addIndicator();
generateDebug(debugInformationBuilder); generateDebug(debugInformationBuilder);
@ -419,19 +449,18 @@ public class CodeServlet extends HttpServlet {
private void postBuild(TeaVM vm, long startTime) { private void postBuild(TeaVM vm, long startTime) {
if (!vm.wasCancelled()) { if (!vm.wasCancelled()) {
log.info("Recompiled stale methods: " + programCache.getPendingItemsCount());
if (vm.getProblemProvider().getSevereProblems().isEmpty()) { if (vm.getProblemProvider().getSevereProblems().isEmpty()) {
log.info("Build complete successfully"); log.info("Build complete successfully");
saveNewResult(); saveNewResult();
lastReachedClasses = vm.getDependencyInfo().getReachableClasses().size(); lastReachedClasses = vm.getDependencyInfo().getReachableClasses().size();
classSource.commit(); classSource.commit();
if (wsEndpoint != null) { programCache.commit();
wsEndpoint.complete(true); astCache.commit();
} reportCompilationComplete(true);
} else { } else {
log.info("Build complete with errors"); log.info("Build complete with errors");
if (wsEndpoint != null) { reportCompilationComplete(false);
wsEndpoint.complete(false);
}
} }
printStats(vm, startTime); printStats(vm, startTime);
TeaVMProblemRenderer.describeProblems(vm, log); TeaVMProblemRenderer.describeProblems(vm, log);
@ -439,6 +468,8 @@ public class CodeServlet extends HttpServlet {
log.info("Build cancelled"); log.info("Build cancelled");
} }
astCache.discard();
programCache.discard();
buildTarget.clear(); buildTarget.clear();
} }
@ -505,6 +536,43 @@ public class CodeServlet extends HttpServlet {
return new URLClassLoader(urls, CodeServlet.class.getClassLoader()); 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(); private final ProgressListenerImpl progressListener = new ProgressListenerImpl();
class ProgressListenerImpl implements TeaVMProgressListener { class ProgressListenerImpl implements TeaVMProgressListener {
@ -540,13 +608,13 @@ public class CodeServlet extends HttpServlet {
@Override @Override
public TeaVMProgressFeedback progressReached(int progress) { public TeaVMProgressFeedback progressReached(int progress) {
if (wsEndpoint != null && indicator) { if (indicator) {
int current = start + Math.min(progress, phaseLimit) * (end - start) / phaseLimit; int current = start + Math.min(progress, phaseLimit) * (end - start) / phaseLimit;
if (current != last) { if (current != last) {
if (current - last > 10 || System.currentTimeMillis() - lastTime > 100) { if (current - last > 10 || System.currentTimeMillis() - lastTime > 100) {
lastTime = System.currentTimeMillis(); lastTime = System.currentTimeMillis();
last = current; last = current;
wsEndpoint.progress(current / 10.0); reportProgress(current / 10.0);
} }
} }
} }

View File

@ -15,7 +15,7 @@
*/ */
package org.teavm.devserver; package org.teavm.devserver;
import java.util.function.Consumer; import javax.websocket.OnClose;
import javax.websocket.OnOpen; import javax.websocket.OnOpen;
import javax.websocket.Session; import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint; import javax.websocket.server.ServerEndpoint;
@ -23,38 +23,35 @@ import javax.websocket.server.ServerEndpoint;
@ServerEndpoint("/") @ServerEndpoint("/")
public class CodeWsEndpoint { public class CodeWsEndpoint {
private Session session; private Session session;
private boolean compiling; private CodeServlet servlet;
private double progress;
@OnOpen @OnOpen
public void open(Session session) { public void open(Session session) {
this.session = session; this.session = session;
@SuppressWarnings("unchecked") servlet = (CodeServlet) session.getUserProperties().get("teavm.servlet");
Consumer<CodeWsEndpoint> consumer = (Consumer<CodeWsEndpoint>) session.getUserProperties().get("ws.consumer"); if (servlet != null) {
if (consumer != null) { servlet.addWsEndpoint(this);
consumer.accept(this);
} }
if (compiling) { }
sendProgress(progress);
@OnClose
public void close() {
if (servlet != null) {
servlet.removeWsEndpoint(this);
} }
servlet = null;
session = null;
} }
public void progress(double value) { public void progress(double value) {
if (session != null) { if (session != null) {
sendProgress(value); session.getAsyncRemote().sendText("{ \"command\": \"compiling\", \"progress\": " + value + " }");
} }
compiling = true;
progress = value;
} }
public void complete(boolean success) { public void complete(boolean success) {
if (session != null) { if (session != null) {
session.getAsyncRemote().sendText("{ \"command\": \"complete\", \"success\": " + success + " }"); session.getAsyncRemote().sendText("{ \"command\": \"complete\", \"success\": " + success + " }");
} }
compiling = false;
}
private void sendProgress(double value) {
session.getAsyncRemote().sendText("{ \"command\": \"compiling\", \"progress\": " + value + " }");
} }
} }

View File

@ -20,7 +20,6 @@ import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.Consumer;
import javax.websocket.Decoder; import javax.websocket.Decoder;
import javax.websocket.Encoder; import javax.websocket.Encoder;
import javax.websocket.Extension; import javax.websocket.Extension;
@ -42,7 +41,8 @@ public class DevServer {
private List<String> sourcePath = new ArrayList<>(); private List<String> sourcePath = new ArrayList<>();
private boolean indicator; private boolean indicator;
private boolean reloadedAutomatically; private boolean reloadedAutomatically;
private TeaVMToolLog log = new ConsoleTeaVMToolLog(false); private boolean verbose;
private TeaVMToolLog log;
private Server server; private Server server;
private int port = 9090; private int port = 9090;
@ -85,11 +85,16 @@ public class DevServer {
this.reloadedAutomatically = reloadedAutomatically; this.reloadedAutomatically = reloadedAutomatically;
} }
public void setVerbose(boolean verbose) {
this.verbose = verbose;
}
public List<String> getSourcePath() { public List<String> getSourcePath() {
return sourcePath; return sourcePath;
} }
public void start() { public void start() {
log = new ConsoleTeaVMToolLog(verbose);
server = new Server(); server = new Server();
ServerConnector connector = new ServerConnector(server); ServerConnector connector = new ServerConnector(server);
connector.setPort(port); connector.setPort(port);
@ -110,7 +115,7 @@ public class DevServer {
try { try {
ServerContainer wscontainer = WebSocketServerContainerInitializer.configureContext(context); ServerContainer wscontainer = WebSocketServerContainerInitializer.configureContext(context);
wscontainer.addEndpoint(new DevServerEndpointConfig(servlet::setWsEndpoint)); wscontainer.addEndpoint(new DevServerEndpointConfig(servlet));
server.start(); server.start();
server.join(); server.join();
} catch (Exception e) { } catch (Exception e) {
@ -129,8 +134,8 @@ public class DevServer {
private class DevServerEndpointConfig implements ServerEndpointConfig { private class DevServerEndpointConfig implements ServerEndpointConfig {
private Map<String, Object> userProperties = new HashMap<>(); private Map<String, Object> userProperties = new HashMap<>();
public DevServerEndpointConfig(Consumer<CodeWsEndpoint> consumer) { public DevServerEndpointConfig(CodeServlet servlet) {
userProperties.put("ws.consumer", consumer); userProperties.put("teavm.servlet", servlet);
} }
@Override @Override