From 3e4914610ce70c73650c81024599e8963bfc0254 Mon Sep 17 00:00:00 2001 From: Colin Alworth Date: Sat, 24 Jan 2026 13:39:44 -0600 Subject: [PATCH 1/7] Run OptimizedOnlyCompilerSuite when draft is disabled Somehow this suite was permenently disabled, and one test has broken in the meantime due to String.contains being simplified to the point it can more easily be inlined. When #10147 is fixed, this can be re-enabled. Fixes #10253 --- user/build.xml | 6 +++--- .../com/google/gwt/dev/jjs/OptimizedOnlyCompilerSuite.java | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/user/build.xml b/user/build.xml index eeb048fe690..1d352ccc0f8 100755 --- a/user/build.xml +++ b/user/build.xml @@ -15,7 +15,7 @@ - + @@ -432,7 +432,7 @@ value="${test.args.web.selenium} -draftCompile"/> + excludes="${gwt.junit.testcase.web.excludes},**/OptimizedOnly*"/> + excludes="${gwt.junit.testcase.web.excludes},**/OptimizedOnly*"/> Date: Sat, 24 Jan 2026 14:14:11 -0600 Subject: [PATCH 2/7] Proposed new annotation and guidelines, started adding it --- .../gwt/dev/jjs/impl/DeadCodeElimination.java | 4 +- .../com/google/gwt/emul/java/lang/Math.java | 5 +++ .../com/google/gwt/emul/java/lang/String.java | 21 +++++++++ .../annotations/ConstantFoldCandidate.java | 43 +++++++++++++++++++ .../internal/annotations/ForceInline.java | 3 +- 5 files changed, 73 insertions(+), 3 deletions(-) create mode 100644 user/super/com/google/gwt/emul/javaemul/internal/annotations/ConstantFoldCandidate.java diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/DeadCodeElimination.java b/dev/core/src/com/google/gwt/dev/jjs/impl/DeadCodeElimination.java index 17caa5ecafa..d2552b685a8 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/impl/DeadCodeElimination.java +++ b/dev/core/src/com/google/gwt/dev/jjs/impl/DeadCodeElimination.java @@ -490,7 +490,7 @@ public void endVisit(JMethodCall x, Context ctx) { if (targetType == program.getTypeJavaLangString() || (instance != null && instance.getType().getUnderlyingType() == program.getTypeJavaLangString())) { - tryOptimizeStringCall(x, ctx, target); + tryFoldMethodCall(x, ctx, target); } else if (JProgram.isClinit(target)) { // Eliminate the call if the target is now empty. if (!targetType.hasClinit()) { @@ -1832,7 +1832,7 @@ private JLiteral tryGetConstant(JVariableRef x) { /** * Replace String methods having literal args with the static result. */ - private void tryOptimizeStringCall(JMethodCall x, Context ctx, JMethod method) { + private void tryFoldMethodCall(JMethodCall x, Context ctx, JMethod method) { if (method.getType() == program.getTypeVoid()) { return; diff --git a/user/super/com/google/gwt/emul/java/lang/Math.java b/user/super/com/google/gwt/emul/java/lang/Math.java index 393d888c224..46b49952e28 100644 --- a/user/super/com/google/gwt/emul/java/lang/Math.java +++ b/user/super/com/google/gwt/emul/java/lang/Math.java @@ -18,6 +18,7 @@ import static javaemul.internal.InternalPreconditions.checkCriticalArithmetic; import javaemul.internal.JsUtils; +import javaemul.internal.annotations.ConstantFoldCandidate; import jsinterop.annotations.JsMethod; import jsinterop.annotations.JsPackage; import jsinterop.annotations.JsType; @@ -87,6 +88,7 @@ public static long addExact(long x, long y) { @JsMethod(namespace = JsPackage.GLOBAL, name = "Math.ceil") public static native double ceil(double x); + @ConstantFoldCandidate public static double copySign(double magnitude, double sign) { return isNegative(sign) ? -abs(magnitude) : abs(magnitude); } @@ -154,6 +156,7 @@ public static int floorMod(long dividend, int divisor) { return (int) floorMod(dividend, (long) divisor); } + @ConstantFoldCandidate @SuppressWarnings("CheckStyle.MethodName") public static double IEEEremainder(double v, double m) { double ratio = v / m; @@ -166,11 +169,13 @@ public static double IEEEremainder(double v, double m) { return closest == 0 ? v : v - m * closest; } + @ConstantFoldCandidate public static int getExponent(double v) { int[] intBits = JsUtils.doubleToRawIntBits(v); return ((intBits[1] >> 20) & 2047) - Double.MAX_EXPONENT; } + @ConstantFoldCandidate public static int getExponent(float v) { return ((JsUtils.floatToRawIntBits(v) >> 23) & 255) - Float.MAX_EXPONENT; } diff --git a/user/super/com/google/gwt/emul/java/lang/String.java b/user/super/com/google/gwt/emul/java/lang/String.java index 67b1cff1107..e824f004c08 100644 --- a/user/super/com/google/gwt/emul/java/lang/String.java +++ b/user/super/com/google/gwt/emul/java/lang/String.java @@ -382,6 +382,7 @@ public int codePointCount(int beginIndex, int endIndex) { } @Override + @ConstantFoldCandidate public int compareTo(String other) { // Trick compiler into thinking that these are double so what we could do arithmetic comparison // which is supported on underlying JavaScript strings. @@ -528,6 +529,7 @@ public int offsetByCodePoints(int index, int codePointOffset) { return Character.offsetByCodePoints(this, index, codePointOffset); } + @ConstantFoldCandidate public boolean regionMatches(boolean ignoreCase, int toffset, String other, int ooffset, int len) { checkNotNull(other); @@ -550,6 +552,7 @@ public boolean regionMatches(int toffset, String other, int ooffset, int len) { return regionMatches(false, toffset, other, ooffset, len); } + @ConstantFoldCandidate public String replace(char from, char to) { // Translate 'from' into unicode escape sequence (\\u and a four-digit hexadecimal number). // Escape sequence replacement is used instead of a string literal replacement @@ -560,6 +563,7 @@ public String replace(char from, char to) { return nativeReplaceAll(regex, replace); } + @ConstantFoldCandidate public String replace(CharSequence from, CharSequence to) { // Implementation note: This uses a regex replacement instead of // a string literal replacement because Safari does not @@ -585,6 +589,7 @@ public String replace(CharSequence from, CharSequence to) { * * TODO(jat): properly handle Java regex syntax */ + @ConstantFoldCandidate public String replaceAll(String regex, String replace) { replace = translateReplaceString(replace); return nativeReplaceAll(regex, replace); @@ -602,6 +607,7 @@ String nativeReplaceAll(String regex, String replace) { * * TODO(jat): properly handle Java regex syntax */ + @ConstantFoldCandidate public String replaceFirst(String regex, String replace) { replace = translateReplaceString(replace); NativeRegExp jsRegEx = new NativeRegExp(regex); @@ -689,14 +695,17 @@ public String[] split(String regex, int maxMatch) { return out; } + @ConstantFoldCandidate public boolean startsWith(String prefix) { return asNativeString().startsWith(prefix); } + @ConstantFoldCandidate public boolean startsWith(String prefix, int toffset) { return asNativeString().startsWith(prefix, toffset); } + @ConstantFoldCandidate @Override public CharSequence subSequence(int beginIndex, int endIndex) { return substring(beginIndex, endIndex); @@ -727,6 +736,7 @@ public char[] toCharArray() { * a transformation based on native locale of the browser, you can do * {@code toLowerCase(Locale.getDefault())} instead. */ + @ConstantFoldCandidate public String toLowerCase() { return asNativeString().toLowerCase(); } @@ -737,27 +747,32 @@ public String toLowerCase() { * to {@code toLowerCase} which performs the right thing for the limited set of Locale's * predefined in GWT Locale emulation. */ + @ConstantFoldCandidate public String toLowerCase(Locale locale) { return locale == Locale.getDefault() ? asNativeString().toLocaleLowerCase() : asNativeString().toLowerCase(); } // See the notes in lowerCase pair. + @ConstantFoldCandidate public String toUpperCase() { return asNativeString().toUpperCase(); } // See the notes in lowerCase pair. + @ConstantFoldCandidate public String toUpperCase(Locale locale) { return locale == Locale.getDefault() ? asNativeString().toLocaleUpperCase() : asNativeString().toUpperCase(); } @Override + @ConstantFoldCandidate public String toString() { return checkNotNull(this); } + @ConstantFoldCandidate public String trim() { int length = length(); int start = 0; @@ -771,6 +786,7 @@ public String trim() { return start > 0 || end < length ? substring(start, end) : this; } + @ConstantFoldCandidate public String strip() { int length = length(); int start = getLeadingWhitespaceLength(); @@ -780,14 +796,17 @@ public String strip() { return substring(start, length - getTrailingWhitespaceLength()); } + @ConstantFoldCandidate public String stripLeading() { return substring(getLeadingWhitespaceLength()); } + @ConstantFoldCandidate public String stripTrailing() { return substring(0, length() - getTrailingWhitespaceLength()); } + @ConstantFoldCandidate public boolean isBlank() { return length() == getLeadingWhitespaceLength(); } @@ -796,6 +815,7 @@ public Stream lines() { return StreamSupport.stream(new LinesSpliterator(), false); } + @ConstantFoldCandidate public String repeat(int count) { checkArgument(count >= 0, "count is negative: " + count); return asNativeString().repeat(count); @@ -825,6 +845,7 @@ private int getTrailingWhitespaceLength() { return length; } + @ConstantFoldCandidate public String indent(int spaces) { if (isEmpty()) { return ""; diff --git a/user/super/com/google/gwt/emul/javaemul/internal/annotations/ConstantFoldCandidate.java b/user/super/com/google/gwt/emul/javaemul/internal/annotations/ConstantFoldCandidate.java new file mode 100644 index 00000000000..0a3b783614a --- /dev/null +++ b/user/super/com/google/gwt/emul/javaemul/internal/annotations/ConstantFoldCandidate.java @@ -0,0 +1,43 @@ +/* + * Copyright 2026 GWT Project Authors + * + * 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 javaemul.internal.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + +/** + * An annotation to mark a method as a candidate for constant folding by the compiler. This method + * should not have its contents inlined until after the optimization loop has halted, then it can be + * inlined. During the loop, the compiler may replaces calls to this method if all arguments are + * literal constants, and if the instance is a constant as well, and if the method has an analogous + * implementation in the JDK. The method is assumed to be free from side effects. + *

