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

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

View File

@ -16,7 +16,6 @@
package org.teavm.backend.javascript; package org.teavm.backend.javascript;
import com.carrotsearch.hppc.ObjectIntHashMap; import com.carrotsearch.hppc.ObjectIntHashMap;
import com.carrotsearch.hppc.ObjectIntMap;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.OutputStreamWriter; import java.io.OutputStreamWriter;
@ -24,7 +23,6 @@ import java.io.Writer;
import java.lang.ref.Reference; import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue; import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import java.lang.reflect.InvocationTargetException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.text.DecimalFormat; import java.text.DecimalFormat;
import java.text.NumberFormat; import java.text.NumberFormat;
@ -37,30 +35,24 @@ import java.util.HashSet;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Properties;
import java.util.Set; import java.util.Set;
import java.util.function.Function; import java.util.function.Function;
import org.teavm.ast.AsyncMethodNode;
import org.teavm.ast.ControlFlowEntry; 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.AliasProvider;
import org.teavm.backend.javascript.codegen.DefaultAliasProvider; import org.teavm.backend.javascript.codegen.DefaultAliasProvider;
import org.teavm.backend.javascript.codegen.DefaultNamingStrategy; import org.teavm.backend.javascript.codegen.DefaultNamingStrategy;
import org.teavm.backend.javascript.codegen.MinifyingAliasProvider; 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.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.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.ReferenceQueueGenerator;
import org.teavm.backend.javascript.intrinsics.ref.ReferenceQueueTransformer; import org.teavm.backend.javascript.intrinsics.ref.ReferenceQueueTransformer;
import org.teavm.backend.javascript.intrinsics.ref.WeakReferenceDependencyListener; import org.teavm.backend.javascript.intrinsics.ref.WeakReferenceDependencyListener;
import org.teavm.backend.javascript.intrinsics.ref.WeakReferenceGenerator; import org.teavm.backend.javascript.intrinsics.ref.WeakReferenceGenerator;
import org.teavm.backend.javascript.intrinsics.ref.WeakReferenceTransformer; 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.Renderer;
import org.teavm.backend.javascript.rendering.RenderingContext; import org.teavm.backend.javascript.rendering.RenderingContext;
import org.teavm.backend.javascript.rendering.RenderingUtil; 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.VirtualMethodContributor;
import org.teavm.backend.javascript.spi.VirtualMethodContributorContext; import org.teavm.backend.javascript.spi.VirtualMethodContributorContext;
import org.teavm.backend.javascript.templating.JavaScriptTemplateFactory; 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.EmptyMethodNodeCache;
import org.teavm.cache.MethodNodeCache; import org.teavm.cache.MethodNodeCache;
import org.teavm.common.ServiceRepository;
import org.teavm.debugging.information.DebugInformationEmitter; import org.teavm.debugging.information.DebugInformationEmitter;
import org.teavm.debugging.information.DummyDebugInformationEmitter; import org.teavm.debugging.information.DummyDebugInformationEmitter;
import org.teavm.debugging.information.SourceLocation; import org.teavm.debugging.information.SourceLocation;
import org.teavm.dependency.AbstractDependencyListener; import org.teavm.dependency.AbstractDependencyListener;
import org.teavm.dependency.DependencyAgent; import org.teavm.dependency.DependencyAgent;
import org.teavm.dependency.DependencyAnalyzer; import org.teavm.dependency.DependencyAnalyzer;
import org.teavm.dependency.DependencyInfo;
import org.teavm.dependency.DependencyListener; import org.teavm.dependency.DependencyListener;
import org.teavm.dependency.DependencyType; import org.teavm.dependency.DependencyType;
import org.teavm.dependency.MethodDependency; import org.teavm.dependency.MethodDependency;
import org.teavm.interop.PlatformMarker; import org.teavm.interop.PlatformMarker;
import org.teavm.interop.Platforms; import org.teavm.interop.Platforms;
import org.teavm.model.AnnotationHolder;
import org.teavm.model.BasicBlock; import org.teavm.model.BasicBlock;
import org.teavm.model.CallLocation; import org.teavm.model.CallLocation;
import org.teavm.model.ClassHolder;
import org.teavm.model.ClassHolderSource;
import org.teavm.model.ClassHolderTransformer; import org.teavm.model.ClassHolderTransformer;
import org.teavm.model.ClassReaderSource; import org.teavm.model.ClassReaderSource;
import org.teavm.model.ElementModifier; 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.BoundCheckInsertion;
import org.teavm.model.transformation.NullCheckFilter; import org.teavm.model.transformation.NullCheckFilter;
import org.teavm.model.transformation.NullCheckInsertion; 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.BuildTarget;
import org.teavm.vm.RenderingException; import org.teavm.vm.RenderingException;
import org.teavm.vm.TeaVMTarget; import org.teavm.vm.TeaVMTarget;
@ -141,18 +123,13 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost {
private DebugInformationEmitter debugEmitter; private DebugInformationEmitter debugEmitter;
private MethodNodeCache astCache = EmptyMethodNodeCache.INSTANCE; private MethodNodeCache astCache = EmptyMethodNodeCache.INSTANCE;
private final Set<MethodReference> asyncMethods = new HashSet<>(); private final Set<MethodReference> asyncMethods = new HashSet<>();
private final Set<MethodReference> asyncFamilyMethods = new HashSet<>();
private List<VirtualMethodContributor> customVirtualMethods = new ArrayList<>(); private List<VirtualMethodContributor> customVirtualMethods = new ArrayList<>();
private int topLevelNameLimit = 500000; private int topLevelNameLimit = 500000;
private AstDependencyExtractor dependencyExtractor = new AstDependencyExtractor();
private boolean strict; private boolean strict;
private BoundCheckInsertion boundCheckInsertion = new BoundCheckInsertion(); private BoundCheckInsertion boundCheckInsertion = new BoundCheckInsertion();
private NullCheckInsertion nullCheckInsertion = new NullCheckInsertion(NullCheckFilter.EMPTY); private NullCheckInsertion nullCheckInsertion = new NullCheckInsertion(NullCheckFilter.EMPTY);
private final Map<String, String> importedModules = new LinkedHashMap<>(); private final Map<String, String> importedModules = new LinkedHashMap<>();
private Map<String, Generator> generatorCache = new HashMap<>();
private JavaScriptTemplateFactory templateFactory; private JavaScriptTemplateFactory templateFactory;
private boolean threadLibraryUsed;
private MethodBodyRenderer bodyRenderer;
@Override @Override
public List<ClassHolderTransformer> getTransformers() { public List<ClassHolderTransformer> getTransformers() {
@ -422,71 +399,47 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost {
}; };
renderingContext.setMinifying(obfuscated); renderingContext.setMinifying(obfuscated);
bodyRenderer = new MethodBodyRenderer(renderingContext, controller.getDiagnostics(), obfuscated,
debugEmitter != null, asyncMethods);
var clsNodes = modelToAst(classes, renderingContext);
if (controller.wasCancelled()) { if (controller.wasCancelled()) {
return; return;
} }
var builder = new OutputSourceWriterBuilder(naming); var builder = new OutputSourceWriterBuilder(naming);
builder.setMinified(obfuscated); builder.setMinified(obfuscated);
var sourceWriter = builder.build(writer);
sourceWriter.setDebugInformationEmitter(debugEmitterToUse); for (var className : classes.getClassNames()) {
var cls = classes.get(className);
var renderer = new Renderer(sourceWriter, asyncMethods, asyncFamilyMethods, renderingContext); for (var method : cls.getMethods()) {
RuntimeRenderer runtimeRenderer = new RuntimeRenderer(classes, sourceWriter); preprocessNativeMethod(method);
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); for (var entry : methodInjectors.entrySet()) {
renderingContext.addInjector(entry.getKey(), entry.getValue());
}
printWrapperStart(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.setProgressConsumer(controller::reportProgress);
for (RendererListener listener : rendererListeners) { for (var listener : rendererListeners) {
listener.begin(renderer, target); listener.begin(renderer, target);
} }
int start = sourceWriter.getOffset(); if (!renderer.render(classes, controller.isFriendlyToDebugger())) {
runtimeRenderer.renderRuntime();
sourceWriter.append("var ").append(renderer.getNaming().getScopeName()).ws().append("=").ws()
.append("Object.create(null);").newLine();
if (!renderer.render(clsNodes)) {
return; return;
} }
runtimeRenderer.renderHandWrittenRuntime("array.js"); var declarations = rememberingWriter.save();
rememberingWriter.clear();
renderer.renderStringPool(); renderer.renderStringPool();
renderer.renderStringConstants(); renderer.renderStringConstants();
renderer.renderCompatibilityStubs(); renderer.renderCompatibilityStubs();
runtimeRenderer.renderHandWrittenRuntime("long.js");
if (threadLibraryUsed) {
runtimeRenderer.renderHandWrittenRuntime("thread.js");
} else {
runtimeRenderer.renderHandWrittenRuntime("simpleThread.js");
}
for (var entry : controller.getEntryPoints().entrySet()) { 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(); var ref = entry.getValue().getMethod();
sourceWriter.appendFunction("$rt_mainStarter").append("(").appendMethodBody(ref); rememberingWriter.appendFunction("$rt_mainStarter").append("(").appendMethodBody(ref);
sourceWriter.append(");").newLine(); rememberingWriter.append(");").newLine();
sourceWriter.appendFunction("$rt_exports").append(".").append(entry.getKey()).append(".") rememberingWriter.appendFunction("$rt_exports").append(".").append(entry.getKey()).append(".")
.append("javaException").ws().append("=").ws().appendFunction("$rt_javaException") .append("javaException").ws().append("=").ws().appendFunction("$rt_javaException")
.append(";").newLine(); .append(";").newLine();
} }
@ -494,11 +447,36 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost {
for (var listener : rendererListeners) { for (var listener : rendererListeners) {
listener.complete(); 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); printWrapperEnd(sourceWriter);
int totalSize = sourceWriter.getOffset() - start; int totalSize = sourceWriter.getOffset() - start;
printStats(renderer, totalSize); printStats(sourceWriter, totalSize);
} }
private void printWrapperStart(SourceWriter writer) { private void printWrapperStart(SourceWriter writer) {
@ -560,19 +538,21 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost {
writer.outdent().append("}));").newLine(); 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"))) { if (!Boolean.parseBoolean(System.getProperty("teavm.js.stats", "false"))) {
return; return;
} }
System.out.println("Total output size: " + STATS_NUM_FORMAT.format(totalSize)); System.out.println("Total output size: " + STATS_NUM_FORMAT.format(totalSize));
System.out.println("Metadata size: " + getSizeWithPercentage(renderer.getMetadataSize(), totalSize)); System.out.println("Metadata size: " + getSizeWithPercentage(
System.out.println("String pool size: " + getSizeWithPercentage(renderer.getStringPoolSize(), totalSize)); 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<>(); var packageSizeMap = new ObjectIntHashMap<String>();
for (String className : renderer.getClassesInStats()) { for (String className : writer.getClassesInStats()) {
String packageName = className.substring(0, className.lastIndexOf('.') + 1); 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); 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) + ")"; 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) { private void preprocessNativeMethod(MethodHolder method) {
if (!method.getModifiers().contains(ElementModifier.NATIVE) if (!method.getModifiers().contains(ElementModifier.NATIVE)
|| methodGenerators.get(method.getReference()) != null || methodGenerators.get(method.getReference()) != null

View File

@ -1,40 +0,0 @@
/*
* Copyright 2016 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.backend.javascript.codegen;
import org.teavm.model.FieldReference;
import org.teavm.model.MethodDescriptor;
import org.teavm.model.MethodReference;
public interface NameFrequencyConsumer {
void consume(MethodReference method);
void consumeInit(MethodReference method);
void consume(MethodDescriptor method);
void consume(String className);
void consumeClassInit(String className);
void consume(FieldReference field);
void consumeStatic(FieldReference field);
void consumeFunction(String name);
void consumeGlobal(String name);
}

View File

@ -1,148 +0,0 @@
/*
* Copyright 2016 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.backend.javascript.codegen;
import java.util.*;
import org.teavm.model.FieldReference;
import org.teavm.model.MethodDescriptor;
import org.teavm.model.MethodReference;
public class NamingOrderer implements NameFrequencyConsumer {
private Map<String, Entry> entries = new HashMap<>();
private Set<String> reservedNames = new HashSet<>();
@Override
public void consume(MethodReference method) {
String key = "R:" + method;
Entry entry = entries.get(key);
if (entry == null) {
entry = new Entry();
entry.operation = naming -> naming.getFullNameFor(method);
entries.put(key, entry);
}
entry.frequency++;
}
@Override
public void consumeInit(MethodReference method) {
String key = "I:" + method;
Entry entry = entries.get(key);
if (entry == null) {
entry = new Entry();
entry.operation = naming -> naming.getNameForInit(method);
entries.put(key, entry);
}
entry.frequency++;
}
@Override
public void consume(MethodDescriptor method) {
String key = "r:" + method;
Entry entry = entries.get(key);
if (entry == null) {
entry = new Entry();
entry.operation = naming -> naming.getNameFor(method);
entries.put(key, entry);
}
entry.frequency++;
}
@Override
public void consume(String className) {
String key = "c:" + className;
Entry entry = entries.get(key);
if (entry == null) {
entry = new Entry();
entry.operation = naming -> naming.getNameFor(className);
entries.put(key, entry);
}
entry.frequency++;
}
@Override
public void consumeClassInit(String className) {
String key = "C:" + className;
Entry entry = entries.get(key);
if (entry == null) {
entry = new Entry();
entry.operation = naming -> naming.getNameForClassInit(className);
entries.put(key, entry);
}
entry.frequency++;
}
@Override
public void consume(FieldReference field) {
String key = "f:" + field;
Entry entry = entries.get(key);
if (entry == null) {
entry = new Entry();
entry.operation = naming -> naming.getNameFor(field);
entries.put(key, entry);
}
entry.frequency++;
}
@Override
public void consumeStatic(FieldReference field) {
var key = "sf:" + field;
var entry = entries.get(key);
if (entry == null) {
entry = new Entry();
entry.operation = naming -> naming.getFullNameFor(field);
entries.put(key, entry);
}
entry.frequency++;
}
@Override
public void consumeFunction(String name) {
String key = "n:" + name;
Entry entry = entries.get(key);
if (entry == null) {
entry = new Entry();
entry.operation = naming -> naming.getNameForFunction(name);
entries.put(key, entry);
}
entry.frequency++;
}
@Override
public void consumeGlobal(String name) {
reservedNames.add(name);
}
public void apply(NamingStrategy naming) {
for (var name : reservedNames) {
naming.reserveName(name);
}
List<Entry> entryList = new ArrayList<>(entries.values());
Collections.sort(entryList, (o1, o2) -> Integer.compare(o2.frequency, o1.frequency));
for (Entry entry : entryList) {
entry.operation.perform(naming);
}
}
static class Entry {
NamingOperation operation;
int frequency;
}
interface NamingOperation {
void perform(NamingStrategy naming);
}
}

View File

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

View File

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

View File

@ -46,8 +46,13 @@ public class RememberingSourceWriter extends SourceWriter {
static final byte ENTER_LOCATION = 16; static final byte ENTER_LOCATION = 16;
static final byte EXIT_LOCATION = 17; static final byte EXIT_LOCATION = 17;
static final byte EMIT_STATEMENT_START = 18; static final byte EMIT_STATEMENT_START = 18;
static final byte EMIT_VARIABLES = 26;
static final byte EMIT_METHOD = 19; static final byte EMIT_METHOD = 19;
static final byte EMIT_CLASS = 20; 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; private boolean debug;
@ -262,6 +267,20 @@ public class RememberingSourceWriter extends SourceWriter {
return this; 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 @Override
public void emitMethod(MethodDescriptor method) { public void emitMethod(MethodDescriptor method) {
if (!debug) { 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() { public void flush() {
if (lastWrittenChar == sb.length()) { if (lastWrittenChar == sb.length()) {
return; return;

View File

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

View File

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

View File

@ -1,45 +0,0 @@
/*
* Copyright 2019 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.backend.javascript.decompile;
import java.util.ArrayList;
import java.util.List;
import org.teavm.model.ClassHolder;
public class PreparedClass {
private ClassHolder classHolder;
private List<PreparedMethod> methods = new ArrayList<>();
public PreparedClass(ClassHolder classHolder) {
this.classHolder = classHolder;
}
public String getName() {
return classHolder.getName();
}
public String getParentName() {
return classHolder.getParent();
}
public List<PreparedMethod> getMethods() {
return methods;
}
public ClassHolder getClassHolder() {
return classHolder;
}
}

View File

@ -1,47 +0,0 @@
/*
* Copyright 2019 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.backend.javascript.decompile;
import java.util.Set;
import org.teavm.ast.ControlFlowEntry;
import org.teavm.backend.javascript.codegen.RememberedSource;
import org.teavm.model.AccessLevel;
import org.teavm.model.ElementModifier;
import org.teavm.model.MethodReference;
public class PreparedMethod {
public final AccessLevel accessLevel;
public final Set<ElementModifier> modifiers;
public final MethodReference reference;
public final RememberedSource body;
public final RememberedSource parameters;
public final boolean async;
public final ControlFlowEntry[] cfg;
public final PreparedVariable[] variables;
public PreparedMethod(AccessLevel accessLevel, Set<ElementModifier> modifiers, MethodReference reference,
RememberedSource body, RememberedSource parameters, boolean async, ControlFlowEntry[] cfg,
PreparedVariable[] variables) {
this.accessLevel = accessLevel;
this.modifiers = modifiers;
this.reference = reference;
this.body = body;
this.parameters = parameters;
this.async = async;
this.cfg = cfg;
this.variables = variables;
}
}

View File

@ -1,32 +0,0 @@
/*
* Copyright 2023 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.backend.javascript.decompile;
import org.teavm.debugging.information.DebugInformationEmitter;
public class PreparedVariable {
public final String[] names;
public final String jsName;
public PreparedVariable(String[] names, String jsName) {
this.names = names;
this.jsName = jsName;
}
public void emit(DebugInformationEmitter emitter) {
emitter.emitVariable(names, jsName);
}
}

View File

@ -25,10 +25,7 @@ import org.teavm.ast.MethodNode;
import org.teavm.ast.MethodNodeVisitor; import org.teavm.ast.MethodNodeVisitor;
import org.teavm.ast.RegularMethodNode; import org.teavm.ast.RegularMethodNode;
import org.teavm.ast.VariableNode; 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.codegen.SourceWriter;
import org.teavm.backend.javascript.decompile.PreparedVariable;
import org.teavm.backend.javascript.spi.Generator; import org.teavm.backend.javascript.spi.Generator;
import org.teavm.backend.javascript.spi.GeneratorContext; import org.teavm.backend.javascript.spi.GeneratorContext;
import org.teavm.dependency.DependencyInfo; import org.teavm.dependency.DependencyInfo;
@ -45,20 +42,17 @@ public class MethodBodyRenderer implements MethodNodeVisitor, GeneratorContext {
private boolean minifying; private boolean minifying;
private boolean async; private boolean async;
private Set<MethodReference> asyncMethods; private Set<MethodReference> asyncMethods;
private RememberingSourceWriter writer; private SourceWriter writer;
private StatementRenderer statementRenderer; private StatementRenderer statementRenderer;
private boolean threadLibraryUsed; private boolean threadLibraryUsed;
private RememberedSource body;
private RememberedSource parameters;
private PreparedVariable[] variables;
public MethodBodyRenderer(RenderingContext context, Diagnostics diagnostics, boolean minifying, boolean debug, public MethodBodyRenderer(RenderingContext context, Diagnostics diagnostics, boolean minifying,
Set<MethodReference> asyncMethods) { Set<MethodReference> asyncMethods, SourceWriter writer) {
this.context = context; this.context = context;
this.diagnostics = diagnostics; this.diagnostics = diagnostics;
this.minifying = minifying; this.minifying = minifying;
this.asyncMethods = asyncMethods; this.asyncMethods = asyncMethods;
writer = new RememberingSourceWriter(debug); this.writer = writer;
statementRenderer = new StatementRenderer(context, writer); statementRenderer = new StatementRenderer(context, writer);
} }
@ -71,15 +65,11 @@ public class MethodBodyRenderer implements MethodNodeVisitor, GeneratorContext {
return context.getDependencyInfo(); return context.getDependencyInfo();
} }
public void renderNative(Generator generator, boolean async, MethodReference reference, public void renderNative(Generator generator, boolean async, MethodReference reference) {
Set<ElementModifier> modifiers) {
threadLibraryUsed = false; threadLibraryUsed = false;
this.async = async; this.async = async;
statementRenderer.setAsync(async); statementRenderer.setAsync(async);
renderParameters(reference, modifiers);
generator.generate(this, writer, reference); generator.generate(this, writer, reference);
body = writer.save();
writer.clear();
} }
public void render(MethodNode node, boolean async) { public void render(MethodNode node, boolean async) {
@ -87,41 +77,18 @@ public class MethodBodyRenderer implements MethodNodeVisitor, GeneratorContext {
this.async = async; this.async = async;
statementRenderer.setAsync(async); statementRenderer.setAsync(async);
statementRenderer.setCurrentMethod(node); statementRenderer.setCurrentMethod(node);
renderParameters(node.getReference(), node.getModifiers());
node.acceptVisitor(this);
body = writer.save();
prepareVariables(node); prepareVariables(node);
writer.clear(); node.acceptVisitor(this);
}
public PreparedVariable[] getVariables() {
return variables;
} }
private void prepareVariables(MethodNode method) { private void prepareVariables(MethodNode method) {
var variables = new ArrayList<PreparedVariable>();
for (int i = 0; i < method.getVariables().size(); ++i) { for (int i = 0; i < method.getVariables().size(); ++i) {
variables.add(new PreparedVariable(new String[] { method.getVariables().get(i).getName() }, writer.emitVariables(new String[] { method.getVariables().get(i).getName() },
statementRenderer.variableName(i))); statementRenderer.variableName(i));
} }
this.variables = variables.toArray(new PreparedVariable[0]);
} }
public RememberedSource getBody() { public void renderParameters(MethodReference reference, Set<ElementModifier> modifiers) {
return body;
}
public RememberedSource getParameters() {
return parameters;
}
public void clear() {
body = null;
parameters = null;
variables = null;
}
private void renderParameters(MethodReference reference, Set<ElementModifier> modifiers) {
int startParam = 0; int startParam = 0;
if (modifiers.contains(ElementModifier.STATIC)) { if (modifiers.contains(ElementModifier.STATIC)) {
startParam = 1; startParam = 1;
@ -139,8 +106,6 @@ public class MethodBodyRenderer implements MethodNodeVisitor, GeneratorContext {
if (count != 1) { if (count != 1) {
writer.append(")"); writer.append(")");
} }
parameters = writer.save();
writer.clear();
} }
private void appendMonitor(StatementRenderer statementRenderer, MethodNode methodNode) { private void appendMonitor(StatementRenderer statementRenderer, MethodNode methodNode) {

View File

@ -15,22 +15,18 @@
*/ */
package org.teavm.backend.javascript.rendering; 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 java.util.Set;
import org.teavm.backend.javascript.codegen.NameFrequencyConsumer; import org.teavm.backend.javascript.codegen.NamingStrategy;
import org.teavm.backend.javascript.codegen.RememberedSource;
import org.teavm.backend.javascript.codegen.SourceWriterSink; 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.FieldReference;
import org.teavm.model.MethodDescriptor; import org.teavm.model.MethodDescriptor;
import org.teavm.model.MethodReader;
import org.teavm.model.MethodReference; 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, static final MethodReference MONITOR_ENTER_METHOD = new MethodReference(Object.class,
"monitorEnter", Object.class, void.class); "monitorEnter", Object.class, void.class);
static final MethodReference MONITOR_ENTER_SYNC_METHOD = new MethodReference(Object.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); "monitorExit", Object.class, void.class);
static final MethodReference MONITOR_EXIT_SYNC_METHOD = new MethodReference(Object.class, static final MethodReference MONITOR_EXIT_SYNC_METHOD = new MethodReference(Object.class,
"monitorExitSync", Object.class, void.class); "monitorExitSync", Object.class, void.class);
private static final MethodDescriptor CLINIT_METHOD = new MethodDescriptor("<clinit>", ValueType.VOID);
private final NameFrequencyConsumer consumer; private Map<String, Entry> entries = new HashMap<>();
private final ClassReaderSource classSource; private Set<String> reservedNames = new HashSet<>();
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");
}
}
@Override @Override
public SourceWriterSink appendClass(String cls) { 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; return this;
} }
@Override @Override
public SourceWriterSink appendField(FieldReference field) { 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; return this;
} }
@Override @Override
public SourceWriterSink appendStaticField(FieldReference field) { 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; return this;
} }
@Override @Override
public SourceWriterSink appendMethod(MethodDescriptor method) { 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; return this;
} }
@Override @Override
public SourceWriterSink appendMethodBody(MethodReference method) { 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; return this;
} }
@Override @Override
public SourceWriterSink appendFunction(String name) { 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; return this;
} }
@Override @Override
public SourceWriterSink appendGlobal(String name) { public SourceWriterSink appendGlobal(String name) {
consumer.consumeGlobal(name); reservedNames.add(name);
return this; return this;
} }
@Override @Override
public SourceWriterSink appendInit(MethodReference method) { 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; return this;
} }
@Override @Override
public SourceWriterSink appendClassInit(String className) { 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; return this;
} }
public void apply(NamingStrategy naming) {
for (var name : reservedNames) {
naming.reserveName(name);
}
var entryList = new ArrayList<>(entries.values());
entryList.sort((o1, o2) -> Integer.compare(o2.frequency, o1.frequency));
for (var entry : entryList) {
entry.operation.perform(naming);
}
}
private static class Entry {
NamingOperation operation;
int frequency;
}
private interface NamingOperation {
void perform(NamingStrategy naming);
}
} }

View File

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

View File

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

View File

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