Improve JSBody implementation

This commit is contained in:
Alexey Andreev 2015-09-25 13:28:46 +03:00
parent d4309bb564
commit 49f05cbb6e
12 changed files with 578 additions and 295 deletions

View File

@ -656,6 +656,7 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext
}
writer.append(";").softNewLine();
}
end = true;
currentPart = 0;
method.getBody().acceptVisitor(Renderer.this);

View File

@ -95,7 +95,7 @@ public class AstWriter {
private static final int PRECEDENCE_ASSIGN = 17;
private static final int PRECEDENCE_COMMA = 18;
private SourceWriter writer;
private Map<String, String> nameMap = new HashMap<>();
private Map<String, NameEmitter> nameMap = new HashMap<>();
private Set<String> aliases = new HashSet<>();
public AstWriter(SourceWriter writer) {
@ -107,13 +107,13 @@ public class AstWriter {
return;
}
if (aliases.add(name)) {
nameMap.put(name, name);
nameMap.put(name, () -> writer.append(name));
return;
}
for (int i = 0;; ++i) {
String alias = name + "_" + i;
if (aliases.add(alias)) {
nameMap.put(name, alias);
nameMap.put(name, () -> writer.append(alias));
return;
}
}
@ -123,7 +123,16 @@ public class AstWriter {
if (!aliases.add(alias)) {
throw new IllegalArgumentException("Alias " + alias + " is already occupied");
}
nameMap.put(name, alias);
nameMap.put(name, () -> writer.append(alias));
}
public void reserveName(String name) {
aliases.add(name);
nameMap.put(name, () -> writer.append(name));
}
public void declareNameEmitter(String name, NameEmitter emitter) {
nameMap.put(name, emitter);
}
public void hoist(AstNode node) {
@ -181,7 +190,11 @@ public class AstWriter {
writer.append("false");
break;
case Token.THIS:
writer.append(nameMap.containsKey("this") ? nameMap.get("this") : "this");
if (nameMap.containsKey("this")) {
nameMap.get("this").emit();
} else {
writer.append("this");
}
break;
case Token.NULL:
writer.append("null");
@ -481,7 +494,7 @@ public class AstWriter {
private void print(PropertyGet node) throws IOException {
print(node.getLeft(), PRECEDENCE_MEMBER);
writer.append('.');
Map<String, String> oldNameMap = nameMap;
Map<String, NameEmitter> oldNameMap = nameMap;
nameMap = Collections.emptyMap();
print(node.getRight());
nameMap = oldNameMap;
@ -583,11 +596,11 @@ public class AstWriter {
}
private void print(Name node) throws IOException {
String alias = nameMap.get(node.getIdentifier());
NameEmitter alias = nameMap.get(node.getIdentifier());
if (alias == null) {
alias = node.getIdentifier();
alias = () -> writer.append(node.getIdentifier());
}
writer.append(alias);
alias.emit();
}
private void print(RegExpLiteral node) throws IOException {
@ -618,7 +631,7 @@ public class AstWriter {
} else if (node.isSetterMethod()) {
writer.append("set ");
}
Map<String, String> oldNameMap = nameMap;
Map<String, NameEmitter> oldNameMap = nameMap;
nameMap = Collections.emptyMap();
print(node.getLeft());
nameMap = oldNameMap;

View File

@ -0,0 +1,73 @@
/*
* 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.io.IOException;
import org.mozilla.javascript.ast.AstRoot;
import org.teavm.codegen.SourceWriter;
import org.teavm.javascript.spi.GeneratorContext;
import org.teavm.javascript.spi.InjectorContext;
import org.teavm.model.MethodReference;
/**
*
* @author Alexey Andreev
*/
class JSBodyAstEmitter implements JSBodyEmitter {
private boolean isStatic;
private AstRoot ast;
private String[] parameterNames;
public JSBodyAstEmitter(boolean isStatic, AstRoot ast, String[] parameterNames) {
this.isStatic = isStatic;
this.ast = ast;
this.parameterNames = parameterNames;
}
@Override
public void emit(InjectorContext context) throws IOException {
AstWriter astWriter = new AstWriter(context.getWriter());
int paramIndex = 0;
if (!isStatic) {
int index = paramIndex++;
astWriter.declareNameEmitter("this", () -> context.writeExpr(context.getArgument(index)));
}
for (int i = 0; i < parameterNames.length; ++i) {
int index = paramIndex++;
astWriter.declareNameEmitter(parameterNames[i], () -> context.writeExpr(context.getArgument(index)));
}
astWriter.hoist(ast);
astWriter.print(ast);
context.getWriter().softNewLine();
}
@Override
public void emit(GeneratorContext context, SourceWriter writer, MethodReference methodRef) throws IOException {
AstWriter astWriter = new AstWriter(writer);
int paramIndex = 1;
if (!isStatic) {
int index = paramIndex++;
astWriter.declareNameEmitter("this", () -> writer.append(context.getParameterName(index)));
}
for (int i = 0; i < parameterNames.length; ++i) {
int index = paramIndex++;
astWriter.declareNameEmitter(parameterNames[i], () -> writer.append(context.getParameterName(index)));
}
astWriter.hoist(ast);
astWriter.print(ast);
writer.softNewLine();
}
}

View File

@ -0,0 +1,105 @@
/*
* 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.io.IOException;
import org.teavm.codegen.SourceWriter;
import org.teavm.javascript.spi.GeneratorContext;
import org.teavm.javascript.spi.InjectorContext;
import org.teavm.model.MethodReference;
/**
*
* @author Alexey Andreev
*/
class JSBodyBloatedEmitter implements JSBodyEmitter {
private boolean isStatic;
private MethodReference method;
private String script;
private String[] parameterNames;
public JSBodyBloatedEmitter(boolean isStatic, MethodReference method, String script, String[] parameterNames) {
this.isStatic = isStatic;
this.method = method;
this.script = script;
this.parameterNames = parameterNames;
}
@Override
public void emit(InjectorContext context) throws IOException {
emit(context.getWriter(), index -> context.writeExpr(context.getArgument(index)));
}
@Override
public void emit(GeneratorContext context, SourceWriter writer, MethodReference methodRef) throws IOException {
emit(writer, index -> writer.append(context.getParameterName(index + 1)));
}
private void emit(SourceWriter writer, EmissionStrategy strategy) throws IOException {
int bodyParamCount = isStatic ? method.parameterCount() : method.parameterCount() - 1;
writer.append("if (!").appendMethodBody(method).append(".$native)").ws().append('{').indent().newLine();
writer.appendMethodBody(method).append(".$native").ws().append('=').ws().append("function(");
int count = method.parameterCount();
for (int i = 0; i < count; ++i) {
if (i > 0) {
writer.append(',').ws();
}
writer.append('_').append(i);
}
writer.append(')').ws().append('{').softNewLine().indent();
writer.append("return (function(");
for (int i = 0; i < bodyParamCount; ++i) {
if (i > 0) {
writer.append(',').ws();
}
String name = parameterNames[i];
writer.append(name);
}
writer.append(')').ws().append('{').softNewLine().indent();
writer.append(script).softNewLine();
writer.outdent().append("})");
if (!isStatic) {
writer.append(".call");
}
writer.append('(');
for (int i = 0; i < count; ++i) {
if (i > 0) {
writer.append(',').ws();
}
writer.append('_').append(i);
}
writer.append(");").softNewLine();
writer.outdent().append("};").softNewLine();
writer.appendMethodBody(method).ws().append('=').ws().appendMethodBody(method).append(".$native;")
.softNewLine();
writer.outdent().append("}").softNewLine();
writer.append("return ").appendMethodBody(method).append('(');
for (int i = 0; i < count; ++i) {
if (i > 0) {
writer.append(',').ws();
}
strategy.emitArgument(i);
}
writer.append(");").softNewLine();
}
interface EmissionStrategy {
void emitArgument(int argument) throws IOException;
}
}

View File

@ -0,0 +1,32 @@
/*
* 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.io.IOException;
import org.teavm.codegen.SourceWriter;
import org.teavm.javascript.spi.GeneratorContext;
import org.teavm.javascript.spi.InjectorContext;
import org.teavm.model.MethodReference;
/**
*
* @author Alexey Andreev
*/
interface JSBodyEmitter {
void emit(InjectorContext context) throws IOException;
void emit(GeneratorContext context, SourceWriter writer, MethodReference methodRef) throws IOException;
}

View File

@ -16,113 +16,29 @@
package org.teavm.jso.plugin;
import java.io.IOException;
import java.io.StringReader;
import java.util.List;
import org.mozilla.javascript.CompilerEnvirons;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.ast.AstRoot;
import org.teavm.codegen.SourceWriter;
import org.teavm.javascript.spi.Generator;
import org.teavm.javascript.spi.GeneratorContext;
import org.teavm.model.AnnotationReader;
import org.teavm.model.AnnotationValue;
import org.teavm.model.CallLocation;
import org.teavm.model.ClassReader;
import org.teavm.model.MethodReader;
import org.teavm.javascript.spi.Injector;
import org.teavm.javascript.spi.InjectorContext;
import org.teavm.model.MethodReference;
/**
*
* @author Alexey Andreev
*/
public class JSBodyGenerator implements Generator {
public class JSBodyGenerator implements Injector, Generator {
@Override
public void generate(InjectorContext context, MethodReference methodRef) throws IOException {
JSBodyRepository emitterRepository = context.getService(JSBodyRepository.class);
JSBodyEmitter emitter = emitterRepository.emitters.get(methodRef);
emitter.emit(context);
}
@Override
public void generate(GeneratorContext context, SourceWriter writer, MethodReference methodRef) throws IOException {
ClassReader cls = context.getClassSource().get(methodRef.getClassName());
MethodReader method = cls.getMethod(methodRef.getDescriptor());
AnnotationReader annot = method.getAnnotations().get(JSBodyImpl.class.getName());
boolean isStatic = annot.getValue("isStatic").getBoolean();
List<AnnotationValue> paramNames = annot.getValue("params").getList();
String script = annot.getValue("script").getString();
TeaVMErrorReporter errorReporter = new TeaVMErrorReporter(context.getDiagnostics(),
new CallLocation(methodRef));
CompilerEnvirons env = new CompilerEnvirons();
env.setRecoverFromErrors(true);
env.setLanguageVersion(Context.VERSION_1_8);
env.setIdeMode(true);
JSParser parser = new JSParser(env, errorReporter);
parser.enterFunction();
AstRoot rootNode = parser.parse(new StringReader(script), null, 0);
parser.exitFunction();
if (errorReporter.hasErrors()) {
generateBloated(context, writer, methodRef, method, isStatic, paramNames, script);
return;
}
AstWriter astWriter = new AstWriter(writer);
int paramIndex = 1;
if (!isStatic) {
astWriter.declareAlias("this", context.getParameterName(paramIndex++));
}
for (int i = 0; i < paramNames.size(); ++i) {
astWriter.declareAlias(paramNames.get(i).getString(), context.getParameterName(paramIndex++));
}
astWriter.hoist(rootNode);
astWriter.print(rootNode);
writer.softNewLine();
}
private void generateBloated(GeneratorContext context, SourceWriter writer, MethodReference methodRef,
MethodReader method, boolean isStatic, List<AnnotationValue> paramNames, String script)
throws IOException {
int bodyParamCount = isStatic ? method.parameterCount() : method.parameterCount() - 1;
writer.append("if (!").appendMethodBody(methodRef).append(".$native)").ws().append('{').indent().newLine();
writer.appendMethodBody(methodRef).append(".$native").ws().append('=').ws().append("function(");
int count = method.parameterCount();
for (int i = 0; i < count; ++i) {
if (i > 0) {
writer.append(',').ws();
}
writer.append('_').append(context.getParameterName(i + 1));
}
writer.append(')').ws().append('{').softNewLine().indent();
writer.append("return (function(");
for (int i = 0; i < bodyParamCount; ++i) {
if (i > 0) {
writer.append(',').ws();
}
String name = paramNames.get(i).getString();
writer.append(name);
}
writer.append(')').ws().append('{').softNewLine().indent();
writer.append(script).softNewLine();
writer.outdent().append("})");
if (!isStatic) {
writer.append(".call");
}
writer.append('(');
for (int i = 0; i < count; ++i) {
if (i > 0) {
writer.append(',').ws();
}
writer.append('_').append(context.getParameterName(i + 1));
}
writer.append(");").softNewLine();
writer.outdent().append("};").softNewLine();
writer.appendMethodBody(methodRef).ws().append('=').ws().appendMethodBody(methodRef).append(".$native;")
.softNewLine();
writer.outdent().append("}").softNewLine();
writer.append("return ").appendMethodBody(methodRef).append('(');
for (int i = 0; i < count; ++i) {
if (i > 0) {
writer.append(',').ws();
}
writer.append(context.getParameterName(i + 1));
}
writer.append(");").softNewLine();
JSBodyRepository emitterRepository = context.getService(JSBodyRepository.class);
JSBodyEmitter emitter = emitterRepository.emitters.get(methodRef);
emitter.emit(context, writer, methodRef);
}
}

View File

@ -26,10 +26,6 @@ import java.lang.annotation.Target;
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface JSBodyImpl {
String[] params();
String script();
boolean isStatic();
@interface JSBodyRef {
String method();
}

View File

@ -0,0 +1,32 @@
/*
* 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.HashSet;
import java.util.Map;
import java.util.Set;
import org.teavm.model.MethodReference;
/**
*
* @author Alexey Andreev
*/
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<>();
}

View File

@ -25,7 +25,9 @@ import org.teavm.vm.spi.TeaVMPlugin;
public class JSOPlugin implements TeaVMPlugin {
@Override
public void install(TeaVMHost host) {
host.add(new JSObjectClassTransformer());
JSBodyRepository repository = new JSBodyRepository();
host.registerService(JSBodyRepository.class, repository);
host.add(new JSObjectClassTransformer(repository));
JSODependencyListener dependencyListener = new JSODependencyListener();
JSOAliasRenderer aliasRenderer = new JSOAliasRenderer(dependencyListener);
host.add(dependencyListener);

View File

@ -16,8 +16,11 @@
package org.teavm.jso.plugin;
import org.teavm.diagnostics.Diagnostics;
import org.teavm.jso.JSBody;
import org.teavm.model.*;
import org.teavm.model.ClassHolder;
import org.teavm.model.ClassHolderTransformer;
import org.teavm.model.ClassReaderSource;
import org.teavm.model.MethodHolder;
import org.teavm.model.MethodReference;
/**
*
@ -25,6 +28,11 @@ import org.teavm.model.*;
*/
public class JSObjectClassTransformer implements ClassHolderTransformer {
private JavascriptNativeProcessor processor;
private JSBodyRepository repository;
public JSObjectClassTransformer(JSBodyRepository repository) {
this.repository = repository;
}
@Override
public void transformClass(ClassHolder cls, ClassReaderSource innerSource, Diagnostics diagnostics) {
@ -46,12 +54,10 @@ public class JSObjectClassTransformer implements ClassHolderTransformer {
}
}
for (MethodHolder method : cls.getMethods().toArray(new MethodHolder[0])) {
if (method.getAnnotations().get(JSBody.class.getName()) != null) {
processor.processJSBody(cls, method);
} else if (method.getProgram() != null
&& method.getAnnotations().get(JSBodyImpl.class.getName()) == null) {
processor.processProgram(method);
if (method.getProgram() != null) {
processor.processProgram(repository, method);
}
}
processor.createJSMethods(repository, cls);
}
}

View File

@ -15,6 +15,8 @@
*/
package org.teavm.jso.plugin;
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
@ -23,6 +25,9 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import org.mozilla.javascript.CompilerEnvirons;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.ast.AstRoot;
import org.teavm.diagnostics.Diagnostics;
import org.teavm.javascript.spi.GeneratedBy;
import org.teavm.javascript.spi.Sync;
@ -60,7 +65,6 @@ import org.teavm.model.Variable;
import org.teavm.model.instructions.AssignInstruction;
import org.teavm.model.instructions.CastInstruction;
import org.teavm.model.instructions.ClassConstantInstruction;
import org.teavm.model.instructions.ExitInstruction;
import org.teavm.model.instructions.InvocationType;
import org.teavm.model.instructions.InvokeInstruction;
import org.teavm.model.instructions.StringConstantInstruction;
@ -275,7 +279,7 @@ class JavascriptNativeProcessor {
return staticSignature;
}
public void processProgram(MethodHolder methodToProcess) {
public void processProgram(JSBodyRepository repository, MethodHolder methodToProcess) {
program = methodToProcess.getProgram();
for (int i = 0; i < program.basicBlockCount(); ++i) {
BasicBlock block = program.basicBlockAt(i);
@ -286,146 +290,218 @@ class JavascriptNativeProcessor {
continue;
}
InvokeInstruction invoke = (InvokeInstruction) insn;
if (!nativeRepos.isJavaScriptClass(invoke.getMethod().getClassName())) {
continue;
}
replacement.clear();
MethodReader method = getMethod(invoke.getMethod());
if (method == null || method.hasModifier(ElementModifier.STATIC)) {
if (method == null) {
continue;
}
if (method.hasModifier(ElementModifier.FINAL)) {
MethodReader overriden = getOverridenMethod(method);
if (overriden != null) {
CallLocation callLocation = new CallLocation(methodToProcess.getReference(),
insn.getLocation());
diagnostics.error(callLocation, "JS final method {{m0}} overrides {{M1}}. "
+ "Overriding final method of overlay types is prohibited.",
method.getReference(), overriden.getReference());
}
if (method.getProgram() != null && method.getProgram().basicBlockCount() > 0) {
invoke.setMethod(new MethodReference(method.getOwnerName(), method.getName() + "$static",
getStaticSignature(method.getReference())));
invoke.getArguments().add(0, invoke.getInstance());
invoke.setInstance(null);
}
invoke.setType(InvocationType.SPECIAL);
continue;
}
CallLocation callLocation = new CallLocation(methodToProcess.getReference(), insn.getLocation());
if (method.getAnnotations().get(JSProperty.class.getName()) != null) {
if (isProperGetter(method.getDescriptor())) {
String propertyName;
AnnotationReader annot = method.getAnnotations().get(JSProperty.class.getName());
if (annot.getValue("value") != null) {
propertyName = annot.getValue("value").getString();
} else {
propertyName = method.getName().charAt(0) == 'i' ? cutPrefix(method.getName(), 2)
: cutPrefix(method.getName(), 3);
}
Variable result = invoke.getReceiver() != null ? program.createVariable() : null;
addPropertyGet(propertyName, invoke.getInstance(), result, invoke.getLocation());
if (result != null) {
result = unwrap(callLocation, result, method.getResultType());
copyVar(result, invoke.getReceiver(), invoke.getLocation());
}
} else if (isProperSetter(method.getDescriptor())) {
String propertyName;
AnnotationReader annot = method.getAnnotations().get(JSProperty.class.getName());
if (annot.getValue("value") != null) {
propertyName = annot.getValue("value").getString();
} else {
propertyName = cutPrefix(method.getName(), 3);
}
Variable wrapped = wrapArgument(callLocation, invoke.getArguments().get(0),
method.parameterType(0));
addPropertySet(propertyName, invoke.getInstance(), wrapped, invoke.getLocation());
} else {
diagnostics.error(callLocation, "Method {{m0}} is not a proper native JavaScript property "
+ "declaration", invoke.getMethod());
continue;
}
} else if (method.getAnnotations().get(JSIndexer.class.getName()) != null) {
if (isProperGetIndexer(method.getDescriptor())) {
Variable result = invoke.getReceiver() != null ? program.createVariable() : null;
addIndexerGet(invoke.getInstance(), wrap(invoke.getArguments().get(0),
method.parameterType(0), invoke.getLocation()), result, invoke.getLocation());
if (result != null) {
result = unwrap(callLocation, result, method.getResultType());
copyVar(result, invoke.getReceiver(), invoke.getLocation());
}
} else if (isProperSetIndexer(method.getDescriptor())) {
Variable index = wrap(invoke.getArguments().get(0), method.parameterType(0),
invoke.getLocation());
Variable value = wrap(invoke.getArguments().get(1), method.parameterType(1),
invoke.getLocation());
addIndexerSet(invoke.getInstance(), index, value, invoke.getLocation());
} else {
diagnostics.error(callLocation, "Method {{m0}} is not a proper native JavaScript indexer "
+ "declaration", invoke.getMethod());
continue;
}
} else {
String name = method.getName();
AnnotationReader methodAnnot = method.getAnnotations().get(JSMethod.class.getName());
if (methodAnnot != null) {
AnnotationValue redefinedMethodName = methodAnnot.getValue("value");
if (redefinedMethodName != null) {
name = redefinedMethodName.getString();
}
}
if (method.getResultType() != ValueType.VOID && !isSupportedType(method.getResultType())) {
diagnostics.error(callLocation, "Method {{m0}} is not a proper native JavaScript method "
+ "declaration", invoke.getMethod());
continue;
}
for (ValueType arg : method.getParameterTypes()) {
if (!isSupportedType(arg)) {
diagnostics.error(callLocation, "Method {{m0}} is not a proper native JavaScript method "
+ "or constructor declaration", invoke.getMethod());
continue;
}
}
Variable result = invoke.getReceiver() != null ? program.createVariable() : null;
InvokeInstruction newInvoke = new InvokeInstruction();
ValueType[] signature = new ValueType[method.parameterCount() + 3];
Arrays.fill(signature, ValueType.object(JSObject.class.getName()));
newInvoke.setMethod(new MethodReference(JS.class.getName(), "invoke", signature));
newInvoke.setType(InvocationType.SPECIAL);
newInvoke.setReceiver(result);
newInvoke.getArguments().add(invoke.getInstance());
newInvoke.getArguments().add(addStringWrap(addString(name, invoke.getLocation()),
invoke.getLocation()));
newInvoke.setLocation(invoke.getLocation());
for (int k = 0; k < invoke.getArguments().size(); ++k) {
Variable arg = wrapArgument(callLocation, invoke.getArguments().get(k),
method.parameterType(k));
newInvoke.getArguments().add(arg);
}
replacement.add(newInvoke);
if (result != null) {
result = unwrap(callLocation, result, method.getResultType());
copyVar(result, invoke.getReceiver(), invoke.getLocation());
}
replacement.clear();
if (processInvocation(repository, method, callLocation, invoke)) {
block.getInstructions().set(j, replacement.get(0));
block.getInstructions().addAll(j + 1, replacement.subList(1, replacement.size()));
j += replacement.size() - 1;
}
block.getInstructions().set(j, replacement.get(0));
block.getInstructions().addAll(j + 1, replacement.subList(1, replacement.size()));
j += replacement.size() - 1;
}
}
}
public void processJSBody(ClassHolder cls, MethodHolder methodToProcess) {
private boolean processInvocation(JSBodyRepository repository, MethodReader method, CallLocation callLocation,
InvokeInstruction invoke) {
if (method.getAnnotations().get(JSBody.class.getName()) != null) {
return processJSBodyInvocation(repository, method, callLocation, invoke);
}
if (!nativeRepos.isJavaScriptClass(invoke.getMethod().getClassName())) {
return false;
}
if (method == null || method.hasModifier(ElementModifier.STATIC)) {
return false;
}
if (method.hasModifier(ElementModifier.FINAL)) {
MethodReader overriden = getOverridenMethod(method);
if (overriden != null) {
diagnostics.error(callLocation, "JS final method {{m0}} overrides {{M1}}. "
+ "Overriding final method of overlay types is prohibited.",
method.getReference(), overriden.getReference());
}
if (method.getProgram() != null && method.getProgram().basicBlockCount() > 0) {
invoke.setMethod(new MethodReference(method.getOwnerName(), method.getName() + "$static",
getStaticSignature(method.getReference())));
invoke.getArguments().add(0, invoke.getInstance());
invoke.setInstance(null);
}
invoke.setType(InvocationType.SPECIAL);
return false;
}
if (method.getAnnotations().get(JSProperty.class.getName()) != null) {
return processProperty(method, callLocation, invoke);
} else if (method.getAnnotations().get(JSIndexer.class.getName()) != null) {
return processIndexer(method, callLocation, invoke);
} else {
return processMethod(method, callLocation, invoke);
}
}
private boolean processJSBodyInvocation(JSBodyRepository repository, MethodReader method,
CallLocation callLocation, InvokeInstruction invoke) {
requireJSBody(repository, diagnostics, method);
MethodReference delegate = repository.methodMap.get(method.getReference());
if (delegate == null) {
return false;
}
Variable result = invoke.getReceiver() != null ? program.createVariable() : null;
InvokeInstruction newInvoke = new InvokeInstruction();
ValueType[] signature = new ValueType[method.parameterCount() + 3];
Arrays.fill(signature, ValueType.object(JSObject.class.getName()));
newInvoke.setMethod(delegate);
newInvoke.setType(InvocationType.SPECIAL);
newInvoke.setReceiver(result);
newInvoke.setLocation(invoke.getLocation());
if (invoke.getInstance() != null) {
Variable arg = wrapArgument(callLocation, invoke.getInstance(), ValueType.object(method.getOwnerName()));
newInvoke.getArguments().add(arg);
}
for (int k = 0; k < invoke.getArguments().size(); ++k) {
Variable arg = wrapArgument(callLocation, invoke.getArguments().get(k),
method.parameterType(k));
newInvoke.getArguments().add(arg);
}
replacement.add(newInvoke);
if (result != null) {
result = unwrap(callLocation, result, method.getResultType());
copyVar(result, invoke.getReceiver(), invoke.getLocation());
}
return true;
}
private boolean processProperty(MethodReader method, CallLocation callLocation, InvokeInstruction invoke) {
if (isProperGetter(method.getDescriptor())) {
String propertyName;
AnnotationReader annot = method.getAnnotations().get(JSProperty.class.getName());
if (annot.getValue("value") != null) {
propertyName = annot.getValue("value").getString();
} else {
propertyName = method.getName().charAt(0) == 'i' ? cutPrefix(method.getName(), 2)
: cutPrefix(method.getName(), 3);
}
Variable result = invoke.getReceiver() != null ? program.createVariable() : null;
addPropertyGet(propertyName, invoke.getInstance(), result, invoke.getLocation());
if (result != null) {
result = unwrap(callLocation, result, method.getResultType());
copyVar(result, invoke.getReceiver(), invoke.getLocation());
}
return true;
}
if (isProperSetter(method.getDescriptor())) {
String propertyName;
AnnotationReader annot = method.getAnnotations().get(JSProperty.class.getName());
if (annot.getValue("value") != null) {
propertyName = annot.getValue("value").getString();
} else {
propertyName = cutPrefix(method.getName(), 3);
}
Variable wrapped = wrapArgument(callLocation, invoke.getArguments().get(0),
method.parameterType(0));
addPropertySet(propertyName, invoke.getInstance(), wrapped, invoke.getLocation());
return true;
}
diagnostics.error(callLocation, "Method {{m0}} is not a proper native JavaScript property "
+ "declaration", invoke.getMethod());
return false;
}
private boolean processIndexer(MethodReader method, CallLocation callLocation, InvokeInstruction invoke) {
if (isProperGetIndexer(method.getDescriptor())) {
Variable result = invoke.getReceiver() != null ? program.createVariable() : null;
addIndexerGet(invoke.getInstance(), wrap(invoke.getArguments().get(0),
method.parameterType(0), invoke.getLocation()), result, invoke.getLocation());
if (result != null) {
result = unwrap(callLocation, result, method.getResultType());
copyVar(result, invoke.getReceiver(), invoke.getLocation());
}
return true;
}
if (isProperSetIndexer(method.getDescriptor())) {
Variable index = wrap(invoke.getArguments().get(0), method.parameterType(0),
invoke.getLocation());
Variable value = wrap(invoke.getArguments().get(1), method.parameterType(1),
invoke.getLocation());
addIndexerSet(invoke.getInstance(), index, value, invoke.getLocation());
return true;
}
diagnostics.error(callLocation, "Method {{m0}} is not a proper native JavaScript indexer "
+ "declaration", invoke.getMethod());
return false;
}
private boolean processMethod(MethodReader method, CallLocation callLocation, InvokeInstruction invoke) {
String name = method.getName();
AnnotationReader methodAnnot = method.getAnnotations().get(JSMethod.class.getName());
if (methodAnnot != null) {
AnnotationValue redefinedMethodName = methodAnnot.getValue("value");
if (redefinedMethodName != null) {
name = redefinedMethodName.getString();
}
}
if (method.getResultType() != ValueType.VOID && !isSupportedType(method.getResultType())) {
diagnostics.error(callLocation, "Method {{m0}} is not a proper native JavaScript method "
+ "declaration", invoke.getMethod());
return false;
}
for (ValueType arg : method.getParameterTypes()) {
if (!isSupportedType(arg)) {
diagnostics.error(callLocation, "Method {{m0}} is not a proper native JavaScript method "
+ "or constructor declaration", invoke.getMethod());
return false;
}
}
Variable result = invoke.getReceiver() != null ? program.createVariable() : null;
InvokeInstruction newInvoke = new InvokeInstruction();
ValueType[] signature = new ValueType[method.parameterCount() + 3];
Arrays.fill(signature, ValueType.object(JSObject.class.getName()));
newInvoke.setMethod(new MethodReference(JS.class.getName(), "invoke", signature));
newInvoke.setType(InvocationType.SPECIAL);
newInvoke.setReceiver(result);
newInvoke.getArguments().add(invoke.getInstance());
newInvoke.getArguments().add(addStringWrap(addString(name, invoke.getLocation()),
invoke.getLocation()));
newInvoke.setLocation(invoke.getLocation());
for (int k = 0; k < invoke.getArguments().size(); ++k) {
Variable arg = wrapArgument(callLocation, invoke.getArguments().get(k),
method.parameterType(k));
newInvoke.getArguments().add(arg);
}
replacement.add(newInvoke);
if (result != null) {
result = unwrap(callLocation, result, method.getResultType());
copyVar(result, invoke.getReceiver(), invoke.getLocation());
}
return true;
}
private void requireJSBody(JSBodyRepository repository, Diagnostics diagnostics, MethodReader methodToProcess) {
if (!repository.processedMethods.add(methodToProcess.getReference())) {
return;
}
processJSBody(repository, diagnostics, methodToProcess);
}
private void processJSBody(JSBodyRepository repository, Diagnostics diagnostics, MethodReader methodToProcess) {
CallLocation location = new CallLocation(methodToProcess.getReference());
boolean isStatic = methodToProcess.hasModifier(ElementModifier.STATIC);
// validate parameter names
AnnotationHolder bodyAnnot = methodToProcess.getAnnotations().get(JSBody.class.getName());
AnnotationReader bodyAnnot = methodToProcess.getAnnotations().get(JSBody.class.getName());
int jsParamCount = bodyAnnot.getValue("params").getList().size();
if (methodToProcess.parameterCount() != jsParamCount) {
diagnostics.error(location, "JSBody method {{m0}} declares " + methodToProcess.parameterCount()
@ -433,10 +509,6 @@ class JavascriptNativeProcessor {
return;
}
// remove annotation and make non-native
methodToProcess.getAnnotations().remove(JSBody.class.getName());
methodToProcess.getModifiers().remove(ElementModifier.NATIVE);
// generate parameter types for original method and validate
int paramCount = methodToProcess.parameterCount();
if (!isStatic) {
@ -445,11 +517,11 @@ class JavascriptNativeProcessor {
ValueType[] paramTypes = new ValueType[paramCount];
int offset = 0;
if (!isStatic) {
ValueType paramType = ValueType.object(cls.getName());
ValueType paramType = ValueType.object(methodToProcess.getOwnerName());
paramTypes[offset++] = paramType;
if (!isSupportedType(paramType)) {
diagnostics.error(location, "Non-static JSBody method {{m0}} is owned by non-JS class {{c1}}",
methodToProcess.getReference(), cls.getName());
methodToProcess.getReference(), methodToProcess.getOwnerName());
}
}
if (methodToProcess.getResultType() != ValueType.VOID && !isSupportedType(methodToProcess.getResultType())) {
@ -470,52 +542,58 @@ class JavascriptNativeProcessor {
: ValueType.parse(JSObject.class);
// create proxy method
MethodHolder proxyMethod = new MethodHolder("$js_body$_" + methodIndexGenerator++, proxyParamTypes);
proxyMethod.getModifiers().add(ElementModifier.NATIVE);
proxyMethod.getModifiers().add(ElementModifier.STATIC);
AnnotationHolder genBodyAnnot = new AnnotationHolder(JSBodyImpl.class.getName());
genBodyAnnot.getValues().put("script", bodyAnnot.getValue("script"));
genBodyAnnot.getValues().put("params", bodyAnnot.getValue("params"));
genBodyAnnot.getValues().put("isStatic", new AnnotationValue(isStatic));
AnnotationHolder generatorAnnot = new AnnotationHolder(GeneratedBy.class.getName());
generatorAnnot.getValues().put("value", new AnnotationValue(ValueType.parse(JSBodyGenerator.class)));
proxyMethod.getAnnotations().add(genBodyAnnot);
proxyMethod.getAnnotations().add(generatorAnnot);
cls.addMethod(proxyMethod);
MethodReference proxyMethod = new MethodReference(methodToProcess.getOwnerName(),
methodToProcess.getName() + "$js_body$_" + methodIndexGenerator++, proxyParamTypes);
String script = bodyAnnot.getValue("script").getString();
String[] parameterNames = bodyAnnot.getValue("params").getList().stream()
.map(ann -> ann.getString())
.toArray(sz -> new String[sz]);
// create program that invokes proxy method
program = new Program();
BasicBlock block = program.createBasicBlock();
for (int i = 0; i < paramCount; ++i) {
program.createVariable();
// Parse JS script
TeaVMErrorReporter errorReporter = new TeaVMErrorReporter(diagnostics,
new CallLocation(methodToProcess.getReference()));
CompilerEnvirons env = new CompilerEnvirons();
env.setRecoverFromErrors(true);
env.setLanguageVersion(Context.VERSION_1_8);
env.setIdeMode(true);
JSParser parser = new JSParser(env, errorReporter);
parser.enterFunction();
AstRoot rootNode;
try {
rootNode = parser.parse(new StringReader(script), null, 0);
} catch (IOException e) {
throw new RuntimeException("IO Error occured", e);
}
if (isStatic) {
program.createVariable();
}
methodToProcess.setProgram(program);
parser.exitFunction();
// Generate invoke instruction
replacement.clear();
InvokeInstruction invoke = new InvokeInstruction();
invoke.setType(InvocationType.SPECIAL);
invoke.setMethod(proxyMethod.getReference());
for (int i = 0; i < paramCount; ++i) {
Variable var = program.variableAt(isStatic ? i + 1 : i);
invoke.getArguments().add(wrapArgument(location, var, paramTypes[i]));
if (errorReporter.hasErrors()) {
repository.emitters.put(proxyMethod, new JSBodyBloatedEmitter(isStatic, proxyMethod,
script, parameterNames));
} else {
repository.emitters.put(proxyMethod, new JSBodyAstEmitter(isStatic, rootNode,
parameterNames));
}
block.getInstructions().addAll(replacement);
block.getInstructions().add(invoke);
repository.methodMap.put(methodToProcess.getReference(), proxyMethod);
}
// Generate return
ExitInstruction exit = new ExitInstruction();
if (methodToProcess.getResultType() != ValueType.VOID) {
replacement.clear();
Variable result = program.createVariable();
invoke.setReceiver(result);
exit.setValueToReturn(unwrap(location, result, methodToProcess.getResultType()));
block.getInstructions().addAll(replacement);
public void createJSMethods(JSBodyRepository repository, ClassHolder cls) {
for (MethodHolder method : cls.getMethods().toArray(new MethodHolder[0])) {
MethodReference methodRef = method.getReference();
if (method.getAnnotations().get(JSBody.class.getName()) != null) {
requireJSBody(repository, diagnostics, method);
if (repository.methodMap.containsKey(method.getReference())) {
MethodReference proxyRef = repository.methodMap.get(methodRef);
MethodHolder proxyMethod = new MethodHolder(proxyRef.getDescriptor());
proxyMethod.getModifiers().add(ElementModifier.NATIVE);
proxyMethod.getModifiers().add(ElementModifier.STATIC);
AnnotationHolder generatorAnnot = new AnnotationHolder(GeneratedBy.class.getName());
generatorAnnot.getValues().put("value",
new AnnotationValue(ValueType.parse(JSBodyGenerator.class)));
proxyMethod.getAnnotations().add(generatorAnnot);
cls.addMethod(proxyMethod);
}
}
}
block.getInstructions().add(exit);
}
private void addPropertyGet(String propertyName, Variable instance, Variable receiver,
@ -971,6 +1049,9 @@ class JavascriptNativeProcessor {
private MethodReader getMethod(MethodReference ref) {
ClassReader cls = classSource.get(ref.getClassName());
if (cls == null) {
return null;
}
MethodReader method = cls.getMethod(ref.getDescriptor());
if (method != null) {
return method;

View File

@ -0,0 +1,26 @@
/*
* 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.io.IOException;
/**
*
* @author Alexey Andreev
*/
interface NameEmitter {
void emit() throws IOException;
}