JS: introduce JS template engine to write runtime, rewrite several functions using this engine

This commit is contained in:
Alexey Andreev 2023-10-24 21:20:27 +02:00
parent 350cff776e
commit 16cd0aaab2
5 changed files with 217 additions and 151 deletions

View File

@ -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<String, NameEmitter> nameMap = new HashMap<>();
private boolean rootScope = true;
private Set<String> 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;

View File

@ -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);
}
}

View File

@ -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,
"<init>", 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, "<init>", 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, "<init>", 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;

View File

@ -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(), "<init>",
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;
}
}

View File

@ -930,3 +930,41 @@ function $rt_substring(string, start, end) {
$rt_substringSink = ($rt_substringSink + result.charCodeAt(result.length - 1)) | 0;
}
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);
}