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

dead code not eliminated when two Boolean.parseBoolean in same function #9731

Open
arenevier opened this issue Jun 10, 2021 · 1 comment
Open

Comments

@arenevier
Copy link

Hi,

In some circumstances, unreachable code is not removed properly.

I am adding a minimal reproducible testcase:

package com.google.gwt.sample.hello.client;
    
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.user.client.Window;

abstract class BaseDelegate {
  abstract public void display();
} 

class DelegateFoo extends BaseDelegate {
  @Override
  public void display() {
    Window.alert("Foo");
  }
} 
  
class DelegateNoFoo extends BaseDelegate {
  @Override
  public void display() {
    Window.alert("No Foo");
  }
}
  
public class Hello implements EntryPoint {
  static boolean isFeatureFooEnabled() {
    if (Boolean.parseBoolean(System.getProperty("myapp.property1"))) {
      return true;
    } 
    if (Boolean.parseBoolean(System.getProperty("myapp.property2"))) {
      return true;
    }
    return false;
  }

  public void onModuleLoad() {
    final BaseDelegate delegate = isFeatureFooEnabled() ? new DelegateFoo() : new DelegateNoFoo();
    delegate.display();
  }
} 

If myapp.property1 and myapp.property2 are both set to false, the above code will compile to

  delegate = ($clinit_Boolean() , $clinit_Boolean() , false)?new DelegateFoo:new DelegateNoFoo;
  delegate.display();

So it means that even if DelegateNoFoo is never used, its code will not be removed. It's a constraint example, but in our application, those delegates are actually pretty big and depend on many other classes. It means that we end up building and shipping a lot of unused code.

If there is only one Boolean.parseBoolean, the code compiles (correctly) to
delegate = ($clinit_Boolean() , new DelegateNoFoo);

Also, if we modify isFeatureFooEnabled to

  static boolean isFeatureFooEnabled() {
    if (Boolean.parseBoolean(System.getProperty("myapp.property1"))) {
      return true;
    }
    return Boolean.parseBoolean(System.getProperty("myapp.property2"));
  }

It will compile to delegate = ($clinit_Boolean() , $clinit_Boolean() , new DelegateNoFoo);

But this is fragile and won't scale if isFeatureFooEnabled() becomes more complex

@jhickman
Copy link
Contributor

The issue you’re having is due to the complexity of your isFeatureFooEnabled method in how the GWT compiler is able to inline everything and remove unreachable code.

I was able to take your provided example and started experimenting with it. Using the initial implementation

  static boolean isFeatureFooEnabled() {
    if (Boolean.parseBoolean(System.getProperty("myapp.property1"))) {
      return true;
    } 
    if (Boolean.parseBoolean(System.getProperty("myapp.property2"))) {
      return true;
    }
    return false;
  }

If I set both properties to true, then it’s able to inline it perfectly and results in a simple:

$wnd.alert(‘Foo’);

And it also removes the class declarations, since those are really useless by this point.

If I set property1 to true and property2 to false, then it results in this:

$wnd.alert(‘Foo’);

Same thing! But when setting property1 to false and property2 to true; then we see the results similar to what you provided:

delegate = ($clinit_Boolean() , $clinit_Boolean() , true)?new DelegateFoo:new DelegateNoFoo;
delegate.display();

I think the reason it’s doing this is because the isFeatureFooEnabled has a ‘short circuit’ behavior due to the separate if statements. If property1 is true, then it will short circuit that entire method and will ALWAYS return true; thus being able to collapse it all and inline the alert. However, if property1 is false and property2 is true.. yes, it’ll still essentially return true, but that first ‘if’ statement is there and is executed before it returns that true. And, since property1 is false, it’s providing a placeholder to the $clinit_Boolean() function, which is essentially the empty function.

It’s possible that GWT could do extra pass at cleaning this up, but there are ways of implementing code to ensure code is removed properly due to being unreachable.

If I were to refactor the isFeatureFooEnabled method to reduce the complexity and help the compiler identify this, I get:

static boolean isFeatureFooEnabled() {
  return (Boolean.parseBoolean(System.getProperty("myapp.property1")))
    ||  (Boolean.parseBoolean(System.getProperty("myapp.property2")));
}

Setting either or both properties to true now will end up getting what we’d expect; inlining of the $wnd.alert(‘Foo’)

It also means that changing both properties to false will now inline $wnd.alert(‘No Foo’)

In both cases, the compiler was able to remove the unreachable class declarations properly.

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