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.29k stars 1.59k forks source link

[dart2wasm] Take advantage of TFA-inferred devirtualization information for closure calls #55231

Open mkustermann opened 8 months ago

mkustermann commented 8 months ago

Dart2wasm already takes advantage of devirtualization information from TFA for instance invocations.

TFA recently gained the ability to devirtualize function invocations. We should take advantage of this in the dart2wasm (just as 8c1f86e5dd1e95c3f64c10057b94d9c679944c9a takes advantage of it in the VM)

/cc @osa1

osa1 commented 8 months ago

I started looking into this. The metadata code isn't documented so I asked @kustermann. Here's how the new metadata works.

In this Dart:

void forEach1<A>(List<A> list, void Function(A) fun) {
  for (final x in list) {
    fun(x);
  }
}

void forEach2<A>(List<A> list, void Function(A) fun) {
  for (final x in list) {
    fun(x);
  }
}

int f(int i) {
  if (int.parse('1') == 1) {
    return i;
  } else {
    throw '';
  }
}

void main() {
  forEach1(<int>[1, 2, 3], (i) => print(f(i)));
  forEach2(<int>[1, 2, 3], f);
}

forEach1 only calls the first closure in main as fun, and forEach2 only calls f.

Ideally we want to compile forEach1 and forEach2 with direct calls to these closures.

(The arguments can then be dropped separately)

The metadata allows this. The kernel metadata is the same as other direct invocation metadata and the key is FunctionInvocation.

Currently the metadata only returns null when the target is a closure: https://github.com/dart-lang/sdk/blob/1b5368f3098e2440af7cd9f670fefd5cf6579519/pkg/vm/lib/metadata/direct_call.dart#L37

We can remove the isClosure guard, but since closures are not members that's not useful.

Instead what the metadata stores for direct function invocations is the member + closure index. When the index is 0 that means the member itself is called. Otherwise it's the index of the function expression (starting from 1) in the member.

In forEach1 above, the metadata _memberReference is main, and _closureId is 1: the closure being called is the first closure in member main.

In forEach2, _memberReference is f and _closureId is 0: meaning f itself is called (instead of a function expression in f.

osa1 commented 1 day ago

This is being implemented in https://dart-review.googlesource.com/c/sdk/+/397260.