Change how method resolution work in DCE and devirtualization.

Since in Java 8 there are default methods in interface, method
resolution algorithm becomes more complicated. This alseocauses
several related changes.

1. Resolve methods as late as possible; do not resolve
   virtual call sites during DCE.
2. Due to several reasons we have to improve linking phase
   to preserve super methods that aren't actually ever called,
   but present in virtual call sites.

Related issue: #311
This commit is contained in:
Alexey Andreev 2017-10-22 17:56:31 +03:00
parent 600151a69b
commit de14a57fe1
16 changed files with 199 additions and 50 deletions

View File

@ -84,7 +84,7 @@ public class DependencyChecker implements DependencyInfo {
this.classSource = new DependencyClassSource(classSource, diagnostics);
this.classLoader = classLoader;
this.services = services;
methodReaderCache = new CachedMapper<>(preimage -> this.classSource.resolveMutable(preimage));
methodReaderCache = new CachedMapper<>(preimage -> this.classSource.resolveMutableImplementation(preimage));
fieldReaderCache = new CachedMapper<>(preimage -> this.classSource.resolveMutable(preimage));
methodCache = new CachedMapper<>(preimage -> {
MethodHolder method = methodReaderCache.map(preimage);

View File

@ -305,7 +305,7 @@ class DependencyGraphBuilder {
private static class VirtualCallConsumer implements DependencyConsumer {
private final DependencyNode node;
private final ClassReader filterClass;
private final String filterClass;
private final MethodDescriptor methodDesc;
private final DependencyChecker checker;
private final DependencyNode[] parameters;
@ -315,7 +315,7 @@ class DependencyGraphBuilder {
private final Set<MethodReference> knownMethods = new HashSet<>();
private ExceptionConsumer exceptionConsumer;
public VirtualCallConsumer(DependencyNode node, ClassReader filterClass,
public VirtualCallConsumer(DependencyNode node, String filterClass,
MethodDescriptor methodDesc, DependencyChecker checker, DependencyNode[] parameters,
DependencyNode result, DefaultCallGraphNode caller, TextLocation location,
ExceptionConsumer exceptionConsumer) {
@ -342,7 +342,7 @@ class DependencyGraphBuilder {
}
ClassReaderSource classSource = checker.getClassSource();
if (!classSource.isSuperType(filterClass.getName(), className).orElse(false)) {
if (!classSource.isSuperType(filterClass, className).orElse(false)) {
return;
}
MethodReference methodRef = new MethodReference(className, methodDesc);
@ -642,19 +642,13 @@ class DependencyGraphBuilder {
private void invokeVirtual(VariableReader receiver, VariableReader instance, MethodReference method,
List<? extends VariableReader> arguments) {
MethodDependency methodDep = dependencyChecker.linkMethod(method,
new CallLocation(caller.getMethod(), currentLocation));
if (methodDep.isMissing()) {
return;
}
DependencyNode[] actualArgs = new DependencyNode[arguments.size() + 1];
for (int i = 0; i < arguments.size(); ++i) {
actualArgs[i + 1] = nodes[arguments.get(i).getIndex()];
}
actualArgs[0] = nodes[instance.getIndex()];
DependencyConsumer listener = new VirtualCallConsumer(nodes[instance.getIndex()],
dependencyChecker.getClassSource().get(methodDep.getMethod().getOwnerName()),
method.getDescriptor(), dependencyChecker, actualArgs,
method.getClassName(), method.getDescriptor(), dependencyChecker, actualArgs,
receiver != null ? nodes[receiver.getIndex()] : null, caller, currentLocation,
currentExceptionConsumer);
nodes[instance.getIndex()].addConsumer(listener);

View File

@ -15,27 +15,66 @@
*/
package org.teavm.dependency;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.teavm.model.BasicBlock;
import org.teavm.model.BasicBlockReader;
import org.teavm.model.ClassHolder;
import org.teavm.model.ClassReader;
import org.teavm.model.ElementModifier;
import org.teavm.model.FieldHolder;
import org.teavm.model.FieldReference;
import org.teavm.model.Instruction;
import org.teavm.model.MethodHolder;
import org.teavm.model.MethodReader;
import org.teavm.model.MethodReference;
import org.teavm.model.Program;
import org.teavm.model.ProgramReader;
import org.teavm.model.VariableReader;
import org.teavm.model.instructions.AbstractInstructionReader;
import org.teavm.model.instructions.GetFieldInstruction;
import org.teavm.model.instructions.InitClassInstruction;
import org.teavm.model.instructions.InvocationType;
import org.teavm.model.instructions.InvokeInstruction;
import org.teavm.model.instructions.PutFieldInstruction;
public class Linker {
private Set<MethodReference> methodsToPreserve = new HashSet<>();
public void prepare(DependencyInfo dependency, ClassReader cls) {
for (MethodReader method : cls.getMethods().toArray(new MethodReader[0])) {
MethodReference methodRef = new MethodReference(cls.getName(), method.getDescriptor());
MethodDependencyInfo methodDep = dependency.getMethod(methodRef);
if (methodDep != null && method.getProgram() != null) {
collectMethodsToPreserve(method.getProgram());
}
}
}
private void collectMethodsToPreserve(ProgramReader program) {
for (BasicBlockReader block : program.getBasicBlocks()) {
block.readAllInstructions(new AbstractInstructionReader() {
@Override
public void invoke(VariableReader receiver, VariableReader instance, MethodReference method,
List<? extends VariableReader> arguments, InvocationType type) {
methodsToPreserve.add(method);
}
});
}
}
public void link(DependencyInfo dependency, ClassHolder cls) {
for (MethodHolder method : cls.getMethods().toArray(new MethodHolder[0])) {
MethodReference methodRef = new MethodReference(cls.getName(), method.getDescriptor());
MethodDependencyInfo methodDep = dependency.getMethod(methodRef);
if (methodDep == null) {
cls.removeMethod(method);
if (methodsToPreserve.contains(methodRef)) {
method.getModifiers().add(ElementModifier.ABSTRACT);
method.setProgram(null);
} else {
cls.removeMethod(method);
}
} else if (!methodDep.isUsed()) {
method.getModifiers().add(ElementModifier.ABSTRACT);
method.setProgram(null);
@ -58,9 +97,11 @@ public class Linker {
for (Instruction insn : block) {
if (insn instanceof InvokeInstruction) {
InvokeInstruction invoke = (InvokeInstruction) insn;
MethodDependencyInfo linkedMethod = dependency.getMethodImplementation(invoke.getMethod());
if (linkedMethod != null) {
invoke.setMethod(linkedMethod.getReference());
if (invoke.getType() == InvocationType.SPECIAL) {
MethodDependencyInfo linkedMethod = dependency.getMethodImplementation(invoke.getMethod());
if (linkedMethod != null) {
invoke.setMethod(linkedMethod.getReference());
}
}
} else if (insn instanceof GetFieldInstruction) {
GetFieldInstruction getField = (GetFieldInstruction) insn;

View File

@ -33,11 +33,15 @@ public interface ClassHolderSource extends ClassReaderSource {
return (MethodHolder) resolve(method);
}
default MethodHolder resolveMutableImplementation(MethodReference method) {
return (MethodHolder) resolveImplementation(method);
}
default FieldHolder resolveMutable(FieldReference field) {
return (FieldHolder) resolve(field);
}
default Stream<MethodHolder> mutableOverridenMethods(MethodReference method) {
default Stream<MethodHolder> mutableOverriddenMethods(MethodReference method) {
return overriddenMethods(method).map(m -> (MethodHolder) m);
}
}

View File

@ -19,6 +19,7 @@ import java.util.ArrayDeque;
import java.util.Deque;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;
@ -89,14 +90,19 @@ public interface ClassReaderSource {
default MethodReader resolve(MethodReference method) {
return getAncestors(method.getClassName())
.map(cls -> cls.getMethod(method.getDescriptor()))
.filter(candidate -> candidate != null)
.filter(Objects::nonNull)
.findFirst().orElse(null);
}
default MethodReader resolveImplementation(MethodReference methodReference) {
return ClassReaderSourceHelper.resolveMethodImplementation(this, methodReference.getClassName(),
methodReference.getDescriptor(), new HashSet<>());
}
default FieldReader resolve(FieldReference field) {
return getAncestors(field.getClassName())
.map(cls -> cls.getField(field.getFieldName()))
.filter(candidate -> candidate != null)
.filter(Objects::nonNull)
.findFirst().orElse(null);
}

View File

@ -0,0 +1,62 @@
/*
* Copyright 2017 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.model;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
final class ClassReaderSourceHelper {
private ClassReaderSourceHelper() {
}
static MethodReader resolveMethodImplementation(ClassReaderSource classSource, String className,
MethodDescriptor methodDescriptor, Set<String> visited) {
if (!visited.add(className)) {
return null;
}
ClassReader cls = classSource.get(className);
if (cls == null) {
return null;
}
MethodReader method = cls.getMethod(methodDescriptor);
if (method != null && !method.hasModifier(ElementModifier.ABSTRACT)) {
return method;
}
MethodReader mostSpecificMethod = null;
List<String> superClasses = new ArrayList<>();
if (cls.getParent() != null) {
superClasses.add(cls.getParent());
}
superClasses.addAll(cls.getInterfaces());
for (String superClass : superClasses) {
MethodReader resultFromSuperClass = resolveMethodImplementation(classSource, superClass,
methodDescriptor, visited);
if (resultFromSuperClass != null) {
if (mostSpecificMethod == null || classSource.isSuperType(mostSpecificMethod.getOwnerName(),
resultFromSuperClass.getOwnerName()).orElse(false)) {
mostSpecificMethod = resultFromSuperClass;
}
}
}
return mostSpecificMethod;
}
}

View File

@ -165,8 +165,7 @@ public class ClassInference {
private void propagate(Program program) {
ClassReaderSource classSource = dependencyInfo.getClassSource();
Queue<Task> queue = new ArrayDeque<>();
queue.addAll(initialTasks);
Queue<Task> queue = new ArrayDeque<>(initialTasks);
initialTasks = null;
while (!queue.isEmpty()) {
@ -239,7 +238,7 @@ public class ClassInference {
for (VirtualCallSite callSite : callSites) {
MethodReference rawMethod = new MethodReference(task.className,
callSite.method.getDescriptor());
MethodReader resolvedMethod = classSource.resolve(rawMethod);
MethodReader resolvedMethod = classSource.resolveImplementation(rawMethod);
if (resolvedMethod == null) {
continue;
}
@ -295,10 +294,10 @@ public class ClassInference {
GraphBuilder arrayGraphBuilder;
GraphBuilder itemGraphBuilder;
MethodDependencyInfo thisMethodDep;
List<IntObjectMap<ValueType>> casts = new ArrayList<>();
List<IntObjectMap<ValueType>> casts;
IntObjectMap<IntSet> exceptionMap = new IntObjectOpenHashMap<>();
List<Task> tasks = new ArrayList<>();
List<List<VirtualCallSite>> virtualCallSites = new ArrayList<>();
List<List<VirtualCallSite>> virtualCallSites;
BasicBlock currentBlock;
GraphBuildingVisitor(int variableCount, DependencyInfo dependencyInfo) {

View File

@ -19,7 +19,6 @@ import org.teavm.model.BasicBlock;
import org.teavm.model.ClassReaderSource;
import org.teavm.model.FieldReference;
import org.teavm.model.Incoming;
import org.teavm.model.MethodReader;
import org.teavm.model.MethodReference;
import org.teavm.model.Phi;
import org.teavm.model.PrimitiveType;
@ -437,10 +436,6 @@ public class ValueEmitter {
.orElse(true)) {
throw new EmitException("Can't call " + method + " on non-compatible class " + type);
}
MethodReader resolvedMethod = pe.classSource.resolve(method);
if (resolvedMethod != null) {
method = resolvedMethod.getReference();
}
Variable result = null;
if (method.getReturnType() != ValueType.VOID) {

View File

@ -20,7 +20,13 @@ import java.util.Set;
import org.teavm.dependency.DependencyInfo;
import org.teavm.dependency.MethodDependencyInfo;
import org.teavm.dependency.ValueDependencyInfo;
import org.teavm.model.*;
import org.teavm.model.BasicBlock;
import org.teavm.model.ClassReader;
import org.teavm.model.ClassReaderSource;
import org.teavm.model.Instruction;
import org.teavm.model.MethodHolder;
import org.teavm.model.MethodReference;
import org.teavm.model.Program;
import org.teavm.model.instructions.InvocationType;
import org.teavm.model.instructions.InvokeInstruction;

View File

@ -324,7 +324,7 @@ public class Inlining {
Set<MethodReference> implementations = new HashSet<>();
for (String className : inference.classesOf(invoke.getInstance().getIndex())) {
MethodReference rawMethod = new MethodReference(className, invoke.getMethod().getDescriptor());
MethodReader resolvedMethod = dependencyInfo.getClassSource().resolve(rawMethod);
MethodReader resolvedMethod = dependencyInfo.getClassSource().resolveImplementation(rawMethod);
if (resolvedMethod != null) {
implementations.add(resolvedMethod.getReference());
}

View File

@ -90,7 +90,7 @@ public class AsyncMethodFinder {
}
}
for (MethodReference method : asyncMethods) {
addOverridenToFamily(method);
addOverriddenToFamily(method);
}
for (String clsName : classSource.getClassNames()) {
ClassReader cls = classSource.get(clsName);
@ -175,11 +175,11 @@ public class AsyncMethodFinder {
}
}
private class CallStack {
static class CallStack {
MethodReference method;
CallStack next;
public CallStack(MethodReference method, CallStack next) {
CallStack(MethodReference method, CallStack next) {
this.method = method;
this.next = next;
}
@ -196,14 +196,14 @@ public class AsyncMethodFinder {
}
}
private void addOverridenToFamily(MethodReference methodRef) {
private void addOverriddenToFamily(MethodReference methodRef) {
asyncFamilyMethods.put(methodRef, true);
ClassReader cls = classSource.get(methodRef.getClassName());
if (cls == null) {
return;
}
for (MethodReference overridenMethod : findOverriddenMethods(cls, methodRef)) {
addOverridenToFamily(overridenMethod);
for (MethodReference overriddenMethod : findOverriddenMethods(cls, methodRef)) {
addOverriddenToFamily(overriddenMethod);
}
}
@ -225,8 +225,8 @@ public class AsyncMethodFinder {
if (cls == null) {
return false;
}
for (MethodReference overridenMethod : findOverriddenMethods(cls, methodRef)) {
if (addToFamily(overridenMethod)) {
for (MethodReference overriddenMethod : findOverriddenMethods(cls, methodRef)) {
if (addToFamily(overriddenMethod)) {
return true;
}
}
@ -241,11 +241,11 @@ public class AsyncMethodFinder {
parents.addAll(cls.getInterfaces());
Set<MethodReference> visited = new HashSet<>();
Set<MethodReference> overriden = new HashSet<>();
Set<MethodReference> overridden = new HashSet<>();
for (String parent : parents) {
findOverriddenMethods(new MethodReference(parent, methodRef.getDescriptor()), overriden, visited);
findOverriddenMethods(new MethodReference(parent, methodRef.getDescriptor()), overridden, visited);
}
return overriden;
return overridden;
}
private void findOverriddenMethods(MethodReference methodRef, Set<MethodReference> result,

View File

@ -19,7 +19,6 @@ import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Queue;
@ -48,7 +47,7 @@ public class AsyncProgramSplitter {
private List<Part> parts = new ArrayList<>();
private Map<Instruction, Integer> partMap = new HashMap<>();
private ClassReaderSource classSource;
private Set<MethodReference> asyncMethods = new HashSet<>();
private Set<MethodReference> asyncMethods;
private Program program;
public AsyncProgramSplitter(ClassReaderSource classSource, Set<MethodReference> asyncMethods) {

View File

@ -19,6 +19,7 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.teavm.dependency.DependencyInfo;
import org.teavm.dependency.MethodDependencyInfo;
import org.teavm.diagnostics.Diagnostics;
import org.teavm.model.*;
import org.teavm.model.instructions.*;
@ -31,20 +32,20 @@ public class MissingItemsProcessor {
private MethodHolder methodHolder;
private Program program;
private Collection<String> achievableClasses;
private Collection<MethodReference> achievableMethods;
private Collection<MethodReference> reachableMethods;
private Collection<FieldReference> achievableFields;
public MissingItemsProcessor(DependencyInfo dependencyInfo, Diagnostics diagnostics) {
this.dependencyInfo = dependencyInfo;
this.diagnostics = diagnostics;
achievableClasses = dependencyInfo.getReachableClasses();
achievableMethods = dependencyInfo.getReachableMethods();
reachableMethods = dependencyInfo.getReachableMethods();
achievableFields = dependencyInfo.getReachableFields();
}
public void processClass(ClassHolder cls) {
for (MethodHolder method : cls.getMethods()) {
if (achievableMethods.contains(method.getReference()) && method.getProgram() != null) {
if (reachableMethods.contains(method.getReference()) && method.getProgram() != null) {
processMethod(method);
}
}
@ -148,9 +149,14 @@ public class MissingItemsProcessor {
if (!checkClass(location, method.getClassName())) {
return false;
}
if (!achievableMethods.contains(method) || !dependencyInfo.getMethod(method).isMissing()) {
if (!reachableMethods.contains(method)) {
return true;
}
MethodDependencyInfo methodDep = dependencyInfo.getMethod(method);
if (!methodDep.isMissing() || !methodDep.isUsed()) {
return true;
}
diagnostics.error(new CallLocation(methodHolder.getReference(), location), "Method {{m0}} was not found",
method);
emitExceptionThrow(location, NoSuchMethodError.class.getName(), "Method not found: " + method);

View File

@ -398,6 +398,17 @@ public class TeaVM implements TeaVMHost, ServiceRepository {
return cutClasses;
}
int index = 0;
for (String className : dependency.getReachableClasses()) {
ClassReader clsReader = dependency.getClassSource().get(className);
if (clsReader != null) {
linker.prepare(dependency, clsReader);
}
}
if (wasCancelled()) {
return cutClasses;
}
for (String className : dependency.getReachableClasses()) {
ClassReader clsReader = dependency.getClassSource().get(className);
if (clsReader == null) {
@ -522,11 +533,11 @@ public class TeaVM implements TeaVMHost, ServiceRepository {
method.setProgram(optimizedProgram);
}
private class MethodOptimizationContextImpl implements MethodOptimizationContext {
class MethodOptimizationContextImpl implements MethodOptimizationContext {
private MethodReader method;
private ClassReaderSource classSource;
public MethodOptimizationContextImpl(MethodReader method, ClassReaderSource classSource) {
MethodOptimizationContextImpl(MethodReader method, ClassReaderSource classSource) {
this.method = method;
this.classSource = classSource;
}

View File

@ -371,4 +371,26 @@ public class VMTest {
super(ONE);
}
}
@Test
public void indirectDefaultMethod() {
PathJoint o = new PathJoint();
assertEquals("SecondPath.foo", o.foo());
}
interface FirstPath {
default String foo() {
return "FirstPath.foo";
}
}
interface SecondPath extends FirstPath {
@Override
default String foo() {
return "SecondPath.foo";
}
}
class PathJoint implements FirstPath, SecondPath {
}
}

View File

@ -16,6 +16,7 @@
package org.teavm.junit;
import com.gargoylesoftware.htmlunit.BrowserVersion;
import com.gargoylesoftware.htmlunit.Page;
import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.WebWindow;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
@ -69,7 +70,10 @@ class HtmlUnitRunStrategy implements TestRunStrategy {
}
private void cleanUp() {
page.get().cleanUp();
Page p = page.get();
if (p != null) {
p.cleanUp();
}
for (WebWindow window : webClient.get().getWebWindows()) {
window.getJobManager().removeAllJobs();
}