Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Explore eagerly initializing selected classes #9922

Open
niloc132 opened this issue Feb 11, 2024 · 2 comments
Open

Explore eagerly initializing selected classes #9922

niloc132 opened this issue Feb 11, 2024 · 2 comments

Comments

@niloc132
Copy link
Contributor

GWT version: GWT 2.11 and below
Browser (with version): Any
Operating System: Any


Background

GWT takes care to follow Java semantics in ways that users may not always expect or appreciate, especially around class initializers. In the JVM, a class must be initialized before it can be instantiated or its static members can be used, so in GWT, a $clinit function is created per class, roughly structured as

function $clinit_SomeClass() {
  $clinit_77 = function() {};
  $clinit_SuperClass();
  // initialize static fields, run static initializer bodies...
}

This ensures that if invoked more than once, the initializer won't replace already-initialized fields, etc. This will appear at the top of constructors and static methods, and will appear before references to static fields:

// RuntimeException constructor
jl.RuntimeException_1 = function RuntimeException_1(message){
  jl.$clinit_RuntimeException();
  jl.Exception_1.call(this, message);
  this.$init_2();
};
// static int Boolean.compare(x, y):
function compare_1(x_0, y_0){
  jl.$clinit_Boolean();
  return x_0 == y_0?0:x_0?1:-1;
}
// byte[] String.getBytes
jl.$getBytes = function $getBytes(this$static){
  return jl.getBytes_Ljava_nio_charset_Charset___B__devirtual$(this$static, (ji2.$clinit_EmulatedCharset() , ji2.UTF_8));
}

This often seems to generate unnecessary output, such as duplicate or pointless clinit calls. Some of these are due to non-obvious side effects from initializing classes, others are unoptimized cases where clinits were appropriately emitted for each individual reference/invocation, but inlining and dead code elimination has left duplicates or leftovers.

Potentially related issues to review when working on this:
#9731
#9004
#6534

Proposal
  • Support a configuration property that takes a list of fully-qualified classes to initialize at startup. This would work somewhat like compiler.splitpoint.initial.sequence, where order matters, and users can add their own items to the list.
  • Items in this list would have their clinit invoked in order before any entrypoint is constructed. Each must not directly or indirectly cause any other class to be initialized, except those which have already been initialized.
  • Several built-in classes should be set to use this by default:
    • StackTraceCreator (resolves an early-startup cycle with Impl)
    • Double/String/Boolean. Potentially other boxed primitives as well, but those three are "always" used due to their relationship with their corresponding JS types. Impl, BigLongLib, other built-in types. Focusing on these should improve output and performance for most applications.
  • Consider automatically applying this to some enums (which define no non-constant static fields or static initializers), to both improve generated code size, and avoid changing program semantics. Some care must be taken to ensure that the enum wouldn't be optimized out to begin with.
  • Allow this configuration property to be further extended by application code, ensuring it can be disabled outright, have new classes added to it, and order specified.
@jnehlmeier
Copy link
Member

Hm I don't like introducing a compiler option right away. If some of the shortcomings in the compiler can be fixed, the property might not be worth it.

  • Making sure that empty clinits are removed
  • Maybe gather statistics about generated non-empty clinit function calls per class and give top contenders shorter obfuscated function names (might already be the case)

Given that GWT orders functions in a way that gzip works even better I am not sure if such an optimization would save a noticeable amount of download size.

@niloc132
Copy link
Contributor Author

Yep, I'm not saying this is definitely the right approach, but worth exploring. Compiled size is only one facet to this - Impl and StackTraceCreator also have a cyclical initialization dependency, and there are a few other classes (DomEvent is the only one I can find right now, but I thought there was another reference).

Additionally, clinits leave side effects in code that otherwise would have none, since they have their own "was I initialized or not" state - if the compiler can't prove that an otherwise pure function is being called on an already initialized class (like Double), it has to leave the clinit in place.

My limited experience with the compiler and the current contributors suggests that this approach is likely to be more fruitful. I have a very old draft of looking at handling these issues in this way, and definitely seemed to go better than my previous attacks on JsInliner and friends. But if you've got time/budget to try directly addressing in other ways, I'm all for it!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants