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
This commit is contained in:
Alexey Andreev 2024-01-30 17:01:28 +01:00
parent ca273390ef
commit 6ac598b927
37 changed files with 697 additions and 352 deletions

View File

@ -44,7 +44,7 @@ public class ServiceLoaderJSSupport implements Generator {
} }
first = false; first = false;
writer.append("[").appendClass(implName).append(",").ws() writer.append("[").appendClass(implName).append(",").ws()
.appendMethodBody(new MethodReference(implName, INIT_METHOD)) .appendMethod(new MethodReference(implName, INIT_METHOD))
.append("]"); .append("]");
} }
} }

View File

@ -272,7 +272,7 @@ public class ClassGenerator implements Generator, Injector, DependencyPlugin {
if (method.getResultType() != ValueType.VOID) { if (method.getResultType() != ValueType.VOID) {
writer.append("return "); writer.append("return ");
} }
writer.appendMethodBody(method.getReference()); writer.appendMethod(method.getReference());
writer.append('('); writer.append('(');
boolean first = true; boolean first = true;
@ -315,30 +315,30 @@ public class ClassGenerator implements Generator, Injector, DependencyPlugin {
if (type instanceof ValueType.Primitive) { if (type instanceof ValueType.Primitive) {
switch (((ValueType.Primitive) type).getKind()) { switch (((ValueType.Primitive) type).getKind()) {
case BOOLEAN: case BOOLEAN:
writer.appendMethodBody(new MethodReference(Boolean.class, "valueOf", boolean.class, writer.appendMethod(new MethodReference(Boolean.class, "valueOf", boolean.class,
Boolean.class)); Boolean.class));
break; break;
case BYTE: 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; break;
case SHORT: 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; break;
case CHARACTER: case CHARACTER:
writer.appendMethodBody(new MethodReference(Character.class, "valueOf", char.class, writer.appendMethod(new MethodReference(Character.class, "valueOf", char.class,
Character.class)); Character.class));
break; break;
case INTEGER: 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; break;
case LONG: 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; break;
case FLOAT: 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; break;
case DOUBLE: 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; break;
} }
writer.append('('); writer.append('(');
@ -355,28 +355,28 @@ public class ClassGenerator implements Generator, Injector, DependencyPlugin {
if (type instanceof ValueType.Primitive) { if (type instanceof ValueType.Primitive) {
switch (((ValueType.Primitive) type).getKind()) { switch (((ValueType.Primitive) type).getKind()) {
case BOOLEAN: case BOOLEAN:
writer.appendMethodBody(new MethodReference(Boolean.class, "booleanValue", boolean.class)); writer.appendMethod(new MethodReference(Boolean.class, "booleanValue", boolean.class));
break; break;
case BYTE: case BYTE:
writer.appendMethodBody(new MethodReference(Byte.class, "byteValue", byte.class)); writer.appendMethod(new MethodReference(Byte.class, "byteValue", byte.class));
break; break;
case SHORT: case SHORT:
writer.appendMethodBody(new MethodReference(Short.class, "shortValue", short.class)); writer.appendMethod(new MethodReference(Short.class, "shortValue", short.class));
break; break;
case CHARACTER: case CHARACTER:
writer.appendMethodBody(new MethodReference(Character.class, "charValue", char.class)); writer.appendMethod(new MethodReference(Character.class, "charValue", char.class));
break; break;
case INTEGER: case INTEGER:
writer.appendMethodBody(new MethodReference(Integer.class, "intValue", int.class)); writer.appendMethod(new MethodReference(Integer.class, "intValue", int.class));
break; break;
case LONG: case LONG:
writer.appendMethodBody(new MethodReference(Long.class, "longValue", long.class)); writer.appendMethod(new MethodReference(Long.class, "longValue", long.class));
break; break;
case FLOAT: case FLOAT:
writer.appendMethodBody(new MethodReference(Float.class, "floatValue", float.class)); writer.appendMethod(new MethodReference(Float.class, "floatValue", float.class));
break; break;
case DOUBLE: case DOUBLE:
writer.appendMethodBody(new MethodReference(Double.class, "doubleValue", double.class)); writer.appendMethod(new MethodReference(Double.class, "doubleValue", double.class));
break; break;
} }
writer.append('('); writer.append('(');

View File

@ -129,6 +129,7 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost {
private final Map<String, String> importedModules = new LinkedHashMap<>(); private final Map<String, String> importedModules = new LinkedHashMap<>();
private JavaScriptTemplateFactory templateFactory; private JavaScriptTemplateFactory templateFactory;
private JSModuleType moduleType = JSModuleType.UMD; private JSModuleType moduleType = JSModuleType.UMD;
private int maxTopLevelNames = 80_000;
@Override @Override
public List<ClassHolderTransformer> getTransformers() { public List<ClassHolderTransformer> getTransformers() {
@ -228,6 +229,10 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost {
this.stackTraceIncluded = stackTraceIncluded; this.stackTraceIncluded = stackTraceIncluded;
} }
public void setMaxTopLevelNames(int maxTopLevelNames) {
this.maxTopLevelNames = maxTopLevelNames;
}
@Override @Override
public List<TeaVMHostExtension> getHostExtensions() { public List<TeaVMHostExtension> getHostExtensions() {
return Collections.singletonList(this); return Collections.singletonList(this);
@ -351,7 +356,9 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost {
} }
private void emit(ListableClassHolderSource classes, Writer writer, BuildTarget target) { 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()); DefaultNamingStrategy naming = new DefaultNamingStrategy(aliasProvider, controller.getUnprocessedClassSource());
DebugInformationEmitter debugEmitterToUse = debugEmitter; DebugInformationEmitter debugEmitterToUse = debugEmitter;
if (debugEmitterToUse == null) { if (debugEmitterToUse == null) {
@ -410,9 +417,9 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost {
for (var entry : controller.getEntryPoints().entrySet()) { for (var entry : controller.getEntryPoints().entrySet()) {
var alias = "$rt_export_" + entry.getKey(); var alias = "$rt_export_" + entry.getKey();
var ref = entry.getValue().getMethod(); var ref = entry.getValue().getMethod();
rememberingWriter.append("let ").appendFunction(alias).ws().append("=").ws() rememberingWriter.startVariableDeclaration().appendFunction(alias)
.appendFunction("$rt_mainStarter").append("(").appendMethodBody(ref); .appendFunction("$rt_mainStarter").append("(").appendMethod(ref);
rememberingWriter.append(");").newLine(); rememberingWriter.append(")").endDeclaration();
rememberingWriter.appendFunction(alias).append(".") rememberingWriter.appendFunction(alias).append(".")
.append("javaException").ws().append("=").ws().appendFunction("$rt_javaException") .append("javaException").ws().append("=").ws().appendFunction("$rt_javaException")
.append(";").newLine(); .append(";").newLine();
@ -424,31 +431,49 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost {
var epilogue = rememberingWriter.save(); var epilogue = rememberingWriter.save();
rememberingWriter.clear(); rememberingWriter.clear();
if (renderingContext.isMinifying()) { var runtimeRenderer = new RuntimeRenderer(classes, rememberingWriter, controller.getClassInitializerInfo());
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());
runtimeRenderer.prepareAstParts(renderer.isThreadLibraryUsed()); runtimeRenderer.prepareAstParts(renderer.isThreadLibraryUsed());
declarations.replay(runtimeRenderer.sink, RememberedSource.FILTER_REF); declarations.replay(runtimeRenderer.sink, RememberedSource.FILTER_REF);
epilogue.replay(runtimeRenderer.sink, RememberedSource.FILTER_REF); epilogue.replay(runtimeRenderer.sink, RememberedSource.FILTER_REF);
runtimeRenderer.removeUnusedParts(); runtimeRenderer.removeUnusedParts();
runtimeRenderer.renderRuntime(); runtimeRenderer.renderRuntime();
declarations.write(sourceWriter, 0); var runtime = rememberingWriter.save();
rememberingWriter.clear();
runtimeRenderer.renderEpilogue(); 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); epilogue.write(sourceWriter, 0);
printWrapperEnd(sourceWriter); printModuleEnd(sourceWriter);
sourceWriter.finish();
int totalSize = sourceWriter.getOffset() - start; int totalSize = sourceWriter.getOffset() - start;
printStats(sourceWriter, totalSize); 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) { switch (moduleType) {
case UMD: case UMD:
printUmdEnd(writer); printUmdEnd(writer);
@ -580,7 +605,7 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost {
private void printUmdEnd(SourceWriter writer) { private void printUmdEnd(SourceWriter writer) {
for (var export : controller.getEntryPoints().keySet()) { for (var export : controller.getEntryPoints().keySet()) {
writer.appendFunction("$rt_exports").append(".").append(export).ws().append("=").ws() writer.appendFunction("$rt_exports").append(".").append(export).ws().append("=").ws()
.appendFunction("$rt_export_" + export); .appendFunction("$rt_export_" + export).append(";").softNewLine();
} }
writer.outdent().append("}));").newLine(); writer.outdent().append("}));").newLine();
} }

View File

@ -22,17 +22,19 @@ import org.teavm.model.MethodReference;
public interface AliasProvider { public interface AliasProvider {
String getFieldAlias(FieldReference field); 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 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); void reserveName(String name);
} }

View File

@ -17,24 +17,27 @@ package org.teavm.backend.javascript.codegen;
import com.carrotsearch.hppc.ObjectIntHashMap; import com.carrotsearch.hppc.ObjectIntHashMap;
import com.carrotsearch.hppc.ObjectIntMap; import com.carrotsearch.hppc.ObjectIntMap;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Map;
import java.util.Set; import java.util.Set;
import org.teavm.model.FieldReference; import org.teavm.model.FieldReference;
import org.teavm.model.MethodDescriptor; import org.teavm.model.MethodDescriptor;
import org.teavm.model.MethodReference; import org.teavm.model.MethodReference;
public class DefaultAliasProvider implements AliasProvider { public class DefaultAliasProvider implements AliasProvider {
private final Map<String, String> classAliases = new HashMap<>(); private final int maxTopLevelNames;
private final Set<String> knownAliases = new HashSet<>(200, 0.5f); private final Set<String> knownAliases = new HashSet<>(200, 0.5f);
private final ObjectIntMap<String> knowAliasesCounter = new ObjectIntHashMap<>(); private final ObjectIntMap<String> knowAliasesCounter = new ObjectIntHashMap<>();
private final Set<String> knownVirtualAliases = new HashSet<>(200, 0.5f); private final Set<String> knownInstanceAliases = new HashSet<>(200, 0.5f);
private final ObjectIntMap<String> knowVirtualAliasesCounter = new ObjectIntHashMap<>(); private final ObjectIntMap<String> knowInstanceAliasesCounter = new ObjectIntHashMap<>();
private boolean additionalScopeStarted;
public DefaultAliasProvider(int maxTopLevelNames) {
this.maxTopLevelNames = maxTopLevelNames;
}
@Override @Override
public String getClassAlias(String cls) { public ScopedName getClassAlias(String cls) {
return classAliases.computeIfAbsent(cls, key -> makeUniqueTopLevel(suggestAliasForClass(key))); return makeUnique(suggestAliasForClass(cls));
} }
private static String suggestAliasForClass(String cls) { private static String suggestAliasForClass(String cls) {
@ -84,11 +87,11 @@ public class DefaultAliasProvider implements AliasProvider {
alias = "$" + alias; alias = "$" + alias;
break; break;
} }
return makeUnique(knownVirtualAliases, knowVirtualAliasesCounter, alias); return makeUniqueInstance(alias);
} }
@Override @Override
public String getStaticMethodAlias(MethodReference method) { public ScopedName getStaticMethodAlias(MethodReference method) {
String suggested = method.getDescriptor().getName(); String suggested = method.getDescriptor().getName();
switch (suggested) { switch (suggested) {
case "<init>": case "<init>":
@ -99,35 +102,55 @@ public class DefaultAliasProvider implements AliasProvider {
break; break;
} }
return makeUniqueTopLevel(getClassAlias(method.getClassName()) + "_" + suggested); return makeUnique(suggestAliasForClass(method.getClassName()) + "_" + suggested);
} }
@Override @Override
public String getFieldAlias(FieldReference field) { public String getFieldAlias(FieldReference field) {
return makeUnique(knownVirtualAliases, knowVirtualAliasesCounter, "$" + field.getFieldName()); return makeUniqueInstance("$" + field.getFieldName());
} }
@Override @Override
public String getStaticFieldAlias(FieldReference field) { public ScopedName getStaticFieldAlias(FieldReference field) {
return makeUniqueTopLevel(getClassAlias(field.getClassName()) + "_" + field.getFieldName()); return makeUnique(suggestAliasForClass(field.getClassName()) + "_" + field.getFieldName());
} }
@Override @Override
public String getFunctionAlias(String name) { public ScopedName getFunctionAlias(String name) {
return name; return makeUnique(name);
} }
@Override @Override
public String getClassInitAlias(String className) { public ScopedName getClassInitAlias(String className) {
return makeUniqueTopLevel(suggestAliasForClass(className) + "_$callClinit"); return makeUnique(suggestAliasForClass(className) + "_$callClinit");
}
@Override
public String getAdditionalScopeName() {
return makeUnique("$rt_java").name;
} }
@Override @Override
public void reserveName(String name) { public void reserveName(String name) {
} }
private String makeUniqueTopLevel(String suggested) { private ScopedName makeUnique(String suggested) {
return makeUnique(knownAliases, knowAliasesCounter, 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) { private String sanitize(String s) {
@ -163,17 +186,17 @@ public class DefaultAliasProvider implements AliasProvider {
return isIdentifierStart(c) || c >= '0' && c <= '9'; return isIdentifierStart(c) || c >= '0' && c <= '9';
} }
private String makeUnique(Set<String> knowAliases, ObjectIntMap<String> indexMap, String alias) { private String makeUniqueInstance(String alias) {
alias = sanitize(alias); alias = sanitize(alias);
String uniqueAlias = alias; String uniqueAlias = alias;
int index = indexMap.get(alias); int index = knowInstanceAliasesCounter.get(alias);
if (index > 0) { if (index > 0) {
uniqueAlias = alias + index++; uniqueAlias = alias + index++;
} }
while (!knowAliases.add(uniqueAlias)) { while (!knownInstanceAliases.add(uniqueAlias)) {
uniqueAlias = alias + index++; uniqueAlias = alias + index++;
} }
indexMap.put(alias, index); knowInstanceAliasesCounter.put(alias, index);
return uniqueAlias; return uniqueAlias;
} }
} }

View File

@ -33,12 +33,13 @@ public class DefaultNamingStrategy implements NamingStrategy {
private final AliasProvider aliasProvider; private final AliasProvider aliasProvider;
private final ClassReaderSource classSource; private final ClassReaderSource classSource;
private final Map<MethodDescriptor, String> aliases = new HashMap<>(); private final Map<MethodDescriptor, String> aliases = new HashMap<>();
private final Map<Key, String> privateAliases = new HashMap<>(); private final Map<Key, ScopedName> privateAliases = new HashMap<>();
private final Map<String, String> classAliases = new HashMap<>(); private final Map<String, ScopedName> classAliases = new HashMap<>();
private final Map<FieldReference, String> fieldAliases = new HashMap<>(); private final Map<FieldReference, String> fieldAliases = new HashMap<>();
private final Map<FieldReference, String> staticFieldAliases = new HashMap<>(); private final Map<FieldReference, ScopedName> staticFieldAliases = new HashMap<>();
private final Map<String, String> functionAliases = new HashMap<>(); private final Map<String, ScopedName> functionAliases = new HashMap<>();
private final Map<String, String> classInitAliases = new HashMap<>(); private final Map<String, ScopedName> classInitAliases = new HashMap<>();
private String additionalScopeName;
public DefaultNamingStrategy(AliasProvider aliasProvider, ClassReaderSource classSource) { public DefaultNamingStrategy(AliasProvider aliasProvider, ClassReaderSource classSource) {
this.aliasProvider = aliasProvider; this.aliasProvider = aliasProvider;
@ -46,12 +47,12 @@ public class DefaultNamingStrategy implements NamingStrategy {
} }
@Override @Override
public String getNameFor(String cls) { public ScopedName className(String cls) {
return classAliases.computeIfAbsent(cls, key -> aliasProvider.getClassAlias(cls)); return classAliases.computeIfAbsent(cls, key -> aliasProvider.getClassAlias(cls));
} }
@Override @Override
public String getNameFor(MethodDescriptor method) { public String instanceMethodName(MethodDescriptor method) {
String alias = aliases.get(method); String alias = aliases.get(method);
if (alias == null) { if (alias == null) {
alias = aliasProvider.getMethodAlias(method); alias = aliasProvider.getMethodAlias(method);
@ -61,16 +62,16 @@ public class DefaultNamingStrategy implements NamingStrategy {
} }
@Override @Override
public String getFullNameFor(MethodReference method) { public ScopedName methodName(MethodReference method) {
return getFullNameFor(method, NO_CLASSIFIER); return getFullNameFor(method, NO_CLASSIFIER);
} }
@Override @Override
public String getNameForInit(MethodReference method) { public ScopedName initializerName(MethodReference method) {
return getFullNameFor(method, INIT_CLASSIFIER); return getFullNameFor(method, INIT_CLASSIFIER);
} }
private String getFullNameFor(MethodReference method, byte classifier) { private ScopedName getFullNameFor(MethodReference method, byte classifier) {
MethodReference originalMethod = method; MethodReference originalMethod = method;
method = getRealMethod(method); method = getRealMethod(method);
if (method == null) { if (method == null) {
@ -82,14 +83,14 @@ public class DefaultNamingStrategy implements NamingStrategy {
} }
@Override @Override
public String getNameFor(FieldReference field) { public String instanceFieldName(FieldReference field) {
String alias = fieldAliases.get(field); String alias = fieldAliases.get(field);
if (alias == null) { if (alias == null) {
FieldReference realField = getRealField(field); FieldReference realField = getRealField(field);
if (realField.equals(field)) { if (realField.equals(field)) {
alias = aliasProvider.getFieldAlias(realField); alias = aliasProvider.getFieldAlias(realField);
} else { } else {
alias = getNameFor(realField); alias = instanceFieldName(realField);
} }
fieldAliases.put(field, alias); fieldAliases.put(field, alias);
} }
@ -97,14 +98,14 @@ public class DefaultNamingStrategy implements NamingStrategy {
} }
@Override @Override
public String getFullNameFor(FieldReference field) { public ScopedName fieldName(FieldReference field) {
var alias = staticFieldAliases.get(field); var alias = staticFieldAliases.get(field);
if (alias == null) { if (alias == null) {
FieldReference realField = getRealField(field); FieldReference realField = getRealField(field);
if (realField.equals(field)) { if (realField.equals(field)) {
alias = aliasProvider.getStaticFieldAlias(realField); alias = aliasProvider.getStaticFieldAlias(realField);
} else { } else {
alias = getFullNameFor(realField); alias = fieldName(realField);
} }
staticFieldAliases.put(field, alias); staticFieldAliases.put(field, alias);
} }
@ -112,13 +113,21 @@ public class DefaultNamingStrategy implements NamingStrategy {
} }
@Override @Override
public String getNameForFunction(String name) { public ScopedName functionName(String name) {
return functionAliases.computeIfAbsent(name, key -> aliasProvider.getFunctionAlias(key)); return functionAliases.computeIfAbsent(name, aliasProvider::getFunctionAlias);
} }
@Override @Override
public String getNameForClassInit(String className) { public ScopedName classInitializerName(String className) {
return classInitAliases.computeIfAbsent(className, key -> aliasProvider.getClassInitAlias(key)); return classInitAliases.computeIfAbsent(className, aliasProvider::getClassInitAlias);
}
@Override
public String additionalScopeName() {
if (additionalScopeName == null) {
additionalScopeName = aliasProvider.getAdditionalScopeName();
}
return additionalScopeName;
} }
@Override @Override

View File

@ -25,64 +25,80 @@ import org.teavm.model.MethodReference;
public class MinifyingAliasProvider implements AliasProvider { public class MinifyingAliasProvider implements AliasProvider {
private static final String startLetters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; private static final String startLetters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
private static final String startVirtualLetters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; private static final String startVirtualLetters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
private final int maxTopLevelNames;
private int lastSuffix; private int lastSuffix;
private int lastVirtual; private int lastInstanceSuffix;
private int topLevelNames;
private boolean additionalScopeStarted;
private final Set<String> usedAliases = new HashSet<>(); private final Set<String> usedAliases = new HashSet<>();
private final Set<String> usedVirtualAliases = new HashSet<>();
@Override public MinifyingAliasProvider(int maxTopLevelNames) {
public String getFieldAlias(FieldReference field) { this.maxTopLevelNames = maxTopLevelNames;
String result;
do {
result = RenderingUtil.indexToId(lastVirtual++, startVirtualLetters);
} while (!usedVirtualAliases.add(result) || RenderingUtil.KEYWORDS.contains(result));
return result;
} }
@Override @Override
public String getStaticFieldAlias(FieldReference field) { public String getFieldAlias(FieldReference field) {
return createInstanceName();
}
@Override
public ScopedName getStaticFieldAlias(FieldReference field) {
return createTopLevelName(); return createTopLevelName();
} }
@Override @Override
public String getStaticMethodAlias(MethodReference method) { public ScopedName getStaticMethodAlias(MethodReference method) {
return createTopLevelName(); return createTopLevelName();
} }
@Override @Override
public String getMethodAlias(MethodDescriptor method) { public String getMethodAlias(MethodDescriptor method) {
String result; return createInstanceName();
do {
result = RenderingUtil.indexToId(lastVirtual++, startVirtualLetters);
} while (!usedVirtualAliases.add(result) || RenderingUtil.KEYWORDS.contains(result));
return result;
} }
@Override @Override
public String getClassAlias(String className) { public ScopedName getClassAlias(String className) {
return createTopLevelName(); return createTopLevelName();
} }
@Override @Override
public String getFunctionAlias(String className) { public ScopedName getFunctionAlias(String className) {
return RenderingUtil.indexToId(lastSuffix++, startLetters); return createTopLevelName();
} }
@Override @Override
public String getClassInitAlias(String className) { public ScopedName getClassInitAlias(String className) {
return createTopLevelName(); return createTopLevelName();
} }
@Override
public String getAdditionalScopeName() {
return createTopLevelName().name;
}
@Override @Override
public void reserveName(String name) { public void reserveName(String name) {
usedAliases.add(name); usedAliases.add(name);
} }
private String createTopLevelName() { private ScopedName createTopLevelName() {
if (!additionalScopeStarted && topLevelNames >= maxTopLevelNames) {
additionalScopeStarted = true;
lastSuffix = 0;
}
String result; String result;
do { do {
result = RenderingUtil.indexToId(lastSuffix++, startLetters); 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; return result;
} }
} }

View File

@ -20,21 +20,23 @@ import org.teavm.model.MethodDescriptor;
import org.teavm.model.MethodReference; import org.teavm.model.MethodReference;
public interface NamingStrategy { 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); void reserveName(String name);
} }

View File

@ -29,6 +29,7 @@ import org.teavm.model.MethodDescriptor;
import org.teavm.model.MethodReference; import org.teavm.model.MethodReference;
public class OutputSourceWriter extends SourceWriter implements LocationProvider { public class OutputSourceWriter extends SourceWriter implements LocationProvider {
private static final int LET_SEQUENCE_LIMIT = 50;
private final Appendable innerWriter; private final Appendable innerWriter;
private int indentSize; private int indentSize;
private final NamingStrategy naming; private final NamingStrategy naming;
@ -45,6 +46,9 @@ public class OutputSourceWriter extends SourceWriter implements LocationProvider
private int sectionMarkSection = -1; private int sectionMarkSection = -1;
private int sectionMarkPos; private int sectionMarkPos;
private IntIntMap sectionSizes = new IntIntHashMap(); private IntIntMap sectionSizes = new IntIntHashMap();
private DeclarationType currentDeclarationType;
private boolean expectingDeclarationName;
private int letSequenceSize;
OutputSourceWriter(NamingStrategy naming, Appendable innerWriter, int lineWidth) { OutputSourceWriter(NamingStrategy naming, Appendable innerWriter, int lineWidth) {
this.naming = naming; this.naming = naming;
@ -61,9 +65,31 @@ public class OutputSourceWriter extends SourceWriter implements LocationProvider
this.minified = minified; 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 @Override
public SourceWriter append(char value) { public SourceWriter append(char value) {
appendIndent(); appendIndent();
finishLetImplicitly();
try { try {
innerWriter.append(value); innerWriter.append(value);
} catch (IOException e) { } catch (IOException e) {
@ -96,6 +122,7 @@ public class OutputSourceWriter extends SourceWriter implements LocationProvider
if (start == end) { if (start == end) {
return; return;
} }
finishLetImplicitly();
appendIndent(); appendIndent();
column += end - start; column += end - start;
offset += end - start; offset += end - start;
@ -108,32 +135,32 @@ public class OutputSourceWriter extends SourceWriter implements LocationProvider
@Override @Override
public SourceWriter appendClass(String cls) { public SourceWriter appendClass(String cls) {
return appendName(naming.getNameFor(cls)); return appendDeclaration(naming.className(cls));
} }
@Override @Override
public SourceWriter appendField(FieldReference field) { public SourceWriter appendField(FieldReference field) {
return append(naming.getNameFor(field)); return append(naming.instanceFieldName(field));
} }
@Override @Override
public SourceWriter appendStaticField(FieldReference field) { public SourceWriter appendStaticField(FieldReference field) {
return appendName(naming.getFullNameFor(field)); return appendDeclaration(naming.fieldName(field));
} }
@Override @Override
public SourceWriter appendMethod(MethodDescriptor method) { public SourceWriter appendVirtualMethod(MethodDescriptor method) {
return append(naming.getNameFor(method)); return append(naming.instanceMethodName(method));
} }
@Override @Override
public SourceWriter appendMethodBody(MethodReference method) { public SourceWriter appendMethod(MethodReference method) {
return appendName(naming.getFullNameFor(method)); return appendDeclaration(naming.methodName(method));
} }
@Override @Override
public SourceWriter appendFunction(String name) { public SourceWriter appendFunction(String name) {
return append(naming.getNameForFunction(name)); return appendDeclaration(naming.functionName(name));
} }
@Override @Override
@ -143,16 +170,117 @@ public class OutputSourceWriter extends SourceWriter implements LocationProvider
@Override @Override
public SourceWriter appendInit(MethodReference method) { public SourceWriter appendInit(MethodReference method) {
return appendName(naming.getNameForInit(method)); return appendDeclaration(naming.initializerName(method));
} }
@Override @Override
public SourceWriter appendClassInit(String className) { public SourceWriter appendClassInit(String className) {
return appendName(naming.getNameForClassInit(className)); return appendDeclaration(naming.classInitializerName(className));
} }
private SourceWriter appendName(String name) { @Override
append(name); 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; return this;
} }
@ -160,6 +288,7 @@ public class OutputSourceWriter extends SourceWriter implements LocationProvider
if (minified) { if (minified) {
return; return;
} }
finishLetImplicitly();
if (lineStart) { if (lineStart) {
try { try {
for (int i = 0; i < indentSize; ++i) { for (int i = 0; i < indentSize; ++i) {
@ -176,6 +305,7 @@ public class OutputSourceWriter extends SourceWriter implements LocationProvider
@Override @Override
public SourceWriter newLine() { public SourceWriter newLine() {
finishLetImplicitly();
try { try {
innerWriter.append('\n'); innerWriter.append('\n');
} catch (IOException e) { } catch (IOException e) {
@ -194,6 +324,7 @@ public class OutputSourceWriter extends SourceWriter implements LocationProvider
newLine(); newLine();
} else { } else {
if (!minified) { if (!minified) {
finishLetImplicitly();
try { try {
innerWriter.append(' '); innerWriter.append(' ');
} catch (IOException e) { } catch (IOException e) {
@ -209,6 +340,7 @@ public class OutputSourceWriter extends SourceWriter implements LocationProvider
@Override @Override
public SourceWriter sameLineWs() { public SourceWriter sameLineWs() {
if (!minified) { if (!minified) {
finishLetImplicitly();
try { try {
innerWriter.append(' '); innerWriter.append(' ');
} catch (IOException e) { } catch (IOException e) {
@ -231,6 +363,7 @@ public class OutputSourceWriter extends SourceWriter implements LocationProvider
@Override @Override
public SourceWriter softNewLine() { public SourceWriter softNewLine() {
if (!minified) { if (!minified) {
finishLetImplicitly();
try { try {
innerWriter.append('\n'); innerWriter.append('\n');
} catch (IOException e) { } catch (IOException e) {
@ -362,4 +495,10 @@ public class OutputSourceWriter extends SourceWriter implements LocationProvider
public int getSectionSize(int sectionId) { public int getSectionSize(int sectionId) {
return sectionSizes.get(sectionId); return sectionSizes.get(sectionId);
} }
private enum DeclarationType {
FUNCTION,
VARIABLE,
VARIABLE_WITHOUT_VALUE
}
} }

View File

@ -88,14 +88,14 @@ public class RememberedSource implements SourceFragment {
case RememberingSourceWriter.METHOD: case RememberingSourceWriter.METHOD:
if ((filter & FILTER_REF) != 0) { if ((filter & FILTER_REF) != 0) {
sink.appendMethod(methodDescriptors[intArgs[intArgIndex]]); sink.appendVirtualMethod(methodDescriptors[intArgs[intArgIndex]]);
} }
intArgIndex++; intArgIndex++;
break; break;
case RememberingSourceWriter.METHOD_BODY: case RememberingSourceWriter.METHOD_BODY:
if ((filter & FILTER_REF) != 0) { if ((filter & FILTER_REF) != 0) {
sink.appendMethodBody(methods[intArgs[intArgIndex]]); sink.appendMethod(methods[intArgs[intArgIndex]]);
} }
intArgIndex++; intArgIndex++;
break; break;
@ -170,6 +170,27 @@ public class RememberedSource implements SourceFragment {
} }
break; 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: case RememberingSourceWriter.EMIT_LOCATION:
if ((filter & FILTER_DEBUG) != 0) { if ((filter & FILTER_DEBUG) != 0) {
var fileIndex = intArgs[intArgIndex]; var fileIndex = intArgs[intArgIndex];

View File

@ -42,6 +42,10 @@ public class RememberingSourceWriter extends SourceWriter {
static final byte SOFT_NEW_LINE = 12; static final byte SOFT_NEW_LINE = 12;
static final byte INDENT = 13; static final byte INDENT = 13;
static final byte OUTDENT = 14; 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 EMIT_LOCATION = 15;
static final byte ENTER_LOCATION = 16; static final byte ENTER_LOCATION = 16;
static final byte EXIT_LOCATION = 17; static final byte EXIT_LOCATION = 17;
@ -73,6 +77,8 @@ public class RememberingSourceWriter extends SourceWriter {
private List<MethodReference> methods = new ArrayList<>(); private List<MethodReference> methods = new ArrayList<>();
private ObjectIntMap<MethodReference> methodIndexes = new ObjectIntHashMap<>(); private ObjectIntMap<MethodReference> methodIndexes = new ObjectIntHashMap<>();
private boolean isInDeclaration;
public RememberingSourceWriter(boolean debug) { public RememberingSourceWriter(boolean debug) {
this.debug = debug; this.debug = debug;
} }
@ -129,7 +135,7 @@ public class RememberingSourceWriter extends SourceWriter {
} }
@Override @Override
public SourceWriter appendMethod(MethodDescriptor method) { public SourceWriter appendVirtualMethod(MethodDescriptor method) {
flush(); flush();
commands.add(METHOD); commands.add(METHOD);
appendMethodDescriptorArg(method); appendMethodDescriptorArg(method);
@ -137,7 +143,7 @@ public class RememberingSourceWriter extends SourceWriter {
} }
@Override @Override
public SourceWriter appendMethodBody(MethodReference method) { public SourceWriter appendMethod(MethodReference method) {
flush(); flush();
commands.add(METHOD_BODY); commands.add(METHOD_BODY);
appendMethodArg(method); appendMethodArg(method);
@ -225,6 +231,49 @@ public class RememberingSourceWriter extends SourceWriter {
return this; 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 @Override
public SourceWriter emitLocation(String fileName, int line) { public SourceWriter emitLocation(String fileName, int line) {
if (debug) { if (debug) {

View File

@ -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;
}
}

View File

@ -76,26 +76,38 @@ public abstract class SourceWriter implements Appendable, SourceWriterSink {
public abstract SourceWriter appendStaticField(FieldReference field); public abstract SourceWriter appendStaticField(FieldReference field);
@Override @Override
public abstract SourceWriter appendMethod(MethodDescriptor method); public abstract SourceWriter appendVirtualMethod(MethodDescriptor method);
public SourceWriter appendMethod(String name, Class<?>... params) { public SourceWriter appendVirtualMethod(String name, Class<?>... params) {
return appendMethod(new MethodDescriptor(name, params)); return appendVirtualMethod(new MethodDescriptor(name, params));
} }
@Override @Override
public abstract SourceWriter appendMethodBody(MethodReference method); public abstract SourceWriter appendMethod(MethodReference method);
public SourceWriter appendMethodBody(String className, String name, ValueType... params) { public SourceWriter appendMethod(String className, String name, ValueType... params) {
return appendMethodBody(new MethodReference(className, new MethodDescriptor(name, params))); return appendMethod(new MethodReference(className, new MethodDescriptor(name, params)));
} }
public SourceWriter appendMethodBody(Class<?> cls, String name, Class<?>... params) { public SourceWriter appendMethod(Class<?> cls, String name, Class<?>... params) {
return appendMethodBody(new MethodReference(cls, name, params)); return appendMethod(new MethodReference(cls, name, params));
} }
@Override @Override
public abstract SourceWriter appendFunction(String name); 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 @Override
public abstract SourceWriter appendGlobal(String name); public abstract SourceWriter appendGlobal(String name);

View File

@ -36,11 +36,27 @@ public interface SourceWriterSink {
return this; return this;
} }
default SourceWriterSink appendMethod(MethodDescriptor method) { default SourceWriterSink appendVirtualMethod(MethodDescriptor method) {
return this; 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; return this;
} }

View File

@ -98,12 +98,18 @@ public class AstWriter {
private Set<String> aliases = new HashSet<>(); private Set<String> aliases = new HashSet<>();
private Function<String, NameEmitter> globalNameWriter; private Function<String, NameEmitter> globalNameWriter;
public final Map<String, Scope> currentScopes = new HashMap<>(); public final Map<String, Scope> currentScopes = new HashMap<>();
protected final Set<Scope> topLevelScopes = new HashSet<>();
private boolean inFunction;
public AstWriter(SourceWriter writer, Function<String, NameEmitter> globalNameWriter) { public AstWriter(SourceWriter writer, Function<String, NameEmitter> globalNameWriter) {
this.writer = writer; this.writer = writer;
this.globalNameWriter = globalNameWriter; this.globalNameWriter = globalNameWriter;
} }
protected final boolean inFunction() {
return inFunction;
}
public void declareName(String name) { public void declareName(String name) {
if (nameMap.containsKey(name)) { if (nameMap.containsKey(name)) {
return; return;
@ -125,10 +131,6 @@ public class AstWriter {
nameMap.put(name, emitter); nameMap.put(name, emitter);
} }
public void hoist(Object node) {
hoist((AstNode) node);
}
public void hoist(AstNode node) { public void hoist(AstNode node) {
declareName("arguments"); declareName("arguments");
node.visit(n -> { node.visit(n -> {
@ -162,11 +164,11 @@ public class AstWriter {
print((AstNode) node, precedence); print((AstNode) node, precedence);
} }
public void print(AstNode node) { public boolean print(AstNode node) {
print(node, PRECEDENCE_COMMA); return print(node, PRECEDENCE_COMMA);
} }
public void print(AstNode node, int precedence) { public boolean print(AstNode node, int precedence) {
switch (node.getType()) { switch (node.getType()) {
case Token.SCRIPT: case Token.SCRIPT:
print((AstRoot) node); print((AstRoot) node);
@ -176,8 +178,7 @@ public class AstWriter {
print((FunctionCall) node, precedence); print((FunctionCall) node, precedence);
break; break;
case Token.FUNCTION: case Token.FUNCTION:
print((FunctionNode) node); return print((FunctionNode) node);
break;
case Token.ARRAYCOMP: case Token.ARRAYCOMP:
print((ArrayComprehension) node); print((ArrayComprehension) node);
break; break;
@ -287,8 +288,7 @@ public class AstWriter {
case Token.CONST: case Token.CONST:
case Token.VAR: case Token.VAR:
case Token.LET: case Token.LET:
print((VariableDeclaration) node); return print((VariableDeclaration) node);
break;
case Token.WHILE: case Token.WHILE:
print((WhileLoop) node); print((WhileLoop) node);
break; break;
@ -302,20 +302,23 @@ public class AstWriter {
} }
break; break;
} }
return false;
} }
private void print(AstRoot node) { private void print(AstRoot node) {
for (Node child : node) { for (Node child : node) {
print((AstNode) child); if (!print((AstNode) child)) {
writer.softNewLine(); writer.softNewLine();
}
} }
} }
private void print(Block node) { private void print(Block node) {
writer.append('{').softNewLine().indent(); writer.append('{').softNewLine().indent();
for (Node child = node.getFirstChild(); child != null; child = child.getNext()) { for (Node child = node.getFirstChild(); child != null; child = child.getNext()) {
print((AstNode) child); if (!print((AstNode) child)) {
writer.softNewLine(); writer.softNewLine();
}
} }
writer.outdent().append('}'); writer.outdent().append('}');
} }
@ -324,8 +327,9 @@ public class AstWriter {
var scope = enterScope(node); var scope = enterScope(node);
writer.append('{').softNewLine().indent(); writer.append('{').softNewLine().indent();
for (Node child = node.getFirstChild(); child != null; child = child.getNext()) { for (Node child = node.getFirstChild(); child != null; child = child.getNext()) {
print((AstNode) child); if (!print((AstNode) child)) {
writer.softNewLine(); writer.softNewLine();
}
} }
writer.outdent().append('}'); writer.outdent().append('}');
leaveScope(scope, node); leaveScope(scope, node);
@ -456,7 +460,7 @@ public class AstWriter {
print(node.getTryBlock()); print(node.getTryBlock());
for (CatchClause cc : node.getCatchClauses()) { for (CatchClause cc : node.getCatchClauses()) {
writer.ws().append("catch").ws().append('('); writer.ws().append("catch").ws().append('(');
print(cc.getVarName()); writer.append(cc.getVarName().getIdentifier());
if (cc.getCatchCondition() != null) { if (cc.getCatchCondition() != null) {
writer.append(" if "); writer.append(" if ");
print(cc.getCatchCondition()); 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()) { switch (node.getType()) {
case Token.VAR: case Token.VAR:
writer.append("var "); writer.append("var ");
@ -492,6 +504,23 @@ public class AstWriter {
if (node.isStatement()) { if (node.isStatement()) {
writer.append(';'); 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) { private void print(VariableInitializer node) {
@ -575,7 +604,7 @@ public class AstWriter {
return false; return false;
} }
writer.appendMethodBody(method).append('('); writer.appendMethod(method).append('(');
printList(node.getArguments()); printList(node.getArguments());
writer.append(')'); writer.append(')');
return true; return true;
@ -722,9 +751,18 @@ public class AstWriter {
print(node.getRight()); print(node.getRight());
} }
protected void print(FunctionNode node) { protected boolean print(FunctionNode node) {
var scope = enterScope(node);
var isArrow = node.getFunctionType() == FunctionNode.ARROW_FUNCTION; 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) { if (!isArrow) {
currentScopes.put("arguments", node); currentScopes.put("arguments", node);
} }
@ -760,6 +798,23 @@ public class AstWriter {
} }
leaveScope(scope, node); 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) { private void print(LetNode node) {
@ -980,6 +1035,9 @@ public class AstWriter {
} }
protected void onEnterScope(Scope scope) { protected void onEnterScope(Scope scope) {
if (isTopLevel() && !inFunction()) {
topLevelScopes.add(scope);
}
} }
private void leaveScope(Map<String, Scope> backup, Scope scope) { private void leaveScope(Map<String, Scope> backup, Scope scope) {
@ -994,9 +1052,16 @@ public class AstWriter {
} }
protected void onLeaveScope(Scope scope) { protected void onLeaveScope(Scope scope) {
if (isTopLevel() && !inFunction()) {
topLevelScopes.remove(scope);
}
} }
protected Scope scopeOfId(String id) { protected Scope scopeOfId(String id) {
return currentScopes.get(id); return currentScopes.get(id);
} }
protected boolean isTopLevel() {
return false;
}
} }

View File

@ -155,7 +155,7 @@ public class MethodBodyRenderer implements MethodNodeVisitor, GeneratorContext {
statementRenderer.setCurrentPart(0); statementRenderer.setCurrentPart(0);
if (method.getModifiers().contains(ElementModifier.SYNCHRONIZED)) { if (method.getModifiers().contains(ElementModifier.SYNCHRONIZED)) {
writer.appendMethodBody(NameFrequencyEstimator.MONITOR_ENTER_SYNC_METHOD); writer.appendMethod(NameFrequencyEstimator.MONITOR_ENTER_SYNC_METHOD);
writer.append("("); writer.append("(");
appendMonitor(statementRenderer, method); appendMonitor(statementRenderer, method);
writer.append(");").softNewLine(); writer.append(");").softNewLine();
@ -168,7 +168,7 @@ public class MethodBodyRenderer implements MethodNodeVisitor, GeneratorContext {
if (method.getModifiers().contains(ElementModifier.SYNCHRONIZED)) { if (method.getModifiers().contains(ElementModifier.SYNCHRONIZED)) {
writer.outdent().append("}").ws().append("finally").ws().append("{").indent().softNewLine(); 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("("); writer.append("(");
appendMonitor(statementRenderer, method); appendMonitor(statementRenderer, method);
writer.append(");").softNewLine(); writer.append(");").softNewLine();
@ -242,7 +242,7 @@ public class MethodBodyRenderer implements MethodNodeVisitor, GeneratorContext {
for (int i = 0; i < methodNode.getBody().size(); ++i) { for (int i = 0; i < methodNode.getBody().size(); ++i) {
writer.append("case ").append(i).append(":").indent().softNewLine(); writer.append("case ").append(i).append(":").indent().softNewLine();
if (i == 0 && methodNode.getModifiers().contains(ElementModifier.SYNCHRONIZED)) { if (i == 0 && methodNode.getModifiers().contains(ElementModifier.SYNCHRONIZED)) {
writer.appendMethodBody(NameFrequencyEstimator.MONITOR_ENTER_METHOD); writer.appendMethod(NameFrequencyEstimator.MONITOR_ENTER_METHOD);
writer.append("("); writer.append("(");
appendMonitor(statementRenderer, methodNode); appendMonitor(statementRenderer, methodNode);
writer.append(");").softNewLine(); writer.append(");").softNewLine();
@ -260,7 +260,7 @@ public class MethodBodyRenderer implements MethodNodeVisitor, GeneratorContext {
writer.outdent().append("}").ws().append("finally").ws().append('{').indent().softNewLine(); writer.outdent().append("}").ws().append("finally").ws().append('{').indent().softNewLine();
writer.append("if").ws().append("(!").appendFunction("$rt_suspending").append("())") writer.append("if").ws().append("(!").appendFunction("$rt_suspending").append("())")
.ws().append("{").indent().softNewLine(); .ws().append("{").indent().softNewLine();
writer.appendMethodBody(NameFrequencyEstimator.MONITOR_EXIT_METHOD).append("("); writer.appendMethod(NameFrequencyEstimator.MONITOR_EXIT_METHOD).append("(");
appendMonitor(statementRenderer, methodNode); appendMonitor(statementRenderer, methodNode);
writer.append(");").softNewLine(); writer.append(");").softNewLine();
writer.outdent().append('}').softNewLine(); writer.outdent().append('}').softNewLine();

View File

@ -38,6 +38,11 @@ public class NameFrequencyEstimator implements SourceWriterSink {
private Map<String, Entry> entries = new HashMap<>(); private Map<String, Entry> entries = new HashMap<>();
private Set<String> reservedNames = new HashSet<>(); private Set<String> reservedNames = new HashSet<>();
private boolean hasAdditionalScope;
public boolean hasAdditionalScope() {
return hasAdditionalScope;
}
@Override @Override
public SourceWriterSink appendClass(String cls) { public SourceWriterSink appendClass(String cls) {
@ -45,7 +50,7 @@ public class NameFrequencyEstimator implements SourceWriterSink {
var entry = entries.get(key); var entry = entries.get(key);
if (entry == null) { if (entry == null) {
entry = new Entry(); entry = new Entry();
entry.operation = naming -> naming.getNameFor(cls); entry.operation = naming -> naming.className(cls).scoped;
entries.put(key, entry); entries.put(key, entry);
} }
entry.frequency++; entry.frequency++;
@ -58,7 +63,10 @@ public class NameFrequencyEstimator implements SourceWriterSink {
var entry = entries.get(key); var entry = entries.get(key);
if (entry == null) { if (entry == null) {
entry = new Entry(); entry = new Entry();
entry.operation = naming -> naming.getNameFor(field); entry.operation = naming -> {
naming.instanceFieldName(field);
return false;
};
entries.put(key, entry); entries.put(key, entry);
} }
entry.frequency++; entry.frequency++;
@ -71,7 +79,7 @@ public class NameFrequencyEstimator implements SourceWriterSink {
var entry = entries.get(key); var entry = entries.get(key);
if (entry == null) { if (entry == null) {
entry = new Entry(); entry = new Entry();
entry.operation = naming -> naming.getFullNameFor(field); entry.operation = naming -> naming.fieldName(field).scoped;
entries.put(key, entry); entries.put(key, entry);
} }
entry.frequency++; entry.frequency++;
@ -79,12 +87,15 @@ public class NameFrequencyEstimator implements SourceWriterSink {
} }
@Override @Override
public SourceWriterSink appendMethod(MethodDescriptor method) { public SourceWriterSink appendVirtualMethod(MethodDescriptor method) {
var key = "r:" + method; var key = "r:" + method;
var entry = entries.get(key); var entry = entries.get(key);
if (entry == null) { if (entry == null) {
entry = new Entry(); entry = new Entry();
entry.operation = naming -> naming.getNameFor(method); entry.operation = naming -> {
naming.instanceMethodName(method);
return false;
};
entries.put(key, entry); entries.put(key, entry);
} }
entry.frequency++; entry.frequency++;
@ -92,12 +103,12 @@ public class NameFrequencyEstimator implements SourceWriterSink {
} }
@Override @Override
public SourceWriterSink appendMethodBody(MethodReference method) { public SourceWriterSink appendMethod(MethodReference method) {
var key = "R:" + method; var key = "R:" + method;
var entry = entries.get(key); var entry = entries.get(key);
if (entry == null) { if (entry == null) {
entry = new Entry(); entry = new Entry();
entry.operation = naming -> naming.getFullNameFor(method); entry.operation = naming -> naming.methodName(method).scoped;
entries.put(key, entry); entries.put(key, entry);
} }
entry.frequency++; entry.frequency++;
@ -110,7 +121,7 @@ public class NameFrequencyEstimator implements SourceWriterSink {
var entry = entries.get(key); var entry = entries.get(key);
if (entry == null) { if (entry == null) {
entry = new Entry(); entry = new Entry();
entry.operation = naming -> naming.getNameForFunction(name); entry.operation = naming -> naming.functionName(name).scoped;
entries.put(key, entry); entries.put(key, entry);
} }
entry.frequency++; entry.frequency++;
@ -129,7 +140,7 @@ public class NameFrequencyEstimator implements SourceWriterSink {
var entry = entries.get(key); var entry = entries.get(key);
if (entry == null) { if (entry == null) {
entry = new Entry(); entry = new Entry();
entry.operation = naming -> naming.getNameForInit(method); entry.operation = naming -> naming.initializerName(method).scoped;
entries.put(key, entry); entries.put(key, entry);
} }
entry.frequency++; entry.frequency++;
@ -142,7 +153,7 @@ public class NameFrequencyEstimator implements SourceWriterSink {
var entry = entries.get(key); var entry = entries.get(key);
if (entry == null) { if (entry == null) {
entry = new Entry(); entry = new Entry();
entry.operation = naming -> naming.getNameForClassInit(className); entry.operation = naming -> naming.classInitializerName(className).scoped;
entries.put(key, entry); entries.put(key, entry);
} }
entry.frequency++; entry.frequency++;
@ -156,7 +167,7 @@ public class NameFrequencyEstimator implements SourceWriterSink {
var entryList = new ArrayList<>(entries.values()); var entryList = new ArrayList<>(entries.values());
entryList.sort((o1, o2) -> Integer.compare(o2.frequency, o1.frequency)); entryList.sort((o1, o2) -> Integer.compare(o2.frequency, o1.frequency));
for (var entry : entryList) { 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 { private interface NamingOperation {
void perform(NamingStrategy naming); boolean perform(NamingStrategy naming);
} }
} }

View File

@ -188,7 +188,7 @@ public class Renderer implements RenderingManager {
writer.appendClass("java.lang.Object").append(".prototype.toString").ws().append("=").ws() writer.appendClass("java.lang.Object").append(".prototype.toString").ws().append("=").ws()
.append("function()").ws().append("{").indent().softNewLine(); .append("function()").ws().append("{").indent().softNewLine();
writer.append("return ").appendFunction("$rt_ustr").append("(") writer.append("return ").appendFunction("$rt_ustr").append("(")
.appendMethodBody(Object.class, "toString", String.class).append("(this));") .appendMethod(Object.class, "toString", String.class).append("(this));")
.softNewLine(); .softNewLine();
writer.outdent().append("};").newLine(); writer.outdent().append("};").newLine();
} }
@ -265,7 +265,6 @@ public class Renderer implements RenderingManager {
renderFullClassFunctionDeclaration(cls, nonStaticFields); renderFullClassFunctionDeclaration(cls, nonStaticFields);
} }
var hasLet = false;
for (FieldHolder field : staticFields) { for (FieldHolder field : staticFields) {
Object value = field.getInitialValue(); Object value = field.getInitialValue();
if (value == null) { if (value == null) {
@ -278,23 +277,16 @@ public class Renderer implements RenderingManager {
value = null; value = null;
} }
if (!hasLet) { writer.startVariableDeclaration().appendStaticField(fieldRef);
writer.append("let ");
hasLet = true;
} else {
writer.append(",").ws();
}
writer.appendStaticField(fieldRef).ws().append("=").ws();
context.constantToString(writer, value); context.constantToString(writer, value);
} writer.endDeclaration();
if (hasLet) {
writer.append(";").newLine();
} }
} }
private void renderFullClassFunctionDeclaration(ClassReader cls, List<FieldHolder> nonStaticFields) { private void renderFullClassFunctionDeclaration(ClassReader cls, List<FieldHolder> nonStaticFields) {
boolean thisAliased = false; 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) { if (nonStaticFields.size() > 1) {
thisAliased = true; thisAliased = true;
writer.append("let a").ws().append("=").ws().append("this;").ws(); writer.append("let a").ws().append("=").ws().append("this;").ws();
@ -321,18 +313,18 @@ public class Renderer implements RenderingManager {
} }
writer.outdent().append("}"); writer.outdent().append("}");
writer.newLine(); writer.endDeclaration();
} }
private void renderShortClassFunctionDeclaration(ClassReader cls) { private void renderShortClassFunctionDeclaration(ClassReader cls) {
writer.append("let ").appendClass(cls.getName()).ws().append("=").ws() writer.startVariableDeclaration().appendClass(cls.getName())
.appendFunction("$rt_classWithoutFields").append("("); .appendFunction("$rt_classWithoutFields").append("(");
if (cls.hasModifier(ElementModifier.INTERFACE)) { if (cls.hasModifier(ElementModifier.INTERFACE)) {
writer.append("0"); writer.append("0");
} else if (!cls.getParent().equals("java.lang.Object")) { } else if (!cls.getParent().equals("java.lang.Object")) {
writer.appendClass(cls.getParent()); writer.appendClass(cls.getParent());
} }
writer.append(");").newLine(); writer.append(")").endDeclaration();
} }
private void renderMethodBodies(ClassHolder cls, Decompiler decompiler) { private void renderMethodBodies(ClassHolder cls, Decompiler decompiler) {
@ -346,27 +338,19 @@ public class Renderer implements RenderingManager {
var needsInitializers = !cls.hasModifier(ElementModifier.INTERFACE) var needsInitializers = !cls.hasModifier(ElementModifier.INTERFACE)
&& !cls.hasModifier(ElementModifier.ABSTRACT); && !cls.hasModifier(ElementModifier.ABSTRACT);
var hasLet = false;
for (var method : cls.getMethods()) { for (var method : cls.getMethods()) {
if (!filterMethod(method)) { if (!filterMethod(method)) {
continue; continue;
} }
if (!hasLet) { writer.startVariableDeclaration();
writer.append("let ");
hasLet = true;
} else {
writer.append(",").newLine();
}
renderBody(method, decompiler); renderBody(method, decompiler);
writer.endDeclaration();
if (needsInitializers && !method.hasModifier(ElementModifier.STATIC) if (needsInitializers && !method.hasModifier(ElementModifier.STATIC)
&& method.getName().equals("<init>")) { && method.getName().equals("<init>")) {
writer.append(",").newLine();
renderInitializer(method); renderInitializer(method);
} }
} }
if (hasLet) {
writer.append(";").newLine();
}
writer.emitClass(null); writer.emitClass(null);
} }
@ -390,11 +374,10 @@ public class Renderer implements RenderingManager {
var clinitCalledField = new FieldReference(cls.getName(), "$_teavm_clinitCalled_$"); var clinitCalledField = new FieldReference(cls.getName(), "$_teavm_clinitCalled_$");
if (isAsync) { if (isAsync) {
writer.append("let ").appendStaticField(clinitCalledField).ws().append("=").ws().append("false;") writer.startVariableDeclaration().appendStaticField(clinitCalledField).append("false").endDeclaration();
.softNewLine();
} }
writer.append("let ").appendClassInit(cls.getName()).ws().append("=").ws(); writer.startVariableDeclaration().appendClassInit(cls.getName());
writer.append("()").sameLineWs().append("=>").ws().append("{").softNewLine().indent(); writer.append("()").sameLineWs().append("=>").ws().append("{").softNewLine().indent();
if (isAsync) { if (isAsync) {
@ -423,7 +406,7 @@ public class Renderer implements RenderingManager {
writer.outdent().append("case 1:").indent().softNewLine(); 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(); .append("();").softNewLine();
if (isAsync) { if (isAsync) {
@ -438,8 +421,7 @@ public class Renderer implements RenderingManager {
writer.appendFunction("$rt_nativeThread").append("().push(" + context.pointerName() + ");").softNewLine(); writer.appendFunction("$rt_nativeThread").append("().push(" + context.pointerName() + ");").softNewLine();
} }
writer.outdent().append("};"); writer.outdent().append("}").endDeclaration();
writer.newLine();
} }
private void renderEraseClinit(ClassReader cls) { private void renderEraseClinit(ClassReader cls) {
@ -696,7 +678,7 @@ public class Renderer implements RenderingManager {
private void renderInitializer(MethodReader method) { private void renderInitializer(MethodReader method) {
MethodReference ref = method.getReference(); MethodReference ref = method.getReference();
writer.emitMethod(ref.getDescriptor()); writer.emitMethod(ref.getDescriptor());
writer.appendInit(ref).ws().append("=").ws(); writer.startVariableDeclaration().appendInit(ref);
if (ref.parameterCount() != 1) { if (ref.parameterCount() != 1) {
writer.append("("); writer.append("(");
} }
@ -714,14 +696,14 @@ public class Renderer implements RenderingManager {
String instanceName = variableNameForInitializer(ref.parameterCount()); String instanceName = variableNameForInitializer(ref.parameterCount());
writer.append("let " + instanceName).ws().append("=").ws().append("new ").appendClass( writer.append("let " + instanceName).ws().append("=").ws().append("new ").appendClass(
ref.getClassName()).append("();").softNewLine(); ref.getClassName()).append("();").softNewLine();
writer.appendMethodBody(ref).append("(" + instanceName); writer.appendMethod(ref).append("(" + instanceName);
for (int i = 0; i < ref.parameterCount(); ++i) { for (int i = 0; i < ref.parameterCount(); ++i) {
writer.append(",").ws(); writer.append(",").ws();
writer.append(variableNameForInitializer(i)); writer.append(variableNameForInitializer(i));
} }
writer.append(");").softNewLine(); writer.append(");").softNewLine();
writer.append("return " + instanceName + ";").softNewLine(); writer.append("return " + instanceName + ";").softNewLine();
writer.outdent().append("}"); writer.outdent().append("}").endDeclaration();
writer.emitMethod(null); writer.emitMethod(null);
} }
@ -753,7 +735,7 @@ public class Renderer implements RenderingManager {
} }
private void emitVirtualDeclaration(MethodReference ref) { 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("\"").append(methodName).append("\"");
writer.append(",").ws(); writer.append(",").ws();
emitVirtualFunctionWrapper(ref); emitVirtualFunctionWrapper(ref);
@ -762,7 +744,7 @@ public class Renderer implements RenderingManager {
private void emitVirtualFunctionWrapper(MethodReference method) { private void emitVirtualFunctionWrapper(MethodReference method) {
if (method.parameterCount() <= 4) { if (method.parameterCount() <= 4) {
writer.appendFunction("$rt_wrapFunction" + method.parameterCount()); writer.appendFunction("$rt_wrapFunction" + method.parameterCount());
writer.append("(").appendMethodBody(method).append(")"); writer.append("(").appendMethod(method).append(")");
return; return;
} }
@ -781,7 +763,7 @@ public class Renderer implements RenderingManager {
if (method.getDescriptor().getResultType() != ValueType.VOID) { if (method.getDescriptor().getResultType() != ValueType.VOID) {
writer.append("return "); writer.append("return ");
} }
writer.appendMethodBody(method).append("("); writer.appendMethod(method).append("(");
writer.append("this"); writer.append("this");
for (String arg : args) { for (String arg : args) {
writer.append(",").ws().append(arg); writer.append(",").ws().append(arg);
@ -793,7 +775,7 @@ public class Renderer implements RenderingManager {
MethodReference ref = method.getReference(); MethodReference ref = method.getReference();
writer.emitMethod(ref.getDescriptor()); writer.emitMethod(ref.getDescriptor());
writer.appendMethodBody(ref).ws().append("=").ws(); writer.appendMethod(ref);
if (method.hasModifier(ElementModifier.NATIVE)) { if (method.hasModifier(ElementModifier.NATIVE)) {
renderNativeBody(method, classSource); renderNativeBody(method, classSource);
} else { } else {

View File

@ -28,7 +28,6 @@ import org.mozilla.javascript.ast.AstRoot;
import org.teavm.backend.javascript.codegen.SourceWriter; import org.teavm.backend.javascript.codegen.SourceWriter;
import org.teavm.backend.javascript.codegen.SourceWriterSink; import org.teavm.backend.javascript.codegen.SourceWriterSink;
import org.teavm.backend.javascript.templating.AstRemoval; 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.RemovablePartsFinder;
import org.teavm.backend.javascript.templating.TemplatingAstTransformer; import org.teavm.backend.javascript.templating.TemplatingAstTransformer;
import org.teavm.backend.javascript.templating.TemplatingAstWriter; import org.teavm.backend.javascript.templating.TemplatingAstWriter;
@ -69,13 +68,13 @@ public class RuntimeRenderer {
public void renderRuntime() { public void renderRuntime() {
for (var ast : runtimeAstParts) { for (var ast : runtimeAstParts) {
renderHandWrittenRuntime(ast); renderRuntimePart(ast);
} }
} }
public void renderEpilogue() { public void renderEpilogue() {
for (var ast : epilogueAstParts) { for (var ast : epilogueAstParts) {
renderHandWrittenRuntime(ast); renderRuntimePart(ast);
} }
} }
@ -87,7 +86,7 @@ public class RuntimeRenderer {
return ast; return ast;
} }
private void renderHandWrittenRuntime(AstRoot ast) { private void renderRuntimePart(AstRoot ast) {
var astWriter = new TemplatingAstWriter(writer, null, null, classInitializerInfo); var astWriter = new TemplatingAstWriter(writer, null, null, classInitializerInfo);
astWriter.hoist(ast); astWriter.hoist(ast);
astWriter.print(ast); astWriter.print(ast);
@ -118,14 +117,11 @@ public class RuntimeRenderer {
public void removeUnusedParts() { public void removeUnusedParts() {
var removal = new AstRemoval(removablePartsFinder.getAllRemovableParts()); var removal = new AstRemoval(removablePartsFinder.getAllRemovableParts());
var letJoiner = new LetJoiner();
for (var part : runtimeAstParts) { for (var part : runtimeAstParts) {
removal.visit(part); removal.visit(part);
letJoiner.visit(part);
} }
for (var part : epilogueAstParts) { for (var part : epilogueAstParts) {
removal.visit(part); removal.visit(part);
letJoiner.visit(part);
} }
} }
} }

View File

@ -1100,10 +1100,10 @@ public class StatementRenderer implements ExprVisitor, StatementVisitor {
expr.getArguments().get(0).acceptVisitor(this); expr.getArguments().get(0).acceptVisitor(this);
} }
MethodReference method = expr.getMethod(); MethodReference method = expr.getMethod();
String name = naming.getNameFor(method.getDescriptor()); String name = naming.instanceMethodName(method.getDescriptor());
switch (expr.getType()) { switch (expr.getType()) {
case STATIC: case STATIC:
writer.appendMethodBody(method).append("("); writer.appendMethod(method).append("(");
for (int i = 0; i < expr.getArguments().size(); ++i) { for (int i = 0; i < expr.getArguments().size(); ++i) {
if (i > 0) { if (i > 0) {
writer.append(",").ws(); writer.append(",").ws();
@ -1113,7 +1113,7 @@ public class StatementRenderer implements ExprVisitor, StatementVisitor {
} }
break; break;
case SPECIAL: case SPECIAL:
writer.appendMethodBody(method).append("("); writer.appendMethod(method).append("(");
precedence = Precedence.min(); precedence = Precedence.min();
expr.getArguments().get(0).acceptVisitor(this); expr.getArguments().get(0).acceptVisitor(this);
for (int i = 1; i < expr.getArguments().size(); ++i) { for (int i = 1; i < expr.getArguments().size(); ++i) {
@ -1507,13 +1507,13 @@ public class StatementRenderer implements ExprVisitor, StatementVisitor {
@Override @Override
public void visit(MonitorEnterStatement statement) { public void visit(MonitorEnterStatement statement) {
if (async) { if (async) {
writer.appendMethodBody(NameFrequencyEstimator.MONITOR_ENTER_METHOD).append("("); writer.appendMethod(NameFrequencyEstimator.MONITOR_ENTER_METHOD).append("(");
precedence = Precedence.min(); precedence = Precedence.min();
statement.getObjectRef().acceptVisitor(this); statement.getObjectRef().acceptVisitor(this);
writer.append(");").softNewLine(); writer.append(");").softNewLine();
emitSuspendChecker(); emitSuspendChecker();
} else { } else {
writer.appendMethodBody(NameFrequencyEstimator.MONITOR_ENTER_SYNC_METHOD).append('('); writer.appendMethod(NameFrequencyEstimator.MONITOR_ENTER_SYNC_METHOD).append('(');
precedence = Precedence.min(); precedence = Precedence.min();
statement.getObjectRef().acceptVisitor(this); statement.getObjectRef().acceptVisitor(this);
writer.append(");").softNewLine(); writer.append(");").softNewLine();
@ -1530,12 +1530,12 @@ public class StatementRenderer implements ExprVisitor, StatementVisitor {
@Override @Override
public void visit(MonitorExitStatement statement) { public void visit(MonitorExitStatement statement) {
if (async) { if (async) {
writer.appendMethodBody(NameFrequencyEstimator.MONITOR_EXIT_METHOD).append("("); writer.appendMethod(NameFrequencyEstimator.MONITOR_EXIT_METHOD).append("(");
precedence = Precedence.min(); precedence = Precedence.min();
statement.getObjectRef().acceptVisitor(this); statement.getObjectRef().acceptVisitor(this);
writer.append(");").softNewLine(); writer.append(");").softNewLine();
} else { } else {
writer.appendMethodBody(NameFrequencyEstimator.MONITOR_EXIT_SYNC_METHOD).append('('); writer.appendMethod(NameFrequencyEstimator.MONITOR_EXIT_SYNC_METHOD).append('(');
precedence = Precedence.min(); precedence = Precedence.min();
statement.getObjectRef().acceptVisitor(this); statement.getObjectRef().acceptVisitor(this);
writer.append(");").softNewLine(); writer.append(");").softNewLine();

View File

@ -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;
}
}
}

View File

@ -16,9 +16,7 @@
package org.teavm.backend.javascript.templating; package org.teavm.backend.javascript.templating;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.Map; import java.util.Map;
import java.util.Set;
import org.mozilla.javascript.ast.ElementGet; import org.mozilla.javascript.ast.ElementGet;
import org.mozilla.javascript.ast.FunctionCall; import org.mozilla.javascript.ast.FunctionCall;
import org.mozilla.javascript.ast.FunctionNode; import org.mozilla.javascript.ast.FunctionNode;
@ -39,8 +37,6 @@ public class TemplatingAstWriter extends AstWriter {
private Scope scope; private Scope scope;
private Map<String, SourceFragment> fragments = new HashMap<>(); private Map<String, SourceFragment> fragments = new HashMap<>();
private ClassInitializerInfo classInitializerInfo; private ClassInitializerInfo classInitializerInfo;
private Set<Scope> topLevelScopes = new HashSet<>();
private boolean inFunction;
public TemplatingAstWriter(SourceWriter writer, Map<String, SourceFragment> names, Scope scope, public TemplatingAstWriter(SourceWriter writer, Map<String, SourceFragment> names, Scope scope,
ClassInitializerInfo classInitializerInfo) { ClassInitializerInfo classInitializerInfo) {
@ -113,7 +109,7 @@ public class TemplatingAstWriter extends AstWriter {
} }
var method = new MethodReference(((StringLiteral) classArg).getValue(), var method = new MethodReference(((StringLiteral) classArg).getValue(),
MethodDescriptor.parse(((StringLiteral) methodArg).getValue())); MethodDescriptor.parse(((StringLiteral) methodArg).getValue()));
writer.appendMethodBody(method); writer.appendMethod(method);
return true; return true;
} }
@ -210,7 +206,7 @@ public class TemplatingAstWriter extends AstWriter {
} }
var method = MethodDescriptor.parse(((StringLiteral) arg).getValue()); var method = MethodDescriptor.parse(((StringLiteral) arg).getValue());
print(get.getTarget()); print(get.getTarget());
writer.append('.').appendMethod(method); writer.append('.').appendVirtualMethod(method);
return true; return true;
} }
@ -250,27 +246,7 @@ public class TemplatingAstWriter extends AstWriter {
} }
@Override @Override
protected void print(FunctionNode node) { protected boolean isTopLevel() {
if (inFunction) { return names == null;
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);
}
} }
} }

View File

@ -50,8 +50,8 @@ class JSAliasRenderer implements RendererListener, VirtualMethodContributor {
return; return;
} }
writer.append("let ").appendFunction("$rt_jso_marker").ws().append("=").ws() writer.startVariableDeclaration().appendFunction("$rt_jso_marker")
.appendGlobal("Symbol").append("('jsoClass');").newLine(); .appendGlobal("Symbol").append("('jsoClass')").endDeclaration();
writer.append("(function()").ws().append("{").softNewLine().indent(); writer.append("(function()").ws().append("{").softNewLine().indent();
writer.append("var c;").softNewLine(); writer.append("var c;").softNewLine();
for (String className : classSource.getClassNames()) { for (String className : classSource.getClassNames()) {
@ -100,7 +100,7 @@ class JSAliasRenderer implements RendererListener, VirtualMethodContributor {
} else { } else {
writer.append("c.").append(aliasEntry.getKey()); 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(); .append(";").softNewLine();
} }
for (var aliasEntry : properties.entrySet()) { for (var aliasEntry : properties.entrySet()) {
@ -111,10 +111,10 @@ class JSAliasRenderer implements RendererListener, VirtualMethodContributor {
writer.append("Object.defineProperty(c,") writer.append("Object.defineProperty(c,")
.ws().append("\"").append(aliasEntry.getKey()).append("\",") .ws().append("\"").append(aliasEntry.getKey()).append("\",")
.ws().append("{").indent().softNewLine(); .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) { if (propInfo.setter != null && classReader.getMethod(propInfo.setter) != null) {
writer.append(",").softNewLine(); 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(); writer.softNewLine().outdent().append("});").softNewLine();
} }
@ -198,7 +198,7 @@ class JSAliasRenderer implements RendererListener, VirtualMethodContributor {
writer.append("this.").appendField(functorField).ws().append('=').ws().append("function("); writer.append("this.").appendField(functorField).ws().append('=').ws().append("function(");
appendArguments(functorMethod.parameterCount()); appendArguments(functorMethod.parameterCount());
writer.append(")").ws().append('{').indent().softNewLine(); writer.append(")").ws().append('{').indent().softNewLine();
writer.append("return self.").appendMethod(functorMethod).append('('); writer.append("return self.").appendVirtualMethod(functorMethod).append('(');
appendArguments(functorMethod.parameterCount()); appendArguments(functorMethod.parameterCount());
writer.append(");").softNewLine(); writer.append(");").softNewLine();
writer.outdent().append("};").softNewLine(); writer.outdent().append("};").softNewLine();

View File

@ -69,8 +69,8 @@ class JSBodyBloatedEmitter implements JSBodyEmitter {
private void emit(SourceWriter writer, EmissionStrategy strategy) { private void emit(SourceWriter writer, EmissionStrategy strategy) {
int bodyParamCount = isStatic ? method.parameterCount() : method.parameterCount() - 1; int bodyParamCount = isStatic ? method.parameterCount() : method.parameterCount() - 1;
writer.append("if (!").appendMethodBody(method).append(".$native)").ws().append('{').indent().newLine(); writer.append("if (!").appendMethod(method).append(".$native)").ws().append('{').indent().newLine();
writer.appendMethodBody(method).append(".$native").ws().append('=').ws().append("function("); writer.appendMethod(method).append(".$native").ws().append('=').ws().append("function(");
int count = method.parameterCount(); int count = method.parameterCount();
var first = true; var first = true;
@ -135,11 +135,11 @@ class JSBodyBloatedEmitter implements JSBodyEmitter {
writer.append(");").softNewLine(); writer.append(");").softNewLine();
writer.outdent().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(); .softNewLine();
writer.outdent().append("}").softNewLine(); writer.outdent().append("}").softNewLine();
writer.append("return ").appendMethodBody(method).append('('); writer.append("return ").appendMethod(method).append('(');
for (int i = 0; i < count; ++i) { for (int i = 0; i < count; ++i) {
if (i > 0) { if (i > 0) {
writer.append(',').ws(); writer.append(',').ws();

View File

@ -49,7 +49,7 @@ public class AsyncMethodGenerator implements Generator, DependencyPlugin, Virtua
template.builder("asyncMethod") template.builder("asyncMethod")
.withContext(context) .withContext(context)
.withFragment("callMethod", (w, p) -> { .withFragment("callMethod", (w, p) -> {
w.appendMethodBody(asyncRef).append('('); w.appendMethod(asyncRef).append('(');
ClassReader cls = context.getClassSource().get(methodRef.getClassName()); ClassReader cls = context.getClassSource().get(methodRef.getClassName());
MethodReader method = cls.getMethod(methodRef.getDescriptor()); MethodReader method = cls.getMethod(methodRef.getDescriptor());
int start = method.hasModifier(ElementModifier.STATIC) ? 1 : 0; int start = method.hasModifier(ElementModifier.STATIC) ? 1 : 0;

View File

@ -132,7 +132,7 @@ public class PlatformGenerator implements Generator, Injector, DependencyPlugin
MethodReader method = cls.getMethod(new MethodDescriptor("<init>", void.class)); MethodReader method = cls.getMethod(new MethodDescriptor("<init>", void.class));
if (method != null) { if (method != null) {
writer.appendClass(clsName).append("[c]").ws().append("=").ws() 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)))); ValueType.arrayOf(ValueType.object(clsName))));
if (method != null) { if (method != null) {
writer.appendClass(clsName).append("[c]").ws().append("=").ws(); writer.appendClass(clsName).append("[c]").ws().append("=").ws();
writer.appendMethodBody(method.getReference()); writer.appendMethod(method.getReference());
writer.append(";").softNewLine(); writer.append(";").softNewLine();
} }
} }
MethodReference selfRef = new MethodReference(Platform.class, "getEnumConstants", MethodReference selfRef = new MethodReference(Platform.class, "getEnumConstants",
PlatformClass.class, Enum[].class); 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(); .append("{").softNewLine().indent();
writer.append("if").ws().append("(!cls.hasOwnProperty(c))").ws().append("{").indent().softNewLine(); writer.append("if").ws().append("(!cls.hasOwnProperty(c))").ws().append("{").indent().softNewLine();
writer.append("return null;").softNewLine(); writer.append("return null;").softNewLine();
@ -178,7 +178,7 @@ public class PlatformGenerator implements Generator, Injector, DependencyPlugin
writer.append("return cls[c];").softNewLine(); writer.append("return cls[c];").softNewLine();
writer.outdent().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(); .append(");").softNewLine();
} }
@ -196,15 +196,16 @@ public class PlatformGenerator implements Generator, Injector, DependencyPlugin
MethodReference selfRef = new MethodReference(Platform.class, "getAnnotations", PlatformClass.class, MethodReference selfRef = new MethodReference(Platform.class, "getAnnotations", PlatformClass.class,
Annotation[].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(); .append("{").softNewLine().indent();
writer.append("if").ws().append("(!cls.hasOwnProperty(c))").ws().append("{").indent().softNewLine(); writer.append("if").ws().append("(!cls.hasOwnProperty(c))").ws().append("{").indent().softNewLine();
writer.append("return null;").softNewLine(); writer.append("return null;").softNewLine();
writer.outdent().append("}").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.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(); .append(");").softNewLine();
} }
} }

View File

@ -241,6 +241,15 @@ public final class TeaVMRunner {
tool.setObfuscated(commandLine.hasOption("m")); tool.setObfuscated(commandLine.hasOption("m"));
tool.setStrict(commandLine.hasOption("strict")); tool.setStrict(commandLine.hasOption("strict"));
parseJsModuleOption(); 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() { private void parseJsModuleOption() {

View File

@ -78,6 +78,7 @@ public class TeaVMTool {
private boolean obfuscated = true; private boolean obfuscated = true;
private JSModuleType jsModuleType = JSModuleType.UMD; private JSModuleType jsModuleType = JSModuleType.UMD;
private boolean strict; private boolean strict;
private int maxTopLevelNames = 80_000;
private String mainClass; private String mainClass;
private String entryPointName = "main"; private String entryPointName = "main";
private Properties properties = new Properties(); private Properties properties = new Properties();
@ -139,6 +140,10 @@ public class TeaVMTool {
this.strict = strict; this.strict = strict;
} }
public void setMaxTopLevelNames(int maxTopLevelNames) {
this.maxTopLevelNames = maxTopLevelNames;
}
public boolean isIncremental() { public boolean isIncremental() {
return incremental; return incremental;
} }
@ -336,6 +341,7 @@ public class TeaVMTool {
javaScriptTarget = new JavaScriptTarget(); javaScriptTarget = new JavaScriptTarget();
javaScriptTarget.setObfuscated(obfuscated); javaScriptTarget.setObfuscated(obfuscated);
javaScriptTarget.setStrict(strict); javaScriptTarget.setStrict(strict);
javaScriptTarget.setMaxTopLevelNames(maxTopLevelNames);
debugEmitter = debugInformationGenerated || sourceMapsFileGenerated debugEmitter = debugInformationGenerated || sourceMapsFileGenerated
? new DebugInformationBuilder(referenceCache) : null; ? new DebugInformationBuilder(referenceCache) : null;

View File

@ -63,6 +63,8 @@ public interface BuildStrategy {
void setJsModuleType(JSModuleType jsModuleType); void setJsModuleType(JSModuleType jsModuleType);
void setMaxTopLevelNames(int maxTopLevelNames);
void setProperties(Properties properties); void setProperties(Properties properties);
void setTransformers(String[] transformers); void setTransformers(String[] transformers);

View File

@ -56,6 +56,7 @@ public class InProcessBuildStrategy implements BuildStrategy {
private boolean obfuscated; private boolean obfuscated;
private JSModuleType jsModuleType; private JSModuleType jsModuleType;
private boolean strict; private boolean strict;
private int maxTopLevelNames = 80_000;
private boolean sourceMapsFileGenerated; private boolean sourceMapsFileGenerated;
private boolean debugInformationGenerated; private boolean debugInformationGenerated;
private TeaVMSourceFilePolicy sourceMapsSourcePolicy; private TeaVMSourceFilePolicy sourceMapsSourcePolicy;
@ -174,6 +175,11 @@ public class InProcessBuildStrategy implements BuildStrategy {
this.jsModuleType = jsModuleType; this.jsModuleType = jsModuleType;
} }
@Override
public void setMaxTopLevelNames(int maxTopLevelNames) {
this.maxTopLevelNames = maxTopLevelNames;
}
@Override @Override
public void setTransformers(String[] transformers) { public void setTransformers(String[] transformers) {
this.transformers = transformers.clone(); this.transformers = transformers.clone();
@ -256,6 +262,7 @@ public class InProcessBuildStrategy implements BuildStrategy {
tool.setObfuscated(obfuscated); tool.setObfuscated(obfuscated);
tool.setJsModuleType(jsModuleType); tool.setJsModuleType(jsModuleType);
tool.setStrict(strict); tool.setStrict(strict);
tool.setMaxTopLevelNames(maxTopLevelNames);
tool.setIncremental(incremental); tool.setIncremental(incremental);
tool.getTransformers().addAll(Arrays.asList(transformers)); tool.getTransformers().addAll(Arrays.asList(transformers));
tool.getClassesToPreserve().addAll(Arrays.asList(classesToPreserve)); tool.getClassesToPreserve().addAll(Arrays.asList(classesToPreserve));

View File

@ -148,6 +148,11 @@ public class RemoteBuildStrategy implements BuildStrategy {
request.jsModuleType = jsModuleType; request.jsModuleType = jsModuleType;
} }
@Override
public void setMaxTopLevelNames(int maxTopLevelNames) {
request.maxTopLevelNames = maxTopLevelNames;
}
@Override @Override
public void setTransformers(String[] transformers) { public void setTransformers(String[] transformers) {
request.transformers = transformers.clone(); request.transformers = transformers.clone();

View File

@ -44,6 +44,7 @@ public class RemoteBuildRequest implements Serializable {
public boolean obfuscated; public boolean obfuscated;
public boolean strict; public boolean strict;
public JSModuleType jsModuleType; public JSModuleType jsModuleType;
public int maxTopLevelNames = 80_000;
public Properties properties; public Properties properties;
public TeaVMOptimizationLevel optimizationLevel; public TeaVMOptimizationLevel optimizationLevel;
public boolean fastDependencyAnalysis; public boolean fastDependencyAnalysis;

View File

@ -123,6 +123,7 @@ public class TeaVMPlugin implements Plugin<Project> {
task.getStrict().convention(js.getStrict()); task.getStrict().convention(js.getStrict());
task.getEntryPointName().convention(js.getEntryPointName()); task.getEntryPointName().convention(js.getEntryPointName());
task.getSourceFilePolicy().convention(js.getSourceFilePolicy()); task.getSourceFilePolicy().convention(js.getSourceFilePolicy());
task.getMaxTopLevelNames().convention(js.getMaxTopLevelNames());
task.getSourceFiles().from(project.provider(() -> { task.getSourceFiles().from(project.provider(() -> {
var result = new ArrayList<File>(); var result = new ArrayList<File>();

View File

@ -31,4 +31,6 @@ public interface TeaVMJSConfiguration extends TeaVMWebConfiguration {
Property<String> getTargetFileName(); Property<String> getTargetFileName();
Property<SourceFilePolicy> getSourceFilePolicy(); Property<SourceFilePolicy> getSourceFilePolicy();
Property<Integer> getMaxTopLevelNames();
} }

View File

@ -63,11 +63,18 @@ public abstract class GenerateJavaScriptTask extends TeaVMTask {
@Optional @Optional
public abstract Property<SourceFilePolicy> getSourceFilePolicy(); public abstract Property<SourceFilePolicy> getSourceFilePolicy();
@Input
@Optional
public abstract Property<Integer> getMaxTopLevelNames();
@Override @Override
protected void setupBuilder(BuildStrategy builder) { protected void setupBuilder(BuildStrategy builder) {
builder.setTargetType(TeaVMTargetType.JAVASCRIPT); builder.setTargetType(TeaVMTargetType.JAVASCRIPT);
builder.setObfuscated(getObfuscated().get()); builder.setObfuscated(getObfuscated().get());
builder.setStrict(getStrict().get()); builder.setStrict(getStrict().get());
if (getMaxTopLevelNames().isPresent()) {
builder.setMaxTopLevelNames(getMaxTopLevelNames().get());
}
switch (getModuleType().get()) { switch (getModuleType().get()) {
case UMD: case UMD:
builder.setJsModuleType(org.teavm.backend.javascript.JSModuleType.UMD); builder.setJsModuleType(org.teavm.backend.javascript.JSModuleType.UMD);

View File

@ -65,7 +65,7 @@ class TestExceptionPlugin implements TeaVMPlugin {
writer.appendClass("java.lang.Throwable").append(".prototype.getMessage").ws().append("=").ws() writer.appendClass("java.lang.Throwable").append(".prototype.getMessage").ws().append("=").ws()
.append("function()").ws().append("{").indent().softNewLine(); .append("function()").ws().append("{").indent().softNewLine();
writer.append("return ").appendFunction("$rt_ustr").append("(this.") writer.append("return ").appendFunction("$rt_ustr").append("(this.")
.appendMethod("getMessage", String.class).append("());").softNewLine(); .appendVirtualMethod("getMessage", String.class).append("());").softNewLine();
writer.outdent().append("};").newLine(); writer.outdent().append("};").newLine();
} }
} }

View File

@ -87,6 +87,9 @@ public class TeaVMCompileMojo extends AbstractMojo {
@Parameter(property = "teavm.jsModuleType", defaultValue = "UMD") @Parameter(property = "teavm.jsModuleType", defaultValue = "UMD")
private JSModuleType jsModuleType; private JSModuleType jsModuleType;
@Parameter(property = "teavm.maxTopLevelNames", defaultValue = "80000")
private int maxTopLevelNames = 80_000;
@Parameter @Parameter
private Properties properties; private Properties properties;
@ -169,6 +172,7 @@ public class TeaVMCompileMojo extends AbstractMojo {
builder.setObfuscated(minifying); builder.setObfuscated(minifying);
builder.setStrict(strict); builder.setStrict(strict);
builder.setJsModuleType(jsModuleType); builder.setJsModuleType(jsModuleType);
builder.setMaxTopLevelNames(maxTopLevelNames);
builder.setTargetDirectory(targetDirectory.getAbsolutePath()); builder.setTargetDirectory(targetDirectory.getAbsolutePath());
if (transformers != null) { if (transformers != null) {
builder.setTransformers(transformers); builder.setTransformers(transformers);