JS: add remembering source writer, refactor PreparedMethod and Renderer to use it, get rid of $rt_globals

This commit is contained in:
Alexey Andreev 2023-11-01 19:43:52 +01:00
parent 8024d84ed5
commit 6738d6f1f8
28 changed files with 1277 additions and 944 deletions

View File

@ -43,6 +43,5 @@ public class LongNativeGenerator implements Generator {
writer.append("return ").appendFunction(name).append("(").append(context.getParameterName(1))
.append(",").ws()
.append(context.getParameterName(2)).append(");").softNewLine();
context.useLongLibrary();
}
}

View File

@ -34,7 +34,7 @@ public class MathNativeGenerator implements Generator {
}
private void function(GeneratorContext context, SourceWriter writer, String name, int paramCount) {
writer.append("return ").append("$rt_globals.Math").append(".").append(name).append("(");
writer.append("return ").appendGlobal("Math").append(".").append(name).append("(");
for (int i = 0; i < paramCount; ++i) {
if (i > 0) {
writer.append(",").ws();

View File

@ -42,6 +42,7 @@ import java.util.Set;
import java.util.function.Function;
import org.teavm.ast.AsyncMethodNode;
import org.teavm.ast.ControlFlowEntry;
import org.teavm.ast.MethodNode;
import org.teavm.ast.RegularMethodNode;
import org.teavm.ast.analysis.LocationGraphBuilder;
import org.teavm.ast.decompilation.DecompilationException;
@ -59,6 +60,7 @@ import org.teavm.backend.javascript.intrinsics.ref.ReferenceQueueTransformer;
import org.teavm.backend.javascript.intrinsics.ref.WeakReferenceDependencyListener;
import org.teavm.backend.javascript.intrinsics.ref.WeakReferenceGenerator;
import org.teavm.backend.javascript.intrinsics.ref.WeakReferenceTransformer;
import org.teavm.backend.javascript.rendering.MethodBodyRenderer;
import org.teavm.backend.javascript.rendering.Renderer;
import org.teavm.backend.javascript.rendering.RenderingContext;
import org.teavm.backend.javascript.rendering.RenderingUtil;
@ -67,8 +69,6 @@ import org.teavm.backend.javascript.spi.GeneratedBy;
import org.teavm.backend.javascript.spi.Generator;
import org.teavm.backend.javascript.spi.InjectedBy;
import org.teavm.backend.javascript.spi.Injector;
import org.teavm.backend.javascript.spi.ModuleImporter;
import org.teavm.backend.javascript.spi.ModuleImporterContext;
import org.teavm.backend.javascript.spi.VirtualMethodContributor;
import org.teavm.backend.javascript.spi.VirtualMethodContributorContext;
import org.teavm.backend.javascript.templating.JavaScriptTemplateFactory;
@ -100,7 +100,6 @@ import org.teavm.model.ClassReaderSource;
import org.teavm.model.ElementModifier;
import org.teavm.model.FieldReference;
import org.teavm.model.ListableClassHolderSource;
import org.teavm.model.ListableClassReaderSource;
import org.teavm.model.MethodHolder;
import org.teavm.model.MethodReader;
import org.teavm.model.MethodReference;
@ -138,7 +137,6 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost {
private final Map<MethodReference, Injector> methodInjectors = new HashMap<>();
private final List<Function<ProviderContext, Generator>> generatorProviders = new ArrayList<>();
private final List<Function<ProviderContext, Injector>> injectorProviders = new ArrayList<>();
private final List<Function<ProviderContext, ModuleImporter>> moduleImporterProviders = new ArrayList<>();
private final List<RendererListener> rendererListeners = new ArrayList<>();
private DebugInformationEmitter debugEmitter;
private MethodNodeCache astCache = EmptyMethodNodeCache.INSTANCE;
@ -153,6 +151,8 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost {
private final Map<String, String> importedModules = new LinkedHashMap<>();
private Map<String, Generator> generatorCache = new HashMap<>();
private JavaScriptTemplateFactory templateFactory;
private boolean threadLibraryUsed;
private MethodBodyRenderer bodyRenderer;
@Override
public List<ClassHolderTransformer> getTransformers() {
@ -210,11 +210,6 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost {
injectorProviders.add(provider);
}
@Override
public void addModuleImporterProvider(Function<ProviderContext, ModuleImporter> provider) {
moduleImporterProviders.add(provider);
}
/**
* Specifies whether this TeaVM instance uses obfuscation when generating the JavaScript code.
*
@ -404,25 +399,14 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost {
}
private void emit(ListableClassHolderSource classes, Writer writer, BuildTarget target) {
List<PreparedClass> clsNodes = modelToAst(classes);
prepareModules(classes);
if (controller.wasCancelled()) {
return;
}
AliasProvider aliasProvider = obfuscated
? new MinifyingAliasProvider(topLevelNameLimit)
: new DefaultAliasProvider(topLevelNameLimit);
DefaultNamingStrategy naming = new DefaultNamingStrategy(aliasProvider, controller.getUnprocessedClassSource());
OutputSourceWriterBuilder builder = new OutputSourceWriterBuilder(naming);
builder.setMinified(obfuscated);
var sourceWriter = builder.build(writer);
DebugInformationEmitter debugEmitterToUse = debugEmitter;
if (debugEmitterToUse == null) {
debugEmitterToUse = new DummyDebugInformationEmitter();
}
sourceWriter.setDebugInformationEmitter(debugEmitterToUse);
var virtualMethodContributorContext = new VirtualMethodContributorContextImpl(classes);
RenderingContext renderingContext = new RenderingContext(debugEmitterToUse,
@ -437,8 +421,22 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost {
}
};
renderingContext.setMinifying(obfuscated);
Renderer renderer = new Renderer(sourceWriter, asyncMethods, asyncFamilyMethods,
controller.getDiagnostics(), renderingContext);
bodyRenderer = new MethodBodyRenderer(renderingContext, controller.getDiagnostics(), obfuscated,
debugEmitter != null, asyncMethods);
var clsNodes = modelToAst(classes, renderingContext);
if (controller.wasCancelled()) {
return;
}
var builder = new OutputSourceWriterBuilder(naming);
builder.setMinified(obfuscated);
var sourceWriter = builder.build(writer);
sourceWriter.setDebugInformationEmitter(debugEmitterToUse);
var renderer = new Renderer(sourceWriter, asyncMethods, asyncFamilyMethods, renderingContext);
RuntimeRenderer runtimeRenderer = new RuntimeRenderer(classes, sourceWriter);
renderer.setProperties(controller.getProperties());
renderer.setMinifying(obfuscated);
@ -456,9 +454,7 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost {
}
renderer.setDebugEmitter(debugEmitter);
}
for (var entry : methodInjectors.entrySet()) {
renderingContext.addInjector(entry.getKey(), entry.getValue());
}
renderer.prepare(clsNodes);
printWrapperStart(sourceWriter);
@ -467,7 +463,6 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost {
}
int start = sourceWriter.getOffset();
renderer.prepare(clsNodes);
runtimeRenderer.renderRuntime();
sourceWriter.append("var ").append(renderer.getNaming().getScopeName()).ws().append("=").ws()
.append("Object.create(null);").newLine();
@ -479,22 +474,21 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost {
renderer.renderStringConstants();
renderer.renderCompatibilityStubs();
if (renderer.isLongLibraryUsed()) {
runtimeRenderer.renderHandWrittenRuntime("long.js");
}
if (renderer.isThreadLibraryUsed()) {
runtimeRenderer.renderHandWrittenRuntime("long.js");
if (threadLibraryUsed) {
runtimeRenderer.renderHandWrittenRuntime("thread.js");
} else {
runtimeRenderer.renderHandWrittenRuntime("simpleThread.js");
}
for (var entry : controller.getEntryPoints().entrySet()) {
sourceWriter.append("$rt_exports.").append(entry.getKey()).ws().append("=").ws();
sourceWriter.appendFunction("$rt_exports").append(".").append(entry.getKey()).ws().append("=").ws();
var ref = entry.getValue().getMethod();
sourceWriter.appendFunction("$rt_mainStarter").append("(").appendMethodBody(ref);
sourceWriter.append(");").newLine();
sourceWriter.append("$rt_exports.").append(entry.getKey()).append(".").append("javaException")
.ws().append("=").ws().appendFunction("$rt_javaException").append(";").newLine();
sourceWriter.appendFunction("$rt_exports").append(".").append(entry.getKey()).append(".")
.append("javaException").ws().append("=").ws().appendFunction("$rt_javaException")
.append(";").newLine();
}
for (var listener : rendererListeners) {
@ -510,7 +504,7 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost {
private void printWrapperStart(SourceWriter writer) {
writer.append("\"use strict\";").newLine();
printUmdStart(writer);
writer.append("function($rt_globals,").ws().append("$rt_exports");
writer.append("function(").appendFunction("$rt_exports");
for (var moduleName : importedModules.values()) {
writer.append(",").ws().appendFunction(moduleName);
}
@ -518,11 +512,11 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost {
}
private String importModule(String name) {
return importedModules.get(name);
return importedModules.computeIfAbsent(name, n -> "$rt_imported_" + importedModules.size());
}
private void printUmdStart(SourceWriter writer) {
writer.append("(function(root,").ws().append("module)").appendBlockStart();
writer.append("(function(module)").appendBlockStart();
writer.appendIf().append("typeof define").ws().append("===").ws().append("'function'")
.ws().append("&&").ws().append("define.amd)").appendBlockStart();
writer.append("define(['exports'");
@ -534,7 +528,7 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost {
writer.append(',').ws().appendFunction(moduleAlias);
}
writer.append(")").ws().appendBlockStart();
writer.append("module(root,").ws().append("exports");
writer.append("module(exports");
for (var moduleAlias : importedModules.values()) {
writer.append(',').ws().appendFunction(moduleAlias);
}
@ -545,19 +539,21 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost {
.append("===").ws().append("'object'").ws().append("&&").ws()
.append("exports").ws().append("!==").ws().append("null").ws().append("&&").ws()
.append("typeof exports.nodeName").ws().append("!==").ws().append("'string')").appendBlockStart();
writer.append("module(global,").ws().append("exports");
writer.append("module(exports");
for (var moduleName : importedModules.keySet()) {
writer.append(',').ws().append("require(\"").append(RenderingUtil.escapeString(moduleName)).append("\")");
}
writer.append(");").softNewLine();
writer.appendElse();
writer.append("module(root,").ws().append("root);").softNewLine();
writer.appendBlockEnd();
writer.outdent().append("}(typeof self").ws().append("!==").ws().append("'undefined'")
writer.append("module(");
writer.outdent().append("typeof self").ws().append("!==").ws().append("'undefined'")
.ws().append("?").ws().append("self")
.ws().append(":").ws().append("this,")
.ws();
.ws().append(":").ws().append("this");
writer.append(");").softNewLine();
writer.appendBlockEnd();
writer.outdent().append("}(");
}
private void printWrapperEnd(SourceWriter writer) {
@ -592,29 +588,7 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost {
return STATS_NUM_FORMAT.format(size) + " (" + STATS_PERCENT_FORMAT.format((double) size / totalSize) + ")";
}
private void prepareModules(ListableClassHolderSource classes) {
var context = new ImporterContext(classes);
for (var className : classes.getClassNames()) {
var cls = classes.get(className);
for (var method : cls.getMethods()) {
if (method.getModifiers().contains(ElementModifier.ABSTRACT)) {
continue;
}
var providerContext = new ProviderContextImpl(method.getReference());
for (var provider : moduleImporterProviders) {
var importer = provider.apply(providerContext);
if (importer != null) {
context.method = method;
importer.importModules(context);
context.method = null;
}
}
}
}
}
private List<PreparedClass> modelToAst(ListableClassHolderSource classes) {
private List<PreparedClass> modelToAst(ListableClassHolderSource classes, RenderingContext context) {
AsyncMethodFinder asyncFinder = new AsyncMethodFinder(controller.getDependencyInfo().getCallGraph(),
controller.getDependencyInfo());
asyncFinder.find(classes);
@ -634,7 +608,16 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost {
break;
}
}
classNodes.add(decompile(decompiler, cls, classes));
}
for (var entry : methodInjectors.entrySet()) {
context.addInjector(entry.getKey(), entry.getValue());
}
for (String className : getClassOrdering(classes)) {
ClassHolder cls = classes.get(className);
if (controller.wasCancelled()) {
break;
}
classNodes.add(prepare(decompiler, cls, classes));
}
return classNodes;
}
@ -665,7 +648,7 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost {
order.add(className);
}
private PreparedClass decompile(Decompiler decompiler, ClassHolder cls, ClassReaderSource classes) {
private PreparedClass prepare(Decompiler decompiler, ClassHolder cls, ClassReaderSource classes) {
PreparedClass clsNode = new PreparedClass(cls);
for (MethodHolder method : cls.getMethods()) {
if (method.getModifiers().contains(ElementModifier.ABSTRACT)) {
@ -680,14 +663,14 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost {
}
PreparedMethod preparedMethod = method.hasModifier(ElementModifier.NATIVE)
? decompileNative(method, classes)
: decompile(decompiler, method);
? prepareNative(method, classes)
: prepare(decompiler, method);
clsNode.getMethods().add(preparedMethod);
}
return clsNode;
}
private PreparedMethod decompileNative(MethodHolder method, ClassReaderSource classes) {
private PreparedMethod prepareNative(MethodHolder method, ClassReaderSource classes) {
MethodReference reference = method.getReference();
Generator generator = methodGenerators.get(reference);
if (generator == null && !isBootstrap()) {
@ -702,7 +685,13 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost {
name -> createGenerator(name, method, classes));
}
return new PreparedMethod(method, null, generator, asyncMethods.contains(reference), null);
var async = asyncMethods.contains(reference);
bodyRenderer.renderNative(generator, async, reference, method.getModifiers());
threadLibraryUsed |= bodyRenderer.isThreadLibraryUsed();
var result = new PreparedMethod(method.getLevel(), method.getModifiers(), method.getReference(),
bodyRenderer.getBody(), bodyRenderer.getParameters(), async, null, null);
bodyRenderer.clear();
return result;
}
private Generator createGenerator(String name, MethodHolder method, ClassReaderSource classes) {
@ -754,16 +743,25 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost {
}
}
private PreparedMethod decompile(Decompiler decompiler, MethodHolder method) {
private PreparedMethod prepare(Decompiler decompiler, MethodHolder method) {
MethodReference reference = method.getReference();
if (asyncMethods.contains(reference)) {
AsyncMethodNode node = decompileAsync(decompiler, method);
ControlFlowEntry[] cfg = ProgramUtils.getLocationCFG(method.getProgram());
return new PreparedMethod(method, node, null, false, cfg);
ControlFlowEntry[] cfg;
MethodNode node;
var async = asyncMethods.contains(reference);
if (async) {
node = decompileAsync(decompiler, method);
cfg = ProgramUtils.getLocationCFG(method.getProgram());
} else {
AstCacheEntry entry = decompileRegular(decompiler, method);
return new PreparedMethod(method, entry.method, null, false, entry.cfg);
var entry = decompileRegular(decompiler, method);
node = entry.method;
cfg = entry.cfg;
}
bodyRenderer.render(node, async);
var result = new PreparedMethod(method.getLevel(), method.getModifiers(), method.getReference(),
bodyRenderer.getBody(), bodyRenderer.getParameters(), async, cfg, bodyRenderer.getVariables());
threadLibraryUsed |= bodyRenderer.isThreadLibraryUsed();
bodyRenderer.clear();
return result;
}
private AstCacheEntry decompileRegular(Decompiler decompiler, MethodHolder method) {
@ -964,48 +962,4 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost {
return classSource;
}
}
class ImporterContext implements ModuleImporterContext {
private ListableClassReaderSource classSource;
MethodReader method;
ImporterContext(ListableClassReaderSource classSource) {
this.classSource = classSource;
}
@Override
public MethodReader getMethod() {
return method;
}
@Override
public void importModule(String name) {
importedModules.computeIfAbsent(name, n -> "$rt_import_" + importedModules.size());
}
@Override
public ListableClassReaderSource getClassSource() {
return classSource;
}
@Override
public ClassLoader getClassLoader() {
return controller.getClassLoader();
}
@Override
public Properties getProperties() {
return JavaScriptTarget.this.controller.getProperties();
}
@Override
public DependencyInfo getDependency() {
return controller.getDependencyInfo();
}
@Override
public <T> T getService(Class<T> type) {
return controller.getServices().getService(type);
}
}
}

View File

@ -18,7 +18,6 @@ package org.teavm.backend.javascript;
import java.util.function.Function;
import org.teavm.backend.javascript.spi.Generator;
import org.teavm.backend.javascript.spi.Injector;
import org.teavm.backend.javascript.spi.ModuleImporter;
import org.teavm.backend.javascript.spi.VirtualMethodContributor;
import org.teavm.model.MethodReference;
import org.teavm.vm.spi.RendererListener;
@ -33,8 +32,6 @@ public interface TeaVMJavaScriptHost extends TeaVMHostExtension {
void addInjectorProvider(Function<ProviderContext, Injector> provider);
void addModuleImporterProvider(Function<ProviderContext, ModuleImporter> provider);
void add(RendererListener listener);
void addVirtualMethods(VirtualMethodContributor virtualMethods);

View File

@ -35,4 +35,6 @@ public interface AliasProvider {
ScopedName getClassInitAlias(String className);
String getScopeAlias();
void reserveName(String name);
}

View File

@ -134,6 +134,10 @@ public class DefaultAliasProvider implements AliasProvider {
return makeUnique(knownAliases, knowAliasesCounter, "$java");
}
@Override
public void reserveName(String name) {
}
private ScopedName makeUniqueTopLevel(String suggested) {
if (knownAliases.size() < topLevelAliasLimit) {
return new ScopedName(false, makeUnique(knownAliases, knowAliasesCounter, suggested));

View File

@ -130,6 +130,11 @@ public class DefaultNamingStrategy implements NamingStrategy {
return scopeName;
}
@Override
public void reserveName(String name) {
aliasProvider.reserveName(name);
}
private MethodReference getRealMethod(MethodReference methodRef) {
String className = methodRef.getClassName();
while (className != null) {

View File

@ -89,6 +89,11 @@ public class MinifyingAliasProvider implements AliasProvider {
return result;
}
@Override
public void reserveName(String name) {
usedAliases.add(name);
}
private ScopedName createTopLevelName() {
if (usedAliases.size() < topLevelAliasLimit) {
String result;

View File

@ -32,5 +32,9 @@ public interface NameFrequencyConsumer {
void consume(FieldReference field);
void consumeStatic(FieldReference field);
void consumeFunction(String name);
void consumeGlobal(String name);
}

View File

@ -22,6 +22,7 @@ import org.teavm.model.MethodReference;
public class NamingOrderer implements NameFrequencyConsumer {
private Map<String, Entry> entries = new HashMap<>();
private Set<String> reservedNames = new HashSet<>();
@Override
public void consume(MethodReference method) {
@ -96,6 +97,18 @@ public class NamingOrderer implements NameFrequencyConsumer {
entry.frequency++;
}
@Override
public void consumeStatic(FieldReference field) {
var key = "sf:" + field;
var entry = entries.get(key);
if (entry == null) {
entry = new Entry();
entry.operation = naming -> naming.getFullNameFor(field);
entries.put(key, entry);
}
entry.frequency++;
}
@Override
public void consumeFunction(String name) {
String key = "n:" + name;
@ -108,7 +121,15 @@ public class NamingOrderer implements NameFrequencyConsumer {
entry.frequency++;
}
@Override
public void consumeGlobal(String name) {
reservedNames.add(name);
}
public void apply(NamingStrategy naming) {
for (var name : reservedNames) {
naming.reserveName(name);
}
List<Entry> entryList = new ArrayList<>(entries.values());
Collections.sort(entryList, (o1, o2) -> Integer.compare(o2.frequency, o1.frequency));
for (Entry entry : entryList) {

View File

@ -37,4 +37,6 @@ public interface NamingStrategy {
ScopedName getNameForClassInit(String className);
String getScopeName();
void reserveName(String name);
}

View File

@ -124,6 +124,11 @@ public class OutputSourceWriter extends SourceWriter implements LocationProvider
return append(naming.getNameForFunction(name));
}
@Override
public SourceWriter appendGlobal(String name) {
return append(name);
}
@Override
public SourceWriter appendInit(MethodReference method) {
return appendName(naming.getNameForInit(method));

View File

@ -0,0 +1,216 @@
/*
* 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.codegen;
import org.teavm.backend.javascript.templating.SourceFragment;
import org.teavm.model.FieldReference;
import org.teavm.model.MethodDescriptor;
import org.teavm.model.MethodReference;
public class RememberedSource implements SourceFragment {
public static final int FILTER_TEXT = 1;
public static final int FILTER_REF = 2;
public static final int FILTER_DEBUG = 4;
public static final int FILTER_ALL = FILTER_TEXT | FILTER_REF | FILTER_DEBUG;
private byte[] commands;
private String chars;
private int[] intArgs;
private String[] strings;
private FieldReference[] fields;
private MethodDescriptor[] methodDescriptors;
private MethodReference[] methods;
RememberedSource(byte[] commands, String chars, int[] intArgs, String[] strings, FieldReference[] fields,
MethodDescriptor[] methodDescriptors, MethodReference[] methods) {
this.commands = commands;
this.chars = chars;
this.intArgs = intArgs;
this.strings = strings;
this.fields = fields;
this.methodDescriptors = methodDescriptors;
this.methods = methods;
}
public void replay(SourceWriterSink sink, int filter) {
var commandIndex = 0;
var charIndex = 0;
var intArgIndex = 0;
var commands = this.commands;
var intArgs = this.intArgs;
var chars = this.chars;
while (commandIndex < commands.length) {
var command = commands[commandIndex++];
if ((command & 128) != 0) {
var count = 1 + (command & 127);
if ((filter & FILTER_TEXT) != 0) {
sink.append(chars, charIndex, charIndex + count);
}
charIndex += count;
continue;
}
switch (command) {
case RememberingSourceWriter.CLASS:
if ((filter & FILTER_REF) != 0) {
sink.appendClass(strings[intArgs[intArgIndex]]);
}
intArgIndex++;
break;
case RememberingSourceWriter.FIELD:
if ((filter & FILTER_REF) != 0) {
sink.appendField(fields[intArgs[intArgIndex]]);
}
intArgIndex++;
break;
case RememberingSourceWriter.STATIC_FIELD:
if ((filter & FILTER_REF) != 0) {
sink.appendStaticField(fields[intArgs[intArgIndex]]);
}
intArgIndex++;
break;
case RememberingSourceWriter.METHOD:
if ((filter & FILTER_REF) != 0) {
sink.appendMethod(methodDescriptors[intArgs[intArgIndex]]);
}
intArgIndex++;
break;
case RememberingSourceWriter.METHOD_BODY:
if ((filter & FILTER_REF) != 0) {
sink.appendMethodBody(methods[intArgs[intArgIndex]]);
}
intArgIndex++;
break;
case RememberingSourceWriter.FUNCTION:
if ((filter & FILTER_REF) != 0) {
sink.appendFunction(strings[intArgs[intArgIndex]]);
}
intArgIndex++;
break;
case RememberingSourceWriter.GLOBAL:
if ((filter & FILTER_REF) != 0) {
sink.appendGlobal(strings[intArgs[intArgIndex]]);
}
intArgIndex++;
break;
case RememberingSourceWriter.INIT:
if ((filter & FILTER_REF) != 0) {
sink.appendInit(methods[intArgs[intArgIndex]]);
}
intArgIndex++;
break;
case RememberingSourceWriter.CLASS_INIT:
if ((filter & FILTER_REF) != 0) {
sink.appendClassInit(strings[intArgs[intArgIndex]]);
}
intArgIndex++;
break;
case RememberingSourceWriter.NEW_LINE:
if ((filter & FILTER_TEXT) != 0) {
sink.newLine();
}
break;
case RememberingSourceWriter.WS:
if ((filter & FILTER_TEXT) != 0) {
sink.ws();
}
break;
case RememberingSourceWriter.TOKEN_BOUNDARY:
if ((filter & FILTER_TEXT) != 0) {
sink.tokenBoundary();
}
break;
case RememberingSourceWriter.SOFT_NEW_LINE:
if ((filter & FILTER_TEXT) != 0) {
sink.softNewLine();
}
break;
case RememberingSourceWriter.INDENT:
if ((filter & FILTER_TEXT) != 0) {
sink.indent();
}
break;
case RememberingSourceWriter.OUTDENT:
if ((filter & FILTER_TEXT) != 0) {
sink.outdent();
}
break;
case RememberingSourceWriter.EMIT_LOCATION:
if ((filter & FILTER_DEBUG) != 0) {
var fileIndex = intArgs[intArgIndex];
var file = fileIndex >= 0 ? strings[fileIndex] : null;
sink.emitLocation(file, intArgs[intArgIndex + 1]);
}
intArgIndex += 2;
break;
case RememberingSourceWriter.ENTER_LOCATION:
if ((filter & FILTER_DEBUG) != 0) {
sink.enterLocation();
}
break;
case RememberingSourceWriter.EXIT_LOCATION:
if ((filter & FILTER_DEBUG) != 0) {
sink.exitLocation();
}
break;
case RememberingSourceWriter.EMIT_STATEMENT_START:
if ((filter & FILTER_DEBUG) != 0) {
sink.emitStatementStart();
}
break;
case RememberingSourceWriter.EMIT_CLASS:
if ((filter & FILTER_DEBUG) != 0) {
var classIndex = intArgs[intArgIndex];
sink.emitClass(classIndex >= 0 ? strings[classIndex] : null);
}
intArgIndex++;
break;
case RememberingSourceWriter.EMIT_METHOD:
if ((filter & FILTER_DEBUG) != 0) {
var methodIndex = intArgs[intArgIndex];
sink.emitMethod(methodIndex >= 0 ? methodDescriptors[methodIndex] : null);
}
intArgIndex++;
break;
}
}
}
@Override
public void write(SourceWriter writer, int precedence) {
replay(writer, FILTER_ALL);
}
}

View File

@ -0,0 +1,345 @@
/*
* 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.codegen;
import com.carrotsearch.hppc.ByteArrayList;
import com.carrotsearch.hppc.IntArrayList;
import com.carrotsearch.hppc.ObjectIntHashMap;
import com.carrotsearch.hppc.ObjectIntMap;
import java.util.ArrayList;
import java.util.List;
import org.teavm.model.FieldReference;
import org.teavm.model.MethodDescriptor;
import org.teavm.model.MethodReference;
public class RememberingSourceWriter extends SourceWriter {
static final byte CLASS = 0;
static final byte FIELD = 1;
static final byte STATIC_FIELD = 2;
static final byte METHOD = 3;
static final byte METHOD_BODY = 4;
static final byte FUNCTION = 5;
static final byte GLOBAL = 6;
static final byte INIT = 7;
static final byte CLASS_INIT = 8;
static final byte NEW_LINE = 9;
static final byte WS = 10;
static final byte TOKEN_BOUNDARY = 11;
static final byte SOFT_NEW_LINE = 12;
static final byte INDENT = 13;
static final byte OUTDENT = 14;
static final byte EMIT_LOCATION = 15;
static final byte ENTER_LOCATION = 16;
static final byte EXIT_LOCATION = 17;
static final byte EMIT_STATEMENT_START = 18;
static final byte EMIT_METHOD = 19;
static final byte EMIT_CLASS = 20;
private boolean debug;
private StringBuilder sb = new StringBuilder();
private int lastWrittenChar;
private IntArrayList intArgs = new IntArrayList();
private ByteArrayList commands = new ByteArrayList();
private List<String> strings = new ArrayList<>();
private ObjectIntMap<String> stringIndexes = new ObjectIntHashMap<>();
private List<FieldReference> fields = new ArrayList<>();
private ObjectIntMap<FieldReference> fieldIndexes = new ObjectIntHashMap<>();
private List<MethodDescriptor> methodDescriptors = new ArrayList<>();
private ObjectIntMap<MethodDescriptor> methodDescriptorIndexes = new ObjectIntHashMap<>();
private List<MethodReference> methods = new ArrayList<>();
private ObjectIntMap<MethodReference> methodIndexes = new ObjectIntHashMap<>();
public RememberingSourceWriter(boolean debug) {
this.debug = debug;
}
public void clear() {
sb.setLength(0);
lastWrittenChar = 0;
intArgs.clear();
commands.clear();
strings.clear();
stringIndexes.clear();
fields.clear();
fieldIndexes.clear();
methodDescriptors.clear();
methodDescriptorIndexes.clear();
methods.clear();
methodIndexes.clear();
}
@Override
public SourceWriter append(char value) {
sb.append(value);
return this;
}
@Override
public SourceWriter append(CharSequence csq, int start, int end) {
sb.append(csq, start, end);
return this;
}
@Override
public SourceWriter appendClass(String cls) {
flush();
commands.add(CLASS);
appendStringArg(cls);
return this;
}
@Override
public SourceWriter appendField(FieldReference field) {
flush();
commands.add(FIELD);
appendFieldArg(field);
return this;
}
@Override
public SourceWriter appendStaticField(FieldReference field) {
flush();
commands.add(STATIC_FIELD);
appendFieldArg(field);
return this;
}
@Override
public SourceWriter appendMethod(MethodDescriptor method) {
flush();
commands.add(METHOD);
appendMethodDescriptorArg(method);
return this;
}
@Override
public SourceWriter appendMethodBody(MethodReference method) {
flush();
commands.add(METHOD_BODY);
appendMethodArg(method);
return this;
}
@Override
public SourceWriter appendFunction(String name) {
flush();
commands.add(FUNCTION);
appendStringArg(name);
return this;
}
@Override
public SourceWriter appendGlobal(String name) {
flush();
commands.add(GLOBAL);
appendStringArg(name);
return this;
}
@Override
public SourceWriter appendInit(MethodReference method) {
flush();
commands.add(INIT);
appendMethodArg(method);
return this;
}
@Override
public SourceWriter appendClassInit(String className) {
flush();
commands.add(CLASS_INIT);
appendStringArg(className);
return this;
}
@Override
public SourceWriter newLine() {
flush();
commands.add(NEW_LINE);
return this;
}
@Override
public SourceWriter ws() {
flush();
commands.add(WS);
return this;
}
@Override
public SourceWriter tokenBoundary() {
flush();
commands.add(TOKEN_BOUNDARY);
return this;
}
@Override
public SourceWriter softNewLine() {
flush();
commands.add(SOFT_NEW_LINE);
return this;
}
@Override
public SourceWriter indent() {
flush();
commands.add(INDENT);
return this;
}
@Override
public SourceWriter outdent() {
flush();
commands.add(OUTDENT);
return this;
}
@Override
public SourceWriter emitLocation(String fileName, int line) {
if (debug) {
flush();
commands.add(EMIT_LOCATION);
if (fileName == null) {
intArgs.add(-1);
} else {
appendStringArg(fileName);
}
intArgs.add(line);
}
return this;
}
@Override
public SourceWriter enterLocation() {
if (debug) {
flush();
commands.add(ENTER_LOCATION);
}
return this;
}
@Override
public SourceWriter exitLocation() {
if (debug) {
flush();
commands.add(EXIT_LOCATION);
}
return this;
}
@Override
public SourceWriter emitStatementStart() {
if (debug) {
flush();
commands.add(EMIT_STATEMENT_START);
}
return this;
}
@Override
public void emitMethod(MethodDescriptor method) {
if (!debug) {
return;
}
flush();
commands.add(EMIT_METHOD);
if (method == null) {
intArgs.add(-1);
} else {
appendMethodDescriptorArg(method);
}
}
@Override
public void emitClass(String className) {
if (!debug) {
return;
}
flush();
commands.add(EMIT_CLASS);
if (className == null) {
intArgs.add(-1);
} else {
appendStringArg(className);
}
}
public void flush() {
if (lastWrittenChar == sb.length()) {
return;
}
for (var i = lastWrittenChar; i < sb.length(); i += 128) {
var j = Math.min(sb.length(), i + 128);
var n = (j - i) - 1;
commands.add((byte) (128 | n));
}
lastWrittenChar = sb.length();
}
public RememberedSource save() {
flush();
return new RememberedSource(commands.toArray(), sb.toString(), intArgs.toArray(),
!strings.isEmpty() ? strings.toArray(new String[0]) : null,
!fields.isEmpty() ? fields.toArray(new FieldReference[0]) : null,
!methodDescriptors.isEmpty() ? methodDescriptors.toArray(new MethodDescriptor[0]) : null,
!methods.isEmpty() ? methods.toArray(new MethodReference[0]) : null);
}
private void appendStringArg(String arg) {
var index = stringIndexes.getOrDefault(arg, -1);
if (index < 0) {
index = strings.size();
stringIndexes.put(arg, index);
strings.add(arg);
}
intArgs.add(index);
}
private void appendFieldArg(FieldReference arg) {
var index = fieldIndexes.getOrDefault(arg, -1);
if (index < 0) {
index = fields.size();
fieldIndexes.put(arg, index);
fields.add(arg);
}
intArgs.add(index);
}
private void appendMethodDescriptorArg(MethodDescriptor arg) {
var index = methodDescriptorIndexes.getOrDefault(arg, -1);
if (index < 0) {
index = methodDescriptors.size();
methodDescriptorIndexes.put(arg, index);
methodDescriptors.add(arg);
}
intArgs.add(index);
}
private void appendMethodArg(MethodReference arg) {
var index = methodIndexes.getOrDefault(arg, -1);
if (index < 0) {
index = methods.size();
methodIndexes.put(arg, index);
methods.add(arg);
}
intArgs.add(index);
}
}

View File

@ -20,7 +20,7 @@ import org.teavm.model.MethodDescriptor;
import org.teavm.model.MethodReference;
import org.teavm.model.ValueType;
public abstract class SourceWriter implements Appendable {
public abstract class SourceWriter implements Appendable, SourceWriterSink {
public SourceWriter append(String value) {
append((CharSequence) value);
return this;
@ -62,22 +62,27 @@ public abstract class SourceWriter implements Appendable {
@Override
public abstract SourceWriter append(CharSequence csq, int start, int end);
@Override
public abstract SourceWriter appendClass(String cls);
public SourceWriter appendClass(Class<?> cls) {
return appendClass(cls.getName());
}
@Override
public abstract SourceWriter appendField(FieldReference field);
@Override
public abstract SourceWriter appendStaticField(FieldReference field);
@Override
public abstract SourceWriter appendMethod(MethodDescriptor method);
public SourceWriter appendMethod(String name, Class<?>... params) {
return appendMethod(new MethodDescriptor(name, params));
}
@Override
public abstract SourceWriter appendMethodBody(MethodReference method);
public SourceWriter appendMethodBody(String className, String name, ValueType... params) {
@ -88,33 +93,51 @@ public abstract class SourceWriter implements Appendable {
return appendMethodBody(new MethodReference(cls, name, params));
}
@Override
public abstract SourceWriter appendFunction(String name);
@Override
public abstract SourceWriter appendGlobal(String name);
@Override
public abstract SourceWriter appendInit(MethodReference method);
@Override
public abstract SourceWriter appendClassInit(String className);
@Override
public abstract SourceWriter newLine();
@Override
public abstract SourceWriter ws();
@Override
public abstract SourceWriter tokenBoundary();
@Override
public abstract SourceWriter softNewLine();
@Override
public abstract SourceWriter indent();
@Override
public abstract SourceWriter outdent();
@Override
public abstract SourceWriter emitLocation(String fileName, int line);
@Override
public abstract SourceWriter enterLocation();
@Override
public abstract SourceWriter exitLocation();
@Override
public abstract SourceWriter emitStatementStart();
@Override
public abstract void emitMethod(MethodDescriptor method);
@Override
public abstract void emitClass(String className);
}

View File

@ -0,0 +1,108 @@
/*
* 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.codegen;
import org.teavm.model.FieldReference;
import org.teavm.model.MethodDescriptor;
import org.teavm.model.MethodReference;
public interface SourceWriterSink {
default SourceWriterSink append(CharSequence csq, int start, int end) {
return this;
}
default SourceWriterSink appendClass(String cls) {
return this;
}
default SourceWriterSink appendField(FieldReference field) {
return this;
}
default SourceWriterSink appendStaticField(FieldReference field) {
return this;
}
default SourceWriterSink appendMethod(MethodDescriptor method) {
return this;
}
default SourceWriterSink appendMethodBody(MethodReference method) {
return this;
}
default SourceWriterSink appendFunction(String name) {
return this;
}
default SourceWriterSink appendGlobal(String name) {
return this;
}
default SourceWriterSink appendInit(MethodReference method) {
return this;
}
default SourceWriterSink appendClassInit(String className) {
return this;
}
default SourceWriterSink newLine() {
return this;
}
default SourceWriterSink ws() {
return this;
}
default SourceWriterSink tokenBoundary() {
return this;
}
default SourceWriterSink softNewLine() {
return this;
}
default SourceWriterSink indent() {
return this;
}
default SourceWriterSink outdent() {
return this;
}
default SourceWriterSink emitLocation(String fileName, int line) {
return this;
}
default SourceWriterSink enterLocation() {
return this;
}
default SourceWriterSink exitLocation() {
return this;
}
default SourceWriterSink emitStatementStart() {
return this;
}
default void emitMethod(MethodDescriptor method) {
}
default void emitClass(String className) {
}
}

View File

@ -15,27 +15,33 @@
*/
package org.teavm.backend.javascript.decompile;
import java.util.Set;
import org.teavm.ast.ControlFlowEntry;
import org.teavm.ast.MethodNode;
import org.teavm.backend.javascript.spi.Generator;
import org.teavm.model.MethodHolder;
import org.teavm.backend.javascript.codegen.RememberedSource;
import org.teavm.model.AccessLevel;
import org.teavm.model.ElementModifier;
import org.teavm.model.MethodReference;
public class PreparedMethod {
public final MethodHolder methodHolder;
public final AccessLevel accessLevel;
public final Set<ElementModifier> modifiers;
public final MethodReference reference;
public final MethodNode node;
public final Generator generator;
public final RememberedSource body;
public final RememberedSource parameters;
public final boolean async;
public final ControlFlowEntry[] cfg;
public final PreparedVariable[] variables;
public PreparedMethod(MethodHolder method, MethodNode node, Generator generator, boolean async,
ControlFlowEntry[] cfg) {
this.reference = method.getReference();
this.methodHolder = method;
this.node = node;
this.generator = generator;
public PreparedMethod(AccessLevel accessLevel, Set<ElementModifier> modifiers, MethodReference reference,
RememberedSource body, RememberedSource parameters, boolean async, ControlFlowEntry[] cfg,
PreparedVariable[] variables) {
this.accessLevel = accessLevel;
this.modifiers = modifiers;
this.reference = reference;
this.body = body;
this.parameters = parameters;
this.async = async;
this.cfg = cfg;
this.variables = variables;
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2023 konsoletyper.
* 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.
@ -13,8 +13,20 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.backend.javascript.spi;
package org.teavm.backend.javascript.decompile;
public interface ModuleImporter {
void importModules(ModuleImporterContext context);
import org.teavm.debugging.information.DebugInformationEmitter;
public class PreparedVariable {
public final String[] names;
public final String jsName;
public PreparedVariable(String[] names, String jsName) {
this.names = names;
this.jsName = jsName;
}
public void emit(DebugInformationEmitter emitter) {
emitter.emitVariable(names, jsName);
}
}

View File

@ -30,6 +30,6 @@ public class DefaultGlobalNameWriter implements Function<String, NameEmitter> {
if (s.startsWith("$rt_") || s.startsWith("Long_") || s.equals("Long")) {
return prec -> writer.appendFunction(s);
}
return prec -> writer.append("$rt_globals").append('.').append(s);
return prec -> writer.appendGlobal(s);
}
}

View File

@ -0,0 +1,361 @@
/*
* 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.rendering;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import org.teavm.ast.AsyncMethodNode;
import org.teavm.ast.AsyncMethodPart;
import org.teavm.ast.MethodNode;
import org.teavm.ast.MethodNodeVisitor;
import org.teavm.ast.RegularMethodNode;
import org.teavm.ast.VariableNode;
import org.teavm.backend.javascript.codegen.RememberedSource;
import org.teavm.backend.javascript.codegen.RememberingSourceWriter;
import org.teavm.backend.javascript.codegen.SourceWriter;
import org.teavm.backend.javascript.decompile.PreparedVariable;
import org.teavm.backend.javascript.spi.Generator;
import org.teavm.backend.javascript.spi.GeneratorContext;
import org.teavm.dependency.DependencyInfo;
import org.teavm.diagnostics.Diagnostics;
import org.teavm.model.ClassReaderSource;
import org.teavm.model.ElementModifier;
import org.teavm.model.ListableClassReaderSource;
import org.teavm.model.MethodReference;
import org.teavm.model.ValueType;
public class MethodBodyRenderer implements MethodNodeVisitor, GeneratorContext {
private RenderingContext context;
private Diagnostics diagnostics;
private boolean minifying;
private boolean async;
private Set<MethodReference> asyncMethods;
private RememberingSourceWriter writer;
private StatementRenderer statementRenderer;
private boolean threadLibraryUsed;
private RememberedSource body;
private RememberedSource parameters;
private PreparedVariable[] variables;
public MethodBodyRenderer(RenderingContext context, Diagnostics diagnostics, boolean minifying, boolean debug,
Set<MethodReference> asyncMethods) {
this.context = context;
this.diagnostics = diagnostics;
this.minifying = minifying;
this.asyncMethods = asyncMethods;
writer = new RememberingSourceWriter(debug);
statementRenderer = new StatementRenderer(context, writer);
}
public boolean isThreadLibraryUsed() {
return threadLibraryUsed;
}
@Override
public DependencyInfo getDependency() {
return context.getDependencyInfo();
}
public void renderNative(Generator generator, boolean async, MethodReference reference,
Set<ElementModifier> modifiers) {
threadLibraryUsed = false;
this.async = async;
statementRenderer.setAsync(async);
renderParameters(reference, modifiers);
generator.generate(this, writer, reference);
body = writer.save();
writer.clear();
}
public void render(MethodNode node, boolean async) {
threadLibraryUsed = false;
this.async = async;
statementRenderer.setAsync(async);
statementRenderer.setCurrentMethod(node);
renderParameters(node.getReference(), node.getModifiers());
node.acceptVisitor(this);
body = writer.save();
prepareVariables(node);
writer.clear();
}
public PreparedVariable[] getVariables() {
return variables;
}
private void prepareVariables(MethodNode method) {
var variables = new ArrayList<PreparedVariable>();
for (int i = 0; i < method.getVariables().size(); ++i) {
variables.add(new PreparedVariable(new String[] { method.getVariables().get(i).getName() },
statementRenderer.variableName(i)));
}
this.variables = variables.toArray(new PreparedVariable[0]);
}
public RememberedSource getBody() {
return body;
}
public RememberedSource getParameters() {
return parameters;
}
public void clear() {
body = null;
parameters = null;
variables = null;
}
private void renderParameters(MethodReference reference, Set<ElementModifier> modifiers) {
int startParam = 0;
if (modifiers.contains(ElementModifier.STATIC)) {
startParam = 1;
}
for (int i = startParam; i <= reference.parameterCount(); ++i) {
if (i > startParam) {
writer.append(",").ws();
}
writer.append(statementRenderer.variableName(i));
}
parameters = writer.save();
writer.clear();
}
private void appendMonitor(StatementRenderer statementRenderer, MethodNode methodNode) {
if (methodNode.getModifiers().contains(ElementModifier.STATIC)) {
writer.appendFunction("$rt_cls").append("(")
.appendClass(methodNode.getReference().getClassName()).append(")");
} else {
writer.append(statementRenderer.variableName(0));
}
}
@Override
public void visit(RegularMethodNode method) {
statementRenderer.setAsync(false);
this.async = false;
int variableCount = 0;
for (VariableNode var : method.getVariables()) {
variableCount = Math.max(variableCount, var.getIndex() + 1);
}
TryCatchFinder tryCatchFinder = new TryCatchFinder();
method.getBody().acceptVisitor(tryCatchFinder);
boolean hasTryCatch = tryCatchFinder.tryCatchFound;
List<String> variableNames = new ArrayList<>();
for (int i = method.getReference().parameterCount() + 1; i < variableCount; ++i) {
variableNames.add(statementRenderer.variableName(i));
}
if (hasTryCatch) {
variableNames.add("$$je");
}
if (!variableNames.isEmpty()) {
writer.append("var ");
for (int i = 0; i < variableNames.size(); ++i) {
if (i > 0) {
writer.append(",").ws();
}
writer.append(variableNames.get(i));
}
writer.append(";").softNewLine();
}
statementRenderer.setEnd(true);
statementRenderer.setCurrentPart(0);
if (method.getModifiers().contains(ElementModifier.SYNCHRONIZED)) {
writer.appendMethodBody(NameFrequencyEstimator.MONITOR_ENTER_SYNC_METHOD);
writer.append("(");
appendMonitor(statementRenderer, method);
writer.append(");").softNewLine();
writer.append("try").ws().append("{").softNewLine().indent();
}
method.getBody().acceptVisitor(statementRenderer);
if (method.getModifiers().contains(ElementModifier.SYNCHRONIZED)) {
writer.outdent().append("}").ws().append("finally").ws().append("{").indent().softNewLine();
writer.appendMethodBody(NameFrequencyEstimator.MONITOR_EXIT_SYNC_METHOD);
writer.append("(");
appendMonitor(statementRenderer, method);
writer.append(");").softNewLine();
writer.outdent().append("}").softNewLine();
}
}
@Override
public void visit(AsyncMethodNode methodNode) {
threadLibraryUsed = true;
statementRenderer.setAsync(true);
this.async = true;
MethodReference ref = methodNode.getReference();
int variableCount = 0;
for (VariableNode var : methodNode.getVariables()) {
variableCount = Math.max(variableCount, var.getIndex() + 1);
}
List<String> variableNames = new ArrayList<>();
for (int i = ref.parameterCount() + 1; i < variableCount; ++i) {
variableNames.add(statementRenderer.variableName(i));
}
TryCatchFinder tryCatchFinder = new TryCatchFinder();
for (AsyncMethodPart part : methodNode.getBody()) {
if (!tryCatchFinder.tryCatchFound) {
part.getStatement().acceptVisitor(tryCatchFinder);
}
}
boolean hasTryCatch = tryCatchFinder.tryCatchFound;
if (hasTryCatch) {
variableNames.add("$$je");
}
variableNames.add(context.pointerName());
variableNames.add(context.tempVarName());
writer.append("var ");
for (int i = 0; i < variableNames.size(); ++i) {
if (i > 0) {
writer.append(",").ws();
}
writer.append(variableNames.get(i));
}
writer.append(";").softNewLine();
int firstToSave = 0;
if (methodNode.getModifiers().contains(ElementModifier.STATIC)) {
firstToSave = 1;
}
String popName = minifying ? "l" : "pop";
String pushName = minifying ? "s" : "push";
writer.append(context.pointerName()).ws().append('=').ws().append("0;").softNewLine();
writer.append("if").ws().append("(").appendFunction("$rt_resuming").append("())").ws()
.append("{").indent().softNewLine();
writer.append("var ").append(context.threadName()).ws().append('=').ws()
.appendFunction("$rt_nativeThread").append("();").softNewLine();
writer.append(context.pointerName()).ws().append('=').ws().append(context.threadName()).append(".")
.append(popName).append("();");
for (int i = variableCount - 1; i >= firstToSave; --i) {
writer.append(statementRenderer.variableName(i)).ws().append('=').ws()
.append(context.threadName())
.append(".").append(popName).append("();");
}
writer.softNewLine();
writer.outdent().append("}").softNewLine();
if (methodNode.getModifiers().contains(ElementModifier.SYNCHRONIZED)) {
writer.append("try").ws().append('{').indent().softNewLine();
}
Renderer.renderAsyncPrologue(writer, context);
for (int i = 0; i < methodNode.getBody().size(); ++i) {
writer.append("case ").append(i).append(":").indent().softNewLine();
if (i == 0 && methodNode.getModifiers().contains(ElementModifier.SYNCHRONIZED)) {
writer.appendMethodBody(NameFrequencyEstimator.MONITOR_ENTER_METHOD);
writer.append("(");
appendMonitor(statementRenderer, methodNode);
writer.append(");").softNewLine();
statementRenderer.emitSuspendChecker();
}
AsyncMethodPart part = methodNode.getBody().get(i);
statementRenderer.setEnd(true);
statementRenderer.setCurrentPart(i);
part.getStatement().acceptVisitor(statementRenderer);
writer.outdent();
}
Renderer.renderAsyncEpilogue(writer);
if (methodNode.getModifiers().contains(ElementModifier.SYNCHRONIZED)) {
writer.outdent().append("}").ws().append("finally").ws().append('{').indent().softNewLine();
writer.append("if").ws().append("(!").appendFunction("$rt_suspending").append("())")
.ws().append("{").indent().softNewLine();
writer.appendMethodBody(NameFrequencyEstimator.MONITOR_EXIT_METHOD).append("(");
appendMonitor(statementRenderer, methodNode);
writer.append(");").softNewLine();
writer.outdent().append('}').softNewLine();
writer.outdent().append('}').softNewLine();
}
writer.appendFunction("$rt_nativeThread").append("().").append(pushName).append("(");
for (int i = firstToSave; i < variableCount; ++i) {
writer.append(statementRenderer.variableName(i)).append(',').ws();
}
writer.append(context.pointerName()).append(");");
writer.softNewLine();
}
@Override
public String getParameterName(int index) {
return statementRenderer.variableName(index);
}
@Override
public ListableClassReaderSource getClassSource() {
return context.getClassSource();
}
@Override
public ClassReaderSource getInitialClassSource() {
return context.getInitialClassSource();
}
@Override
public ClassLoader getClassLoader() {
return context.getClassLoader();
}
@Override
public Properties getProperties() {
return new Properties(context.getProperties());
}
@Override
public <T> T getService(Class<T> type) {
return context.getServices().getService(type);
}
@Override
public boolean isAsync() {
return async;
}
@Override
public boolean isAsync(MethodReference method) {
return asyncMethods.contains(method);
}
@Override
public Diagnostics getDiagnostics() {
return diagnostics;
}
@Override
public void typeToClassString(SourceWriter writer, ValueType type) {
context.typeToClsString(writer, type);
}
@Override
public boolean isDynamicInitializer(String className) {
return context.isDynamicInitializer(className);
}
@Override
public String importModule(String name) {
return context.importModule(name);
}
}

View File

@ -16,35 +16,11 @@
package org.teavm.backend.javascript.rendering;
import java.util.Set;
import org.teavm.ast.ArrayFromDataExpr;
import org.teavm.ast.AssignmentStatement;
import org.teavm.ast.AsyncMethodNode;
import org.teavm.ast.AsyncMethodPart;
import org.teavm.ast.BinaryExpr;
import org.teavm.ast.BoundCheckExpr;
import org.teavm.ast.CastExpr;
import org.teavm.ast.ConstantExpr;
import org.teavm.ast.InitClassStatement;
import org.teavm.ast.InstanceOfExpr;
import org.teavm.ast.InvocationExpr;
import org.teavm.ast.MethodNodeVisitor;
import org.teavm.ast.MonitorEnterStatement;
import org.teavm.ast.MonitorExitStatement;
import org.teavm.ast.NewArrayExpr;
import org.teavm.ast.NewExpr;
import org.teavm.ast.NewMultiArrayExpr;
import org.teavm.ast.OperationType;
import org.teavm.ast.PrimitiveCastExpr;
import org.teavm.ast.QualificationExpr;
import org.teavm.ast.RecursiveVisitor;
import org.teavm.ast.RegularMethodNode;
import org.teavm.ast.ThrowStatement;
import org.teavm.ast.TryCatchStatement;
import org.teavm.ast.UnaryExpr;
import org.teavm.backend.javascript.codegen.NameFrequencyConsumer;
import org.teavm.backend.javascript.codegen.RememberedSource;
import org.teavm.backend.javascript.codegen.SourceWriterSink;
import org.teavm.backend.javascript.decompile.PreparedClass;
import org.teavm.backend.javascript.decompile.PreparedMethod;
import org.teavm.model.ClassReader;
import org.teavm.model.ClassReaderSource;
import org.teavm.model.ElementModifier;
import org.teavm.model.FieldHolder;
@ -54,7 +30,7 @@ import org.teavm.model.MethodReader;
import org.teavm.model.MethodReference;
import org.teavm.model.ValueType;
class NameFrequencyEstimator extends RecursiveVisitor implements MethodNodeVisitor {
class NameFrequencyEstimator implements SourceWriterSink {
static final MethodReference MONITOR_ENTER_METHOD = new MethodReference(Object.class,
"monitorEnter", Object.class, void.class);
static final MethodReference MONITOR_ENTER_SYNC_METHOD = new MethodReference(Object.class,
@ -67,19 +43,13 @@ class NameFrequencyEstimator extends RecursiveVisitor implements MethodNodeVisit
private final NameFrequencyConsumer consumer;
private final ClassReaderSource classSource;
private boolean async;
private final Set<MethodReference> injectedMethods;
private final Set<MethodReference> asyncFamilyMethods;
private final boolean strict;
NameFrequencyEstimator(NameFrequencyConsumer consumer, ClassReaderSource classSource,
Set<MethodReference> injectedMethods, Set<MethodReference> asyncFamilyMethods,
boolean strict) {
Set<MethodReference> asyncFamilyMethods) {
this.consumer = consumer;
this.classSource = classSource;
this.injectedMethods = injectedMethods;
this.asyncFamilyMethods = asyncFamilyMethods;
this.strict = strict;
}
public void estimate(PreparedClass cls) {
@ -102,11 +72,11 @@ class NameFrequencyEstimator extends RecursiveVisitor implements MethodNodeVisit
if (asyncFamilyMethods.contains(method.reference)) {
consumer.consume(method.reference);
}
if (clinit != null && (method.methodHolder.getModifiers().contains(ElementModifier.STATIC)
if (clinit != null && (method.modifiers.contains(ElementModifier.STATIC)
|| method.reference.getName().equals("<init>"))) {
consumer.consume(method.reference);
}
if (!method.methodHolder.getModifiers().contains(ElementModifier.STATIC)) {
if (!method.modifiers.contains(ElementModifier.STATIC)) {
consumer.consume(method.reference.getDescriptor());
consumer.consume(method.reference);
}
@ -117,9 +87,7 @@ class NameFrequencyEstimator extends RecursiveVisitor implements MethodNodeVisit
consumer.consumeFunction("$rt_invalidPointer");
}
if (method.node != null) {
method.node.acceptVisitor(this);
}
method.body.replay(this, RememberedSource.FILTER_REF);
}
if (clinit != null) {
@ -149,367 +117,56 @@ class NameFrequencyEstimator extends RecursiveVisitor implements MethodNodeVisit
}
@Override
public void visit(RegularMethodNode methodNode) {
async = false;
methodNode.getBody().acceptVisitor(this);
public SourceWriterSink appendClass(String cls) {
consumer.consume(cls);
return this;
}
@Override
public void visit(AsyncMethodNode methodNode) {
async = true;
for (AsyncMethodPart part : methodNode.getBody()) {
part.getStatement().acceptVisitor(this);
}
public SourceWriterSink appendField(FieldReference field) {
consumer.consume(field);
return this;
}
@Override
public void visit(AssignmentStatement statement) {
super.visit(statement);
if (statement.isAsync()) {
consumer.consumeFunction("$rt_suspending");
}
public SourceWriterSink appendStaticField(FieldReference field) {
consumer.consumeStatic(field);
return this;
}
@Override
public void visit(ThrowStatement statement) {
statement.getException().acceptVisitor(this);
consumer.consumeFunction("$rt_throw");
public SourceWriterSink appendMethod(MethodDescriptor method) {
consumer.consume(method);
return this;
}
@Override
public void visit(InitClassStatement statement) {
consumer.consumeClassInit(statement.getClassName());
public SourceWriterSink appendMethodBody(MethodReference method) {
consumer.consume(method);
return this;
}
@Override
public void visit(TryCatchStatement statement) {
super.visit(statement);
if (statement.getExceptionType() != null) {
consumer.consume(statement.getExceptionType());
}
consumer.consumeFunction("$rt_wrapException");
public SourceWriterSink appendFunction(String name) {
consumer.consumeFunction(name);
return this;
}
@Override
public void visit(MonitorEnterStatement statement) {
super.visit(statement);
if (async) {
consumer.consume(MONITOR_ENTER_METHOD);
consumer.consumeFunction("$rt_suspending");
} else {
consumer.consume(MONITOR_ENTER_SYNC_METHOD);
}
public SourceWriterSink appendGlobal(String name) {
consumer.consumeGlobal(name);
return this;
}
@Override
public void visit(MonitorExitStatement statement) {
super.visit(statement);
if (async) {
consumer.consume(MONITOR_EXIT_METHOD);
} else {
consumer.consume(MONITOR_EXIT_SYNC_METHOD);
}
public SourceWriterSink appendInit(MethodReference method) {
consumer.consumeInit(method);
return this;
}
@Override
public void visit(BinaryExpr expr) {
super.visit(expr);
if (expr.getType() == OperationType.LONG) {
switch (expr.getOperation()) {
case ADD:
consumer.consumeFunction("Long_add");
break;
case SUBTRACT:
consumer.consumeFunction("Long_sub");
break;
case MULTIPLY:
consumer.consumeFunction("Long_mul");
break;
case DIVIDE:
consumer.consumeFunction("Long_div");
break;
case MODULO:
consumer.consumeFunction("Long_rem");
break;
case BITWISE_OR:
consumer.consumeFunction("Long_or");
break;
case BITWISE_AND:
consumer.consumeFunction("Long_and");
break;
case BITWISE_XOR:
consumer.consumeFunction("Long_xor");
break;
case LEFT_SHIFT:
consumer.consumeFunction("Long_shl");
break;
case RIGHT_SHIFT:
consumer.consumeFunction("Long_shr");
break;
case UNSIGNED_RIGHT_SHIFT:
consumer.consumeFunction("Long_shru");
break;
case COMPARE:
consumer.consumeFunction("Long_compare");
break;
case EQUALS:
consumer.consumeFunction("Long_eq");
break;
case NOT_EQUALS:
consumer.consumeFunction("Long_ne");
break;
case LESS:
consumer.consumeFunction("Long_lt");
break;
case LESS_OR_EQUALS:
consumer.consumeFunction("Long_le");
break;
case GREATER:
consumer.consumeFunction("Long_gt");
break;
case GREATER_OR_EQUALS:
consumer.consumeFunction("Long_ge");
break;
}
return;
}
switch (expr.getOperation()) {
case COMPARE:
consumer.consumeFunction("$rt_compare");
break;
case MULTIPLY:
if (expr.getType() == OperationType.INT && !RenderingUtil.isSmallInteger(expr.getFirstOperand())
&& !RenderingUtil.isSmallInteger(expr.getSecondOperand())) {
consumer.consumeFunction("$rt_imul");
}
break;
default:
break;
}
}
@Override
public void visit(UnaryExpr expr) {
super.visit(expr);
switch (expr.getOperation()) {
case NULL_CHECK:
consumer.consumeFunction("$rt_nullCheck");
break;
case NEGATE:
if (expr.getType() == OperationType.LONG) {
consumer.consumeFunction("Long_neg");
}
break;
case NOT:
if (expr.getType() == OperationType.LONG) {
consumer.consumeFunction("Long_not");
}
break;
default:
break;
}
}
@Override
public void visit(PrimitiveCastExpr expr) {
super.visit(expr);
if (expr.getSource() == OperationType.LONG) {
if (expr.getTarget() == OperationType.DOUBLE || expr.getTarget() == OperationType.FLOAT) {
consumer.consumeFunction("Long_toNumber");
} else if (expr.getTarget() == OperationType.INT) {
consumer.consumeFunction("Long_lo");
}
} else if (expr.getTarget() == OperationType.LONG) {
switch (expr.getSource()) {
case INT:
consumer.consumeFunction("Long_fromInt");
break;
case FLOAT:
case DOUBLE:
consumer.consumeFunction("Long_fromNUmber");
break;
}
}
}
@Override
public void visit(ConstantExpr expr) {
if (expr.getValue() instanceof ValueType) {
visitType((ValueType) expr.getValue());
} else if (expr.getValue() instanceof String) {
consumer.consumeFunction("$rt_s");
} else if (expr.getValue() instanceof Long) {
long value = (Long) expr.getValue();
if (value == 0) {
consumer.consumeFunction("Long_ZERO");
} else if ((int) value == value) {
consumer.consumeFunction("Long_fromInt");
} else {
consumer.consumeFunction("Long_create");
}
}
}
private void visitType(ValueType type) {
while (type instanceof ValueType.Array) {
type = ((ValueType.Array) type).getItemType();
}
if (type instanceof ValueType.Object) {
String clsName = ((ValueType.Object) type).getClassName();
consumer.consume(clsName);
consumer.consumeFunction("$rt_cls");
}
}
@Override
public void visit(InvocationExpr expr) {
super.visit(expr);
if (injectedMethods.contains(expr.getMethod())) {
return;
}
switch (expr.getType()) {
case SPECIAL:
case STATIC:
consumer.consume(expr.getMethod());
break;
case CONSTRUCTOR:
consumer.consumeInit(expr.getMethod());
break;
case DYNAMIC:
consumer.consume(expr.getMethod().getDescriptor());
break;
}
}
@Override
public void visit(QualificationExpr expr) {
super.visit(expr);
consumer.consume(expr.getField());
}
@Override
public void visit(NewExpr expr) {
super.visit(expr);
consumer.consume(expr.getConstructedClass());
}
@Override
public void visit(NewArrayExpr expr) {
super.visit(expr);
visitType(expr.getType());
if (expr.getType() instanceof ValueType.Primitive) {
switch (((ValueType.Primitive) expr.getType()).getKind()) {
case BOOLEAN:
consumer.consumeFunction("$rt_createBooleanArray");
break;
case BYTE:
consumer.consumeFunction("$rt_createByteArray");
break;
case SHORT:
consumer.consumeFunction("$rt_createShortArray");
break;
case CHARACTER:
consumer.consumeFunction("$rt_createCharArray");
break;
case INTEGER:
consumer.consumeFunction("$rt_createIntArray");
break;
case LONG:
consumer.consumeFunction("$rt_createLongArray");
break;
case FLOAT:
consumer.consumeFunction("$rt_createFloatArray");
break;
case DOUBLE:
consumer.consumeFunction("$rt_createDoubleArray");
break;
}
} else {
consumer.consumeFunction("$rt_createArray");
}
}
@Override
public void visit(ArrayFromDataExpr expr) {
super.visit(expr);
visitType(expr.getType());
if (expr.getType() instanceof ValueType.Primitive) {
switch (((ValueType.Primitive) expr.getType()).getKind()) {
case BOOLEAN:
consumer.consumeFunction("$rt_createBooleanArrayFromData");
break;
case BYTE:
consumer.consumeFunction("$rt_createByteArrayFromData");
break;
case SHORT:
consumer.consumeFunction("$rt_createShortArrayFromData");
break;
case CHARACTER:
consumer.consumeFunction("$rt_createCharArrayFromData");
break;
case INTEGER:
consumer.consumeFunction("$rt_createIntArrayFromData");
break;
case LONG:
consumer.consumeFunction("$rt_createLongArrayFromData");
break;
case FLOAT:
consumer.consumeFunction("$rt_createFloatArrayFromData");
break;
case DOUBLE:
consumer.consumeFunction("$rt_createDoubleArrayFromData");
break;
}
} else {
consumer.consumeFunction("$rt_createArrayFromData");
}
}
@Override
public void visit(NewMultiArrayExpr expr) {
super.visit(expr);
visitType(expr.getType());
}
@Override
public void visit(InstanceOfExpr expr) {
super.visit(expr);
visitType(expr.getType());
if (!isClass(expr.getType())) {
consumer.consumeFunction("$rt_isInstance");
}
}
@Override
public void visit(CastExpr expr) {
super.visit(expr);
if (strict) {
visitType(expr.getTarget());
if (isClass(expr.getTarget())) {
consumer.consumeFunction("$rt_castToClass");
} else {
consumer.consumeFunction("$rt_castToInterface");
}
}
}
private boolean isClass(ValueType type) {
if (!(type instanceof ValueType.Object)) {
return false;
}
String className = ((ValueType.Object) type).getClassName();
ClassReader cls = classSource.get(className);
return cls != null && !cls.hasModifier(ElementModifier.INTERFACE);
}
@Override
public void visit(BoundCheckExpr expr) {
super.visit(expr);
if (expr.getArray() != null && expr.getIndex() != null) {
consumer.consumeFunction("$rt_checkBounds");
} else if (expr.getArray() != null) {
consumer.consumeFunction("$rt_checkUpperBound");
} else if (expr.isLower()) {
consumer.consumeFunction("$rt_checkLowerBound");
}
public SourceWriterSink appendClassInit(String className) {
consumer.consumeClassInit(className);
return this;
}
}

View File

@ -27,30 +27,19 @@ import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.function.IntFunction;
import org.teavm.ast.AsyncMethodNode;
import org.teavm.ast.AsyncMethodPart;
import org.teavm.ast.MethodNode;
import org.teavm.ast.MethodNodeVisitor;
import org.teavm.ast.RegularMethodNode;
import org.teavm.ast.ReturnStatement;
import org.teavm.ast.Statement;
import org.teavm.ast.VariableNode;
import org.teavm.backend.javascript.codegen.NamingOrderer;
import org.teavm.backend.javascript.codegen.NamingStrategy;
import org.teavm.backend.javascript.codegen.OutputSourceWriter;
import org.teavm.backend.javascript.codegen.RememberedSource;
import org.teavm.backend.javascript.codegen.ScopedName;
import org.teavm.backend.javascript.codegen.SourceWriter;
import org.teavm.backend.javascript.decompile.PreparedClass;
import org.teavm.backend.javascript.decompile.PreparedMethod;
import org.teavm.backend.javascript.spi.GeneratorContext;
import org.teavm.common.ServiceRepository;
import org.teavm.debugging.information.DebugInformationEmitter;
import org.teavm.debugging.information.DummyDebugInformationEmitter;
import org.teavm.dependency.DependencyInfo;
import org.teavm.diagnostics.Diagnostics;
import org.teavm.model.AccessLevel;
import org.teavm.model.ClassReader;
import org.teavm.model.ClassReaderSource;
import org.teavm.model.ElementModifier;
import org.teavm.model.FieldHolder;
import org.teavm.model.FieldReference;
@ -74,7 +63,6 @@ public class Renderer implements RenderingManager {
private DebugInformationEmitter debugEmitter = new DummyDebugInformationEmitter();
private final Set<MethodReference> asyncMethods;
private final Set<MethodReference> asyncFamilyMethods;
private final Diagnostics diagnostics;
private RenderingContext context;
private List<PostponedFieldInitializer> postponedFieldInitializers = new ArrayList<>();
private IntFunction<TeaVMProgressFeedback> progressConsumer = p -> TeaVMProgressFeedback.CONTINUE;
@ -84,11 +72,8 @@ public class Renderer implements RenderingManager {
private int stringPoolSize;
private int metadataSize;
private boolean longLibraryUsed;
private boolean threadLibraryUsed;
public Renderer(OutputSourceWriter writer, Set<MethodReference> asyncMethods,
Set<MethodReference> asyncFamilyMethods, Diagnostics diagnostics, RenderingContext context) {
Set<MethodReference> asyncFamilyMethods, RenderingContext context) {
this.naming = context.getNaming();
this.writer = writer;
this.classSource = context.getClassSource();
@ -96,18 +81,9 @@ public class Renderer implements RenderingManager {
this.services = context.getServices();
this.asyncMethods = new HashSet<>(asyncMethods);
this.asyncFamilyMethods = new HashSet<>(asyncFamilyMethods);
this.diagnostics = diagnostics;
this.context = context;
}
public boolean isLongLibraryUsed() {
return longLibraryUsed;
}
public boolean isThreadLibraryUsed() {
return threadLibraryUsed;
}
public int getStringPoolSize() {
return stringPoolSize;
}
@ -160,10 +136,6 @@ public class Renderer implements RenderingManager {
return properties;
}
public DebugInformationEmitter getDebugEmitter() {
return debugEmitter;
}
public void setDebugEmitter(DebugInformationEmitter debugEmitter) {
this.debugEmitter = debugEmitter;
}
@ -242,8 +214,8 @@ public class Renderer implements RenderingManager {
public void prepare(List<PreparedClass> classes) {
if (minifying) {
NamingOrderer orderer = new NamingOrderer();
NameFrequencyEstimator estimator = new NameFrequencyEstimator(orderer, classSource, asyncMethods,
asyncFamilyMethods, context.isStrict());
NameFrequencyEstimator estimator = new NameFrequencyEstimator(orderer, classSource,
asyncFamilyMethods);
for (PreparedClass cls : classes) {
estimator.estimate(cls);
}
@ -374,7 +346,7 @@ public class Renderer implements RenderingManager {
if (!cls.getClassHolder().hasModifier(ElementModifier.INTERFACE)
&& !cls.getClassHolder().hasModifier(ElementModifier.ABSTRACT)) {
for (PreparedMethod method : cls.getMethods()) {
if (!method.methodHolder.getModifiers().contains(ElementModifier.STATIC)) {
if (!method.modifiers.contains(ElementModifier.STATIC)) {
if (method.reference.getName().equals("<init>")) {
renderInitializer(method);
}
@ -416,7 +388,7 @@ public class Renderer implements RenderingManager {
writer.append("return;").softNewLine();
writer.outdent().append("}").softNewLine();
renderAsyncPrologue();
renderAsyncPrologue(writer, context);
writer.append("case 0:").indent().softNewLine();
writer.append(clinitCalled).ws().append('=').ws().append("true;").softNewLine();
@ -440,7 +412,7 @@ public class Renderer implements RenderingManager {
renderEraseClinit(cls);
writer.append("return;").softNewLine().outdent();
renderAsyncEpilogue();
renderAsyncEpilogue(writer);
writer.appendFunction("$rt_nativeThread").append("().push(" + context.pointerName() + ");").softNewLine();
}
@ -561,8 +533,8 @@ public class Renderer implements RenderingManager {
Map<MethodDescriptor, MethodReference> virtualMethods = new LinkedHashMap<>();
collectMethodsToCopyFromInterfaces(classSource.get(cls.getName()), virtualMethods);
for (PreparedMethod method : cls.getMethods()) {
if (!method.methodHolder.getModifiers().contains(ElementModifier.STATIC)
&& method.methodHolder.getLevel() != AccessLevel.PRIVATE) {
if (!method.modifiers.contains(ElementModifier.STATIC)
&& method.accessLevel != AccessLevel.PRIVATE) {
virtualMethods.put(method.reference.getDescriptor(), method.reference);
}
}
@ -811,37 +783,20 @@ public class Renderer implements RenderingManager {
}
private void renderBody(PreparedMethod method) {
StatementRenderer statementRenderer = new StatementRenderer(context, writer);
statementRenderer.setCurrentMethod(method.node);
MethodReference ref = method.reference;
debugEmitter.emitMethod(ref.getDescriptor());
ScopedName name = naming.getFullNameFor(ref);
renderFunctionDeclaration(name);
writer.append("(");
int startParam = 0;
if (method.methodHolder.getModifiers().contains(ElementModifier.STATIC)) {
startParam = 1;
}
for (int i = startParam; i <= ref.parameterCount(); ++i) {
if (i > startParam) {
writer.append(",").ws();
method.parameters.replay(writer, RememberedSource.FILTER_ALL);
if (method.variables != null) {
for (var variable : method.variables) {
variable.emit(debugEmitter);
}
writer.append(statementRenderer.variableName(i));
}
writer.append(")").ws().append("{").indent();
MethodBodyRenderer renderer = new MethodBodyRenderer(statementRenderer);
if (method.node != null) {
if (!isTrivialBody(method.node)) {
writer.softNewLine();
method.node.acceptVisitor(renderer);
}
} else {
writer.softNewLine();
renderer.renderNative(method);
}
writer.append(")").ws().append("{").indent().softNewLine();
method.body.replay(writer, RememberedSource.FILTER_ALL);
writer.outdent().append("}");
if (name.scoped) {
@ -850,16 +805,6 @@ public class Renderer implements RenderingManager {
writer.newLine();
debugEmitter.emitMethod(null);
longLibraryUsed |= statementRenderer.isLongLibraryUsed();
}
private static boolean isTrivialBody(MethodNode node) {
if (!(node instanceof RegularMethodNode)) {
return false;
}
Statement body = ((RegularMethodNode) node).getBody();
return body instanceof ReturnStatement && ((ReturnStatement) body).getResult() == null;
}
private void renderFunctionDeclaration(ScopedName name) {
@ -872,280 +817,18 @@ public class Renderer implements RenderingManager {
}
}
private void renderAsyncPrologue() {
static void renderAsyncPrologue(SourceWriter writer, RenderingContext context) {
writer.append(context.mainLoopName()).append(":").ws().append("while").ws().append("(true)")
.ws().append("{").ws();
writer.append("switch").ws().append("(").append(context.pointerName()).append(")").ws()
.append('{').softNewLine();
}
private void renderAsyncEpilogue() {
static void renderAsyncEpilogue(SourceWriter writer) {
writer.append("default:").ws().appendFunction("$rt_invalidPointer").append("();").softNewLine();
writer.append("}}").softNewLine();
}
private class MethodBodyRenderer implements MethodNodeVisitor, GeneratorContext {
private boolean async;
private StatementRenderer statementRenderer;
MethodBodyRenderer(StatementRenderer statementRenderer) {
this.statementRenderer = statementRenderer;
}
@Override
public DependencyInfo getDependency() {
return context.getDependencyInfo();
}
public void renderNative(PreparedMethod method) {
this.async = method.async;
statementRenderer.setAsync(method.async);
method.generator.generate(this, writer, method.reference);
}
@Override
public void visit(RegularMethodNode method) {
statementRenderer.setAsync(false);
this.async = false;
MethodReference ref = method.getReference();
for (int i = 0; i < method.getVariables().size(); ++i) {
debugEmitter.emitVariable(new String[] { method.getVariables().get(i).getName() },
statementRenderer.variableName(i));
}
int variableCount = 0;
for (VariableNode var : method.getVariables()) {
variableCount = Math.max(variableCount, var.getIndex() + 1);
}
TryCatchFinder tryCatchFinder = new TryCatchFinder();
method.getBody().acceptVisitor(tryCatchFinder);
boolean hasTryCatch = tryCatchFinder.tryCatchFound;
List<String> variableNames = new ArrayList<>();
for (int i = ref.parameterCount() + 1; i < variableCount; ++i) {
variableNames.add(statementRenderer.variableName(i));
}
if (hasTryCatch) {
variableNames.add("$$je");
}
if (!variableNames.isEmpty()) {
writer.append("var ");
for (int i = 0; i < variableNames.size(); ++i) {
if (i > 0) {
writer.append(",").ws();
}
writer.append(variableNames.get(i));
}
writer.append(";").softNewLine();
}
statementRenderer.setEnd(true);
statementRenderer.setCurrentPart(0);
if (method.getModifiers().contains(ElementModifier.SYNCHRONIZED)) {
writer.appendMethodBody(NameFrequencyEstimator.MONITOR_ENTER_SYNC_METHOD);
writer.append("(");
appendMonitor(statementRenderer, method);
writer.append(");").softNewLine();
writer.append("try").ws().append("{").softNewLine().indent();
}
method.getBody().acceptVisitor(statementRenderer);
if (method.getModifiers().contains(ElementModifier.SYNCHRONIZED)) {
writer.outdent().append("}").ws().append("finally").ws().append("{").indent().softNewLine();
writer.appendMethodBody(NameFrequencyEstimator.MONITOR_EXIT_SYNC_METHOD);
writer.append("(");
appendMonitor(statementRenderer, method);
writer.append(");").softNewLine();
writer.outdent().append("}").softNewLine();
}
}
@Override
public void visit(AsyncMethodNode methodNode) {
threadLibraryUsed = true;
statementRenderer.setAsync(true);
this.async = true;
MethodReference ref = methodNode.getReference();
for (int i = 0; i < methodNode.getVariables().size(); ++i) {
debugEmitter.emitVariable(new String[] { methodNode.getVariables().get(i).getName() },
statementRenderer.variableName(i));
}
int variableCount = 0;
for (VariableNode var : methodNode.getVariables()) {
variableCount = Math.max(variableCount, var.getIndex() + 1);
}
List<String> variableNames = new ArrayList<>();
for (int i = ref.parameterCount() + 1; i < variableCount; ++i) {
variableNames.add(statementRenderer.variableName(i));
}
TryCatchFinder tryCatchFinder = new TryCatchFinder();
for (AsyncMethodPart part : methodNode.getBody()) {
if (!tryCatchFinder.tryCatchFound) {
part.getStatement().acceptVisitor(tryCatchFinder);
}
}
boolean hasTryCatch = tryCatchFinder.tryCatchFound;
if (hasTryCatch) {
variableNames.add("$$je");
}
variableNames.add(context.pointerName());
variableNames.add(context.tempVarName());
writer.append("var ");
for (int i = 0; i < variableNames.size(); ++i) {
if (i > 0) {
writer.append(",").ws();
}
writer.append(variableNames.get(i));
}
writer.append(";").softNewLine();
int firstToSave = 0;
if (methodNode.getModifiers().contains(ElementModifier.STATIC)) {
firstToSave = 1;
}
String popName = minifying ? "l" : "pop";
String pushName = minifying ? "s" : "push";
writer.append(context.pointerName()).ws().append('=').ws().append("0;").softNewLine();
writer.append("if").ws().append("(").appendFunction("$rt_resuming").append("())").ws()
.append("{").indent().softNewLine();
writer.append("var ").append(context.threadName()).ws().append('=').ws()
.appendFunction("$rt_nativeThread").append("();").softNewLine();
writer.append(context.pointerName()).ws().append('=').ws().append(context.threadName()).append(".")
.append(popName).append("();");
for (int i = variableCount - 1; i >= firstToSave; --i) {
writer.append(statementRenderer.variableName(i)).ws().append('=').ws().append(context.threadName())
.append(".").append(popName).append("();");
}
writer.softNewLine();
writer.outdent().append("}").softNewLine();
if (methodNode.getModifiers().contains(ElementModifier.SYNCHRONIZED)) {
writer.append("try").ws().append('{').indent().softNewLine();
}
renderAsyncPrologue();
for (int i = 0; i < methodNode.getBody().size(); ++i) {
writer.append("case ").append(i).append(":").indent().softNewLine();
if (i == 0 && methodNode.getModifiers().contains(ElementModifier.SYNCHRONIZED)) {
writer.appendMethodBody(NameFrequencyEstimator.MONITOR_ENTER_METHOD);
writer.append("(");
appendMonitor(statementRenderer, methodNode);
writer.append(");").softNewLine();
statementRenderer.emitSuspendChecker();
}
AsyncMethodPart part = methodNode.getBody().get(i);
statementRenderer.setEnd(true);
statementRenderer.setCurrentPart(i);
part.getStatement().acceptVisitor(statementRenderer);
writer.outdent();
}
renderAsyncEpilogue();
if (methodNode.getModifiers().contains(ElementModifier.SYNCHRONIZED)) {
writer.outdent().append("}").ws().append("finally").ws().append('{').indent().softNewLine();
writer.append("if").ws().append("(!").appendFunction("$rt_suspending").append("())")
.ws().append("{").indent().softNewLine();
writer.appendMethodBody(NameFrequencyEstimator.MONITOR_EXIT_METHOD);
writer.append("(");
appendMonitor(statementRenderer, methodNode);
writer.append(");").softNewLine();
writer.outdent().append('}').softNewLine();
writer.outdent().append('}').softNewLine();
}
writer.appendFunction("$rt_nativeThread").append("().").append(pushName).append("(");
for (int i = firstToSave; i < variableCount; ++i) {
writer.append(statementRenderer.variableName(i)).append(',').ws();
}
writer.append(context.pointerName()).append(");");
writer.softNewLine();
}
@Override
public String getParameterName(int index) {
return statementRenderer.variableName(index);
}
@Override
public ListableClassReaderSource getClassSource() {
return classSource;
}
@Override
public ClassReaderSource getInitialClassSource() {
return context.getInitialClassSource();
}
@Override
public ClassLoader getClassLoader() {
return classLoader;
}
@Override
public Properties getProperties() {
return new Properties(properties);
}
@Override
public <T> T getService(Class<T> type) {
return services.getService(type);
}
@Override
public boolean isAsync() {
return async;
}
@Override
public boolean isAsync(MethodReference method) {
return asyncMethods.contains(method);
}
@Override
public boolean isAsyncFamily(MethodReference method) {
return asyncFamilyMethods.contains(method);
}
@Override
public Diagnostics getDiagnostics() {
return diagnostics;
}
@Override
public void typeToClassString(SourceWriter writer, ValueType type) {
context.typeToClsString(writer, type);
}
@Override
public void useLongLibrary() {
longLibraryUsed = true;
}
@Override
public boolean isDynamicInitializer(String className) {
return context.isDynamicInitializer(className);
}
@Override
public String importModule(String name) {
return context.importModule(name);
}
}
private void appendMonitor(StatementRenderer statementRenderer, MethodNode methodNode) {
if (methodNode.getModifiers().contains(ElementModifier.STATIC)) {
writer.appendFunction("$rt_cls").append("(")
.appendClass(methodNode.getReference().getClassName()).append(")");
} else {
writer.append(statementRenderer.variableName(0));
}
}
@Override
public <T> T getService(Class<T> type) {
return services.getService(type);

View File

@ -96,7 +96,6 @@ public class StatementRenderer implements ExprVisitor, StatementVisitor {
private int currentPart;
private List<String> blockIds = new ArrayList<>();
private IntIndexedContainer blockIndexMap = new IntArrayList();
private boolean longLibraryUsed;
private static final MethodDescriptor CLINIT_METHOD = new MethodDescriptor("<clinit>", ValueType.VOID);
private VariableNameGenerator variableNameGenerator;
private final Deque<LocationStackEntry> locationStack = new ArrayDeque<>();
@ -111,8 +110,16 @@ public class StatementRenderer implements ExprVisitor, StatementVisitor {
variableNameGenerator = new VariableNameGenerator(minifying);
}
public boolean isLongLibraryUsed() {
return longLibraryUsed;
public void clear() {
blockIdMap.clear();
blockIds.clear();
blockIndexMap.clear();
currentPart = 0;
end = false;
precedence = null;
variableNameGenerator.setCurrentMethod(null);
locationStack.clear();
lastEmittedLocation = TextLocation.EMPTY;
}
public boolean isAsync() {
@ -624,7 +631,6 @@ public class StatementRenderer implements ExprVisitor, StatementVisitor {
@Override
public void visit(BinaryExpr expr) {
if (expr.getType() == OperationType.LONG) {
longLibraryUsed = true;
switch (expr.getOperation()) {
case ADD:
visitBinaryFunction(expr, "Long_add");
@ -772,7 +778,6 @@ public class StatementRenderer implements ExprVisitor, StatementVisitor {
switch (expr.getOperation()) {
case NOT: {
if (expr.getType() == OperationType.LONG) {
longLibraryUsed = true;
writer.appendFunction("Long_not").append("(");
precedence = Precedence.min();
expr.getOperand().acceptVisitor(this);
@ -792,7 +797,6 @@ public class StatementRenderer implements ExprVisitor, StatementVisitor {
}
case NEGATE:
if (expr.getType() == OperationType.LONG) {
longLibraryUsed = true;
writer.appendFunction("Long_neg").append("(");
precedence = Precedence.min();
expr.getOperand().acceptVisitor(this);

View File

@ -42,15 +42,11 @@ public interface GeneratorContext extends ServiceRepository {
boolean isAsync(MethodReference method);
boolean isAsyncFamily(MethodReference method);
Diagnostics getDiagnostics();
DependencyInfo getDependency();
void typeToClassString(SourceWriter writer, ValueType type);
void useLongLibrary();
boolean isDynamicInitializer(String className);
}

View File

@ -1,36 +0,0 @@
/*
* Copyright 2023 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.spi;
import java.util.Properties;
import org.teavm.common.ServiceRepository;
import org.teavm.dependency.DependencyInfo;
import org.teavm.model.ListableClassReaderSource;
import org.teavm.model.MethodReader;
public interface ModuleImporterContext extends ServiceRepository {
MethodReader getMethod();
void importModule(String name);
ListableClassReaderSource getClassSource();
ClassLoader getClassLoader();
Properties getProperties();
DependencyInfo getDependency();
}

View File

@ -182,8 +182,7 @@ public class TemplatingAstWriter extends AstWriter {
if (scope == null && name.getIdentifier().equals("teavm_globals")) {
var oldRootScope = rootScope;
rootScope = false;
writer.append("$rt_globals").append(".");
print(node.getProperty());
writer.appendGlobal(node.getProperty().getIdentifier());
rootScope = oldRootScope;
return;
}

View File

@ -81,10 +81,5 @@ public class JSOPlugin implements TeaVMPlugin {
wrapperGenerator);
TeaVMPluginUtil.handleNatives(host, JS.class);
jsHost.addModuleImporterProvider(providerContext -> {
var imports = repository.imports.get(providerContext.getMethod());
return imports != null ? new JsBodyImportsContributor(imports) : null;
});
}
}

View File

@ -1,34 +0,0 @@
/*
* Copyright 2023 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.jso.impl;
import org.teavm.backend.javascript.spi.ModuleImporter;
import org.teavm.backend.javascript.spi.ModuleImporterContext;
class JsBodyImportsContributor implements ModuleImporter {
private JsBodyImportInfo[] imports;
JsBodyImportsContributor(JsBodyImportInfo[] imports) {
this.imports = imports;
}
@Override
public void importModules(ModuleImporterContext context) {
for (var importInfo : imports) {
context.importModule(importInfo.fromModule);
}
}
}