dart-lang / sdk

The Dart SDK, including the VM, JS and Wasm compilers, analysis, core libraries, and more.
https://dart.dev
BSD 3-Clause "New" or "Revised" License
10.09k stars 1.56k forks source link

dart2js: isCalledOnce signal unreliable #34203

Open rakudrama opened 6 years ago

rakudrama commented 6 years ago

The 'isCalledOnce' inlining signal is unreliable. The main problem is that call sites in Kernel do not correspond one-to-one to call sites in the expanded generative constructor factories.

Consider:

class A {
  var x;
  A() : x = foo();
}
class B extends A {}

There is one call site to foo. The factory constructors are essentially:

factory A() {
  var x_value = foo();
  var result = allocate_a(x: x_value);
  result.body_A();
  return result;
}
factory B() {
  var x_value = foo();
  var result = allocate_B(x: x_value);
  result.body_A();
  result.body_B();
  return result;
}

In this expansion there are two call sites for foo. The context for each call site can be different. Below, when inlining A(), the total call stack has factory A which is called twice, but when inlining B(), factory B is called once, but in a loop:

new A();
new A();
for (int i = 0; i < 1; i++) new B();

A decision to not inline foo() outside of a loop in a non-called-once stack (via A) conflicts with a decision to inline foo() inside a loop (via B). A work-around is to not store update decisions for called-once methods.

johnniwinther commented 5 years ago

The work-around is not enough to ensure consistency. If a method, that is itself called once, is attempted inlined in an inlining stack where not all frames are called once, we register the method as being non-inlinable. Had we met this one call-site first we would have inlined it - though with the first work-around, not cached this decision.

For instance

foo() {}
bar() => foo();
baz() {
  bar();
  bar();
}

If we codegen bar first we will inline foo whereas if we codegen baz first and inlined bar we might have registered foo as being non-inlinable.

The full(?) work-around is therefore to not mark foo as non-inlinable because it is itself called once and the inlining decision of foo therefore is potentially based on it being called once.