Trying to improve functors for better performance and for functor

persistence (see https://github.com/konsoletyper/teavm/issues/103)
This commit is contained in:
Alexey Andreev 2015-05-08 13:10:28 +04:00
parent 11d69f15c9
commit 25310a5082
8 changed files with 195 additions and 6 deletions

View File

@ -18,6 +18,7 @@ package org.teavm.jso;
import java.lang.reflect.Array; import java.lang.reflect.Array;
import java.util.Iterator; import java.util.Iterator;
import org.teavm.dependency.PluggableDependency; import org.teavm.dependency.PluggableDependency;
import org.teavm.javascript.spi.GeneratedBy;
import org.teavm.javascript.spi.InjectedBy; import org.teavm.javascript.spi.InjectedBy;
import org.teavm.jso.plugin.JSNativeGenerator; import org.teavm.jso.plugin.JSNativeGenerator;
@ -557,7 +558,7 @@ public final class JS {
@InjectedBy(JSNativeGenerator.class) @InjectedBy(JSNativeGenerator.class)
public static native void set(JSObject instance, JSObject index, JSObject obj); public static native void set(JSObject instance, JSObject index, JSObject obj);
@InjectedBy(JSNativeGenerator.class) @GeneratedBy(JSNativeGenerator.class)
@PluggableDependency(JSNativeGenerator.class) @PluggableDependency(JSNativeGenerator.class)
public static native JSObject function(JSObject instance, JSObject property); public static native JSObject function(JSObject instance, JSObject property);
} }

View File

