Skip to content

Missing compile-time constant folding operations #10147

Description

@niloc132

DeadCodeElimination does a lot of work to rewrite code with no runtime values into constants, saving output size and runtime performance. Given two constants, most binary operations (except assignment) can be rewritten, and with only one constant there are still cases where they can be rewritten depending on the constant (e.g. 0 * <expr>, 1 * , 0 + , true && `, etc).

Additionally, DeadCodeVisitor.tryOptimizeStringCall's purpose is to replace static calls on java.lang.String as well as instance methods called on string literals. Other methods are rewritten, such as .ordinal() calls on constant enums, and GWT.isScript(), always compiled to the boolean literal true (note that this probably instead should be handed by UnifyAst.maybeHandleMagicMethodCall).

There are other pure static and instance methods that could be optimized out here as well - most of java.lang.Math and StrictMath (except random()).

Math.pow(2, 43)
$wnd.Math.pow(2, 43)

Integer.parseInt(String) and Long.parseLong(String) both delegate to Number.__parseAndValidateLong with a constant radix=10. If no other radix values are used in an application, eventually Character.digit is rewritten to this implementation:

function digit_0(c){
  if (c >= 48 && c < 48 + $wnd.Math.min(10, 10)) {
    return c - 48;
  }
  if (c >= 97 && c < 97) {
    return c - 97 + 10;
  }
  if (c >= 65 && c < 65) {
    return c - 65 + 10;
  }
  return -1;
}

It is also suspicious that the SUM operations are not combined - addition is associative over int, so regrouping (c - 65) + 10 into c - (65 - 10) should be done.

Boxed primitive types could perhaps be treated the same way for some marginal impact - Integer.hashCode will be inlined away, and Integer.max()/min() will be inlined to allow Math.max/min to be compiled out.

Activity

  1. niloc132 commented on Jan 24, 2026

    @niloc132
    MemberAuthor

    Proposed plan to resolve this issue:

    DCE is hardcoded to only apply to java.lang.String, so either we need to expand that hardcoded list, or decorate specific methods to indicate that they should be optimized out.

    There aren't many cases where user code should need to support this as a feature for having their own code optimized in this way, but I don't think there are zero cases - for example, a user might add some JRE emulation that makes sense to use on constants, and expect that it is possible to have it optimized out in those cases. This suggests that an annotation could make more sense than a hardcoded list, as long as it doesn't increase complexity of the feature by too much.

    Looking also then at #10253 and how this occurred because DCE doesn't get the chance to run before the implementation is inlined (and so the signature of the method in question removed), it makes sense for the MethodInliner to have access to this list as well. This begins to rhyme a lot with how @SpecializeMethod is implemented - forbid inlining for most of compilation, then remove the marker and run MethodInliner again (with DCE) until no more changes can be made, to simplify away specifics of the method further.

    A new annotation would be required - @SpecializeMethod has specific other metadata it conveys so it can't be reused or generalized, and @DoNotInline is intended to be absolute, enforce that it won't happen either in Java or JS optimization. In theory we could use DoNotInline for this purpose, but it would have unintended consequences, such as preventing even a single use of the method from being inlined, or allowing something like Math.multiplyExact(x, 0) from being inlined away as 0.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions