Add inlining of JSBody scripts

This commit is contained in:
Alexey Andreev 2015-09-25 16:51:06 +03:00
parent 49f05cbb6e
commit 9b7760c639
4 changed files with 142 additions and 7 deletions

View File

@ -16,7 +16,7 @@
package org.teavm.jso.plugin;
import java.io.IOException;
import org.mozilla.javascript.ast.AstRoot;
import org.mozilla.javascript.ast.AstNode;
import org.teavm.codegen.SourceWriter;
import org.teavm.javascript.spi.GeneratorContext;
import org.teavm.javascript.spi.InjectorContext;
@ -28,10 +28,10 @@ import org.teavm.model.MethodReference;
*/
class JSBodyAstEmitter implements JSBodyEmitter {
private boolean isStatic;
private AstRoot ast;
private AstNode ast;
private String[] parameterNames;
public JSBodyAstEmitter(boolean isStatic, AstRoot ast, String[] parameterNames) {
public JSBodyAstEmitter(boolean isStatic, AstNode ast, String[] parameterNames) {
this.isStatic = isStatic;
this.ast = ast;
this.parameterNames = parameterNames;
@ -51,7 +51,6 @@ class JSBodyAstEmitter implements JSBodyEmitter {
}
astWriter.hoist(ast);
astWriter.print(ast);
context.getWriter().softNewLine();
}
@Override

View File

@ -0,0 +1,125 @@
/*
* 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.util.HashMap;
import java.util.Map;
import org.mozilla.javascript.Token;
import org.mozilla.javascript.ast.AstNode;
import org.mozilla.javascript.ast.AstRoot;
import org.mozilla.javascript.ast.ExpressionStatement;
import org.mozilla.javascript.ast.Name;
import org.mozilla.javascript.ast.NodeVisitor;
import org.mozilla.javascript.ast.ReturnStatement;
import org.mozilla.javascript.ast.ThrowStatement;
import org.teavm.model.MethodReference;
import org.teavm.model.ValueType;
/**
*
* @author Alexey Andreev
*/
final class JSBodyInlineUtil {
private static final int COMPLEXITY_THRESHOLD = 20;
private JSBodyInlineUtil() {
}
public static AstNode isSuitableForInlining(MethodReference method, String[] parameters, AstRoot ast) {
AstNode statement = isSingleStatement(ast);
if (statement == null) {
return null;
}
AstNode expression = getExpression(method, statement);
if (expression == null) {
return null;
}
ComplexityCounter complexityCounter = new ComplexityCounter();
expression.visit(complexityCounter);
if (complexityCounter.getComplexity() > COMPLEXITY_THRESHOLD) {
return null;
}
VariableUsageCounter usageCounter = new VariableUsageCounter();
expression.visit(usageCounter);
for (String param : parameters) {
if (usageCounter.getUsage(param) > 1) {
return null;
}
}
return expression;
}
private static AstNode getExpression(MethodReference method, AstNode statement) {
if (method.getReturnType() == ValueType.VOID) {
if (statement instanceof ExpressionStatement) {
return ((ExpressionStatement) statement).getExpression();
} else if (statement instanceof ThrowStatement) {
return ((ThrowStatement) statement).getExpression();
}
} else {
if (statement instanceof ReturnStatement) {
return ((ReturnStatement) statement).getReturnValue();
}
}
return null;
}
private static AstNode isSingleStatement(AstNode ast) {
if (ast.getFirstChild() == null || ast.getFirstChild().getNext() != null) {
return null;
}
if (ast.getFirstChild().getType() == Token.BLOCK) {
return isSingleStatement((AstNode) ast.getFirstChild());
}
return (AstNode) ast.getFirstChild();
}
static class ComplexityCounter implements NodeVisitor {
private int complexity;
public int getComplexity() {
return complexity;
}
@Override
public boolean visit(AstNode node) {
++complexity;
return true;
}
}
static class VariableUsageCounter implements NodeVisitor {
private Map<String, Integer> usages = new HashMap<>();
public int getUsage(String varName) {
return usages.computeIfAbsent(varName, i -> 0);
}
@Override
public boolean visit(AstNode node) {
if (node instanceof Name) {
Name name = (Name) node;
if (!name.isLocalName()) {
String id = name.getIdentifier();
usages.put(id, getUsage(id));
}
}
return true;
}
}
}

View File

@ -29,4 +29,5 @@ class JSBodyRepository {
public final Map<MethodReference, JSBodyEmitter> emitters = new HashMap<>();
public final Map<MethodReference, MethodReference> methodMap = new HashMap<>();
public final Set<MethodReference> processedMethods = new HashSet<>();
public final Set<MethodReference> inlineMethods = new HashSet<>();
}

View File

@ -27,9 +27,11 @@ import java.util.Set;
import java.util.function.Function;
import org.mozilla.javascript.CompilerEnvirons;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.ast.AstNode;
import org.mozilla.javascript.ast.AstRoot;
import org.teavm.diagnostics.Diagnostics;
import org.teavm.javascript.spi.GeneratedBy;
import org.teavm.javascript.spi.InjectedBy;
import org.teavm.javascript.spi.Sync;
import org.teavm.jso.JSBody;
import org.teavm.jso.JSFunctor;
@ -570,8 +572,14 @@ class JavascriptNativeProcessor {
repository.emitters.put(proxyMethod, new JSBodyBloatedEmitter(isStatic, proxyMethod,
script, parameterNames));
} else {
repository.emitters.put(proxyMethod, new JSBodyAstEmitter(isStatic, rootNode,
parameterNames));
AstNode expr = JSBodyInlineUtil.isSuitableForInlining(methodToProcess.getReference(),
parameterNames, rootNode);
if (expr != null) {
repository.inlineMethods.add(methodToProcess.getReference());
} else {
expr = rootNode;
}
repository.emitters.put(proxyMethod, new JSBodyAstEmitter(isStatic, expr, parameterNames));
}
repository.methodMap.put(methodToProcess.getReference(), proxyMethod);
}
@ -586,7 +594,9 @@ class JavascriptNativeProcessor {
MethodHolder proxyMethod = new MethodHolder(proxyRef.getDescriptor());
proxyMethod.getModifiers().add(ElementModifier.NATIVE);
proxyMethod.getModifiers().add(ElementModifier.STATIC);
AnnotationHolder generatorAnnot = new AnnotationHolder(GeneratedBy.class.getName());
boolean inline = repository.inlineMethods.contains(methodRef);
AnnotationHolder generatorAnnot = new AnnotationHolder(inline
? InjectedBy.class.getName() : GeneratedBy.class.getName());
generatorAnnot.getValues().put("value",
new AnnotationValue(ValueType.parse(JSBodyGenerator.class)));
proxyMethod.getAnnotations().add(generatorAnnot);