Files
sdk/tests/web/wasm/closures/function_apply_test.dart
Martin Kustermann 59c8081950 [dart2wasm] Use shared closure argument dispatchers for dynamic call entries
This removes 4591 functions from ACX gallery main module
which translates to -43 KB / -1.6% (-3.5% code section)

We have already today a callsite guarantee that the closure call
(type, positional, named) arguments are valid arguments to the
target closure (they also have the right type).

That means the `closure.vtable.dynamicCall` entry's only purpose
is to unpack the (type, positional, named) argument arrays and call
the target.

Instead of calling the target directly (as we did so far) we now
unpack argument arrays and call the right vtable entry. This logic
can be shared amongst all closures of the same representation and
therefore leads to big reduction in wasm functions.

=> We do that in this CL.

There's two exceptions to this:

* In dynamic module scenario we don't have closed-world knowledge
  of closure definitions & closure call site. There's no specific
  vtable entries for positional+name combinations we could forward
  to.

* In closed world scenario where there's a usage of `Function.apply`
  with named arguments: We don't generate vtable entries for all
  possible name combinations a closure can be called with.

So we change the closure layouter algorithm to find out if there's
a usage of `Function.apply` with named arguments.

A few tangential changes:

* Fix a bug revealed by this change: The static tearoff
  instantiation constant's dynamic call entry must pass the generic
  closure object when calling the generic closure.
  => The shared dynamic call entry dispatchers will now verify
     (in assertion) mode the assumptions, which revealed this issue

* The closure layouter algorithm will now consider `obj.foo(a: ...)`
  as a potential dynamic call site (due to call-via-field) and
  therefore record the name combinations used there
  => Tested via `web/wasm/closures/dynamic_call_via_field_test`

We test the optimization by checking in 3 tests that show what
ends up in the vtables:

* `pkg/dart2wasm/test/ir_tests/dyn_closure.dart`
  => uses dynamic calls
  => dynamic call entries are "closure arguments dispatcher"

* `pkg/dart2wasm/test/ir_tests/dyn_closure_function_apply.dart`
  => uses Function.apply without names
  => dynamic call entries are "closure arguments dispatcher"

* `pkg/dart2wasm/test/ir_tests/dyn_closure_function_apply_named.dart`
  => uses Function.apply with named arguments
  => dynamic call entries are closure specific

Issue https://github.com/dart-lang/sdk/issues/60458

Change-Id: I099984b542b05920b02596410a1bf6a08d2a0302
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/460080
Reviewed-by: Ömer Ağacan <omersa@google.com>
2025-11-07 11:17:57 -08:00

30 lines
998 B
Dart

// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import "package:expect/expect.dart";
main() {
// Prevent constant propagation of closure into `Function.apply`.
for (final (name, closure) in <(String, Function)>[
('static1', static1),
('static2', static2),
]) {
test('$name: 1 missing-b', closure, [1]);
test('$name: 1 2', closure, [1, 2]);
}
}
String static1(a, [b = 'missing-b']) => 'static1: $a $b';
String static2(a, [b = 'missing-b']) => 'static2: $a $b';
void test(String expected, Function function, List positional) {
Expect.equals(expected, Function.apply(function, positional));
final oneMoreThanAllowed = [
for (int i = positional.length; i < 3; ++i) 'extra$i',
];
Expect.throwsNoSuchMethodError(
() => Function.apply(function, [...positional, ...oneMoreThanAllowed]),
);
}