@ -0,0 +1,31 @@
/*
* Copyright 2015 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.jso.plugin;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
*
* @author Alexey Andreev
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface FunctorImpl {
String value();
}

View File

@ -21,6 +21,8 @@ import org.teavm.dependency.*;
import org.teavm.javascript.ast.ConstantExpr; import org.teavm.javascript.ast.ConstantExpr;
import org.teavm.javascript.ast.Expr; import org.teavm.javascript.ast.Expr;
import org.teavm.javascript.ast.InvocationExpr; import org.teavm.javascript.ast.InvocationExpr;
import org.teavm.javascript.spi.Generator;
import org.teavm.javascript.spi.GeneratorContext;
import org.teavm.javascript.spi.Injector; import org.teavm.javascript.spi.Injector;
import org.teavm.javascript.spi.InjectorContext; import org.teavm.javascript.spi.InjectorContext;
import org.teavm.jso.JS; import org.teavm.jso.JS;
@ -33,7 +35,39 @@ import org.teavm.model.MethodReference;
* *
* @author Alexey Andreev * @author Alexey Andreev
*/ */
public class JSNativeGenerator implements Injector, DependencyPlugin { public class JSNativeGenerator implements Injector, DependencyPlugin, Generator {
@Override
public void generate(GeneratorContext context, SourceWriter writer, MethodReference methodRef)
throws IOException {
switch (methodRef.getName()) {
case "function":
writeFunction(context, writer);
break;
}
}
private void writeFunction(GeneratorContext context, SourceWriter writer) throws IOException {
String thisName = context.getParameterName(1);
String methodName = context.getParameterName(2);
writer.append("var name").ws().append('=').ws().append("'jso$functor$'").ws().append('+').ws()
.append(methodName).append(';').softNewLine();
writer.append("if").ws().append("(!").append(thisName).append("[name])").ws().append('{')
.indent().softNewLine();
writer.append("var fn").ws().append('=').ws().append("function()").ws().append('{')
.indent().softNewLine();
writer.append("return ").append(thisName).append('[').append(methodName).append(']')
.append(".apply(").append(thisName).append(',').ws().append("arguments);").softNewLine();
writer.outdent().append("};").softNewLine();
writer.append(thisName).append("[name]").ws().append('=').ws().append("function()").ws().append('{')
.indent().softNewLine();
writer.append("return fn;").softNewLine();
writer.outdent().append("};").softNewLine();
writer.outdent().append('}').softNewLine();
writer.append("return ").append(thisName).append("[name]();").softNewLine();
}
@Override @Override
public void generate(InjectorContext context, MethodReference methodRef) throws IOException { public void generate(InjectorContext context, MethodReference methodRef) throws IOException {
SourceWriter writer = context.getWriter(); SourceWriter writer = context.getWriter();

View File

@ -29,6 +29,7 @@ import org.teavm.vm.spi.RendererListener;
* @author Alexey Andreev * @author Alexey Andreev
*/ */
class JSOAliasRenderer implements RendererListener { class JSOAliasRenderer implements RendererListener {
private static String variableChars = "abcdefghijklmnopqrstuvwxyz";
private JSODependencyListener dependencyListener; private JSODependencyListener dependencyListener;
private SourceWriter writer; private SourceWriter writer;
@ -50,15 +51,64 @@ class JSOAliasRenderer implements RendererListener {
writer.append("(function()").ws().append("{").softNewLine().indent(); writer.append("(function()").ws().append("{").softNewLine().indent();
writer.append("var c;").softNewLine(); writer.append("var c;").softNewLine();
for (Map.Entry<String, ExposedClass> entry : dependencyListener.getExposedClasses().entrySet()) { for (Map.Entry<String, ExposedClass> entry : dependencyListener.getExposedClasses().entrySet()) {
if (entry.getValue().methods.isEmpty()) { ExposedClass cls = entry.getValue();
if (cls.methods.isEmpty()) {
continue; continue;
} }
writer.append("c").ws().append("=").ws().appendClass(entry.getKey()).append(".prototype;").softNewLine(); writer.append("c").ws().append("=").ws().appendClass(entry.getKey()).append(".prototype;").softNewLine();
for (Map.Entry<MethodDescriptor, String> aliasEntry : entry.getValue().methods.entrySet()) { for (Map.Entry<MethodDescriptor, String> aliasEntry : cls.methods.entrySet()) {
writer.append("c.").append(aliasEntry.getValue()).ws().append("=").ws().append("c.") writer.append("c.").append(aliasEntry.getValue()).ws().append("=").ws().append("c.")
.appendMethod(aliasEntry.getKey()).append(";").softNewLine(); .appendMethod(aliasEntry.getKey()).append(";").softNewLine();
} }
if (cls.functorField != null) {
writeFunctor(cls);
}
} }
writer.outdent().append("})();").newLine(); writer.outdent().append("})();").newLine();
} }
private void writeFunctor(ExposedClass cls) throws IOException {
String alias = cls.methods.get(cls.functorMethod);
if (alias == null) {
return;
}
writer.append("c.jso$functor$").append(alias).ws().append("=").ws().append("function()").ws().append("{")
.indent().softNewLine();
writer.append("if").ws().append("(!this.").appendField(cls.functorField).append(")").ws().append("{")
.indent().softNewLine();
writer.append("var self").ws().append('=').ws().append("this;").softNewLine();
writer.append("this.").appendField(cls.functorField).ws().append('=').ws().append("function(");
appendArguments(cls.functorMethod.parameterCount());
writer.append(")").ws().append('{').indent().softNewLine();
writer.append("return self.").appendMethod(cls.functorMethod).append('(');
appendArguments(cls.functorMethod.parameterCount());
writer.append(");").softNewLine();
writer.outdent().append("};").softNewLine();
writer.outdent().append("}").softNewLine();
writer.append("return this.").appendField(cls.functorField).append(';').softNewLine();
writer.outdent().append("};").softNewLine();
}
private void appendArguments(int count) throws IOException {
for (int i = 0; i < count; ++i) {
if (i > 0) {
writer.append(',').ws();
}
writer.append(variableName(i));
}
}
private String variableName(int index) {
StringBuilder sb = new StringBuilder();
sb.append(variableChars.charAt(index % variableChars.length()));
index /= variableChars.length();
if (index > 0) {
sb.append(index);
}
return sb.toString();
}
} }

View File

@ -60,6 +60,8 @@ class JSODependencyListener implements DependencyListener {
Map<MethodDescriptor, String> inheritedMethods = new HashMap<>(); Map<MethodDescriptor, String> inheritedMethods = new HashMap<>();
Map<MethodDescriptor, String> methods = new HashMap<>(); Map<MethodDescriptor, String> methods = new HashMap<>();
Set<String> implementedInterfaces = new HashSet<>(); Set<String> implementedInterfaces = new HashSet<>();
FieldReference functorField;
MethodDescriptor functorMethod;
} }
private ExposedClass getExposedClass(String name) { private ExposedClass getExposedClass(String name) {
@ -92,6 +94,14 @@ class JSODependencyListener implements DependencyListener {
methodDep.use(); methodDep.use();
} }
} }
if (exposedCls.functorField == null) {
FieldReader functorField = cls.getField("$$jso_functor$$");
if (functorField != null) {
exposedCls.functorField = functorField.getReference();
AnnotationReader annot = cls.getAnnotations().get(FunctorImpl.class.getName());
exposedCls.functorMethod = MethodDescriptor.parse(annot.getValue("value").getString());
}
}
return exposedCls; return exposedCls;
} }

View File

