mirror of
https://github.com/Eaglercraft-TeaVM-Fork/eagler-teavm.git
synced 2024-12-22 08:14:09 -08:00
Improve performance and stability of development server and incremental compilation
This commit is contained in:
parent
c9c9c15dfe
commit
bab0cd59a6
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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<ElementModifier> 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<ElementModifier> getModifiers() {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -25,8 +25,8 @@ public class DefaultNamingStrategy implements NamingStrategy {
|
|||
private final Map<String, String> aliases = new HashMap<>();
|
||||
private final Map<String, String> privateAliases = new HashMap<>();
|
||||
private final Map<String, String> classAliases = new HashMap<>();
|
||||
private final Map<String, String> fieldAliases = new HashMap<>();
|
||||
private final Map<String, String> staticFieldAliases = new HashMap<>();
|
||||
private final Map<FieldReference, String> fieldAliases = new HashMap<>();
|
||||
private final Map<FieldReference, String> staticFieldAliases = new HashMap<>();
|
||||
private final Map<String, String> functionAliases = new HashMap<>();
|
||||
private final Map<String, String> 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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Predicate<String>> synthesizedClasses = new ArrayList<>();
|
||||
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.dependencyProvider = dependencyProvider;
|
||||
this.classSource = classSource;
|
||||
}
|
||||
|
||||
public void addSynthesizedClasses(Predicate<String> 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;
|
||||
}
|
||||
|
||||
|
|
|
@ -25,7 +25,9 @@ import org.teavm.model.MethodReference;
|
|||
|
||||
public class InMemoryMethodNodeCache implements MethodNodeCache {
|
||||
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> 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<String[]> 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<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 {
|
||||
|
|
|
@ -25,6 +25,7 @@ import org.teavm.model.ProgramCache;
|
|||
|
||||
public class InMemoryProgramCache implements ProgramCache {
|
||||
private Map<MethodReference, Item> cache = new HashMap<>();
|
||||
private Map<MethodReference, Item> 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<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 {
|
||||
|
|
|
@ -88,7 +88,6 @@ public class DependencyAgent implements DependencyInfo, ServiceRepository {
|
|||
return analyzer.getClassLoader();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClassHierarchy getClassHierarchy() {
|
||||
return analyzer.getClassHierarchy();
|
||||
}
|
||||
|
|
|
@ -214,7 +214,6 @@ public abstract class DependencyAnalyzer implements DependencyInfo {
|
|||
return classSource.isGeneratedClass(className);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClassHierarchy getClassHierarchy() {
|
||||
return classHierarchy;
|
||||
}
|
||||
|
|
|
@ -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<MethodReference> getReachableMethods();
|
||||
|
||||
Collection<FieldReference> getReachableFields();
|
||||
|
|
|
@ -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<? extends VariableReader> 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<? extends VariableReader> 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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ class FastVirtualCallConsumer implements DependencyConsumer {
|
|||
private final MethodDescriptor methodDesc;
|
||||
private final DependencyAnalyzer analyzer;
|
||||
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) {
|
||||
this.node = node;
|
||||
|
|
|
@ -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<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) {
|
||||
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<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) {
|
||||
return superclassPredicateCache.computeIfAbsent(superclass, SuperclassPredicate::new);
|
||||
}
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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<Instruction> 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<PlanEntry> plan = buildPlan(program, classSource, 0);
|
||||
private boolean applyOnce(Program program) {
|
||||
List<PlanEntry> plan = buildPlan(program, 0);
|
||||
if (plan.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
@ -210,7 +219,7 @@ public class Inlining {
|
|||
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) {
|
||||
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()) {
|
||||
|
|
|
@ -46,6 +46,7 @@ public class AsyncMethodFinder {
|
|||
private Map<MethodReference, Boolean> asyncFamilyMethods = new HashMap<>();
|
||||
private Set<MethodReference> readonlyAsyncMethods = Collections.unmodifiableSet(asyncMethods);
|
||||
private Set<MethodReference> readonlyAsyncFamilyMethods = Collections.unmodifiableSet(asyncFamilyMethods.keySet());
|
||||
private Map<MethodReference, Set<MethodReference>> 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<MethodReference> findOverriddenMethods(ClassReader cls, MethodReference methodRef) {
|
||||
private Set<MethodReference> findOverriddenMethods(ClassReader cls, MethodDescriptor methodDesc) {
|
||||
List<String> parents = new ArrayList<>();
|
||||
if (cls.getParent() != null) {
|
||||
parents.add(cls.getParent());
|
||||
}
|
||||
parents.addAll(cls.getInterfaces());
|
||||
|
||||
Set<MethodReference> visited = new HashSet<>();
|
||||
Set<String> visited = new HashSet<>();
|
||||
Set<MethodReference> 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<MethodReference> result,
|
||||
Set<MethodReference> visited) {
|
||||
if (!visited.add(methodRef)) {
|
||||
private void findOverriddenMethods(String className, MethodDescriptor methodDesc, Set<MethodReference> result,
|
||||
Set<String> visited) {
|
||||
if (!visited.add(className)) {
|
||||
return;
|
||||
}
|
||||
if (methodRef.getName().equals("<init>") || methodRef.getName().equals("<clinit>")) {
|
||||
if (methodDesc.getName().equals("<init>") || methodDesc.getName().equals("<clinit>")) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ import org.teavm.model.optimization.UnreachableBasicBlockEliminator;
|
|||
|
||||
public class MissingItemsProcessor {
|
||||
private DependencyInfo dependencyInfo;
|
||||
private ClassHierarchy hierarchy;
|
||||
private Diagnostics diagnostics;
|
||||
private List<Instruction> instructionsToAdd = new ArrayList<>();
|
||||
private MethodHolder methodHolder;
|
||||
|
@ -35,9 +36,10 @@ public class MissingItemsProcessor {
|
|||
private Collection<MethodReference> reachableMethods;
|
||||
private Collection<FieldReference> 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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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<MethodReference, Program> 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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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<String, Integer> 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;
|
||||
|
||||
|
|
|
@ -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<Assertion> 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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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")));
|
||||
|
|
|
@ -43,6 +43,7 @@ public class FileSystemWatcher {
|
|||
private WatchService watchService;
|
||||
private Map<WatchKey, Path> keysToPath = new HashMap<>();
|
||||
private Map<Path, WatchKey> pathsToKey = new HashMap<>();
|
||||
private Map<Path, Integer> refCount = new HashMap<>();
|
||||
private Set<File> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<String, byte[]> content = new HashMap<>();
|
||||
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) {
|
||||
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<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);
|
||||
}
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<CodeWsEndpoint> consumer = (Consumer<CodeWsEndpoint>) 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 + " }");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<String> 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<String> 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<String, Object> userProperties = new HashMap<>();
|
||||
|
||||
public DevServerEndpointConfig(Consumer<CodeWsEndpoint> consumer) {
|
||||
userProperties.put("ws.consumer", consumer);
|
||||
public DevServerEndpointConfig(CodeServlet servlet) {
|
||||
userProperties.put("teavm.servlet", servlet);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
Loading…
Reference in New Issue
Block a user