Don't generate virtual wrappers for methods that aren't ever called virtually

This commit is contained in:
Alexey Andreev 2018-10-04 11:55:34 +03:00
parent 681e21ecca
commit b66053f5ce
21 changed files with 240 additions and 35 deletions

View File

@ -0,0 +1,33 @@
/*
* Copyright 2018 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.classlib.impl;
import org.teavm.backend.javascript.spi.VirtualMethodContributor;
import org.teavm.backend.javascript.spi.VirtualMethodContributorContext;
import org.teavm.model.ClassReader;
import org.teavm.model.MethodReference;
import org.teavm.platform.PlatformAnnotationProvider;
public class AnnotationVirtualMethods implements VirtualMethodContributor {
@Override
public boolean isVirtual(VirtualMethodContributorContext context, MethodReference methodRef) {
ClassReader cls = context.getClassSource().get(methodRef.getClassName());
if (cls == null || !cls.getInterfaces().contains(PlatformAnnotationProvider.class.getName())) {
return false;
}
return methodRef.getName().equals("getAnnotations");
}
}

View File

@ -46,6 +46,7 @@ public class JCLPlugin implements TeaVMPlugin {
TeaVMJavaScriptHost jsExtension = host.getExtension(TeaVMJavaScriptHost.class);
if (jsExtension != null) {
jsExtension.add(loadServicesMethod, serviceLoaderSupp);
jsExtension.addVirtualMethods(new AnnotationVirtualMethods());
}
JavacSupport javacSupport = new JavacSupport();

View File

@ -267,17 +267,19 @@ public class ClassGenerator implements Generator, Injector, DependencyPlugin {
if (method.getResultType() != ValueType.VOID) {
writer.append("return ");
}
if (method.hasModifier(ElementModifier.STATIC)) {
writer.appendMethodBody(method.getReference());
} else {
writer.append("obj.").appendMethod(method.getDescriptor());
}
writer.appendMethodBody(method.getReference());
writer.append('(');
boolean first = true;
if (!method.hasModifier(ElementModifier.STATIC)) {
writer.append("obj").ws();
first = false;
}
for (int i = 0; i < method.parameterCount(); ++i) {
if (i > 0) {
if (!first) {
writer.append(',').ws();
}
first = false;
int index = i;
unboxIfNecessary(writer, method.parameterType(i), () -> writer.append("args[" + index + "]"));
}

View File

@ -24,7 +24,6 @@ import org.teavm.dependency.DependencyPlugin;
import org.teavm.dependency.MethodDependency;
import org.teavm.model.CallLocation;
import org.teavm.model.ClassReader;
import org.teavm.model.MethodDescriptor;
import org.teavm.model.MethodReference;
import org.teavm.model.ValueType;
@ -40,13 +39,18 @@ public class ArrayNativeGenerator implements Generator, DependencyPlugin {
public void methodReached(DependencyAgent agent, MethodDependency method, CallLocation location) {
switch (method.getReference().getName()) {
case "getLength":
achieveGetLength(agent, method);
reachGetLength(agent, method);
break;
case "newInstance":
method.getVariable(1).getClassValueNode().addConsumer(t -> {
String arrayTypeName = t.getName().startsWith("[")
? t.getName()
: ValueType.object(t.getName()).toString();
String arrayTypeName;
if (t.getName().startsWith("[")) {
arrayTypeName = t.getName();
} else if (t.getName().startsWith("~")) {
arrayTypeName = t.getName().substring(1);
} else {
arrayTypeName = ValueType.object(t.getName()).toString();
}
method.getResult().propagate(agent.getType("[" + arrayTypeName));
});
break;
@ -81,14 +85,13 @@ public class ArrayNativeGenerator implements Generator, DependencyPlugin {
String array = context.getParameterName(1);
writer.append("if (" + array + " === null || " + array + ".constructor.$meta.item === undefined) {")
.softNewLine().indent();
String clsName = "java.lang.IllegalArgumentException";
MethodDescriptor cons = new MethodDescriptor("<init>", ValueType.VOID);
writer.append("$rt_throw(").appendClass(clsName).append(".").appendMethod(cons).append("());").softNewLine();
MethodReference cons = new MethodReference("java.lang.IllegalArgumentException", "<init>", ValueType.VOID);
writer.append("$rt_throw(").append(writer.getNaming().getNameForInit(cons)).append("());").softNewLine();
writer.outdent().append("}").softNewLine();
writer.append("return " + array + ".data.length;").softNewLine();
}
private void achieveGetLength(final DependencyAgent agent, final MethodDependency method) {
private void reachGetLength(final DependencyAgent agent, final MethodDependency method) {
method.getVariable(1).addConsumer(type -> {
if (!type.getName().startsWith("[")) {
MethodReference cons = new MethodReference(IllegalArgumentException.class, "<init>", void.class);

View File

@ -49,6 +49,8 @@ import org.teavm.backend.javascript.spi.GeneratedBy;
import org.teavm.backend.javascript.spi.Generator;
import org.teavm.backend.javascript.spi.InjectedBy;
import org.teavm.backend.javascript.spi.Injector;
import org.teavm.backend.javascript.spi.VirtualMethodContributor;
import org.teavm.backend.javascript.spi.VirtualMethodContributorContext;
import org.teavm.debugging.information.DebugInformationEmitter;
import org.teavm.debugging.information.DummyDebugInformationEmitter;
import org.teavm.debugging.information.SourceLocation;
@ -104,6 +106,7 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost {
private final Set<MethodReference> asyncMethods = new HashSet<>();
private final Set<MethodReference> asyncFamilyMethods = new HashSet<>();
private ClassInitializerInsertionTransformer clinitInsertionTransformer;
private List<VirtualMethodContributor> customVirtualMethods = new ArrayList<>();
@Override
public List<ClassHolderTransformer> getTransformers() {
@ -194,19 +197,35 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost {
@Override
public void contributeDependencies(DependencyAnalyzer dependencyAnalyzer) {
dependencyAnalyzer.linkMethod(new MethodReference(Class.class.getName(), "getClass",
ValueType.object("org.teavm.platform.PlatformClass"), ValueType.parse(Class.class)), null).use();
dependencyAnalyzer.linkMethod(new MethodReference(String.class, "<init>", char[].class, void.class),
null).use();
dependencyAnalyzer.linkMethod(new MethodReference(String.class, "getChars", int.class, int.class, char[].class,
int.class, void.class), null).use();
MethodDependency dep;
dep = dependencyAnalyzer.linkMethod(new MethodReference(Class.class.getName(), "getClass",
ValueType.object("org.teavm.platform.PlatformClass"), ValueType.parse(Class.class)), null);
dep.getVariable(0).propagate(dependencyAnalyzer.getType("org.teavm.platform.PlatformClass"));
dep.getResult().propagate(dependencyAnalyzer.getType("java.lang.Class"));
dep.use();
dep = dependencyAnalyzer.linkMethod(new MethodReference(String.class, "<init>", char[].class, void.class),
null);
dep.getVariable(0).propagate(dependencyAnalyzer.getType("java.lang.String"));
dep.getVariable(1).propagate(dependencyAnalyzer.getType("[C"));
dep.use();
dep = dependencyAnalyzer.linkMethod(new MethodReference(String.class, "getChars", int.class, int.class,
char[].class, int.class, void.class), null);
dep.getVariable(0).propagate(dependencyAnalyzer.getType("java.lang.String"));
dep.getVariable(3).propagate(dependencyAnalyzer.getType("[C"));
dep.use();
MethodDependency internDep = dependencyAnalyzer.linkMethod(new MethodReference(String.class, "intern",
String.class), null);
internDep.getVariable(0).propagate(dependencyAnalyzer.getType("java.lang.String"));
internDep.use();
dependencyAnalyzer.linkMethod(new MethodReference(String.class, "length", int.class), null).use();
dep = dependencyAnalyzer.linkMethod(new MethodReference(String.class, "length", int.class), null);
dep.getVariable(0).propagate(dependencyAnalyzer.getType("java.lang.String"));
dep.use();
dependencyAnalyzer.linkMethod(new MethodReference(Object.class, "clone", Object.class), null).use();
dependencyAnalyzer.linkMethod(new MethodReference(Thread.class, "currentThread", Thread.class), null).use();
dependencyAnalyzer.linkMethod(new MethodReference(Thread.class, "getMainThread", Thread.class), null).use();
@ -264,10 +283,12 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost {
if (debugEmitterToUse == null) {
debugEmitterToUse = new DummyDebugInformationEmitter();
}
VirtualMethodContributorContext virtualMethodContributorContext = new VirtualMethodContributorContextImpl(
classes);
RenderingContext renderingContext = new RenderingContext(debugEmitterToUse,
controller.getUnprocessedClassSource(), classes,
controller.getClassLoader(), controller.getServices(), controller.getProperties(), naming,
controller.getDependencyInfo());
controller.getDependencyInfo(), m -> isVirtual(virtualMethodContributorContext, m));
renderingContext.setMinifying(minifying);
Renderer renderer = new Renderer(sourceWriter, asyncMethods, asyncFamilyMethods,
controller.getDiagnostics(), renderingContext);
@ -505,8 +526,38 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost {
return new String[] { PlatformMarkers.JAVASCRIPT };
}
@Override
public void addVirtualMethods(VirtualMethodContributor virtualMethods) {
customVirtualMethods.add(virtualMethods);
}
@Override
public boolean isAsyncSupported() {
return true;
}
private boolean isVirtual(VirtualMethodContributorContext context, MethodReference method) {
if (controller.isVirtual(method)) {
return true;
}
for (VirtualMethodContributor predicate : customVirtualMethods) {
if (predicate.isVirtual(context, method)) {
return true;
}
}
return false;
}
static class VirtualMethodContributorContextImpl implements VirtualMethodContributorContext {
private ClassReaderSource classSource;
VirtualMethodContributorContextImpl(ClassReaderSource classSource) {
this.classSource = classSource;
}
@Override
public ClassReaderSource getClassSource() {
return classSource;
}
};
}

View File

@ -18,6 +18,7 @@ package org.teavm.backend.javascript;
import java.util.function.Function;
import org.teavm.backend.javascript.spi.Generator;
import org.teavm.backend.javascript.spi.Injector;
import org.teavm.backend.javascript.spi.VirtualMethodContributor;
import org.teavm.model.MethodReference;
import org.teavm.vm.spi.RendererListener;
import org.teavm.vm.spi.TeaVMHostExtension;
@ -32,4 +33,6 @@ public interface TeaVMJavaScriptHost extends TeaVMHostExtension {
void addInjectorProvider(Function<ProviderContext, Injector> provider);
void add(RendererListener listener);
void addVirtualMethods(VirtualMethodContributor virtualMethods);
}

View File

@ -118,10 +118,6 @@ public class SourceWriter implements Appendable, LocationProvider {
return append(naming.getNameFor(method));
}
public SourceWriter appendMethod(String name, ValueType... params) throws NamingException, IOException {
return append(naming.getNameFor(new MethodDescriptor(name, params)));
}
public SourceWriter appendMethod(String name, Class<?>... params) throws NamingException, IOException {
return append(naming.getNameFor(new MethodDescriptor(name, params)));
}

View File

@ -741,6 +741,9 @@ public class Renderer implements RenderingManager {
writer.append("[");
boolean first = true;
for (MethodReference method : methods) {
if (!isVirtual(method)) {
continue;
}
debugEmitter.emitMethod(method.getDescriptor());
if (!first) {
writer.append(",").ws();
@ -1069,4 +1072,8 @@ public class Renderer implements RenderingManager {
this.value = value;
}
}
private boolean isVirtual(MethodReference method) {
return context.isVirtual(method);
}
}

View File

@ -25,6 +25,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.function.Predicate;
import org.teavm.backend.javascript.codegen.NamingStrategy;
import org.teavm.backend.javascript.spi.InjectedBy;
import org.teavm.backend.javascript.spi.Injector;
@ -50,6 +51,7 @@ public class RenderingContext {
private Properties properties;
private NamingStrategy naming;
private DependencyInfo dependencyInfo;
private Predicate<MethodReference> virtualPredicate;
private final Deque<LocationStackEntry> locationStack = new ArrayDeque<>();
private final Map<String, Integer> stringPoolMap = new HashMap<>();
private final List<String> stringPool = new ArrayList<>();
@ -60,7 +62,8 @@ public class RenderingContext {
public RenderingContext(DebugInformationEmitter debugEmitter,
ClassReaderSource initialClassSource, ListableClassReaderSource classSource,
ClassLoader classLoader, ServiceRepository services, Properties properties,
NamingStrategy naming, DependencyInfo dependencyInfo) {
NamingStrategy naming, DependencyInfo dependencyInfo,
Predicate<MethodReference> virtualPredicate) {
this.debugEmitter = debugEmitter;
this.initialClassSource = initialClassSource;
this.classSource = classSource;
@ -69,6 +72,7 @@ public class RenderingContext {
this.properties = properties;
this.naming = naming;
this.dependencyInfo = dependencyInfo;
this.virtualPredicate = virtualPredicate;
}
public ClassReaderSource getInitialClassSource() {
@ -107,6 +111,10 @@ public class RenderingContext {
return debugEmitter;
}
public boolean isVirtual(MethodReference method) {
return virtualPredicate.test(method);
}
public void pushLocation(TextLocation location) {
LocationStackEntry prevEntry = locationStack.peek();
if (location != null) {

View File

@ -0,0 +1,22 @@
/*
* Copyright 2018 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.backend.javascript.spi;
import org.teavm.model.MethodReference;
public interface VirtualMethodContributor {
boolean isVirtual(VirtualMethodContributorContext context, MethodReference methodRef);
}

View File

@ -0,0 +1,22 @@
/*
* Copyright 2018 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.backend.javascript.spi;
import org.teavm.model.ClassReaderSource;
public interface VirtualMethodContributorContext {
ClassReaderSource getClassSource();
}

View File

@ -112,7 +112,11 @@ public class DependencyAnalyzer implements DependencyInfo {
if (field != null && !field.getReference().equals(preimage)) {
return fieldCache.map(field.getReference());
}
return createFieldNode(preimage, field);
FieldDependency node = createFieldNode(preimage, field);
if (field != null && field.getInitialValue() instanceof String) {
node.getValue().propagate(getType("java.lang.String"));
}
return node;
});
classCache = new CachedMapper<>(this::createClassDependency);

View File

@ -403,6 +403,8 @@ class DependencyGraphBuilder {
sb.append(cst.toString());
}
node.getClassValueNode().propagate(dependencyAnalyzer.getType(sb.toString()));
} else {
node.getClassValueNode().propagate(dependencyAnalyzer.getType("~" + cst.toString()));
}
}
while (cst instanceof ValueType.Array) {

View File

@ -15,6 +15,7 @@
*/
package org.teavm.model.optimization;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import org.teavm.dependency.DependencyInfo;
@ -33,6 +34,8 @@ import org.teavm.model.instructions.InvokeInstruction;
public class Devirtualization {
private DependencyInfo dependency;
private ClassReaderSource classSource;
private Set<MethodReference> virtualMethods = new HashSet<>();
private Set<? extends MethodReference> readonlyVirtualMethods = Collections.unmodifiableSet(virtualMethods);
public Devirtualization(DependencyInfo dependency, ClassReaderSource classSource) {
this.dependency = dependency;
@ -61,6 +64,8 @@ public class Devirtualization {
if (implementations.size() == 1) {
invoke.setType(InvocationType.SPECIAL);
invoke.setMethod(implementations.iterator().next());
} else {
virtualMethods.addAll(implementations);
}
}
}
@ -84,4 +89,8 @@ public class Devirtualization {
}
return methods;
}
public Set<? extends MethodReference> getVirtualMethods() {
return readonlyVirtualMethods;
}
}

