From 5ec4450bf8ae04b93e079514fdc40dfb09f3e6a4 Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Fri, 3 Nov 2023 21:03:21 +0100 Subject: [PATCH] JS: render all declarations into one remembered fragment, then output this fragment to real output --- .../backend/javascript/JavaScriptTarget.java | 354 ++--------- .../codegen/NameFrequencyConsumer.java | 40 -- .../javascript/codegen/NamingOrderer.java | 148 ----- .../codegen/OutputSourceWriter.java | 73 ++- .../javascript/codegen/RememberedSource.java | 43 +- .../codegen/RememberingSourceWriter.java | 45 ++ .../javascript/codegen/SourceWriter.java | 15 + .../javascript/codegen/SourceWriterSink.java | 16 + .../javascript/decompile/PreparedClass.java | 45 -- .../javascript/decompile/PreparedMethod.java | 47 -- .../decompile/PreparedVariable.java | 32 - .../rendering/MethodBodyRenderer.java | 53 +- .../rendering/NameFrequencyEstimator.java | 187 +++--- .../javascript/rendering/Renderer.java | 553 +++++++++++------- .../rendering/RenderingManager.java | 4 - .../src/main/resources/test-server/client.js | 4 +- 16 files changed, 679 insertions(+), 980 deletions(-) delete mode 100644 core/src/main/java/org/teavm/backend/javascript/codegen/NameFrequencyConsumer.java delete mode 100644 core/src/main/java/org/teavm/backend/javascript/codegen/NamingOrderer.java delete mode 100644 core/src/main/java/org/teavm/backend/javascript/decompile/PreparedClass.java delete mode 100644 core/src/main/java/org/teavm/backend/javascript/decompile/PreparedMethod.java delete mode 100644 core/src/main/java/org/teavm/backend/javascript/decompile/PreparedVariable.java diff --git a/core/src/main/java/org/teavm/backend/javascript/JavaScriptTarget.java b/core/src/main/java/org/teavm/backend/javascript/JavaScriptTarget.java index eae64fe82..e78701332 100644 --- a/core/src/main/java/org/teavm/backend/javascript/JavaScriptTarget.java +++ b/core/src/main/java/org/teavm/backend/javascript/JavaScriptTarget.java @@ -16,7 +16,6 @@ package org.teavm.backend.javascript; import com.carrotsearch.hppc.ObjectIntHashMap; -import com.carrotsearch.hppc.ObjectIntMap; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; @@ -24,7 +23,6 @@ import java.io.Writer; import java.lang.ref.Reference; import java.lang.ref.ReferenceQueue; import java.lang.ref.WeakReference; -import java.lang.reflect.InvocationTargetException; import java.nio.charset.StandardCharsets; import java.text.DecimalFormat; import java.text.NumberFormat; @@ -37,30 +35,24 @@ import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.Properties; import java.util.Set; import java.util.function.Function; -import org.teavm.ast.AsyncMethodNode; import org.teavm.ast.ControlFlowEntry; -import org.teavm.ast.MethodNode; -import org.teavm.ast.RegularMethodNode; -import org.teavm.ast.analysis.LocationGraphBuilder; -import org.teavm.ast.decompilation.DecompilationException; -import org.teavm.ast.decompilation.Decompiler; import org.teavm.backend.javascript.codegen.AliasProvider; import org.teavm.backend.javascript.codegen.DefaultAliasProvider; import org.teavm.backend.javascript.codegen.DefaultNamingStrategy; import org.teavm.backend.javascript.codegen.MinifyingAliasProvider; +import org.teavm.backend.javascript.codegen.OutputSourceWriter; import org.teavm.backend.javascript.codegen.OutputSourceWriterBuilder; +import org.teavm.backend.javascript.codegen.RememberedSource; +import org.teavm.backend.javascript.codegen.RememberingSourceWriter; import org.teavm.backend.javascript.codegen.SourceWriter; -import org.teavm.backend.javascript.decompile.PreparedClass; -import org.teavm.backend.javascript.decompile.PreparedMethod; import org.teavm.backend.javascript.intrinsics.ref.ReferenceQueueGenerator; import org.teavm.backend.javascript.intrinsics.ref.ReferenceQueueTransformer; import org.teavm.backend.javascript.intrinsics.ref.WeakReferenceDependencyListener; import org.teavm.backend.javascript.intrinsics.ref.WeakReferenceGenerator; import org.teavm.backend.javascript.intrinsics.ref.WeakReferenceTransformer; -import org.teavm.backend.javascript.rendering.MethodBodyRenderer; +import org.teavm.backend.javascript.rendering.NameFrequencyEstimator; import org.teavm.backend.javascript.rendering.Renderer; import org.teavm.backend.javascript.rendering.RenderingContext; import org.teavm.backend.javascript.rendering.RenderingUtil; @@ -72,29 +64,21 @@ import org.teavm.backend.javascript.spi.Injector; import org.teavm.backend.javascript.spi.VirtualMethodContributor; import org.teavm.backend.javascript.spi.VirtualMethodContributorContext; import org.teavm.backend.javascript.templating.JavaScriptTemplateFactory; -import org.teavm.cache.AstCacheEntry; -import org.teavm.cache.AstDependencyExtractor; -import org.teavm.cache.CacheStatus; import org.teavm.cache.EmptyMethodNodeCache; import org.teavm.cache.MethodNodeCache; -import org.teavm.common.ServiceRepository; import org.teavm.debugging.information.DebugInformationEmitter; import org.teavm.debugging.information.DummyDebugInformationEmitter; import org.teavm.debugging.information.SourceLocation; import org.teavm.dependency.AbstractDependencyListener; import org.teavm.dependency.DependencyAgent; import org.teavm.dependency.DependencyAnalyzer; -import org.teavm.dependency.DependencyInfo; import org.teavm.dependency.DependencyListener; import org.teavm.dependency.DependencyType; import org.teavm.dependency.MethodDependency; import org.teavm.interop.PlatformMarker; import org.teavm.interop.Platforms; -import org.teavm.model.AnnotationHolder; import org.teavm.model.BasicBlock; import org.teavm.model.CallLocation; -import org.teavm.model.ClassHolder; -import org.teavm.model.ClassHolderSource; import org.teavm.model.ClassHolderTransformer; import org.teavm.model.ClassReaderSource; import org.teavm.model.ElementModifier; @@ -115,8 +99,6 @@ import org.teavm.model.instructions.StringConstantInstruction; import org.teavm.model.transformation.BoundCheckInsertion; import org.teavm.model.transformation.NullCheckFilter; import org.teavm.model.transformation.NullCheckInsertion; -import org.teavm.model.util.AsyncMethodFinder; -import org.teavm.model.util.ProgramUtils; import org.teavm.vm.BuildTarget; import org.teavm.vm.RenderingException; import org.teavm.vm.TeaVMTarget; @@ -141,18 +123,13 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost { private DebugInformationEmitter debugEmitter; private MethodNodeCache astCache = EmptyMethodNodeCache.INSTANCE; private final Set asyncMethods = new HashSet<>(); - private final Set asyncFamilyMethods = new HashSet<>(); private List customVirtualMethods = new ArrayList<>(); private int topLevelNameLimit = 500000; - private AstDependencyExtractor dependencyExtractor = new AstDependencyExtractor(); private boolean strict; private BoundCheckInsertion boundCheckInsertion = new BoundCheckInsertion(); private NullCheckInsertion nullCheckInsertion = new NullCheckInsertion(NullCheckFilter.EMPTY); private final Map importedModules = new LinkedHashMap<>(); - private Map generatorCache = new HashMap<>(); private JavaScriptTemplateFactory templateFactory; - private boolean threadLibraryUsed; - private MethodBodyRenderer bodyRenderer; @Override public List getTransformers() { @@ -422,71 +399,47 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost { }; renderingContext.setMinifying(obfuscated); - bodyRenderer = new MethodBodyRenderer(renderingContext, controller.getDiagnostics(), obfuscated, - debugEmitter != null, asyncMethods); - - var clsNodes = modelToAst(classes, renderingContext); if (controller.wasCancelled()) { return; } var builder = new OutputSourceWriterBuilder(naming); builder.setMinified(obfuscated); - var sourceWriter = builder.build(writer); - sourceWriter.setDebugInformationEmitter(debugEmitterToUse); - - var renderer = new Renderer(sourceWriter, asyncMethods, asyncFamilyMethods, renderingContext); - RuntimeRenderer runtimeRenderer = new RuntimeRenderer(classes, sourceWriter); - 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; - } + for (var className : classes.getClassNames()) { + var cls = classes.get(className); + for (var method : cls.getMethods()) { + preprocessNativeMethod(method); } - 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); } - int start = sourceWriter.getOffset(); - - runtimeRenderer.renderRuntime(); - sourceWriter.append("var ").append(renderer.getNaming().getScopeName()).ws().append("=").ws() - .append("Object.create(null);").newLine(); - if (!renderer.render(clsNodes)) { + if (!renderer.render(classes, controller.isFriendlyToDebugger())) { return; } - runtimeRenderer.renderHandWrittenRuntime("array.js"); + var declarations = rememberingWriter.save(); + rememberingWriter.clear(); + renderer.renderStringPool(); renderer.renderStringConstants(); renderer.renderCompatibilityStubs(); - - runtimeRenderer.renderHandWrittenRuntime("long.js"); - if (threadLibraryUsed) { - runtimeRenderer.renderHandWrittenRuntime("thread.js"); - } else { - runtimeRenderer.renderHandWrittenRuntime("simpleThread.js"); - } - for (var entry : controller.getEntryPoints().entrySet()) { - sourceWriter.appendFunction("$rt_exports").append(".").append(entry.getKey()).ws().append("=").ws(); + rememberingWriter.appendFunction("$rt_exports").append(".").append(entry.getKey()).ws().append("=").ws(); var ref = entry.getValue().getMethod(); - sourceWriter.appendFunction("$rt_mainStarter").append("(").appendMethodBody(ref); - sourceWriter.append(");").newLine(); - sourceWriter.appendFunction("$rt_exports").append(".").append(entry.getKey()).append(".") + rememberingWriter.appendFunction("$rt_mainStarter").append("(").appendMethodBody(ref); + rememberingWriter.append(");").newLine(); + rememberingWriter.appendFunction("$rt_exports").append(".").append(entry.getKey()).append(".") .append("javaException").ws().append("=").ws().appendFunction("$rt_javaException") .append(";").newLine(); } @@ -494,11 +447,36 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost { for (var listener : rendererListeners) { listener.complete(); } + var epilogue = rememberingWriter.save(); + rememberingWriter.clear(); + + var frequencyEstimator = new NameFrequencyEstimator(); + declarations.replay(frequencyEstimator, RememberedSource.FILTER_REF); + epilogue.replay(frequencyEstimator, RememberedSource.FILTER_REF); + frequencyEstimator.apply(naming); + + var sourceWriter = builder.build(writer); + sourceWriter.setDebugInformationEmitter(debugEmitterToUse); + printWrapperStart(sourceWriter); + + int start = sourceWriter.getOffset(); + + RuntimeRenderer runtimeRenderer = new RuntimeRenderer(classes, sourceWriter); + runtimeRenderer.renderRuntime(); + runtimeRenderer.renderHandWrittenRuntime("long.js"); + if (renderer.isThreadLibraryUsed()) { + runtimeRenderer.renderHandWrittenRuntime("thread.js"); + } else { + runtimeRenderer.renderHandWrittenRuntime("simpleThread.js"); + } + declarations.write(sourceWriter, 0); + runtimeRenderer.renderHandWrittenRuntime("array.js"); + epilogue.write(sourceWriter, 0); printWrapperEnd(sourceWriter); int totalSize = sourceWriter.getOffset() - start; - printStats(renderer, totalSize); + printStats(sourceWriter, totalSize); } private void printWrapperStart(SourceWriter writer) { @@ -560,19 +538,21 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost { writer.outdent().append("}));").newLine(); } - private void printStats(Renderer renderer, int totalSize) { + private void printStats(OutputSourceWriter writer, int totalSize) { if (!Boolean.parseBoolean(System.getProperty("teavm.js.stats", "false"))) { return; } System.out.println("Total output size: " + STATS_NUM_FORMAT.format(totalSize)); - System.out.println("Metadata size: " + getSizeWithPercentage(renderer.getMetadataSize(), totalSize)); - System.out.println("String pool size: " + getSizeWithPercentage(renderer.getStringPoolSize(), totalSize)); + System.out.println("Metadata size: " + getSizeWithPercentage( + writer.getSectionSize(Renderer.SECTION_METADATA), totalSize)); + System.out.println("String pool size: " + getSizeWithPercentage( + writer.getSectionSize(Renderer.SECTION_STRING_POOL), totalSize)); - ObjectIntMap packageSizeMap = new ObjectIntHashMap<>(); - for (String className : renderer.getClassesInStats()) { + var packageSizeMap = new ObjectIntHashMap(); + for (String className : writer.getClassesInStats()) { String packageName = className.substring(0, className.lastIndexOf('.') + 1); - int classSize = renderer.getClassSize(className); + int classSize = writer.getClassSize(className); packageSizeMap.put(packageName, packageSizeMap.getOrDefault(packageName, 0) + classSize); } @@ -588,222 +568,6 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost { return STATS_NUM_FORMAT.format(size) + " (" + STATS_PERCENT_FORMAT.format((double) size / totalSize) + ")"; } - private List 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 splitMethods = new HashSet<>(asyncMethods); - splitMethods.addAll(asyncFamilyMethods); - - Decompiler decompiler = new Decompiler(classes, splitMethods, controller.isFriendlyToDebugger()); - - List 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 getClassOrdering(ListableClassHolderSource classes) { - List sequence = new ArrayList<>(); - Set visited = new HashSet<>(); - for (String className : classes.getClassNames()) { - orderClasses(classes, className, visited, sequence); - } - return sequence; - } - - private void orderClasses(ClassHolderSource classes, String className, Set visited, List order) { - if (!visited.add(className)) { - return; - } - ClassHolder cls = classes.get(className); - if (cls == null) { - return; - } - if (cls.getParent() != null) { - orderClasses(classes, cls.getParent(), visited, order); - } - for (String iface : cls.getInterfaces()) { - orderClasses(classes, iface, visited, order); - } - order.add(className); - } - - private PreparedClass prepare(Decompiler decompiler, ClassHolder cls, ClassReaderSource classes) { - PreparedClass clsNode = new PreparedClass(cls); - for (MethodHolder method : cls.getMethods()) { - if (method.getModifiers().contains(ElementModifier.ABSTRACT)) { - continue; - } - if ((!isBootstrap() && method.getAnnotations().get(InjectedBy.class.getName()) != null) - || methodInjectors.containsKey(method.getReference())) { - continue; - } - if (!method.hasModifier(ElementModifier.NATIVE) && !method.hasProgram()) { - continue; - } - - PreparedMethod preparedMethod = method.hasModifier(ElementModifier.NATIVE) - ? prepareNative(method, classes) - : prepare(decompiler, method); - clsNode.getMethods().add(preparedMethod); - } - return clsNode; - } - - private PreparedMethod prepareNative(MethodHolder method, ClassReaderSource classes) { - MethodReference reference = method.getReference(); - Generator generator = methodGenerators.get(reference); - if (generator == null && !isBootstrap()) { - AnnotationHolder annotHolder = method.getAnnotations().get(GeneratedBy.class.getName()); - if (annotHolder == null) { - throw new DecompilationException("Method " + method.getOwnerName() + "." + method.getDescriptor() - + " is native, but no " + GeneratedBy.class.getName() + " annotation found"); - } - ValueType annotValue = annotHolder.getValues().get("value").getJavaClass(); - String generatorClassName = ((ValueType.Object) annotValue).getClassName(); - generator = generatorCache.computeIfAbsent(generatorClassName, - name -> createGenerator(name, method, classes)); - } - - var async = asyncMethods.contains(reference); - bodyRenderer.renderNative(generator, async, reference, method.getModifiers()); - threadLibraryUsed |= bodyRenderer.isThreadLibraryUsed(); - var result = new PreparedMethod(method.getLevel(), method.getModifiers(), method.getReference(), - bodyRenderer.getBody(), bodyRenderer.getParameters(), async, null, null); - bodyRenderer.clear(); - return result; - } - - private Generator createGenerator(String name, MethodHolder method, ClassReaderSource classes) { - Class generatorClass; - try { - generatorClass = Class.forName(name, true, controller.getClassLoader()); - } catch (ClassNotFoundException e) { - throw new DecompilationException("Error instantiating generator " + name - + " for native method " + method.getOwnerName() + "." + method.getDescriptor()); - } - - var constructors = generatorClass.getConstructors(); - if (constructors.length != 1) { - throw new DecompilationException("Error instantiating generator " + name - + " for native method " + method.getOwnerName() + "." + method.getDescriptor()); - } - - var constructor = constructors[0]; - var parameterTypes = constructor.getParameterTypes(); - var arguments = new Object[parameterTypes.length]; - for (var i = 0; i < arguments.length; ++i) { - var parameterType = parameterTypes[i]; - if (parameterType.equals(ClassReaderSource.class)) { - arguments[i] = classes; - } else if (parameterType.equals(Properties.class)) { - arguments[i] = controller.getProperties(); - } else if (parameterType.equals(DependencyInfo.class)) { - arguments[i] = controller.getDependencyInfo(); - } else if (parameterType.equals(ServiceRepository.class)) { - arguments[i] = controller.getServices(); - } else if (parameterType.equals(JavaScriptTemplateFactory.class)) { - arguments[i] = templateFactory; - } else { - var service = controller.getServices().getService(parameterType); - if (service == null) { - throw new DecompilationException("Error instantiating generator " + name - + " for native method " + method.getOwnerName() + "." + method.getDescriptor() + ". " - + "Its constructor requires " + parameterType + " as its parameter #" + (i + 1) - + " which is not available."); - } - } - } - - try { - return (Generator) constructor.newInstance(arguments); - } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { - throw new DecompilationException("Error instantiating generator " + name - + " for native method " + method.getOwnerName() + "." + method.getDescriptor(), e); - } - } - - private PreparedMethod prepare(Decompiler decompiler, MethodHolder method) { - MethodReference reference = method.getReference(); - ControlFlowEntry[] cfg; - MethodNode node; - var async = asyncMethods.contains(reference); - if (async) { - node = decompileAsync(decompiler, method); - cfg = ProgramUtils.getLocationCFG(method.getProgram()); - } else { - var entry = decompileRegular(decompiler, method); - node = entry.method; - cfg = entry.cfg; - } - bodyRenderer.render(node, async); - var result = new PreparedMethod(method.getLevel(), method.getModifiers(), method.getReference(), - bodyRenderer.getBody(), bodyRenderer.getParameters(), async, cfg, bodyRenderer.getVariables()); - threadLibraryUsed |= bodyRenderer.isThreadLibraryUsed(); - bodyRenderer.clear(); - return result; - } - - private AstCacheEntry decompileRegular(Decompiler decompiler, MethodHolder method) { - if (astCache == null) { - return decompileRegularCacheMiss(decompiler, method); - } - - CacheStatus cacheStatus = controller.getCacheStatus(); - AstCacheEntry entry = !cacheStatus.isStaleMethod(method.getReference()) - ? astCache.get(method.getReference(), cacheStatus) - : null; - if (entry == null) { - entry = decompileRegularCacheMiss(decompiler, method); - RegularMethodNode finalNode = entry.method; - astCache.store(method.getReference(), entry, () -> dependencyExtractor.extract(finalNode)); - } - return entry; - } - - private AstCacheEntry decompileRegularCacheMiss(Decompiler decompiler, MethodHolder method) { - RegularMethodNode node = decompiler.decompileRegular(method); - ControlFlowEntry[] cfg = LocationGraphBuilder.build(node.getBody()); - return new AstCacheEntry(node, cfg); - } - - private AsyncMethodNode decompileAsync(Decompiler decompiler, MethodHolder method) { - if (astCache == null) { - return decompiler.decompileAsync(method); - } - - CacheStatus cacheStatus = controller.getCacheStatus(); - AsyncMethodNode node = !cacheStatus.isStaleMethod(method.getReference()) - ? astCache.getAsync(method.getReference(), cacheStatus) - : null; - if (node == null) { - node = decompiler.decompileAsync(method); - AsyncMethodNode finalNode = node; - astCache.storeAsync(method.getReference(), node, () -> dependencyExtractor.extract(finalNode)); - } - return node; - } - private void preprocessNativeMethod(MethodHolder method) { if (!method.getModifiers().contains(ElementModifier.NATIVE) || methodGenerators.get(method.getReference()) != null diff --git a/core/src/main/java/org/teavm/backend/javascript/codegen/NameFrequencyConsumer.java b/core/src/main/java/org/teavm/backend/javascript/codegen/NameFrequencyConsumer.java deleted file mode 100644 index 2c224cf4d..000000000 --- a/core/src/main/java/org/teavm/backend/javascript/codegen/NameFrequencyConsumer.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2016 Alexey Andreev. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.teavm.backend.javascript.codegen; - -import org.teavm.model.FieldReference; -import org.teavm.model.MethodDescriptor; -import org.teavm.model.MethodReference; - -public interface NameFrequencyConsumer { - void consume(MethodReference method); - - void consumeInit(MethodReference method); - - void consume(MethodDescriptor method); - - void consume(String className); - - void consumeClassInit(String className); - - void consume(FieldReference field); - - void consumeStatic(FieldReference field); - - void consumeFunction(String name); - - void consumeGlobal(String name); -} diff --git a/core/src/main/java/org/teavm/backend/javascript/codegen/NamingOrderer.java b/core/src/main/java/org/teavm/backend/javascript/codegen/NamingOrderer.java deleted file mode 100644 index 2166e60e2..000000000 --- a/core/src/main/java/org/teavm/backend/javascript/codegen/NamingOrderer.java +++ /dev/null @@ -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 entries = new HashMap<>(); - private Set 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 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); - } -} diff --git a/core/src/main/java/org/teavm/backend/javascript/codegen/OutputSourceWriter.java b/core/src/main/java/org/teavm/backend/javascript/codegen/OutputSourceWriter.java index 03e256e56..0c4ea8623 100644 --- a/core/src/main/java/org/teavm/backend/javascript/codegen/OutputSourceWriter.java +++ b/core/src/main/java/org/teavm/backend/javascript/codegen/OutputSourceWriter.java @@ -15,7 +15,13 @@ */ package org.teavm.backend.javascript.codegen; +import com.carrotsearch.hppc.IntIntHashMap; +import com.carrotsearch.hppc.IntIntMap; +import com.carrotsearch.hppc.ObjectIntHashMap; +import com.carrotsearch.hppc.ObjectIntMap; import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; import org.teavm.debugging.information.DebugInformationEmitter; import org.teavm.debugging.information.DummyDebugInformationEmitter; import org.teavm.model.FieldReference; @@ -33,6 +39,12 @@ public class OutputSourceWriter extends SourceWriter implements LocationProvider private int line; private int offset; private DebugInformationEmitter debugInformationEmitter = new DummyDebugInformationEmitter(); + private String classMarkClass; + private int classMarkPos; + private ObjectIntMap classSizes = new ObjectIntHashMap<>(); + private int sectionMarkSection = -1; + private int sectionMarkPos; + private IntIntMap sectionSizes = new IntIntHashMap(); OutputSourceWriter(NamingStrategy naming, Appendable innerWriter, int lineWidth) { this.naming = naming; @@ -140,9 +152,6 @@ public class OutputSourceWriter extends SourceWriter implements LocationProvider } private SourceWriter appendName(ScopedName name) { - if (name.scoped) { - append(naming.getScopeName()).append("."); - } append(name.value); return this; } @@ -271,6 +280,12 @@ public class OutputSourceWriter extends SourceWriter implements LocationProvider return this; } + @Override + public SourceWriter emitVariables(String[] names, String jsName) { + debugInformationEmitter.emitVariable(names, jsName); + return this; + } + @Override public void emitMethod(MethodDescriptor method) { debugInformationEmitter.emitMethod(method); @@ -295,4 +310,56 @@ public class OutputSourceWriter extends SourceWriter implements LocationProvider public int getOffset() { return offset; } + + @Override + public void markClassStart(String className) { + classMarkClass = className; + classMarkPos = offset; + } + + @Override + public void markClassEnd() { + if (classMarkClass != null) { + var size = offset - classMarkPos; + if (size > 0) { + var currentSize = classSizes.get(classMarkClass); + classSizes.put(classMarkClass, currentSize + size); + } + classMarkClass = null; + } + } + + @Override + public void markSectionStart(int id) { + sectionMarkSection = id; + sectionMarkPos = offset; + } + + @Override + public void markSectionEnd() { + if (sectionMarkSection >= 0) { + int size = offset - sectionMarkPos; + if (size > 0) { + var currentSize = sectionSizes.get(sectionMarkSection); + sectionSizes.put(sectionMarkSection, currentSize + size); + } + sectionMarkSection = -1; + } + } + + public Collection getClassesInStats() { + var result = new ArrayList(); + 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); + } } diff --git a/core/src/main/java/org/teavm/backend/javascript/codegen/RememberedSource.java b/core/src/main/java/org/teavm/backend/javascript/codegen/RememberedSource.java index 38afa7e70..57bcf0ff3 100644 --- a/core/src/main/java/org/teavm/backend/javascript/codegen/RememberedSource.java +++ b/core/src/main/java/org/teavm/backend/javascript/codegen/RememberedSource.java @@ -24,7 +24,8 @@ public class RememberedSource implements SourceFragment { public static final int FILTER_TEXT = 1; public static final int FILTER_REF = 2; public static final int FILTER_DEBUG = 4; - public static final int FILTER_ALL = FILTER_TEXT | FILTER_REF | FILTER_DEBUG; + public static final int FILTER_STATS = 8; + public static final int FILTER_ALL = FILTER_TEXT | FILTER_REF | FILTER_DEBUG | FILTER_STATS; private byte[] commands; private String chars; @@ -196,6 +197,20 @@ public class RememberedSource implements SourceFragment { } break; + case RememberingSourceWriter.EMIT_VARIABLES: + var count = intArgs[intArgIndex++]; + if ((filter & FILTER_DEBUG) != 0) { + var names = new String[count]; + for (var i = 0; i < count; ++i) { + names[i] = strings[intArgs[intArgIndex++]]; + } + var jsName = strings[intArgs[intArgIndex++]]; + sink.emitVariables(names, jsName); + } else { + intArgIndex += count + 1; + } + break; + case RememberingSourceWriter.EMIT_CLASS: if ((filter & FILTER_DEBUG) != 0) { var classIndex = intArgs[intArgIndex]; @@ -211,6 +226,32 @@ public class RememberedSource implements SourceFragment { } intArgIndex++; break; + + case RememberingSourceWriter.MARK_CLASS_START: + if ((filter & FILTER_STATS) != 0) { + sink.markClassStart(strings[intArgs[intArgIndex]]); + } + intArgIndex++; + break; + + case RememberingSourceWriter.MARK_CLASS_END: + if ((filter & FILTER_STATS) != 0) { + sink.markClassEnd(); + } + break; + + case RememberingSourceWriter.MARK_SECTION_START: + if ((filter & FILTER_STATS) != 0) { + sink.markSectionStart(intArgs[intArgIndex]); + } + intArgIndex++; + break; + + case RememberingSourceWriter.MARK_SECTION_END: + if ((filter & FILTER_STATS) != 0) { + sink.markSectionEnd(); + } + break; } } } diff --git a/core/src/main/java/org/teavm/backend/javascript/codegen/RememberingSourceWriter.java b/core/src/main/java/org/teavm/backend/javascript/codegen/RememberingSourceWriter.java index 29e67c704..dd081e68f 100644 --- a/core/src/main/java/org/teavm/backend/javascript/codegen/RememberingSourceWriter.java +++ b/core/src/main/java/org/teavm/backend/javascript/codegen/RememberingSourceWriter.java @@ -46,8 +46,13 @@ public class RememberingSourceWriter extends SourceWriter { static final byte ENTER_LOCATION = 16; static final byte EXIT_LOCATION = 17; static final byte EMIT_STATEMENT_START = 18; + static final byte EMIT_VARIABLES = 26; static final byte EMIT_METHOD = 19; static final byte EMIT_CLASS = 20; + static final byte MARK_CLASS_START = 22; + static final byte MARK_CLASS_END = 23; + static final byte MARK_SECTION_START = 24; + static final byte MARK_SECTION_END = 25; private boolean debug; @@ -262,6 +267,20 @@ public class RememberingSourceWriter extends SourceWriter { return this; } + @Override + public SourceWriter emitVariables(String[] names, String jsName) { + if (debug) { + flush(); + commands.add(EMIT_VARIABLES); + intArgs.add(names.length); + for (var name : names) { + appendStringArg(name); + } + appendStringArg(jsName); + } + return this; + } + @Override public void emitMethod(MethodDescriptor method) { if (!debug) { @@ -290,6 +309,32 @@ public class RememberingSourceWriter extends SourceWriter { } } + @Override + public void markClassStart(String className) { + flush(); + commands.add(MARK_CLASS_START); + appendStringArg(className); + } + + @Override + public void markClassEnd() { + flush(); + commands.add(MARK_CLASS_END); + } + + @Override + public void markSectionStart(int id) { + flush(); + commands.add(MARK_SECTION_START); + intArgs.add(id); + } + + @Override + public void markSectionEnd() { + flush(); + commands.add(MARK_SECTION_END); + } + public void flush() { if (lastWrittenChar == sb.length()) { return; diff --git a/core/src/main/java/org/teavm/backend/javascript/codegen/SourceWriter.java b/core/src/main/java/org/teavm/backend/javascript/codegen/SourceWriter.java index c18d55a05..d1f1b5068 100644 --- a/core/src/main/java/org/teavm/backend/javascript/codegen/SourceWriter.java +++ b/core/src/main/java/org/teavm/backend/javascript/codegen/SourceWriter.java @@ -138,9 +138,24 @@ public abstract class SourceWriter implements Appendable, SourceWriterSink { @Override public abstract SourceWriter emitStatementStart(); + @Override + public abstract SourceWriter emitVariables(String[] names, String jsName); + @Override public abstract void emitMethod(MethodDescriptor method); @Override public abstract void emitClass(String className); + + @Override + public abstract void markClassStart(String className); + + @Override + public abstract void markClassEnd(); + + @Override + public abstract void markSectionStart(int id); + + @Override + public abstract void markSectionEnd(); } diff --git a/core/src/main/java/org/teavm/backend/javascript/codegen/SourceWriterSink.java b/core/src/main/java/org/teavm/backend/javascript/codegen/SourceWriterSink.java index 6ca617597..72356b847 100644 --- a/core/src/main/java/org/teavm/backend/javascript/codegen/SourceWriterSink.java +++ b/core/src/main/java/org/teavm/backend/javascript/codegen/SourceWriterSink.java @@ -104,9 +104,25 @@ public interface SourceWriterSink { return this; } + default SourceWriterSink emitVariables(String[] names, String jsName) { + return this; + } + default void emitMethod(MethodDescriptor method) { } default void emitClass(String className) { } + + default void markClassStart(String className) { + } + + default void markClassEnd() { + } + + default void markSectionStart(int id) { + } + + default void markSectionEnd() { + } } diff --git a/core/src/main/java/org/teavm/backend/javascript/decompile/PreparedClass.java b/core/src/main/java/org/teavm/backend/javascript/decompile/PreparedClass.java deleted file mode 100644 index 52c433ff4..000000000 --- a/core/src/main/java/org/teavm/backend/javascript/decompile/PreparedClass.java +++ /dev/null @@ -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 methods = new ArrayList<>(); - - public PreparedClass(ClassHolder classHolder) { - this.classHolder = classHolder; - } - - public String getName() { - return classHolder.getName(); - } - - public String getParentName() { - return classHolder.getParent(); - } - - public List getMethods() { - return methods; - } - - public ClassHolder getClassHolder() { - return classHolder; - } -} diff --git a/core/src/main/java/org/teavm/backend/javascript/decompile/PreparedMethod.java b/core/src/main/java/org/teavm/backend/javascript/decompile/PreparedMethod.java deleted file mode 100644 index 8c4176c83..000000000 --- a/core/src/main/java/org/teavm/backend/javascript/decompile/PreparedMethod.java +++ /dev/null @@ -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 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 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; - } -} diff --git a/core/src/main/java/org/teavm/backend/javascript/decompile/PreparedVariable.java b/core/src/main/java/org/teavm/backend/javascript/decompile/PreparedVariable.java deleted file mode 100644 index cad1d38e0..000000000 --- a/core/src/main/java/org/teavm/backend/javascript/decompile/PreparedVariable.java +++ /dev/null @@ -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); - } -} diff --git a/core/src/main/java/org/teavm/backend/javascript/rendering/MethodBodyRenderer.java b/core/src/main/java/org/teavm/backend/javascript/rendering/MethodBodyRenderer.java index 58e19383c..6d9b983c9 100644 --- a/core/src/main/java/org/teavm/backend/javascript/rendering/MethodBodyRenderer.java +++ b/core/src/main/java/org/teavm/backend/javascript/rendering/MethodBodyRenderer.java @@ -25,10 +25,7 @@ import org.teavm.ast.MethodNode; import org.teavm.ast.MethodNodeVisitor; import org.teavm.ast.RegularMethodNode; import org.teavm.ast.VariableNode; -import org.teavm.backend.javascript.codegen.RememberedSource; -import org.teavm.backend.javascript.codegen.RememberingSourceWriter; import org.teavm.backend.javascript.codegen.SourceWriter; -import org.teavm.backend.javascript.decompile.PreparedVariable; import org.teavm.backend.javascript.spi.Generator; import org.teavm.backend.javascript.spi.GeneratorContext; import org.teavm.dependency.DependencyInfo; @@ -45,20 +42,17 @@ public class MethodBodyRenderer implements MethodNodeVisitor, GeneratorContext { private boolean minifying; private boolean async; private Set asyncMethods; - private RememberingSourceWriter writer; + private SourceWriter writer; private StatementRenderer statementRenderer; private boolean threadLibraryUsed; - private RememberedSource body; - private RememberedSource parameters; - private PreparedVariable[] variables; - public MethodBodyRenderer(RenderingContext context, Diagnostics diagnostics, boolean minifying, boolean debug, - Set asyncMethods) { + public MethodBodyRenderer(RenderingContext context, Diagnostics diagnostics, boolean minifying, + Set asyncMethods, SourceWriter writer) { this.context = context; this.diagnostics = diagnostics; this.minifying = minifying; this.asyncMethods = asyncMethods; - writer = new RememberingSourceWriter(debug); + this.writer = writer; statementRenderer = new StatementRenderer(context, writer); } @@ -71,15 +65,11 @@ public class MethodBodyRenderer implements MethodNodeVisitor, GeneratorContext { return context.getDependencyInfo(); } - public void renderNative(Generator generator, boolean async, MethodReference reference, - Set modifiers) { + public void renderNative(Generator generator, boolean async, MethodReference reference) { threadLibraryUsed = false; this.async = async; statementRenderer.setAsync(async); - renderParameters(reference, modifiers); generator.generate(this, writer, reference); - body = writer.save(); - writer.clear(); } public void render(MethodNode node, boolean async) { @@ -87,41 +77,18 @@ public class MethodBodyRenderer implements MethodNodeVisitor, GeneratorContext { this.async = async; statementRenderer.setAsync(async); statementRenderer.setCurrentMethod(node); - renderParameters(node.getReference(), node.getModifiers()); - node.acceptVisitor(this); - body = writer.save(); prepareVariables(node); - writer.clear(); - } - - public PreparedVariable[] getVariables() { - return variables; + node.acceptVisitor(this); } private void prepareVariables(MethodNode method) { - var variables = new ArrayList(); for (int i = 0; i < method.getVariables().size(); ++i) { - variables.add(new PreparedVariable(new String[] { method.getVariables().get(i).getName() }, - statementRenderer.variableName(i))); + writer.emitVariables(new String[] { method.getVariables().get(i).getName() }, + statementRenderer.variableName(i)); } - this.variables = variables.toArray(new PreparedVariable[0]); } - public RememberedSource getBody() { - return body; - } - - public RememberedSource getParameters() { - return parameters; - } - - public void clear() { - body = null; - parameters = null; - variables = null; - } - - private void renderParameters(MethodReference reference, Set modifiers) { + public void renderParameters(MethodReference reference, Set modifiers) { int startParam = 0; if (modifiers.contains(ElementModifier.STATIC)) { startParam = 1; @@ -139,8 +106,6 @@ public class MethodBodyRenderer implements MethodNodeVisitor, GeneratorContext { if (count != 1) { writer.append(")"); } - parameters = writer.save(); - writer.clear(); } private void appendMonitor(StatementRenderer statementRenderer, MethodNode methodNode) { diff --git a/core/src/main/java/org/teavm/backend/javascript/rendering/NameFrequencyEstimator.java b/core/src/main/java/org/teavm/backend/javascript/rendering/NameFrequencyEstimator.java index dec8a8015..f10f5c616 100644 --- a/core/src/main/java/org/teavm/backend/javascript/rendering/NameFrequencyEstimator.java +++ b/core/src/main/java/org/teavm/backend/javascript/rendering/NameFrequencyEstimator.java @@ -15,22 +15,18 @@ */ package org.teavm.backend.javascript.rendering; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; import java.util.Set; -import org.teavm.backend.javascript.codegen.NameFrequencyConsumer; -import org.teavm.backend.javascript.codegen.RememberedSource; +import org.teavm.backend.javascript.codegen.NamingStrategy; import org.teavm.backend.javascript.codegen.SourceWriterSink; -import org.teavm.backend.javascript.decompile.PreparedClass; -import org.teavm.backend.javascript.decompile.PreparedMethod; -import org.teavm.model.ClassReaderSource; -import org.teavm.model.ElementModifier; -import org.teavm.model.FieldHolder; import org.teavm.model.FieldReference; import org.teavm.model.MethodDescriptor; -import org.teavm.model.MethodReader; import org.teavm.model.MethodReference; -import org.teavm.model.ValueType; -class NameFrequencyEstimator implements SourceWriterSink { +public class NameFrequencyEstimator implements SourceWriterSink { static final MethodReference MONITOR_ENTER_METHOD = new MethodReference(Object.class, "monitorEnter", Object.class, void.class); static final MethodReference MONITOR_ENTER_SYNC_METHOD = new MethodReference(Object.class, @@ -39,134 +35,137 @@ class NameFrequencyEstimator implements SourceWriterSink { "monitorExit", Object.class, void.class); static final MethodReference MONITOR_EXIT_SYNC_METHOD = new MethodReference(Object.class, "monitorExitSync", Object.class, void.class); - private static final MethodDescriptor CLINIT_METHOD = new MethodDescriptor("", ValueType.VOID); - private final NameFrequencyConsumer consumer; - private final ClassReaderSource classSource; - private final Set asyncFamilyMethods; - - NameFrequencyEstimator(NameFrequencyConsumer consumer, ClassReaderSource classSource, - Set 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(""))) { - consumer.consume(method.reference); - } - if (!method.modifiers.contains(ElementModifier.STATIC)) { - consumer.consume(method.reference.getDescriptor()); - consumer.consume(method.reference); - } - if (method.async) { - consumer.consumeFunction("$rt_nativeThread"); - consumer.consumeFunction("$rt_nativeThread"); - consumer.consumeFunction("$rt_resuming"); - consumer.consumeFunction("$rt_invalidPointer"); - } - - method.body.replay(this, RememberedSource.FILTER_REF); - } - - if (clinit != null) { - consumer.consumeFunction("$rt_eraseClinit"); - } - - // Metadata - consumer.consume(cls.getName()); - consumer.consume(cls.getName()); - if (cls.getParentName() != null) { - consumer.consume(cls.getParentName()); - } - for (String iface : cls.getClassHolder().getInterfaces()) { - consumer.consume(iface); - } - - boolean hasFields = false; - for (FieldHolder field : cls.getClassHolder().getFields()) { - if (!field.hasModifier(ElementModifier.STATIC)) { - hasFields = true; - break; - } - } - if (!hasFields) { - consumer.consumeFunction("$rt_classWithoutFields"); - } - } + private Map entries = new HashMap<>(); + private Set reservedNames = new HashSet<>(); @Override public SourceWriterSink appendClass(String cls) { - consumer.consume(cls); + var key = "c:" + cls; + var entry = entries.get(key); + if (entry == null) { + entry = new Entry(); + entry.operation = naming -> naming.getNameFor(cls); + entries.put(key, entry); + } + entry.frequency++; return this; } @Override public SourceWriterSink appendField(FieldReference field) { - consumer.consume(field); + var key = "f:" + field; + var entry = entries.get(key); + if (entry == null) { + entry = new Entry(); + entry.operation = naming -> naming.getNameFor(field); + entries.put(key, entry); + } + entry.frequency++; return this; } @Override public SourceWriterSink appendStaticField(FieldReference field) { - consumer.consumeStatic(field); + var key = "sf:" + field; + var entry = entries.get(key); + if (entry == null) { + entry = new Entry(); + entry.operation = naming -> naming.getFullNameFor(field); + entries.put(key, entry); + } + entry.frequency++; return this; } @Override public SourceWriterSink appendMethod(MethodDescriptor method) { - consumer.consume(method); + var key = "r:" + method; + var entry = entries.get(key); + if (entry == null) { + entry = new Entry(); + entry.operation = naming -> naming.getNameFor(method); + entries.put(key, entry); + } + entry.frequency++; return this; } @Override public SourceWriterSink appendMethodBody(MethodReference method) { - consumer.consume(method); + var key = "R:" + method; + var entry = entries.get(key); + if (entry == null) { + entry = new Entry(); + entry.operation = naming -> naming.getFullNameFor(method); + entries.put(key, entry); + } + entry.frequency++; return this; } @Override public SourceWriterSink appendFunction(String name) { - consumer.consumeFunction(name); + var key = "n:" + name; + var entry = entries.get(key); + if (entry == null) { + entry = new Entry(); + entry.operation = naming -> naming.getNameForFunction(name); + entries.put(key, entry); + } + entry.frequency++; return this; } @Override public SourceWriterSink appendGlobal(String name) { - consumer.consumeGlobal(name); + reservedNames.add(name); return this; } @Override public SourceWriterSink appendInit(MethodReference method) { - consumer.consumeInit(method); + var key = "I:" + method; + var entry = entries.get(key); + if (entry == null) { + entry = new Entry(); + entry.operation = naming -> naming.getNameForInit(method); + entries.put(key, entry); + } + entry.frequency++; return this; } @Override public SourceWriterSink appendClassInit(String className) { - consumer.consumeClassInit(className); + var key = "C:" + className; + var entry = entries.get(key); + if (entry == null) { + entry = new Entry(); + entry.operation = naming -> naming.getNameForClassInit(className); + entries.put(key, entry); + } + entry.frequency++; return this; } + + public void apply(NamingStrategy naming) { + for (var name : reservedNames) { + naming.reserveName(name); + } + var entryList = new ArrayList<>(entries.values()); + entryList.sort((o1, o2) -> Integer.compare(o2.frequency, o1.frequency)); + for (var entry : entryList) { + entry.operation.perform(naming); + } + } + + private static class Entry { + NamingOperation operation; + int frequency; + } + + private interface NamingOperation { + void perform(NamingStrategy naming); + } } diff --git a/core/src/main/java/org/teavm/backend/javascript/rendering/Renderer.java b/core/src/main/java/org/teavm/backend/javascript/rendering/Renderer.java index a2101b8b2..b6258b169 100644 --- a/core/src/main/java/org/teavm/backend/javascript/rendering/Renderer.java +++ b/core/src/main/java/org/teavm/backend/javascript/rendering/Renderer.java @@ -17,6 +17,7 @@ package org.teavm.backend.javascript.rendering; import com.carrotsearch.hppc.ObjectIntHashMap; import com.carrotsearch.hppc.ObjectIntMap; +import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; @@ -27,77 +28,84 @@ import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.function.IntFunction; -import org.teavm.backend.javascript.codegen.NamingOrderer; -import org.teavm.backend.javascript.codegen.NamingStrategy; -import org.teavm.backend.javascript.codegen.OutputSourceWriter; -import org.teavm.backend.javascript.codegen.RememberedSource; -import org.teavm.backend.javascript.codegen.ScopedName; +import org.teavm.ast.AsyncMethodNode; +import org.teavm.ast.ControlFlowEntry; +import org.teavm.ast.MethodNode; +import org.teavm.ast.RegularMethodNode; +import org.teavm.ast.analysis.LocationGraphBuilder; +import org.teavm.ast.decompilation.DecompilationException; +import org.teavm.ast.decompilation.Decompiler; import org.teavm.backend.javascript.codegen.SourceWriter; -import org.teavm.backend.javascript.decompile.PreparedClass; -import org.teavm.backend.javascript.decompile.PreparedMethod; +import org.teavm.backend.javascript.spi.GeneratedBy; +import org.teavm.backend.javascript.spi.Generator; +import org.teavm.backend.javascript.spi.InjectedBy; +import org.teavm.backend.javascript.templating.JavaScriptTemplateFactory; +import org.teavm.cache.AstCacheEntry; +import org.teavm.cache.AstDependencyExtractor; +import org.teavm.cache.CacheStatus; +import org.teavm.cache.MethodNodeCache; import org.teavm.common.ServiceRepository; -import org.teavm.debugging.information.DebugInformationEmitter; -import org.teavm.debugging.information.DummyDebugInformationEmitter; +import org.teavm.dependency.DependencyInfo; +import org.teavm.diagnostics.Diagnostics; import org.teavm.model.AccessLevel; +import org.teavm.model.AnnotationHolder; +import org.teavm.model.ClassHolder; +import org.teavm.model.ClassHolderSource; import org.teavm.model.ClassReader; +import org.teavm.model.ClassReaderSource; import org.teavm.model.ElementModifier; import org.teavm.model.FieldHolder; import org.teavm.model.FieldReference; +import org.teavm.model.ListableClassHolderSource; import org.teavm.model.ListableClassReaderSource; import org.teavm.model.MethodDescriptor; +import org.teavm.model.MethodHolder; import org.teavm.model.MethodReader; import org.teavm.model.MethodReference; import org.teavm.model.ValueType; import org.teavm.model.analysis.ClassMetadataRequirements; +import org.teavm.model.util.AsyncMethodFinder; import org.teavm.vm.RenderingException; import org.teavm.vm.TeaVMProgressFeedback; public class Renderer implements RenderingManager { - private final NamingStrategy naming; - private final OutputSourceWriter writer; + public static final int SECTION_STRING_POOL = 0; + public static final int SECTION_METADATA = 1; + + private final SourceWriter writer; private final ListableClassReaderSource classSource; private final ClassLoader classLoader; - private boolean minifying; private final Properties properties = new Properties(); private final ServiceRepository services; - private DebugInformationEmitter debugEmitter = new DummyDebugInformationEmitter(); private final Set asyncMethods; - private final Set asyncFamilyMethods; private RenderingContext context; private List postponedFieldInitializers = new ArrayList<>(); private IntFunction progressConsumer = p -> TeaVMProgressFeedback.CONTINUE; + private MethodBodyRenderer methodBodyRenderer; + private Map generatorCache = new HashMap<>(); + private Map 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("", ValueType.VOID); - private ObjectIntMap sizeByClass = new ObjectIntHashMap<>(); - private int stringPoolSize; - private int metadataSize; - - public Renderer(OutputSourceWriter writer, Set asyncMethods, - Set asyncFamilyMethods, RenderingContext context) { - this.naming = context.getNaming(); + public Renderer(SourceWriter writer, Set asyncMethods, RenderingContext context, + Diagnostics diagnostics, Map generators, + MethodNodeCache astCache, CacheStatus cacheStatus, JavaScriptTemplateFactory templateFactory) { this.writer = writer; this.classSource = context.getClassSource(); this.classLoader = context.getClassLoader(); this.services = context.getServices(); this.asyncMethods = new HashSet<>(asyncMethods); - this.asyncFamilyMethods = new HashSet<>(asyncFamilyMethods); this.context = context; - } - - public int getStringPoolSize() { - return stringPoolSize; - } - - public int getMetadataSize() { - return metadataSize; - } - - public String[] getClassesInStats() { - return sizeByClass.keys().toArray(String.class); - } - - public int getClassSize(String className) { - return sizeByClass.getOrDefault(className, 0); + methodBodyRenderer = new MethodBodyRenderer(context, diagnostics, context.isMinifying(), asyncMethods, + writer); + this.generators = generators; + this.astCache = astCache; + this.cacheStatus = cacheStatus; + this.templateFactory = templateFactory; } @Override @@ -105,18 +113,8 @@ public class Renderer implements RenderingManager { return writer; } - @Override - public NamingStrategy getNaming() { - return naming; - } - - @Override - public boolean isMinifying() { - return minifying; - } - - public void setMinifying(boolean minifying) { - this.minifying = minifying; + public boolean isThreadLibraryUsed() { + return threadLibraryUsed; } @Override @@ -136,10 +134,6 @@ public class Renderer implements RenderingManager { return properties; } - public void setDebugEmitter(DebugInformationEmitter debugEmitter) { - this.debugEmitter = debugEmitter; - } - public void setProgressConsumer(IntFunction progressConsumer) { this.progressConsumer = progressConsumer; } @@ -153,7 +147,7 @@ public class Renderer implements RenderingManager { if (context.getStringPool().isEmpty()) { return; } - int start = writer.getOffset(); + writer.markSectionStart(SECTION_STRING_POOL); writer.appendFunction("$rt_stringPool").append("(["); for (int i = 0; i < context.getStringPool().size(); ++i) { if (i > 0) { @@ -162,17 +156,16 @@ public class Renderer implements RenderingManager { RenderingUtil.writeString(writer, context.getStringPool().get(i)); } writer.append("]);").newLine(); - stringPoolSize = writer.getOffset() - start; + writer.markSectionEnd(); } public void renderStringConstants() throws RenderingException { for (PostponedFieldInitializer initializer : postponedFieldInitializers) { - int start = writer.getOffset(); + writer.markSectionStart(SECTION_STRING_POOL); writer.appendStaticField(initializer.field).ws().append("=").ws(); context.constantToString(writer, initializer.value); writer.append(";").softNewLine(); - int sz = writer.getOffset() - start; - appendClassSize(initializer.field.getClassName(), sz); + writer.markSectionEnd(); } } @@ -207,44 +200,58 @@ public class Renderer implements RenderingManager { writer.outdent().append("};").newLine(); } - private void appendClassSize(String className, int sz) { - sizeByClass.put(className, sizeByClass.getOrDefault(className, 0) + sz); - } - - public void prepare(List classes) { - if (minifying) { - NamingOrderer orderer = new NamingOrderer(); - NameFrequencyEstimator estimator = new NameFrequencyEstimator(orderer, classSource, - asyncFamilyMethods); - for (PreparedClass cls : classes) { - estimator.estimate(cls); - } - naming.getScopeName(); - orderer.apply(naming); + public boolean render(ListableClassHolderSource classes, boolean isFriendlyToDebugger) { + var sequence = new ArrayList(); + var visited = new HashSet(); + for (String className : classes.getClassNames()) { + orderClasses(classes, className, visited, sequence); } - } - public boolean render(List 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; - for (PreparedClass cls : classes) { - int start = writer.getOffset(); + for (var cls : sequence) { + writer.markClassStart(cls.getName()); renderDeclaration(cls); - renderMethodBodies(cls); - appendClassSize(cls.getName(), writer.getOffset() - start); - if (progressConsumer.apply(1000 * ++index / classes.size()) == TeaVMProgressFeedback.CANCEL) { + renderMethodBodies(cls, decompiler); + writer.markClassEnd(); + if (progressConsumer.apply(1000 * ++index / sequence.size()) == TeaVMProgressFeedback.CANCEL) { return false; } } - renderClassMetadata(classes); + renderClassMetadata(sequence); return true; } - private void renderDeclaration(PreparedClass cls) throws RenderingException { - ScopedName jsName = naming.getNameFor(cls.getName()); - debugEmitter.addClass(jsName.value, cls.getName(), cls.getParentName()); + private void orderClasses(ClassHolderSource classes, String className, Set visited, + List order) { + if (!visited.add(className)) { + return; + } + ClassHolder cls = classes.get(className); + if (cls == null) { + return; + } + if (cls.getParent() != null) { + orderClasses(classes, cls.getParent(), visited, order); + } + for (String iface : cls.getInterfaces()) { + orderClasses(classes, iface, visited, order); + } + order.add(cls); + } + + private void renderDeclaration(ClassHolder cls) throws RenderingException { List nonStaticFields = new ArrayList<>(); List staticFields = new ArrayList<>(); - for (FieldHolder field : cls.getClassHolder().getFields()) { + for (FieldHolder field : cls.getFields()) { if (field.getModifiers().contains(ElementModifier.STATIC)) { staticFields.add(field); } else { @@ -252,12 +259,13 @@ public class Renderer implements RenderingManager { } } - if (nonStaticFields.isEmpty() && !cls.getClassHolder().getName().equals("java.lang.Object")) { - renderShortClassFunctionDeclaration(cls, jsName); + if (nonStaticFields.isEmpty() && !cls.getName().equals("java.lang.Object")) { + renderShortClassFunctionDeclaration(cls); } else { - renderFullClassFunctionDeclaration(cls, jsName, nonStaticFields); + renderFullClassFunctionDeclaration(cls, nonStaticFields); } + var hasLet = false; for (FieldHolder field : staticFields) { Object value = field.getInitialValue(); if (value == null) { @@ -270,30 +278,30 @@ public class Renderer implements RenderingManager { value = null; } - ScopedName fieldName = naming.getFullNameFor(fieldRef); - if (fieldName.scoped) { - writer.append(naming.getScopeName()).append("."); - } else { + if (!hasLet) { writer.append("let "); + hasLet = true; + } else { + writer.append(",").ws(); } - writer.append(fieldName.value).ws().append("=").ws(); + writer.appendStaticField(fieldRef).ws().append("=").ws(); context.constantToString(writer, value); - writer.append(";").softNewLine(); + } + if (hasLet) { + writer.append(";").newLine(); } } - private void renderFullClassFunctionDeclaration(PreparedClass cls, ScopedName jsName, - List nonStaticFields) { + private void renderFullClassFunctionDeclaration(ClassReader cls, List nonStaticFields) { boolean thisAliased = false; - renderFunctionDeclaration(jsName); - writer.append("()").ws().append("{").indent().softNewLine(); + writer.append("function ").appendClass(cls.getName()).append("()").ws().append("{").indent().softNewLine(); if (nonStaticFields.size() > 1) { thisAliased = true; writer.append("let a").ws().append("=").ws().append("this;").ws(); } - if (!cls.getClassHolder().getModifiers().contains(ElementModifier.INTERFACE) - && cls.getParentName() != null) { - writer.appendClass(cls.getParentName()).append(".call(").append(thisAliased ? "a" : "this") + if (!cls.readModifiers().contains(ElementModifier.INTERFACE) + && cls.getParent() != null) { + writer.appendClass(cls.getParent()).append(".call(").append(thisAliased ? "a" : "this") .append(");").softNewLine(); } for (FieldHolder field : nonStaticFields) { @@ -306,7 +314,6 @@ public class Renderer implements RenderingManager { .append("=").ws(); context.constantToString(writer, value); writer.append(";").softNewLine(); - debugEmitter.addField(field.getName(), naming.getNameFor(fieldRef)); } if (cls.getName().equals("java.lang.Object")) { @@ -314,65 +321,83 @@ public class Renderer implements RenderingManager { } writer.outdent().append("}"); - if (jsName.scoped) { - writer.append(";"); - } writer.newLine(); } - private void renderShortClassFunctionDeclaration(PreparedClass cls, ScopedName jsName) { - if (jsName.scoped) { - writer.append(naming.getScopeName()).append("."); - } else { - writer.append("let "); - } - writer.append(jsName.value).ws().append("=").ws().appendFunction("$rt_classWithoutFields").append("("); - if (cls.getClassHolder().hasModifier(ElementModifier.INTERFACE)) { + private void renderShortClassFunctionDeclaration(ClassReader cls) { + writer.append("let ").appendClass(cls.getName()).ws().append("=").ws() + .appendFunction("$rt_classWithoutFields").append("("); + if (cls.hasModifier(ElementModifier.INTERFACE)) { writer.append("0"); - } else if (!cls.getParentName().equals("java.lang.Object")) { - writer.appendClass(cls.getParentName()); + } else if (!cls.getParent().equals("java.lang.Object")) { + writer.appendClass(cls.getParent()); } writer.append(");").newLine(); } - private void renderMethodBodies(PreparedClass cls) throws RenderingException { - debugEmitter.emitClass(cls.getName()); + private void renderMethodBodies(ClassHolder cls, Decompiler decompiler) { + writer.emitClass(cls.getName()); MethodReader clinit = classSource.get(cls.getName()).getMethod(CLINIT_METHOD); if (clinit != null && context.isDynamicInitializer(cls.getName())) { renderCallClinit(clinit, cls); } - if (!cls.getClassHolder().hasModifier(ElementModifier.INTERFACE) - && !cls.getClassHolder().hasModifier(ElementModifier.ABSTRACT)) { - for (PreparedMethod method : cls.getMethods()) { - if (!method.modifiers.contains(ElementModifier.STATIC)) { - if (method.reference.getName().equals("")) { + if (!cls.hasModifier(ElementModifier.INTERFACE) + && !cls.hasModifier(ElementModifier.ABSTRACT)) { + for (var method : cls.getMethods()) { + if (!method.hasModifier(ElementModifier.STATIC)) { + if (method.getName().equals("")) { renderInitializer(method); } } } } - for (PreparedMethod method : cls.getMethods()) { - renderBody(method); + var hasLet = false; + for (var method : cls.getMethods()) { + if (!filterMethod(method)) { + continue; + } + if (!hasLet) { + writer.append("let "); + hasLet = true; + } else { + writer.append(",").newLine(); + } + renderBody(method, decompiler); + } + if (hasLet) { + writer.append(";").newLine(); } - debugEmitter.emitClass(null); + writer.emitClass(null); } - private void renderCallClinit(MethodReader clinit, PreparedClass cls) { + private boolean filterMethod(MethodReader method) { + if (method.hasModifier(ElementModifier.ABSTRACT)) { + return false; + } + if (method.getAnnotations().get(InjectedBy.class.getName()) != null + || context.getInjector(method.getReference()) != null) { + return false; + } + if (!method.hasModifier(ElementModifier.NATIVE) && method.getProgram() == null) { + return false; + } + return true; + } + + private void renderCallClinit(MethodReader clinit, ClassReader cls) { boolean isAsync = asyncMethods.contains(clinit.getReference()); - ScopedName className = naming.getNameFor(cls.getName()); - String clinitCalled = (className.scoped ? naming.getScopeName() + "_" : "") + className.value - + "_$clinitCalled"; + var clinitCalledField = new FieldReference(cls.getName(), "$_teavm_clinitCalled_$"); if (isAsync) { - writer.append("let ").append(clinitCalled).ws().append("=").ws().append("false;").softNewLine(); + writer.append("let ").appendStaticField(clinitCalledField).ws().append("=").ws().append("false;") + .softNewLine(); } - ScopedName name = naming.getNameForClassInit(cls.getName()); - renderLambdaDeclaration(name); + writer.append("let ").appendClassInit(cls.getName()).ws().append("=").ws(); writer.append("()").sameLineWs().append("=>").ws().append("{").softNewLine().indent(); if (isAsync) { @@ -383,7 +408,7 @@ public class Renderer implements RenderingManager { writer.append(context.pointerName()).ws().append("=").ws().appendFunction("$rt_nativeThread") .append("().pop();").softNewLine(); writer.outdent().append("}").ws(); - writer.append("else if").ws().append("(").append(clinitCalled).append(")").ws() + writer.append("else if").ws().append("(").appendStaticField(clinitCalledField).append(")").ws() .append("{").indent().softNewLine(); writer.append("return;").softNewLine(); writer.outdent().append("}").softNewLine(); @@ -391,7 +416,7 @@ public class Renderer implements RenderingManager { renderAsyncPrologue(writer, context); writer.append("case 0:").indent().softNewLine(); - writer.append(clinitCalled).ws().append('=').ws().append("true;").softNewLine(); + writer.appendStaticField(clinitCalledField).ws().append('=').ws().append("true;").softNewLine(); } else { renderEraseClinit(cls); } @@ -416,55 +441,48 @@ public class Renderer implements RenderingManager { writer.appendFunction("$rt_nativeThread").append("().push(" + context.pointerName() + ");").softNewLine(); } - writer.outdent().append("}"); - if (name.scoped) { - writer.append(";"); - } + writer.outdent().append("};"); writer.newLine(); } - private void renderEraseClinit(PreparedClass cls) { + private void renderEraseClinit(ClassReader cls) { writer.appendClassInit(cls.getName()).ws().append("=").ws() .appendFunction("$rt_eraseClinit").append("(") .appendClass(cls.getName()).append(");").softNewLine(); } - private void renderClassMetadata(List classes) { - if (classes.isEmpty()) { - return; - } - + private void renderClassMetadata(List classReaders) { ClassMetadataRequirements metadataRequirements = new ClassMetadataRequirements(context.getDependencyInfo()); - int start = writer.getOffset(); + writer.markSectionStart(SECTION_METADATA); writer.appendFunction("$rt_packages").append("(["); - ObjectIntMap packageIndexes = generatePackageMetadata(classes, metadataRequirements); + ObjectIntMap packageIndexes = generatePackageMetadata(classReaders, metadataRequirements); writer.append("]);").newLine(); - for (int i = 0; i < classes.size(); i += 50) { - int j = Math.min(i + 50, classes.size()); - renderClassMetadataPortion(classes.subList(i, j), packageIndexes, metadataRequirements); + for (int i = 0; i < classReaders.size(); i += 50) { + int j = Math.min(i + 50, classReaders.size()); + renderClassMetadataPortion(classReaders.subList(i, j), packageIndexes, metadataRequirements); } - metadataSize = writer.getOffset() - start; + writer.markSectionEnd(); } - private void renderClassMetadataPortion(List classes, ObjectIntMap packageIndexes, + private void renderClassMetadataPortion(List classes, ObjectIntMap packageIndexes, ClassMetadataRequirements metadataRequirements) { writer.appendFunction("$rt_metadata").append("(["); boolean first = true; - for (PreparedClass cls : classes) { + for (var cls : classes) { if (!first) { writer.append(',').softNewLine(); } first = false; - debugEmitter.emitClass(cls.getName()); + writer.emitClass(cls.getName()); writer.appendClass(cls.getName()).append(",").ws(); - ClassMetadataRequirements.Info requiredMetadata = metadataRequirements.getInfo(cls.getName()); + var className = cls.getName(); + var requiredMetadata = metadataRequirements.getInfo(className); if (requiredMetadata.name()) { - String className = cls.getName(); int dotIndex = className.lastIndexOf('.') + 1; String packageName = className.substring(0, dotIndex); className = className.substring(dotIndex); @@ -475,14 +493,14 @@ public class Renderer implements RenderingManager { } writer.append(",").ws(); - if (cls.getParentName() != null) { - writer.appendClass(cls.getParentName()); + if (cls.getParent() != null) { + writer.appendClass(cls.getParent()); } else { writer.append("0"); } writer.append(',').ws(); writer.append("["); - List interfaces = new ArrayList<>(cls.getClassHolder().getInterfaces()); + var interfaces = new ArrayList<>(cls.getInterfaces()); for (int i = 0; i < interfaces.size(); ++i) { String iface = interfaces.get(i); if (i > 0) { @@ -492,28 +510,28 @@ public class Renderer implements RenderingManager { } writer.append("],").ws(); - writer.append(ElementModifier.pack(cls.getClassHolder().getModifiers())).append(',').ws(); - writer.append(cls.getClassHolder().getLevel().ordinal()).append(',').ws(); + writer.append(ElementModifier.pack(cls.readModifiers())).append(',').ws(); + writer.append(cls.getLevel().ordinal()).append(',').ws(); if (!requiredMetadata.enclosingClass() && !requiredMetadata.declaringClass() && !requiredMetadata.simpleName()) { writer.append("0"); } else { writer.append('['); - if (requiredMetadata.enclosingClass() && cls.getClassHolder().getOwnerName() != null) { - writer.appendClass(cls.getClassHolder().getOwnerName()); + if (requiredMetadata.enclosingClass() && cls.getOwnerName() != null) { + writer.appendClass(cls.getOwnerName()); } else { writer.append('0'); } writer.append(','); - if (requiredMetadata.declaringClass() && cls.getClassHolder().getDeclaringClassName() != null) { - writer.appendClass(cls.getClassHolder().getDeclaringClassName()); + if (requiredMetadata.declaringClass() && cls.getDeclaringClassName() != null) { + writer.appendClass(cls.getDeclaringClassName()); } else { writer.append('0'); } writer.append(','); - if (requiredMetadata.simpleName() && cls.getClassHolder().getSimpleName() != null) { - writer.append("\"").append(RenderingUtil.escapeString(cls.getClassHolder().getSimpleName())) + if (requiredMetadata.simpleName() && cls.getSimpleName() != null) { + writer.append("\"").append(RenderingUtil.escapeString(cls.getSimpleName())) .append("\""); } else { writer.append('0'); @@ -532,30 +550,30 @@ public class Renderer implements RenderingManager { Map virtualMethods = new LinkedHashMap<>(); collectMethodsToCopyFromInterfaces(classSource.get(cls.getName()), virtualMethods); - for (PreparedMethod method : cls.getMethods()) { - if (!method.modifiers.contains(ElementModifier.STATIC) - && method.accessLevel != AccessLevel.PRIVATE) { - virtualMethods.put(method.reference.getDescriptor(), method.reference); + for (var method : cls.getMethods()) { + if (filterMethod(method) && !method.readModifiers().contains(ElementModifier.STATIC) + && method.getLevel() != AccessLevel.PRIVATE) { + virtualMethods.put(method.getDescriptor(), method.getReference()); } } renderVirtualDeclarations(virtualMethods.values()); - debugEmitter.emitClass(null); + writer.emitClass(null); } writer.append("]);").newLine(); } - private ObjectIntMap generatePackageMetadata(List classes, + private ObjectIntMap generatePackageMetadata(List classes, ClassMetadataRequirements metadataRequirements) { PackageNode root = new PackageNode(null); - for (PreparedClass classNode : classes) { - String className = classNode.getName(); - ClassMetadataRequirements.Info requiredMetadata = metadataRequirements.getInfo(className); + for (var cls : classes) { + var requiredMetadata = metadataRequirements.getInfo(cls.getName()); if (!requiredMetadata.name()) { continue; } + var className = cls.getName(); int dotIndex = className.lastIndexOf('.'); if (dotIndex < 0) { continue; @@ -593,14 +611,6 @@ public class Renderer implements RenderingManager { PackageNode(String name) { this.name = name; } - - int count() { - int result = 0; - for (PackageNode child : children.values()) { - result += 1 + child.count(); - } - return result; - } } private void addPackageName(PackageNode node, String name) { @@ -686,11 +696,10 @@ public class Renderer implements RenderingManager { return null; } - private void renderInitializer(PreparedMethod method) { - MethodReference ref = method.reference; - debugEmitter.emitMethod(ref.getDescriptor()); - ScopedName name = naming.getNameForInit(ref); - renderLambdaDeclaration(name); + private void renderInitializer(MethodReader method) { + MethodReference ref = method.getReference(); + writer.emitMethod(ref.getDescriptor()); + writer.append("let ").appendInit(ref).ws().append("=").ws(); if (ref.parameterCount() != 1) { writer.append("("); } @@ -715,16 +724,13 @@ public class Renderer implements RenderingManager { } writer.append(");").softNewLine(); writer.append("return " + instanceName + ";").softNewLine(); - writer.outdent().append("}"); - if (name.scoped) { - writer.append(";"); - } + writer.outdent().append("};"); writer.newLine(); - debugEmitter.emitMethod(null); + writer.emitMethod(null); } private String variableNameForInitializer(int index) { - return minifying ? RenderingUtil.indexToId(index) : "var_" + index; + return context.isMinifying() ? RenderingUtil.indexToId(index) : "var_" + index; } private void renderVirtualDeclarations(Collection methods) { @@ -739,19 +745,19 @@ public class Renderer implements RenderingManager { if (!isVirtual(method)) { continue; } - debugEmitter.emitMethod(method.getDescriptor()); + writer.emitMethod(method.getDescriptor()); if (!first) { writer.append(",").ws(); } first = false; emitVirtualDeclaration(method); - debugEmitter.emitMethod(null); + writer.emitMethod(null); } writer.append("]"); } private void emitVirtualDeclaration(MethodReference ref) { - String methodName = naming.getNameFor(ref.getDescriptor()); + String methodName = context.getNaming().getNameFor(ref.getDescriptor()); writer.append("\"").append(methodName).append("\""); writer.append(",").ws(); emitVirtualFunctionWrapper(ref); @@ -787,47 +793,142 @@ public class Renderer implements RenderingManager { writer.append(");").ws().append("}"); } - private void renderBody(PreparedMethod method) { - MethodReference ref = method.reference; - debugEmitter.emitMethod(ref.getDescriptor()); - ScopedName name = naming.getFullNameFor(ref); + private void renderBody(MethodHolder method, Decompiler decompiler) { + MethodReference ref = method.getReference(); + writer.emitMethod(ref.getDescriptor()); - renderLambdaDeclaration(name); - method.parameters.replay(writer, RememberedSource.FILTER_ALL); - if (method.variables != null) { - for (var variable : method.variables) { - variable.emit(debugEmitter); - } - } + writer.appendMethodBody(ref).ws().append("=").ws(); + methodBodyRenderer.renderParameters(ref, method.getModifiers()); writer.sameLineWs().append("=>").ws().append("{").indent().softNewLine(); - method.body.replay(writer, RememberedSource.FILTER_ALL); + if (method.hasModifier(ElementModifier.NATIVE)) { + renderNativeBody(method, classSource); + } else { + renderRegularBody(method, decompiler); + } writer.outdent().append("}"); - if (name.scoped) { - writer.append(";"); - } - - writer.newLine(); - debugEmitter.emitMethod(null); + writer.emitMethod(null); } - private void renderLambdaDeclaration(ScopedName name) { - if (name.scoped) { - writer.append(naming.getScopeName()).append(".").append(name.value); + private void renderNativeBody(MethodHolder method, ClassReaderSource classes) { + var reference = method.getReference(); + var generator = generators.get(reference); + if (generator == null) { + AnnotationHolder annotHolder = method.getAnnotations().get(GeneratedBy.class.getName()); + if (annotHolder == null) { + throw new DecompilationException("Method " + method.getOwnerName() + "." + method.getDescriptor() + + " is native, but no " + GeneratedBy.class.getName() + " annotation found"); + } + ValueType annotValue = annotHolder.getValues().get("value").getJavaClass(); + String generatorClassName = ((ValueType.Object) annotValue).getClassName(); + generator = generatorCache.computeIfAbsent(generatorClassName, + name -> createGenerator(name, method, classes)); + } + + 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 { - 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) { - if (name.scoped) { - writer.append(naming.getScopeName()).append(".").append(name.value).ws().append("=").ws(); + private AstCacheEntry decompileRegular(Decompiler decompiler, MethodHolder method) { + if (astCache == null) { + return decompileRegularCacheMiss(decompiler, method); } - writer.append("function"); - if (!name.scoped) { - writer.append(" ").append(name.value); + + AstCacheEntry entry = !cacheStatus.isStaleMethod(method.getReference()) + ? astCache.get(method.getReference(), cacheStatus) + : null; + if (entry == null) { + entry = decompileRegularCacheMiss(decompiler, method); + RegularMethodNode finalNode = entry.method; + astCache.store(method.getReference(), entry, () -> dependencyExtractor.extract(finalNode)); } + return entry; + } + + private AstCacheEntry decompileRegularCacheMiss(Decompiler decompiler, MethodHolder method) { + RegularMethodNode node = decompiler.decompileRegular(method); + ControlFlowEntry[] cfg = LocationGraphBuilder.build(node.getBody()); + return new AstCacheEntry(node, cfg); + } + + private AsyncMethodNode decompileAsync(Decompiler decompiler, MethodHolder method) { + if (astCache == null) { + return decompiler.decompileAsync(method); + } + + AsyncMethodNode node = !cacheStatus.isStaleMethod(method.getReference()) + ? astCache.getAsync(method.getReference(), cacheStatus) + : null; + if (node == null) { + node = decompiler.decompileAsync(method); + AsyncMethodNode finalNode = node; + astCache.storeAsync(method.getReference(), node, () -> dependencyExtractor.extract(finalNode)); + } + return node; } static void renderAsyncPrologue(SourceWriter writer, RenderingContext context) { diff --git a/core/src/main/java/org/teavm/backend/javascript/rendering/RenderingManager.java b/core/src/main/java/org/teavm/backend/javascript/rendering/RenderingManager.java index 53e0ef84e..dbf44d888 100644 --- a/core/src/main/java/org/teavm/backend/javascript/rendering/RenderingManager.java +++ b/core/src/main/java/org/teavm/backend/javascript/rendering/RenderingManager.java @@ -22,12 +22,8 @@ import org.teavm.common.ServiceRepository; import org.teavm.model.ListableClassReaderSource; public interface RenderingManager extends ServiceRepository { - NamingStrategy getNaming(); - SourceWriter getWriter(); - boolean isMinifying(); - ListableClassReaderSource getClassSource(); ClassLoader getClassLoader(); diff --git a/tools/junit/src/main/resources/test-server/client.js b/tools/junit/src/main/resources/test-server/client.js index fd3fb67e5..e4c6b4c36 100644 --- a/tools/junit/src/main/resources/test-server/client.js +++ b/tools/junit/src/main/resources/test-server/client.js @@ -18,7 +18,9 @@ let logging = false; let deobfuscation = false; -deobfuscator(); +if (typeof deobfuscator !== 'undefined') { + deobfuscator(); +} function tryConnect() { let ws = new WebSocket("ws://localhost:{{PORT}}/ws");