From c53054b9a839f8bc87789f80bb00a665a1dc9fee Mon Sep 17 00:00:00 2001 From: Nate Biggs Date: Thu, 12 Feb 2026 09:50:34 -0800 Subject: [PATCH] [dart2wasm] Fix function instantiation in dynamic modules. Closures are all invoked dynamically when dynamic modules are enabled so we can skip most of the closure representation logic. However, we still need to handle instantiation of type parameters. Previously we had only one representation for all generic functions. However, the instantiation logic is dependent on the number of type parameters. So we update this to have one representation per type parameter count. Fixes: https://github.com/dart-lang/sdk/issues/62592 Change-Id: I5ec1468e1f366ffe4db9b339f777e7de98efe12c Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/479800 Reviewed-by: Martin Kustermann Commit-Queue: Nate Biggs --- pkg/dart2wasm/lib/closures.dart | 14 +++++-------- .../dynamic_interface.yaml | 7 +++++++ .../test/data/closure_instantiation/main.dart | 21 +++++++++++++++++++ .../closure_instantiation/modules/entry1.dart | 15 +++++++++++++ .../closure_instantiation/shared/shared.dart | 5 +++++ 5 files changed, 53 insertions(+), 9 deletions(-) create mode 100644 pkg/dynamic_modules/test/data/closure_instantiation/dynamic_interface.yaml create mode 100644 pkg/dynamic_modules/test/data/closure_instantiation/main.dart create mode 100644 pkg/dynamic_modules/test/data/closure_instantiation/modules/entry1.dart create mode 100644 pkg/dynamic_modules/test/data/closure_instantiation/shared/shared.dart diff --git a/pkg/dart2wasm/lib/closures.dart b/pkg/dart2wasm/lib/closures.dart index 1c60925f201..baa63995e69 100644 --- a/pkg/dart2wasm/lib/closures.dart +++ b/pkg/dart2wasm/lib/closures.dart @@ -203,10 +203,10 @@ class ClosureLayouter extends RecursiveVisitor { representations; // Dynamic submodules invoke closures dynamically so they use the base structs - // in all cases. Therefore, We only need one global copy of the - // ClosureRepresentation for generic and one for non-generic functions. - ClosureRepresentation? _dynamicSubmoduleRepresentation; - ClosureRepresentation? _dynamicSubmoduleGenericRepresentation; + // in all cases. Therefore, we only need one global copy of the + // ClosureRepresentation for each type parameter count. + final Map + _dynamicSubmoduleGenericRepresentations = {}; Set visitedConstants = Set.identity(); @@ -484,11 +484,7 @@ class ClosureLayouter extends RecursiveVisitor { ClosureRepresentation? getClosureRepresentation( int typeCount, int positionalCount, List names) { if (translator.dynamicModuleSupportEnabled) { - if (typeCount == 0) { - return _dynamicSubmoduleRepresentation ??= - _createRepresentation(typeCount, 0, const [], null, null, const []); - } - return _dynamicSubmoduleGenericRepresentation ??= + return _dynamicSubmoduleGenericRepresentations[typeCount] ??= _createRepresentation(typeCount, 0, const [], null, null, const []); } final representations = diff --git a/pkg/dynamic_modules/test/data/closure_instantiation/dynamic_interface.yaml b/pkg/dynamic_modules/test/data/closure_instantiation/dynamic_interface.yaml new file mode 100644 index 00000000000..988bfc2a53d --- /dev/null +++ b/pkg/dynamic_modules/test/data/closure_instantiation/dynamic_interface.yaml @@ -0,0 +1,7 @@ +# Copyright (c) 2026, 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. +callable: + - library: 'dart:core' + - library: 'shared/shared.dart' + - library: 'package:expect/expect.dart' diff --git a/pkg/dynamic_modules/test/data/closure_instantiation/main.dart b/pkg/dynamic_modules/test/data/closure_instantiation/main.dart new file mode 100644 index 00000000000..a8dd748b8cb --- /dev/null +++ b/pkg/dynamic_modules/test/data/closure_instantiation/main.dart @@ -0,0 +1,21 @@ +// Copyright (c) 2026, 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 '../../common/testing.dart' as helper; +import 'package:expect/expect.dart'; + +import 'shared/shared.dart' as shared; + +void main() async { + final wrap = shared.wrap; + final wrapped = wrap(3, 'hello'); + Expect.equals(3, wrapped.$1); + Expect.equals('hello', wrapped.$2); + final dynModWrap = + await helper.load('entry1.dart') as (bool, int) Function(bool k, int v); + final dynModWrapped = dynModWrap(true, 5); + Expect.equals(true, dynModWrapped.$1); + Expect.equals(5, dynModWrapped.$2); + helper.done(); +} diff --git a/pkg/dynamic_modules/test/data/closure_instantiation/modules/entry1.dart b/pkg/dynamic_modules/test/data/closure_instantiation/modules/entry1.dart new file mode 100644 index 00000000000..1c5dab89ff2 --- /dev/null +++ b/pkg/dynamic_modules/test/data/closure_instantiation/modules/entry1.dart @@ -0,0 +1,15 @@ +// Copyright (c) 2026, 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 '../shared/shared.dart' as shared; +import 'package:expect/expect.dart'; + +@pragma('dyn-module:entry-point') +Object dynamicModuleEntrypoint() { + final wrap = shared.wrap, String>; + final wrapped = wrap([20], 'foo'); + Expect.equals(20, wrapped.$1[0]); + Expect.equals('foo', wrapped.$2); + return shared.wrap; +} diff --git a/pkg/dynamic_modules/test/data/closure_instantiation/shared/shared.dart b/pkg/dynamic_modules/test/data/closure_instantiation/shared/shared.dart new file mode 100644 index 00000000000..5b2edcf2828 --- /dev/null +++ b/pkg/dynamic_modules/test/data/closure_instantiation/shared/shared.dart @@ -0,0 +1,5 @@ +// Copyright (c) 2026, 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. + +(K, V) wrap(K k, V v) => (k, v);