View File

@ -124,6 +124,7 @@ public class TeaVM implements TeaVMHost, ServiceRepository {
private ListableClassHolderSource writtenClasses;
private TeaVMTarget target;
private Map<Class<?>, TeaVMHostExtension> extensions = new HashMap<>();
private Set<? extends MethodReference> virtualMethods;
TeaVM(TeaVMBuilder builder) {
target = builder.target;
@ -457,6 +458,7 @@ public class TeaVM implements TeaVMHost, ServiceRepository {
return;
}
}
virtualMethods = devirtualization.getVirtualMethods();
}
private void inline(ListableClassHolderSource classes, DependencyInfo dependencyInfo) {
@ -692,5 +694,10 @@ public class TeaVM implements TeaVMHost, ServiceRepository {
public boolean isFriendlyToDebugger() {
return optimizationLevel == TeaVMOptimizationLevel.SIMPLE;
}
@Override
public boolean isVirtual(MethodReference method) {
return incremental || virtualMethods == null || virtualMethods.contains(method);
}
};
}

View File

@ -22,6 +22,7 @@ import org.teavm.common.ServiceRepository;
import org.teavm.dependency.DependencyInfo;
import org.teavm.diagnostics.Diagnostics;
import org.teavm.model.ClassReaderSource;
import org.teavm.model.MethodReference;
public interface TeaVMTargetController {
boolean wasCancelled();
@ -45,4 +46,6 @@ public interface TeaVMTargetController {
Map<? extends String, ? extends TeaVMEntryPoint> getEntryPoints();
Set<? extends String> getPreservedClasses();
boolean isVirtual(MethodReference method);
}

View File

@ -20,6 +20,8 @@ import java.util.HashMap;
import java.util.Map;
import org.teavm.backend.javascript.codegen.SourceWriter;
import org.teavm.backend.javascript.rendering.RenderingManager;
import org.teavm.backend.javascript.spi.VirtualMethodContributor;
import org.teavm.backend.javascript.spi.VirtualMethodContributorContext;
import org.teavm.model.AnnotationReader;
import org.teavm.model.ClassReader;
import org.teavm.model.FieldReader;
@ -27,16 +29,17 @@ import org.teavm.model.FieldReference;
import org.teavm.model.ListableClassReaderSource;
import org.teavm.model.MethodDescriptor;
import org.teavm.model.MethodReader;
import org.teavm.model.MethodReference;
import org.teavm.vm.BuildTarget;
import org.teavm.vm.spi.RendererListener;
class JSAliasRenderer implements RendererListener {
class JSAliasRenderer implements RendererListener, VirtualMethodContributor {
private static String variableChars = "abcdefghijklmnopqrstuvwxyz";
private SourceWriter writer;
private ListableClassReaderSource classSource;
@Override
public void begin(RenderingManager context, BuildTarget buildTarget) throws IOException {
public void begin(RenderingManager context, BuildTarget buildTarget) {
writer = context.getWriter();
classSource = context.getClassSource();
}
@ -172,4 +175,19 @@ class JSAliasRenderer implements RendererListener {
}
return sb.toString();
}
@Override
public boolean isVirtual(VirtualMethodContributorContext context, MethodReference methodRef) {
ClassReader classReader = context.getClassSource().get(methodRef.getClassName());
if (classReader == null) {
return false;
}
if (getFunctorField(classReader) != null) {
return true;
}
MethodReader methodReader = classReader.getMethod(methodRef.getDescriptor());
return methodReader != null && getPublicAlias(methodReader) != null;
}
}

View File

@ -35,12 +35,12 @@ public class JSOPlugin implements TeaVMPlugin {
JSAliasRenderer aliasRenderer = new JSAliasRenderer();
host.add(dependencyListener);
jsHost.add(aliasRenderer);
jsHost.addGeneratorProvider(new GeneratorAnnotationInstaller<>(new JSBodyGenerator(),
DynamicGenerator.class.getName()));
jsHost.addInjectorProvider(new GeneratorAnnotationInstaller<>(new JSBodyGenerator(),
DynamicInjector.class.getName()));
jsHost.addVirtualMethods(aliasRenderer);
TeaVMPluginUtil.handleNatives(host, JS.class);
}

View File

@ -19,6 +19,8 @@ import java.io.IOException;
import org.teavm.backend.javascript.codegen.SourceWriter;
import org.teavm.backend.javascript.spi.Generator;
import org.teavm.backend.javascript.spi.GeneratorContext;
import org.teavm.backend.javascript.spi.VirtualMethodContributor;
import org.teavm.backend.javascript.spi.VirtualMethodContributorContext;
import org.teavm.dependency.DependencyAgent;
import org.teavm.dependency.DependencyPlugin;
import org.teavm.dependency.MethodDependency;
@ -30,7 +32,7 @@ import org.teavm.model.MethodReference;
import org.teavm.model.ValueType;
import org.teavm.platform.async.AsyncCallback;
public class AsyncMethodGenerator implements Generator, DependencyPlugin {
public class AsyncMethodGenerator implements Generator, DependencyPlugin, VirtualMethodContributor {
private static final MethodReference completeMethod = new MethodReference(AsyncCallback.class, "complete",
Object.class, void.class);
private static final MethodReference errorMethod = new MethodReference(AsyncCallback.class, "error",
@ -122,4 +124,9 @@ public class AsyncMethodGenerator implements Generator, DependencyPlugin {
asyncMethod.use();
}
@Override
public boolean isVirtual(VirtualMethodContributorContext context, MethodReference methodRef) {
return methodRef.equals(completeMethod) || methodRef.equals(errorMethod);
}
}

View File

@ -25,7 +25,12 @@ import org.teavm.backend.javascript.spi.InjectorContext;
import org.teavm.dependency.DependencyAgent;
import org.teavm.dependency.DependencyPlugin;
import org.teavm.dependency.MethodDependency;
import org.teavm.model.*;
import org.teavm.model.CallLocation;
import org.teavm.model.ClassReader;
import org.teavm.model.MethodDescriptor;
import org.teavm.model.MethodReader;
import org.teavm.model.MethodReference;
import org.teavm.model.ValueType;
import org.teavm.platform.Platform;
import org.teavm.platform.PlatformClass;
import org.teavm.platform.PlatformRunnable;

View File

@ -43,7 +43,8 @@ public class PlatformPlugin implements TeaVMPlugin {
host.add(new ResourceTransformer());
host.add(new ResourceAccessorTransformer(host));
host.add(new ResourceAccessorDependencyListener());
host.getExtension(TeaVMJavaScriptHost.class).addGeneratorProvider(context -> {
TeaVMJavaScriptHost jsHost = host.getExtension(TeaVMJavaScriptHost.class);
jsHost.addGeneratorProvider(context -> {
ClassReader cls = context.getClassSource().get(context.getMethod().getClassName());
if (cls == null) {
return null;
@ -55,6 +56,7 @@ public class PlatformPlugin implements TeaVMPlugin {
return method.getAnnotations().get(Async.class.getName()) != null
? new AsyncMethodGenerator() : null;
});
jsHost.addVirtualMethods(new AsyncMethodGenerator());
} else if (!isBootstrap()) {
host.add(new StringAmplifierTransformer());
}