mirror of
https://github.com/Eaglercraft-TeaVM-Fork/eagler-teavm.git
synced 2024-12-22 16:14:10 -08:00
JS: render all declarations into one remembered fragment, then output this fragment to real output
This commit is contained in:
parent
ceffde38f3
commit
5ec4450bf8
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -18,7 +18,9 @@
|
|||
|
||||
let logging = false;
|
||||
let deobfuscation = false;
|
||||
if (typeof deobfuscator !== 'undefined') {
|
||||
deobfuscator();
|
||||
}
|
||||
|
||||
function tryConnect() {
|
||||
let ws = new WebSocket("ws://localhost:{{PORT}}/ws");
|
||||
|
|
Loading…
Reference in New Issue
Block a user