@ -39,6 +39,12 @@ public class JSObjectClassTransformer implements ClassHolderTransformer {
if (processor.isNativeImplementation(cls.getName())) { if (processor.isNativeImplementation(cls.getName())) {
processor.makeSync(cls); processor.makeSync(cls);
} }
MethodReference functorMethod = processor.isFunctor(cls.getName());
if (functorMethod != null) {
if (processor.isFunctor(cls.getParent()) == null) {
processor.addFunctorField(cls, functorMethod);
}
}
for (MethodHolder method : cls.getMethods().toArray(new MethodHolder[0])) { for (MethodHolder method : cls.getMethods().toArray(new MethodHolder[0])) {
if (method.getAnnotations().get(JSBody.class.getName()) != null) { if (method.getAnnotations().get(JSBody.class.getName()) != null) {
processor.processJSBody(cls, method); processor.processJSBody(cls, method);

View File

@ -59,6 +59,48 @@ class JavascriptNativeProcessor {
this.diagnostics = diagnostics; this.diagnostics = diagnostics;
} }
public MethodReference isFunctor(String className) {
if (!nativeRepos.isJavaScriptImplementation(className)) {
return null;
}
ClassReader cls = classSource.get(className);
if (cls == null) {
return null;
}
Map<MethodDescriptor, MethodReference> methods = new HashMap<>();
getFunctorMethods(className, new HashSet<String>(), methods);
if (methods.size() == 1) {
return methods.values().iterator().next();
}
return null;
}
private void getFunctorMethods(String className, Set<String> visited,
Map<MethodDescriptor, MethodReference> methods) {
if (!visited.add(className)) {
return;
}
ClassReader cls = classSource.get(className);
if (cls == null) {
return;
}
if (cls.getAnnotations().get(JSFunctor.class.getName()) != null && isProperFunctor(cls)) {
MethodReference method = cls.getMethods().iterator().next().getReference();
if (!methods.containsKey(method.getDescriptor())) {
methods.put(method.getDescriptor(), method);
}
}
if (cls.getParent() != null && !cls.getParent().equals(cls.getName())) {
getFunctorMethods(cls.getParent(), visited, methods);
}
for (String iface : cls.getInterfaces()) {
getFunctorMethods(iface, visited, methods);
}
}
public void processClass(ClassHolder cls) { public void processClass(ClassHolder cls) {
Set<MethodDescriptor> preservedMethods = new HashSet<>(); Set<MethodDescriptor> preservedMethods = new HashSet<>();
for (String iface : cls.getInterfaces()) { for (String iface : cls.getInterfaces()) {
@ -126,6 +168,17 @@ class JavascriptNativeProcessor {
} }
} }
public void addFunctorField(ClassHolder cls, MethodReference method) {
FieldHolder field = new FieldHolder("$$jso_functor$$");
field.setLevel(AccessLevel.PUBLIC);
field.setType(ValueType.parse(JSObject.class));
cls.addField(field);
AnnotationHolder annot = new AnnotationHolder(FunctorImpl.class.getName());
annot.getValues().put("value", new AnnotationValue(method.getDescriptor().toString()));
cls.getAnnotations().add(annot);
}
public void makeSync(ClassHolder cls) { public void makeSync(ClassHolder cls) {
Set<MethodDescriptor> methods = new HashSet<>(); Set<MethodDescriptor> methods = new HashSet<>();
findInheritedMethods(cls, methods, new HashSet<String>()); findInheritedMethods(cls, methods, new HashSet<String>());
@ -664,8 +717,12 @@ class JavascriptNativeProcessor {
return wrap(var, type, location.getSourceLocation()); return wrap(var, type, location.getSourceLocation());
} }
private boolean isProperFunctor(ClassReader type) {
return type.hasModifier(ElementModifier.INTERFACE) && type.getMethods().size() == 1;
}
private Variable wrapFunctor(CallLocation location, Variable var, ClassReader type) { private Variable wrapFunctor(CallLocation location, Variable var, ClassReader type) {
if (!type.hasModifier(ElementModifier.INTERFACE) || type.getMethods().size() != 1) { if (!isProperFunctor(type)) {
diagnostics.error(location, "Wrong functor: {{c0}}", type.getName()); diagnostics.error(location, "Wrong functor: {{c0}}", type.getName());
return var; return var;
} }

View File

@ -120,7 +120,7 @@
<targetDirectory>${project.build.directory}/generated/js/teavm</targetDirectory> <targetDirectory>${project.build.directory}/generated/js/teavm</targetDirectory>
<mainClass>org.teavm.samples.benchmark.teavm.BenchmarkStarter</mainClass> <mainClass>org.teavm.samples.benchmark.teavm.BenchmarkStarter</mainClass>
<runtime>SEPARATE</runtime> <runtime>SEPARATE</runtime>
<minifying>true</minifying> <minifying>false</minifying>
<debugInformationGenerated>true</debugInformationGenerated> <debugInformationGenerated>true</debugInformationGenerated>
</configuration> </configuration>
</execution> </execution>