JS: render all declarations into one remembered fragment, then output this fragment to real output

This commit is contained in:
Alexey Andreev 2023-11-03 21:03:21 +01:00
parent ceffde38f3
commit 5ec4450bf8
16 changed files with 679 additions and 980 deletions

View File

@ -16,7 +16,6 @@
package org.teavm.backend.javascript;
import com.carrotsearch.hppc.ObjectIntHashMap;
import com.carrotsearch.hppc.ObjectIntMap;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
@ -24,7 +23,6 @@ import java.io.Writer;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.lang.reflect.InvocationTargetException;
import java.nio.charset.StandardCharsets;
import java.text.DecimalFormat;
import java.text.NumberFormat;
@ -37,30 +35,24 @@ import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
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;
import org.teavm.ast.decompilation.Decompiler;
import org.teavm.backend.javascript.codegen.AliasProvider;
import org.teavm.backend.javascript.codegen.DefaultAliasProvider;
import org.teavm.backend.javascript.codegen.DefaultNamingStrategy;
import org.teavm.backend.javascript.codegen.MinifyingAliasProvider;
import org.teavm.backend.javascript.codegen.OutputSourceWriter;
import org.teavm.backend.javascript.codegen.OutputSourceWriterBuilder;
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.PreparedClass;
import org.teavm.backend.javascript.decompile.PreparedMethod;
import org.teavm.backend.javascript.intrinsics.ref.ReferenceQueueGenerator;
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.NameFrequencyEstimator;
import org.teavm.backend.javascript.rendering.Renderer;
import org.teavm.backend.javascript.rendering.RenderingContext;
import org.teavm.backend.javascript.rendering.RenderingUtil;
@ -72,29 +64,21 @@ import org.teavm.backend.javascript.spi.Injector;
import org.teavm.backend.javascript.spi.VirtualMethodContributor;
import org.teavm.backend.javascript.spi.VirtualMethodContributorContext;
import org.teavm.backend.javascript.templating.JavaScriptTemplateFactory;
import org.teavm.cache.AstCacheEntry;
import org.teavm.cache.AstDependencyExtractor;
import org.teavm.cache.CacheStatus;
import org.teavm.cache.EmptyMethodNodeCache;
import org.teavm.cache.MethodNodeCache;
import org.teavm.common.ServiceRepository;
import org.teavm.debugging.information.DebugInformationEmitter;
import org.teavm.debugging.information.DummyDebugInformationEmitter;
import org.teavm.debugging.information.SourceLocation;
import org.teavm.dependency.AbstractDependencyListener;
import org.teavm.dependency.DependencyAgent;
import org.teavm.dependency.DependencyAnalyzer;
import org.teavm.dependency.DependencyInfo;
import org.teavm.dependency.DependencyListener;
import org.teavm.dependency.DependencyType;
import org.teavm.dependency.MethodDependency;
import org.teavm.interop.PlatformMarker;
import org.teavm.interop.Platforms;
import org.teavm.model.AnnotationHolder;
import org.teavm.model.BasicBlock;
import org.teavm.model.CallLocation;
import org.teavm.model.ClassHolder;
import org.teavm.model.ClassHolderSource;
import org.teavm.model.ClassHolderTransformer;
import org.teavm.model.ClassReaderSource;
import org.teavm.model.ElementModifier;
@ -115,8 +99,6 @@ import org.teavm.model.instructions.StringConstantInstruction;
import org.teavm.model.transformation.BoundCheckInsertion;
import org.teavm.model.transformation.NullCheckFilter;
import org.teavm.model.transformation.NullCheckInsertion;
import org.teavm.model.util.AsyncMethodFinder;
import org.teavm.model.util.ProgramUtils;
import org.teavm.vm.BuildTarget;
import org.teavm.vm.RenderingException;
import org.teavm.vm.TeaVMTarget;
@ -141,18 +123,13 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost {
private DebugInformationEmitter debugEmitter;
private MethodNodeCache astCache = EmptyMethodNodeCache.INSTANCE;
private final Set<MethodReference> asyncMethods = new HashSet<>();
private final Set<MethodReference> asyncFamilyMethods = new HashSet<>();
private List<VirtualMethodContributor> customVirtualMethods = new ArrayList<>();
private int topLevelNameLimit = 500000;
private AstDependencyExtractor dependencyExtractor = new AstDependencyExtractor();
private boolean strict;
private BoundCheckInsertion boundCheckInsertion = new BoundCheckInsertion();
private NullCheckInsertion nullCheckInsertion = new NullCheckInsertion(NullCheckFilter.EMPTY);
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() {
@ -422,71 +399,47 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost {
};
renderingContext.setMinifying(obfuscated);
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);
for (var className : classes.getClassNames()) {
var cls = classes.get(className);
for (var method : cls.getMethods()) {
preprocessNativeMethod(method);
}
}
for (var entry : methodInjectors.entrySet()) {
renderingContext.addInjector(entry.getKey(), entry.getValue());
}
var renderer = new Renderer(sourceWriter, asyncMethods, asyncFamilyMethods, renderingContext);
RuntimeRenderer runtimeRenderer = new RuntimeRenderer(classes, sourceWriter);
var rememberingWriter = new RememberingSourceWriter(debugEmitter != null);
var renderer = new Renderer(rememberingWriter, asyncMethods, renderingContext, controller.getDiagnostics(),
methodGenerators, astCache, controller.getCacheStatus(), templateFactory);
renderer.setProperties(controller.getProperties());
renderer.setMinifying(obfuscated);
renderer.setProgressConsumer(controller::reportProgress);
if (debugEmitter != null) {
for (PreparedClass preparedClass : clsNodes) {
for (PreparedMethod preparedMethod : preparedClass.getMethods()) {
if (preparedMethod.cfg != null) {
emitCFG(debugEmitter, preparedMethod.cfg);
}
}
if (controller.wasCancelled()) {
return;
}
}
renderer.setDebugEmitter(debugEmitter);
}
renderer.prepare(clsNodes);
printWrapperStart(sourceWriter);
for (RendererListener listener : rendererListeners) {
for (var listener : rendererListeners) {
listener.begin(renderer, target);
}
int start = sourceWriter.getOffset();
runtimeRenderer.renderRuntime();
sourceWriter.append("var ").append(renderer.getNaming().getScopeName()).ws().append("=").ws()
.append("Object.create(null);").newLine();
if (!renderer.render(clsNodes)) {
if (!renderer.render(classes, controller.isFriendlyToDebugger())) {
return;
}
runtimeRenderer.renderHandWrittenRuntime("array.js");
var declarations = rememberingWriter.save();
rememberingWriter.clear();
renderer.renderStringPool();
renderer.renderStringConstants();
renderer.renderCompatibilityStubs();
runtimeRenderer.renderHandWrittenRuntime("long.js");
if (threadLibraryUsed) {
runtimeRenderer.renderHandWrittenRuntime("thread.js");
} else {
runtimeRenderer.renderHandWrittenRuntime("simpleThread.js");
}
for (var entry : controller.getEntryPoints().entrySet()) {
sourceWriter.appendFunction("$rt_exports").append(".").append(entry.getKey()).ws().append("=").ws();
rememberingWriter.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.appendFunction("$rt_exports").append(".").append(entry.getKey()).append(".")
rememberingWriter.appendFunction("$rt_mainStarter").append("(").appendMethodBody(ref);
rememberingWriter.append(");").newLine();
rememberingWriter.appendFunction("$rt_exports").append(".").append(entry.getKey()).append(".")
.append("javaException").ws().append("=").ws().appendFunction("$rt_javaException")
.append(";").newLine();
}
@ -494,11 +447,36 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost {
for (var listener : rendererListeners) {
listener.complete();
}
var epilogue = rememberingWriter.save();
rememberingWriter.clear();
var frequencyEstimator = new NameFrequencyEstimator();
declarations.replay(frequencyEstimator, RememberedSource.FILTER_REF);
epilogue.replay(frequencyEstimator, RememberedSource.FILTER_REF);
frequencyEstimator.apply(naming);
var sourceWriter = builder.build(writer);
sourceWriter.setDebugInformationEmitter(debugEmitterToUse);
printWrapperStart(sourceWriter);
int start = sourceWriter.getOffset();
RuntimeRenderer runtimeRenderer = new RuntimeRenderer(classes, sourceWriter);
runtimeRenderer.renderRuntime();
runtimeRenderer.renderHandWrittenRuntime("long.js");
if (renderer.isThreadLibraryUsed()) {
runtimeRenderer.renderHandWrittenRuntime("thread.js");
} else {
runtimeRenderer.renderHandWrittenRuntime("simpleThread.js");
}
declarations.write(sourceWriter, 0);
runtimeRenderer.renderHandWrittenRuntime("array.js");
epilogue.write(sourceWriter, 0);
printWrapperEnd(sourceWriter);
int totalSize = sourceWriter.getOffset() - start;
printStats(renderer, totalSize);
printStats(sourceWriter, totalSize);
}
private void printWrapperStart(SourceWriter writer) {
@ -560,19 +538,21 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost {
writer.outdent().append("}));").newLine();
}
private void printStats(Renderer renderer, int totalSize) {
private void printStats(OutputSourceWriter writer, int totalSize) {
if (!Boolean.parseBoolean(System.getProperty("teavm.js.stats", "false"))) {
return;
}
System.out.println("Total output size: " + STATS_NUM_FORMAT.format(totalSize));
System.out.println("Metadata size: " + getSizeWithPercentage(renderer.getMetadataSize(), totalSize));
System.out.println("String pool size: " + getSizeWithPercentage(renderer.getStringPoolSize(), totalSize));
System.out.println("Metadata size: " + getSizeWithPercentage(
writer.getSectionSize(Renderer.SECTION_METADATA), totalSize));
System.out.println("String pool size: " + getSizeWithPercentage(
writer.getSectionSize(Renderer.SECTION_STRING_POOL), totalSize));
ObjectIntMap<String> packageSizeMap = new ObjectIntHashMap<>();
for (String className : renderer.getClassesInStats()) {
var packageSizeMap = new ObjectIntHashMap<String>();
for (String className : writer.getClassesInStats()) {
String packageName = className.substring(0, className.lastIndexOf('.') + 1);
int classSize = renderer.getClassSize(className);
int classSize = writer.getClassSize(className);
packageSizeMap.put(packageName, packageSizeMap.getOrDefault(packageName, 0) + classSize);
}
@ -588,222 +568,6 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost {
return STATS_NUM_FORMAT.format(size) + " (" + STATS_PERCENT_FORMAT.format((double) size / totalSize) + ")";
}
private List<PreparedClass> modelToAst(ListableClassHolderSource classes, RenderingContext context) {
AsyncMethodFinder asyncFinder = new AsyncMethodFinder(controller.getDependencyInfo().getCallGraph(),
controller.getDependencyInfo());
asyncFinder.find(classes);
asyncMethods.addAll(asyncFinder.getAsyncMethods());
asyncFamilyMethods.addAll(asyncFinder.getAsyncFamilyMethods());
Set<MethodReference> splitMethods = new HashSet<>(asyncMethods);
splitMethods.addAll(asyncFamilyMethods);
Decompiler decompiler = new Decompiler(classes, splitMethods, controller.isFriendlyToDebugger());
List<PreparedClass> classNodes = new ArrayList<>();
for (String className : getClassOrdering(classes)) {
ClassHolder cls = classes.get(className);
for (MethodHolder method : cls.getMethods()) {
preprocessNativeMethod(method);
if (controller.wasCancelled()) {
break;
}
}
}
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;
}
private List<String> getClassOrdering(ListableClassHolderSource classes) {
List<String> sequence = new ArrayList<>();
Set<String> visited = new HashSet<>();
for (String className : classes.getClassNames()) {
orderClasses(classes, className, visited, sequence);
}
return sequence;
}
private void orderClasses(ClassHolderSource classes, String className, Set<String> visited, List<String> order) {
if (!visited.add(className)) {
return;
}
ClassHolder cls = classes.get(className);
if (cls == null) {
return;
}
if (cls.getParent() != null) {
orderClasses(classes, cls.getParent(), visited, order);
}
for (String iface : cls.getInterfaces()) {
orderClasses(classes, iface, visited, order);
}
order.add(className);
}
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)) {
continue;
}
if ((!isBootstrap() && method.getAnnotations().get(InjectedBy.class.getName()) != null)
|| methodInjectors.containsKey(method.getReference())) {
continue;
}
if (!method.hasModifier(ElementModifier.NATIVE) && !method.hasProgram()) {
continue;
}
PreparedMethod preparedMethod = method.hasModifier(ElementModifier.NATIVE)
? prepareNative(method, classes)
: prepare(decompiler, method);
clsNode.getMethods().add(preparedMethod);
}
return clsNode;
}
private PreparedMethod prepareNative(MethodHolder method, ClassReaderSource classes) {
MethodReference reference = method.getReference();
Generator generator = methodGenerators.get(reference);
if (generator == null && !isBootstrap()) {
AnnotationHolder annotHolder = method.getAnnotations().get(GeneratedBy.class.getName());
if (annotHolder == null) {
throw new DecompilationException("Method " + method.getOwnerName() + "." + method.getDescriptor()
+ " is native, but no " + GeneratedBy.class.getName() + " annotation found");
}
ValueType annotValue = annotHolder.getValues().get("value").getJavaClass();
String generatorClassName = ((ValueType.Object) annotValue).getClassName();
generator = generatorCache.computeIfAbsent(generatorClassName,
name -> createGenerator(name, method, classes));
}
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) {
Class<?> generatorClass;
try {
generatorClass = Class.forName(name, true, controller.getClassLoader());
} catch (ClassNotFoundException e) {
throw new DecompilationException("Error instantiating generator " + name
+ " for native method " + method.getOwnerName() + "." + method.getDescriptor());
}
var constructors = generatorClass.getConstructors();
if (constructors.length != 1) {
throw new DecompilationException("Error instantiating generator " + name
+ " for native method " + method.getOwnerName() + "." + method.getDescriptor());
}
var constructor = constructors[0];
var parameterTypes = constructor.getParameterTypes();
var arguments = new Object[parameterTypes.length];
for (var i = 0; i < arguments.length; ++i) {
var parameterType = parameterTypes[i];
if (parameterType.equals(ClassReaderSource.class)) {
arguments[i] = classes;
} else if (parameterType.equals(Properties.class)) {
arguments[i] = controller.getProperties();
} else if (parameterType.equals(DependencyInfo.class)) {
arguments[i] = controller.getDependencyInfo();
} else if (parameterType.equals(ServiceRepository.class)) {
arguments[i] = controller.getServices();
} else if (parameterType.equals(JavaScriptTemplateFactory.class)) {
arguments[i] = templateFactory;
} else {
var service = controller.getServices().getService(parameterType);
if (service == null) {
throw new DecompilationException("Error instantiating generator " + name
+ " for native method " + method.getOwnerName() + "." + method.getDescriptor() + ". "
+ "Its constructor requires " + parameterType + " as its parameter #" + (i + 1)
+ " which is not available.");
}
}
}
try {
return (Generator) constructor.newInstance(arguments);
} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
throw new DecompilationException("Error instantiating generator " + name
+ " for native method " + method.getOwnerName() + "." + method.getDescriptor(), e);
}
}
private PreparedMethod prepare(Decompiler decompiler, MethodHolder method) {
MethodReference reference = method.getReference();
ControlFlowEntry[] cfg;
MethodNode node;
var async = asyncMethods.contains(reference);
if (async) {
node = decompileAsync(decompiler, method);
cfg = ProgramUtils.getLocationCFG(method.getProgram());
} else {
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) {
if (astCache == null) {
return decompileRegularCacheMiss(decompiler, method);
}
CacheStatus cacheStatus = controller.getCacheStatus();
AstCacheEntry entry = !cacheStatus.isStaleMethod(method.getReference())
? astCache.get(method.getReference(), cacheStatus)
: null;
if (entry == null) {
entry = decompileRegularCacheMiss(decompiler, method);
RegularMethodNode finalNode = entry.method;
astCache.store(method.getReference(), entry, () -> dependencyExtractor.extract(finalNode));
}
return entry;
}
private AstCacheEntry decompileRegularCacheMiss(Decompiler decompiler, MethodHolder method) {
RegularMethodNode node = decompiler.decompileRegular(method);
ControlFlowEntry[] cfg = LocationGraphBuilder.build(node.getBody());
return new AstCacheEntry(node, cfg);
}
private AsyncMethodNode decompileAsync(Decompiler decompiler, MethodHolder method) {
if (astCache == null) {
return decompiler.decompileAsync(method);
}
CacheStatus cacheStatus = controller.getCacheStatus();
AsyncMethodNode node = !cacheStatus.isStaleMethod(method.getReference())
? astCache.getAsync(method.getReference(), cacheStatus)
: null;
if (node == null) {
node = decompiler.decompileAsync(method);
AsyncMethodNode finalNode = node;
astCache.storeAsync(method.getReference(), node, () -> dependencyExtractor.extract(finalNode));
}
return node;
}
private void preprocessNativeMethod(MethodHolder method) {
if (!method.getModifiers().contains(ElementModifier.NATIVE)
|| methodGenerators.get(method.getReference()) != null

View File

@ -1,40 +0,0 @@
/*
* Copyright 2016 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 NameFrequencyConsumer {
void consume(MethodReference method);
void consumeInit(MethodReference method);
void consume(MethodDescriptor method);
void consume(String className);
void consumeClassInit(String className);
void consume(FieldReference field);
void consumeStatic(FieldReference field);
void consumeFunction(String name);
void consumeGlobal(String name);
}

View File

@ -1,148 +0,0 @@
/*
* Copyright 2016 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 java.util.*;
import org.teavm.model.FieldReference;
import org.teavm.model.MethodDescriptor;
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) {
String key = "R:" + method;
Entry entry = entries.get(key);
if (entry == null) {
entry = new Entry();
entry.operation = naming -> naming.getFullNameFor(method);
entries.put(key, entry);
}
entry.frequency++;
}
@Override
public void consumeInit(MethodReference method) {
String key = "I:" + method;
Entry entry = entries.get(key);
if (entry == null) {
entry = new Entry();
entry.operation = naming -> naming.getNameForInit(method);
entries.put(key, entry);
}
entry.frequency++;
}
@Override
public void consume(MethodDescriptor method) {
String key = "r:" + method;
Entry entry = entries.get(key);
if (entry == null) {
entry = new Entry();
entry.operation = naming -> naming.getNameFor(method);
entries.put(key, entry);
}
entry.frequency++;
}
@Override
public void consume(String className) {
String key = "c:" + className;
Entry entry = entries.get(key);
if (entry == null) {
entry = new Entry();
entry.operation = naming -> naming.getNameFor(className);
entries.put(key, entry);
}
entry.frequency++;
}
@Override
public void consumeClassInit(String className) {
String key = "C:" + className;
Entry entry = entries.get(key);
if (entry == null) {
entry = new Entry();
entry.operation = naming -> naming.getNameForClassInit(className);
entries.put(key, entry);
}
entry.frequency++;
}
@Override
public void consume(FieldReference field) {
String key = "f:" + field;
Entry entry = entries.get(key);
if (entry == null) {
entry = new Entry();
entry.operation = naming -> naming.getNameFor(field);
entries.put(key, entry);
}
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;
Entry entry = entries.get(key);
if (entry == null) {
entry = new Entry();
entry.operation = naming -> naming.getNameForFunction(name);
entries.put(key, entry);
}
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) {
entry.operation.perform(naming);
}
}
static class Entry {
NamingOperation operation;
int frequency;
}
interface NamingOperation {
void perform(NamingStrategy naming);
}
}

View File

@ -15,7 +15,13 @@
*/
package org.teavm.backend.javascript.codegen;
import com.carrotsearch.hppc.IntIntHashMap;
import com.carrotsearch.hppc.IntIntMap;
import com.carrotsearch.hppc.ObjectIntHashMap;
import com.carrotsearch.hppc.ObjectIntMap;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import org.teavm.debugging.information.DebugInformationEmitter;
import org.teavm.debugging.information.DummyDebugInformationEmitter;
import org.teavm.model.FieldReference;
@ -33,6 +39,12 @@ public class OutputSourceWriter extends SourceWriter implements LocationProvider
private int line;
private int offset;
private DebugInformationEmitter debugInformationEmitter = new DummyDebugInformationEmitter();
private String classMarkClass;
private int classMarkPos;
private ObjectIntMap<String> classSizes = new ObjectIntHashMap<>();
private int sectionMarkSection = -1;
private int sectionMarkPos;
private IntIntMap sectionSizes = new IntIntHashMap();
OutputSourceWriter(NamingStrategy naming, Appendable innerWriter, int lineWidth) {
this.naming = naming;
@ -140,9 +152,6 @@ public class OutputSourceWriter extends SourceWriter implements LocationProvider
}
private SourceWriter appendName(ScopedName name) {
if (name.scoped) {
append(naming.getScopeName()).append(".");
}
append(name.value);
return this;
}
@ -271,6 +280,12 @@ public class OutputSourceWriter extends SourceWriter implements LocationProvider
return this;
}
@Override
public SourceWriter emitVariables(String[] names, String jsName) {
debugInformationEmitter.emitVariable(names, jsName);
return this;
}
@Override
public void emitMethod(MethodDescriptor method) {
debugInformationEmitter.emitMethod(method);
@ -295,4 +310,56 @@ public class OutputSourceWriter extends SourceWriter implements LocationProvider
public int getOffset() {
return offset;
}
@Override
public void markClassStart(String className) {
classMarkClass = className;
classMarkPos = offset;
}
@Override
public void markClassEnd() {
if (classMarkClass != null) {
var size = offset - classMarkPos;
if (size > 0) {
var currentSize = classSizes.get(classMarkClass);
classSizes.put(classMarkClass, currentSize + size);
}
classMarkClass = null;
}
}
@Override
public void markSectionStart(int id) {
sectionMarkSection = id;
sectionMarkPos = offset;
}
@Override
public void markSectionEnd() {
if (sectionMarkSection >= 0) {
int size = offset - sectionMarkPos;
if (size > 0) {
var currentSize = sectionSizes.get(sectionMarkSection);
sectionSizes.put(sectionMarkSection, currentSize + size);
}
sectionMarkSection = -1;
}
}
public Collection<String> getClassesInStats() {
var result = new ArrayList<String>();
for (var cursor : classSizes.keys()) {
result.add(cursor.value);
}
return result;
}
public int getClassSize(String className) {
return classSizes.get(className);
}
public int getSectionSize(int sectionId) {
return sectionSizes.get(sectionId);
}
}

View File

@ -24,7 +24,8 @@ 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;
public static final int FILTER_STATS = 8;
public static final int FILTER_ALL = FILTER_TEXT | FILTER_REF | FILTER_DEBUG | FILTER_STATS;
private byte[] commands;
private String chars;
@ -196,6 +197,20 @@ public class RememberedSource implements SourceFragment {
}
break;
case RememberingSourceWriter.EMIT_VARIABLES:
var count = intArgs[intArgIndex++];
if ((filter & FILTER_DEBUG) != 0) {
var names = new String[count];
for (var i = 0; i < count; ++i) {
names[i] = strings[intArgs[intArgIndex++]];
}
var jsName = strings[intArgs[intArgIndex++]];
sink.emitVariables(names, jsName);
} else {
intArgIndex += count + 1;
}
break;
case RememberingSourceWriter.EMIT_CLASS:
if ((filter & FILTER_DEBUG) != 0) {
var classIndex = intArgs[intArgIndex];
@ -211,6 +226,32 @@ public class RememberedSource implements SourceFragment {
}
intArgIndex++;
break;
case RememberingSourceWriter.MARK_CLASS_START:
if ((filter & FILTER_STATS) != 0) {
sink.markClassStart(strings[intArgs[intArgIndex]]);
}
intArgIndex++;
break;
case RememberingSourceWriter.MARK_CLASS_END:
if ((filter & FILTER_STATS) != 0) {
sink.markClassEnd();
}
break;
case RememberingSourceWriter.MARK_SECTION_START:
if ((filter & FILTER_STATS) != 0) {
sink.markSectionStart(intArgs[intArgIndex]);
}
intArgIndex++;
break;
case RememberingSourceWriter.MARK_SECTION_END:
if ((filter & FILTER_STATS) != 0) {
sink.markSectionEnd();
}
break;
}
}
}

View File

@ -46,8 +46,13 @@ public class RememberingSourceWriter extends SourceWriter {
static final byte ENTER_LOCATION = 16;
static final byte EXIT_LOCATION = 17;
static final byte EMIT_STATEMENT_START = 18;
static final byte EMIT_VARIABLES = 26;
static final byte EMIT_METHOD = 19;
static final byte EMIT_CLASS = 20;
static final byte MARK_CLASS_START = 22;
static final byte MARK_CLASS_END = 23;
static final byte MARK_SECTION_START = 24;
static final byte MARK_SECTION_END = 25;
private boolean debug;
@ -262,6 +267,20 @@ public class RememberingSourceWriter extends SourceWriter {
return this;
}
@Override
public SourceWriter emitVariables(String[] names, String jsName) {
if (debug) {
flush();
commands.add(EMIT_VARIABLES);
intArgs.add(names.length);
for (var name : names) {
appendStringArg(name);
}
appendStringArg(jsName);
}
return this;
}
@Override
public void emitMethod(MethodDescriptor method) {
if (!debug) {
@ -290,6 +309,32 @@ public class RememberingSourceWriter extends SourceWriter {
}
}
@Override
public void markClassStart(String className) {
flush();
commands.add(MARK_CLASS_START);
appendStringArg(className);
}
@Override
public void markClassEnd() {
flush();
commands.add(MARK_CLASS_END);
}
@Override
public void markSectionStart(int id) {
flush();
commands.add(MARK_SECTION_START);
intArgs.add(id);
}
@Override
public void markSectionEnd() {
flush();
commands.add(MARK_SECTION_END);
}
public void flush() {
if (lastWrittenChar == sb.length()) {
return;

View File

@ -138,9 +138,24 @@ public abstract class SourceWriter implements Appendable, SourceWriterSink {
@Override
public abstract SourceWriter emitStatementStart();
@Override
public abstract SourceWriter emitVariables(String[] names, String jsName);
@Override
public abstract void emitMethod(MethodDescriptor method);
@Override
public abstract void emitClass(String className);
@Override
public abstract void markClassStart(String className);
@Override
public abstract void markClassEnd();
@Override
public abstract void markSectionStart(int id);
@Override
public abstract void markSectionEnd();
}

View File

@ -104,9 +104,25 @@ public interface SourceWriterSink {
return this;
}
default SourceWriterSink emitVariables(String[] names, String jsName) {
return this;
}
default void emitMethod(MethodDescriptor method) {
}
default void emitClass(String className) {
}
default void markClassStart(String className) {
}
default void markClassEnd() {
}
default void markSectionStart(int id) {
}
default void markSectionEnd() {
}
}

View File

@ -1,45 +0,0 @@
/*
* Copyright 2019 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.decompile;
import java.util.ArrayList;
import java.util.List;
import org.teavm.model.ClassHolder;
public class PreparedClass {
private ClassHolder classHolder;
private List<PreparedMethod> methods = new ArrayList<>();
public PreparedClass(ClassHolder classHolder) {
this.classHolder = classHolder;
}
public String getName() {
return classHolder.getName();
}
public String getParentName() {
return classHolder.getParent();
}
public List<PreparedMethod> getMethods() {
return methods;
}
public ClassHolder getClassHolder() {
return classHolder;
}
}

View File

@ -1,47 +0,0 @@
/*
* Copyright 2019 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.decompile;
import java.util.Set;
import org.teavm.ast.ControlFlowEntry;
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 AccessLevel accessLevel;
public final Set<ElementModifier> modifiers;
public final MethodReference reference;
public final RememberedSource body;
public final RememberedSource parameters;
public final boolean async;
public final ControlFlowEntry[] cfg;
public final PreparedVariable[] variables;
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,32 +0,0 @@
/*
* 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.decompile;
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

@ -25,10 +25,7 @@ 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;
@ -45,20 +42,17 @@ public class MethodBodyRenderer implements MethodNodeVisitor, GeneratorContext {
private boolean minifying;
private boolean async;
private Set<MethodReference> asyncMethods;
private RememberingSourceWriter writer;
private SourceWriter 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) {
public MethodBodyRenderer(RenderingContext context, Diagnostics diagnostics, boolean minifying,
Set<MethodReference> asyncMethods, SourceWriter writer) {
this.context = context;
this.diagnostics = diagnostics;
this.minifying = minifying;
this.asyncMethods = asyncMethods;
writer = new RememberingSourceWriter(debug);
this.writer = writer;
statementRenderer = new StatementRenderer(context, writer);
}
@ -71,15 +65,11 @@ public class MethodBodyRenderer implements MethodNodeVisitor, GeneratorContext {
return context.getDependencyInfo();
}
public void renderNative(Generator generator, boolean async, MethodReference reference,
Set<ElementModifier> modifiers) {
public void renderNative(Generator generator, boolean async, MethodReference reference) {
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) {
@ -87,41 +77,18 @@ public class MethodBodyRenderer implements MethodNodeVisitor, GeneratorContext {
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;
node.acceptVisitor(this);
}
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)));
writer.emitVariables(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) {
public void renderParameters(MethodReference reference, Set<ElementModifier> modifiers) {
int startParam = 0;
if (modifiers.contains(ElementModifier.STATIC)) {
startParam = 1;
@ -139,8 +106,6 @@ public class MethodBodyRenderer implements MethodNodeVisitor, GeneratorContext {
if (count != 1) {
writer.append(")");
}
parameters = writer.save();
writer.clear();
}
private void appendMonitor(StatementRenderer statementRenderer, MethodNode methodNode) {

View File

@ -15,22 +15,18 @@
*/
package org.teavm.backend.javascript.rendering;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.teavm.backend.javascript.codegen.NameFrequencyConsumer;
import org.teavm.backend.javascript.codegen.RememberedSource;
import org.teavm.backend.javascript.codegen.NamingStrategy;
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.ClassReaderSource;
import org.teavm.model.ElementModifier;
import org.teavm.model.FieldHolder;
import org.teavm.model.FieldReference;
import org.teavm.model.MethodDescriptor;
import org.teavm.model.MethodReader;
import org.teavm.model.MethodReference;
import org.teavm.model.ValueType;
class NameFrequencyEstimator implements SourceWriterSink {
public 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,
@ -39,134 +35,137 @@ class NameFrequencyEstimator implements SourceWriterSink {
"monitorExit", Object.class, void.class);
static final MethodReference MONITOR_EXIT_SYNC_METHOD = new MethodReference(Object.class,
"monitorExitSync", Object.class, void.class);
private static final MethodDescriptor CLINIT_METHOD = new MethodDescriptor("<clinit>", ValueType.VOID);
private final NameFrequencyConsumer consumer;
private final ClassReaderSource classSource;
private final Set<MethodReference> asyncFamilyMethods;
NameFrequencyEstimator(NameFrequencyConsumer consumer, ClassReaderSource classSource,
Set<MethodReference> asyncFamilyMethods) {
this.consumer = consumer;
this.classSource = classSource;
this.asyncFamilyMethods = asyncFamilyMethods;
}
public void estimate(PreparedClass cls) {
// Declaration
consumer.consume(cls.getName());
if (cls.getParentName() != null) {
consumer.consume(cls.getParentName());
}
for (FieldHolder field : cls.getClassHolder().getFields()) {
consumer.consume(new FieldReference(cls.getName(), field.getName()));
if (field.getModifiers().contains(ElementModifier.STATIC)) {
consumer.consume(cls.getName());
}
}
// Methods
MethodReader clinit = classSource.get(cls.getName()).getMethod(CLINIT_METHOD);
for (PreparedMethod method : cls.getMethods()) {
consumer.consume(method.reference);
if (asyncFamilyMethods.contains(method.reference)) {
consumer.consume(method.reference);
}
if (clinit != null && (method.modifiers.contains(ElementModifier.STATIC)
|| method.reference.getName().equals("<init>"))) {
consumer.consume(method.reference);
}
if (!method.modifiers.contains(ElementModifier.STATIC)) {
consumer.consume(method.reference.getDescriptor());
consumer.consume(method.reference);
}
if (method.async) {
consumer.consumeFunction("$rt_nativeThread");
consumer.consumeFunction("$rt_nativeThread");
consumer.consumeFunction("$rt_resuming");
consumer.consumeFunction("$rt_invalidPointer");
}
method.body.replay(this, RememberedSource.FILTER_REF);
}
if (clinit != null) {
consumer.consumeFunction("$rt_eraseClinit");
}
// Metadata
consumer.consume(cls.getName());
consumer.consume(cls.getName());
if (cls.getParentName() != null) {
consumer.consume(cls.getParentName());
}
for (String iface : cls.getClassHolder().getInterfaces()) {
consumer.consume(iface);
}
boolean hasFields = false;
for (FieldHolder field : cls.getClassHolder().getFields()) {
if (!field.hasModifier(ElementModifier.STATIC)) {
hasFields = true;
break;
}
}
if (!hasFields) {
consumer.consumeFunction("$rt_classWithoutFields");
}
}
private Map<String, Entry> entries = new HashMap<>();
private Set<String> reservedNames = new HashSet<>();
@Override
public SourceWriterSink appendClass(String cls) {
consumer.consume(cls);
var key = "c:" + cls;
var entry = entries.get(key);
if (entry == null) {
entry = new Entry();
entry.operation = naming -> naming.getNameFor(cls);
entries.put(key, entry);
}
entry.frequency++;
return this;
}
@Override
public SourceWriterSink appendField(FieldReference field) {
consumer.consume(field);
var key = "f:" + field;
var entry = entries.get(key);
if (entry == null) {
entry = new Entry();
entry.operation = naming -> naming.getNameFor(field);
entries.put(key, entry);
}
entry.frequency++;
return this;
}
@Override
public SourceWriterSink appendStaticField(FieldReference field) {
consumer.consumeStatic(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++;
return this;
}
@Override
public SourceWriterSink appendMethod(MethodDescriptor method) {
consumer.consume(method);
var key = "r:" + method;
var entry = entries.get(key);
if (entry == null) {
entry = new Entry();
entry.operation = naming -> naming.getNameFor(method);
entries.put(key, entry);
}
entry.frequency++;
return this;
}
@Override
public SourceWriterSink appendMethodBody(MethodReference method) {
consumer.consume(method);
var key = "R:" + method;
var entry = entries.get(key);
if (entry == null) {
entry = new Entry();
entry.operation = naming -> naming.getFullNameFor(method);
entries.put(key, entry);
}
entry.frequency++;
return this;
}
@Override
public SourceWriterSink appendFunction(String name) {
consumer.consumeFunction(name);
var key = "n:" + name;
var entry = entries.get(key);
if (entry == null) {
entry = new Entry();
entry.operation = naming -> naming.getNameForFunction(name);
entries.put(key, entry);
}
entry.frequency++;
return this;
}
@Override
public SourceWriterSink appendGlobal(String name) {
consumer.consumeGlobal(name);
reservedNames.add(name);
return this;
}
@Override
public SourceWriterSink appendInit(MethodReference method) {
consumer.consumeInit(method);
var key = "I:" + method;
var entry = entries.get(key);
if (entry == null) {
entry = new Entry();
entry.operation = naming -> naming.getNameForInit(method);
entries.put(key, entry);
}
entry.frequency++;
return this;
}
@Override
public SourceWriterSink appendClassInit(String className) {
consumer.consumeClassInit(className);
var key = "C:" + className;
var entry = entries.get(key);
if (entry == null) {
entry = new Entry();
entry.operation = naming -> naming.getNameForClassInit(className);
entries.put(key, entry);
}
entry.frequency++;
return this;
}
public void apply(NamingStrategy naming) {
for (var name : reservedNames) {
naming.reserveName(name);
}
var entryList = new ArrayList<>(entries.values());
entryList.sort((o1, o2) -> Integer.compare(o2.frequency, o1.frequency));
for (var entry : entryList) {
entry.operation.perform(naming);
}
}
private static class Entry {
NamingOperation operation;
int frequency;
}
private interface NamingOperation {
void perform(NamingStrategy naming);
}
}

View File

@ -17,6 +17,7 @@ package org.teavm.backend.javascript.rendering;
import com.carrotsearch.hppc.ObjectIntHashMap;
import com.carrotsearch.hppc.ObjectIntMap;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
@ -27,77 +28,84 @@ import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.function.IntFunction;
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.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;
import org.teavm.ast.decompilation.Decompiler;
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.GeneratedBy;
import org.teavm.backend.javascript.spi.Generator;
import org.teavm.backend.javascript.spi.InjectedBy;
import org.teavm.backend.javascript.templating.JavaScriptTemplateFactory;
import org.teavm.cache.AstCacheEntry;
import org.teavm.cache.AstDependencyExtractor;
import org.teavm.cache.CacheStatus;
import org.teavm.cache.MethodNodeCache;
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.AnnotationHolder;
import org.teavm.model.ClassHolder;
import org.teavm.model.ClassHolderSource;
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;
import org.teavm.model.ListableClassHolderSource;
import org.teavm.model.ListableClassReaderSource;
import org.teavm.model.MethodDescriptor;
import org.teavm.model.MethodHolder;
import org.teavm.model.MethodReader;
import org.teavm.model.MethodReference;
import org.teavm.model.ValueType;
import org.teavm.model.analysis.ClassMetadataRequirements;
import org.teavm.model.util.AsyncMethodFinder;
import org.teavm.vm.RenderingException;
import org.teavm.vm.TeaVMProgressFeedback;
public class Renderer implements RenderingManager {
private final NamingStrategy naming;
private final OutputSourceWriter writer;
public static final int SECTION_STRING_POOL = 0;
public static final int SECTION_METADATA = 1;
private final SourceWriter writer;
private final ListableClassReaderSource classSource;
private final ClassLoader classLoader;
private boolean minifying;
private final Properties properties = new Properties();
private final ServiceRepository services;
private DebugInformationEmitter debugEmitter = new DummyDebugInformationEmitter();
private final Set<MethodReference> asyncMethods;
private final Set<MethodReference> asyncFamilyMethods;
private RenderingContext context;
private List<PostponedFieldInitializer> postponedFieldInitializers = new ArrayList<>();
private IntFunction<TeaVMProgressFeedback> progressConsumer = p -> TeaVMProgressFeedback.CONTINUE;
private MethodBodyRenderer methodBodyRenderer;
private Map<String, Generator> generatorCache = new HashMap<>();
private Map<MethodReference, Generator> generators;
private MethodNodeCache astCache;
private CacheStatus cacheStatus;
private JavaScriptTemplateFactory templateFactory;
private boolean threadLibraryUsed;
private AstDependencyExtractor dependencyExtractor = new AstDependencyExtractor();
public static final MethodDescriptor CLINIT_METHOD = new MethodDescriptor("<clinit>", ValueType.VOID);
private ObjectIntMap<String> sizeByClass = new ObjectIntHashMap<>();
private int stringPoolSize;
private int metadataSize;
public Renderer(OutputSourceWriter writer, Set<MethodReference> asyncMethods,
Set<MethodReference> asyncFamilyMethods, RenderingContext context) {
this.naming = context.getNaming();
public Renderer(SourceWriter writer, Set<MethodReference> asyncMethods, RenderingContext context,
Diagnostics diagnostics, Map<MethodReference, Generator> generators,
MethodNodeCache astCache, CacheStatus cacheStatus, JavaScriptTemplateFactory templateFactory) {
this.writer = writer;
this.classSource = context.getClassSource();
this.classLoader = context.getClassLoader();
this.services = context.getServices();
this.asyncMethods = new HashSet<>(asyncMethods);
this.asyncFamilyMethods = new HashSet<>(asyncFamilyMethods);
this.context = context;
}
public int getStringPoolSize() {
return stringPoolSize;
}
public int getMetadataSize() {
return metadataSize;
}
public String[] getClassesInStats() {
return sizeByClass.keys().toArray(String.class);
}
public int getClassSize(String className) {
return sizeByClass.getOrDefault(className, 0);
methodBodyRenderer = new MethodBodyRenderer(context, diagnostics, context.isMinifying(), asyncMethods,
writer);
this.generators = generators;
this.astCache = astCache;
this.cacheStatus = cacheStatus;
this.templateFactory = templateFactory;
}
@Override
@ -105,18 +113,8 @@ public class Renderer implements RenderingManager {
return writer;
}
@Override
public NamingStrategy getNaming() {
return naming;
}
@Override
public boolean isMinifying() {
return minifying;
}
public void setMinifying(boolean minifying) {
this.minifying = minifying;
public boolean isThreadLibraryUsed() {
return threadLibraryUsed;
}
@Override
@ -136,10 +134,6 @@ public class Renderer implements RenderingManager {
return properties;
}
public void setDebugEmitter(DebugInformationEmitter debugEmitter) {
this.debugEmitter = debugEmitter;
}
public void setProgressConsumer(IntFunction<TeaVMProgressFeedback> progressConsumer) {
this.progressConsumer = progressConsumer;
}
@ -153,7 +147,7 @@ public class Renderer implements RenderingManager {
if (context.getStringPool().isEmpty()) {
return;
}
int start = writer.getOffset();
writer.markSectionStart(SECTION_STRING_POOL);
writer.appendFunction("$rt_stringPool").append("([");
for (int i = 0; i < context.getStringPool().size(); ++i) {
if (i > 0) {
@ -162,17 +156,16 @@ public class Renderer implements RenderingManager {
RenderingUtil.writeString(writer, context.getStringPool().get(i));
}
writer.append("]);").newLine();
stringPoolSize = writer.getOffset() - start;
writer.markSectionEnd();
}
public void renderStringConstants() throws RenderingException {
for (PostponedFieldInitializer initializer : postponedFieldInitializers) {
int start = writer.getOffset();
writer.markSectionStart(SECTION_STRING_POOL);
writer.appendStaticField(initializer.field).ws().append("=").ws();
context.constantToString(writer, initializer.value);
writer.append(";").softNewLine();
int sz = writer.getOffset() - start;
appendClassSize(initializer.field.getClassName(), sz);
writer.markSectionEnd();
}
}
@ -207,44 +200,58 @@ public class Renderer implements RenderingManager {
writer.outdent().append("};").newLine();
}
private void appendClassSize(String className, int sz) {
sizeByClass.put(className, sizeByClass.getOrDefault(className, 0) + sz);
public boolean render(ListableClassHolderSource classes, boolean isFriendlyToDebugger) {
var sequence = new ArrayList<ClassHolder>();
var visited = new HashSet<String>();
for (String className : classes.getClassNames()) {
orderClasses(classes, className, visited, sequence);
}
public void prepare(List<PreparedClass> classes) {
if (minifying) {
NamingOrderer orderer = new NamingOrderer();
NameFrequencyEstimator estimator = new NameFrequencyEstimator(orderer, classSource,
asyncFamilyMethods);
for (PreparedClass cls : classes) {
estimator.estimate(cls);
}
naming.getScopeName();
orderer.apply(naming);
}
}
var asyncFinder = new AsyncMethodFinder(context.getDependencyInfo().getCallGraph(),
context.getDependencyInfo());
asyncFinder.find(classes);
asyncMethods.addAll(asyncFinder.getAsyncMethods());
var splitMethods = new HashSet<>(asyncMethods);
splitMethods.addAll(asyncFinder.getAsyncFamilyMethods());
var decompiler = new Decompiler(classes, splitMethods, isFriendlyToDebugger);
public boolean render(List<PreparedClass> classes) throws RenderingException {
int index = 0;
for (PreparedClass cls : classes) {
int start = writer.getOffset();
for (var cls : sequence) {
writer.markClassStart(cls.getName());
renderDeclaration(cls);
renderMethodBodies(cls);
appendClassSize(cls.getName(), writer.getOffset() - start);
if (progressConsumer.apply(1000 * ++index / classes.size()) == TeaVMProgressFeedback.CANCEL) {
renderMethodBodies(cls, decompiler);
writer.markClassEnd();
if (progressConsumer.apply(1000 * ++index / sequence.size()) == TeaVMProgressFeedback.CANCEL) {
return false;
}
}
renderClassMetadata(classes);
renderClassMetadata(sequence);
return true;
}
private void renderDeclaration(PreparedClass cls) throws RenderingException {
ScopedName jsName = naming.getNameFor(cls.getName());
debugEmitter.addClass(jsName.value, cls.getName(), cls.getParentName());
private void orderClasses(ClassHolderSource classes, String className, Set<String> visited,
List<ClassHolder> order) {
if (!visited.add(className)) {
return;
}
ClassHolder cls = classes.get(className);
if (cls == null) {
return;
}
if (cls.getParent() != null) {
orderClasses(classes, cls.getParent(), visited, order);
}
for (String iface : cls.getInterfaces()) {
orderClasses(classes, iface, visited, order);
}
order.add(cls);
}
private void renderDeclaration(ClassHolder cls) throws RenderingException {
List<FieldHolder> nonStaticFields = new ArrayList<>();
List<FieldHolder> staticFields = new ArrayList<>();
for (FieldHolder field : cls.getClassHolder().getFields()) {
for (FieldHolder field : cls.getFields()) {
if (field.getModifiers().contains(ElementModifier.STATIC)) {
staticFields.add(field);
} else {
@ -252,12 +259,13 @@ public class Renderer implements RenderingManager {
}
}
if (nonStaticFields.isEmpty() && !cls.getClassHolder().getName().equals("java.lang.Object")) {
renderShortClassFunctionDeclaration(cls, jsName);
if (nonStaticFields.isEmpty() && !cls.getName().equals("java.lang.Object")) {
renderShortClassFunctionDeclaration(cls);
} else {
renderFullClassFunctionDeclaration(cls, jsName, nonStaticFields);
renderFullClassFunctionDeclaration(cls, nonStaticFields);
}
var hasLet = false;
for (FieldHolder field : staticFields) {
Object value = field.getInitialValue();
if (value == null) {
@ -270,30 +278,30 @@ public class Renderer implements RenderingManager {
value = null;
}
ScopedName fieldName = naming.getFullNameFor(fieldRef);
if (fieldName.scoped) {
writer.append(naming.getScopeName()).append(".");
} else {
if (!hasLet) {
writer.append("let ");
hasLet = true;
} else {
writer.append(",").ws();
}
writer.append(fieldName.value).ws().append("=").ws();
writer.appendStaticField(fieldRef).ws().append("=").ws();
context.constantToString(writer, value);
writer.append(";").softNewLine();
}
if (hasLet) {
writer.append(";").newLine();
}
}
private void renderFullClassFunctionDeclaration(PreparedClass cls, ScopedName jsName,
List<FieldHolder> nonStaticFields) {
private void renderFullClassFunctionDeclaration(ClassReader cls, List<FieldHolder> nonStaticFields) {
boolean thisAliased = false;
renderFunctionDeclaration(jsName);
writer.append("()").ws().append("{").indent().softNewLine();
writer.append("function ").appendClass(cls.getName()).append("()").ws().append("{").indent().softNewLine();
if (nonStaticFields.size() > 1) {
thisAliased = true;
writer.append("let a").ws().append("=").ws().append("this;").ws();
}
if (!cls.getClassHolder().getModifiers().contains(ElementModifier.INTERFACE)
&& cls.getParentName() != null) {
writer.appendClass(cls.getParentName()).append(".call(").append(thisAliased ? "a" : "this")
if (!cls.readModifiers().contains(ElementModifier.INTERFACE)
&& cls.getParent() != null) {
writer.appendClass(cls.getParent()).append(".call(").append(thisAliased ? "a" : "this")
.append(");").softNewLine();
}
for (FieldHolder field : nonStaticFields) {
@ -306,7 +314,6 @@ public class Renderer implements RenderingManager {
.append("=").ws();
context.constantToString(writer, value);
writer.append(";").softNewLine();
debugEmitter.addField(field.getName(), naming.getNameFor(fieldRef));
}
if (cls.getName().equals("java.lang.Object")) {
@ -314,65 +321,83 @@ public class Renderer implements RenderingManager {
}
writer.outdent().append("}");
if (jsName.scoped) {
writer.append(";");
}
writer.newLine();
}
private void renderShortClassFunctionDeclaration(PreparedClass cls, ScopedName jsName) {
if (jsName.scoped) {
writer.append(naming.getScopeName()).append(".");
} else {
writer.append("let ");
}
writer.append(jsName.value).ws().append("=").ws().appendFunction("$rt_classWithoutFields").append("(");
if (cls.getClassHolder().hasModifier(ElementModifier.INTERFACE)) {
private void renderShortClassFunctionDeclaration(ClassReader cls) {
writer.append("let ").appendClass(cls.getName()).ws().append("=").ws()
.appendFunction("$rt_classWithoutFields").append("(");
if (cls.hasModifier(ElementModifier.INTERFACE)) {
writer.append("0");
} else if (!cls.getParentName().equals("java.lang.Object")) {
writer.appendClass(cls.getParentName());
} else if (!cls.getParent().equals("java.lang.Object")) {
writer.appendClass(cls.getParent());
}
writer.append(");").newLine();
}
private void renderMethodBodies(PreparedClass cls) throws RenderingException {
debugEmitter.emitClass(cls.getName());
private void renderMethodBodies(ClassHolder cls, Decompiler decompiler) {
writer.emitClass(cls.getName());
MethodReader clinit = classSource.get(cls.getName()).getMethod(CLINIT_METHOD);
if (clinit != null && context.isDynamicInitializer(cls.getName())) {
renderCallClinit(clinit, cls);
}
if (!cls.getClassHolder().hasModifier(ElementModifier.INTERFACE)
&& !cls.getClassHolder().hasModifier(ElementModifier.ABSTRACT)) {
for (PreparedMethod method : cls.getMethods()) {
if (!method.modifiers.contains(ElementModifier.STATIC)) {
if (method.reference.getName().equals("<init>")) {
if (!cls.hasModifier(ElementModifier.INTERFACE)
&& !cls.hasModifier(ElementModifier.ABSTRACT)) {
for (var method : cls.getMethods()) {
if (!method.hasModifier(ElementModifier.STATIC)) {
if (method.getName().equals("<init>")) {
renderInitializer(method);
}
}
}
}
for (PreparedMethod method : cls.getMethods()) {
renderBody(method);
var hasLet = false;
for (var method : cls.getMethods()) {
if (!filterMethod(method)) {
continue;
}
if (!hasLet) {
writer.append("let ");
hasLet = true;
} else {
writer.append(",").newLine();
}
renderBody(method, decompiler);
}
if (hasLet) {
writer.append(";").newLine();
}
debugEmitter.emitClass(null);
writer.emitClass(null);
}
private void renderCallClinit(MethodReader clinit, PreparedClass cls) {
private boolean filterMethod(MethodReader method) {
if (method.hasModifier(ElementModifier.ABSTRACT)) {
return false;
}
if (method.getAnnotations().get(InjectedBy.class.getName()) != null
|| context.getInjector(method.getReference()) != null) {
return false;
}
if (!method.hasModifier(ElementModifier.NATIVE) && method.getProgram() == null) {
return false;
}
return true;
}
private void renderCallClinit(MethodReader clinit, ClassReader cls) {
boolean isAsync = asyncMethods.contains(clinit.getReference());
ScopedName className = naming.getNameFor(cls.getName());
String clinitCalled = (className.scoped ? naming.getScopeName() + "_" : "") + className.value
+ "_$clinitCalled";
var clinitCalledField = new FieldReference(cls.getName(), "$_teavm_clinitCalled_$");
if (isAsync) {
writer.append("let ").append(clinitCalled).ws().append("=").ws().append("false;").softNewLine();
writer.append("let ").appendStaticField(clinitCalledField).ws().append("=").ws().append("false;")
.softNewLine();
}
ScopedName name = naming.getNameForClassInit(cls.getName());
renderLambdaDeclaration(name);
writer.append("let ").appendClassInit(cls.getName()).ws().append("=").ws();
writer.append("()").sameLineWs().append("=>").ws().append("{").softNewLine().indent();
if (isAsync) {
@ -383,7 +408,7 @@ public class Renderer implements RenderingManager {
writer.append(context.pointerName()).ws().append("=").ws().appendFunction("$rt_nativeThread")
.append("().pop();").softNewLine();
writer.outdent().append("}").ws();
writer.append("else if").ws().append("(").append(clinitCalled).append(")").ws()
writer.append("else if").ws().append("(").appendStaticField(clinitCalledField).append(")").ws()
.append("{").indent().softNewLine();
writer.append("return;").softNewLine();
writer.outdent().append("}").softNewLine();
@ -391,7 +416,7 @@ public class Renderer implements RenderingManager {
renderAsyncPrologue(writer, context);
writer.append("case 0:").indent().softNewLine();
writer.append(clinitCalled).ws().append('=').ws().append("true;").softNewLine();
writer.appendStaticField(clinitCalledField).ws().append('=').ws().append("true;").softNewLine();
} else {
renderEraseClinit(cls);
}
@ -416,55 +441,48 @@ public class Renderer implements RenderingManager {
writer.appendFunction("$rt_nativeThread").append("().push(" + context.pointerName() + ");").softNewLine();
}
writer.outdent().append("}");
if (name.scoped) {
writer.append(";");
}
writer.outdent().append("};");
writer.newLine();
}
private void renderEraseClinit(PreparedClass cls) {
private void renderEraseClinit(ClassReader cls) {
writer.appendClassInit(cls.getName()).ws().append("=").ws()
.appendFunction("$rt_eraseClinit").append("(")
.appendClass(cls.getName()).append(");").softNewLine();
}
private void renderClassMetadata(List<PreparedClass> classes) {
if (classes.isEmpty()) {
return;
}
private void renderClassMetadata(List<? extends ClassReader> classReaders) {
ClassMetadataRequirements metadataRequirements = new ClassMetadataRequirements(context.getDependencyInfo());
int start = writer.getOffset();
writer.markSectionStart(SECTION_METADATA);
writer.appendFunction("$rt_packages").append("([");
ObjectIntMap<String> packageIndexes = generatePackageMetadata(classes, metadataRequirements);
ObjectIntMap<String> packageIndexes = generatePackageMetadata(classReaders, metadataRequirements);
writer.append("]);").newLine();
for (int i = 0; i < classes.size(); i += 50) {
int j = Math.min(i + 50, classes.size());
renderClassMetadataPortion(classes.subList(i, j), packageIndexes, metadataRequirements);
for (int i = 0; i < classReaders.size(); i += 50) {
int j = Math.min(i + 50, classReaders.size());
renderClassMetadataPortion(classReaders.subList(i, j), packageIndexes, metadataRequirements);
}
metadataSize = writer.getOffset() - start;
writer.markSectionEnd();
}
private void renderClassMetadataPortion(List<PreparedClass> classes, ObjectIntMap<String> packageIndexes,
private void renderClassMetadataPortion(List<? extends ClassReader> classes, ObjectIntMap<String> packageIndexes,
ClassMetadataRequirements metadataRequirements) {
writer.appendFunction("$rt_metadata").append("([");
boolean first = true;
for (PreparedClass cls : classes) {
for (var cls : classes) {
if (!first) {
writer.append(',').softNewLine();
}
first = false;
debugEmitter.emitClass(cls.getName());
writer.emitClass(cls.getName());
writer.appendClass(cls.getName()).append(",").ws();
ClassMetadataRequirements.Info requiredMetadata = metadataRequirements.getInfo(cls.getName());
var className = cls.getName();
var requiredMetadata = metadataRequirements.getInfo(className);
if (requiredMetadata.name()) {
String className = cls.getName();
int dotIndex = className.lastIndexOf('.') + 1;
String packageName = className.substring(0, dotIndex);
className = className.substring(dotIndex);
@ -475,14 +493,14 @@ public class Renderer implements RenderingManager {
}
writer.append(",").ws();
if (cls.getParentName() != null) {
writer.appendClass(cls.getParentName());
if (cls.getParent() != null) {
writer.appendClass(cls.getParent());
} else {
writer.append("0");
}
writer.append(',').ws();
writer.append("[");
List<String> interfaces = new ArrayList<>(cls.getClassHolder().getInterfaces());
var interfaces = new ArrayList<>(cls.getInterfaces());
for (int i = 0; i < interfaces.size(); ++i) {
String iface = interfaces.get(i);
if (i > 0) {
@ -492,28 +510,28 @@ public class Renderer implements RenderingManager {
}
writer.append("],").ws();
writer.append(ElementModifier.pack(cls.getClassHolder().getModifiers())).append(',').ws();
writer.append(cls.getClassHolder().getLevel().ordinal()).append(',').ws();
writer.append(ElementModifier.pack(cls.readModifiers())).append(',').ws();
writer.append(cls.getLevel().ordinal()).append(',').ws();
if (!requiredMetadata.enclosingClass() && !requiredMetadata.declaringClass()
&& !requiredMetadata.simpleName()) {
writer.append("0");
} else {
writer.append('[');
if (requiredMetadata.enclosingClass() && cls.getClassHolder().getOwnerName() != null) {
writer.appendClass(cls.getClassHolder().getOwnerName());
if (requiredMetadata.enclosingClass() && cls.getOwnerName() != null) {
writer.appendClass(cls.getOwnerName());
} else {
writer.append('0');
}
writer.append(',');
if (requiredMetadata.declaringClass() && cls.getClassHolder().getDeclaringClassName() != null) {
writer.appendClass(cls.getClassHolder().getDeclaringClassName());
if (requiredMetadata.declaringClass() && cls.getDeclaringClassName() != null) {
writer.appendClass(cls.getDeclaringClassName());
} else {
writer.append('0');
}
writer.append(',');
if (requiredMetadata.simpleName() && cls.getClassHolder().getSimpleName() != null) {
writer.append("\"").append(RenderingUtil.escapeString(cls.getClassHolder().getSimpleName()))
if (requiredMetadata.simpleName() && cls.getSimpleName() != null) {
writer.append("\"").append(RenderingUtil.escapeString(cls.getSimpleName()))
.append("\"");
} else {
writer.append('0');
@ -532,30 +550,30 @@ public class Renderer implements RenderingManager {
Map<MethodDescriptor, MethodReference> virtualMethods = new LinkedHashMap<>();
collectMethodsToCopyFromInterfaces(classSource.get(cls.getName()), virtualMethods);
for (PreparedMethod method : cls.getMethods()) {
if (!method.modifiers.contains(ElementModifier.STATIC)
&& method.accessLevel != AccessLevel.PRIVATE) {
virtualMethods.put(method.reference.getDescriptor(), method.reference);
for (var method : cls.getMethods()) {
if (filterMethod(method) && !method.readModifiers().contains(ElementModifier.STATIC)
&& method.getLevel() != AccessLevel.PRIVATE) {
virtualMethods.put(method.getDescriptor(), method.getReference());
}
}
renderVirtualDeclarations(virtualMethods.values());
debugEmitter.emitClass(null);
writer.emitClass(null);
}
writer.append("]);").newLine();
}
private ObjectIntMap<String> generatePackageMetadata(List<PreparedClass> classes,
private ObjectIntMap<String> generatePackageMetadata(List<? extends ClassReader> classes,
ClassMetadataRequirements metadataRequirements) {
PackageNode root = new PackageNode(null);
for (PreparedClass classNode : classes) {
String className = classNode.getName();
ClassMetadataRequirements.Info requiredMetadata = metadataRequirements.getInfo(className);
for (var cls : classes) {
var requiredMetadata = metadataRequirements.getInfo(cls.getName());
if (!requiredMetadata.name()) {
continue;
}
var className = cls.getName();
int dotIndex = className.lastIndexOf('.');
if (dotIndex < 0) {
continue;
@ -593,14 +611,6 @@ public class Renderer implements RenderingManager {
PackageNode(String name) {
this.name = name;
}
int count() {
int result = 0;
for (PackageNode child : children.values()) {
result += 1 + child.count();
}
return result;
}
}
private void addPackageName(PackageNode node, String name) {
@ -686,11 +696,10 @@ public class Renderer implements RenderingManager {
return null;
}
private void renderInitializer(PreparedMethod method) {
MethodReference ref = method.reference;
debugEmitter.emitMethod(ref.getDescriptor());
ScopedName name = naming.getNameForInit(ref);
renderLambdaDeclaration(name);
private void renderInitializer(MethodReader method) {
MethodReference ref = method.getReference();
writer.emitMethod(ref.getDescriptor());
writer.append("let ").appendInit(ref).ws().append("=").ws();
if (ref.parameterCount() != 1) {
writer.append("(");
}
@ -715,16 +724,13 @@ public class Renderer implements RenderingManager {
}
writer.append(");").softNewLine();
writer.append("return " + instanceName + ";").softNewLine();
writer.outdent().append("}");
if (name.scoped) {
writer.append(";");
}
writer.outdent().append("};");
writer.newLine();
debugEmitter.emitMethod(null);
writer.emitMethod(null);
}
private String variableNameForInitializer(int index) {
return minifying ? RenderingUtil.indexToId(index) : "var_" + index;
return context.isMinifying() ? RenderingUtil.indexToId(index) : "var_" + index;
}
private void renderVirtualDeclarations(Collection<MethodReference> methods) {
@ -739,19 +745,19 @@ public class Renderer implements RenderingManager {
if (!isVirtual(method)) {
continue;
}
debugEmitter.emitMethod(method.getDescriptor());
writer.emitMethod(method.getDescriptor());
if (!first) {
writer.append(",").ws();
}
first = false;
emitVirtualDeclaration(method);
debugEmitter.emitMethod(null);
writer.emitMethod(null);
}
writer.append("]");
}
private void emitVirtualDeclaration(MethodReference ref) {
String methodName = naming.getNameFor(ref.getDescriptor());
String methodName = context.getNaming().getNameFor(ref.getDescriptor());
writer.append("\"").append(methodName).append("\"");
writer.append(",").ws();
emitVirtualFunctionWrapper(ref);
@ -787,47 +793,142 @@ public class Renderer implements RenderingManager {
writer.append(");").ws().append("}");
}
private void renderBody(PreparedMethod method) {
MethodReference ref = method.reference;
debugEmitter.emitMethod(ref.getDescriptor());
ScopedName name = naming.getFullNameFor(ref);
private void renderBody(MethodHolder method, Decompiler decompiler) {
MethodReference ref = method.getReference();
writer.emitMethod(ref.getDescriptor());
renderLambdaDeclaration(name);
method.parameters.replay(writer, RememberedSource.FILTER_ALL);
if (method.variables != null) {
for (var variable : method.variables) {
variable.emit(debugEmitter);
}
}
writer.appendMethodBody(ref).ws().append("=").ws();
methodBodyRenderer.renderParameters(ref, method.getModifiers());
writer.sameLineWs().append("=>").ws().append("{").indent().softNewLine();
method.body.replay(writer, RememberedSource.FILTER_ALL);
if (method.hasModifier(ElementModifier.NATIVE)) {
renderNativeBody(method, classSource);
} else {
renderRegularBody(method, decompiler);
}
writer.outdent().append("}");
if (name.scoped) {
writer.append(";");
writer.emitMethod(null);
}
writer.newLine();
debugEmitter.emitMethod(null);
private void renderNativeBody(MethodHolder method, ClassReaderSource classes) {
var reference = method.getReference();
var generator = generators.get(reference);
if (generator == null) {
AnnotationHolder annotHolder = method.getAnnotations().get(GeneratedBy.class.getName());
if (annotHolder == null) {
throw new DecompilationException("Method " + method.getOwnerName() + "." + method.getDescriptor()
+ " is native, but no " + GeneratedBy.class.getName() + " annotation found");
}
ValueType annotValue = annotHolder.getValues().get("value").getJavaClass();
String generatorClassName = ((ValueType.Object) annotValue).getClassName();
generator = generatorCache.computeIfAbsent(generatorClassName,
name -> createGenerator(name, method, classes));
}
private void renderLambdaDeclaration(ScopedName name) {
if (name.scoped) {
writer.append(naming.getScopeName()).append(".").append(name.value);
var async = asyncMethods.contains(reference);
methodBodyRenderer.renderNative(generator, async, reference);
threadLibraryUsed |= methodBodyRenderer.isThreadLibraryUsed();
}
private Generator createGenerator(String name, MethodHolder method, ClassReaderSource classes) {
Class<?> generatorClass;
try {
generatorClass = Class.forName(name, true, context.getClassLoader());
} catch (ClassNotFoundException e) {
throw new DecompilationException("Error instantiating generator " + name
+ " for native method " + method.getOwnerName() + "." + method.getDescriptor());
}
var constructors = generatorClass.getConstructors();
if (constructors.length != 1) {
throw new DecompilationException("Error instantiating generator " + name
+ " for native method " + method.getOwnerName() + "." + method.getDescriptor());
}
var constructor = constructors[0];
var parameterTypes = constructor.getParameterTypes();
var arguments = new Object[parameterTypes.length];
for (var i = 0; i < arguments.length; ++i) {
var parameterType = parameterTypes[i];
if (parameterType.equals(ClassReaderSource.class)) {
arguments[i] = classes;
} else if (parameterType.equals(Properties.class)) {
arguments[i] = context.getProperties();
} else if (parameterType.equals(DependencyInfo.class)) {
arguments[i] = context.getDependencyInfo();
} else if (parameterType.equals(ServiceRepository.class)) {
arguments[i] = context.getServices();
} else if (parameterType.equals(JavaScriptTemplateFactory.class)) {
arguments[i] = templateFactory;
} else {
writer.append("let ").append(name.value);
var service = context.getServices().getService(parameterType);
if (service == null) {
throw new DecompilationException("Error instantiating generator " + name
+ " for native method " + method.getOwnerName() + "." + method.getDescriptor() + ". "
+ "Its constructor requires " + parameterType + " as its parameter #" + (i + 1)
+ " which is not available.");
}
}
writer.ws().append("=").ws();
}
private void renderFunctionDeclaration(ScopedName name) {
if (name.scoped) {
writer.append(naming.getScopeName()).append(".").append(name.value).ws().append("=").ws();
try {
return (Generator) constructor.newInstance(arguments);
} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
throw new DecompilationException("Error instantiating generator " + name
+ " for native method " + method.getOwnerName() + "." + method.getDescriptor(), e);
}
writer.append("function");
if (!name.scoped) {
writer.append(" ").append(name.value);
}
private void renderRegularBody(MethodHolder method, Decompiler decompiler) {
MethodReference reference = method.getReference();
MethodNode node;
var async = asyncMethods.contains(reference);
if (async) {
node = decompileAsync(decompiler, method);
} else {
var entry = decompileRegular(decompiler, method);
node = entry.method;
}
methodBodyRenderer.render(node, async);
threadLibraryUsed |= methodBodyRenderer.isThreadLibraryUsed();
}
private AstCacheEntry decompileRegular(Decompiler decompiler, MethodHolder method) {
if (astCache == null) {
return decompileRegularCacheMiss(decompiler, method);
}
AstCacheEntry entry = !cacheStatus.isStaleMethod(method.getReference())
? astCache.get(method.getReference(), cacheStatus)
: null;
if (entry == null) {
entry = decompileRegularCacheMiss(decompiler, method);
RegularMethodNode finalNode = entry.method;
astCache.store(method.getReference(), entry, () -> dependencyExtractor.extract(finalNode));
}
return entry;
}
private AstCacheEntry decompileRegularCacheMiss(Decompiler decompiler, MethodHolder method) {
RegularMethodNode node = decompiler.decompileRegular(method);
ControlFlowEntry[] cfg = LocationGraphBuilder.build(node.getBody());
return new AstCacheEntry(node, cfg);
}
private AsyncMethodNode decompileAsync(Decompiler decompiler, MethodHolder method) {
if (astCache == null) {
return decompiler.decompileAsync(method);
}
AsyncMethodNode node = !cacheStatus.isStaleMethod(method.getReference())
? astCache.getAsync(method.getReference(), cacheStatus)
: null;
if (node == null) {
node = decompiler.decompileAsync(method);
AsyncMethodNode finalNode = node;
astCache.storeAsync(method.getReference(), node, () -> dependencyExtractor.extract(finalNode));
}
return node;
}
static void renderAsyncPrologue(SourceWriter writer, RenderingContext context) {

View File

@ -22,12 +22,8 @@ import org.teavm.common.ServiceRepository;
import org.teavm.model.ListableClassReaderSource;
public interface RenderingManager extends ServiceRepository {
NamingStrategy getNaming();
SourceWriter getWriter();
boolean isMinifying();
ListableClassReaderSource getClassSource();
ClassLoader getClassLoader();

View File

@ -18,7 +18,9 @@
let logging = false;
let deobfuscation = false;
deobfuscator();
if (typeof deobfuscator !== 'undefined') {
deobfuscator();
}
function tryConnect() {
let ws = new WebSocket("ws://localhost:{{PORT}}/ws");