diff --git a/teavm-jso-impl/src/main/java/org/teavm/jso/plugin/JSBodyAstEmitter.java b/teavm-jso-impl/src/main/java/org/teavm/jso/plugin/JSBodyAstEmitter.java index c85531481..0683f027d 100644 --- a/teavm-jso-impl/src/main/java/org/teavm/jso/plugin/JSBodyAstEmitter.java +++ b/teavm-jso-impl/src/main/java/org/teavm/jso/plugin/JSBodyAstEmitter.java @@ -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 diff --git a/teavm-jso-impl/src/main/java/org/teavm/jso/plugin/JSBodyInlineUtil.java b/teavm-jso-impl/src/main/java/org/teavm/jso/plugin/JSBodyInlineUtil.java new file mode 100644 index 000000000..509387e30 --- /dev/null +++ b/teavm-jso-impl/src/main/java/org/teavm/jso/plugin/JSBodyInlineUtil.java @@ -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 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; + } + } +} diff --git a/teavm-jso-impl/src/main/java/org/teavm/jso/plugin/JSBodyRepository.java b/teavm-jso-impl/src/main/java/org/teavm/jso/plugin/JSBodyRepository.java index bc24b1114..9f81cb6ea 100644 --- a/teavm-jso-impl/src/main/java/org/teavm/jso/plugin/JSBodyRepository.java +++ b/teavm-jso-impl/src/main/java/org/teavm/jso/plugin/JSBodyRepository.java @@ -29,4 +29,5 @@ class JSBodyRepository { public final Map emitters = new HashMap<>(); public final Map methodMap = new HashMap<>(); public final Set processedMethods = new HashSet<>(); + public final Set inlineMethods = new HashSet<>(); } diff --git a/teavm-jso-impl/src/main/java/org/teavm/jso/plugin/JavascriptNativeProcessor.java b/teavm-jso-impl/src/main/java/org/teavm/jso/plugin/JavascriptNativeProcessor.java index a8fc3233b..ff2dbcd13 100644 --- a/teavm-jso-impl/src/main/java/org/teavm/jso/plugin/JavascriptNativeProcessor.java +++ b/teavm-jso-impl/src/main/java/org/teavm/jso/plugin/JavascriptNativeProcessor.java @@ -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);