From 6ac598b92799c64c01abe43ed633fea4da30f5f2 Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Tue, 30 Jan 2024 17:01:28 +0100 Subject: [PATCH] js: add limit for top-level declarations Rationale: turns out that V8 utilizes stack even to represent module-level functions. This can cause SOE when there's too many classes and methods in source JVM --- .../classlib/impl/ServiceLoaderJSSupport.java | 2 +- .../classlib/java/lang/ClassGenerator.java | 34 ++-- .../backend/javascript/JavaScriptTarget.java | 71 +++++--- .../javascript/codegen/AliasProvider.java | 12 +- .../codegen/DefaultAliasProvider.java | 69 +++++--- .../codegen/DefaultNamingStrategy.java | 45 +++-- .../codegen/MinifyingAliasProvider.java | 60 ++++--- .../javascript/codegen/NamingStrategy.java | 18 +- .../codegen/OutputSourceWriter.java | 163 ++++++++++++++++-- .../javascript/codegen/RememberedSource.java | 25 ++- .../codegen/RememberingSourceWriter.java | 53 +++++- .../javascript/codegen/ScopedName.java | 26 +++ .../javascript/codegen/SourceWriter.java | 28 ++- .../javascript/codegen/SourceWriterSink.java | 20 ++- .../javascript/rendering/AstWriter.java | 109 +++++++++--- .../rendering/MethodBodyRenderer.java | 8 +- .../rendering/NameFrequencyEstimator.java | 35 ++-- .../javascript/rendering/Renderer.java | 62 +++---- .../javascript/rendering/RuntimeRenderer.java | 10 +- .../rendering/StatementRenderer.java | 14 +- .../javascript/templating/LetJoiner.java | 70 -------- .../templating/TemplatingAstWriter.java | 32 +--- .../org/teavm/jso/impl/JSAliasRenderer.java | 12 +- .../teavm/jso/impl/JSBodyBloatedEmitter.java | 8 +- .../platform/plugin/AsyncMethodGenerator.java | 2 +- .../platform/plugin/PlatformGenerator.java | 15 +- .../main/java/org/teavm/cli/TeaVMRunner.java | 9 + .../java/org/teavm/tooling/TeaVMTool.java | 6 + .../teavm/tooling/builder/BuildStrategy.java | 2 + .../builder/InProcessBuildStrategy.java | 7 + .../tooling/builder/RemoteBuildStrategy.java | 5 + .../tooling/daemon/RemoteBuildRequest.java | 1 + .../java/org/teavm/gradle/TeaVMPlugin.java | 1 + .../gradle/api/TeaVMJSConfiguration.java | 2 + .../gradle/tasks/GenerateJavaScriptTask.java | 7 + .../org/teavm/junit/TestExceptionPlugin.java | 2 +- .../org/teavm/maven/TeaVMCompileMojo.java | 4 + 37 files changed, 697 insertions(+), 352 deletions(-) create mode 100644 core/src/main/java/org/teavm/backend/javascript/codegen/ScopedName.java delete mode 100644 core/src/main/java/org/teavm/backend/javascript/templating/LetJoiner.java diff --git a/classlib/src/main/java/org/teavm/classlib/impl/ServiceLoaderJSSupport.java b/classlib/src/main/java/org/teavm/classlib/impl/ServiceLoaderJSSupport.java index 7d67ccef0..87d2cf83c 100644 --- a/classlib/src/main/java/org/teavm/classlib/impl/ServiceLoaderJSSupport.java +++ b/classlib/src/main/java/org/teavm/classlib/impl/ServiceLoaderJSSupport.java @@ -44,7 +44,7 @@ public class ServiceLoaderJSSupport implements Generator { } first = false; writer.append("[").appendClass(implName).append(",").ws() - .appendMethodBody(new MethodReference(implName, INIT_METHOD)) + .appendMethod(new MethodReference(implName, INIT_METHOD)) .append("]"); } } diff --git a/classlib/src/main/java/org/teavm/classlib/java/lang/ClassGenerator.java b/classlib/src/main/java/org/teavm/classlib/java/lang/ClassGenerator.java index 619506bc1..07434d5a7 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/lang/ClassGenerator.java +++ b/classlib/src/main/java/org/teavm/classlib/java/lang/ClassGenerator.java @@ -272,7 +272,7 @@ public class ClassGenerator implements Generator, Injector, DependencyPlugin { if (method.getResultType() != ValueType.VOID) { writer.append("return "); } - writer.appendMethodBody(method.getReference()); + writer.appendMethod(method.getReference()); writer.append('('); boolean first = true; @@ -315,30 +315,30 @@ public class ClassGenerator implements Generator, Injector, DependencyPlugin { if (type instanceof ValueType.Primitive) { switch (((ValueType.Primitive) type).getKind()) { case BOOLEAN: - writer.appendMethodBody(new MethodReference(Boolean.class, "valueOf", boolean.class, + writer.appendMethod(new MethodReference(Boolean.class, "valueOf", boolean.class, Boolean.class)); break; case BYTE: - writer.appendMethodBody(new MethodReference(Byte.class, "valueOf", byte.class, Byte.class)); + writer.appendMethod(new MethodReference(Byte.class, "valueOf", byte.class, Byte.class)); break; case SHORT: - writer.appendMethodBody(new MethodReference(Short.class, "valueOf", short.class, Short.class)); + writer.appendMethod(new MethodReference(Short.class, "valueOf", short.class, Short.class)); break; case CHARACTER: - writer.appendMethodBody(new MethodReference(Character.class, "valueOf", char.class, + writer.appendMethod(new MethodReference(Character.class, "valueOf", char.class, Character.class)); break; case INTEGER: - writer.appendMethodBody(new MethodReference(Integer.class, "valueOf", int.class, Integer.class)); + writer.appendMethod(new MethodReference(Integer.class, "valueOf", int.class, Integer.class)); break; case LONG: - writer.appendMethodBody(new MethodReference(Long.class, "valueOf", long.class, Long.class)); + writer.appendMethod(new MethodReference(Long.class, "valueOf", long.class, Long.class)); break; case FLOAT: - writer.appendMethodBody(new MethodReference(Float.class, "valueOf", float.class, Float.class)); + writer.appendMethod(new MethodReference(Float.class, "valueOf", float.class, Float.class)); break; case DOUBLE: - writer.appendMethodBody(new MethodReference(Double.class, "valueOf", double.class, Double.class)); + writer.appendMethod(new MethodReference(Double.class, "valueOf", double.class, Double.class)); break; } writer.append('('); @@ -355,28 +355,28 @@ public class ClassGenerator implements Generator, Injector, DependencyPlugin { if (type instanceof ValueType.Primitive) { switch (((ValueType.Primitive) type).getKind()) { case BOOLEAN: - writer.appendMethodBody(new MethodReference(Boolean.class, "booleanValue", boolean.class)); + writer.appendMethod(new MethodReference(Boolean.class, "booleanValue", boolean.class)); break; case BYTE: - writer.appendMethodBody(new MethodReference(Byte.class, "byteValue", byte.class)); + writer.appendMethod(new MethodReference(Byte.class, "byteValue", byte.class)); break; case SHORT: - writer.appendMethodBody(new MethodReference(Short.class, "shortValue", short.class)); + writer.appendMethod(new MethodReference(Short.class, "shortValue", short.class)); break; case CHARACTER: - writer.appendMethodBody(new MethodReference(Character.class, "charValue", char.class)); + writer.appendMethod(new MethodReference(Character.class, "charValue", char.class)); break; case INTEGER: - writer.appendMethodBody(new MethodReference(Integer.class, "intValue", int.class)); + writer.appendMethod(new MethodReference(Integer.class, "intValue", int.class)); break; case LONG: - writer.appendMethodBody(new MethodReference(Long.class, "longValue", long.class)); + writer.appendMethod(new MethodReference(Long.class, "longValue", long.class)); break; case FLOAT: - writer.appendMethodBody(new MethodReference(Float.class, "floatValue", float.class)); + writer.appendMethod(new MethodReference(Float.class, "floatValue", float.class)); break; case DOUBLE: - writer.appendMethodBody(new MethodReference(Double.class, "doubleValue", double.class)); + writer.appendMethod(new MethodReference(Double.class, "doubleValue", double.class)); break; } writer.append('('); 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 8f4db001e..cd5b37444 100644 --- a/core/src/main/java/org/teavm/backend/javascript/JavaScriptTarget.java +++ b/core/src/main/java/org/teavm/backend/javascript/JavaScriptTarget.java @@ -129,6 +129,7 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost { private final Map importedModules = new LinkedHashMap<>(); private JavaScriptTemplateFactory templateFactory; private JSModuleType moduleType = JSModuleType.UMD; + private int maxTopLevelNames = 80_000; @Override public List getTransformers() { @@ -228,6 +229,10 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost { this.stackTraceIncluded = stackTraceIncluded; } + public void setMaxTopLevelNames(int maxTopLevelNames) { + this.maxTopLevelNames = maxTopLevelNames; + } + @Override public List getHostExtensions() { return Collections.singletonList(this); @@ -351,7 +356,9 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost { } private void emit(ListableClassHolderSource classes, Writer writer, BuildTarget target) { - var aliasProvider = obfuscated ? new MinifyingAliasProvider() : new DefaultAliasProvider(); + var aliasProvider = obfuscated + ? new MinifyingAliasProvider(maxTopLevelNames) + : new DefaultAliasProvider(maxTopLevelNames); DefaultNamingStrategy naming = new DefaultNamingStrategy(aliasProvider, controller.getUnprocessedClassSource()); DebugInformationEmitter debugEmitterToUse = debugEmitter; if (debugEmitterToUse == null) { @@ -410,9 +417,9 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost { for (var entry : controller.getEntryPoints().entrySet()) { var alias = "$rt_export_" + entry.getKey(); var ref = entry.getValue().getMethod(); - rememberingWriter.append("let ").appendFunction(alias).ws().append("=").ws() - .appendFunction("$rt_mainStarter").append("(").appendMethodBody(ref); - rememberingWriter.append(");").newLine(); + rememberingWriter.startVariableDeclaration().appendFunction(alias) + .appendFunction("$rt_mainStarter").append("(").appendMethod(ref); + rememberingWriter.append(")").endDeclaration(); rememberingWriter.appendFunction(alias).append(".") .append("javaException").ws().append("=").ws().appendFunction("$rt_javaException") .append(";").newLine(); @@ -424,31 +431,49 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost { var epilogue = rememberingWriter.save(); rememberingWriter.clear(); - if (renderingContext.isMinifying()) { - 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, - controller.getClassInitializerInfo()); + var runtimeRenderer = new RuntimeRenderer(classes, rememberingWriter, controller.getClassInitializerInfo()); runtimeRenderer.prepareAstParts(renderer.isThreadLibraryUsed()); declarations.replay(runtimeRenderer.sink, RememberedSource.FILTER_REF); epilogue.replay(runtimeRenderer.sink, RememberedSource.FILTER_REF); runtimeRenderer.removeUnusedParts(); runtimeRenderer.renderRuntime(); - declarations.write(sourceWriter, 0); + var runtime = rememberingWriter.save(); + rememberingWriter.clear(); runtimeRenderer.renderEpilogue(); + var runtimeEpilogue = rememberingWriter.save(); + rememberingWriter.clear(); + + naming.additionalScopeName(); + naming.functionName("$rt_exports"); + for (var module : importedModules.values()) { + naming.functionName(module); + } + for (var exportedName : controller.getEntryPoints().keySet()) { + naming.functionName("$rt_export_" + exportedName); + } + var frequencyEstimator = new NameFrequencyEstimator(); + runtime.replay(frequencyEstimator, RememberedSource.FILTER_REF); + runtimeEpilogue.replay(frequencyEstimator, RememberedSource.FILTER_REF); + 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); + if (frequencyEstimator.hasAdditionalScope()) { + sourceWriter.append("let ").append(naming.additionalScopeName()).ws().append('=').ws() + .append("{};").softNewLine(); + } + + int start = sourceWriter.getOffset(); + runtime.write(sourceWriter, 0); + declarations.write(sourceWriter, 0); + runtimeEpilogue.write(sourceWriter, 0); epilogue.write(sourceWriter, 0); - printWrapperEnd(sourceWriter); + printModuleEnd(sourceWriter); + sourceWriter.finish(); int totalSize = sourceWriter.getOffset() - start; printStats(sourceWriter, totalSize); @@ -560,7 +585,7 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost { } } - private void printWrapperEnd(SourceWriter writer) { + private void printModuleEnd(SourceWriter writer) { switch (moduleType) { case UMD: printUmdEnd(writer); @@ -580,7 +605,7 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost { private void printUmdEnd(SourceWriter writer) { for (var export : controller.getEntryPoints().keySet()) { writer.appendFunction("$rt_exports").append(".").append(export).ws().append("=").ws() - .appendFunction("$rt_export_" + export); + .appendFunction("$rt_export_" + export).append(";").softNewLine(); } writer.outdent().append("}));").newLine(); } diff --git a/core/src/main/java/org/teavm/backend/javascript/codegen/AliasProvider.java b/core/src/main/java/org/teavm/backend/javascript/codegen/AliasProvider.java index 9c6ffaae8..4b1bde28a 100644 --- a/core/src/main/java/org/teavm/backend/javascript/codegen/AliasProvider.java +++ b/core/src/main/java/org/teavm/backend/javascript/codegen/AliasProvider.java @@ -22,17 +22,19 @@ import org.teavm.model.MethodReference; public interface AliasProvider { String getFieldAlias(FieldReference field); - String getStaticFieldAlias(FieldReference field); + ScopedName getStaticFieldAlias(FieldReference field); - String getStaticMethodAlias(MethodReference method); + ScopedName getStaticMethodAlias(MethodReference method); String getMethodAlias(MethodDescriptor method); - String getClassAlias(String className); + ScopedName getClassAlias(String className); - String getFunctionAlias(String name); + ScopedName getFunctionAlias(String name); - String getClassInitAlias(String className); + ScopedName getClassInitAlias(String className); + + String getAdditionalScopeName(); void reserveName(String name); } diff --git a/core/src/main/java/org/teavm/backend/javascript/codegen/DefaultAliasProvider.java b/core/src/main/java/org/teavm/backend/javascript/codegen/DefaultAliasProvider.java index b950f2444..381696cf3 100644 --- a/core/src/main/java/org/teavm/backend/javascript/codegen/DefaultAliasProvider.java +++ b/core/src/main/java/org/teavm/backend/javascript/codegen/DefaultAliasProvider.java @@ -17,24 +17,27 @@ package org.teavm.backend.javascript.codegen; import com.carrotsearch.hppc.ObjectIntHashMap; import com.carrotsearch.hppc.ObjectIntMap; -import java.util.HashMap; import java.util.HashSet; -import java.util.Map; import java.util.Set; import org.teavm.model.FieldReference; import org.teavm.model.MethodDescriptor; import org.teavm.model.MethodReference; public class DefaultAliasProvider implements AliasProvider { - private final Map classAliases = new HashMap<>(); + private final int maxTopLevelNames; private final Set knownAliases = new HashSet<>(200, 0.5f); private final ObjectIntMap knowAliasesCounter = new ObjectIntHashMap<>(); - private final Set knownVirtualAliases = new HashSet<>(200, 0.5f); - private final ObjectIntMap knowVirtualAliasesCounter = new ObjectIntHashMap<>(); + private final Set knownInstanceAliases = new HashSet<>(200, 0.5f); + private final ObjectIntMap knowInstanceAliasesCounter = new ObjectIntHashMap<>(); + private boolean additionalScopeStarted; + + public DefaultAliasProvider(int maxTopLevelNames) { + this.maxTopLevelNames = maxTopLevelNames; + } @Override - public String getClassAlias(String cls) { - return classAliases.computeIfAbsent(cls, key -> makeUniqueTopLevel(suggestAliasForClass(key))); + public ScopedName getClassAlias(String cls) { + return makeUnique(suggestAliasForClass(cls)); } private static String suggestAliasForClass(String cls) { @@ -84,11 +87,11 @@ public class DefaultAliasProvider implements AliasProvider { alias = "$" + alias; break; } - return makeUnique(knownVirtualAliases, knowVirtualAliasesCounter, alias); + return makeUniqueInstance(alias); } @Override - public String getStaticMethodAlias(MethodReference method) { + public ScopedName getStaticMethodAlias(MethodReference method) { String suggested = method.getDescriptor().getName(); switch (suggested) { case "": @@ -99,35 +102,55 @@ public class DefaultAliasProvider implements AliasProvider { break; } - return makeUniqueTopLevel(getClassAlias(method.getClassName()) + "_" + suggested); + return makeUnique(suggestAliasForClass(method.getClassName()) + "_" + suggested); } @Override public String getFieldAlias(FieldReference field) { - return makeUnique(knownVirtualAliases, knowVirtualAliasesCounter, "$" + field.getFieldName()); + return makeUniqueInstance("$" + field.getFieldName()); } @Override - public String getStaticFieldAlias(FieldReference field) { - return makeUniqueTopLevel(getClassAlias(field.getClassName()) + "_" + field.getFieldName()); + public ScopedName getStaticFieldAlias(FieldReference field) { + return makeUnique(suggestAliasForClass(field.getClassName()) + "_" + field.getFieldName()); } @Override - public String getFunctionAlias(String name) { - return name; + public ScopedName getFunctionAlias(String name) { + return makeUnique(name); } @Override - public String getClassInitAlias(String className) { - return makeUniqueTopLevel(suggestAliasForClass(className) + "_$callClinit"); + public ScopedName getClassInitAlias(String className) { + return makeUnique(suggestAliasForClass(className) + "_$callClinit"); + } + + @Override + public String getAdditionalScopeName() { + return makeUnique("$rt_java").name; } @Override public void reserveName(String name) { } - private String makeUniqueTopLevel(String suggested) { - return makeUnique(knownAliases, knowAliasesCounter, suggested); + private ScopedName makeUnique(String suggested) { + suggested = sanitize(suggested); + if (!additionalScopeStarted && knownAliases.size() >= maxTopLevelNames) { + additionalScopeStarted = true; + knownAliases.clear(); + knowAliasesCounter.clear(); + } + var alias = suggested; + int index = knowAliasesCounter.get(alias); + if (index > 0) { + alias = suggested + index++; + } + while (!knownAliases.add(alias)) { + alias = suggested + index++; + } + knowAliasesCounter.put(alias, index); + return new ScopedName(alias, additionalScopeStarted); } private String sanitize(String s) { @@ -163,17 +186,17 @@ public class DefaultAliasProvider implements AliasProvider { return isIdentifierStart(c) || c >= '0' && c <= '9'; } - private String makeUnique(Set knowAliases, ObjectIntMap indexMap, String alias) { + private String makeUniqueInstance(String alias) { alias = sanitize(alias); String uniqueAlias = alias; - int index = indexMap.get(alias); + int index = knowInstanceAliasesCounter.get(alias); if (index > 0) { uniqueAlias = alias + index++; } - while (!knowAliases.add(uniqueAlias)) { + while (!knownInstanceAliases.add(uniqueAlias)) { uniqueAlias = alias + index++; } - indexMap.put(alias, index); + knowInstanceAliasesCounter.put(alias, index); return uniqueAlias; } } diff --git a/core/src/main/java/org/teavm/backend/javascript/codegen/DefaultNamingStrategy.java b/core/src/main/java/org/teavm/backend/javascript/codegen/DefaultNamingStrategy.java index e8f4c3c01..0f6ac1cea 100644 --- a/core/src/main/java/org/teavm/backend/javascript/codegen/DefaultNamingStrategy.java +++ b/core/src/main/java/org/teavm/backend/javascript/codegen/DefaultNamingStrategy.java @@ -33,12 +33,13 @@ public class DefaultNamingStrategy implements NamingStrategy { private final AliasProvider aliasProvider; private final ClassReaderSource classSource; private final Map aliases = new HashMap<>(); - private final Map privateAliases = new HashMap<>(); - private final Map classAliases = new HashMap<>(); + private final Map privateAliases = new HashMap<>(); + private final Map classAliases = new HashMap<>(); private final Map fieldAliases = new HashMap<>(); - private final Map staticFieldAliases = new HashMap<>(); - private final Map functionAliases = new HashMap<>(); - private final Map classInitAliases = new HashMap<>(); + private final Map staticFieldAliases = new HashMap<>(); + private final Map functionAliases = new HashMap<>(); + private final Map classInitAliases = new HashMap<>(); + private String additionalScopeName; public DefaultNamingStrategy(AliasProvider aliasProvider, ClassReaderSource classSource) { this.aliasProvider = aliasProvider; @@ -46,12 +47,12 @@ public class DefaultNamingStrategy implements NamingStrategy { } @Override - public String getNameFor(String cls) { + public ScopedName className(String cls) { return classAliases.computeIfAbsent(cls, key -> aliasProvider.getClassAlias(cls)); } @Override - public String getNameFor(MethodDescriptor method) { + public String instanceMethodName(MethodDescriptor method) { String alias = aliases.get(method); if (alias == null) { alias = aliasProvider.getMethodAlias(method); @@ -61,16 +62,16 @@ public class DefaultNamingStrategy implements NamingStrategy { } @Override - public String getFullNameFor(MethodReference method) { + public ScopedName methodName(MethodReference method) { return getFullNameFor(method, NO_CLASSIFIER); } @Override - public String getNameForInit(MethodReference method) { + public ScopedName initializerName(MethodReference method) { return getFullNameFor(method, INIT_CLASSIFIER); } - private String getFullNameFor(MethodReference method, byte classifier) { + private ScopedName getFullNameFor(MethodReference method, byte classifier) { MethodReference originalMethod = method; method = getRealMethod(method); if (method == null) { @@ -82,14 +83,14 @@ public class DefaultNamingStrategy implements NamingStrategy { } @Override - public String getNameFor(FieldReference field) { + public String instanceFieldName(FieldReference field) { String alias = fieldAliases.get(field); if (alias == null) { FieldReference realField = getRealField(field); if (realField.equals(field)) { alias = aliasProvider.getFieldAlias(realField); } else { - alias = getNameFor(realField); + alias = instanceFieldName(realField); } fieldAliases.put(field, alias); } @@ -97,14 +98,14 @@ public class DefaultNamingStrategy implements NamingStrategy { } @Override - public String getFullNameFor(FieldReference field) { + public ScopedName fieldName(FieldReference field) { var alias = staticFieldAliases.get(field); if (alias == null) { FieldReference realField = getRealField(field); if (realField.equals(field)) { alias = aliasProvider.getStaticFieldAlias(realField); } else { - alias = getFullNameFor(realField); + alias = fieldName(realField); } staticFieldAliases.put(field, alias); } @@ -112,13 +113,21 @@ public class DefaultNamingStrategy implements NamingStrategy { } @Override - public String getNameForFunction(String name) { - return functionAliases.computeIfAbsent(name, key -> aliasProvider.getFunctionAlias(key)); + public ScopedName functionName(String name) { + return functionAliases.computeIfAbsent(name, aliasProvider::getFunctionAlias); } @Override - public String getNameForClassInit(String className) { - return classInitAliases.computeIfAbsent(className, key -> aliasProvider.getClassInitAlias(key)); + public ScopedName classInitializerName(String className) { + return classInitAliases.computeIfAbsent(className, aliasProvider::getClassInitAlias); + } + + @Override + public String additionalScopeName() { + if (additionalScopeName == null) { + additionalScopeName = aliasProvider.getAdditionalScopeName(); + } + return additionalScopeName; } @Override diff --git a/core/src/main/java/org/teavm/backend/javascript/codegen/MinifyingAliasProvider.java b/core/src/main/java/org/teavm/backend/javascript/codegen/MinifyingAliasProvider.java index c1cf7153d..46956d362 100644 --- a/core/src/main/java/org/teavm/backend/javascript/codegen/MinifyingAliasProvider.java +++ b/core/src/main/java/org/teavm/backend/javascript/codegen/MinifyingAliasProvider.java @@ -25,64 +25,80 @@ import org.teavm.model.MethodReference; public class MinifyingAliasProvider implements AliasProvider { private static final String startLetters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; private static final String startVirtualLetters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + private final int maxTopLevelNames; private int lastSuffix; - private int lastVirtual; + private int lastInstanceSuffix; + private int topLevelNames; + private boolean additionalScopeStarted; private final Set usedAliases = new HashSet<>(); - private final Set usedVirtualAliases = new HashSet<>(); - @Override - public String getFieldAlias(FieldReference field) { - String result; - do { - result = RenderingUtil.indexToId(lastVirtual++, startVirtualLetters); - } while (!usedVirtualAliases.add(result) || RenderingUtil.KEYWORDS.contains(result)); - return result; + public MinifyingAliasProvider(int maxTopLevelNames) { + this.maxTopLevelNames = maxTopLevelNames; } @Override - public String getStaticFieldAlias(FieldReference field) { + public String getFieldAlias(FieldReference field) { + return createInstanceName(); + } + + @Override + public ScopedName getStaticFieldAlias(FieldReference field) { return createTopLevelName(); } @Override - public String getStaticMethodAlias(MethodReference method) { + public ScopedName getStaticMethodAlias(MethodReference method) { return createTopLevelName(); } @Override public String getMethodAlias(MethodDescriptor method) { - String result; - do { - result = RenderingUtil.indexToId(lastVirtual++, startVirtualLetters); - } while (!usedVirtualAliases.add(result) || RenderingUtil.KEYWORDS.contains(result)); - return result; + return createInstanceName(); } @Override - public String getClassAlias(String className) { + public ScopedName getClassAlias(String className) { return createTopLevelName(); } @Override - public String getFunctionAlias(String className) { - return RenderingUtil.indexToId(lastSuffix++, startLetters); + public ScopedName getFunctionAlias(String className) { + return createTopLevelName(); } @Override - public String getClassInitAlias(String className) { + public ScopedName getClassInitAlias(String className) { return createTopLevelName(); } + @Override + public String getAdditionalScopeName() { + return createTopLevelName().name; + } + @Override public void reserveName(String name) { usedAliases.add(name); } - private String createTopLevelName() { + private ScopedName createTopLevelName() { + if (!additionalScopeStarted && topLevelNames >= maxTopLevelNames) { + additionalScopeStarted = true; + lastSuffix = 0; + } String result; do { result = RenderingUtil.indexToId(lastSuffix++, startLetters); - } while (!usedAliases.add(result) || RenderingUtil.KEYWORDS.contains(result)); + } while ((!additionalScopeStarted && usedAliases.contains(result)) || RenderingUtil.KEYWORDS.contains(result)); + ++topLevelNames; + return new ScopedName(result, additionalScopeStarted); + } + + private String createInstanceName() { + String result; + do { + result = RenderingUtil.indexToId(lastInstanceSuffix++, startVirtualLetters); + } while (RenderingUtil.KEYWORDS.contains(result)); return result; } } diff --git a/core/src/main/java/org/teavm/backend/javascript/codegen/NamingStrategy.java b/core/src/main/java/org/teavm/backend/javascript/codegen/NamingStrategy.java index 14dc4ed30..606cec13d 100644 --- a/core/src/main/java/org/teavm/backend/javascript/codegen/NamingStrategy.java +++ b/core/src/main/java/org/teavm/backend/javascript/codegen/NamingStrategy.java @@ -20,21 +20,23 @@ import org.teavm.model.MethodDescriptor; import org.teavm.model.MethodReference; public interface NamingStrategy { - String getNameFor(String cls); + ScopedName className(String cls); - String getNameFor(MethodDescriptor method); + String instanceMethodName(MethodDescriptor method); - String getNameForInit(MethodReference method); + ScopedName initializerName(MethodReference method); - String getFullNameFor(MethodReference method); + ScopedName methodName(MethodReference method); - String getNameFor(FieldReference field); + String instanceFieldName(FieldReference field); - String getFullNameFor(FieldReference method); + ScopedName fieldName(FieldReference method); - String getNameForFunction(String name); + ScopedName functionName(String name); - String getNameForClassInit(String className); + ScopedName classInitializerName(String className); + + String additionalScopeName(); void reserveName(String name); } 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 1bbdcc005..ed6ad0605 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 @@ -29,6 +29,7 @@ import org.teavm.model.MethodDescriptor; import org.teavm.model.MethodReference; public class OutputSourceWriter extends SourceWriter implements LocationProvider { + private static final int LET_SEQUENCE_LIMIT = 50; private final Appendable innerWriter; private int indentSize; private final NamingStrategy naming; @@ -45,6 +46,9 @@ public class OutputSourceWriter extends SourceWriter implements LocationProvider private int sectionMarkSection = -1; private int sectionMarkPos; private IntIntMap sectionSizes = new IntIntHashMap(); + private DeclarationType currentDeclarationType; + private boolean expectingDeclarationName; + private int letSequenceSize; OutputSourceWriter(NamingStrategy naming, Appendable innerWriter, int lineWidth) { this.naming = naming; @@ -61,9 +65,31 @@ public class OutputSourceWriter extends SourceWriter implements LocationProvider this.minified = minified; } + public void finish() { + finishLet(); + } + + private void finishLetImplicitly() { + if (currentDeclarationType == null) { + finishLet(); + } + } + + private void finishLet() { + if (letSequenceSize > 0) { + letSequenceSize = 0; + try { + innerWriter.append(";\n"); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + @Override public SourceWriter append(char value) { appendIndent(); + finishLetImplicitly(); try { innerWriter.append(value); } catch (IOException e) { @@ -96,6 +122,7 @@ public class OutputSourceWriter extends SourceWriter implements LocationProvider if (start == end) { return; } + finishLetImplicitly(); appendIndent(); column += end - start; offset += end - start; @@ -108,32 +135,32 @@ public class OutputSourceWriter extends SourceWriter implements LocationProvider @Override public SourceWriter appendClass(String cls) { - return appendName(naming.getNameFor(cls)); + return appendDeclaration(naming.className(cls)); } @Override public SourceWriter appendField(FieldReference field) { - return append(naming.getNameFor(field)); + return append(naming.instanceFieldName(field)); } @Override public SourceWriter appendStaticField(FieldReference field) { - return appendName(naming.getFullNameFor(field)); + return appendDeclaration(naming.fieldName(field)); } @Override - public SourceWriter appendMethod(MethodDescriptor method) { - return append(naming.getNameFor(method)); + public SourceWriter appendVirtualMethod(MethodDescriptor method) { + return append(naming.instanceMethodName(method)); } @Override - public SourceWriter appendMethodBody(MethodReference method) { - return appendName(naming.getFullNameFor(method)); + public SourceWriter appendMethod(MethodReference method) { + return appendDeclaration(naming.methodName(method)); } @Override public SourceWriter appendFunction(String name) { - return append(naming.getNameForFunction(name)); + return appendDeclaration(naming.functionName(name)); } @Override @@ -143,16 +170,117 @@ public class OutputSourceWriter extends SourceWriter implements LocationProvider @Override public SourceWriter appendInit(MethodReference method) { - return appendName(naming.getNameForInit(method)); + return appendDeclaration(naming.initializerName(method)); } @Override public SourceWriter appendClassInit(String className) { - return appendName(naming.getNameForClassInit(className)); + return appendDeclaration(naming.classInitializerName(className)); } - private SourceWriter appendName(String name) { - append(name); + @Override + public SourceWriter startVariableDeclaration() { + checkStartDeclaration(); + currentDeclarationType = DeclarationType.VARIABLE; + expectingDeclarationName = true; + return this; + } + + @Override + public SourceWriter startFunctionDeclaration() { + checkStartDeclaration(); + currentDeclarationType = DeclarationType.FUNCTION; + expectingDeclarationName = true; + return this; + } + + @Override + public SourceWriter declareVariable() { + checkStartDeclaration(); + currentDeclarationType = DeclarationType.VARIABLE_WITHOUT_VALUE; + expectingDeclarationName = true; + return this; + } + + private void checkStartDeclaration() { + if (currentDeclarationType != null) { + throw new IllegalStateException(); + } + } + + @Override + public SourceWriter endDeclaration() { + if (currentDeclarationType == null || expectingDeclarationName) { + throw new IllegalStateException(); + } + switch (currentDeclarationType) { + case FUNCTION: + newLine(); + break; + case VARIABLE: + if (letSequenceSize == 0) { + append(';').softNewLine(); + } else if (letSequenceSize >= LET_SEQUENCE_LIMIT) { + finishLet(); + } + break; + case VARIABLE_WITHOUT_VALUE: + throw new IllegalStateException(); + } + currentDeclarationType = null; + return this; + } + + private SourceWriter appendDeclaration(ScopedName name) { + if (!expectingDeclarationName) { + return appendName(name); + } + expectingDeclarationName = false; + switch (currentDeclarationType) { + case FUNCTION: + finishLet(); + if (name.scoped) { + append(naming.additionalScopeName()).append('.').append(name.name).ws() + .append('=').ws().append("function"); + } else { + append("function ").append(name.name); + } + break; + case VARIABLE: + if (name.scoped) { + finishLet(); + append(naming.additionalScopeName()).append('.'); + } else { + if (letSequenceSize++ == 0) { + append("let "); + } else { + append(',').softNewLine(); + } + } + append(name.name).ws().append('=').ws(); + break; + case VARIABLE_WITHOUT_VALUE: + if (!name.scoped) { + if (letSequenceSize++ == 0) { + append("let "); + } else { + append(',').softNewLine(); + } + append(name.name); + } + expectingDeclarationName = false; + currentDeclarationType = null; + break; + } + return this; + } + + private SourceWriter appendName(ScopedName name) { + if (name.scoped) { + append(naming.additionalScopeName()); + append('.'); + } + append(name.name); return this; } @@ -160,6 +288,7 @@ public class OutputSourceWriter extends SourceWriter implements LocationProvider if (minified) { return; } + finishLetImplicitly(); if (lineStart) { try { for (int i = 0; i < indentSize; ++i) { @@ -176,6 +305,7 @@ public class OutputSourceWriter extends SourceWriter implements LocationProvider @Override public SourceWriter newLine() { + finishLetImplicitly(); try { innerWriter.append('\n'); } catch (IOException e) { @@ -194,6 +324,7 @@ public class OutputSourceWriter extends SourceWriter implements LocationProvider newLine(); } else { if (!minified) { + finishLetImplicitly(); try { innerWriter.append(' '); } catch (IOException e) { @@ -209,6 +340,7 @@ public class OutputSourceWriter extends SourceWriter implements LocationProvider @Override public SourceWriter sameLineWs() { if (!minified) { + finishLetImplicitly(); try { innerWriter.append(' '); } catch (IOException e) { @@ -231,6 +363,7 @@ public class OutputSourceWriter extends SourceWriter implements LocationProvider @Override public SourceWriter softNewLine() { if (!minified) { + finishLetImplicitly(); try { innerWriter.append('\n'); } catch (IOException e) { @@ -362,4 +495,10 @@ public class OutputSourceWriter extends SourceWriter implements LocationProvider public int getSectionSize(int sectionId) { return sectionSizes.get(sectionId); } + + private enum DeclarationType { + FUNCTION, + VARIABLE, + VARIABLE_WITHOUT_VALUE + } } 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 57bcf0ff3..2b9506516 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 @@ -88,14 +88,14 @@ public class RememberedSource implements SourceFragment { case RememberingSourceWriter.METHOD: if ((filter & FILTER_REF) != 0) { - sink.appendMethod(methodDescriptors[intArgs[intArgIndex]]); + sink.appendVirtualMethod(methodDescriptors[intArgs[intArgIndex]]); } intArgIndex++; break; case RememberingSourceWriter.METHOD_BODY: if ((filter & FILTER_REF) != 0) { - sink.appendMethodBody(methods[intArgs[intArgIndex]]); + sink.appendMethod(methods[intArgs[intArgIndex]]); } intArgIndex++; break; @@ -170,6 +170,27 @@ public class RememberedSource implements SourceFragment { } break; + case RememberingSourceWriter.START_FUNCTION: + if ((filter & FILTER_TEXT) != 0) { + sink.startFunctionDeclaration(); + } + break; + case RememberingSourceWriter.START_VARIABLE: + if ((filter & FILTER_TEXT) != 0) { + sink.startVariableDeclaration(); + } + break; + case RememberingSourceWriter.END_DECLARATION: + if ((filter & FILTER_TEXT) != 0) { + sink.endDeclaration(); + } + break; + case RememberingSourceWriter.DECLARE_VARIABLE: + if ((filter & FILTER_TEXT) != 0) { + sink.declareVariable(); + } + break; + case RememberingSourceWriter.EMIT_LOCATION: if ((filter & FILTER_DEBUG) != 0) { var fileIndex = intArgs[intArgIndex]; 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 dd081e68f..e64a0e1a9 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 @@ -42,6 +42,10 @@ public class RememberingSourceWriter extends SourceWriter { static final byte SOFT_NEW_LINE = 12; static final byte INDENT = 13; static final byte OUTDENT = 14; + static final byte START_FUNCTION = 27; + static final byte START_VARIABLE = 28; + static final byte END_DECLARATION = 29; + static final byte DECLARE_VARIABLE = 30; static final byte EMIT_LOCATION = 15; static final byte ENTER_LOCATION = 16; static final byte EXIT_LOCATION = 17; @@ -73,6 +77,8 @@ public class RememberingSourceWriter extends SourceWriter { private List methods = new ArrayList<>(); private ObjectIntMap methodIndexes = new ObjectIntHashMap<>(); + private boolean isInDeclaration; + public RememberingSourceWriter(boolean debug) { this.debug = debug; } @@ -129,7 +135,7 @@ public class RememberingSourceWriter extends SourceWriter { } @Override - public SourceWriter appendMethod(MethodDescriptor method) { + public SourceWriter appendVirtualMethod(MethodDescriptor method) { flush(); commands.add(METHOD); appendMethodDescriptorArg(method); @@ -137,7 +143,7 @@ public class RememberingSourceWriter extends SourceWriter { } @Override - public SourceWriter appendMethodBody(MethodReference method) { + public SourceWriter appendMethod(MethodReference method) { flush(); commands.add(METHOD_BODY); appendMethodArg(method); @@ -225,6 +231,49 @@ public class RememberingSourceWriter extends SourceWriter { return this; } + @Override + public SourceWriter startFunctionDeclaration() { + checkNotInDeclaration(); + isInDeclaration = true; + flush(); + commands.add(START_FUNCTION); + return this; + } + + @Override + public SourceWriter startVariableDeclaration() { + checkNotInDeclaration(); + isInDeclaration = true; + flush(); + commands.add(START_VARIABLE); + return this; + } + + private void checkNotInDeclaration() { + if (isInDeclaration) { + throw new IllegalStateException(); + } + } + + @Override + public SourceWriter endDeclaration() { + if (!isInDeclaration) { + throw new IllegalStateException(); + } + isInDeclaration = false; + flush(); + commands.add(END_DECLARATION); + return this; + } + + @Override + public SourceWriter declareVariable() { + checkNotInDeclaration(); + flush(); + commands.add(DECLARE_VARIABLE); + return this; + } + @Override public SourceWriter emitLocation(String fileName, int line) { if (debug) { diff --git a/core/src/main/java/org/teavm/backend/javascript/codegen/ScopedName.java b/core/src/main/java/org/teavm/backend/javascript/codegen/ScopedName.java new file mode 100644 index 000000000..ff9689b2e --- /dev/null +++ b/core/src/main/java/org/teavm/backend/javascript/codegen/ScopedName.java @@ -0,0 +1,26 @@ +/* + * Copyright 2024 konsoletyper. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.backend.javascript.codegen; + +public class ScopedName { + public final String name; + public final boolean scoped; + + public ScopedName(String name, boolean scoped) { + this.name = name; + this.scoped = scoped; + } +} 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 d1f1b5068..e9ae7bd35 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 @@ -76,26 +76,38 @@ public abstract class SourceWriter implements Appendable, SourceWriterSink { public abstract SourceWriter appendStaticField(FieldReference field); @Override - public abstract SourceWriter appendMethod(MethodDescriptor method); + public abstract SourceWriter appendVirtualMethod(MethodDescriptor method); - public SourceWriter appendMethod(String name, Class... params) { - return appendMethod(new MethodDescriptor(name, params)); + public SourceWriter appendVirtualMethod(String name, Class... params) { + return appendVirtualMethod(new MethodDescriptor(name, params)); } @Override - public abstract SourceWriter appendMethodBody(MethodReference method); + public abstract SourceWriter appendMethod(MethodReference method); - public SourceWriter appendMethodBody(String className, String name, ValueType... params) { - return appendMethodBody(new MethodReference(className, new MethodDescriptor(name, params))); + public SourceWriter appendMethod(String className, String name, ValueType... params) { + return appendMethod(new MethodReference(className, new MethodDescriptor(name, params))); } - public SourceWriter appendMethodBody(Class cls, String name, Class... params) { - return appendMethodBody(new MethodReference(cls, name, params)); + public SourceWriter appendMethod(Class cls, String name, Class... params) { + return appendMethod(new MethodReference(cls, name, params)); } @Override public abstract SourceWriter appendFunction(String name); + @Override + public abstract SourceWriter startFunctionDeclaration(); + + @Override + public abstract SourceWriter startVariableDeclaration(); + + @Override + public abstract SourceWriter endDeclaration(); + + @Override + public abstract SourceWriter declareVariable(); + @Override public abstract SourceWriter appendGlobal(String name); 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 72356b847..539bfb4be 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 @@ -36,11 +36,27 @@ public interface SourceWriterSink { return this; } - default SourceWriterSink appendMethod(MethodDescriptor method) { + default SourceWriterSink appendVirtualMethod(MethodDescriptor method) { return this; } - default SourceWriterSink appendMethodBody(MethodReference method) { + default SourceWriterSink appendMethod(MethodReference method) { + return this; + } + + default SourceWriterSink startVariableDeclaration() { + return this; + } + + default SourceWriterSink startFunctionDeclaration() { + return this; + } + + default SourceWriterSink endDeclaration() { + return this; + } + + default SourceWriterSink declareVariable() { return this; } diff --git a/core/src/main/java/org/teavm/backend/javascript/rendering/AstWriter.java b/core/src/main/java/org/teavm/backend/javascript/rendering/AstWriter.java index 1f9ad1841..acfc85032 100644 --- a/core/src/main/java/org/teavm/backend/javascript/rendering/AstWriter.java +++ b/core/src/main/java/org/teavm/backend/javascript/rendering/AstWriter.java @@ -98,12 +98,18 @@ public class AstWriter { private Set aliases = new HashSet<>(); private Function globalNameWriter; public final Map currentScopes = new HashMap<>(); + protected final Set topLevelScopes = new HashSet<>(); + private boolean inFunction; public AstWriter(SourceWriter writer, Function globalNameWriter) { this.writer = writer; this.globalNameWriter = globalNameWriter; } + protected final boolean inFunction() { + return inFunction; + } + public void declareName(String name) { if (nameMap.containsKey(name)) { return; @@ -125,10 +131,6 @@ public class AstWriter { nameMap.put(name, emitter); } - public void hoist(Object node) { - hoist((AstNode) node); - } - public void hoist(AstNode node) { declareName("arguments"); node.visit(n -> { @@ -162,11 +164,11 @@ public class AstWriter { print((AstNode) node, precedence); } - public void print(AstNode node) { - print(node, PRECEDENCE_COMMA); + public boolean print(AstNode node) { + return print(node, PRECEDENCE_COMMA); } - public void print(AstNode node, int precedence) { + public boolean print(AstNode node, int precedence) { switch (node.getType()) { case Token.SCRIPT: print((AstRoot) node); @@ -176,8 +178,7 @@ public class AstWriter { print((FunctionCall) node, precedence); break; case Token.FUNCTION: - print((FunctionNode) node); - break; + return print((FunctionNode) node); case Token.ARRAYCOMP: print((ArrayComprehension) node); break; @@ -287,8 +288,7 @@ public class AstWriter { case Token.CONST: case Token.VAR: case Token.LET: - print((VariableDeclaration) node); - break; + return print((VariableDeclaration) node); case Token.WHILE: print((WhileLoop) node); break; @@ -302,20 +302,23 @@ public class AstWriter { } break; } + return false; } private void print(AstRoot node) { for (Node child : node) { - print((AstNode) child); - writer.softNewLine(); + if (!print((AstNode) child)) { + writer.softNewLine(); + } } } private void print(Block node) { writer.append('{').softNewLine().indent(); for (Node child = node.getFirstChild(); child != null; child = child.getNext()) { - print((AstNode) child); - writer.softNewLine(); + if (!print((AstNode) child)) { + writer.softNewLine(); + } } writer.outdent().append('}'); } @@ -324,8 +327,9 @@ public class AstWriter { var scope = enterScope(node); writer.append('{').softNewLine().indent(); for (Node child = node.getFirstChild(); child != null; child = child.getNext()) { - print((AstNode) child); - writer.softNewLine(); + if (!print((AstNode) child)) { + writer.softNewLine(); + } } writer.outdent().append('}'); leaveScope(scope, node); @@ -456,7 +460,7 @@ public class AstWriter { print(node.getTryBlock()); for (CatchClause cc : node.getCatchClauses()) { writer.ws().append("catch").ws().append('('); - print(cc.getVarName()); + writer.append(cc.getVarName().getIdentifier()); if (cc.getCatchCondition() != null) { writer.append(" if "); print(cc.getCatchCondition()); @@ -470,7 +474,15 @@ public class AstWriter { } } - private void print(VariableDeclaration node) { + private boolean print(VariableDeclaration node) { + if (isTopLevel() && node.getVariables().get(0).getTarget() instanceof Name) { + var name = (Name) node.getVariables().get(0).getTarget(); + var definingScope = scopeOfId(name.getIdentifier()); + if (definingScope == null || topLevelScopes.contains(definingScope)) { + printTopLevel(node); + return true; + } + } switch (node.getType()) { case Token.VAR: writer.append("var "); @@ -492,6 +504,23 @@ public class AstWriter { if (node.isStatement()) { writer.append(';'); } + return false; + } + + private void printTopLevel(VariableDeclaration node) { + for (var variable : node.getVariables()) { + var target = variable.getTarget(); + if (target instanceof Name) { + var id = ((Name) target).getIdentifier(); + if (variable.getInitializer() != null) { + writer.startVariableDeclaration().appendFunction(id); + print(variable.getInitializer()); + writer.endDeclaration(); + } else { + writer.declareVariable().appendFunction(id); + } + } + } } private void print(VariableInitializer node) { @@ -575,7 +604,7 @@ public class AstWriter { return false; } - writer.appendMethodBody(method).append('('); + writer.appendMethod(method).append('('); printList(node.getArguments()); writer.append(')'); return true; @@ -722,9 +751,18 @@ public class AstWriter { print(node.getRight()); } - protected void print(FunctionNode node) { - var scope = enterScope(node); + protected boolean print(FunctionNode node) { var isArrow = node.getFunctionType() == FunctionNode.ARROW_FUNCTION; + if (isTopLevel() && !isArrow && node.getFunctionName() != null) { + var definingScope = scopeOfId(node.getFunctionName().getIdentifier()); + if (definingScope == null || topLevelScopes.contains(definingScope)) { + printTopLevel(node); + return true; + } + } + var wasInFunction = inFunction; + inFunction = true; + var scope = enterScope(node); if (!isArrow) { currentScopes.put("arguments", node); } @@ -760,6 +798,23 @@ public class AstWriter { } leaveScope(scope, node); + inFunction = wasInFunction; + return false; + } + + private void printTopLevel(FunctionNode node) { + var wasInFunction = inFunction; + inFunction = true; + var scope = enterScope(node); + currentScopes.put("arguments", node); + writer.startFunctionDeclaration().appendFunction(node.getFunctionName().getIdentifier()); + writer.append('('); + printList(node.getParams()); + writer.append(')').ws(); + print(node.getBody()); + writer.endDeclaration(); + leaveScope(scope, node); + inFunction = wasInFunction; } private void print(LetNode node) { @@ -980,6 +1035,9 @@ public class AstWriter { } protected void onEnterScope(Scope scope) { + if (isTopLevel() && !inFunction()) { + topLevelScopes.add(scope); + } } private void leaveScope(Map backup, Scope scope) { @@ -994,9 +1052,16 @@ public class AstWriter { } protected void onLeaveScope(Scope scope) { + if (isTopLevel() && !inFunction()) { + topLevelScopes.remove(scope); + } } protected Scope scopeOfId(String id) { return currentScopes.get(id); } + + protected boolean isTopLevel() { + return false; + } } 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 facccfab8..0c48aba77 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 @@ -155,7 +155,7 @@ public class MethodBodyRenderer implements MethodNodeVisitor, GeneratorContext { statementRenderer.setCurrentPart(0); if (method.getModifiers().contains(ElementModifier.SYNCHRONIZED)) { - writer.appendMethodBody(NameFrequencyEstimator.MONITOR_ENTER_SYNC_METHOD); + writer.appendMethod(NameFrequencyEstimator.MONITOR_ENTER_SYNC_METHOD); writer.append("("); appendMonitor(statementRenderer, method); writer.append(");").softNewLine(); @@ -168,7 +168,7 @@ public class MethodBodyRenderer implements MethodNodeVisitor, GeneratorContext { if (method.getModifiers().contains(ElementModifier.SYNCHRONIZED)) { writer.outdent().append("}").ws().append("finally").ws().append("{").indent().softNewLine(); - writer.appendMethodBody(NameFrequencyEstimator.MONITOR_EXIT_SYNC_METHOD); + writer.appendMethod(NameFrequencyEstimator.MONITOR_EXIT_SYNC_METHOD); writer.append("("); appendMonitor(statementRenderer, method); writer.append(");").softNewLine(); @@ -242,7 +242,7 @@ public class MethodBodyRenderer implements MethodNodeVisitor, GeneratorContext { for (int i = 0; i < methodNode.getBody().size(); ++i) { writer.append("case ").append(i).append(":").indent().softNewLine(); if (i == 0 && methodNode.getModifiers().contains(ElementModifier.SYNCHRONIZED)) { - writer.appendMethodBody(NameFrequencyEstimator.MONITOR_ENTER_METHOD); + writer.appendMethod(NameFrequencyEstimator.MONITOR_ENTER_METHOD); writer.append("("); appendMonitor(statementRenderer, methodNode); writer.append(");").softNewLine(); @@ -260,7 +260,7 @@ public class MethodBodyRenderer implements MethodNodeVisitor, GeneratorContext { writer.outdent().append("}").ws().append("finally").ws().append('{').indent().softNewLine(); writer.append("if").ws().append("(!").appendFunction("$rt_suspending").append("())") .ws().append("{").indent().softNewLine(); - writer.appendMethodBody(NameFrequencyEstimator.MONITOR_EXIT_METHOD).append("("); + writer.appendMethod(NameFrequencyEstimator.MONITOR_EXIT_METHOD).append("("); appendMonitor(statementRenderer, methodNode); writer.append(");").softNewLine(); writer.outdent().append('}').softNewLine(); 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 f10f5c616..9696aaee9 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 @@ -38,6 +38,11 @@ public class NameFrequencyEstimator implements SourceWriterSink { private Map entries = new HashMap<>(); private Set reservedNames = new HashSet<>(); + private boolean hasAdditionalScope; + + public boolean hasAdditionalScope() { + return hasAdditionalScope; + } @Override public SourceWriterSink appendClass(String cls) { @@ -45,7 +50,7 @@ public class NameFrequencyEstimator implements SourceWriterSink { var entry = entries.get(key); if (entry == null) { entry = new Entry(); - entry.operation = naming -> naming.getNameFor(cls); + entry.operation = naming -> naming.className(cls).scoped; entries.put(key, entry); } entry.frequency++; @@ -58,7 +63,10 @@ public class NameFrequencyEstimator implements SourceWriterSink { var entry = entries.get(key); if (entry == null) { entry = new Entry(); - entry.operation = naming -> naming.getNameFor(field); + entry.operation = naming -> { + naming.instanceFieldName(field); + return false; + }; entries.put(key, entry); } entry.frequency++; @@ -71,7 +79,7 @@ public class NameFrequencyEstimator implements SourceWriterSink { var entry = entries.get(key); if (entry == null) { entry = new Entry(); - entry.operation = naming -> naming.getFullNameFor(field); + entry.operation = naming -> naming.fieldName(field).scoped; entries.put(key, entry); } entry.frequency++; @@ -79,12 +87,15 @@ public class NameFrequencyEstimator implements SourceWriterSink { } @Override - public SourceWriterSink appendMethod(MethodDescriptor method) { + public SourceWriterSink appendVirtualMethod(MethodDescriptor method) { var key = "r:" + method; var entry = entries.get(key); if (entry == null) { entry = new Entry(); - entry.operation = naming -> naming.getNameFor(method); + entry.operation = naming -> { + naming.instanceMethodName(method); + return false; + }; entries.put(key, entry); } entry.frequency++; @@ -92,12 +103,12 @@ public class NameFrequencyEstimator implements SourceWriterSink { } @Override - public SourceWriterSink appendMethodBody(MethodReference method) { + public SourceWriterSink appendMethod(MethodReference method) { var key = "R:" + method; var entry = entries.get(key); if (entry == null) { entry = new Entry(); - entry.operation = naming -> naming.getFullNameFor(method); + entry.operation = naming -> naming.methodName(method).scoped; entries.put(key, entry); } entry.frequency++; @@ -110,7 +121,7 @@ public class NameFrequencyEstimator implements SourceWriterSink { var entry = entries.get(key); if (entry == null) { entry = new Entry(); - entry.operation = naming -> naming.getNameForFunction(name); + entry.operation = naming -> naming.functionName(name).scoped; entries.put(key, entry); } entry.frequency++; @@ -129,7 +140,7 @@ public class NameFrequencyEstimator implements SourceWriterSink { var entry = entries.get(key); if (entry == null) { entry = new Entry(); - entry.operation = naming -> naming.getNameForInit(method); + entry.operation = naming -> naming.initializerName(method).scoped; entries.put(key, entry); } entry.frequency++; @@ -142,7 +153,7 @@ public class NameFrequencyEstimator implements SourceWriterSink { var entry = entries.get(key); if (entry == null) { entry = new Entry(); - entry.operation = naming -> naming.getNameForClassInit(className); + entry.operation = naming -> naming.classInitializerName(className).scoped; entries.put(key, entry); } entry.frequency++; @@ -156,7 +167,7 @@ public class NameFrequencyEstimator implements SourceWriterSink { var entryList = new ArrayList<>(entries.values()); entryList.sort((o1, o2) -> Integer.compare(o2.frequency, o1.frequency)); for (var entry : entryList) { - entry.operation.perform(naming); + hasAdditionalScope |= entry.operation.perform(naming); } } @@ -166,6 +177,6 @@ public class NameFrequencyEstimator implements SourceWriterSink { } private interface NamingOperation { - void perform(NamingStrategy naming); + boolean 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 e265df5ea..bd60225bc 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 @@ -188,7 +188,7 @@ public class Renderer implements RenderingManager { writer.appendClass("java.lang.Object").append(".prototype.toString").ws().append("=").ws() .append("function()").ws().append("{").indent().softNewLine(); writer.append("return ").appendFunction("$rt_ustr").append("(") - .appendMethodBody(Object.class, "toString", String.class).append("(this));") + .appendMethod(Object.class, "toString", String.class).append("(this));") .softNewLine(); writer.outdent().append("};").newLine(); } @@ -265,7 +265,6 @@ public class Renderer implements RenderingManager { renderFullClassFunctionDeclaration(cls, nonStaticFields); } - var hasLet = false; for (FieldHolder field : staticFields) { Object value = field.getInitialValue(); if (value == null) { @@ -278,23 +277,16 @@ public class Renderer implements RenderingManager { value = null; } - if (!hasLet) { - writer.append("let "); - hasLet = true; - } else { - writer.append(",").ws(); - } - writer.appendStaticField(fieldRef).ws().append("=").ws(); + writer.startVariableDeclaration().appendStaticField(fieldRef); context.constantToString(writer, value); - } - if (hasLet) { - writer.append(";").newLine(); + writer.endDeclaration(); } } private void renderFullClassFunctionDeclaration(ClassReader cls, List nonStaticFields) { boolean thisAliased = false; - writer.append("function ").appendClass(cls.getName()).append("()").ws().append("{").indent().softNewLine(); + writer.startFunctionDeclaration().appendClass(cls.getName()).append("()").ws().append("{") + .indent().softNewLine(); if (nonStaticFields.size() > 1) { thisAliased = true; writer.append("let a").ws().append("=").ws().append("this;").ws(); @@ -321,18 +313,18 @@ public class Renderer implements RenderingManager { } writer.outdent().append("}"); - writer.newLine(); + writer.endDeclaration(); } private void renderShortClassFunctionDeclaration(ClassReader cls) { - writer.append("let ").appendClass(cls.getName()).ws().append("=").ws() + writer.startVariableDeclaration().appendClass(cls.getName()) .appendFunction("$rt_classWithoutFields").append("("); if (cls.hasModifier(ElementModifier.INTERFACE)) { writer.append("0"); } else if (!cls.getParent().equals("java.lang.Object")) { writer.appendClass(cls.getParent()); } - writer.append(");").newLine(); + writer.append(")").endDeclaration(); } private void renderMethodBodies(ClassHolder cls, Decompiler decompiler) { @@ -346,27 +338,19 @@ public class Renderer implements RenderingManager { var needsInitializers = !cls.hasModifier(ElementModifier.INTERFACE) && !cls.hasModifier(ElementModifier.ABSTRACT); - var hasLet = false; for (var method : cls.getMethods()) { if (!filterMethod(method)) { continue; } - if (!hasLet) { - writer.append("let "); - hasLet = true; - } else { - writer.append(",").newLine(); - } + writer.startVariableDeclaration(); renderBody(method, decompiler); + writer.endDeclaration(); if (needsInitializers && !method.hasModifier(ElementModifier.STATIC) && method.getName().equals("")) { - writer.append(",").newLine(); renderInitializer(method); } } - if (hasLet) { - writer.append(";").newLine(); - } + writer.emitClass(null); } @@ -390,11 +374,10 @@ public class Renderer implements RenderingManager { var clinitCalledField = new FieldReference(cls.getName(), "$_teavm_clinitCalled_$"); if (isAsync) { - writer.append("let ").appendStaticField(clinitCalledField).ws().append("=").ws().append("false;") - .softNewLine(); + writer.startVariableDeclaration().appendStaticField(clinitCalledField).append("false").endDeclaration(); } - writer.append("let ").appendClassInit(cls.getName()).ws().append("=").ws(); + writer.startVariableDeclaration().appendClassInit(cls.getName()); writer.append("()").sameLineWs().append("=>").ws().append("{").softNewLine().indent(); if (isAsync) { @@ -423,7 +406,7 @@ public class Renderer implements RenderingManager { writer.outdent().append("case 1:").indent().softNewLine(); } - writer.appendMethodBody(new MethodReference(cls.getName(), clinit.getDescriptor())) + writer.appendMethod(new MethodReference(cls.getName(), clinit.getDescriptor())) .append("();").softNewLine(); if (isAsync) { @@ -438,8 +421,7 @@ public class Renderer implements RenderingManager { writer.appendFunction("$rt_nativeThread").append("().push(" + context.pointerName() + ");").softNewLine(); } - writer.outdent().append("};"); - writer.newLine(); + writer.outdent().append("}").endDeclaration(); } private void renderEraseClinit(ClassReader cls) { @@ -696,7 +678,7 @@ public class Renderer implements RenderingManager { private void renderInitializer(MethodReader method) { MethodReference ref = method.getReference(); writer.emitMethod(ref.getDescriptor()); - writer.appendInit(ref).ws().append("=").ws(); + writer.startVariableDeclaration().appendInit(ref); if (ref.parameterCount() != 1) { writer.append("("); } @@ -714,14 +696,14 @@ public class Renderer implements RenderingManager { String instanceName = variableNameForInitializer(ref.parameterCount()); writer.append("let " + instanceName).ws().append("=").ws().append("new ").appendClass( ref.getClassName()).append("();").softNewLine(); - writer.appendMethodBody(ref).append("(" + instanceName); + writer.appendMethod(ref).append("(" + instanceName); for (int i = 0; i < ref.parameterCount(); ++i) { writer.append(",").ws(); writer.append(variableNameForInitializer(i)); } writer.append(");").softNewLine(); writer.append("return " + instanceName + ";").softNewLine(); - writer.outdent().append("}"); + writer.outdent().append("}").endDeclaration(); writer.emitMethod(null); } @@ -753,7 +735,7 @@ public class Renderer implements RenderingManager { } private void emitVirtualDeclaration(MethodReference ref) { - String methodName = context.getNaming().getNameFor(ref.getDescriptor()); + String methodName = context.getNaming().instanceMethodName(ref.getDescriptor()); writer.append("\"").append(methodName).append("\""); writer.append(",").ws(); emitVirtualFunctionWrapper(ref); @@ -762,7 +744,7 @@ public class Renderer implements RenderingManager { private void emitVirtualFunctionWrapper(MethodReference method) { if (method.parameterCount() <= 4) { writer.appendFunction("$rt_wrapFunction" + method.parameterCount()); - writer.append("(").appendMethodBody(method).append(")"); + writer.append("(").appendMethod(method).append(")"); return; } @@ -781,7 +763,7 @@ public class Renderer implements RenderingManager { if (method.getDescriptor().getResultType() != ValueType.VOID) { writer.append("return "); } - writer.appendMethodBody(method).append("("); + writer.appendMethod(method).append("("); writer.append("this"); for (String arg : args) { writer.append(",").ws().append(arg); @@ -793,7 +775,7 @@ public class Renderer implements RenderingManager { MethodReference ref = method.getReference(); writer.emitMethod(ref.getDescriptor()); - writer.appendMethodBody(ref).ws().append("=").ws(); + writer.appendMethod(ref); if (method.hasModifier(ElementModifier.NATIVE)) { renderNativeBody(method, classSource); } else { diff --git a/core/src/main/java/org/teavm/backend/javascript/rendering/RuntimeRenderer.java b/core/src/main/java/org/teavm/backend/javascript/rendering/RuntimeRenderer.java index 42eb3e5ce..884bfb7e5 100644 --- a/core/src/main/java/org/teavm/backend/javascript/rendering/RuntimeRenderer.java +++ b/core/src/main/java/org/teavm/backend/javascript/rendering/RuntimeRenderer.java @@ -28,7 +28,6 @@ import org.mozilla.javascript.ast.AstRoot; import org.teavm.backend.javascript.codegen.SourceWriter; import org.teavm.backend.javascript.codegen.SourceWriterSink; import org.teavm.backend.javascript.templating.AstRemoval; -import org.teavm.backend.javascript.templating.LetJoiner; import org.teavm.backend.javascript.templating.RemovablePartsFinder; import org.teavm.backend.javascript.templating.TemplatingAstTransformer; import org.teavm.backend.javascript.templating.TemplatingAstWriter; @@ -69,13 +68,13 @@ public class RuntimeRenderer { public void renderRuntime() { for (var ast : runtimeAstParts) { - renderHandWrittenRuntime(ast); + renderRuntimePart(ast); } } public void renderEpilogue() { for (var ast : epilogueAstParts) { - renderHandWrittenRuntime(ast); + renderRuntimePart(ast); } } @@ -87,7 +86,7 @@ public class RuntimeRenderer { return ast; } - private void renderHandWrittenRuntime(AstRoot ast) { + private void renderRuntimePart(AstRoot ast) { var astWriter = new TemplatingAstWriter(writer, null, null, classInitializerInfo); astWriter.hoist(ast); astWriter.print(ast); @@ -118,14 +117,11 @@ public class RuntimeRenderer { public void removeUnusedParts() { var removal = new AstRemoval(removablePartsFinder.getAllRemovableParts()); - var letJoiner = new LetJoiner(); for (var part : runtimeAstParts) { removal.visit(part); - letJoiner.visit(part); } for (var part : epilogueAstParts) { removal.visit(part); - letJoiner.visit(part); } } } diff --git a/core/src/main/java/org/teavm/backend/javascript/rendering/StatementRenderer.java b/core/src/main/java/org/teavm/backend/javascript/rendering/StatementRenderer.java index 4b441f573..2862f3ddf 100644 --- a/core/src/main/java/org/teavm/backend/javascript/rendering/StatementRenderer.java +++ b/core/src/main/java/org/teavm/backend/javascript/rendering/StatementRenderer.java @@ -1100,10 +1100,10 @@ public class StatementRenderer implements ExprVisitor, StatementVisitor { expr.getArguments().get(0).acceptVisitor(this); } MethodReference method = expr.getMethod(); - String name = naming.getNameFor(method.getDescriptor()); + String name = naming.instanceMethodName(method.getDescriptor()); switch (expr.getType()) { case STATIC: - writer.appendMethodBody(method).append("("); + writer.appendMethod(method).append("("); for (int i = 0; i < expr.getArguments().size(); ++i) { if (i > 0) { writer.append(",").ws(); @@ -1113,7 +1113,7 @@ public class StatementRenderer implements ExprVisitor, StatementVisitor { } break; case SPECIAL: - writer.appendMethodBody(method).append("("); + writer.appendMethod(method).append("("); precedence = Precedence.min(); expr.getArguments().get(0).acceptVisitor(this); for (int i = 1; i < expr.getArguments().size(); ++i) { @@ -1507,13 +1507,13 @@ public class StatementRenderer implements ExprVisitor, StatementVisitor { @Override public void visit(MonitorEnterStatement statement) { if (async) { - writer.appendMethodBody(NameFrequencyEstimator.MONITOR_ENTER_METHOD).append("("); + writer.appendMethod(NameFrequencyEstimator.MONITOR_ENTER_METHOD).append("("); precedence = Precedence.min(); statement.getObjectRef().acceptVisitor(this); writer.append(");").softNewLine(); emitSuspendChecker(); } else { - writer.appendMethodBody(NameFrequencyEstimator.MONITOR_ENTER_SYNC_METHOD).append('('); + writer.appendMethod(NameFrequencyEstimator.MONITOR_ENTER_SYNC_METHOD).append('('); precedence = Precedence.min(); statement.getObjectRef().acceptVisitor(this); writer.append(");").softNewLine(); @@ -1530,12 +1530,12 @@ public class StatementRenderer implements ExprVisitor, StatementVisitor { @Override public void visit(MonitorExitStatement statement) { if (async) { - writer.appendMethodBody(NameFrequencyEstimator.MONITOR_EXIT_METHOD).append("("); + writer.appendMethod(NameFrequencyEstimator.MONITOR_EXIT_METHOD).append("("); precedence = Precedence.min(); statement.getObjectRef().acceptVisitor(this); writer.append(");").softNewLine(); } else { - writer.appendMethodBody(NameFrequencyEstimator.MONITOR_EXIT_SYNC_METHOD).append('('); + writer.appendMethod(NameFrequencyEstimator.MONITOR_EXIT_SYNC_METHOD).append('('); precedence = Precedence.min(); statement.getObjectRef().acceptVisitor(this); writer.append(");").softNewLine(); diff --git a/core/src/main/java/org/teavm/backend/javascript/templating/LetJoiner.java b/core/src/main/java/org/teavm/backend/javascript/templating/LetJoiner.java deleted file mode 100644 index 5fcb08c7f..000000000 --- a/core/src/main/java/org/teavm/backend/javascript/templating/LetJoiner.java +++ /dev/null @@ -1,70 +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.templating; - -import org.mozilla.javascript.ast.AstNode; -import org.mozilla.javascript.ast.AstRoot; -import org.mozilla.javascript.ast.Block; -import org.mozilla.javascript.ast.FunctionNode; -import org.mozilla.javascript.ast.Scope; -import org.mozilla.javascript.ast.VariableDeclaration; -import org.teavm.backend.javascript.ast.AstVisitor; - -public class LetJoiner extends AstVisitor { - @Override - public void visit(Block node) { - visitMany(node); - super.visit(node); - } - - @Override - public void visit(Scope node) { - visitMany(node); - super.visit(node); - } - - @Override - public void visit(AstRoot node) { - visitMany(node); - super.visit(node); - } - - @Override - public void visit(FunctionNode node) { - visitMany(node.getBody()); - super.visit(node); - } - - private void visitMany(AstNode node) { - VariableDeclaration previous = null; - for (var childNode = node.getFirstChild(); childNode != null;) { - var nextNode = childNode.getNext(); - var child = (AstNode) childNode; - if (child instanceof VariableDeclaration) { - var decl = (VariableDeclaration) childNode; - if (previous != null && previous.getType() == decl.getType()) { - previous.getVariables().addAll(decl.getVariables()); - node.removeChild(decl); - } else { - previous = decl; - } - } else { - previous = null; - } - childNode = nextNode; - } - } -} diff --git a/core/src/main/java/org/teavm/backend/javascript/templating/TemplatingAstWriter.java b/core/src/main/java/org/teavm/backend/javascript/templating/TemplatingAstWriter.java index f8abf10ae..5963fc841 100644 --- a/core/src/main/java/org/teavm/backend/javascript/templating/TemplatingAstWriter.java +++ b/core/src/main/java/org/teavm/backend/javascript/templating/TemplatingAstWriter.java @@ -16,9 +16,7 @@ package org.teavm.backend.javascript.templating; import java.util.HashMap; -import java.util.HashSet; import java.util.Map; -import java.util.Set; import org.mozilla.javascript.ast.ElementGet; import org.mozilla.javascript.ast.FunctionCall; import org.mozilla.javascript.ast.FunctionNode; @@ -39,8 +37,6 @@ public class TemplatingAstWriter extends AstWriter { private Scope scope; private Map fragments = new HashMap<>(); private ClassInitializerInfo classInitializerInfo; - private Set topLevelScopes = new HashSet<>(); - private boolean inFunction; public TemplatingAstWriter(SourceWriter writer, Map names, Scope scope, ClassInitializerInfo classInitializerInfo) { @@ -113,7 +109,7 @@ public class TemplatingAstWriter extends AstWriter { } var method = new MethodReference(((StringLiteral) classArg).getValue(), MethodDescriptor.parse(((StringLiteral) methodArg).getValue())); - writer.appendMethodBody(method); + writer.appendMethod(method); return true; } @@ -210,7 +206,7 @@ public class TemplatingAstWriter extends AstWriter { } var method = MethodDescriptor.parse(((StringLiteral) arg).getValue()); print(get.getTarget()); - writer.append('.').appendMethod(method); + writer.append('.').appendVirtualMethod(method); return true; } @@ -250,27 +246,7 @@ public class TemplatingAstWriter extends AstWriter { } @Override - protected void print(FunctionNode node) { - if (inFunction) { - super.print(node); - } else { - inFunction = true; - super.print(node); - inFunction = false; - } - } - - @Override - protected void onEnterScope(Scope scope) { - if (names == null && !inFunction) { - topLevelScopes.add(scope); - } - } - - @Override - protected void onLeaveScope(Scope scope) { - if (names == null && !inFunction) { - topLevelScopes.remove(scope); - } + protected boolean isTopLevel() { + return names == null; } } diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/JSAliasRenderer.java b/jso/impl/src/main/java/org/teavm/jso/impl/JSAliasRenderer.java index fa7e1dd17..26f86b7ba 100644 --- a/jso/impl/src/main/java/org/teavm/jso/impl/JSAliasRenderer.java +++ b/jso/impl/src/main/java/org/teavm/jso/impl/JSAliasRenderer.java @@ -50,8 +50,8 @@ class JSAliasRenderer implements RendererListener, VirtualMethodContributor { return; } - writer.append("let ").appendFunction("$rt_jso_marker").ws().append("=").ws() - .appendGlobal("Symbol").append("('jsoClass');").newLine(); + writer.startVariableDeclaration().appendFunction("$rt_jso_marker") + .appendGlobal("Symbol").append("('jsoClass')").endDeclaration(); writer.append("(function()").ws().append("{").softNewLine().indent(); writer.append("var c;").softNewLine(); for (String className : classSource.getClassNames()) { @@ -100,7 +100,7 @@ class JSAliasRenderer implements RendererListener, VirtualMethodContributor { } else { writer.append("c.").append(aliasEntry.getKey()); } - writer.ws().append("=").ws().append("c.").appendMethod(aliasEntry.getValue()) + writer.ws().append("=").ws().append("c.").appendVirtualMethod(aliasEntry.getValue()) .append(";").softNewLine(); } for (var aliasEntry : properties.entrySet()) { @@ -111,10 +111,10 @@ class JSAliasRenderer implements RendererListener, VirtualMethodContributor { writer.append("Object.defineProperty(c,") .ws().append("\"").append(aliasEntry.getKey()).append("\",") .ws().append("{").indent().softNewLine(); - writer.append("get:").ws().append("c.").appendMethod(propInfo.getter); + writer.append("get:").ws().append("c.").appendVirtualMethod(propInfo.getter); if (propInfo.setter != null && classReader.getMethod(propInfo.setter) != null) { writer.append(",").softNewLine(); - writer.append("set:").ws().append("c.").appendMethod(propInfo.setter); + writer.append("set:").ws().append("c.").appendVirtualMethod(propInfo.setter); } writer.softNewLine().outdent().append("});").softNewLine(); } @@ -198,7 +198,7 @@ class JSAliasRenderer implements RendererListener, VirtualMethodContributor { writer.append("this.").appendField(functorField).ws().append('=').ws().append("function("); appendArguments(functorMethod.parameterCount()); writer.append(")").ws().append('{').indent().softNewLine(); - writer.append("return self.").appendMethod(functorMethod).append('('); + writer.append("return self.").appendVirtualMethod(functorMethod).append('('); appendArguments(functorMethod.parameterCount()); writer.append(");").softNewLine(); writer.outdent().append("};").softNewLine(); diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/JSBodyBloatedEmitter.java b/jso/impl/src/main/java/org/teavm/jso/impl/JSBodyBloatedEmitter.java index 586c4b206..19d740ba8 100644 --- a/jso/impl/src/main/java/org/teavm/jso/impl/JSBodyBloatedEmitter.java +++ b/jso/impl/src/main/java/org/teavm/jso/impl/JSBodyBloatedEmitter.java @@ -69,8 +69,8 @@ class JSBodyBloatedEmitter implements JSBodyEmitter { private void emit(SourceWriter writer, EmissionStrategy strategy) { int bodyParamCount = isStatic ? method.parameterCount() : method.parameterCount() - 1; - writer.append("if (!").appendMethodBody(method).append(".$native)").ws().append('{').indent().newLine(); - writer.appendMethodBody(method).append(".$native").ws().append('=').ws().append("function("); + writer.append("if (!").appendMethod(method).append(".$native)").ws().append('{').indent().newLine(); + writer.appendMethod(method).append(".$native").ws().append('=').ws().append("function("); int count = method.parameterCount(); var first = true; @@ -135,11 +135,11 @@ class JSBodyBloatedEmitter implements JSBodyEmitter { writer.append(");").softNewLine(); writer.outdent().append("};").softNewLine(); - writer.appendMethodBody(method).ws().append('=').ws().appendMethodBody(method).append(".$native;") + writer.appendMethod(method).ws().append('=').ws().appendMethod(method).append(".$native;") .softNewLine(); writer.outdent().append("}").softNewLine(); - writer.append("return ").appendMethodBody(method).append('('); + writer.append("return ").appendMethod(method).append('('); for (int i = 0; i < count; ++i) { if (i > 0) { writer.append(',').ws(); diff --git a/platform/src/main/java/org/teavm/platform/plugin/AsyncMethodGenerator.java b/platform/src/main/java/org/teavm/platform/plugin/AsyncMethodGenerator.java index 2d3e6bd84..abd2cbfd3 100644 --- a/platform/src/main/java/org/teavm/platform/plugin/AsyncMethodGenerator.java +++ b/platform/src/main/java/org/teavm/platform/plugin/AsyncMethodGenerator.java @@ -49,7 +49,7 @@ public class AsyncMethodGenerator implements Generator, DependencyPlugin, Virtua template.builder("asyncMethod") .withContext(context) .withFragment("callMethod", (w, p) -> { - w.appendMethodBody(asyncRef).append('('); + w.appendMethod(asyncRef).append('('); ClassReader cls = context.getClassSource().get(methodRef.getClassName()); MethodReader method = cls.getMethod(methodRef.getDescriptor()); int start = method.hasModifier(ElementModifier.STATIC) ? 1 : 0; diff --git a/platform/src/main/java/org/teavm/platform/plugin/PlatformGenerator.java b/platform/src/main/java/org/teavm/platform/plugin/PlatformGenerator.java index daaf05db5..77b8fdd97 100644 --- a/platform/src/main/java/org/teavm/platform/plugin/PlatformGenerator.java +++ b/platform/src/main/java/org/teavm/platform/plugin/PlatformGenerator.java @@ -132,7 +132,7 @@ public class PlatformGenerator implements Generator, Injector, DependencyPlugin MethodReader method = cls.getMethod(new MethodDescriptor("", void.class)); if (method != null) { writer.appendClass(clsName).append("[c]").ws().append("=").ws() - .appendMethodBody(method.getReference()).append(";").softNewLine(); + .appendMethod(method.getReference()).append(";").softNewLine(); } } } @@ -159,14 +159,14 @@ public class PlatformGenerator implements Generator, Injector, DependencyPlugin ValueType.arrayOf(ValueType.object(clsName)))); if (method != null) { writer.appendClass(clsName).append("[c]").ws().append("=").ws(); - writer.appendMethodBody(method.getReference()); + writer.appendMethod(method.getReference()); writer.append(";").softNewLine(); } } MethodReference selfRef = new MethodReference(Platform.class, "getEnumConstants", PlatformClass.class, Enum[].class); - writer.appendMethodBody(selfRef).ws().append("=").ws().append("cls").sameLineWs().append("=>").ws() + writer.appendMethod(selfRef).ws().append("=").ws().append("cls").sameLineWs().append("=>").ws() .append("{").softNewLine().indent(); writer.append("if").ws().append("(!cls.hasOwnProperty(c))").ws().append("{").indent().softNewLine(); writer.append("return null;").softNewLine(); @@ -178,7 +178,7 @@ public class PlatformGenerator implements Generator, Injector, DependencyPlugin writer.append("return cls[c];").softNewLine(); writer.outdent().append("};").softNewLine(); - writer.append("return ").appendMethodBody(selfRef).append("(").append(context.getParameterName(1)) + writer.append("return ").appendMethod(selfRef).append("(").append(context.getParameterName(1)) .append(");").softNewLine(); } @@ -196,15 +196,16 @@ public class PlatformGenerator implements Generator, Injector, DependencyPlugin MethodReference selfRef = new MethodReference(Platform.class, "getAnnotations", PlatformClass.class, Annotation[].class); - writer.appendMethodBody(selfRef).ws().append("=").ws().append("cls").sameLineWs().append("=>").ws() + writer.appendMethod(selfRef).ws().append("=").ws().append("cls").sameLineWs().append("=>").ws() .append("{").softNewLine().indent(); writer.append("if").ws().append("(!cls.hasOwnProperty(c))").ws().append("{").indent().softNewLine(); writer.append("return null;").softNewLine(); writer.outdent().append("}").softNewLine(); - writer.append("return cls[c].").appendMethod("getAnnotations", Annotation[].class).append("();").softNewLine(); + writer.append("return cls[c].").appendVirtualMethod("getAnnotations", Annotation[].class).append("();") + .softNewLine(); writer.outdent().append("};").softNewLine(); - writer.append("return ").appendMethodBody(selfRef).append("(").append(context.getParameterName(1)) + writer.append("return ").appendMethod(selfRef).append("(").append(context.getParameterName(1)) .append(");").softNewLine(); } } diff --git a/tools/cli/src/main/java/org/teavm/cli/TeaVMRunner.java b/tools/cli/src/main/java/org/teavm/cli/TeaVMRunner.java index 78186d5b6..4303296f2 100644 --- a/tools/cli/src/main/java/org/teavm/cli/TeaVMRunner.java +++ b/tools/cli/src/main/java/org/teavm/cli/TeaVMRunner.java @@ -241,6 +241,15 @@ public final class TeaVMRunner { tool.setObfuscated(commandLine.hasOption("m")); tool.setStrict(commandLine.hasOption("strict")); parseJsModuleOption(); + + if (commandLine.hasOption("max-toplevel-names")) { + try { + tool.setMaxTopLevelNames(Integer.parseInt(commandLine.getOptionValue("max-toplevel-names"))); + } catch (NumberFormatException e) { + System.err.println("'--max-toplevel-names' must be integer number"); + printUsage(); + } + } } private void parseJsModuleOption() { diff --git a/tools/core/src/main/java/org/teavm/tooling/TeaVMTool.java b/tools/core/src/main/java/org/teavm/tooling/TeaVMTool.java index 2c3946b43..421d6b913 100644 --- a/tools/core/src/main/java/org/teavm/tooling/TeaVMTool.java +++ b/tools/core/src/main/java/org/teavm/tooling/TeaVMTool.java @@ -78,6 +78,7 @@ public class TeaVMTool { private boolean obfuscated = true; private JSModuleType jsModuleType = JSModuleType.UMD; private boolean strict; + private int maxTopLevelNames = 80_000; private String mainClass; private String entryPointName = "main"; private Properties properties = new Properties(); @@ -139,6 +140,10 @@ public class TeaVMTool { this.strict = strict; } + public void setMaxTopLevelNames(int maxTopLevelNames) { + this.maxTopLevelNames = maxTopLevelNames; + } + public boolean isIncremental() { return incremental; } @@ -336,6 +341,7 @@ public class TeaVMTool { javaScriptTarget = new JavaScriptTarget(); javaScriptTarget.setObfuscated(obfuscated); javaScriptTarget.setStrict(strict); + javaScriptTarget.setMaxTopLevelNames(maxTopLevelNames); debugEmitter = debugInformationGenerated || sourceMapsFileGenerated ? new DebugInformationBuilder(referenceCache) : null; diff --git a/tools/core/src/main/java/org/teavm/tooling/builder/BuildStrategy.java b/tools/core/src/main/java/org/teavm/tooling/builder/BuildStrategy.java index 3e4861740..59bd0a4c0 100644 --- a/tools/core/src/main/java/org/teavm/tooling/builder/BuildStrategy.java +++ b/tools/core/src/main/java/org/teavm/tooling/builder/BuildStrategy.java @@ -63,6 +63,8 @@ public interface BuildStrategy { void setJsModuleType(JSModuleType jsModuleType); + void setMaxTopLevelNames(int maxTopLevelNames); + void setProperties(Properties properties); void setTransformers(String[] transformers); diff --git a/tools/core/src/main/java/org/teavm/tooling/builder/InProcessBuildStrategy.java b/tools/core/src/main/java/org/teavm/tooling/builder/InProcessBuildStrategy.java index c8e1cfb94..0ef888670 100644 --- a/tools/core/src/main/java/org/teavm/tooling/builder/InProcessBuildStrategy.java +++ b/tools/core/src/main/java/org/teavm/tooling/builder/InProcessBuildStrategy.java @@ -56,6 +56,7 @@ public class InProcessBuildStrategy implements BuildStrategy { private boolean obfuscated; private JSModuleType jsModuleType; private boolean strict; + private int maxTopLevelNames = 80_000; private boolean sourceMapsFileGenerated; private boolean debugInformationGenerated; private TeaVMSourceFilePolicy sourceMapsSourcePolicy; @@ -174,6 +175,11 @@ public class InProcessBuildStrategy implements BuildStrategy { this.jsModuleType = jsModuleType; } + @Override + public void setMaxTopLevelNames(int maxTopLevelNames) { + this.maxTopLevelNames = maxTopLevelNames; + } + @Override public void setTransformers(String[] transformers) { this.transformers = transformers.clone(); @@ -256,6 +262,7 @@ public class InProcessBuildStrategy implements BuildStrategy { tool.setObfuscated(obfuscated); tool.setJsModuleType(jsModuleType); tool.setStrict(strict); + tool.setMaxTopLevelNames(maxTopLevelNames); tool.setIncremental(incremental); tool.getTransformers().addAll(Arrays.asList(transformers)); tool.getClassesToPreserve().addAll(Arrays.asList(classesToPreserve)); diff --git a/tools/core/src/main/java/org/teavm/tooling/builder/RemoteBuildStrategy.java b/tools/core/src/main/java/org/teavm/tooling/builder/RemoteBuildStrategy.java index b3de32f97..eaeec7a0f 100644 --- a/tools/core/src/main/java/org/teavm/tooling/builder/RemoteBuildStrategy.java +++ b/tools/core/src/main/java/org/teavm/tooling/builder/RemoteBuildStrategy.java @@ -148,6 +148,11 @@ public class RemoteBuildStrategy implements BuildStrategy { request.jsModuleType = jsModuleType; } + @Override + public void setMaxTopLevelNames(int maxTopLevelNames) { + request.maxTopLevelNames = maxTopLevelNames; + } + @Override public void setTransformers(String[] transformers) { request.transformers = transformers.clone(); diff --git a/tools/core/src/main/java/org/teavm/tooling/daemon/RemoteBuildRequest.java b/tools/core/src/main/java/org/teavm/tooling/daemon/RemoteBuildRequest.java index 9e58df4e9..36c870fa6 100644 --- a/tools/core/src/main/java/org/teavm/tooling/daemon/RemoteBuildRequest.java +++ b/tools/core/src/main/java/org/teavm/tooling/daemon/RemoteBuildRequest.java @@ -44,6 +44,7 @@ public class RemoteBuildRequest implements Serializable { public boolean obfuscated; public boolean strict; public JSModuleType jsModuleType; + public int maxTopLevelNames = 80_000; public Properties properties; public TeaVMOptimizationLevel optimizationLevel; public boolean fastDependencyAnalysis; diff --git a/tools/gradle/src/main/java/org/teavm/gradle/TeaVMPlugin.java b/tools/gradle/src/main/java/org/teavm/gradle/TeaVMPlugin.java index 549ac28a3..a4f9609fe 100644 --- a/tools/gradle/src/main/java/org/teavm/gradle/TeaVMPlugin.java +++ b/tools/gradle/src/main/java/org/teavm/gradle/TeaVMPlugin.java @@ -123,6 +123,7 @@ public class TeaVMPlugin implements Plugin { task.getStrict().convention(js.getStrict()); task.getEntryPointName().convention(js.getEntryPointName()); task.getSourceFilePolicy().convention(js.getSourceFilePolicy()); + task.getMaxTopLevelNames().convention(js.getMaxTopLevelNames()); task.getSourceFiles().from(project.provider(() -> { var result = new ArrayList(); diff --git a/tools/gradle/src/main/java/org/teavm/gradle/api/TeaVMJSConfiguration.java b/tools/gradle/src/main/java/org/teavm/gradle/api/TeaVMJSConfiguration.java index ccfa02dfa..eb29f4429 100644 --- a/tools/gradle/src/main/java/org/teavm/gradle/api/TeaVMJSConfiguration.java +++ b/tools/gradle/src/main/java/org/teavm/gradle/api/TeaVMJSConfiguration.java @@ -31,4 +31,6 @@ public interface TeaVMJSConfiguration extends TeaVMWebConfiguration { Property getTargetFileName(); Property getSourceFilePolicy(); + + Property getMaxTopLevelNames(); } diff --git a/tools/gradle/src/main/java/org/teavm/gradle/tasks/GenerateJavaScriptTask.java b/tools/gradle/src/main/java/org/teavm/gradle/tasks/GenerateJavaScriptTask.java index 0fde2ce9b..fe74365d3 100644 --- a/tools/gradle/src/main/java/org/teavm/gradle/tasks/GenerateJavaScriptTask.java +++ b/tools/gradle/src/main/java/org/teavm/gradle/tasks/GenerateJavaScriptTask.java @@ -63,11 +63,18 @@ public abstract class GenerateJavaScriptTask extends TeaVMTask { @Optional public abstract Property getSourceFilePolicy(); + @Input + @Optional + public abstract Property getMaxTopLevelNames(); + @Override protected void setupBuilder(BuildStrategy builder) { builder.setTargetType(TeaVMTargetType.JAVASCRIPT); builder.setObfuscated(getObfuscated().get()); builder.setStrict(getStrict().get()); + if (getMaxTopLevelNames().isPresent()) { + builder.setMaxTopLevelNames(getMaxTopLevelNames().get()); + } switch (getModuleType().get()) { case UMD: builder.setJsModuleType(org.teavm.backend.javascript.JSModuleType.UMD); diff --git a/tools/junit/src/main/java/org/teavm/junit/TestExceptionPlugin.java b/tools/junit/src/main/java/org/teavm/junit/TestExceptionPlugin.java index b8a04fc99..07cc6fbea 100644 --- a/tools/junit/src/main/java/org/teavm/junit/TestExceptionPlugin.java +++ b/tools/junit/src/main/java/org/teavm/junit/TestExceptionPlugin.java @@ -65,7 +65,7 @@ class TestExceptionPlugin implements TeaVMPlugin { writer.appendClass("java.lang.Throwable").append(".prototype.getMessage").ws().append("=").ws() .append("function()").ws().append("{").indent().softNewLine(); writer.append("return ").appendFunction("$rt_ustr").append("(this.") - .appendMethod("getMessage", String.class).append("());").softNewLine(); + .appendVirtualMethod("getMessage", String.class).append("());").softNewLine(); writer.outdent().append("};").newLine(); } } diff --git a/tools/maven/plugin/src/main/java/org/teavm/maven/TeaVMCompileMojo.java b/tools/maven/plugin/src/main/java/org/teavm/maven/TeaVMCompileMojo.java index 23383057a..21adf8fcd 100644 --- a/tools/maven/plugin/src/main/java/org/teavm/maven/TeaVMCompileMojo.java +++ b/tools/maven/plugin/src/main/java/org/teavm/maven/TeaVMCompileMojo.java @@ -87,6 +87,9 @@ public class TeaVMCompileMojo extends AbstractMojo { @Parameter(property = "teavm.jsModuleType", defaultValue = "UMD") private JSModuleType jsModuleType; + @Parameter(property = "teavm.maxTopLevelNames", defaultValue = "80000") + private int maxTopLevelNames = 80_000; + @Parameter private Properties properties; @@ -169,6 +172,7 @@ public class TeaVMCompileMojo extends AbstractMojo { builder.setObfuscated(minifying); builder.setStrict(strict); builder.setJsModuleType(jsModuleType); + builder.setMaxTopLevelNames(maxTopLevelNames); builder.setTargetDirectory(targetDirectory.getAbsolutePath()); if (transformers != null) { builder.setTransformers(transformers);