From de84105241916d671b3fd734244e699ba21c2638 Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Fri, 22 Mar 2019 19:40:56 +0300 Subject: [PATCH] Call methods before main method when possible, eliminate lazy class initialization for these methods. --- .../org/teavm/classlib/impl/IntegerUtil.java | 6 +- .../classlib/java/lang/ClassGenerator.java | 22 +- .../java/lang/StringNativeGenerator.java | 47 +++ .../org/teavm/classlib/java/lang/TClass.java | 2 + .../classlib/java/lang/TClassLoader.java | 2 + .../org/teavm/classlib/java/lang/TDouble.java | 3 + .../org/teavm/classlib/java/lang/TFloat.java | 2 + .../teavm/classlib/java/lang/TInteger.java | 4 + .../org/teavm/classlib/java/lang/TLong.java | 4 + .../org/teavm/classlib/java/lang/TMath.java | 2 + .../org/teavm/classlib/java/lang/TObject.java | 5 + .../org/teavm/classlib/java/lang/TString.java | 21 +- .../org/teavm/classlib/java/lang/TSystem.java | 3 + .../classlib/java/lang/reflect/TArray.java | 5 + .../java/org/teavm/backend/c/CTarget.java | 3 +- .../backend/javascript/JavaScriptTarget.java | 6 +- .../javascript/rendering/Renderer.java | 13 +- .../rendering/RenderingContext.java | 9 +- .../javascript/rendering/RuntimeRenderer.java | 27 +- .../javascript/spi/GeneratorContext.java | 2 + .../org/teavm/backend/wasm/WasmTarget.java | 3 +- .../analysis/ClassInitializerAnalysis.java | 306 ++++++++++++++++++ .../model/analysis/ClassInitializerInfo.java | 37 +++ .../model/optimization/Devirtualization.java | 8 +- .../teavm/model/optimization/Inlining.java | 8 +- .../ClassInitializerInsertionTransformer.java | 10 +- core/src/main/java/org/teavm/vm/TeaVM.java | 87 ++++- .../org/teavm/vm/TeaVMTargetController.java | 3 + .../org/teavm/backend/javascript/intern.js | 66 ++++ .../interop/DoesNotModifyStaticFields.java | 26 ++ .../org/teavm/jso/impl/JSClassProcessor.java | 2 + .../plugin/MetadataProviderTransformer.java | 4 + 32 files changed, 690 insertions(+), 58 deletions(-) create mode 100644 classlib/src/main/java/org/teavm/classlib/java/lang/StringNativeGenerator.java create mode 100644 core/src/main/java/org/teavm/model/analysis/ClassInitializerAnalysis.java create mode 100644 core/src/main/java/org/teavm/model/analysis/ClassInitializerInfo.java create mode 100644 core/src/main/resources/org/teavm/backend/javascript/intern.js create mode 100644 interop/core/src/main/java/org/teavm/interop/DoesNotModifyStaticFields.java diff --git a/classlib/src/main/java/org/teavm/classlib/impl/IntegerUtil.java b/classlib/src/main/java/org/teavm/classlib/impl/IntegerUtil.java index c5e2271f1..296c3b1c7 100644 --- a/classlib/src/main/java/org/teavm/classlib/impl/IntegerUtil.java +++ b/classlib/src/main/java/org/teavm/classlib/impl/IntegerUtil.java @@ -15,8 +15,6 @@ */ package org.teavm.classlib.impl; -import org.teavm.classlib.java.lang.TCharacter; - public final class IntegerUtil { private IntegerUtil() { } @@ -34,7 +32,7 @@ public final class IntegerUtil { int pos = (sz - 1) * radixLog2; int target = 0; while (pos >= 0) { - chars[target++] = TCharacter.forDigit((value >>> pos) & mask, radix); + chars[target++] = Character.forDigit((value >>> pos) & mask, radix); pos -= radixLog2; } @@ -54,7 +52,7 @@ public final class IntegerUtil { long pos = (sz - 1) * radixLog2; int target = 0; while (pos >= 0) { - chars[target++] = TCharacter.forDigit((int) (value >>> pos) & mask, radix); + chars[target++] = Character.forDigit((int) (value >>> pos) & mask, radix); pos -= radixLog2; } diff --git a/classlib/src/main/java/org/teavm/classlib/java/lang/ClassGenerator.java b/classlib/src/main/java/org/teavm/classlib/java/lang/ClassGenerator.java index c2b692a3a..9d72ab303 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/lang/ClassGenerator.java +++ b/classlib/src/main/java/org/teavm/classlib/java/lang/ClassGenerator.java @@ -151,7 +151,7 @@ public class ClassGenerator implements Generator, Injector, DependencyPlugin { appendProperty(writer, "getter", false, () -> { if (accessibleFields != null && accessibleFields.contains(field.getName())) { - renderGetter(writer, field); + renderGetter(context, writer, field); } else { writer.append("null"); } @@ -159,7 +159,7 @@ public class ClassGenerator implements Generator, Injector, DependencyPlugin { appendProperty(writer, "setter", false, () -> { if (accessibleFields != null && accessibleFields.contains(field.getName())) { - renderSetter(writer, field); + renderSetter(context, writer, field); } else { writer.append("null"); } @@ -199,7 +199,7 @@ public class ClassGenerator implements Generator, Injector, DependencyPlugin { appendProperty(writer, "callable", false, () -> { if (accessibleMethods != null && accessibleMethods.contains(method.getDescriptor())) { - renderCallable(writer, method); + renderCallable(context, writer, method); } else { writer.append("null"); } @@ -239,18 +239,18 @@ public class ClassGenerator implements Generator, Injector, DependencyPlugin { value.render(); } - private void renderGetter(SourceWriter writer, FieldReader field) throws IOException { + private void renderGetter(GeneratorContext context, SourceWriter writer, FieldReader field) throws IOException { writer.append("function(obj)").ws().append("{").indent().softNewLine(); - initClass(writer, field); + initClass(context, writer, field); writer.append("return "); boxIfNecessary(writer, field.getType(), () -> fieldAccess(writer, field)); writer.append(";").softNewLine(); writer.outdent().append("}"); } - private void renderSetter(SourceWriter writer, FieldReader field) throws IOException { + private void renderSetter(GeneratorContext context, SourceWriter writer, FieldReader field) throws IOException { writer.append("function(obj,").ws().append("val)").ws().append("{").indent().softNewLine(); - initClass(writer, field); + initClass(context, writer, field); fieldAccess(writer, field); writer.ws().append('=').ws(); unboxIfNecessary(writer, field.getType(), () -> writer.append("val")); @@ -258,10 +258,10 @@ public class ClassGenerator implements Generator, Injector, DependencyPlugin { writer.outdent().append("}"); } - private void renderCallable(SourceWriter writer, MethodReader method) throws IOException { + private void renderCallable(GeneratorContext context, SourceWriter writer, MethodReader method) throws IOException { writer.append("function(obj,").ws().append("args)").ws().append("{").indent().softNewLine(); - initClass(writer, method); + initClass(context, writer, method); if (method.getResultType() != ValueType.VOID) { writer.append("return "); @@ -290,8 +290,8 @@ public class ClassGenerator implements Generator, Injector, DependencyPlugin { writer.outdent().append("}"); } - private void initClass(SourceWriter writer, MemberReader member) throws IOException { - if (member.hasModifier(ElementModifier.STATIC)) { + private void initClass(GeneratorContext context, SourceWriter writer, MemberReader member) throws IOException { + if (member.hasModifier(ElementModifier.STATIC) && context.isDynamicInitializer(member.getOwnerName())) { writer.appendClassInit(member.getOwnerName()).append("();").softNewLine(); } } diff --git a/classlib/src/main/java/org/teavm/classlib/java/lang/StringNativeGenerator.java b/classlib/src/main/java/org/teavm/classlib/java/lang/StringNativeGenerator.java new file mode 100644 index 000000000..2ab178f25 --- /dev/null +++ b/classlib/src/main/java/org/teavm/classlib/java/lang/StringNativeGenerator.java @@ -0,0 +1,47 @@ +/* + * Copyright 2019 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.classlib.java.lang; + +import java.io.IOException; +import org.teavm.backend.javascript.codegen.SourceWriter; +import org.teavm.backend.javascript.spi.Generator; +import org.teavm.backend.javascript.spi.GeneratorContext; +import org.teavm.dependency.DependencyAgent; +import org.teavm.dependency.DependencyPlugin; +import org.teavm.dependency.MethodDependency; +import org.teavm.model.MethodReference; + +public class StringNativeGenerator implements Generator, DependencyPlugin { + @Override + public void generate(GeneratorContext context, SourceWriter writer, MethodReference methodRef) throws IOException { + if (methodRef.getName().equals("intern")) { + writer.append("return $rt_intern(").append(context.getParameterName(0)).append(");").softNewLine(); + } + } + + @Override + public void methodReached(DependencyAgent agent, MethodDependency method) { + if (method.getReference().getName().equals("intern")) { + agent.linkMethod(new MethodReference(String.class, "hashCode", int.class)) + .propagate(0, agent.getType("java.lang.String")) + .use(); + agent.linkMethod(new MethodReference(String.class, "equals", Object.class, boolean.class)) + .propagate(0, agent.getType("java.lang.String")) + .propagate(1, agent.getType("java.lang.String")) + .use(); + } + } +} diff --git a/classlib/src/main/java/org/teavm/classlib/java/lang/TClass.java b/classlib/src/main/java/org/teavm/classlib/java/lang/TClass.java index 98c834491..c4c43d061 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/lang/TClass.java +++ b/classlib/src/main/java/org/teavm/classlib/java/lang/TClass.java @@ -41,6 +41,7 @@ import org.teavm.classlib.java.lang.reflect.TModifier; import org.teavm.dependency.PluggableDependency; import org.teavm.interop.Address; import org.teavm.interop.DelegateTo; +import org.teavm.interop.DoesNotModifyStaticFields; import org.teavm.interop.Unmanaged; import org.teavm.jso.core.JSArray; import org.teavm.platform.Platform; @@ -209,6 +210,7 @@ public class TClass extends TObject implements TAnnotatedElement { } @GeneratedBy(ClassGenerator.class) + @DoesNotModifyStaticFields private static native void createMetadata(); public TField[] getFields() throws TSecurityException { diff --git a/classlib/src/main/java/org/teavm/classlib/java/lang/TClassLoader.java b/classlib/src/main/java/org/teavm/classlib/java/lang/TClassLoader.java index 972f247cb..d9c59910b 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/lang/TClassLoader.java +++ b/classlib/src/main/java/org/teavm/classlib/java/lang/TClassLoader.java @@ -19,6 +19,7 @@ import java.io.ByteArrayInputStream; import java.io.InputStream; import org.teavm.backend.javascript.spi.InjectedBy; import org.teavm.classlib.impl.Base64Impl; +import org.teavm.interop.DoesNotModifyStaticFields; import org.teavm.jso.JSBody; import org.teavm.jso.JSIndexer; import org.teavm.jso.JSObject; @@ -68,6 +69,7 @@ public abstract class TClassLoader extends TObject { private static native String resourceToString(JSObject resource); @InjectedBy(ClassLoaderNativeGenerator.class) + @DoesNotModifyStaticFields private static native ResourceContainer supplyResources(); interface ResourceContainer extends JSObject { diff --git a/classlib/src/main/java/org/teavm/classlib/java/lang/TDouble.java b/classlib/src/main/java/org/teavm/classlib/java/lang/TDouble.java index a49a6bf6f..8ff43cb61 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/lang/TDouble.java +++ b/classlib/src/main/java/org/teavm/classlib/java/lang/TDouble.java @@ -16,9 +16,11 @@ package org.teavm.classlib.java.lang; import org.teavm.backend.javascript.spi.InjectedBy; +import org.teavm.interop.DoesNotModifyStaticFields; import org.teavm.interop.Import; import org.teavm.jso.JSBody; +@DoesNotModifyStaticFields public class TDouble extends TNumber implements TComparable { public static final double POSITIVE_INFINITY = 1 / 0.0; public static final double NEGATIVE_INFINITY = -POSITIVE_INFINITY; @@ -208,6 +210,7 @@ public class TDouble extends TNumber implements TComparable { return (int) (h >>> 32) ^ (int) h; } + @DoesNotModifyStaticFields public static native int compare(double a, double b); @Override diff --git a/classlib/src/main/java/org/teavm/classlib/java/lang/TFloat.java b/classlib/src/main/java/org/teavm/classlib/java/lang/TFloat.java index 2dd0d482b..1d71b8059 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/lang/TFloat.java +++ b/classlib/src/main/java/org/teavm/classlib/java/lang/TFloat.java @@ -15,6 +15,7 @@ */ package org.teavm.classlib.java.lang; +import org.teavm.interop.DoesNotModifyStaticFields; import org.teavm.interop.Import; import org.teavm.jso.JSBody; @@ -237,6 +238,7 @@ public class TFloat extends TNumber implements TComparable { return isInfinite(value); } + @DoesNotModifyStaticFields public static native int compare(float f1, float f2); @Override diff --git a/classlib/src/main/java/org/teavm/classlib/java/lang/TInteger.java b/classlib/src/main/java/org/teavm/classlib/java/lang/TInteger.java index 42099cb2c..7f1daf576 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/lang/TInteger.java +++ b/classlib/src/main/java/org/teavm/classlib/java/lang/TInteger.java @@ -17,6 +17,7 @@ package org.teavm.classlib.java.lang; import static org.teavm.classlib.impl.IntegerUtil.toUnsignedLogRadixString; import org.teavm.backend.javascript.spi.InjectedBy; +import org.teavm.interop.DoesNotModifyStaticFields; public class TInteger extends TNumber implements TComparable { public static final int SIZE = 32; @@ -251,6 +252,7 @@ public class TInteger extends TNumber implements TComparable { return compare(value, other.value); } + @DoesNotModifyStaticFields public static native int compare(int x, int y); public static int numberOfLeadingZeros(int i) { @@ -356,8 +358,10 @@ public class TInteger extends TNumber implements TComparable { } @InjectedBy(IntegerNativeGenerator.class) + @DoesNotModifyStaticFields public static native int divideUnsigned(int dividend, int divisor); @InjectedBy(IntegerNativeGenerator.class) + @DoesNotModifyStaticFields public static native int remainderUnsigned(int dividend, int divisor); } diff --git a/classlib/src/main/java/org/teavm/classlib/java/lang/TLong.java b/classlib/src/main/java/org/teavm/classlib/java/lang/TLong.java index 990455656..6bf0dc5aa 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/lang/TLong.java +++ b/classlib/src/main/java/org/teavm/classlib/java/lang/TLong.java @@ -17,6 +17,7 @@ package org.teavm.classlib.java.lang; import static org.teavm.classlib.impl.IntegerUtil.toUnsignedLogRadixString; import org.teavm.backend.javascript.spi.GeneratedBy; +import org.teavm.interop.DoesNotModifyStaticFields; public class TLong extends TNumber implements TComparable { public static final long MIN_VALUE = -0x8000000000000000L; @@ -212,6 +213,7 @@ public class TLong extends TNumber implements TComparable { return other instanceof TLong && ((TLong) other).value == value; } + @DoesNotModifyStaticFields public static native int compare(long a, long b); @Override @@ -351,8 +353,10 @@ public class TLong extends TNumber implements TComparable { } @GeneratedBy(LongNativeGenerator.class) + @DoesNotModifyStaticFields public static native long divideUnsigned(long dividend, long divisor); @GeneratedBy(LongNativeGenerator.class) + @DoesNotModifyStaticFields public static native long remainderUnsigned(long dividend, long divisor); } diff --git a/classlib/src/main/java/org/teavm/classlib/java/lang/TMath.java b/classlib/src/main/java/org/teavm/classlib/java/lang/TMath.java index 9ff246aed..1a3f47647 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/lang/TMath.java +++ b/classlib/src/main/java/org/teavm/classlib/java/lang/TMath.java @@ -16,8 +16,10 @@ package org.teavm.classlib.java.lang; import org.teavm.backend.javascript.spi.GeneratedBy; +import org.teavm.interop.DoesNotModifyStaticFields; import org.teavm.interop.Import; +@DoesNotModifyStaticFields public final class TMath extends TObject { public static final double E = 2.71828182845904523536; public static final double PI = 3.14159265358979323846; diff --git a/classlib/src/main/java/org/teavm/classlib/java/lang/TObject.java b/classlib/src/main/java/org/teavm/classlib/java/lang/TObject.java index 46315f25f..48bdb11bc 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/lang/TObject.java +++ b/classlib/src/main/java/org/teavm/classlib/java/lang/TObject.java @@ -21,6 +21,7 @@ import org.teavm.interop.Address; import org.teavm.interop.Async; import org.teavm.interop.AsyncCallback; import org.teavm.interop.DelegateTo; +import org.teavm.interop.DoesNotModifyStaticFields; import org.teavm.interop.Rename; import org.teavm.interop.Structure; import org.teavm.interop.Superclass; @@ -272,6 +273,7 @@ public class TObject { } @DelegateTo("hashCodeLowLevelImpl") + @DoesNotModifyStaticFields private static native int hashCodeLowLevel(TObject obj); @Unmanaged @@ -280,6 +282,7 @@ public class TObject { } @DelegateTo("setHashCodeLowLevelImpl") + @DoesNotModifyStaticFields private static native void setHashCodeLowLevel(TObject obj, int value); @Unmanaged @@ -300,6 +303,7 @@ public class TObject { } @DelegateTo("identityOrMonitorLowLevel") + @DoesNotModifyStaticFields private native int identityOrMonitor(); private static int identityOrMonitorLowLevel(RuntimeObject object) { @@ -307,6 +311,7 @@ public class TObject { } @DelegateTo("setIdentityLowLevel") + @DoesNotModifyStaticFields native void setIdentity(int id); private static void setIdentityLowLevel(RuntimeObject object, int id) { diff --git a/classlib/src/main/java/org/teavm/classlib/java/lang/TString.java b/classlib/src/main/java/org/teavm/classlib/java/lang/TString.java index a615e7bf2..bc4383ac7 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/lang/TString.java +++ b/classlib/src/main/java/org/teavm/classlib/java/lang/TString.java @@ -17,6 +17,7 @@ package org.teavm.classlib.java.lang; import java.util.Iterator; import java.util.Locale; +import org.teavm.backend.javascript.spi.GeneratedBy; import org.teavm.classlib.java.io.TSerializable; import org.teavm.classlib.java.io.TUnsupportedEncodingException; import org.teavm.classlib.java.nio.TByteBuffer; @@ -26,10 +27,10 @@ import org.teavm.classlib.java.nio.charset.impl.TUTF8Charset; import org.teavm.classlib.java.util.TArrays; import org.teavm.classlib.java.util.TComparator; import org.teavm.classlib.java.util.TFormatter; -import org.teavm.classlib.java.util.THashMap; import org.teavm.classlib.java.util.TLocale; -import org.teavm.classlib.java.util.TMap; import org.teavm.classlib.java.util.regex.TPattern; +import org.teavm.dependency.PluggableDependency; +import org.teavm.interop.DoesNotModifyStaticFields; public class TString extends TObject implements TSerializable, TComparable, TCharSequence { public static final TComparator CASE_INSENSITIVE_ORDER = (o1, o2) -> o1.compareToIgnoreCase(o2); @@ -625,14 +626,10 @@ public class TString extends TObject implements TSerializable, TComparable pool = new THashMap<>(); - } } diff --git a/classlib/src/main/java/org/teavm/classlib/java/lang/TSystem.java b/classlib/src/main/java/org/teavm/classlib/java/lang/TSystem.java index 688bd50d8..23641bc7a 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/lang/TSystem.java +++ b/classlib/src/main/java/org/teavm/classlib/java/lang/TSystem.java @@ -25,6 +25,7 @@ import org.teavm.classlib.java.io.TPrintStream; import org.teavm.classlib.java.lang.reflect.TArray; import org.teavm.interop.Address; import org.teavm.interop.DelegateTo; +import org.teavm.interop.DoesNotModifyStaticFields; import org.teavm.interop.Import; import org.teavm.interop.Unmanaged; import org.teavm.runtime.Allocator; @@ -103,6 +104,7 @@ public final class TSystem extends TObject { @GeneratedBy(SystemNativeGenerator.class) @DelegateTo("doArrayCopyLowLevel") + @DoesNotModifyStaticFields private static native void doArrayCopy(Object src, int srcPos, Object dest, int destPos, int length); @Unmanaged @@ -124,6 +126,7 @@ public final class TSystem extends TObject { @DelegateTo("currentTimeMillisLowLevel") @GeneratedBy(SystemNativeGenerator.class) + @DoesNotModifyStaticFields public static native long currentTimeMillis(); private static long currentTimeMillisLowLevel() { diff --git a/classlib/src/main/java/org/teavm/classlib/java/lang/reflect/TArray.java b/classlib/src/main/java/org/teavm/classlib/java/lang/reflect/TArray.java index a49dc2622..604064eef 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/lang/reflect/TArray.java +++ b/classlib/src/main/java/org/teavm/classlib/java/lang/reflect/TArray.java @@ -24,6 +24,7 @@ import org.teavm.classlib.java.lang.TNullPointerException; import org.teavm.classlib.java.lang.TObject; import org.teavm.dependency.PluggableDependency; import org.teavm.interop.DelegateTo; +import org.teavm.interop.DoesNotModifyStaticFields; import org.teavm.interop.Unmanaged; import org.teavm.platform.PlatformClass; import org.teavm.runtime.Allocator; @@ -35,6 +36,7 @@ public final class TArray extends TObject { @GeneratedBy(ArrayNativeGenerator.class) @PluggableDependency(ArrayNativeGenerator.class) @DelegateTo("getLengthLowLevel") + @DoesNotModifyStaticFields public static native int getLength(TObject array) throws TIllegalArgumentException; @SuppressWarnings("unused") @@ -63,6 +65,7 @@ public final class TArray extends TObject { @GeneratedBy(ArrayNativeGenerator.class) @DelegateTo("newInstanceLowLevel") + @DoesNotModifyStaticFields private static native TObject newInstanceImpl(PlatformClass componentType, int length); @SuppressWarnings("unused") @@ -89,9 +92,11 @@ public final class TArray extends TObject { @GeneratedBy(ArrayNativeGenerator.class) @PluggableDependency(ArrayNativeGenerator.class) + @DoesNotModifyStaticFields private static native TObject getImpl(TObject array, int index); @GeneratedBy(ArrayNativeGenerator.class) @PluggableDependency(ArrayNativeGenerator.class) + @DoesNotModifyStaticFields private static native void setImpl(TObject array, int index, TObject value); } diff --git a/core/src/main/java/org/teavm/backend/c/CTarget.java b/core/src/main/java/org/teavm/backend/c/CTarget.java index 3ed182e76..4dc47f3fc 100644 --- a/core/src/main/java/org/teavm/backend/c/CTarget.java +++ b/core/src/main/java/org/teavm/backend/c/CTarget.java @@ -153,7 +153,8 @@ public class CTarget implements TeaVMTarget, TeaVMCHost { classInitializerEliminator = new ClassInitializerEliminator(controller.getUnprocessedClassSource()); classInitializerTransformer = new ClassInitializerTransformer(); shadowStackTransformer = new ShadowStackTransformer(characteristics); - clinitInsertionTransformer = new ClassInitializerInsertionTransformer(controller.getUnprocessedClassSource()); + clinitInsertionTransformer = new ClassInitializerInsertionTransformer(controller.getUnprocessedClassSource(), + controller.getClassInitializerInfo()); nullCheckInsertion = new NullCheckInsertion(characteristics); nullCheckTransformation = new NullCheckTransformation(); diff --git a/core/src/main/java/org/teavm/backend/javascript/JavaScriptTarget.java b/core/src/main/java/org/teavm/backend/javascript/JavaScriptTarget.java index cd5f9d133..e05b477e1 100644 --- a/core/src/main/java/org/teavm/backend/javascript/JavaScriptTarget.java +++ b/core/src/main/java/org/teavm/backend/javascript/JavaScriptTarget.java @@ -142,7 +142,8 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost { @Override public void setController(TeaVMTargetController controller) { this.controller = controller; - clinitInsertionTransformer = new ClassInitializerInsertionTransformer(controller.getUnprocessedClassSource()); + clinitInsertionTransformer = new ClassInitializerInsertionTransformer(controller.getUnprocessedClassSource(), + controller.getClassInitializerInfo()); } @Override @@ -350,7 +351,8 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost { RenderingContext renderingContext = new RenderingContext(debugEmitterToUse, controller.getUnprocessedClassSource(), classes, controller.getClassLoader(), controller.getServices(), controller.getProperties(), naming, - controller.getDependencyInfo(), m -> isVirtual(virtualMethodContributorContext, m)); + controller.getDependencyInfo(), m -> isVirtual(virtualMethodContributorContext, m), + controller.getClassInitializerInfo()); renderingContext.setMinifying(minifying); Renderer renderer = new Renderer(sourceWriter, asyncMethods, asyncFamilyMethods, controller.getDiagnostics(), renderingContext); diff --git a/core/src/main/java/org/teavm/backend/javascript/rendering/Renderer.java b/core/src/main/java/org/teavm/backend/javascript/rendering/Renderer.java index fc827d9fd..6165a5369 100644 --- a/core/src/main/java/org/teavm/backend/javascript/rendering/Renderer.java +++ b/core/src/main/java/org/teavm/backend/javascript/rendering/Renderer.java @@ -76,7 +76,7 @@ public class Renderer implements RenderingManager { private RenderingContext context; private List postponedFieldInitializers = new ArrayList<>(); private IntFunction progressConsumer = p -> TeaVMProgressFeedback.CONTINUE; - private static final MethodDescriptor CLINIT_METHOD = new MethodDescriptor("", ValueType.VOID); + public static final MethodDescriptor CLINIT_METHOD = new MethodDescriptor("", ValueType.VOID); private ObjectIntMap sizeByClass = new ObjectIntHashMap<>(); private int stringPoolSize; @@ -379,7 +379,7 @@ public class Renderer implements RenderingManager { try { MethodReader clinit = classSource.get(cls.getName()).getMethod(CLINIT_METHOD); - if (clinit != null) { + if (clinit != null && context.isDynamicInitializer(cls.getName())) { renderCallClinit(clinit, cls); } if (!cls.getClassHolder().getModifiers().contains(ElementModifier.INTERFACE)) { @@ -541,7 +541,7 @@ public class Renderer implements RenderingManager { writer.append(cls.getClassHolder().getLevel().ordinal()).append(',').ws(); MethodReader clinit = classSource.get(cls.getName()).getMethod(CLINIT_METHOD); - if (clinit != null) { + if (clinit != null && context.isDynamicInitializer(cls.getName())) { writer.appendClassInit(cls.getName()); } else { writer.append('0'); @@ -1112,6 +1112,11 @@ public class Renderer implements RenderingManager { public void useLongLibrary() { longLibraryUsed = true; } + + @Override + public boolean isDynamicInitializer(String className) { + return context.isDynamicInitializer(className); + } } private void appendMonitor(StatementRenderer statementRenderer, MethodNode methodNode) throws IOException { @@ -1132,7 +1137,7 @@ public class Renderer implements RenderingManager { FieldReference field; String value; - public PostponedFieldInitializer(FieldReference field, String value) { + PostponedFieldInitializer(FieldReference field, String value) { this.field = field; this.value = value; } diff --git a/core/src/main/java/org/teavm/backend/javascript/rendering/RenderingContext.java b/core/src/main/java/org/teavm/backend/javascript/rendering/RenderingContext.java index ce6c0fa55..50f9073da 100644 --- a/core/src/main/java/org/teavm/backend/javascript/rendering/RenderingContext.java +++ b/core/src/main/java/org/teavm/backend/javascript/rendering/RenderingContext.java @@ -43,6 +43,7 @@ import org.teavm.model.MethodReader; import org.teavm.model.MethodReference; import org.teavm.model.TextLocation; import org.teavm.model.ValueType; +import org.teavm.model.analysis.ClassInitializerInfo; public class RenderingContext { private final DebugInformationEmitter debugEmitter; @@ -60,12 +61,13 @@ public class RenderingContext { private final List readonlyStringPool = Collections.unmodifiableList(stringPool); private final Map injectorMap = new HashMap<>(); private boolean minifying; + private ClassInitializerInfo classInitializerInfo; public RenderingContext(DebugInformationEmitter debugEmitter, ClassReaderSource initialClassSource, ListableClassReaderSource classSource, ClassLoader classLoader, ServiceRepository services, Properties properties, NamingStrategy naming, DependencyInfo dependencyInfo, - Predicate virtualPredicate) { + Predicate virtualPredicate, ClassInitializerInfo classInitializerInfo) { this.debugEmitter = debugEmitter; this.initialClassSource = initialClassSource; this.classSource = classSource; @@ -75,6 +77,7 @@ public class RenderingContext { this.naming = naming; this.dependencyInfo = dependencyInfo; this.virtualPredicate = virtualPredicate; + this.classInitializerInfo = classInitializerInfo; } public ClassReaderSource getInitialClassSource() { @@ -117,6 +120,10 @@ public class RenderingContext { return virtualPredicate.test(method); } + public boolean isDynamicInitializer(String className) { + return classInitializerInfo.isDynamicInitializer(className); + } + public void pushLocation(TextLocation location) { LocationStackEntry prevEntry = locationStack.peek(); if (location != null) { diff --git a/core/src/main/java/org/teavm/backend/javascript/rendering/RuntimeRenderer.java b/core/src/main/java/org/teavm/backend/javascript/rendering/RuntimeRenderer.java index 6c3f832cb..31c5e4854 100644 --- a/core/src/main/java/org/teavm/backend/javascript/rendering/RuntimeRenderer.java +++ b/core/src/main/java/org/teavm/backend/javascript/rendering/RuntimeRenderer.java @@ -151,16 +151,29 @@ public class RuntimeRenderer { } private void renderRuntimeIntern() throws IOException { - writer.append("function $rt_intern(str) {").indent().softNewLine(); - ClassReader stringCls = classSource.get(STRING_CLASS); - if (stringCls != null && stringCls.getMethod(STRING_INTERN_METHOD) != null) { - writer.append("return ").appendMethodBody(new MethodReference(STRING_CLASS, STRING_INTERN_METHOD)) - .append("(str);").softNewLine(); - } else { + if (!needInternMethod()) { + writer.append("function $rt_intern(str) {").indent().softNewLine(); writer.append("return str;").softNewLine(); + writer.outdent().append("}").softNewLine(); + } else { + renderHandWrittenRuntime("intern.js"); + writer.append("function $rt_stringHash(s)").ws().append("{").indent().softNewLine(); + writer.append("return ").appendMethodBody(String.class, "hashCode", int.class) + .append("(s);").softNewLine(); + writer.outdent().append("}").softNewLine(); + writer.append("function $rt_stringEquals(a,").ws().append("b)").ws().append("{").indent().softNewLine(); + writer.append("return ").appendMethodBody(String.class, "equals", Object.class, boolean.class) + .append("(a").ws().append(",b);").softNewLine(); + writer.outdent().append("}").softNewLine(); } + } - writer.outdent().append("}").newLine(); + private boolean needInternMethod() { + ClassReader cls = classSource.get(STRING_CLASS); + if (cls == null) { + return false; + } + return cls.getMethod(STRING_INTERN_METHOD) != null; } private void renderRuntimeObjcls() throws IOException { diff --git a/core/src/main/java/org/teavm/backend/javascript/spi/GeneratorContext.java b/core/src/main/java/org/teavm/backend/javascript/spi/GeneratorContext.java index 10285a51d..fb612fde5 100644 --- a/core/src/main/java/org/teavm/backend/javascript/spi/GeneratorContext.java +++ b/core/src/main/java/org/teavm/backend/javascript/spi/GeneratorContext.java @@ -49,4 +49,6 @@ public interface GeneratorContext extends ServiceRepository { void typeToClassString(SourceWriter writer, ValueType type); void useLongLibrary(); + + boolean isDynamicInitializer(String className); } diff --git a/core/src/main/java/org/teavm/backend/wasm/WasmTarget.java b/core/src/main/java/org/teavm/backend/wasm/WasmTarget.java index edbe79d66..7403d3dd5 100644 --- a/core/src/main/java/org/teavm/backend/wasm/WasmTarget.java +++ b/core/src/main/java/org/teavm/backend/wasm/WasmTarget.java @@ -164,7 +164,8 @@ public class WasmTarget implements TeaVMTarget, TeaVMWasmHost { classInitializerEliminator = new ClassInitializerEliminator(controller.getUnprocessedClassSource()); classInitializerTransformer = new ClassInitializerTransformer(); shadowStackTransformer = new ShadowStackTransformer(managedMethodRepository); - clinitInsertionTransformer = new ClassInitializerInsertionTransformer(controller.getUnprocessedClassSource()); + clinitInsertionTransformer = new ClassInitializerInsertionTransformer(controller.getUnprocessedClassSource(), + controller.getClassInitializerInfo()); } @Override diff --git a/core/src/main/java/org/teavm/model/analysis/ClassInitializerAnalysis.java b/core/src/main/java/org/teavm/model/analysis/ClassInitializerAnalysis.java new file mode 100644 index 000000000..79ab1cddc --- /dev/null +++ b/core/src/main/java/org/teavm/model/analysis/ClassInitializerAnalysis.java @@ -0,0 +1,306 @@ +/* + * Copyright 2019 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.model.analysis; + +import com.carrotsearch.hppc.ObjectByteHashMap; +import com.carrotsearch.hppc.ObjectByteMap; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.teavm.dependency.DependencyInfo; +import org.teavm.dependency.MethodDependencyInfo; +import org.teavm.dependency.ValueDependencyInfo; +import org.teavm.interop.DoesNotModifyStaticFields; +import org.teavm.model.BasicBlockReader; +import org.teavm.model.ClassHierarchy; +import org.teavm.model.ClassReader; +import org.teavm.model.ElementModifier; +import org.teavm.model.FieldReference; +import org.teavm.model.ListableClassReaderSource; +import org.teavm.model.MethodDescriptor; +import org.teavm.model.MethodReader; +import org.teavm.model.MethodReference; +import org.teavm.model.ProgramReader; +import org.teavm.model.ValueType; +import org.teavm.model.VariableReader; +import org.teavm.model.instructions.AbstractInstructionReader; +import org.teavm.model.instructions.InvocationType; +import org.teavm.model.optimization.Devirtualization; + +public class ClassInitializerAnalysis implements ClassInitializerInfo { + private static final MethodDescriptor CLINIT = new MethodDescriptor("", void.class); + private static final byte BEING_ANALYZED = 1; + private static final byte DYNAMIC = 2; + private static final byte STATIC = 3; + private ObjectByteMap classStatuses = new ObjectByteHashMap<>(); + private Map methodInfoMap = new HashMap<>(); + private ListableClassReaderSource classes; + private ClassHierarchy hierarchy; + private List order = new ArrayList<>(); + private List readonlyOrder = Collections.unmodifiableList(order); + private String currentAnalyzedClass; + private DependencyInfo dependencyInfo; + + public ClassInitializerAnalysis(ListableClassReaderSource classes, ClassHierarchy hierarchy) { + this.classes = classes; + this.hierarchy = hierarchy; + } + + public void analyze(DependencyInfo dependencyInfo) { + if (methodInfoMap == null) { + return; + } + this.dependencyInfo = dependencyInfo; + + for (String className : classes.getClassNames()) { + analyze(className); + } + + methodInfoMap = null; + classes = null; + hierarchy = null; + this.dependencyInfo = null; + } + + @Override + public boolean isDynamicInitializer(String className) { + return classStatuses.get(className) != STATIC; + } + + @Override + public List getInitializationOrder() { + return readonlyOrder; + } + + private void analyze(String className) { + byte classStatus = classStatuses.get(className); + switch (classStatus) { + case BEING_ANALYZED: + if (!className.equals(currentAnalyzedClass)) { + classStatuses.put(className, DYNAMIC); + } + return; + case DYNAMIC: + case STATIC: + return; + } + + ClassReader cls = classes.get(className); + + if (cls == null) { + classStatuses.put(className, STATIC); + return; + } + + classStatuses.put(className, BEING_ANALYZED); + String previousClass = currentAnalyzedClass; + currentAnalyzedClass = className; + + MethodReader initializer = cls.getMethod(CLINIT); + boolean isStatic = true; + if (initializer != null) { + MethodInfo initializerInfo = analyzeMethod(initializer); + if (isDynamicInitializer(initializerInfo, className)) { + isStatic = false; + } + } + + currentAnalyzedClass = previousClass; + if (classStatuses.get(className) == BEING_ANALYZED) { + classStatuses.put(className, isStatic ? STATIC : DYNAMIC); + if (isStatic && initializer != null) { + order.add(className); + } + } + } + + private boolean isDynamicInitializer(MethodInfo methodInfo, String className) { + if (methodInfo.anyFieldModified) { + return true; + } + if (methodInfo.classesWithModifiedFields != null) { + for (String affectedClass : methodInfo.classesWithModifiedFields) { + if (!affectedClass.equals(className)) { + return true; + } + } + } + return false; + } + + private MethodInfo analyzeMethod(MethodReader method) { + MethodInfo methodInfo = methodInfoMap.get(method.getReference()); + if (methodInfo == null) { + methodInfo = new MethodInfo(method.getReference()); + methodInfoMap.put(method.getReference(), methodInfo); + + String currentClass = method.getDescriptor().equals(CLINIT) ? method.getOwnerName() : null; + InstructionAnalyzer reader = new InstructionAnalyzer(currentClass, methodInfo); + ProgramReader program = method.getProgram(); + if (program == null) { + methodInfo.anyFieldModified = true; + if (method.getAnnotations().get(DoesNotModifyStaticFields.class.getName()) != null) { + methodInfo.anyFieldModified = false; + } else { + ClassReader containingClass = classes.get(method.getOwnerName()); + if (containingClass.getAnnotations().get(DoesNotModifyStaticFields.class.getName()) != null) { + methodInfo.anyFieldModified = false; + } + } + } else { + for (BasicBlockReader block : program.getBasicBlocks()) { + block.readAllInstructions(reader); + } + } + + if (method.hasModifier(ElementModifier.SYNCHRONIZED)) { + reader.initClass("java.lang.Thread"); + } + + methodInfo.complete = true; + } + + return methodInfo; + } + + class InstructionAnalyzer extends AbstractInstructionReader { + String currentClass; + MethodInfo methodInfo; + MethodDependencyInfo methodDep; + + InstructionAnalyzer(String currentClass, MethodInfo methodInfo) { + this.currentClass = currentClass; + this.methodInfo = methodInfo; + methodDep = dependencyInfo.getMethod(methodInfo.method); + } + + @Override + public void stringConstant(VariableReader receiver, String cst) { + analyzeInitializer("java.lang.String"); + } + + @Override + public void create(VariableReader receiver, String type) { + analyzeInitializer(type); + } + + @Override + public void getField(VariableReader receiver, VariableReader instance, FieldReference field, + ValueType fieldType) { + if (instance == null) { + analyzeInitializer(field.getClassName()); + } + } + + @Override + public void putField(VariableReader instance, FieldReference field, VariableReader value, ValueType fieldType) { + if (instance == null) { + analyzeInitializer(field.getClassName()); + if (!methodInfo.anyFieldModified && !field.getClassName().equals(currentClass)) { + if (methodInfo.classesWithModifiedFields == null) { + methodInfo.classesWithModifiedFields = new HashSet<>(); + } + methodInfo.classesWithModifiedFields.add(field.getClassName()); + } + } + } + + @Override + public void invoke(VariableReader receiver, VariableReader instance, MethodReference method, + List arguments, InvocationType type) { + if (type == InvocationType.VIRTUAL) { + ValueDependencyInfo instanceDep = methodDep.getVariable(instance.getIndex()); + Set implementations = Devirtualization.implementations(hierarchy, dependencyInfo, + instanceDep.getTypes(), method); + for (MethodReference implementation : implementations) { + invokeMethod(implementation); + } + } else { + analyzeInitializer(method.getClassName()); + invokeMethod(method); + } + } + + private void invokeMethod(MethodReference method) { + ClassReader cls = classes.get(method.getClassName()); + if (cls != null) { + MethodReader methodReader = cls.getMethod(method.getDescriptor()); + if (methodReader != null) { + analyzeCalledMethod(analyzeMethod(methodReader)); + } + } + } + + @Override + public void initClass(String className) { + analyzeInitializer(className); + } + + @Override + public void monitorEnter(VariableReader objectRef) { + initClass("java.lang.Thread"); + } + + @Override + public void monitorExit(VariableReader objectRef) { + initClass("java.lang.Thread"); + } + + void analyzeInitializer(String className) { + if (className.equals(currentClass)) { + return; + } + + analyze(className); + } + + private void analyzeCalledMethod(MethodInfo calledMethod) { + if (methodInfo.anyFieldModified) { + return; + } + + if (calledMethod.anyFieldModified) { + methodInfo.anyFieldModified = true; + methodInfo.classesWithModifiedFields = null; + } else if (calledMethod.classesWithModifiedFields != null) { + for (String className : calledMethod.classesWithModifiedFields) { + if (className.equals(currentClass)) { + if (methodInfo.classesWithModifiedFields == null) { + methodInfo.classesWithModifiedFields = new HashSet<>(); + } + methodInfo.classesWithModifiedFields.add(className); + } + } + } + } + } + + static class MethodInfo { + MethodReference method; + boolean complete; + Set recursiveCallers; + Set classesWithModifiedFields; + boolean anyFieldModified; + + MethodInfo(MethodReference method) { + this.method = method; + } + } +} diff --git a/core/src/main/java/org/teavm/model/analysis/ClassInitializerInfo.java b/core/src/main/java/org/teavm/model/analysis/ClassInitializerInfo.java new file mode 100644 index 000000000..7b21972a0 --- /dev/null +++ b/core/src/main/java/org/teavm/model/analysis/ClassInitializerInfo.java @@ -0,0 +1,37 @@ +/* + * Copyright 2019 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.model.analysis; + +import java.util.Collections; +import java.util.List; + +public interface ClassInitializerInfo { + boolean isDynamicInitializer(String className); + + List getInitializationOrder(); + + ClassInitializerInfo EMPTY = new ClassInitializerInfo() { + @Override + public boolean isDynamicInitializer(String className) { + return true; + } + + @Override + public List getInitializationOrder() { + return Collections.emptyList(); + } + }; +} diff --git a/core/src/main/java/org/teavm/model/optimization/Devirtualization.java b/core/src/main/java/org/teavm/model/optimization/Devirtualization.java index 2774896e3..c67f3b5f7 100644 --- a/core/src/main/java/org/teavm/model/optimization/Devirtualization.java +++ b/core/src/main/java/org/teavm/model/optimization/Devirtualization.java @@ -17,6 +17,7 @@ package org.teavm.model.optimization; import java.util.Collections; import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.Set; import org.teavm.common.OptionalPredicate; import org.teavm.dependency.DependencyInfo; @@ -73,8 +74,13 @@ public class Devirtualization { } private Set getImplementations(String[] classNames, MethodReference ref) { + return implementations(hierarchy, dependency, classNames, ref); + } + + public static Set implementations(ClassHierarchy hierarchy, DependencyInfo dependency, + String[] classNames, MethodReference ref) { OptionalPredicate isSuperclass = hierarchy.getSuperclassPredicate(ref.getClassName()); - Set methods = new HashSet<>(); + Set methods = new LinkedHashSet<>(); for (String className : classNames) { if (className.startsWith("[")) { className = "java.lang.Object"; diff --git a/core/src/main/java/org/teavm/model/optimization/Inlining.java b/core/src/main/java/org/teavm/model/optimization/Inlining.java index 69cacdaf6..2c16d4bf9 100644 --- a/core/src/main/java/org/teavm/model/optimization/Inlining.java +++ b/core/src/main/java/org/teavm/model/optimization/Inlining.java @@ -47,6 +47,7 @@ import org.teavm.model.ProgramReader; import org.teavm.model.TryCatchBlock; import org.teavm.model.VariableReader; import org.teavm.model.analysis.ClassInference; +import org.teavm.model.analysis.ClassInitializerInfo; import org.teavm.model.instructions.AbstractInstructionReader; import org.teavm.model.instructions.AssignInstruction; import org.teavm.model.instructions.ExitInstruction; @@ -70,15 +71,17 @@ public class Inlining { private MethodUsageCounter usageCounter; private Set methodsUsedOnce = new HashSet<>(); private boolean devirtualization; + private ClassInitializerInfo classInitializerInfo; public Inlining(ClassHierarchy hierarchy, DependencyInfo dependencyInfo, InliningStrategy strategy, ListableClassReaderSource classes, Predicate externalMethods, - boolean devirtualization) { + boolean devirtualization, ClassInitializerInfo classInitializerInfo) { this.hierarchy = hierarchy; this.classes = classes; this.dependencyInfo = dependencyInfo; this.strategy = strategy; this.devirtualization = devirtualization; + this.classInitializerInfo = classInitializerInfo; usageCounter = new MethodUsageCounter(externalMethods); for (String className : classes.getClassNames()) { @@ -223,7 +226,8 @@ public class Inlining { splitBlock.getTryCatchBlocks().addAll(ProgramUtils.copyTryCatches(block, program)); invoke.delete(); - if (invoke.getMethod().getName().equals("") || invoke.getInstance() == null) { + if ((invoke.getMethod().getName().equals("") || invoke.getInstance() == null) + && classInitializerInfo.isDynamicInitializer(invoke.getMethod().getClassName())) { InitClassInstruction clinit = new InitClassInstruction(); clinit.setClassName(invoke.getMethod().getClassName()); block.add(clinit); diff --git a/core/src/main/java/org/teavm/model/transformation/ClassInitializerInsertionTransformer.java b/core/src/main/java/org/teavm/model/transformation/ClassInitializerInsertionTransformer.java index e66575bb4..28e40b981 100644 --- a/core/src/main/java/org/teavm/model/transformation/ClassInitializerInsertionTransformer.java +++ b/core/src/main/java/org/teavm/model/transformation/ClassInitializerInsertionTransformer.java @@ -22,20 +22,24 @@ import org.teavm.model.ElementModifier; import org.teavm.model.MethodDescriptor; import org.teavm.model.MethodReader; import org.teavm.model.Program; +import org.teavm.model.analysis.ClassInitializerInfo; import org.teavm.model.instructions.InitClassInstruction; public class ClassInitializerInsertionTransformer { private static final MethodDescriptor clinitDescriptor = new MethodDescriptor("", void.class); private ClassReaderSource classes; + private ClassInitializerInfo classInitializerInfo; - public ClassInitializerInsertionTransformer(ClassReaderSource classes) { + public ClassInitializerInsertionTransformer(ClassReaderSource classes, ClassInitializerInfo classInitializerInfo) { this.classes = classes; + this.classInitializerInfo = classInitializerInfo; } public void apply(MethodReader method, Program program) { ClassReader cls = classes.get(method.getOwnerName()); - boolean hasClinit = cls.getMethod(clinitDescriptor) != null; - if (needsClinitCall(method) && hasClinit) { + boolean hasClinit = cls.getMethod(clinitDescriptor) != null + && classInitializerInfo.isDynamicInitializer(cls.getName()); + if (hasClinit && needsClinitCall(method)) { BasicBlock entryBlock = program.basicBlockAt(0); InitClassInstruction initInsn = new InitClassInstruction(); initInsn.setClassName(method.getOwnerName()); diff --git a/core/src/main/java/org/teavm/vm/TeaVM.java b/core/src/main/java/org/teavm/vm/TeaVM.java index 78b831e61..c55fb8aed 100644 --- a/core/src/main/java/org/teavm/vm/TeaVM.java +++ b/core/src/main/java/org/teavm/vm/TeaVM.java @@ -51,14 +51,17 @@ import org.teavm.dependency.MethodDependencyInfo; import org.teavm.diagnostics.AccumulationDiagnostics; import org.teavm.diagnostics.Diagnostics; import org.teavm.diagnostics.ProblemProvider; +import org.teavm.model.BasicBlock; import org.teavm.model.ClassHierarchy; import org.teavm.model.ClassHolder; +import org.teavm.model.ClassHolderSource; import org.teavm.model.ClassHolderTransformer; import org.teavm.model.ClassReader; import org.teavm.model.ClassReaderSource; import org.teavm.model.ElementModifier; import org.teavm.model.FieldHolder; import org.teavm.model.FieldReference; +import org.teavm.model.Instruction; import org.teavm.model.ListableClassHolderSource; import org.teavm.model.ListableClassReaderSource; import org.teavm.model.MethodDescriptor; @@ -69,6 +72,10 @@ import org.teavm.model.MutableClassHolderSource; import org.teavm.model.Program; import org.teavm.model.ProgramCache; import org.teavm.model.ValueType; +import org.teavm.model.analysis.ClassInitializerAnalysis; +import org.teavm.model.analysis.ClassInitializerInfo; +import org.teavm.model.instructions.InitClassInstruction; +import org.teavm.model.instructions.InvokeInstruction; import org.teavm.model.optimization.ArrayUnwrapMotion; import org.teavm.model.optimization.ClassInitElimination; import org.teavm.model.optimization.ConstantConditionElimination; @@ -154,6 +161,7 @@ public class TeaVM implements TeaVMHost, ServiceRepository { private int compileProgressLimit; private int compileProgressValue; private ClassSourcePacker classSourcePacker; + private ClassInitializerInfo classInitializerInfo; TeaVM(TeaVMBuilder builder) { target = builder.target; @@ -374,19 +382,19 @@ public class TeaVM implements TeaVMHost, ServiceRepository { cacheStatus = new AnnotationAwareCacheStatus(rawCacheStatus, dependencyAnalyzer.getIncrementalDependencies(), dependencyAnalyzer.getClassSource()); cacheStatus.addSynthesizedClasses(dependencyAnalyzer::isSynthesizedClass); - target.setController(targetController); if (wasCancelled()) { return; } - target.analyzeBeforeOptimizations(new ListableClassReaderSourceAdapter( - dependencyAnalyzer.getClassSource(), - new LinkedHashSet<>(dependencyAnalyzer.getReachableClasses()))); - boolean isLazy = optimizationLevel == TeaVMOptimizationLevel.SIMPLE; ListableClassHolderSource classSet; if (isLazy) { + classInitializerInfo = ClassInitializerInfo.EMPTY; + target.setController(targetController); + target.analyzeBeforeOptimizations(new ListableClassReaderSourceAdapter( + dependencyAnalyzer.getClassSource(), + new LinkedHashSet<>(dependencyAnalyzer.getReachableClasses()))); initCompileProgress(1000); classSet = lazyPipeline(); } else { @@ -439,6 +447,14 @@ public class TeaVM implements TeaVMHost, ServiceRepository { if (wasCancelled()) { return null; } + + ClassInitializerAnalysis classInitializerAnalysis = new ClassInitializerAnalysis(classSet, + dependencyAnalyzer.getClassHierarchy()); + classInitializerAnalysis.analyze(dependencyAnalyzer); + classInitializerInfo = classInitializerAnalysis; + eliminateClassInit(classSet); + } else { + classInitializerInfo = ClassInitializerInfo.EMPTY; } dependencyAnalyzer.cleanupTypes(); @@ -448,6 +464,11 @@ public class TeaVM implements TeaVMHost, ServiceRepository { return null; } + target.setController(targetController); + target.analyzeBeforeOptimizations(new ListableClassReaderSourceAdapter( + dependencyAnalyzer.getClassSource(), + new LinkedHashSet<>(dependencyAnalyzer.getReachableClasses()))); + // Optimize and allocate registers optimize(classSet); if (wasCancelled()) { @@ -461,6 +482,53 @@ public class TeaVM implements TeaVMHost, ServiceRepository { return new PostProcessingClassHolderSource(); } + private void eliminateClassInit(ListableClassHolderSource classes) { + for (String className : classes.getClassNames()) { + ClassHolder cls = classes.get(className); + for (MethodHolder method : cls.getMethods()) { + Program program = method.getProgram(); + if (program == null) { + continue; + } + for (BasicBlock block : program.getBasicBlocks()) { + for (Instruction instruction : block) { + if (instruction instanceof InitClassInstruction) { + InitClassInstruction clinit = (InitClassInstruction) instruction; + if (!classInitializerInfo.isDynamicInitializer(clinit.getClassName())) { + clinit.delete(); + } + } + } + } + } + } + + for (TeaVMEntryPoint entryPoint : entryPoints.values()) { + addInitializersToEntryPoint(classes, entryPoint.getMethod()); + } + } + + private void addInitializersToEntryPoint(ClassHolderSource classes, MethodReference methodRef) { + ClassHolder cls = classes.get(methodRef.getClassName()); + if (cls == null) { + return; + } + + MethodHolder method = cls.getMethod(methodRef.getDescriptor()); + if (method == null) { + return; + } + + Program program = method.getProgram(); + BasicBlock block = program.basicBlockAt(0); + Instruction first = block.getFirstInstruction(); + for (String className : classInitializerInfo.getInitializationOrder()) { + InvokeInstruction invoke = new InvokeInstruction(); + invoke.setMethod(new MethodReference(className, "", ValueType.VOID)); + first.insertPrevious(invoke); + } + } + public ListableClassHolderSource link(DependencyAnalyzer dependency) { Linker linker = new Linker(dependency); MutableClassHolderSource cutClasses = new MutableClassHolderSource(); @@ -529,7 +597,7 @@ public class TeaVM implements TeaVMHost, ServiceRepository { } private void inline(ListableClassHolderSource classes) { - if (optimizationLevel != TeaVMOptimizationLevel.ADVANCED && optimizationLevel != TeaVMOptimizationLevel.FULL) { + if (optimizationLevel == TeaVMOptimizationLevel.SIMPLE) { return; } @@ -541,7 +609,7 @@ public class TeaVM implements TeaVMHost, ServiceRepository { } Inlining inlining = new Inlining(new ClassHierarchy(classes), dependencyAnalyzer, inliningStrategy, - classes, this::isExternal, optimizationLevel == TeaVMOptimizationLevel.FULL); + classes, this::isExternal, optimizationLevel == TeaVMOptimizationLevel.FULL, classInitializerInfo); List methodReferences = inlining.getOrder(); int classCount = classes.getClassNames().size(); int initialValue = compileProgressValue; @@ -811,6 +879,11 @@ public class TeaVM implements TeaVMHost, ServiceRepository { public void addVirtualMethods(Predicate methods) { TeaVM.this.addVirtualMethods(methods); } + + @Override + public ClassInitializerInfo getClassInitializerInfo() { + return classInitializerInfo; + } }; class PostProcessingClassHolderSource implements ListableClassHolderSource { diff --git a/core/src/main/java/org/teavm/vm/TeaVMTargetController.java b/core/src/main/java/org/teavm/vm/TeaVMTargetController.java index c37bd1467..757d124da 100644 --- a/core/src/main/java/org/teavm/vm/TeaVMTargetController.java +++ b/core/src/main/java/org/teavm/vm/TeaVMTargetController.java @@ -25,6 +25,7 @@ import org.teavm.dependency.DependencyInfo; import org.teavm.diagnostics.Diagnostics; import org.teavm.model.ClassReaderSource; import org.teavm.model.MethodReference; +import org.teavm.model.analysis.ClassInitializerInfo; public interface TeaVMTargetController { boolean wasCancelled(); @@ -54,4 +55,6 @@ public interface TeaVMTargetController { TeaVMProgressFeedback reportProgress(int progress); void addVirtualMethods(Predicate methods); + + ClassInitializerInfo getClassInitializerInfo(); } diff --git a/core/src/main/resources/org/teavm/backend/javascript/intern.js b/core/src/main/resources/org/teavm/backend/javascript/intern.js new file mode 100644 index 000000000..dae99429d --- /dev/null +++ b/core/src/main/resources/org/teavm/backend/javascript/intern.js @@ -0,0 +1,66 @@ +/* + * Copyright 2019 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +"use strict"; + +var $rt_intern = function() { + var table = new Array(100); + var size = 0; + + function get(str) { + var hash = $rt_stringHash(str); + var bucket = getBucket(hash); + for (var i = 0; i < bucket.length; ++i) { + if ($rt_stringEquals(bucket[i], str)) { + return bucket[i]; + } + } + bucket.push(str); + return str; + } + + function getBucket(hash) { + while (true) { + var position = hash % table.length; + var bucket = table[position]; + if (typeof bucket !== "undefined") { + return bucket; + } + if (++size / table.length > 0.5) { + rehash(); + } else { + bucket = []; + table[position] = bucket; + return bucket; + } + } + } + + function rehash() { + var old = table; + table = new Array(table.length * 2); + size = 0; + for (var i = 0; i < old.length; ++i) { + var bucket = old[i]; + if (typeof bucket !== "undefined") { + for (var j = 0; j < bucket.length; ++j) { + get(bucket[j]); + } + } + } + } + + return get; +}(); diff --git a/interop/core/src/main/java/org/teavm/interop/DoesNotModifyStaticFields.java b/interop/core/src/main/java/org/teavm/interop/DoesNotModifyStaticFields.java new file mode 100644 index 000000000..1f4cbcc73 --- /dev/null +++ b/interop/core/src/main/java/org/teavm/interop/DoesNotModifyStaticFields.java @@ -0,0 +1,26 @@ +/* + * Copyright 2019 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.interop; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.METHOD, ElementType.TYPE }) +public @interface DoesNotModifyStaticFields { +} diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/JSClassProcessor.java b/jso/impl/src/main/java/org/teavm/jso/impl/JSClassProcessor.java index b7878bcb5..01a661188 100644 --- a/jso/impl/src/main/java/org/teavm/jso/impl/JSClassProcessor.java +++ b/jso/impl/src/main/java/org/teavm/jso/impl/JSClassProcessor.java @@ -32,6 +32,7 @@ import org.mozilla.javascript.ast.FunctionNode; import org.teavm.backend.javascript.rendering.JSParser; import org.teavm.cache.IncrementalDependencyRegistration; import org.teavm.diagnostics.Diagnostics; +import org.teavm.interop.DoesNotModifyStaticFields; import org.teavm.interop.Sync; import org.teavm.jso.JSBody; import org.teavm.jso.JSByRef; @@ -653,6 +654,7 @@ class JSClassProcessor { MethodHolder proxyMethod = new MethodHolder(proxyRef.getDescriptor()); proxyMethod.getModifiers().add(ElementModifier.NATIVE); proxyMethod.getModifiers().add(ElementModifier.STATIC); + proxyMethod.getAnnotations().add(new AnnotationHolder(DoesNotModifyStaticFields.class.getName())); boolean inline = repository.inlineMethods.contains(methodRef); AnnotationHolder generatorAnnot = new AnnotationHolder(inline ? DynamicInjector.class.getName() : DynamicGenerator.class.getName()); diff --git a/platform/src/main/java/org/teavm/platform/plugin/MetadataProviderTransformer.java b/platform/src/main/java/org/teavm/platform/plugin/MetadataProviderTransformer.java index 38ab0a4a3..6f8d0a432 100644 --- a/platform/src/main/java/org/teavm/platform/plugin/MetadataProviderTransformer.java +++ b/platform/src/main/java/org/teavm/platform/plugin/MetadataProviderTransformer.java @@ -18,6 +18,7 @@ package org.teavm.platform.plugin; import java.util.HashSet; import java.util.Set; import org.teavm.backend.javascript.spi.GeneratedBy; +import org.teavm.interop.DoesNotModifyStaticFields; import org.teavm.model.AccessLevel; import org.teavm.model.AnnotationHolder; import org.teavm.model.AnnotationValue; @@ -67,6 +68,9 @@ class MetadataProviderTransformer implements ClassHolderTransformer { MetadataProviderNativeGenerator.class.getName()))); createMethod.getAnnotations().add(genAnnot); ModelUtils.copyAnnotations(method.getAnnotations(), createMethod.getAnnotations()); + if (createMethod.getAnnotations().get(DoesNotModifyStaticFields.class.getName()) == null) { + createMethod.getAnnotations().add(new AnnotationHolder(DoesNotModifyStaticFields.class.getName())); + } AnnotationHolder refAnnot = new AnnotationHolder(MetadataProviderRef.class.getName()); refAnnot.getValues().put("value", new AnnotationValue(method.getReference().toString()));