Reduce memory used by call graph in dev server mode

This commit is contained in:
Alexey Andreev 2019-03-07 16:51:00 +03:00
parent 88dca1bd02
commit fcfa998e1c
20 changed files with 284 additions and 192 deletions

View File

@ -18,37 +18,13 @@ package org.teavm.callgraph;
import java.util.Collection; import java.util.Collection;
import org.teavm.model.MethodReference; import org.teavm.model.MethodReference;
/**
* Represents a method with information about what methods does it call and what method do call the method.
* @author Alexey Andreev
*/
public interface CallGraphNode { public interface CallGraphNode {
/**
* Returns reference to entire call graph.
*
* @return graph
*/
CallGraph getGraph(); CallGraph getGraph();
/**
* Returns the method that this node represents.
*
* @return method
*/
MethodReference getMethod(); MethodReference getMethod();
/**
* Returns immutable collection of all call sites that are in the method.
*
* @return call site
*/
Collection<? extends CallSite> getCallSites(); Collection<? extends CallSite> getCallSites();
/**
* Returns immutable collection of all call sites that call this method.
*
* @return call sites
*/
Collection<? extends CallSite> getCallerCallSites(); Collection<? extends CallSite> getCallerCallSites();
Collection<? extends FieldAccessSite> getFieldAccessSites(); Collection<? extends FieldAccessSite> getFieldAccessSites();

View File

@ -15,31 +15,13 @@
*/ */
package org.teavm.callgraph; package org.teavm.callgraph;
import java.util.Collection;
import org.teavm.model.TextLocation; import org.teavm.model.TextLocation;
/**
* <p>Call site that represents exact place in the code that calls a method.</p>.
* @author Alexey Andreev
*/
public interface CallSite { public interface CallSite {
/** Collection<? extends TextLocation> getLocations(CallGraphNode caller);
* <p>Gets location of the call site</p>.
*
* @return location of the call site or <code>null</code> if no debug information found for this call site.
*/
TextLocation getLocation();
/** Collection<? extends CallGraphNode> getCalledMethods();
* <p>Gets a method that this call site invokes.</p>
*
* @return a node that represent methods being called
*/
CallGraphNode getCallee();
/** Collection<? extends CallGraphNode> getCallers();
* <p>Gets a method that contains this call site.</p>
*
* @return a node that represents methods's caller
*/
CallGraphNode getCaller();
} }

View File

@ -1,65 +0,0 @@
/*
* Copyright 2014 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.callgraph;
import java.io.Serializable;
import java.util.Objects;
import org.teavm.model.TextLocation;
public class DefaultCallSite implements CallSite, Serializable {
private TextLocation location;
private DefaultCallGraphNode callee;
private DefaultCallGraphNode caller;
DefaultCallSite(TextLocation location, DefaultCallGraphNode callee, DefaultCallGraphNode caller) {
this.location = location;
this.callee = callee;
this.caller = caller;
}
@Override
public TextLocation getLocation() {
return location;
}
@Override
public DefaultCallGraphNode getCallee() {
return callee;
}
@Override
public DefaultCallGraphNode getCaller() {
return caller;
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof DefaultCallSite)) {
return false;
}
DefaultCallSite other = (DefaultCallSite) obj;
return Objects.equals(callee.getMethod(), other.callee.getMethod())
&& Objects.equals(location, other.location);
}
@Override
public int hashCode() {
return Objects.hash(callee.getMethod(), location);
}
}

View File

@ -294,8 +294,8 @@ public class DebugInformation {
private DebuggerCallSite getCallSite(int index) { private DebuggerCallSite getCallSite(int index) {
RecordArray.Record record = callSiteMapping.get(index); RecordArray.Record record = callSiteMapping.get(index);
int type = record.get(2); int type = record.get(0);
int method = record.get(3); int method = record.get(1);
switch (type) { switch (type) {
case DebuggerCallSite.NONE: case DebuggerCallSite.NONE:
return null; return null;

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2014 Alexey Andreev. * Copyright 2019 Alexey Andreev.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.teavm.callgraph; package org.teavm.dependency;
import com.carrotsearch.hppc.ObjectIntHashMap; import com.carrotsearch.hppc.ObjectIntHashMap;
import com.carrotsearch.hppc.ObjectIntMap; import com.carrotsearch.hppc.ObjectIntMap;
@ -30,10 +30,12 @@ import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import org.teavm.callgraph.CallGraph;
import org.teavm.model.FieldReference; import org.teavm.model.FieldReference;
import org.teavm.model.MethodReference; import org.teavm.model.MethodReference;
import org.teavm.model.TextLocation;
public class DefaultCallGraph implements CallGraph, Serializable { class DefaultCallGraph implements CallGraph, Serializable {
public Map<MethodReference, DefaultCallGraphNode> nodes = new HashMap<>(); public Map<MethodReference, DefaultCallGraphNode> nodes = new HashMap<>();
Map<FieldReference, Set<DefaultFieldAccessSite>> fieldAccessSites = new HashMap<>(); Map<FieldReference, Set<DefaultFieldAccessSite>> fieldAccessSites = new HashMap<>();
@ -127,9 +129,19 @@ public class DefaultCallGraph implements CallGraph, Serializable {
for (DefaultCallSite callSite : callSitesToProcess.toArray(new DefaultCallSite[0])) { for (DefaultCallSite callSite : callSitesToProcess.toArray(new DefaultCallSite[0])) {
int index = callSiteToIndex.get(callSite); int index = callSiteToIndex.get(callSite);
SerializableCallGraph.CallSite scs = callSites.get(index); SerializableCallGraph.CallSite scs = callSites.get(index);
scs.location = callSite.getLocation(); scs.virtual = callSite.callers != null;
scs.caller = getNode(callSite.getCaller()); List<SerializableCallGraph.Location> locations = new ArrayList<>();
scs.callee = getNode(callSite.getCallee()); for (DefaultCallGraphNode caller : callSite.getCallers()) {
for (TextLocation textLocation : callSite.getLocations(caller)) {
SerializableCallGraph.Location location = new SerializableCallGraph.Location();
location.caller = getNode(caller);
location.value = textLocation;
locations.add(location);
}
}
scs.locations = locations.toArray(new SerializableCallGraph.Location[0]);
scs.callers = getNodes(callSite.getCallers());
scs.calledMethods = getNodes(callSite.getCalledMethods());
hasAny = true; hasAny = true;
} }
callSitesToProcess.clear(); callSitesToProcess.clear();
@ -161,6 +173,15 @@ public class DefaultCallGraph implements CallGraph, Serializable {
return index; return index;
} }
private int[] getNodes(Collection<? extends DefaultCallGraphNode> nodes) {
int[] result = new int[nodes.size()];
int index = 0;
for (DefaultCallGraphNode node : nodes) {
result[index++] = getNode(node);
}
return result;
}
private int getCallSite(DefaultCallSite callSite) { private int getCallSite(DefaultCallSite callSite) {
int index = callSiteToIndex.getOrDefault(callSite, -1); int index = callSiteToIndex.getOrDefault(callSite, -1);
if (index < 0) { if (index < 0) {
@ -194,7 +215,17 @@ public class DefaultCallGraph implements CallGraph, Serializable {
nodes.add(new DefaultCallGraphNode(cg, serializableNode.method)); nodes.add(new DefaultCallGraphNode(cg, serializableNode.method));
} }
for (SerializableCallGraph.CallSite scs : scg.callSites) { for (SerializableCallGraph.CallSite scs : scg.callSites) {
callSites.add(new DefaultCallSite(scs.location, nodes.get(scs.callee), nodes.get(scs.caller))); DefaultCallSite callSite;
if (scs.virtual) {
callSite = new DefaultCallSite(scs.method, mapNodes(scs.callers));
callSite.calledMethods.addAll(mapNodes(scs.calledMethods));
} else {
callSite = new DefaultCallSite(nodes.get(scs.calledMethods[0]), nodes.get(scs.callers[0]));
}
for (SerializableCallGraph.Location location : scs.locations) {
callSite.addLocation(nodes.get(location.caller), location.value);
}
callSites.add(callSite);
} }
for (SerializableCallGraph.FieldAccess sfa : scg.fieldAccessList) { for (SerializableCallGraph.FieldAccess sfa : scg.fieldAccessList) {
fieldAccessList.add(new DefaultFieldAccessSite(sfa.location, nodes.get(sfa.callee), sfa.field)); fieldAccessList.add(new DefaultFieldAccessSite(sfa.location, nodes.get(sfa.callee), sfa.field));
@ -208,5 +239,13 @@ public class DefaultCallGraph implements CallGraph, Serializable {
cg.addFieldAccess(fieldAccessList.get(index)); cg.addFieldAccess(fieldAccessList.get(index));
} }
} }
private Set<DefaultCallGraphNode> mapNodes(int[] nodes) {
Set<DefaultCallGraphNode> result = new LinkedHashSet<>();
for (int i = 0; i < nodes.length; ++i) {
result.add(this.nodes.get(nodes[i]));
}
return result;
}
} }
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2014 Alexey Andreev. * Copyright 2019 Alexey Andreev.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -13,29 +13,34 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.teavm.callgraph; package org.teavm.dependency;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
import org.teavm.callgraph.CallGraphNode;
import org.teavm.model.FieldReference; import org.teavm.model.FieldReference;
import org.teavm.model.MethodReference; import org.teavm.model.MethodReference;
import org.teavm.model.TextLocation; import org.teavm.model.TextLocation;
public class DefaultCallGraphNode implements CallGraphNode { class DefaultCallGraphNode implements CallGraphNode {
private DefaultCallGraph graph; private DefaultCallGraph graph;
private MethodReference method; private MethodReference method;
private Set<DefaultCallSite> callSites; private Map<MethodReference, DefaultCallSite> callSiteMap;
private List<DefaultCallSite> callSites;
private DefaultCallSite singleCallSite; private DefaultCallSite singleCallSite;
private Set<DefaultCallSite> safeCallSites; private Collection<DefaultCallSite> safeCallSites;
private DefaultCallSite singleCaller; private DefaultCallSite singleCaller;
private List<DefaultCallSite> callerCallSites; private List<DefaultCallSite> callerCallSites;
private List<DefaultCallSite> safeCallersCallSites; private List<DefaultCallSite> safeCallersCallSites;
private Set<DefaultFieldAccessSite> fieldAccessSites = new LinkedHashSet<>(); private Set<DefaultFieldAccessSite> fieldAccessSites = new LinkedHashSet<>();
private Set<DefaultFieldAccessSite> safeFieldAccessSites; private Set<DefaultFieldAccessSite> safeFieldAccessSites;
private DefaultCallSite virtualCallSite;
DefaultCallGraphNode(DefaultCallGraph graph, MethodReference method) { DefaultCallGraphNode(DefaultCallGraph graph, MethodReference method) {
this.graph = graph; this.graph = graph;
@ -61,7 +66,7 @@ public class DefaultCallGraphNode implements CallGraphNode {
return Collections.emptyList(); return Collections.emptyList();
} }
if (safeCallSites == null) { if (safeCallSites == null) {
safeCallSites = Collections.unmodifiableSet(callSites); safeCallSites = Collections.unmodifiableCollection(callSites);
} }
return safeCallSites; return safeCallSites;
} }
@ -80,28 +85,64 @@ public class DefaultCallGraphNode implements CallGraphNode {
return safeCallersCallSites; return safeCallersCallSites;
} }
public boolean addCallSite(MethodReference method, TextLocation location) { DefaultCallSite addCallSite(MethodReference method) {
DefaultCallGraphNode callee = graph.getNode(method); DefaultCallGraphNode callee = graph.getNode(method);
DefaultCallSite callSite = new DefaultCallSite(location, callee, this);
if (callSites == null) { if (callSites == null) {
if (singleCallSite == null) { if (singleCallSite == null) {
singleCallSite = callSite; singleCallSite = new DefaultCallSite(callee, this);
callee.addCaller(callSite); callee.addCaller(singleCallSite);
return true; return singleCallSite;
} }
callSites = new LinkedHashSet<>(); if (singleCallSite != null) {
if (singleCallSite.singleCalledMethod.getMethod().equals(method)) {
return singleCallSite;
}
}
callSiteMap = new LinkedHashMap<>();
callSites = new ArrayList<>();
callSiteMap.put(singleCallSite.singleCalledMethod.getMethod(), singleCallSite);
callSites.add(singleCallSite); callSites.add(singleCallSite);
singleCallSite = null; singleCallSite = null;
} }
if (callSites.add(callSite)) {
DefaultCallSite callSite = callSiteMap.get(method);
if (callSite == null) {
callSite = new DefaultCallSite(callee, this);
callee.addCaller(callSite); callee.addCaller(callSite);
return true; callSiteMap.put(method, callSite);
} else { callSites.add(callSite);
return false; }
return callSite;
}
DefaultCallSite getVirtualCallSite() {
if (virtualCallSite == null) {
virtualCallSite = new DefaultCallSite(method, new LinkedHashSet<>());
}
return virtualCallSite;
}
void addVirtualCallSite(DefaultCallSite callSite) {
if (callSite.callers == null) {
throw new IllegalArgumentException("Call site is not virtual");
}
if (callSite.callers.add(this)) {
if (callSites == null) {
callSites = new ArrayList<>();
callSiteMap = new LinkedHashMap<>();
if (singleCallSite != null) {
callSites.add(singleCallSite);
callSiteMap.put(singleCallSite.method, singleCallSite);
singleCallSite = null;
}
}
callSites.add(callSite);
} }
} }
private void addCaller(DefaultCallSite caller) { void addCaller(DefaultCallSite caller) {
if (callerCallSites == null) { if (callerCallSites == null) {
if (singleCaller == null) { if (singleCaller == null) {
singleCaller = caller; singleCaller = caller;
@ -114,10 +155,6 @@ public class DefaultCallGraphNode implements CallGraphNode {
callerCallSites.add(caller); callerCallSites.add(caller);
} }
public boolean addCallSite(MethodReference method) {
return addCallSite(method, null);
}
@Override @Override
public Collection<DefaultFieldAccessSite> getFieldAccessSites() { public Collection<DefaultFieldAccessSite> getFieldAccessSites() {
if (safeFieldAccessSites == null) { if (safeFieldAccessSites == null) {
@ -126,7 +163,7 @@ public class DefaultCallGraphNode implements CallGraphNode {
return safeFieldAccessSites; return safeFieldAccessSites;
} }
public boolean addFieldAccess(FieldReference field, TextLocation location) { boolean addFieldAccess(FieldReference field, TextLocation location) {
DefaultFieldAccessSite site = new DefaultFieldAccessSite(location, this, field); DefaultFieldAccessSite site = new DefaultFieldAccessSite(location, this, field);
if (fieldAccessSites.add(site)) { if (fieldAccessSites.add(site)) {
graph.addFieldAccess(site); graph.addFieldAccess(site);

View File

@ -0,0 +1,96 @@
/*
* Copyright 2019 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.dependency;
import java.io.Serializable;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import org.teavm.callgraph.CallGraphNode;
import org.teavm.callgraph.CallSite;
import org.teavm.model.MethodReference;
import org.teavm.model.TextLocation;
public class DefaultCallSite implements CallSite, Serializable {
private Map<CallGraphNode, Set<TextLocation>> locations;
private TextLocation singleLocation;
MethodReference method;
Set<DefaultCallGraphNode> callers;
private DefaultCallGraphNode singleCaller;
Set<DefaultCallGraphNode> calledMethods;
DefaultCallGraphNode singleCalledMethod;
Collection<? extends DefaultCallGraphNode> readonlyCalledMethods;
DefaultCallSite(MethodReference method, Set<DefaultCallGraphNode> callers) {
this.method = method;
this.callers = callers;
locations = new HashMap<>();
calledMethods = new LinkedHashSet<>();
}
DefaultCallSite(DefaultCallGraphNode callee, DefaultCallGraphNode caller) {
this.singleCalledMethod = callee;
this.singleCaller = caller;
}
@Override
public Collection<? extends TextLocation> getLocations(CallGraphNode caller) {
if (singleLocation != null) {
return caller == this.singleCaller ? Collections.singleton(singleLocation) : Collections.emptySet();
}
if (locations == null) {
return Collections.emptyList();
}
Set<TextLocation> result = locations.get(caller);
return result != null ? Collections.unmodifiableSet(result) : Collections.emptySet();
}
public void addLocation(DefaultCallGraphNode caller, TextLocation location) {
if (locations == null) {
if (singleLocation == null && callers == null) {
singleLocation = location;
return;
}
locations = new LinkedHashMap<>();
if (singleLocation != null) {
Set<TextLocation> singleLocations = new LinkedHashSet<>();
singleLocations.add(singleLocation);
locations.put(singleCaller, singleLocations);
}
}
locations.computeIfAbsent(caller, k -> new LinkedHashSet<>()).add(location);
}
@Override
public Collection<? extends DefaultCallGraphNode> getCalledMethods() {
if (singleCalledMethod == null) {
return Collections.singletonList(singleCalledMethod);
}
if (readonlyCalledMethods == null) {
readonlyCalledMethods = Collections.unmodifiableCollection(calledMethods);
}
return readonlyCalledMethods;
}
@Override
public Collection<? extends DefaultCallGraphNode> getCallers() {
return callers != null ? callers : Collections.singletonList(singleCaller);
}
}

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2014 Alexey Andreev. * Copyright 2019 Alexey Andreev.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -13,14 +13,15 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.teavm.callgraph; package org.teavm.dependency;
import java.io.Serializable; import java.io.Serializable;
import java.util.Objects; import java.util.Objects;
import org.teavm.callgraph.FieldAccessSite;
import org.teavm.model.FieldReference; import org.teavm.model.FieldReference;
import org.teavm.model.TextLocation; import org.teavm.model.TextLocation;
public class DefaultFieldAccessSite implements FieldAccessSite, Serializable { class DefaultFieldAccessSite implements FieldAccessSite, Serializable {
private TextLocation location; private TextLocation location;
private DefaultCallGraphNode callee; private DefaultCallGraphNode callee;
private FieldReference field; private FieldReference field;

View File

@ -36,7 +36,6 @@ import org.objectweb.asm.tree.ClassNode;
import org.teavm.cache.IncrementalDependencyProvider; import org.teavm.cache.IncrementalDependencyProvider;
import org.teavm.cache.IncrementalDependencyRegistration; import org.teavm.cache.IncrementalDependencyRegistration;
import org.teavm.callgraph.CallGraph; import org.teavm.callgraph.CallGraph;
import org.teavm.callgraph.DefaultCallGraph;
import org.teavm.common.CachedMapper; import org.teavm.common.CachedMapper;
import org.teavm.common.Mapper; import org.teavm.common.Mapper;
import org.teavm.common.ServiceRepository; import org.teavm.common.ServiceRepository;

View File

@ -22,7 +22,6 @@ import static org.teavm.dependency.AbstractInstructionAnalyzer.MONITOR_EXIT_SYNC
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import org.teavm.callgraph.DefaultCallGraphNode;
import org.teavm.model.BasicBlockReader; import org.teavm.model.BasicBlockReader;
import org.teavm.model.CallLocation; import org.teavm.model.CallLocation;
import org.teavm.model.ClassHierarchy; import org.teavm.model.ClassHierarchy;

View File

@ -177,7 +177,7 @@ public class FastDependencyAnalyzer extends DependencyAnalyzer {
FastVirtualCallConsumer getVirtualCallConsumer(MethodReference method) { FastVirtualCallConsumer getVirtualCallConsumer(MethodReference method) {
return virtualCallConsumers.computeIfAbsent(method, key -> { return virtualCallConsumers.computeIfAbsent(method, key -> {
FastVirtualCallConsumer consumer = new FastVirtualCallConsumer(instancesNode, key.getDescriptor(), this); FastVirtualCallConsumer consumer = new FastVirtualCallConsumer(instancesNode, key, this);
defer(() -> { defer(() -> {
getSubtypeNode(method.getClassName()).addConsumer(consumer); getSubtypeNode(method.getClassName()).addConsumer(consumer);
}); });

View File

@ -47,7 +47,8 @@ class FastInstructionAnalyzer extends AbstractInstructionAnalyzer {
protected void invokeVirtual(VariableReader receiver, VariableReader instance, MethodReference method, protected void invokeVirtual(VariableReader receiver, VariableReader instance, MethodReference method,
List<? extends VariableReader> arguments) { List<? extends VariableReader> arguments) {
invokeGetClass(method); invokeGetClass(method);
dependencyAnalyzer.getVirtualCallConsumer(method).addLocation(impreciseLocation); FastVirtualCallConsumer consumer = dependencyAnalyzer.getVirtualCallConsumer(method);
consumer.addLocation(impreciseLocation);
} }
private void invokeGetClass(MethodReference method) { private void invokeGetClass(MethodReference method) {

View File

@ -20,41 +20,45 @@ import java.util.LinkedHashSet;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import org.teavm.model.CallLocation; import org.teavm.model.CallLocation;
import org.teavm.model.MethodDescriptor;
import org.teavm.model.MethodReference; import org.teavm.model.MethodReference;
class FastVirtualCallConsumer implements DependencyConsumer { class FastVirtualCallConsumer implements DependencyConsumer {
private final DependencyNode node; private final DependencyNode node;
private final MethodDescriptor methodDesc; private final MethodReference methodRef;
private final DependencyAnalyzer analyzer; private final DependencyAnalyzer analyzer;
private final Map<MethodReference, CallLocation> callLocations = new LinkedHashMap<>(); private final Map<MethodReference, CallLocation> callLocations = new LinkedHashMap<>();
private final Set<MethodDependency> methods = new LinkedHashSet<>(100, 0.5f); private final Set<MethodDependency> methods = new LinkedHashSet<>(100, 0.5f);
final DefaultCallSite callSite;
FastVirtualCallConsumer(DependencyNode node, MethodDescriptor methodDesc, DependencyAnalyzer analyzer) { FastVirtualCallConsumer(DependencyNode node, MethodReference methodRef, DependencyAnalyzer analyzer) {
this.node = node; this.node = node;
this.methodDesc = methodDesc; this.methodRef = methodRef;
this.analyzer = analyzer; this.analyzer = analyzer;
callSite = analyzer.callGraph.getNode(methodRef).getVirtualCallSite();
} }
@Override @Override
public void consume(DependencyType type) { public void consume(DependencyType type) {
String className = type.getName(); String className = type.getName();
if (DependencyAnalyzer.shouldLog) { if (DependencyAnalyzer.shouldLog) {
System.out.println("Virtual call of " + methodDesc + " detected on " + node.getTag() + ". " System.out.println("Virtual call of " + methodRef + " detected on " + node.getTag() + ". "
+ "Target class is " + className); + "Target class is " + className);
} }
if (className.startsWith("[")) { if (className.startsWith("[")) {
className = "java.lang.Object"; className = "java.lang.Object";
type = analyzer.getType(className);
} }
MethodDependency methodDep = analyzer.linkMethod(className, methodDesc); MethodDependency methodDep = analyzer.linkMethod(className, methodRef.getDescriptor());
if (!methods.add(methodDep)) { if (!methods.add(methodDep)) {
return; return;
} }
DefaultCallGraphNode calledMethodNode = analyzer.callGraph.getNode(methodDep.getReference());
callSite.calledMethods.add(calledMethodNode);
calledMethodNode.addCaller(callSite);
for (CallLocation location : callLocations.values()) { for (CallLocation location : callLocations.values()) {
methodDep.addLocation(location); methodDep.addLocation(location, false);
} }
if (!methodDep.isMissing()) { if (!methodDep.isMissing()) {
@ -64,8 +68,13 @@ class FastVirtualCallConsumer implements DependencyConsumer {
void addLocation(CallLocation location) { void addLocation(CallLocation location) {
if (callLocations.putIfAbsent(location.getMethod(), location) == null) { if (callLocations.putIfAbsent(location.getMethod(), location) == null) {
DefaultCallGraphNode caller = analyzer.callGraph.getNode(location.getMethod());
caller.addVirtualCallSite(callSite);
if (location.getSourceLocation() != null) {
callSite.addLocation(caller, location.getSourceLocation());
}
for (MethodDependency method : methods) { for (MethodDependency method : methods) {
method.addLocation(location); method.addLocation(location, false);
} }
} }
} }

View File

@ -19,7 +19,6 @@ import java.util.ArrayList;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import org.teavm.callgraph.DefaultCallGraphNode;
import org.teavm.model.CallLocation; import org.teavm.model.CallLocation;
import org.teavm.model.FieldReader; import org.teavm.model.FieldReader;
import org.teavm.model.FieldReference; import org.teavm.model.FieldReference;

View File

@ -20,7 +20,6 @@ import java.util.Arrays;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import org.teavm.callgraph.DefaultCallGraphNode;
import org.teavm.model.CallLocation; import org.teavm.model.CallLocation;
import org.teavm.model.MethodHolder; import org.teavm.model.MethodHolder;
import org.teavm.model.MethodReader; import org.teavm.model.MethodReader;
@ -111,12 +110,21 @@ public class MethodDependency implements MethodDependencyInfo {
} }
public MethodDependency addLocation(CallLocation location) { public MethodDependency addLocation(CallLocation location) {
return addLocation(location, true);
}
MethodDependency addLocation(CallLocation location, boolean addCallSite) {
DefaultCallGraphNode node = dependencyAnalyzer.callGraph.getNode(location.getMethod()); DefaultCallGraphNode node = dependencyAnalyzer.callGraph.getNode(location.getMethod());
if (locations == null) { if (locations == null) {
locations = new LinkedHashSet<>(); locations = new LinkedHashSet<>();
} }
if (locations.add(location)) { if (locations.add(location)) {
node.addCallSite(reference, location.getSourceLocation()); if (addCallSite) {
DefaultCallSite callSite = node.addCallSite(reference);
if (location.getSourceLocation() != null) {
callSite.addLocation(node, location.getSourceLocation());
}
}
if (locationListeners != null) { if (locationListeners != null) {
for (LocationListener listener : locationListeners.toArray(new LocationListener[0])) { for (LocationListener listener : locationListeners.toArray(new LocationListener[0])) {
listener.locationAdded(location); listener.locationAdded(location);

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2017 Alexey Andreev. * Copyright 2019 Alexey Andreev.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.teavm.callgraph; package org.teavm.dependency;
import java.io.Serializable; import java.io.Serializable;
import org.teavm.model.FieldReference; import org.teavm.model.FieldReference;
@ -35,15 +35,22 @@ class SerializableCallGraph implements Serializable {
int[] fieldAccessSites; int[] fieldAccessSites;
} }
static class CallSite implements Serializable { static class CallSite implements Serializable {
TextLocation location; MethodReference method;
int callee; boolean virtual;
int caller; Location[] locations;
int[] calledMethods;
int[] callers;
} }
static class FieldAccess implements Serializable { static class FieldAccess implements Serializable {
TextLocation location; TextLocation location;
int callee; int callee;
FieldReference field; FieldReference field;
} }
static class Location {
TextLocation value;
int caller;
}
} }

View File

@ -62,7 +62,6 @@ class VirtualCallConsumer implements DependencyConsumer {
} }
if (className.startsWith("[")) { if (className.startsWith("[")) {
className = "java.lang.Object"; className = "java.lang.Object";
type = analyzer.getType(className);
} }
MethodDependency methodDep = analyzer.linkMethod(className, methodDesc); MethodDependency methodDep = analyzer.linkMethod(className, methodDesc);

View File

@ -46,7 +46,6 @@ public class AsyncMethodFinder {
private Map<MethodReference, Boolean> asyncFamilyMethods = new HashMap<>(); private Map<MethodReference, Boolean> asyncFamilyMethods = new HashMap<>();
private Set<MethodReference> readonlyAsyncMethods = Collections.unmodifiableSet(asyncMethods); private Set<MethodReference> readonlyAsyncMethods = Collections.unmodifiableSet(asyncMethods);
private Set<MethodReference> readonlyAsyncFamilyMethods = Collections.unmodifiableSet(asyncFamilyMethods.keySet()); private Set<MethodReference> readonlyAsyncFamilyMethods = Collections.unmodifiableSet(asyncFamilyMethods.keySet());
private Map<MethodReference, Set<MethodReference>> overiddenMethodsCache = new HashMap<>();
private CallGraph callGraph; private CallGraph callGraph;
private Diagnostics diagnostics; private Diagnostics diagnostics;
private ListableClassReaderSource classSource; private ListableClassReaderSource classSource;
@ -178,8 +177,9 @@ public class AsyncMethodFinder {
return; return;
} }
for (CallSite callSite : node.getCallerCallSites()) { for (CallSite callSite : node.getCallerCallSites()) {
MethodReference nextMethod = callSite.getCaller().getMethod(); for (CallGraphNode caller : callSite.getCallers()) {
add(nextMethod, new CallStack(nextMethod, stack)); add(caller.getMethod(), new CallStack(caller.getMethod(), stack));
}
} }
} }

View File

@ -125,7 +125,6 @@ public class TeaVM implements TeaVMHost, ServiceRepository {
private static final MethodDescriptor MAIN_METHOD_DESC = new MethodDescriptor("main", private static final MethodDescriptor MAIN_METHOD_DESC = new MethodDescriptor("main",
ValueType.arrayOf(ValueType.object("java.lang.String")), ValueType.VOID); ValueType.arrayOf(ValueType.object("java.lang.String")), ValueType.VOID);
private final ClassReaderSource classSource;
private final DependencyAnalyzer dependencyAnalyzer; private final DependencyAnalyzer dependencyAnalyzer;
private final AccumulationDiagnostics diagnostics = new AccumulationDiagnostics(); private final AccumulationDiagnostics diagnostics = new AccumulationDiagnostics();
private final ClassLoader classLoader; private final ClassLoader classLoader;
@ -155,9 +154,8 @@ public class TeaVM implements TeaVMHost, ServiceRepository {
TeaVM(TeaVMBuilder builder) { TeaVM(TeaVMBuilder builder) {
target = builder.target; target = builder.target;
classSource = builder.classSource;
classLoader = builder.classLoader; classLoader = builder.classLoader;
dependencyAnalyzer = builder.dependencyAnalyzerFactory.create(this.classSource, classLoader, dependencyAnalyzer = builder.dependencyAnalyzerFactory.create(builder.classSource, classLoader,
this, diagnostics, builder.referenceCache); this, diagnostics, builder.referenceCache);
progressListener = new TeaVMProgressListener() { progressListener = new TeaVMProgressListener() {
@Override public TeaVMProgressFeedback progressReached(int progress) { @Override public TeaVMProgressFeedback progressReached(int progress) {
@ -313,21 +311,6 @@ public class TeaVM implements TeaVMHost, ServiceRepository {
preservedClasses.add(className); preservedClasses.add(className);
} }
/**
* Gets a {@link ClassReaderSource} which is used by this TeaVM instance. It is exactly what was
* passed to {@link TeaVMBuilder#setClassSource(ClassReaderSource)}.
*
* @return class source.
*/
public ClassReaderSource getClassSource() {
return classSource;
}
/**
* Gets a {@link ClassReaderSource} which is similar to that of {@link #getClassSource()},
* except that it also contains classes with applied transformations together with
* classes, generated via {@link org.teavm.dependency.DependencyAgent#submitClass(ClassHolder)}.
*/
public ClassReaderSource getDependencyClassSource() { public ClassReaderSource getDependencyClassSource() {
return dependencyAnalyzer.getClassSource(); return dependencyAnalyzer.getClassSource();
} }

View File

@ -15,6 +15,7 @@
*/ */
package org.teavm.tooling; package org.teavm.tooling;
import java.util.Collection;
import java.util.Iterator; import java.util.Iterator;
import org.teavm.callgraph.CallGraph; import org.teavm.callgraph.CallGraph;
import org.teavm.callgraph.CallGraphNode; import org.teavm.callgraph.CallGraphNode;
@ -70,12 +71,33 @@ public final class TeaVMProblemRenderer {
} }
CallSite callSite = callSites.next(); CallSite callSite = callSites.next();
sb.append("\n at "); sb.append("\n at ");
renderCallLocation(callSite.getCaller().getMethod(), callSite.getLocation(), sb);
node = callSite.getCaller(); CallGraphNode caller = getCaller(callSite);
renderCallLocation(caller.getMethod(), getLocation(callSite, caller), sb);
node = caller;
} }
} }
} }
private static CallGraphNode getCaller(CallSite callSite) {
Collection<? extends CallGraphNode> callers = callSite.getCallers();
if (callers.isEmpty()) {
return null;
}
return callers.iterator().next();
}
private static TextLocation getLocation(CallSite callSite, CallGraphNode caller) {
if (caller == null) {
return null;
}
Collection<? extends TextLocation> locations = callSite.getLocations(caller);
if (locations.isEmpty()) {
return null;
}
return locations.iterator().next();
}
public static void renderCallLocation(MethodReference method, TextLocation location, StringBuilder sb) { public static void renderCallLocation(MethodReference method, TextLocation location, StringBuilder sb) {
if (method != null) { if (method != null) {
sb.append(method.getClassName() + "." + method.getName()); sb.append(method.getClassName() + "." + method.getName());