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;
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;
}

View File

@ -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() {

View File

@ -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);

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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 {

View File

@ -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 {

View File

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

View File

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

View File

@ -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();

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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);
}

View File

@ -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)) {

View File

@ -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();
}

View File

@ -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()) {

View File

@ -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);
}
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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);

View File

@ -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;

View File

@ -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) {

View File

@ -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) {

View File

@ -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")));

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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 + " }");
}
}

View File

@ -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