Skip to content

Commit 0e040b8

Browse files
natebiggsCommit Queue
authored andcommitted
[dart2wasm] Use dynamic invocations to call closures when dynamic modules are enabled.
Running with dynamic modules means that closure invocation shapes cannot be statically known. A closure can flow between any modules and can then be invoked with a shape that's unknown to the module that defines the closure. Given this, our 2 options are to generate code for every invocation shape or invoke the closure as if we don't know it's shape (dynamic invocation). The former would scale exponentially relative to the number of named parameters so is not practical. The latter generates slower, bigger code but is the more practical of the 2. Change-Id: I5e3613d23662e5b2213fdaa725642678fe26e43a Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/421920 Reviewed-by: Martin Kustermann <[email protected]> Reviewed-by: Ömer Ağacan <[email protected]>
1 parent d8e046c commit 0e040b8

File tree

10 files changed

+131
-17
lines changed

10 files changed

+131
-17
lines changed

pkg/dart2wasm/lib/closures.dart

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,14 @@ class ClosureLayouter extends RecursiveVisitor {
200200
final Translator translator;
201201
final Map<TreeNode, ProcedureAttributesMetadata> procedureAttributeMetadata;
202202

203-
List<List<ClosureRepresentationsForParameterCount>> representations = [];
203+
late final List<List<ClosureRepresentationsForParameterCount>>
204+
representations;
205+
206+
// Dynamic modules invoke closures dynamically so they use the base structs
207+
// in all cases. Therefore, We only need one global copy of the
208+
// ClosureRepresentation for generic and one for non-generic functions.
209+
ClosureRepresentation? _dynamicModuleRepresentation;
210+
ClosureRepresentation? _dynamicModuleGenericRepresentation;
204211

205212
Set<Constant> visitedConstants = Set.identity();
206213

@@ -356,6 +363,12 @@ class ClosureLayouter extends RecursiveVisitor {
356363
.mapping;
357364

358365
void collect() {
366+
// Dynamic module enabled builds use dynamic call entry points for all
367+
// closure invocations so we don't need to generate any representation
368+
// info.
369+
if (translator.dynamicModuleSupportEnabled) return;
370+
representations = [];
371+
359372
translator.component.accept(this);
360373
computeClusters();
361374
}
@@ -391,6 +404,14 @@ class ClosureLayouter extends RecursiveVisitor {
391404
/// `names` should be sorted.
392405
ClosureRepresentation? getClosureRepresentation(
393406
int typeCount, int positionalCount, List<String> names) {
407+
if (translator.dynamicModuleSupportEnabled) {
408+
if (typeCount == 0) {
409+
return _dynamicModuleRepresentation ??=
410+
_createRepresentation(typeCount, 0, const [], null, null, const []);
411+
}
412+
return _dynamicModuleGenericRepresentation ??=
413+
_createRepresentation(typeCount, 0, const [], null, null, const []);
414+
}
394415
final representations =
395416
_representationsForCounts(typeCount, positionalCount);
396417
if (representations.withoutNamed == null) {
@@ -423,8 +444,6 @@ class ClosureLayouter extends RecursiveVisitor {
423444
ClosureRepresentation? parent,
424445
Map<NameCombination, int>? indexOfCombination,
425446
Iterable<int> paramCounts) {
426-
// TODO(natebiggs): Add logic to allow for changing signatures in a dynamic
427-
// module.
428447
List<String> nameTags = ["$typeCount", "$positionalCount", ...names];
429448
String vtableName = ["#Vtable", ...nameTags].join("-");
430449
String closureName = ["#Closure", ...nameTags].join("-");
@@ -505,6 +524,9 @@ class ClosureLayouter extends RecursiveVisitor {
505524
// generation, after the imports have been added.
506525

507526
representation._instantiationTrampolinesGenerator = (module) {
527+
// Dynamic modules do not have any trampolines, only a dynamic call
528+
// entry point.
529+
if (translator.dynamicModuleSupportEnabled) return const [];
508530
List<w.BaseFunction> instantiationTrampolines = [
509531
...?parent?._instantiationTrampolinesForModule(module)
510532
];

pkg/dart2wasm/lib/code_generator.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2454,7 +2454,8 @@ abstract class AstCodeGenerator
24542454
intrinsifier.generateFunctionCallIntrinsic(node);
24552455
if (intrinsicResult != null) return intrinsicResult;
24562456

2457-
if (node.kind == FunctionAccessKind.Function) {
2457+
if (node.kind == FunctionAccessKind.Function ||
2458+
translator.dynamicModuleSupportEnabled) {
24582459
// Type of function is `Function`, without the argument types.
24592460
return visitDynamicInvocation(
24602461
DynamicInvocation(DynamicAccessKind.Dynamic, node.receiver, node.name,

pkg/dart2wasm/lib/dispatch_table.dart

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,9 @@ class DispatchTable {
424424
void serialize(DataSerializer sink) {
425425
sink.writeList(_selectorInfo.values, (s) => s.serialize(sink));
426426
sink.writeList(_table, (r) => sink.writeNullable(r, sink.writeReference));
427+
// Preserve call selectors for closure calls which are handled dynamically.
428+
final callSelectors = _dynamicGetters['call']!;
429+
sink.writeList(callSelectors, (s) => sink.writeInt(s.id));
427430
}
428431

429432
factory DispatchTable.deserialize(DataDeserializer source) {
@@ -433,11 +436,20 @@ class DispatchTable {
433436
source.readList(() => SelectorInfo.deserialize(source, dispatchTable));
434437
final table = source
435438
.readList(() => source.readNullable(() => source.readReference()));
439+
final callSelectorIds = source.readList(source.readInt);
436440

437441
for (final selector in selectors) {
438442
dispatchTable._selectorInfo[selector.id] = selector;
439443
}
440444
dispatchTable._table = table;
445+
446+
// Preserve call selectors for closure calls which are handled dynamically.
447+
final callSelectors = <SelectorInfo>{};
448+
for (final selectorId in callSelectorIds) {
449+
callSelectors.add(dispatchTable._selectorInfo[selectorId]!);
450+
}
451+
dispatchTable._dynamicGetters['call'] = callSelectors;
452+
441453
return dispatchTable;
442454
}
443455

pkg/dart2wasm/lib/dynamic_forwarders.dart

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,9 @@ class Forwarder {
7777
Forwarder._(Translator translator, this._kind, this.memberName,
7878
w.ModuleBuilder module)
7979
: function = module.functions.define(_kind.functionType(translator),
80-
"$_kind forwarder for '$memberName'");
80+
"$_kind forwarder for '$memberName'"),
81+
assert(!translator.isDynamicModule ||
82+
(memberName == 'call' && _kind == _ForwarderKind.Getter));
8183

8284
void _generateCode(Translator translator) {
8385
switch (_kind) {
@@ -478,10 +480,8 @@ class Forwarder {
478480
}
479481
}
480482

481-
final getterSelectors =
482-
translator.dispatchTable.dynamicGetterSelectors(memberName);
483483
final getterValueLocal = b.addLocal(translator.topInfo.nullableType);
484-
for (final selector in getterSelectors) {
484+
void handleGetterSelector(SelectorInfo selector) {
485485
for (final (:range, :target)
486486
in selector.targets(unchecked: false).targetRanges) {
487487
for (int classId = range.start; classId <= range.end; ++classId) {
@@ -569,6 +569,21 @@ class Forwarder {
569569
}
570570
}
571571

572+
final getterSelectors =
573+
translator.dispatchTable.dynamicGetterSelectors(memberName);
574+
for (final selector in getterSelectors) {
575+
handleGetterSelector(selector);
576+
}
577+
578+
final dynamicMainModuleGetterSelectors = translator
579+
.dynamicMainModuleDispatchTable
580+
?.dynamicGetterSelectors(memberName);
581+
if (dynamicMainModuleGetterSelectors != null) {
582+
for (final selector in dynamicMainModuleGetterSelectors) {
583+
handleGetterSelector(selector);
584+
}
585+
}
586+
572587
b.end(); // noSuchMethodBlock
573588

574589
// Unable to find a matching member, call `noSuchMethod`

pkg/dart2wasm/lib/dynamic_modules.dart

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -491,14 +491,16 @@ class DynamicModuleInfo {
491491

492492
for (final lib in translator.component.libraries) {
493493
for (final member in lib.members) {
494-
if (!member.isDynamicModuleCallable(translator.coreTypes)) continue;
495-
collectCallableReferences(member);
494+
if (member.isDynamicModuleCallable(translator.coreTypes)) {
495+
collectCallableReferences(member);
496+
}
496497
}
497498

498499
for (final cls in lib.classes) {
499500
for (final member in cls.members) {
500-
if (!member.isDynamicModuleCallable(translator.coreTypes)) continue;
501-
collectCallableReferences(member);
501+
if (member.isDynamicModuleCallable(translator.coreTypes)) {
502+
collectCallableReferences(member);
503+
}
502504
}
503505
}
504506
}

pkg/dart2wasm/lib/translator.dart

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -836,6 +836,15 @@ class Translator with KernelNodes {
836836
return topInfo.typeWithNullability(nullable);
837837
}
838838
if (type is FunctionType) {
839+
if (dynamicModuleSupportEnabled) {
840+
// The closure representation is based on the available closure
841+
// definitions and invocations seen in the program. For dynamic modules,
842+
// this can differ from one module to another. So use the less specific
843+
// closure base class everywhere. Usages will get downcast to the
844+
// appropriate closure type.
845+
return w.RefType.def(closureLayouter.closureBaseStruct,
846+
nullable: nullable);
847+
}
839848
ClosureRepresentation? representation =
840849
closureLayouter.getClosureRepresentation(
841850
type.typeParameters.length,
@@ -1095,11 +1104,13 @@ class Translator with KernelNodes {
10951104
representation.instantiationTypeHashFunctionForModule(ib.module));
10961105
ib.ref_func(representation.instantiationFunctionForModule(ib.module));
10971106
}
1098-
for (int posArgCount = 0; posArgCount <= positionalCount; posArgCount++) {
1099-
fillVtableEntry(ib, posArgCount, const []);
1100-
}
1101-
for (NameCombination nameCombination in representation.nameCombinations) {
1102-
fillVtableEntry(ib, positionalCount, nameCombination.names);
1107+
if (!dynamicModuleSupportEnabled) {
1108+
for (int posArgCount = 0; posArgCount <= positionalCount; posArgCount++) {
1109+
fillVtableEntry(ib, posArgCount, const []);
1110+
}
1111+
for (NameCombination nameCombination in representation.nameCombinations) {
1112+
fillVtableEntry(ib, positionalCount, nameCombination.names);
1113+
}
11031114
}
11041115
ib.struct_new(representation.vtableStruct);
11051116
ib.end();
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
# for details. All rights reserved. Use of this source code is governed by a
3+
# BSD-style license that can be found in the LICENSE file.
4+
callable:
5+
- library: 'shared/shared.dart'
6+
- library: 'dart:core'
7+
class: ['int', 'num', 'Object']
8+
- library: 'dart:core'
9+
class: 'pragma'
10+
member: '_'
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import '../../common/testing.dart' as helper;
6+
import 'package:expect/expect.dart';
7+
8+
import 'shared/shared.dart';
9+
10+
/// A dynamic module is allowed to extend a class in the dynamic interface and
11+
/// override its members.
12+
void main() async {
13+
final c = (await helper.load('entry1.dart')) as int Function(int, int);
14+
Expect.equals(1, c1(0));
15+
Expect.equals(49, c(0, 0));
16+
helper.done();
17+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import '../shared/shared.dart';
6+
7+
int f3(int i, int j) =>
8+
c1(i) +
9+
c2(i, j: i + 10) +
10+
c2(j, k: j + 10) +
11+
c2(i, j: j + 10, k: j + 10) +
12+
3;
13+
14+
@pragma('dyn-module:entry-point')
15+
Object? dynamicModuleEntrypoint() => f3;
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
int f1(int i) => i + 1;
6+
int f2(int i, {int j = 2, int k = 3}) => i + j + k;
7+
8+
final c1 = f1;
9+
final c2 = f2;

0 commit comments

Comments
 (0)