From 16cd0aaab2f3c40511ca133e15e2cffa793d9987 Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Tue, 24 Oct 2023 21:20:27 +0200 Subject: [PATCH] JS: introduce JS template engine to write runtime, rewrite several functions using this engine --- .../javascript/rendering/AstWriter.java | 12 +- .../rendering/RuntimeAstTransformer.java | 59 ------- .../javascript/rendering/RuntimeRenderer.java | 91 +--------- .../templating/TemplatingAstWriter.java | 166 ++++++++++++++++++ .../org/teavm/backend/javascript/runtime.js | 40 ++++- 5 files changed, 217 insertions(+), 151 deletions(-) delete mode 100644 core/src/main/java/org/teavm/backend/javascript/rendering/RuntimeAstTransformer.java create mode 100644 core/src/main/java/org/teavm/backend/javascript/templating/TemplatingAstWriter.java diff --git a/core/src/main/java/org/teavm/backend/javascript/rendering/AstWriter.java b/core/src/main/java/org/teavm/backend/javascript/rendering/AstWriter.java index 4edbe0a26..9dbab4d4e 100644 --- a/core/src/main/java/org/teavm/backend/javascript/rendering/AstWriter.java +++ b/core/src/main/java/org/teavm/backend/javascript/rendering/AstWriter.java @@ -91,7 +91,7 @@ public class AstWriter { public static final int PRECEDENCE_COND = 16; public static final int PRECEDENCE_ASSIGN = 17; public static final int PRECEDENCE_COMMA = 18; - private SourceWriter writer; + protected final SourceWriter writer; private Map nameMap = new HashMap<>(); private boolean rootScope = true; private Set aliases = new HashSet<>(); @@ -495,7 +495,7 @@ public class AstWriter { writer.append(';'); } - private void print(ElementGet node) throws IOException { + protected void print(ElementGet node) throws IOException { print(node.getTarget(), PRECEDENCE_MEMBER); writer.append('['); print(node.getElement()); @@ -512,6 +512,10 @@ public class AstWriter { } private void print(FunctionCall node, int precedence) throws IOException { + if (intrinsic(node, precedence)) { + return; + } + if (tryJavaInvocation(node)) { return; } @@ -539,6 +543,10 @@ public class AstWriter { } } + protected boolean intrinsic(FunctionCall node, int precedence) throws IOException { + return false; + } + private boolean tryJavaInvocation(FunctionCall node) throws IOException { if (!(node.getTarget() instanceof PropertyGet)) { return false; diff --git a/core/src/main/java/org/teavm/backend/javascript/rendering/RuntimeAstTransformer.java b/core/src/main/java/org/teavm/backend/javascript/rendering/RuntimeAstTransformer.java deleted file mode 100644 index 4df67d9c3..000000000 --- a/core/src/main/java/org/teavm/backend/javascript/rendering/RuntimeAstTransformer.java +++ /dev/null @@ -1,59 +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.Token; -import org.mozilla.javascript.ast.AstNode; -import org.mozilla.javascript.ast.ExpressionStatement; -import org.mozilla.javascript.ast.FunctionCall; -import org.mozilla.javascript.ast.InfixExpression; -import org.mozilla.javascript.ast.KeywordLiteral; -import org.mozilla.javascript.ast.Name; -import org.mozilla.javascript.ast.PropertyGet; -import org.teavm.backend.javascript.codegen.NamingStrategy; -import org.teavm.model.FieldReference; - -public class RuntimeAstTransformer extends AstVisitor { - private static final FieldReference MONITOR_FIELD = new FieldReference( - "java.lang.Object", "monitor"); - private NamingStrategy names; - - public RuntimeAstTransformer(NamingStrategy names) { - this.names = names; - } - - @Override - protected void visitExpressionStatement(ExpressionStatement node) { - AstNode expression = node.getExpression(); - if (expression.getType() == Token.CALL) { - FunctionCall call = (FunctionCall) expression; - if (call.getTarget().getType() == Token.NAME) { - String id = ((Name) call.getTarget()).getIdentifier(); - if (id.equals("$rt_initMonitorField")) { - AstNode arg = call.getArguments().get(0); - accept(arg); - String fieldId = names.getNameFor(MONITOR_FIELD); - PropertyGet propertyGet = new PropertyGet(arg, new Name(0, fieldId)); - KeywordLiteral nullExpr = new KeywordLiteral(0, 0, Token.NULL); - InfixExpression assign = new InfixExpression(Token.ASSIGN, propertyGet, nullExpr, 0); - node.setExpression(assign); - return; - } - } - } - super.visitExpressionStatement(node); - } -} 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 6ed87eec1..641faebff 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,14 +24,13 @@ 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.TemplatingAstWriter; import org.teavm.model.ClassReader; import org.teavm.model.ClassReaderSource; import org.teavm.model.ElementModifier; -import org.teavm.model.FieldReference; import org.teavm.model.MethodDescriptor; import org.teavm.model.MethodReader; import org.teavm.model.MethodReference; -import org.teavm.model.ValueType; import org.teavm.vm.RenderingException; public class RuntimeRenderer { @@ -39,8 +38,6 @@ public class RuntimeRenderer { private static final String THREAD_CLASS = Thread.class.getName(); private static final String STE_CLASS = StackTraceElement.class.getName(); - private static final MethodReference NPE_INIT_METHOD = new MethodReference(NullPointerException.class, - "", void.class); private static final MethodDescriptor STRING_INTERN_METHOD = new MethodDescriptor("intern", String.class); private static final MethodDescriptor CURRENT_THREAD_METHOD = new MethodDescriptor("currentThread", Thread.class); @@ -64,18 +61,9 @@ public class RuntimeRenderer { public void renderRuntime() throws RenderingException { try { renderHandWrittenRuntime("runtime.js"); - renderSetCloneMethod(); - renderRuntimeCls(); - renderRuntimeString(); - renderRuntimeUnwrapString(); - renderRuntimeObjcls(); renderRuntimeThrowablecls(); - renderRuntimeThrowableMethods(); - renderRuntimeNullCheck(); renderRuntimeIntern(); - renderStringClassInit(); renderRuntimeThreads(); - renderRuntimeCreateException(); renderCreateStackTraceElement(); renderSetStackTrace(); renderThrowAIOOBE(); @@ -88,8 +76,7 @@ public class RuntimeRenderer { public void renderHandWrittenRuntime(String name) throws IOException { AstRoot ast = parseRuntime(name); ast.visit(new StringConstantElimination()); - new RuntimeAstTransformer(writer.getNaming()).accept(ast); - var astWriter = new AstWriter(writer, new DefaultGlobalNameWriter(writer)); + var astWriter = new TemplatingAstWriter(writer); astWriter.hoist(ast); astWriter.print(ast); } @@ -107,50 +94,6 @@ public class RuntimeRenderer { } } - private void renderSetCloneMethod() throws IOException { - writer.append("function $rt_setCloneMethod(target, f)").ws().append("{").softNewLine().indent(); - writer.append("target.").appendMethod("clone", Object.class).ws().append('=').ws().append("f;"). - softNewLine(); - writer.outdent().append("}").newLine(); - } - - private void renderRuntimeCls() throws IOException { - writer.append("function $rt_cls(cls)").ws().append("{").softNewLine().indent(); - writer.append("return ").appendMethodBody("java.lang.Class", "getClass", - ValueType.object("org.teavm.platform.PlatformClass"), - ValueType.object("java.lang.Class")).append("(cls);") - .softNewLine(); - writer.outdent().append("}").newLine(); - } - - private void renderRuntimeString() throws IOException { - MethodReference stringCons = new MethodReference(String.class, "", Object.class, void.class); - writer.append("function $rt_str(str)").ws().append("{").indent().softNewLine(); - writer.append("if (str === null) {").indent().softNewLine(); - writer.append("return null;").softNewLine(); - writer.outdent().append("}").softNewLine(); - writer.append("return ").appendInit(stringCons).append("(str);").softNewLine(); - writer.outdent().append("}").newLine(); - } - - private void renderRuntimeUnwrapString() throws IOException { - FieldReference stringChars = new FieldReference(STRING_CLASS, "nativeString"); - writer.append("function $rt_ustr(str)").ws().append("{").indent().softNewLine(); - writer.append("return str").ws().append("!==").ws().append("null"); - writer.ws().append("?").ws().append("str.").appendField(stringChars); - writer.ws().append(":").ws().append("null").append(";").softNewLine(); - writer.outdent().append("}").newLine(); - } - - private void renderRuntimeNullCheck() throws IOException { - writer.append("function $rt_nullCheck(val) {").indent().softNewLine(); - writer.append("if (val === null) {").indent().softNewLine(); - writer.append("$rt_throw(").appendInit(NPE_INIT_METHOD).append("());").softNewLine(); - writer.outdent().append("}").softNewLine(); - writer.append("return val;").softNewLine(); - writer.outdent().append("}").newLine(); - } - private void renderRuntimeIntern() throws IOException { if (!needInternMethod()) { writer.append("function $rt_intern(str) {").indent().softNewLine(); @@ -161,12 +104,6 @@ public class RuntimeRenderer { } } - private void renderStringClassInit() throws IOException { - writer.append("function $rt_stringClassInit(str)").ws().append("{").indent().softNewLine(); - writer.appendClassInit("java.lang.String").append("();").softNewLine(); - writer.outdent().append("}").softNewLine(); - } - private boolean needInternMethod() { ClassReader cls = classSource.get(STRING_CLASS); if (cls == null) { @@ -176,10 +113,6 @@ public class RuntimeRenderer { return method != null && method.hasModifier(ElementModifier.NATIVE); } - private void renderRuntimeObjcls() throws IOException { - writer.append("function $rt_objcls() { return ").appendClass("java.lang.Object").append("; }").newLine(); - } - private void renderRuntimeThrowablecls() throws IOException { writer.append("function $rt_stecls()").ws().append("{").indent().softNewLine(); writer.append("return "); @@ -191,18 +124,6 @@ public class RuntimeRenderer { writer.append(";").softNewLine().outdent().append("}").newLine(); } - private void renderRuntimeThrowableMethods() throws IOException { - writer.append("function $rt_throwableMessage(t)").ws().append("{").indent().softNewLine(); - writer.append("return "); - writer.appendMethodBody(Throwable.class, "getMessage", String.class).append("(t);").softNewLine(); - writer.outdent().append("}").newLine(); - - writer.append("function $rt_throwableCause(t)").ws().append("{").indent().softNewLine(); - writer.append("return "); - writer.appendMethodBody(Throwable.class, "getCause", Throwable.class).append("(t);").softNewLine(); - writer.outdent().append("}").newLine(); - } - private void renderRuntimeThreads() throws IOException { ClassReader threadCls = classSource.get(THREAD_CLASS); MethodReader currentThreadMethod = threadCls != null ? threadCls.getMethod(CURRENT_THREAD_METHOD) : null; @@ -225,14 +146,6 @@ public class RuntimeRenderer { writer.outdent().append("}").newLine(); } - private void renderRuntimeCreateException() throws IOException { - writer.append("function $rt_createException(message)").ws().append("{").indent().softNewLine(); - writer.append("return "); - writer.appendInit(new MethodReference(RuntimeException.class, "", String.class, void.class)); - writer.append("(message);").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; diff --git a/core/src/main/java/org/teavm/backend/javascript/templating/TemplatingAstWriter.java b/core/src/main/java/org/teavm/backend/javascript/templating/TemplatingAstWriter.java new file mode 100644 index 000000000..d3392cebd --- /dev/null +++ b/core/src/main/java/org/teavm/backend/javascript/templating/TemplatingAstWriter.java @@ -0,0 +1,166 @@ +/* + * 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 java.io.IOException; +import org.mozilla.javascript.ast.ElementGet; +import org.mozilla.javascript.ast.FunctionCall; +import org.mozilla.javascript.ast.Name; +import org.mozilla.javascript.ast.StringLiteral; +import org.teavm.backend.javascript.codegen.SourceWriter; +import org.teavm.backend.javascript.rendering.AstWriter; +import org.teavm.backend.javascript.rendering.DefaultGlobalNameWriter; +import org.teavm.model.FieldReference; +import org.teavm.model.MethodDescriptor; +import org.teavm.model.MethodReference; + +public class TemplatingAstWriter extends AstWriter { + public TemplatingAstWriter(SourceWriter writer) { + super(writer, new DefaultGlobalNameWriter(writer)); + } + + @Override + protected boolean intrinsic(FunctionCall node, int precedence) throws IOException { + if (node.getTarget() instanceof Name) { + var name = (Name) node.getTarget(); + if (name.getDefiningScope() == null) { + return tryIntrinsicName(node, name.getIdentifier()); + } + } + return super.intrinsic(node, precedence); + } + + private boolean tryIntrinsicName(FunctionCall node, String name) throws IOException { + switch (name) { + case "teavm_javaClass": + return writeJavaClass(node); + case "teavm_javaMethod": + return writeJavaMethod(node); + case "teavm_javaConstructor": + return writeJavaConstructor(node); + case "teavm_javaClassInit": + return writeJavaClassInit(node); + default: + return false; + } + } + + private boolean writeJavaClass(FunctionCall node) throws IOException { + if (node.getArguments().size() != 1) { + return false; + } + var classArg = node.getArguments().get(0); + if (!(classArg instanceof StringLiteral)) { + return false; + } + writer.appendClass(((StringLiteral) classArg).getValue()); + return true; + } + + private boolean writeJavaMethod(FunctionCall node) throws IOException { + if (node.getArguments().size() != 2) { + return false; + } + var classArg = node.getArguments().get(0); + var methodArg = node.getArguments().get(1); + if (!(classArg instanceof StringLiteral) || !(methodArg instanceof StringLiteral)) { + return false; + } + var method = new MethodReference(((StringLiteral) classArg).getValue(), + MethodDescriptor.parse(((StringLiteral) methodArg).getValue())); + writer.appendMethodBody(method); + return true; + } + + private boolean writeJavaConstructor(FunctionCall node) throws IOException { + if (node.getArguments().size() != 2) { + return false; + } + var classArg = node.getArguments().get(0); + var methodArg = node.getArguments().get(1); + if (!(classArg instanceof StringLiteral) || !(methodArg instanceof StringLiteral)) { + return false; + } + var method = new MethodReference(((StringLiteral) classArg).getValue(), "", + MethodDescriptor.parseSignature(((StringLiteral) methodArg).getValue())); + writer.appendInit(method); + return true; + } + + private boolean writeJavaClassInit(FunctionCall node) throws IOException { + if (node.getArguments().size() != 1) { + return false; + } + var classArg = node.getArguments().get(0); + if (!(classArg instanceof StringLiteral)) { + return false; + } + writer.appendClassInit(((StringLiteral) classArg).getValue()); + return true; + } + + @Override + protected void print(ElementGet node) throws IOException { + if (node.getElement() instanceof FunctionCall) { + var call = (FunctionCall) node.getElement(); + if (call.getTarget() instanceof Name) { + var name = (Name) call.getTarget(); + if (name.getDefiningScope() == null) { + switch (name.getIdentifier()) { + case "teavm_javaVirtualMethod": + if (writeJavaVirtualMethod(node, call)) { + return; + } + break; + case "teavm_javaField": + if (writeJavaField(node, call)) { + return; + } + break; + } + } + } + } + super.print(node); + } + + private boolean writeJavaVirtualMethod(ElementGet get, FunctionCall call) throws IOException { + var arg = call.getArguments().get(0); + if (!(arg instanceof StringLiteral)) { + return false; + } + var method = MethodDescriptor.parse(((StringLiteral) arg).getValue()); + print(get.getTarget()); + writer.append('.').appendMethod(method); + return true; + } + + private boolean writeJavaField(ElementGet get, FunctionCall call) throws IOException { + if (call.getArguments().size() != 2) { + return false; + } + var classArg = call.getArguments().get(0); + var fieldArg = call.getArguments().get(1); + if (!(classArg instanceof StringLiteral) || !(fieldArg instanceof StringLiteral)) { + return false; + } + var className = ((StringLiteral) classArg).getValue(); + var fieldName = ((StringLiteral) fieldArg).getValue(); + print(get.getTarget()); + writer.append('.').appendField(new FieldReference(className, fieldName)); + return true; + } +} 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 e6e2c539a..bbc30bbdf 100644 --- a/core/src/main/resources/org/teavm/backend/javascript/runtime.js +++ b/core/src/main/resources/org/teavm/backend/javascript/runtime.js @@ -929,4 +929,42 @@ function $rt_substring(string, start, end) { var result = start.substring(start, end - 1) + start.substring(end - 1, end); $rt_substringSink = ($rt_substringSink + result.charCodeAt(result.length - 1)) | 0; } -var $rt_substringSink = 0; \ No newline at end of file +var $rt_substringSink = 0; + +function $rt_setCloneMethod(target, method) { + target[teavm_javaVirtualMethod('clone()Ljava/lang/Object;')] = method; +} +function $rt_cls(cls) { + return teavm_javaMethod("java.lang.Class", + "getClass(Lorg/teavm/platform/PlatformClass;)Ljava/lang/Class;")(cls); +} +function $rt_str(str) { + if (str === null) { + return null; + } + return teavm_javaConstructor("java.lang.String", "(Ljava/lang/Object;)V")(str); +} +function $rt_ustr(str) { + return str === null ? null : str[teavm_javaField("java.lang.String", "nativeString")]; +} +function $rt_nullCheck(val) { + if (val === null) { + $rt_throw(teavm_javaConstructor("java.lang.NullPointerException", "()V")()); + } + return val; +} +function $rt_stringClassInit() { + teavm_javaClassInit("java.lang.String")(); +} +function $rt_objcls() { + return teavm_javaClass("java.lang.Object"); +} +function $rt_createException(message) { + return teavm_javaConstructor("java.lang.RuntimeException", "(Ljava/lang/String;)V")(message); +} +function $rt_throwableMessage(t) { + return teavm_javaMethod("java.lang.Throwable", "getMessage()Ljava/lang/String;")(t); +} +function $rt_throwableCause(t) { + return teavm_javaMethod("java.lang.Throwable", "getCause()Ljava/lang/Throwable;")(t); +} \ No newline at end of file