From 5d5fb47ca896058fe4f75d52d300c22507587e70 Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Thu, 26 Oct 2023 21:17:08 +0200 Subject: [PATCH] JS: add conditional intrinsic to JS template engine, use it to get rid of remaining generated methods in runtime --- .../backend/javascript/ast/AstVisitor.java | 470 ++++++++++++++++++ .../javascript/rendering/AstVisitor.java | 441 ---------------- .../javascript/rendering/RuntimeRenderer.java | 148 +----- .../templating/TemplatingAstTransformer.java | 180 +++++++ .../org/teavm/backend/javascript/intern.js | 63 +-- .../org/teavm/backend/javascript/runtime.js | 46 ++ 6 files changed, 734 insertions(+), 614 deletions(-) create mode 100644 core/src/main/java/org/teavm/backend/javascript/ast/AstVisitor.java delete mode 100644 core/src/main/java/org/teavm/backend/javascript/rendering/AstVisitor.java create mode 100644 core/src/main/java/org/teavm/backend/javascript/templating/TemplatingAstTransformer.java diff --git a/core/src/main/java/org/teavm/backend/javascript/ast/AstVisitor.java b/core/src/main/java/org/teavm/backend/javascript/ast/AstVisitor.java new file mode 100644 index 000000000..f2a4fd093 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/javascript/ast/AstVisitor.java @@ -0,0 +1,470 @@ +/* + * Copyright 2023 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.ast; + +import java.util.List; +import java.util.function.BiConsumer; +import java.util.function.Function; +import java.util.function.Supplier; +import org.mozilla.javascript.Token; +import org.mozilla.javascript.ast.ArrayComprehension; +import org.mozilla.javascript.ast.ArrayComprehensionLoop; +import org.mozilla.javascript.ast.ArrayLiteral; +import org.mozilla.javascript.ast.AstNode; +import org.mozilla.javascript.ast.AstRoot; +import org.mozilla.javascript.ast.Block; +import org.mozilla.javascript.ast.BreakStatement; +import org.mozilla.javascript.ast.CatchClause; +import org.mozilla.javascript.ast.ConditionalExpression; +import org.mozilla.javascript.ast.ContinueStatement; +import org.mozilla.javascript.ast.DoLoop; +import org.mozilla.javascript.ast.ElementGet; +import org.mozilla.javascript.ast.EmptyExpression; +import org.mozilla.javascript.ast.EmptyStatement; +import org.mozilla.javascript.ast.ExpressionStatement; +import org.mozilla.javascript.ast.ForInLoop; +import org.mozilla.javascript.ast.ForLoop; +import org.mozilla.javascript.ast.FunctionCall; +import org.mozilla.javascript.ast.FunctionNode; +import org.mozilla.javascript.ast.GeneratorExpression; +import org.mozilla.javascript.ast.GeneratorExpressionLoop; +import org.mozilla.javascript.ast.IfStatement; +import org.mozilla.javascript.ast.InfixExpression; +import org.mozilla.javascript.ast.KeywordLiteral; +import org.mozilla.javascript.ast.LabeledStatement; +import org.mozilla.javascript.ast.LetNode; +import org.mozilla.javascript.ast.Name; +import org.mozilla.javascript.ast.NewExpression; +import org.mozilla.javascript.ast.NumberLiteral; +import org.mozilla.javascript.ast.ObjectLiteral; +import org.mozilla.javascript.ast.ObjectProperty; +import org.mozilla.javascript.ast.ParenthesizedExpression; +import org.mozilla.javascript.ast.PropertyGet; +import org.mozilla.javascript.ast.RegExpLiteral; +import org.mozilla.javascript.ast.ReturnStatement; +import org.mozilla.javascript.ast.Scope; +import org.mozilla.javascript.ast.StringLiteral; +import org.mozilla.javascript.ast.SwitchCase; +import org.mozilla.javascript.ast.SwitchStatement; +import org.mozilla.javascript.ast.ThrowStatement; +import org.mozilla.javascript.ast.TryStatement; +import org.mozilla.javascript.ast.UnaryExpression; +import org.mozilla.javascript.ast.UpdateExpression; +import org.mozilla.javascript.ast.VariableDeclaration; +import org.mozilla.javascript.ast.VariableInitializer; +import org.mozilla.javascript.ast.WhileLoop; + +public class AstVisitor { + protected AstNode replacement; + protected boolean hasReplacement; + + public final void visit(AstNode node) { + switch (node.getType()) { + case Token.SCRIPT: + visit((AstRoot) node); + break; + case Token.CALL: + case Token.NEW: + visit((FunctionCall) node); + break; + case Token.FUNCTION: + visit((FunctionNode) node); + break; + case Token.ARRAYCOMP: + visit((ArrayComprehension) node); + break; + case Token.GETPROP: + visit((PropertyGet) node); + break; + case Token.GENEXPR: + visit((GeneratorExpression) node); + break; + case Token.NUMBER: + visit((NumberLiteral) node); + break; + case Token.STRING: + visit((StringLiteral) node); + break; + case Token.TRUE: + case Token.FALSE: + case Token.THIS: + case Token.NULL: + visit((KeywordLiteral) node); + break; + case Token.NAME: + visit((Name) node); + break; + case Token.REGEXP: + visit((RegExpLiteral) node); + break; + case Token.OBJECTLIT: + visit((ObjectLiteral) node); + break; + case Token.ARRAYLIT: + visit((ArrayLiteral) node); + break; + case Token.BLOCK: + if (node instanceof Block) { + visit((Block) node); + } else if (node instanceof Scope) { + visit((Scope) node); + } + break; + case Token.HOOK: + visit((ConditionalExpression) node); + break; + case Token.GETELEM: + visit((ElementGet) node); + break; + case Token.LETEXPR: + visit((LetNode) node); + break; + case Token.LP: + visit((ParenthesizedExpression) node); + break; + case Token.EMPTY: + if (node instanceof EmptyExpression) { + visit((EmptyExpression) node); + } else if (node instanceof EmptyStatement) { + visit((EmptyStatement) node); + } + break; + case Token.EXPR_VOID: + case Token.EXPR_RESULT: + if (node instanceof ExpressionStatement) { + visit((ExpressionStatement) node); + } else if (node instanceof LabeledStatement) { + visit((LabeledStatement) node); + } + break; + case Token.BREAK: + visit((BreakStatement) node); + break; + case Token.CONTINUE: + visit((ContinueStatement) node); + break; + case Token.RETURN: + visit((ReturnStatement) node); + break; + case Token.DO: + visit((DoLoop) node); + break; + case Token.FOR: + if (node instanceof ForInLoop) { + visit((ForInLoop) node); + } else if (node instanceof ForLoop) { + visit((ForLoop) node); + } + break; + case Token.IF: + visit((IfStatement) node); + break; + case Token.SWITCH: + visit((SwitchStatement) node); + break; + case Token.THROW: + visit((ThrowStatement) node); + break; + case Token.TRY: + visit((TryStatement) node); + break; + case Token.CONST: + case Token.VAR: + case Token.LET: + visit((VariableDeclaration) node); + break; + case Token.WHILE: + visit((WhileLoop) node); + break; + default: + if (node instanceof InfixExpression) { + visit((InfixExpression) node); + } else if (node instanceof UnaryExpression) { + visit((UnaryExpression) node); + } else if (node instanceof UpdateExpression) { + visit((UpdateExpression) node); + } + break; + } + } + + protected final void visitMany(List nodes) { + for (var i = 0; i < nodes.size(); ++i) { + var node = nodes.get(i); + visit(node); + if (hasReplacement) { + nodes.set(i, replacement); + } + hasReplacement = false; + replacement = null; + } + } + + protected final void visitChildren(AstNode node) { + for (var child = node.getFirstChild(); child != null;) { + var next = child.getNext(); + visit((AstNode) child); + if (hasReplacement) { + if (replacement != null) { + node.replaceChild(child, replacement); + } else { + node.removeChild(child); + } + replacement = null; + hasReplacement = false; + } + child = next; + } + } + + protected final void visitProperty(T owner, Function getter, + BiConsumer setter) { + visitProperty(owner, getter, setter, null); + } + + protected final void visitProperty(T owner, Function getter, + BiConsumer setter, Supplier defaultValue) { + var node = getter.apply(owner); + if (node != null) { + visit(node); + if (hasReplacement) { + if (replacement == null && defaultValue != null) { + replacement = defaultValue.get(); + } + //noinspection unchecked + setter.accept(owner, (S) replacement); + } + replacement = null; + hasReplacement = false; + } + } + + public void visit(AstRoot node) { + visitChildren(node); + } + + public void visit(Block node) { + visitChildren(node); + } + + public void visit(Scope node) { + visitChildren(node); + } + + public void visit(LabeledStatement node) { + visitProperty(node, LabeledStatement::getStatement, LabeledStatement::setStatement, EMPTY_DEFAULT); + } + + public void visit(BreakStatement node) { + } + + public void visit(ContinueStatement node) { + } + + public void visit(ReturnStatement node) { + visitProperty(node, ReturnStatement::getReturnValue, ReturnStatement::setReturnValue); + } + + public void visit(ThrowStatement node) { + visitProperty(node, ThrowStatement::getExpression, ThrowStatement::setExpression, + () -> new KeywordLiteral(0, 0, Token.NULL)); + } + + public void visit(DoLoop node) { + visitProperty(node, DoLoop::getBody, DoLoop::setBody, EMPTY_DEFAULT); + visitProperty(node, DoLoop::getCondition, DoLoop::setCondition, NULL_DEFAULT); + } + + public void visit(ForInLoop node) { + visitProperty(node, ForInLoop::getIterator, ForInLoop::setIterator, NULL_DEFAULT); + visitProperty(node, ForInLoop::getIteratedObject, ForInLoop::setIteratedObject, NULL_DEFAULT); + visitProperty(node, ForInLoop::getBody, ForInLoop::setBody, EMPTY_DEFAULT); + } + + public void visit(ForLoop node) { + visitProperty(node, ForLoop::getInitializer, ForLoop::setInitializer, EMPTY_EXPR_DEFAULT); + visitProperty(node, ForLoop::getCondition, ForLoop::setCondition, EMPTY_EXPR_DEFAULT); + visitProperty(node, ForLoop::getIncrement, ForLoop::setIncrement, EMPTY_EXPR_DEFAULT); + visitProperty(node, ForLoop::getBody, ForLoop::setBody, EMPTY_DEFAULT); + } + + public void visit(WhileLoop node) { + visitProperty(node, WhileLoop::getCondition, WhileLoop::setCondition, NULL_DEFAULT); + visitProperty(node, WhileLoop::getBody, WhileLoop::setBody, EMPTY_DEFAULT); + } + + public void visit(IfStatement node) { + visitProperty(node, IfStatement::getCondition, IfStatement::setCondition, NULL_DEFAULT); + visitProperty(node, IfStatement::getThenPart, IfStatement::setThenPart, EMPTY_DEFAULT); + visitProperty(node, IfStatement::getElsePart, IfStatement::setElsePart); + } + + public void visit(SwitchStatement node) { + visitProperty(node, SwitchStatement::getExpression, SwitchStatement::setExpression, NULL_DEFAULT); + for (var sc : node.getCases()) { + visitProperty(sc, SwitchCase::getExpression, SwitchCase::setExpression); + if (sc.getStatements() != null) { + visitMany(sc.getStatements()); + } + } + } + + public void visit(TryStatement node) { + visitProperty(node, TryStatement::getTryBlock, TryStatement::setTryBlock, NULL_DEFAULT); + for (var cc : node.getCatchClauses()) { + visitProperty(cc, CatchClause::getVarName, CatchClause::setVarName); + visitProperty(cc, CatchClause::getCatchCondition, CatchClause::setCatchCondition); + if (cc.getBody() != null) { + visitChildren(cc.getBody()); + } + } + visitProperty(node, TryStatement::getFinallyBlock, TryStatement::setFinallyBlock); + } + + public void visit(VariableDeclaration node) { + for (var variable : node.getVariables()) { + visit(variable); + } + } + + public void visit(VariableInitializer node) { + visitProperty(node, VariableInitializer::getTarget, VariableInitializer::setTarget); + visitProperty(node, VariableInitializer::getInitializer, VariableInitializer::setInitializer); + } + + public void visit(ExpressionStatement node) { + visitProperty(node, ExpressionStatement::getExpression, ExpressionStatement::setExpression, NULL_DEFAULT); + } + + public void visit(ElementGet node) { + visitProperty(node, ElementGet::getTarget, ElementGet::setTarget, NULL_DEFAULT); + visitProperty(node, ElementGet::getElement, ElementGet::setElement, NULL_DEFAULT); + } + + public void visit(PropertyGet node) { + visitProperty(node, PropertyGet::getTarget, PropertyGet::setTarget, NULL_DEFAULT); + visitProperty(node, PropertyGet::getProperty, PropertyGet::setProperty); + } + + public void visit(FunctionCall node) { + visitProperty(node, FunctionCall::getTarget, FunctionCall::setTarget); + visitMany(node.getArguments()); + if (node instanceof NewExpression) { + var newExpr = (NewExpression) node; + visitProperty(newExpr, NewExpression::getInitializer, NewExpression::setInitializer); + } + } + + public void visit(ConditionalExpression node) { + visitProperty(node, ConditionalExpression::getTestExpression, ConditionalExpression::setTestExpression); + visitProperty(node, ConditionalExpression::getTrueExpression, ConditionalExpression::setTrueExpression); + visitProperty(node, ConditionalExpression::getFalseExpression, ConditionalExpression::setFalseExpression); + } + + public void visit(ArrayComprehension node) { + for (var loop : node.getLoops()) { + visitProperty(loop, ArrayComprehensionLoop::getIterator, ArrayComprehensionLoop::setIterator); + visitProperty(loop, ArrayComprehensionLoop::getIteratedObject, ArrayComprehensionLoop::setIteratedObject); + } + visitProperty(node, ArrayComprehension::getFilter, ArrayComprehension::setFilter); + visitProperty(node, ArrayComprehension::getResult, ArrayComprehension::setResult); + } + + public void visit(GeneratorExpression node) { + for (var loop : node.getLoops()) { + visitProperty(loop, GeneratorExpressionLoop::getIterator, GeneratorExpressionLoop::setIterator); + visitProperty(loop, GeneratorExpressionLoop::getIteratedObject, + GeneratorExpressionLoop::setIteratedObject); + } + visitProperty(node, GeneratorExpression::getFilter, GeneratorExpression::setFilter); + visitProperty(node, GeneratorExpression::getResult, GeneratorExpression::setResult); + } + + public void visit(NumberLiteral node) { + } + + public void visit(StringLiteral node) { + } + + public void visit(KeywordLiteral node) { + } + + public void visit(Name node) { + } + + public void visit(RegExpLiteral node) { + } + + public void visit(ArrayLiteral node) { + visitMany(node.getElements()); + } + + public void visit(ObjectLiteral node) { + if (node.getElements() != null) { + for (var element : node.getElements()) { + visit(element); + } + } + } + + public void visit(ObjectProperty node) { + visitProperty(node, ObjectProperty::getLeft, ObjectProperty::setLeft); + visitProperty(node, ObjectProperty::getRight, ObjectProperty::setRight); + } + + public void visit(FunctionNode node) { + visitProperty(node, FunctionNode::getFunctionName, FunctionNode::setFunctionName); + visitMany(node.getParams()); + visitChildren(node.getBody()); + } + + public void visit(LetNode node) { + visitProperty(node, LetNode::getVariables, LetNode::setVariables); + visitProperty(node, LetNode::getBody, LetNode::setBody); + } + + public void visit(ParenthesizedExpression node) { + visitProperty(node, ParenthesizedExpression::getExpression, ParenthesizedExpression::setExpression); + } + + public void visit(EmptyExpression node) { + } + + public void visit(EmptyStatement node) { + } + + public void visit(InfixExpression node) { + visitProperty(node, InfixExpression::getLeft, InfixExpression::setLeft); + visitProperty(node, InfixExpression::getRight, InfixExpression::setRight); + } + + public void visit(UnaryExpression node) { + visitProperty(node, UnaryExpression::getOperand, UnaryExpression::setOperand); + } + + public void visit(UpdateExpression node) { + visitProperty(node, UpdateExpression::getOperand, UpdateExpression::setOperand); + } + + protected final void replaceWith(AstNode node) { + hasReplacement = true; + replacement = node; + } + + private static final Supplier NULL_DEFAULT = () -> new KeywordLiteral(0, 0, Token.NULL); + private static final Supplier EMPTY_DEFAULT = () -> new EmptyStatement(0, 0); + private static final Supplier EMPTY_EXPR_DEFAULT = () -> new EmptyExpression(0, 0); +} diff --git a/core/src/main/java/org/teavm/backend/javascript/rendering/AstVisitor.java b/core/src/main/java/org/teavm/backend/javascript/rendering/AstVisitor.java deleted file mode 100644 index 54232cddc..000000000 --- a/core/src/main/java/org/teavm/backend/javascript/rendering/AstVisitor.java +++ /dev/null @@ -1,441 +0,0 @@ -/* - * Copyright 2019 konsoletyper. - * - * 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.rendering; - -import org.mozilla.javascript.Node; -import org.mozilla.javascript.Token; -import org.mozilla.javascript.ast.ArrayComprehension; -import org.mozilla.javascript.ast.ArrayComprehensionLoop; -import org.mozilla.javascript.ast.ArrayLiteral; -import org.mozilla.javascript.ast.AstNode; -import org.mozilla.javascript.ast.AstRoot; -import org.mozilla.javascript.ast.Block; -import org.mozilla.javascript.ast.BreakStatement; -import org.mozilla.javascript.ast.CatchClause; -import org.mozilla.javascript.ast.ConditionalExpression; -import org.mozilla.javascript.ast.ContinueStatement; -import org.mozilla.javascript.ast.DoLoop; -import org.mozilla.javascript.ast.ElementGet; -import org.mozilla.javascript.ast.EmptyExpression; -import org.mozilla.javascript.ast.EmptyStatement; -import org.mozilla.javascript.ast.ExpressionStatement; -import org.mozilla.javascript.ast.ForInLoop; -import org.mozilla.javascript.ast.ForLoop; -import org.mozilla.javascript.ast.FunctionCall; -import org.mozilla.javascript.ast.FunctionNode; -import org.mozilla.javascript.ast.GeneratorExpression; -import org.mozilla.javascript.ast.GeneratorExpressionLoop; -import org.mozilla.javascript.ast.IfStatement; -import org.mozilla.javascript.ast.InfixExpression; -import org.mozilla.javascript.ast.LabeledStatement; -import org.mozilla.javascript.ast.LetNode; -import org.mozilla.javascript.ast.Name; -import org.mozilla.javascript.ast.NewExpression; -import org.mozilla.javascript.ast.NumberLiteral; -import org.mozilla.javascript.ast.ObjectLiteral; -import org.mozilla.javascript.ast.ObjectProperty; -import org.mozilla.javascript.ast.ParenthesizedExpression; -import org.mozilla.javascript.ast.PropertyGet; -import org.mozilla.javascript.ast.RegExpLiteral; -import org.mozilla.javascript.ast.ReturnStatement; -import org.mozilla.javascript.ast.Scope; -import org.mozilla.javascript.ast.StringLiteral; -import org.mozilla.javascript.ast.SwitchCase; -import org.mozilla.javascript.ast.SwitchStatement; -import org.mozilla.javascript.ast.ThrowStatement; -import org.mozilla.javascript.ast.TryStatement; -import org.mozilla.javascript.ast.UnaryExpression; -import org.mozilla.javascript.ast.VariableDeclaration; -import org.mozilla.javascript.ast.VariableInitializer; -import org.mozilla.javascript.ast.WhileLoop; - -public class AstVisitor { - public void accept(AstNode node) { - switch (node.getType()) { - case Token.SCRIPT: - visitRoot((AstRoot) node); - break; - case Token.CALL: - case Token.NEW: - visitFunctionCall((FunctionCall) node); - break; - case Token.FUNCTION: - visitFunction((FunctionNode) node); - break; - case Token.ARRAYCOMP: - visitArrayComprehension((ArrayComprehension) node); - break; - case Token.GETPROP: - visitPropertyGet((PropertyGet) node); - break; - case Token.GENEXPR: - visitGenerator((GeneratorExpression) node); - break; - case Token.NUMBER: - visitNumber((NumberLiteral) node); - break; - case Token.STRING: - visitString((StringLiteral) node); - break; - case Token.TRUE: - visitTrue(node); - break; - case Token.FALSE: - visitFalse(node); - break; - case Token.THIS: - visitThis(node); - break; - case Token.NULL: - visitNull(node); - break; - case Token.NAME: - visitName((Name) node); - break; - case Token.REGEXP: - visitRegexp((RegExpLiteral) node); - break; - case Token.OBJECTLIT: - visitObjectLiteral((ObjectLiteral) node); - break; - case Token.ARRAYLIT: - visitArrayLiteral((ArrayLiteral) node); - break; - case Token.BLOCK: - if (node instanceof Block) { - visitBlock((Block) node); - } else if (node instanceof Scope) { - visitScope((Scope) node); - } - break; - case Token.HOOK: - visitConditionalExpr((ConditionalExpression) node); - break; - case Token.GETELEM: - visitElementGet((ElementGet) node); - break; - case Token.LETEXPR: - visitLet((LetNode) node); - break; - case Token.LP: - visitParenthesized((ParenthesizedExpression) node); - break; - case Token.EMPTY: - if (node instanceof EmptyStatement) { - visitEmpty((EmptyStatement) node); - } else { - visitEmpty((EmptyExpression) node); - } - break; - case Token.EXPR_VOID: - case Token.EXPR_RESULT: - if (node instanceof ExpressionStatement) { - visitExpressionStatement((ExpressionStatement) node); - } else if (node instanceof LabeledStatement) { - visitLabeledStatement((LabeledStatement) node); - } - break; - case Token.BREAK: - visitBreak((BreakStatement) node); - break; - case Token.CONTINUE: - visitContinue((ContinueStatement) node); - break; - case Token.RETURN: - visitReturn((ReturnStatement) node); - break; - case Token.DO: - visitDo((DoLoop) node); - break; - case Token.FOR: - if (node instanceof ForInLoop) { - visitForIn((ForInLoop) node); - } else if (node instanceof ForLoop) { - visitFor((ForLoop) node); - } - break; - case Token.IF: - visitIf((IfStatement) node); - break; - case Token.SWITCH: - visitSwitch((SwitchStatement) node); - break; - case Token.THROW: - visitThrow((ThrowStatement) node); - break; - case Token.TRY: - visitTry((TryStatement) node); - break; - case Token.CONST: - case Token.VAR: - case Token.LET: - visitVariableDeclaration((VariableDeclaration) node); - break; - case Token.WHILE: - visitWhile((WhileLoop) node); - break; - default: - if (node instanceof InfixExpression) { - visitInfix((InfixExpression) node); - } else if (node instanceof UnaryExpression) { - visitUnary((UnaryExpression) node); - } - break; - } - } - - protected void visitRoot(AstRoot node) { - for (Node child : node) { - accept((AstNode) child); - } - } - - protected void visitBlock(Block node) { - for (Node child = node.getFirstChild(); child != null; child = child.getNext()) { - accept((AstNode) child); - } - } - - protected void visitScope(Scope node) { - for (Node child = node.getFirstChild(); child != null; child = child.getNext()) { - accept((AstNode) child); - } - } - - protected void visitLabeledStatement(LabeledStatement node) { - accept(node.getStatement()); - } - - protected void visitBreak(BreakStatement node) { - } - - protected void visitContinue(ContinueStatement node) { - } - - protected void visitReturn(ReturnStatement node) { - if (node.getReturnValue() != null) { - accept(node.getReturnValue()); - } - } - - protected void visitThrow(ThrowStatement node) { - accept(node.getExpression()); - } - - protected void visitDo(DoLoop node) { - accept(node.getBody()); - accept(node.getCondition()); - } - - protected void visitForIn(ForInLoop node) { - accept(node.getIterator()); - accept(node.getIteratedObject()); - accept(node.getBody()); - } - - protected void visitFor(ForLoop node) { - accept(node.getInitializer()); - accept(node.getCondition()); - accept(node.getIncrement()); - accept(node.getBody()); - } - - protected void visitWhile(WhileLoop node) { - accept(node.getCondition()); - accept(node.getBody()); - } - - protected void visitIf(IfStatement node) { - accept(node.getCondition()); - accept(node.getThenPart()); - if (node.getElsePart() != null) { - accept(node.getElsePart()); - } - } - - protected void visitSwitch(SwitchStatement node) { - accept(node.getExpression()); - for (SwitchCase sc : node.getCases()) { - if (sc.getExpression() != null) { - accept(sc.getExpression()); - } - if (sc.getStatements() != null) { - for (AstNode stmt : sc.getStatements()) { - accept(stmt); - } - } - } - } - - protected void visitTry(TryStatement node) { - accept(node.getTryBlock()); - for (CatchClause cc : node.getCatchClauses()) { - if (cc.getCatchCondition() != null) { - accept(cc.getCatchCondition()); - } - accept(cc.getBody()); - } - if (node.getFinallyBlock() != null) { - accept(node.getFinallyBlock()); - } - } - - protected void visitVariableDeclaration(VariableDeclaration node) { - for (int i = 1; i < node.getVariables().size(); ++i) { - visitVariableInitializer(node.getVariables().get(i)); - } - } - - protected void visitVariableInitializer(VariableInitializer node) { - accept(node.getTarget()); - if (node.getInitializer() != null) { - accept(node.getInitializer()); - } - } - - protected void visitExpressionStatement(ExpressionStatement node) { - accept(node.getExpression()); - } - - protected void visitElementGet(ElementGet node) { - accept(node.getTarget()); - accept(node.getElement()); - } - - protected void visitPropertyGet(PropertyGet node) { - accept(node.getLeft()); - accept(node.getRight()); - } - - protected void visitFunctionCall(FunctionCall node) { - accept(node.getTarget()); - for (AstNode arg : node.getArguments()) { - accept(arg); - } - if (node instanceof NewExpression) { - NewExpression newExpr = (NewExpression) node; - if (newExpr.getInitializer() != null) { - accept(newExpr.getInitializer()); - } - } - } - - protected void visitConditionalExpr(ConditionalExpression node) { - accept(node.getTestExpression()); - accept(node.getTrueExpression()); - accept(node.getFalseExpression()); - } - - protected void visitArrayComprehension(ArrayComprehension node) { - for (ArrayComprehensionLoop loop : node.getLoops()) { - accept(loop.getIterator()); - accept(loop.getIteratedObject()); - } - if (node.getFilter() != null) { - accept(node.getFilter()); - } - accept(node.getResult()); - } - - protected void visitGenerator(GeneratorExpression node) { - for (GeneratorExpressionLoop loop : node.getLoops()) { - accept(loop.getIterator()); - accept(loop.getIteratedObject()); - } - if (node.getFilter() != null) { - accept(node.getFilter()); - } - accept(node.getResult()); - } - - protected void visitNumber(NumberLiteral node) { - } - - protected void visitString(StringLiteral node) { - } - - protected void visitThis(AstNode node) { - } - - protected void visitTrue(AstNode node) { - } - - protected void visitFalse(AstNode node) { - } - - protected void visitNull(AstNode node) { - } - - protected void visitEmpty(EmptyStatement node) { - } - - protected void visitEmpty(EmptyExpression node) { - } - - protected void visitName(Name node) { - } - - protected void visitRegexp(RegExpLiteral node) { - } - - protected void visitArrayLiteral(ArrayLiteral node) { - for (AstNode element : node.getElements()) { - accept(element); - } - } - - protected void visitObjectLiteral(ObjectLiteral node) { - if (node.getElements() != null) { - for (ObjectProperty property : node.getElements()) { - visitObjectProperty(property); - } - } - } - - protected void visitObjectProperty(ObjectProperty node) { - accept(node.getLeft()); - accept(node.getRight()); - } - - protected void visitFunction(FunctionNode node) { - if (node.getFunctionName() != null) { - accept(node.getFunctionName()); - } - for (AstNode param : node.getParams()) { - accept(param); - } - - accept(node.getBody()); - } - - protected void visitLet(LetNode node) { - accept(node.getVariables()); - accept(node.getBody()); - } - - protected void visitParenthesized(ParenthesizedExpression node) { - accept(node.getExpression()); - } - - protected void visitUnary(UnaryExpression node) { - accept(node.getOperand()); - } - - protected void visitInfix(InfixExpression node) { - accept(node.getLeft()); - accept(node.getRight()); - } -} diff --git a/core/src/main/java/org/teavm/backend/javascript/rendering/RuntimeRenderer.java b/core/src/main/java/org/teavm/backend/javascript/rendering/RuntimeRenderer.java index 641faebff..388a12434 100644 --- a/core/src/main/java/org/teavm/backend/javascript/rendering/RuntimeRenderer.java +++ b/core/src/main/java/org/teavm/backend/javascript/rendering/RuntimeRenderer.java @@ -24,32 +24,12 @@ import org.mozilla.javascript.CompilerEnvirons; import org.mozilla.javascript.Context; import org.mozilla.javascript.ast.AstRoot; import org.teavm.backend.javascript.codegen.SourceWriter; +import org.teavm.backend.javascript.templating.TemplatingAstTransformer; import org.teavm.backend.javascript.templating.TemplatingAstWriter; -import org.teavm.model.ClassReader; import org.teavm.model.ClassReaderSource; -import org.teavm.model.ElementModifier; -import org.teavm.model.MethodDescriptor; -import org.teavm.model.MethodReader; -import org.teavm.model.MethodReference; import org.teavm.vm.RenderingException; public class RuntimeRenderer { - private static final String STRING_CLASS = String.class.getName(); - private static final String THREAD_CLASS = Thread.class.getName(); - private static final String STE_CLASS = StackTraceElement.class.getName(); - - private static final MethodDescriptor STRING_INTERN_METHOD = new MethodDescriptor("intern", String.class); - private static final MethodDescriptor CURRENT_THREAD_METHOD = new MethodDescriptor("currentThread", - Thread.class); - private static final MethodReference STACK_TRACE_ELEM_INIT = new MethodReference(StackTraceElement.class, - "", String.class, String.class, String.class, int.class, void.class); - private static final MethodReference SET_STACK_TRACE_METHOD = new MethodReference(Throwable.class, - "setStackTrace", StackTraceElement[].class, void.class); - private static final MethodReference AIOOBE_INIT_METHOD = new MethodReference(ArrayIndexOutOfBoundsException.class, - "", void.class); - private static final MethodReference CCE_INIT_METHOD = new MethodReference(ClassCastException.class, - "", void.class); - private final ClassReaderSource classSource; private final SourceWriter writer; @@ -61,13 +41,7 @@ public class RuntimeRenderer { public void renderRuntime() throws RenderingException { try { renderHandWrittenRuntime("runtime.js"); - renderRuntimeThrowablecls(); - renderRuntimeIntern(); - renderRuntimeThreads(); - renderCreateStackTraceElement(); - renderSetStackTrace(); - renderThrowAIOOBE(); - renderThrowCCE(); + renderHandWrittenRuntime("intern.js"); } catch (IOException e) { throw new RenderingException("IO error", e); } @@ -76,6 +50,7 @@ public class RuntimeRenderer { public void renderHandWrittenRuntime(String name) throws IOException { AstRoot ast = parseRuntime(name); ast.visit(new StringConstantElimination()); + new TemplatingAstTransformer(classSource).visit(ast); var astWriter = new TemplatingAstWriter(writer); astWriter.hoist(ast); astWriter.print(ast); @@ -93,121 +68,4 @@ public class RuntimeRenderer { return factory.parse(reader, null, 0); } } - - private void renderRuntimeIntern() throws IOException { - if (!needInternMethod()) { - writer.append("function $rt_intern(str) {").indent().softNewLine(); - writer.append("return str;").softNewLine(); - writer.outdent().append("}").softNewLine(); - } else { - renderHandWrittenRuntime("intern.js"); - } - } - - private boolean needInternMethod() { - ClassReader cls = classSource.get(STRING_CLASS); - if (cls == null) { - return false; - } - MethodReader method = cls.getMethod(STRING_INTERN_METHOD); - return method != null && method.hasModifier(ElementModifier.NATIVE); - } - - private void renderRuntimeThrowablecls() throws IOException { - writer.append("function $rt_stecls()").ws().append("{").indent().softNewLine(); - writer.append("return "); - if (classSource.get(STE_CLASS) != null) { - writer.appendClass(STE_CLASS); - } else { - writer.appendClass("java.lang.Object"); - } - writer.append(";").softNewLine().outdent().append("}").newLine(); - } - - private void renderRuntimeThreads() throws IOException { - ClassReader threadCls = classSource.get(THREAD_CLASS); - MethodReader currentThreadMethod = threadCls != null ? threadCls.getMethod(CURRENT_THREAD_METHOD) : null; - boolean threadUsed = currentThreadMethod != null && currentThreadMethod.getProgram() != null; - - writer.append("function $rt_getThread()").ws().append("{").indent().softNewLine(); - if (threadUsed) { - writer.append("return ").appendMethodBody(Thread.class, "currentThread", Thread.class).append("();") - .softNewLine(); - } else { - writer.append("return null;").softNewLine(); - } - writer.outdent().append("}").newLine(); - - writer.append("function $rt_setThread(t)").ws().append("{").indent().softNewLine(); - if (threadUsed) { - writer.append("return ").appendMethodBody(Thread.class, "setCurrentThread", Thread.class, void.class) - .append("(t);").softNewLine(); - } - writer.outdent().append("}").newLine(); - } - - private void renderCreateStackTraceElement() throws IOException { - ClassReader cls = classSource.get(STACK_TRACE_ELEM_INIT.getClassName()); - MethodReader stackTraceElemInit = cls != null ? cls.getMethod(STACK_TRACE_ELEM_INIT.getDescriptor()) : null; - boolean supported = stackTraceElemInit != null && stackTraceElemInit.getProgram() != null; - - writer.append("function $rt_createStackElement(") - .append("className,").ws() - .append("methodName,").ws() - .append("fileName,").ws() - .append("lineNumber)").ws().append("{").indent().softNewLine(); - writer.append("return "); - if (supported) { - writer.appendInit(STACK_TRACE_ELEM_INIT); - writer.append("(className,").ws() - .append("methodName,").ws() - .append("fileName,").ws() - .append("lineNumber)"); - } else { - writer.append("null"); - } - writer.append(";").softNewLine(); - writer.outdent().append("}").newLine(); - } - - private void renderSetStackTrace() throws IOException { - ClassReader cls = classSource.get(SET_STACK_TRACE_METHOD.getClassName()); - MethodReader setStackTrace = cls != null ? cls.getMethod(SET_STACK_TRACE_METHOD.getDescriptor()) : null; - boolean supported = setStackTrace != null && setStackTrace.getProgram() != null; - - writer.append("function $rt_setStack(e,").ws().append("stack)").ws().append("{").indent().softNewLine(); - if (supported) { - writer.appendMethodBody(SET_STACK_TRACE_METHOD); - writer.append("(e,").ws().append("stack);").softNewLine(); - } - writer.outdent().append("}").newLine(); - } - - private void renderThrowAIOOBE() throws IOException { - writer.append("function $rt_throwAIOOBE()").ws().append("{").indent().softNewLine(); - - ClassReader cls = classSource.get(AIOOBE_INIT_METHOD.getClassName()); - if (cls != null) { - MethodReader method = cls.getMethod(AIOOBE_INIT_METHOD.getDescriptor()); - if (method != null && !method.hasModifier(ElementModifier.ABSTRACT)) { - writer.append("$rt_throw(").appendInit(AIOOBE_INIT_METHOD).append("());").softNewLine(); - } - } - - writer.outdent().append("}").newLine(); - } - - private void renderThrowCCE() throws IOException { - writer.append("function $rt_throwCCE()").ws().append("{").indent().softNewLine(); - - ClassReader cls = classSource.get(CCE_INIT_METHOD.getClassName()); - if (cls != null) { - MethodReader method = cls.getMethod(CCE_INIT_METHOD.getDescriptor()); - if (method != null && !method.hasModifier(ElementModifier.ABSTRACT)) { - writer.append("$rt_throw(").appendInit(CCE_INIT_METHOD).append("());").softNewLine(); - } - } - - writer.outdent().append("}").newLine(); - } } diff --git a/core/src/main/java/org/teavm/backend/javascript/templating/TemplatingAstTransformer.java b/core/src/main/java/org/teavm/backend/javascript/templating/TemplatingAstTransformer.java new file mode 100644 index 000000000..a160bc3db --- /dev/null +++ b/core/src/main/java/org/teavm/backend/javascript/templating/TemplatingAstTransformer.java @@ -0,0 +1,180 @@ +/* + * Copyright 2023 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.templating; + +import org.mozilla.javascript.Token; +import org.mozilla.javascript.ast.AstNode; +import org.mozilla.javascript.ast.Block; +import org.mozilla.javascript.ast.ConditionalExpression; +import org.mozilla.javascript.ast.FunctionCall; +import org.mozilla.javascript.ast.IfStatement; +import org.mozilla.javascript.ast.InfixExpression; +import org.mozilla.javascript.ast.KeywordLiteral; +import org.mozilla.javascript.ast.Name; +import org.mozilla.javascript.ast.StringLiteral; +import org.mozilla.javascript.ast.UnaryExpression; +import org.teavm.backend.javascript.ast.AstVisitor; +import org.teavm.model.ClassReaderSource; +import org.teavm.model.ElementModifier; +import org.teavm.model.MethodDescriptor; +import org.teavm.model.MethodReference; + +public class TemplatingAstTransformer extends AstVisitor { + private ClassReaderSource classes; + + public TemplatingAstTransformer(ClassReaderSource classes) { + this.classes = classes; + } + + @Override + public void visit(Block node) { + super.visit(node); + if (node.getFirstChild() == null) { + replaceWith(null); + } else if (node.getFirstChild() == node.getLastChild()) { + replaceWith((AstNode) node.getFirstChild()); + } + } + + @Override + public void visit(IfStatement node) { + super.visit(node); + if (node.getCondition().getType() == Token.TRUE) { + replaceWith(node.getThenPart()); + } else if (node.getCondition().getType() == Token.FALSE) { + replaceWith(node.getElsePart()); + } + } + + @Override + public void visit(ConditionalExpression node) { + super.visit(node); + if (node.getTestExpression().getType() == Token.TRUE) { + replaceWith(node.getTrueExpression()); + } else if (node.getTestExpression().getType() == Token.FALSE) { + replaceWith(node.getFalseExpression()); + } + } + + @Override + public void visit(InfixExpression node) { + super.visit(node); + if (node.getType() == Token.AND) { + if (node.getLeft().getType() == Token.FALSE) { + replaceWith(node.getLeft()); + } else if (node.getRight().getType() == Token.FALSE) { + replaceWith(node.getRight()); + } else if (node.getLeft().getType() == Token.TRUE) { + replaceWith(node.getRight()); + } else if (node.getRight().getType() == Token.TRUE) { + replaceWith(node.getLeft()); + } + } else if (node.getType() == Token.OR) { + if (node.getLeft().getType() == Token.TRUE) { + replaceWith(node.getLeft()); + } else if (node.getRight().getType() == Token.TRUE) { + replaceWith(node.getRight()); + } else if (node.getLeft().getType() == Token.FALSE) { + replaceWith(node.getRight()); + } else if (node.getRight().getType() == Token.FALSE) { + replaceWith(node.getLeft()); + } + } + } + + @Override + public void visit(UnaryExpression node) { + super.visit(node); + if (node.getType() == Token.NOT) { + if (node.getOperand().getType() == Token.TRUE) { + node.getOperand().setType(Token.FALSE); + } else if (node.getOperand().getType() == Token.FALSE) { + node.getOperand().setType(Token.TRUE); + } + } + } + + @Override + public void visit(FunctionCall node) { + super.visit(node); + if (node.getTarget() instanceof Name) { + var name = (Name) node.getTarget(); + if (name.getDefiningScope() == null) { + tryIntrinsicName(node, name.getIdentifier()); + } + } + } + + private void tryIntrinsicName(FunctionCall node, String identifier) { + switch (identifier) { + case "teavm_javaClassExists": + javaClassExists(node); + break; + case "teavm_javaMethodExists": + javaMethodExists(node); + break; + case "teavm_javaConstructorExists": + javaConstructorExists(node); + break; + } + } + + private void javaClassExists(FunctionCall node) { + if (node.getArguments().size() != 1) { + return; + } + var classArg = node.getArguments().get(0); + if (!(classArg instanceof StringLiteral)) { + return; + } + var className = ((StringLiteral) classArg).getValue(); + var exists = classes.get(className) != null; + replaceWith(new KeywordLiteral(0, 0, exists ? Token.TRUE : Token.FALSE)); + } + + private void javaMethodExists(FunctionCall node) { + if (node.getArguments().size() != 2) { + return; + } + var classArg = node.getArguments().get(0); + var methodArg = node.getArguments().get(1); + if (!(classArg instanceof StringLiteral) || !(methodArg instanceof StringLiteral)) { + return; + } + var className = ((StringLiteral) classArg).getValue(); + var methodName = ((StringLiteral) methodArg).getValue(); + var method = classes.resolveImplementation(new MethodReference(className, MethodDescriptor.parse(methodName))); + var exists = method != null && (method.getProgram() != null || method.hasModifier(ElementModifier.NATIVE)); + replaceWith(new KeywordLiteral(0, 0, exists ? Token.TRUE : Token.FALSE)); + } + + private void javaConstructorExists(FunctionCall node) { + if (node.getArguments().size() != 2) { + return; + } + var classArg = node.getArguments().get(0); + var methodArg = node.getArguments().get(1); + if (!(classArg instanceof StringLiteral) || !(methodArg instanceof StringLiteral)) { + return; + } + var className = ((StringLiteral) classArg).getValue(); + var methodName = ((StringLiteral) methodArg).getValue(); + var method = classes.resolveImplementation(new MethodReference(className, "", + MethodDescriptor.parseSignature(methodName))); + var exists = method != null && (method.getProgram() != null || method.hasModifier(ElementModifier.NATIVE)); + replaceWith(new KeywordLiteral(0, 0, exists ? Token.TRUE : Token.FALSE)); + } +} diff --git a/core/src/main/resources/org/teavm/backend/javascript/intern.js b/core/src/main/resources/org/teavm/backend/javascript/intern.js index 9805be5ce..6df4f1211 100644 --- a/core/src/main/resources/org/teavm/backend/javascript/intern.js +++ b/core/src/main/resources/org/teavm/backend/javascript/intern.js @@ -15,37 +15,44 @@ */ "use strict"; -var $rt_intern = function() { - var map = Object.create(null); +var $rt_intern +if (teavm_javaMethodExists("java.lang.String", "intern()Ljava/lang/String;")) { + $rt_intern = function() { + var map = Object.create(null); - var get; - if (typeof WeakRef !== 'undefined') { - var registry = new FinalizationRegistry(value => { - delete map[value]; - }); + var get; + if (typeof WeakRef !== 'undefined') { + var registry = new FinalizationRegistry(value => { + delete map[value]; + }); - get = function(str) { - var key = $rt_ustr(str); - var ref = map[key]; - var result = typeof ref !== 'undefined' ? ref.deref() : void 0; - if (typeof result !== 'object') { - result = str; - map[key] = new WeakRef(result); - registry.register(result, key); + get = function (str) { + var key = $rt_ustr(str); + var ref = map[key]; + var result = typeof ref !== 'undefined' ? ref.deref() : void 0; + if (typeof result !== 'object') { + result = str; + map[key] = new WeakRef(result); + registry.register(result, key); + } + return result; } - return result; - } - } else { - get = function(str) { - var key = $rt_ustr(str); - var result = map[key]; - if (typeof result !== 'object') { - result = str; - map[key] = result; + } else { + get = function (str) { + var key = $rt_ustr(str); + var result = map[key]; + if (typeof result !== 'object') { + result = str; + map[key] = result; + } + return result; } - return result; } + + return get; + }(); +} else { + $rt_intern = function(str) { + return str; } - - return get; -}(); +} \ No newline at end of file diff --git a/core/src/main/resources/org/teavm/backend/javascript/runtime.js b/core/src/main/resources/org/teavm/backend/javascript/runtime.js index bbc30bbdf..21ab7bc39 100644 --- a/core/src/main/resources/org/teavm/backend/javascript/runtime.js +++ b/core/src/main/resources/org/teavm/backend/javascript/runtime.js @@ -967,4 +967,50 @@ function $rt_throwableMessage(t) { } function $rt_throwableCause(t) { return teavm_javaMethod("java.lang.Throwable", "getCause()Ljava/lang/Throwable;")(t); +} +function $rt_stecls() { + if (teavm_javaClassExists("java.lang.StackTraceElement")) { + return teavm_javaClass("java.lang.StackTraceElement"); + } else { + return $rt_objcls(); + } +} +function $rt_throwAIOOBE() { + if (teavm_javaConstructorExists("java.lang.ArrayIndexOutOfBoundsException", "()V")) { + $rt_throw(teavm_javaConstructor("java.lang.ArrayIndexOutOfBoundsException", "()V")()); + } else { + $rt_throw($rt_createException($rt_str(""))); + } +} +function $rt_throwCCE() { + if (teavm_javaConstructorExists("java.lang.ClassCastException", "()V")) { + $rt_throw(teavm_javaConstructor("java.lang.ClassCastException", "()V")()); + } else { + $rt_throw($rt_createException($rt_str(""))); + } +} + +function $rt_getThread() { + if (teavm_javaMethodExists("java.lang.Thread", "currentThread()Ljava/lang/Thread;")) { + return teavm_javaMethod("java.lang.Thread", "currentThread()Ljava/lang/Thread;")(); + } +} +function $rt_setThread(t) { + if (teavm_javaMethodExists("java.lang.Thread", "setCurrentThread(Ljava/lang/Thread;)V")) { + return teavm_javaMethod("java.lang.Thread", "setCurrentThread(Ljava/lang/Thread;)V")(t); + } +} +function $rt_createStackElement(className, methodName, fileName, lineNumber) { + if (teavm_javaConstructorExists("java.lang.StackTraceElement", + "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;I)V")) { + return teavm_javaConstructor("java.lang.StackTraceElement", + "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;I)V")(className, methodName, fileName, lineNumber); + } else { + return null; + } +} +function $rt_setStack(e, stack) { + if (teavm_javaMethodExists("java.lang.Throwable", "setStackTrace([Ljava/lang/StackTraceElement;)V")) { + teavm_javaMethod("java.lang.Throwable", "setStackTrace([Ljava/lang/StackTraceElement;)V")(e, stack); + } } \ No newline at end of file