+ * Only Strings and boxed primitives are valid instances for the purposes of this optimization + * though static methods in other classes may be considered as well if they only take literal + * parameters at a callsite. + *

+ * It is generally unnecessary to annotate methods that just perform standard math operations, + * since the compiler can inline and execute those operations on constants already. + *

+ * TODO what about methods that throw exceptions? + * TODO what about enums? + *

+ * Internal SDK use only, might change or disappear at any time. + */ +@CompilerHint +@Target(ElementType.METHOD) +public @interface ConstantFoldCandidate { +} \ No newline at end of file diff --git a/user/super/com/google/gwt/emul/javaemul/internal/annotations/ForceInline.java b/user/super/com/google/gwt/emul/javaemul/internal/annotations/ForceInline.java index c0561c2b127..ebcc9dd7099 100644 --- a/user/super/com/google/gwt/emul/javaemul/internal/annotations/ForceInline.java +++ b/user/super/com/google/gwt/emul/javaemul/internal/annotations/ForceInline.java @@ -19,7 +19,8 @@ import java.lang.annotation.Target; /** - * An annotation to mark a given method as not inlineable. + * An annotation to mark a given method as not inlineable. If the method + * cannot be inlined for any reason, the compiler will fail. *

* Internal SDK use only, might change or disappear at any time. */ From 31f01ed526be2dfb20d6e98406380e8adc01fffe Mon Sep 17 00:00:00 2001 From: Colin Alworth Date: Sat, 24 Jan 2026 15:34:49 -0600 Subject: [PATCH 3/7] Mostly working POC --- .../javac/testing/impl/JavaResourceBase.java | 14 +++- .../com/google/gwt/dev/jjs/ast/JMethod.java | 10 +++ .../gwt/dev/jjs/impl/DeadCodeElimination.java | 72 +++++++++++-------- .../gwt/dev/jjs/impl/GwtAstBuilder.java | 8 +++ .../gwt/dev/jjs/impl/MakeCallsStatic.java | 3 + .../com/google/gwt/emul/java/lang/Long.java | 6 ++ 6 files changed, 81 insertions(+), 32 deletions(-) diff --git a/dev/core/src/com/google/gwt/dev/javac/testing/impl/JavaResourceBase.java b/dev/core/src/com/google/gwt/dev/javac/testing/impl/JavaResourceBase.java index e4fe4fea2cf..563dc2b25f2 100644 --- a/dev/core/src/com/google/gwt/dev/javac/testing/impl/JavaResourceBase.java +++ b/dev/core/src/com/google/gwt/dev/javac/testing/impl/JavaResourceBase.java @@ -330,30 +330,38 @@ public class JavaResourceBase { createMockJavaResource("java.lang.String", "package java.lang;", "import java.io.Serializable;", + "import javaemul.internal.annotations.ConstantFoldCandidate;", "import javaemul.internal.annotations.SpecializeMethod;", "public final class String implements Comparable, CharSequence, Serializable {", " public String() { }", " public String(char c) { }", " public String(String s) { }", - " public static String _String() { return \"\"; }", - " public static String _String(char c) { return \"\" + c; }", - " public static String _String(String s) { return s; }", +// " public static String _String() { return \"\"; }", +// " public static String _String(char c) { return \"\" + c; }", +// " public static String _String(String s) { return s; }", + " @ConstantFoldCandidate", " public char charAt(int index) { return 'a'; }", " public int compareTo(String other) { return -1; }", + " @ConstantFoldCandidate", " @SpecializeMethod(params = String.class, target = \"equals\")", " public boolean equals(Object other) {", " return (other instanceof String) && equals((String) other);", " }", + " @ConstantFoldCandidate", " private native boolean equals(String obj) /*-{ return false; }-*/;", + " @ConstantFoldCandidate", " public boolean equalsIgnoreCase(String str) { return false; }", + " @ConstantFoldCandidate", " public int length() { return 0; }", " public static String valueOf(int i) { return \"\" + i; }", " public static String valueOf(char c) { return \"\" + c; }", + " @ConstantFoldCandidate", " public int hashCode() { return 0; }", " public String replace(char c1, char c2) { return null; }", " public boolean startsWith(String str) { return false; }", " public native String substring(int start, int len) /*-{ return \"\"; }-*/;", " public String toLowerCase() { return null; }", + " @ConstantFoldCandidate", " public String toString() { return this; }", " public static String valueOf(boolean b) { return null; }", "}"); diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JMethod.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JMethod.java index cecef6ac680..d9e883739db 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/ast/JMethod.java +++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JMethod.java @@ -59,6 +59,7 @@ public int compare(JMethod m1, JMethod m2) { private boolean preventDevirtualization = false; private boolean hasSideEffects = true; private boolean defaultMethod = false; + private boolean allowConstantFolding = false; private boolean syntheticAccidentalOverride = false; private Set suppressedWarnings; @@ -322,6 +323,15 @@ public boolean isJsMethodVarargs() { JParameter lastParameter = Iterables.getLast(getParams()); return lastParameter.isVarargs(); } + + public boolean isConstantFoldingAllowed() { + return allowConstantFolding; + } + + public void allowConstantFolding() { + this.allowConstantFolding = true; + } + /** * AST representation of @SpecializeMethod. */ diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/DeadCodeElimination.java b/dev/core/src/com/google/gwt/dev/jjs/impl/DeadCodeElimination.java index d2552b685a8..8e5519b8ae8 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/impl/DeadCodeElimination.java +++ b/dev/core/src/com/google/gwt/dev/jjs/impl/DeadCodeElimination.java @@ -487,9 +487,7 @@ public void endVisit(JMethodCall x, Context ctx) { // Normal optimizations. JDeclaredType targetType = target.getEnclosingType(); - if (targetType == program.getTypeJavaLangString() || - (instance != null && - instance.getType().getUnderlyingType() == program.getTypeJavaLangString())) { + if (target.isConstantFoldingAllowed()) { tryFoldMethodCall(x, ctx, target); } else if (JProgram.isClinit(target)) { // Eliminate the call if the target is now empty. @@ -1835,6 +1833,7 @@ private JLiteral tryGetConstant(JVariableRef x) { private void tryFoldMethodCall(JMethodCall x, Context ctx, JMethod method) { if (method.getType() == program.getTypeVoid()) { + // TODO should warn, this shouldn't even be decorated return; } @@ -1851,23 +1850,30 @@ private void tryFoldMethodCall(JMethodCall x, Context ctx, JMethod method) { ? x.getArgs().subList(1, x.getArgs().size()) : x.getArgs(); - // Handle toString specially to make sure it is a noop on non null string objects. - if (method.getName().endsWith("toString")) { - // replaces s.toString() with s - if (!instance.getType().canBeNull()) { - // Only replace when it known to be non null, otherwise it should follow the normal path - // and throw an NPE if null at runtime. - ctx.replaceMe(instance); + Class methodEnclosingType = classObjectForType(method.getEnclosingType()); + + final Object instanceLiteral; + if (instance != null) { + // Handle toString specially to make sure it is a noop on non-null string objects. + if (method.getName().endsWith("toString") && instance.getType() == program.getTypeJavaLangString()) { + // replaces s.toString() with s + if (!instance.getType().canBeNull()) { + // Only replace when it is known to be non-null, otherwise it should follow the normal path + // and throw an NPE if null at runtime. + ctx.replaceMe(instance); + } + return; } - return; - } - Object instanceLiteral = tryTranslateLiteral(instance, String.class); - method = isStaticImplMethod ? program.instanceMethodForStaticImpl(method) : method; + instanceLiteral = tryTranslateLiteral(instance, methodEnclosingType); + method = isStaticImplMethod ? program.instanceMethodForStaticImpl(method) : method; - if (instanceLiteral == null && !method.isStatic()) { - // Instance method on an instance that was not a literal. - return; + if (instanceLiteral == null && !method.isStatic()) { + // Instance method on an instance that was not a literal. + return; + } + } else { + instanceLiteral = null; } // Extract constant values from arguments or bail out if not possible, and also extract the @@ -1885,20 +1891,20 @@ private void tryFoldMethodCall(JMethodCall x, Context ctx, JMethod method) { } // Finally invoke the method statically. - JLiteral resultValue = staticallyInvokeStringMethod( - method.getName(), instanceLiteral, parametersClasses, argumentConstantValues); + JLiteral resultValue = staticallyInvokeMethod( + method.getName(), methodEnclosingType, instanceLiteral, parametersClasses, argumentConstantValues); if (resultValue != null) { ctx.replaceMe(resultValue); } } - private JLiteral staticallyInvokeStringMethod( - String methodName, Object instance, Class[] parameterClasses, Object[] argumentValues) { - Method actualMethod = getMethod(methodName, String.class, parameterClasses); + private JLiteral staticallyInvokeMethod( + String methodName, Class methodEnclosingType, Object instance, Class[] parameterClasses, Object[] argumentValues) { + Method actualMethod = getMethod(methodName, methodEnclosingType, parameterClasses); if (actualMethod == null) { // Convert all parameters types to Object to find a more generic applicable method. Arrays.fill(parameterClasses, Object.class); - actualMethod = getMethod(methodName, String.class, parameterClasses); + actualMethod = getMethod(methodName, methodEnclosingType, parameterClasses); } if (actualMethod == null) { return null; @@ -1979,22 +1985,22 @@ private Object tryTranslateLiteral(JExpression maybeLiteral, Class type) { } // TODO: make this way better by a mile if (type == boolean.class && maybeLiteral instanceof JBooleanLiteral) { - return Boolean.valueOf(((JBooleanLiteral) maybeLiteral).getValue()); + return ((JBooleanLiteral) maybeLiteral).getValue(); } if (type == char.class && maybeLiteral instanceof JCharLiteral) { - return Character.valueOf(((JCharLiteral) maybeLiteral).getValue()); + return ((JCharLiteral) maybeLiteral).getValue(); } if (type == double.class && maybeLiteral instanceof JFloatLiteral) { - return new Double(((JFloatLiteral) maybeLiteral).getValue()); + return ((JFloatLiteral) maybeLiteral).getValue(); } if (type == double.class && maybeLiteral instanceof JDoubleLiteral) { - return new Double(((JDoubleLiteral) maybeLiteral).getValue()); + return ((JDoubleLiteral) maybeLiteral).getValue(); } if (type == int.class && maybeLiteral instanceof JIntLiteral) { - return Integer.valueOf(((JIntLiteral) maybeLiteral).getValue()); + return ((JIntLiteral) maybeLiteral).getValue(); } if (type == long.class && maybeLiteral instanceof JLongLiteral) { - return Long.valueOf(((JLongLiteral) maybeLiteral).getValue()); + return ((JLongLiteral) maybeLiteral).getValue(); } if (type == String.class && maybeLiteral instanceof JStringLiteral) { return ((JStringLiteral) maybeLiteral).getValue(); @@ -2076,13 +2082,21 @@ public DeadCodeElimination(JProgram program) { .put(program.getTypeJavaLangObject(), Object.class) .put(program.getTypeJavaLangString(), String.class) .put(program.getTypePrimitiveBoolean(), boolean.class) + .put(program.getTypePrimitiveBoolean().getWrapperTypeName(), Boolean.class) .put(program.getTypePrimitiveByte(), byte.class) + .put(program.getTypePrimitiveByte().getWrapperTypeName(), Byte.class) .put(program.getTypePrimitiveChar(), char.class) + .put(program.getTypePrimitiveChar().getWrapperTypeName(), Character.class) .put(program.getTypePrimitiveDouble(), double.class) + .put(program.getTypePrimitiveDouble().getWrapperTypeName(), Double.class) .put(program.getTypePrimitiveFloat(), float.class) + .put(program.getTypePrimitiveFloat().getWrapperTypeName(), Float.class) .put(program.getTypePrimitiveInt(), int.class) + .put(program.getTypePrimitiveInt().getWrapperTypeName(), Integer.class) .put(program.getTypePrimitiveLong(), long.class) + .put(program.getTypePrimitiveLong().getWrapperTypeName(), Long.class) .put(program.getTypePrimitiveShort(), short.class) + .put(program.getTypePrimitiveShort().getWrapperTypeName(), Short.class) .build(); } diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/GwtAstBuilder.java b/dev/core/src/com/google/gwt/dev/jjs/impl/GwtAstBuilder.java index eb8b806cc27..207f7a14824 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/impl/GwtAstBuilder.java +++ b/dev/core/src/com/google/gwt/dev/jjs/impl/GwtAstBuilder.java @@ -4479,6 +4479,7 @@ private void createMethod(AbstractMethodDeclaration x) { private void processAnnotations(Annotation[] annotations, JMethod method) { maybeAddMethodSpecialization(annotations, method); + maybeMarkConstantFoldCandidate(annotations, method); maybeSetInliningMode(annotations, method); maybeSetHasNoSideEffects(annotations, method); JsInteropUtil.maybeSetJsInteropProperties(method, shouldExport(method), annotations); @@ -4541,6 +4542,13 @@ private void maybeAddMethodSpecialization(Annotation[] annotations, JMethod meth method.setSpecialization(paramTypes, returnsType, targetMethod); } + private void maybeMarkConstantFoldCandidate(Annotation[] annotations, JMethod method) { + if (JdtUtil.getAnnotationByName( + annotations, "javaemul.internal.annotations.ConstantFoldCandidate") != null) { + method.allowConstantFolding(); + } + } + private void createParameter(SourceInfo info, LocalVariableBinding binding, boolean isVarargs, JMethod method, Annotation... annotations) { createParameter(info, binding, intern(binding.name), method, isVarargs, annotations); diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/MakeCallsStatic.java b/dev/core/src/com/google/gwt/dev/jjs/impl/MakeCallsStatic.java index 33e4920dd73..f56cb160f38 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/impl/MakeCallsStatic.java +++ b/dev/core/src/com/google/gwt/dev/jjs/impl/MakeCallsStatic.java @@ -165,6 +165,9 @@ public boolean visit(JMethod x, Context ctx) { .getAccess()); newMethod.setInliningMode(x.getInliningMode()); newMethod.setHasSideEffects(x.hasSideEffects()); + if (x.isConstantFoldingAllowed()) { + newMethod.allowConstantFolding(); + } newMethod.setSynthetic(); newMethod.addThrownExceptions(x.getThrownExceptions()); if (x.isJsOverlay()) { diff --git a/user/super/com/google/gwt/emul/java/lang/Long.java b/user/super/com/google/gwt/emul/java/lang/Long.java index 33622aa6b4f..fde9946cf33 100644 --- a/user/super/com/google/gwt/emul/java/lang/Long.java +++ b/user/super/com/google/gwt/emul/java/lang/Long.java @@ -16,6 +16,7 @@ package java.lang; import javaemul.internal.LongUtils; +import javaemul.internal.annotations.ConstantFoldCandidate; import javaemul.internal.annotations.HasNoSideEffects; /** Wraps a primitive long as an object. */ @@ -43,6 +44,7 @@ private static Long get(long l) { public static final int BYTES = SIZE / Byte.SIZE; public static final Class TYPE = long.class; + @ConstantFoldCandidate public static int bitCount(long l) { int high = LongUtils.getHighBits(l); int low = (int) l; @@ -89,6 +91,7 @@ public static long min(long a, long b) { return Math.min(a, b); } + @ConstantFoldCandidate public static int numberOfLeadingZeros(long l) { int high = LongUtils.getHighBits(l); if (high != 0) { @@ -98,6 +101,7 @@ public static int numberOfLeadingZeros(long l) { } } + @ConstantFoldCandidate public static int numberOfTrailingZeros(long l) { int low = (int) l; if (low != 0) { @@ -111,10 +115,12 @@ public static long parseLong(String s) throws NumberFormatException { return parseLong(s, 10); } + @ConstantFoldCandidate public static long parseLong(String s, int radix) throws NumberFormatException { return __parseAndValidateLong(s, radix); } + @ConstantFoldCandidate public static long reverse(long l) { int high = LongUtils.getHighBits(l); int low = (int) l; From 6637c3770f8df3b7a30ac15adbc8ee40448d9062 Mon Sep 17 00:00:00 2001 From: Colin Alworth Date: Sat, 24 Jan 2026 18:16:11 -0600 Subject: [PATCH 4/7] Restore the test, continue to add annotations, fix impl for more cases --- dev/core/src/com/google/gwt/dev/jjs/ast/JMethod.java | 4 ++-- .../google/gwt/dev/jjs/impl/DeadCodeElimination.java | 9 ++++----- .../src/com/google/gwt/dev/jjs/impl/GwtAstBuilder.java | 2 +- .../com/google/gwt/dev/jjs/impl/MakeCallsStatic.java | 4 +--- .../src/com/google/gwt/dev/jjs/impl/MethodInliner.java | 5 +++++ .../google/gwt/dev/jjs/impl/RemoveSpecializations.java | 8 ++++++++ user/super/com/google/gwt/emul/java/lang/String.java | 10 ++++++++++ .../google/gwt/dev/jjs/OptimizedOnlyCompilerSuite.java | 4 ++-- 8 files changed, 33 insertions(+), 13 deletions(-) diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JMethod.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JMethod.java index d9e883739db..67a64d89195 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/ast/JMethod.java +++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JMethod.java @@ -328,8 +328,8 @@ public boolean isConstantFoldingAllowed() { return allowConstantFolding; } - public void allowConstantFolding() { - this.allowConstantFolding = true; + public void setAllowConstantFolding(boolean allowConstantFolding) { + this.allowConstantFolding = allowConstantFolding; } /** diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/DeadCodeElimination.java b/dev/core/src/com/google/gwt/dev/jjs/impl/DeadCodeElimination.java index 8e5519b8ae8..4c5b0456dd2 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/impl/DeadCodeElimination.java +++ b/dev/core/src/com/google/gwt/dev/jjs/impl/DeadCodeElimination.java @@ -2005,11 +2005,8 @@ private Object tryTranslateLiteral(JExpression maybeLiteral, Class type) { if (type == String.class && maybeLiteral instanceof JStringLiteral) { return ((JStringLiteral) maybeLiteral).getValue(); } - if (type == Object.class) { - // We already know it is a JValueLiteral instance - return ((JValueLiteral) maybeLiteral).getValueObj(); - } - return null; + // We already know it is a JValueLiteral instance + return ((JValueLiteral) maybeLiteral).getValueObj(); } } @@ -2078,9 +2075,11 @@ private static Set affectedMethods(OptimizerContext optimizerCtx) { public DeadCodeElimination(JProgram program) { this.program = program; + // TODO try to start with the root types were concerned with and work upwards? classObjectsByType = new ImmutableMap.Builder() .put(program.getTypeJavaLangObject(), Object.class) .put(program.getTypeJavaLangString(), String.class) + .put(program.getIndexedType("CharSequence"), CharSequence.class) .put(program.getTypePrimitiveBoolean(), boolean.class) .put(program.getTypePrimitiveBoolean().getWrapperTypeName(), Boolean.class) .put(program.getTypePrimitiveByte(), byte.class) diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/GwtAstBuilder.java b/dev/core/src/com/google/gwt/dev/jjs/impl/GwtAstBuilder.java index 207f7a14824..905a2d979cc 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/impl/GwtAstBuilder.java +++ b/dev/core/src/com/google/gwt/dev/jjs/impl/GwtAstBuilder.java @@ -4545,7 +4545,7 @@ private void maybeAddMethodSpecialization(Annotation[] annotations, JMethod meth private void maybeMarkConstantFoldCandidate(Annotation[] annotations, JMethod method) { if (JdtUtil.getAnnotationByName( annotations, "javaemul.internal.annotations.ConstantFoldCandidate") != null) { - method.allowConstantFolding(); + method.setAllowConstantFolding(true); } } diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/MakeCallsStatic.java b/dev/core/src/com/google/gwt/dev/jjs/impl/MakeCallsStatic.java index f56cb160f38..0c60d129586 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/impl/MakeCallsStatic.java +++ b/dev/core/src/com/google/gwt/dev/jjs/impl/MakeCallsStatic.java @@ -165,9 +165,7 @@ public boolean visit(JMethod x, Context ctx) { .getAccess()); newMethod.setInliningMode(x.getInliningMode()); newMethod.setHasSideEffects(x.hasSideEffects()); - if (x.isConstantFoldingAllowed()) { - newMethod.allowConstantFolding(); - } + newMethod.setAllowConstantFolding(x.isConstantFoldingAllowed()); newMethod.setSynthetic(); newMethod.addThrownExceptions(x.getThrownExceptions()); if (x.isJsOverlay()) { diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/MethodInliner.java b/dev/core/src/com/google/gwt/dev/jjs/impl/MethodInliner.java index d6cec047540..7a33475923e 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/impl/MethodInliner.java +++ b/dev/core/src/com/google/gwt/dev/jjs/impl/MethodInliner.java @@ -131,6 +131,11 @@ public void endVisit(JMethodCall x, Context ctx) { return; } + if (method.isConstantFoldingAllowed()) { + // Can't inline this method while it might still be constant-folded. + return; + } + JMethod.Specialization specialization = getCurrentMethod().getSpecialization(); // If we have a specialization, don't inline that away - specializations must be called // so they aren't pruned or type tightened into uselessness. diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/RemoveSpecializations.java b/dev/core/src/com/google/gwt/dev/jjs/impl/RemoveSpecializations.java index 25595175bf5..41c6177e262 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/impl/RemoveSpecializations.java +++ b/dev/core/src/com/google/gwt/dev/jjs/impl/RemoveSpecializations.java @@ -34,8 +34,16 @@ public RemoveSpecializations(JProgram program) { @Override public boolean visit(JMethod x, Context ctx) { + boolean modified = false; if (x.getSpecialization() != null) { x.removeSpecialization(); + modified = true; + } + if (x.isConstantFoldingAllowed()) { + x.setAllowConstantFolding(false); + modified = true; + } + if (modified) { numMods++; } diff --git a/user/super/com/google/gwt/emul/java/lang/String.java b/user/super/com/google/gwt/emul/java/lang/String.java index e824f004c08..ee72c3dbfed 100644 --- a/user/super/com/google/gwt/emul/java/lang/String.java +++ b/user/super/com/google/gwt/emul/java/lang/String.java @@ -42,6 +42,7 @@ import javaemul.internal.EmulatedCharset; import javaemul.internal.JsUtils; import javaemul.internal.NativeRegExp; +import javaemul.internal.annotations.ConstantFoldCandidate; import javaemul.internal.annotations.DoNotInline; import jsinterop.annotations.JsMethod; import jsinterop.annotations.JsNonNull; @@ -395,27 +396,33 @@ public int compareToIgnoreCase(String other) { return toLowerCase().compareTo(other.toLowerCase()); } + @ConstantFoldCandidate public String concat(String str) { return checkNotNull(this) + checkNotNull(str); } + @ConstantFoldCandidate public boolean contains(CharSequence s) { return asNativeString().includes(s.toString()); } + @ConstantFoldCandidate public boolean contentEquals(CharSequence cs) { return equals(cs.toString()); } + @ConstantFoldCandidate public boolean contentEquals(StringBuffer sb) { return equals(sb.toString()); } + @ConstantFoldCandidate public boolean endsWith(String suffix) { return asNativeString().endsWith(suffix); } // Marked with @DoNotInline because we don't have static eval for "==" yet. + @ConstantFoldCandidate @DoNotInline @Override public boolean equals(Object other) { @@ -424,6 +431,7 @@ public boolean equals(Object other) { return checkNotNull(this) == other; } + @ConstantFoldCandidate public boolean equalsIgnoreCase(String other) { checkNotNull(this); if (other == null) { @@ -461,6 +469,7 @@ private void getChars0(int srcBegin, int srcEnd, char[] dst, int dstBegin) { } @Override + @ConstantFoldCandidate public int hashCode() { int h = 0; for (int i = 0; i < length(); i++) { @@ -506,6 +515,7 @@ public int lastIndexOf(String str, int start) { return start < 0 ? -1 : asNativeString().lastIndexOf(str, start); } + @ConstantFoldCandidate @Override public int length() { return asNativeString().length; diff --git a/user/test/com/google/gwt/dev/jjs/OptimizedOnlyCompilerSuite.java b/user/test/com/google/gwt/dev/jjs/OptimizedOnlyCompilerSuite.java index d4e337ffd4a..3cfa70b23c0 100644 --- a/user/test/com/google/gwt/dev/jjs/OptimizedOnlyCompilerSuite.java +++ b/user/test/com/google/gwt/dev/jjs/OptimizedOnlyCompilerSuite.java @@ -18,6 +18,7 @@ import com.google.gwt.dev.jjs.optimized.ArrayListOptimizationTest; import com.google.gwt.dev.jjs.optimized.ArrayStoreOptimizationTest; import com.google.gwt.dev.jjs.optimized.CastOptimizationTest; +import com.google.gwt.dev.jjs.optimized.JsOverlayMethodOptimizationTest; import com.google.gwt.dev.jjs.optimized.SpecializationTest; import com.google.gwt.dev.jjs.optimized.StringOptimizationTest; import com.google.gwt.dev.jjs.optimized.UncheckedCastOptimizationTest; @@ -40,8 +41,7 @@ public static Test suite() { suite.addTestSuite(ArrayStoreOptimizationTest.class); suite.addTestSuite(StringOptimizationTest.class); suite.addTestSuite(CastOptimizationTest.class); - // Disabled until #10147 is resolved. - // suite.addTestSuite(JsOverlayMethodOptimizationTest.class); + suite.addTestSuite(JsOverlayMethodOptimizationTest.class); suite.addTestSuite(SpecializationTest.class); suite.addTestSuite(HasNoSideEffectsTest.class); suite.addTestSuite(UncheckedCastOptimizationTest.class); From 071af9adb306b490186719b64a4217d93c3d7927 Mon Sep 17 00:00:00 2001 From: Colin Alworth Date: Sat, 24 Jan 2026 18:27:21 -0600 Subject: [PATCH 5/7] add more annotations --- .../com/google/gwt/emul/java/lang/Long.java | 7 +++++++ .../com/google/gwt/emul/java/lang/Math.java | 8 ++++++++ .../com/google/gwt/emul/java/lang/String.java | 17 +++++++++++++++++ 3 files changed, 32 insertions(+) diff --git a/user/super/com/google/gwt/emul/java/lang/Long.java b/user/super/com/google/gwt/emul/java/lang/Long.java index fde9946cf33..5e1799199ee 100644 --- a/user/super/com/google/gwt/emul/java/lang/Long.java +++ b/user/super/com/google/gwt/emul/java/lang/Long.java @@ -70,6 +70,7 @@ public static int hashCode(long l) { return LongUtils.getHighBits(l) ^ (int) l; } + @ConstantFoldCandidate public static long highestOneBit(long l) { int high = LongUtils.getHighBits(l); if (high != 0) { @@ -127,18 +128,21 @@ public static long reverse(long l) { return LongUtils.fromBits(Integer.reverse(high), Integer.reverse(low)); } + @ConstantFoldCandidate public static long reverseBytes(long l) { int high = LongUtils.getHighBits(l); int low = (int) l; return LongUtils.fromBits(Integer.reverseBytes(high), Integer.reverseBytes(low)); } + @ConstantFoldCandidate public static long rotateLeft(long i, int distance) { long lowerBits = i >>> (SIZE - distance); long upperBits = i << distance; return upperBits | lowerBits; } + @ConstantFoldCandidate public static long rotateRight(long i, int distance) { long upperBits = i << (SIZE - distance); long lowerBits = i >>> distance; @@ -159,14 +163,17 @@ public static long sum(long a, long b) { return a + b; } + @ConstantFoldCandidate public static String toBinaryString(long value) { return toPowerOfTwoUnsignedString(value, 1); } + @ConstantFoldCandidate public static String toHexString(long value) { return toPowerOfTwoUnsignedString(value, 4); } + @ConstantFoldCandidate public static String toOctalString(long value) { return toPowerOfTwoUnsignedString(value, 3); } diff --git a/user/super/com/google/gwt/emul/java/lang/Math.java b/user/super/com/google/gwt/emul/java/lang/Math.java index 46b49952e28..0eb92cb71a4 100644 --- a/user/super/com/google/gwt/emul/java/lang/Math.java +++ b/user/super/com/google/gwt/emul/java/lang/Math.java @@ -350,6 +350,7 @@ public static float scalb(float f, int scaleFactor) { return (float) scalb((double) f, scaleFactor); } + @ConstantFoldCandidate @JsMethod(namespace = JsPackage.GLOBAL, name = "Math.sign") public static native double signum(double d); @@ -357,18 +358,23 @@ public static float signum(float f) { return (float) signum((double) f); } + @ConstantFoldCandidate @JsMethod(namespace = JsPackage.GLOBAL, name = "Math.sin") public static native double sin(double x); + @ConstantFoldCandidate @JsMethod(namespace = JsPackage.GLOBAL, name = "Math.sinh") public static native double sinh(double x); + @ConstantFoldCandidate @JsMethod(namespace = JsPackage.GLOBAL, name = "Math.sqrt") public static native double sqrt(double x); + @ConstantFoldCandidate @JsMethod(namespace = JsPackage.GLOBAL, name = "Math.tan") public static native double tan(double x); + @ConstantFoldCandidate @JsMethod(namespace = JsPackage.GLOBAL, name = "Math.tanh") public static native double tanh(double x); @@ -376,6 +382,7 @@ public static double toDegrees(double x) { return x * PI_UNDER_180; } + @ConstantFoldCandidate public static int toIntExact(long x) { int ix = (int) x; checkCriticalArithmetic(ix == x); @@ -386,6 +393,7 @@ public static double toRadians(double x) { return x * PI_OVER_180; } + @ConstantFoldCandidate public static double nextAfter(double start, double direction) { // Simple case described by Javadoc: if (start == direction) { diff --git a/user/super/com/google/gwt/emul/java/lang/String.java b/user/super/com/google/gwt/emul/java/lang/String.java index ee72c3dbfed..6af19ac1b04 100644 --- a/user/super/com/google/gwt/emul/java/lang/String.java +++ b/user/super/com/google/gwt/emul/java/lang/String.java @@ -365,19 +365,23 @@ private NativeString asNativeString() { } @Override + @ConstantFoldCandidate public char charAt(int index) { checkStringElementIndex(index, length()); return asNativeString().charCodeAt(index); } + @ConstantFoldCandidate public int codePointAt(int index) { return Character.codePointAt(this, index, length()); } + @ConstantFoldCandidate public int codePointBefore(int index) { return Character.codePointBefore(this, index, 0); } + @ConstantFoldCandidate public int codePointCount(int beginIndex, int endIndex) { return Character.codePointCount(this, beginIndex, endIndex); } @@ -392,6 +396,7 @@ public int compareTo(String other) { return a == b ? 0 : (a < b ? -1 : 1); } + @ConstantFoldCandidate public int compareToIgnoreCase(String other) { return toLowerCase().compareTo(other.toLowerCase()); } @@ -479,38 +484,47 @@ public int hashCode() { return h; } + @ConstantFoldCandidate public int indexOf(int codePoint) { return indexOf(fromCodePoint(codePoint)); } + @ConstantFoldCandidate public int indexOf(int codePoint, int startIndex) { return indexOf(fromCodePoint(codePoint), startIndex); } + @ConstantFoldCandidate public int indexOf(String str) { return asNativeString().indexOf(str); } + @ConstantFoldCandidate public int indexOf(String str, int startIndex) { return asNativeString().indexOf(str, startIndex); } + @ConstantFoldCandidate public String intern() { return checkNotNull(this); } + @ConstantFoldCandidate public int lastIndexOf(int codePoint) { return lastIndexOf(fromCodePoint(codePoint)); } + @ConstantFoldCandidate public int lastIndexOf(int codePoint, int startIndex) { return lastIndexOf(fromCodePoint(codePoint), startIndex); } + @ConstantFoldCandidate public int lastIndexOf(String str) { return asNativeString().lastIndexOf(str); } + @ConstantFoldCandidate public int lastIndexOf(String str, int start) { return start < 0 ? -1 : asNativeString().lastIndexOf(str, start); } @@ -721,11 +735,13 @@ public CharSequence subSequence(int beginIndex, int endIndex) { return substring(beginIndex, endIndex); } + @ConstantFoldCandidate public String substring(int beginIndex) { checkStringElementIndex(beginIndex, length() + 1); return asNativeString().substr(beginIndex); } + @ConstantFoldCandidate public String substring(int beginIndex, int endIndex) { checkStringBounds(beginIndex, endIndex, length()); return asNativeString().substr(beginIndex, endIndex - beginIndex); @@ -871,6 +887,7 @@ public String indent(int spaces) { return indentedLines.collect(Collectors.joining("\n", "", "\n")); } + @ConstantFoldCandidate public String stripIndent() { if (isEmpty()) { return ""; From 9d921c6ba7837a43e04c521314affc83d06d6eec Mon Sep 17 00:00:00 2001 From: Colin Alworth Date: Sat, 24 Jan 2026 21:54:31 -0600 Subject: [PATCH 6/7] Move specialization cleanup earlier and add inlining --- .../src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java index efd7bf15a33..5b0ba60629b 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java +++ b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java @@ -538,6 +538,8 @@ private void optimizeJava() throws InterruptedException { if (shouldOptimize()) { optimizeJavaToFixedPoint(); RemoveEmptySuperCalls.exec(jprogram); + RemoveSpecializations.exec(jprogram); + MethodInliner.exec(jprogram); } } @@ -551,7 +553,6 @@ private void optimizeJs(Set inlinableJsFunctions) throws InterruptedExce private void postNormalizationOptimizeJava() { try (SimpleEvent ignored = new SimpleEvent("Post-Normalization Java Optimizers")) { if (shouldOptimize()) { - RemoveSpecializations.exec(jprogram); Pruner.exec(jprogram, false, OptimizerContext.NULL_OPTIMIZATION_CONTEXT); // Last Java optimization step, update type oracle accordingly. jprogram.typeOracle.recomputeAfterOptimizations(jprogram.getDeclaredTypes()); From 38b97d58a8a6dcdae15857414e569b5111992546 Mon Sep 17 00:00:00 2001 From: Colin Alworth Date: Sun, 25 Jan 2026 14:02:46 -0600 Subject: [PATCH 7/7] Handle static methods better, add more annotations --- .../gwt/dev/jjs/impl/DeadCodeElimination.java | 22 ++++++++--- .../google/gwt/emul/java/lang/Integer.java | 2 + .../com/google/gwt/emul/java/lang/Math.java | 37 +++++++++++++++++++ 3 files changed, 55 insertions(+), 6 deletions(-) diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/DeadCodeElimination.java b/dev/core/src/com/google/gwt/dev/jjs/impl/DeadCodeElimination.java index 4c5b0456dd2..6291e85ce3e 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/impl/DeadCodeElimination.java +++ b/dev/core/src/com/google/gwt/dev/jjs/impl/DeadCodeElimination.java @@ -1850,14 +1850,27 @@ private void tryFoldMethodCall(JMethodCall x, Context ctx, JMethod method) { ? x.getArgs().subList(1, x.getArgs().size()) : x.getArgs(); - Class methodEnclosingType = classObjectForType(method.getEnclosingType()); + Class methodEnclosingType; + try { + methodEnclosingType = Class.forName(method.getEnclosingType().getName()); + } catch (ClassNotFoundException e) { + // Fall back to built-in types only + methodEnclosingType = classObjectForType(method.getEnclosingType()); + } + if (methodEnclosingType == null) { + // Can't find a type to invoke the method on, give up + return; + } final Object instanceLiteral; if (instance != null) { + instanceLiteral = tryTranslateLiteral(instance, methodEnclosingType); + method = isStaticImplMethod ? program.instanceMethodForStaticImpl(method) : method; + // Handle toString specially to make sure it is a noop on non-null string objects. - if (method.getName().endsWith("toString") && instance.getType() == program.getTypeJavaLangString()) { + if (method.getName().equals("toString") && method.getOriginalParamTypes().isEmpty()) { // replaces s.toString() with s - if (!instance.getType().canBeNull()) { + if (!instance.getType().canBeNull() && instance.getType() == program.getTypeJavaLangString().strengthenToNonNull()) { // Only replace when it is known to be non-null, otherwise it should follow the normal path // and throw an NPE if null at runtime. ctx.replaceMe(instance); @@ -1865,9 +1878,6 @@ private void tryFoldMethodCall(JMethodCall x, Context ctx, JMethod method) { return; } - instanceLiteral = tryTranslateLiteral(instance, methodEnclosingType); - method = isStaticImplMethod ? program.instanceMethodForStaticImpl(method) : method; - if (instanceLiteral == null && !method.isStatic()) { // Instance method on an instance that was not a literal. return; diff --git a/user/super/com/google/gwt/emul/java/lang/Integer.java b/user/super/com/google/gwt/emul/java/lang/Integer.java index d349bdab092..d582f206df1 100644 --- a/user/super/com/google/gwt/emul/java/lang/Integer.java +++ b/user/super/com/google/gwt/emul/java/lang/Integer.java @@ -16,6 +16,7 @@ package java.lang; import javaemul.internal.JsUtils; +import javaemul.internal.annotations.ConstantFoldCandidate; import javaemul.internal.annotations.HasNoSideEffects; /** @@ -81,6 +82,7 @@ public static int compare(int x, int y) { } } + @ConstantFoldCandidate public static Integer decode(String s) throws NumberFormatException { return Integer.valueOf(__decodeAndValidateInt(s, MIN_VALUE, MAX_VALUE)); } diff --git a/user/super/com/google/gwt/emul/java/lang/Math.java b/user/super/com/google/gwt/emul/java/lang/Math.java index 0eb92cb71a4..6016ea7d9b8 100644 --- a/user/super/com/google/gwt/emul/java/lang/Math.java +++ b/user/super/com/google/gwt/emul/java/lang/Math.java @@ -34,12 +34,15 @@ public final class Math { private static final double PI_OVER_180 = PI / 180.0; private static final double PI_UNDER_180 = 180.0 / PI; + @ConstantFoldCandidate @JsMethod(namespace = JsPackage.GLOBAL, name = "Math.abs") public static native double abs(double x); + @ConstantFoldCandidate @JsMethod(namespace = JsPackage.GLOBAL, name = "Math.abs") public static native float abs(float x); + @ConstantFoldCandidate @JsMethod(namespace = JsPackage.GLOBAL, name = "Math.abs") public static native int abs(int x); @@ -47,28 +50,34 @@ public static long abs(long x) { return x < 0 ? -x : x; } + @ConstantFoldCandidate public static int absExact(int v) { checkCriticalArithmetic(v != Integer.MIN_VALUE); return abs(v); } + @ConstantFoldCandidate public static long absExact(long v) { checkCriticalArithmetic(v != Long.MIN_VALUE); return abs(v); } + @ConstantFoldCandidate @JsMethod(namespace = JsPackage.GLOBAL, name = "Math.acos") public static native double acos(double x); + @ConstantFoldCandidate @JsMethod(namespace = JsPackage.GLOBAL, name = "Math.asin") public static native double asin(double x); + @ConstantFoldCandidate public static int addExact(int x, int y) { double r = (double) x + (double) y; checkCriticalArithmetic(isSafeIntegerRange(r)); return (int) r; } + @ConstantFoldCandidate public static long addExact(long x, long y) { long r = x + y; // "Hacker's Delight" 2-12 Overflow if both arguments have the opposite sign of the result @@ -76,15 +85,19 @@ public static long addExact(long x, long y) { return r; } + @ConstantFoldCandidate @JsMethod(namespace = JsPackage.GLOBAL, name = "Math.atan") public static native double atan(double x); + @ConstantFoldCandidate @JsMethod(namespace = JsPackage.GLOBAL, name = "Math.atan2") public static native double atan2(double y, double x); + @ConstantFoldCandidate @JsMethod(namespace = JsPackage.GLOBAL, name = "Math.cbrt") public static native double cbrt(double x); + @ConstantFoldCandidate @JsMethod(namespace = JsPackage.GLOBAL, name = "Math.ceil") public static native double ceil(double x); @@ -107,46 +120,56 @@ public static float copySign(float magnitude, float sign) { @JsMethod(namespace = JsPackage.GLOBAL, name = "Math.cosh") public static native double cosh(double x); + @ConstantFoldCandidate public static int decrementExact(int x) { checkCriticalArithmetic(x != Integer.MIN_VALUE); return x - 1; } + @ConstantFoldCandidate public static long decrementExact(long x) { checkCriticalArithmetic(x != Long.MIN_VALUE); return x - 1; } + @ConstantFoldCandidate @JsMethod(namespace = JsPackage.GLOBAL, name = "Math.exp") public static native double exp(double x); + @ConstantFoldCandidate @JsMethod(namespace = JsPackage.GLOBAL, name = "Math.expm1") public static native double expm1(double d); + @ConstantFoldCandidate @JsMethod(namespace = JsPackage.GLOBAL, name = "Math.floor") public static native double floor(double x); + @ConstantFoldCandidate public static int floorDiv(int dividend, int divisor) { checkCriticalArithmetic(divisor != 0); // round down division if the signs are different and modulo not zero return ((dividend ^ divisor) >= 0 ? dividend / divisor : ((dividend + 1) / divisor) - 1); } + @ConstantFoldCandidate public static long floorDiv(long dividend, long divisor) { checkCriticalArithmetic(divisor != 0); // round down division if the signs are different and modulo not zero return ((dividend ^ divisor) >= 0 ? dividend / divisor : ((dividend + 1) / divisor) - 1); } + @ConstantFoldCandidate public static long floorDiv(long dividend, int divisor) { return floorDiv(dividend, (long) divisor); } + @ConstantFoldCandidate public static int floorMod(int dividend, int divisor) { checkCriticalArithmetic(divisor != 0); return ((dividend % divisor) + divisor) % divisor; } + @ConstantFoldCandidate public static long floorMod(long dividend, long divisor) { checkCriticalArithmetic(divisor != 0); return ((dividend % divisor) + divisor) % divisor; @@ -199,34 +222,43 @@ public static float ulp(float v) { return (float) Math.pow(2, exponent - 23); } + @ConstantFoldCandidate @JsMethod(namespace = JsPackage.GLOBAL, name = "Math.hypot") public static native double hypot(double x, double y); + @ConstantFoldCandidate public static int incrementExact(int x) { checkCriticalArithmetic(x != Integer.MAX_VALUE); return x + 1; } + @ConstantFoldCandidate public static long incrementExact(long x) { checkCriticalArithmetic(x != Long.MAX_VALUE); return x + 1; } + @ConstantFoldCandidate @JsMethod(namespace = JsPackage.GLOBAL, name = "Math.log") public static native double log(double x); + @ConstantFoldCandidate @JsMethod(namespace = JsPackage.GLOBAL, name = "Math.log10") public static native double log10(double x); + @ConstantFoldCandidate @JsMethod(namespace = JsPackage.GLOBAL, name = "Math.log1p") public static native double log1p(double x); + @ConstantFoldCandidate @JsMethod(namespace = JsPackage.GLOBAL, name = "Math.max") public static native double max(double x, double y); + @ConstantFoldCandidate @JsMethod(namespace = JsPackage.GLOBAL, name = "Math.max") public static native float max(float x, float y); + @ConstantFoldCandidate @JsMethod(namespace = JsPackage.GLOBAL, name = "Math.max") public static native int max(int x, int y); @@ -234,12 +266,15 @@ public static long max(long x, long y) { return x > y ? x : y; } + @ConstantFoldCandidate @JsMethod(namespace = JsPackage.GLOBAL, name = "Math.min") public static native double min(double x, double y); + @ConstantFoldCandidate @JsMethod(namespace = JsPackage.GLOBAL, name = "Math.min") public static native float min(float x, float y); + @ConstantFoldCandidate @JsMethod(namespace = JsPackage.GLOBAL, name = "Math.min") public static native int min(int x, int y); @@ -291,9 +326,11 @@ public static long negateExact(long x) { return -x; } + @ConstantFoldCandidate @JsMethod(namespace = JsPackage.GLOBAL, name = "Math.pow") public static native double pow(double x, double exp); + @ConstantFoldCandidate @JsMethod(namespace = JsPackage.GLOBAL, name = "Math.random") public static native double random();