Skip to content

Commit dbee089

Browse files
osa1Commit Queue
authored andcommitted
[dart2wasm] Devirtualize closure calls based on TFA direct-call metadata
Use the TFA direct-call metadata to directly call a closure in function invocations. Closes #55231. Tested: existing tests cover the new code paths, but I also added a new test. Change-Id: Ib5f26b10efd77570e256196b4bfb07e6bef800c0 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/397260 Reviewed-by: Martin Kustermann <[email protected]> Commit-Queue: Ömer Ağacan <[email protected]>
1 parent f9f7b29 commit dbee089

File tree

7 files changed

+269
-47
lines changed

7 files changed

+269
-47
lines changed

pkg/dart2wasm/lib/closures.dart

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@
55
import 'dart:collection';
66
import 'dart:math' show min;
77

8+
import 'package:collection/collection.dart';
89
import 'package:kernel/ast.dart';
910
import 'package:vm/metadata/procedure_attributes.dart';
1011
import 'package:vm/transformations/type_flow/utils.dart' show UnionFind;
1112
import 'package:wasm_builder/wasm_builder.dart' as w;
1213

1314
import 'class_info.dart';
15+
import 'param_info.dart';
1416
import 'translator.dart';
1517

1618
/// Describes the implementation of a concrete closure, including its vtable
@@ -34,8 +36,16 @@ class ClosureImplementation {
3436
/// The module this closure is implemented in.
3537
final w.ModuleBuilder module;
3638

37-
ClosureImplementation(this.representation, this.functions,
38-
this.dynamicCallEntry, this.vtable, this.module);
39+
/// [ParameterInfo] to be used when directly calling the closure.
40+
final ParameterInfo directCallParamInfo;
41+
42+
ClosureImplementation(
43+
this.representation,
44+
this.functions,
45+
this.dynamicCallEntry,
46+
this.vtable,
47+
this.module,
48+
this.directCallParamInfo);
3949
}
4050

4151
/// Describes the representation of closures for a particular function
@@ -131,6 +141,8 @@ class ClosureRepresentation {
131141
/// The field index in the vtable struct for the function entry to use when
132142
/// calling the closure with the given number of positional arguments and the
133143
/// given set of named arguments.
144+
///
145+
/// `argNames` should be sorted.
134146
int fieldIndexForSignature(int posArgCount, List<String> argNames) {
135147
if (argNames.isEmpty) {
136148
return vtableBaseIndex + posArgCount;
@@ -155,7 +167,9 @@ class ClosureRepresentation {
155167
class NameCombination implements Comparable<NameCombination> {
156168
final List<String> names;
157169

158-
NameCombination(this.names);
170+
NameCombination(this.names) {
171+
assert(names.isSorted(Comparable.compare));
172+
}
159173

160174
@override
161175
int compareTo(NameCombination other) {
@@ -363,6 +377,8 @@ class ClosureLayouter extends RecursiveVisitor {
363377
/// Get the representation for closures with a specific signature, described
364378
/// by the number of type parameters, the maximum number of positional
365379
/// parameters and the names of named parameters.
380+
///
381+
/// `names` should be sorted.
366382
ClosureRepresentation? getClosureRepresentation(
367383
int typeCount, int positionalCount, List<String> names) {
368384
final representations =
@@ -1002,10 +1018,20 @@ class ClosureRepresentationCluster {
10021018
/// A local function or function expression.
10031019
class Lambda {
10041020
final FunctionNode functionNode;
1021+
1022+
// Note: creating a `Lambda` does not add this function to the compilation
1023+
// queue. Make sure to get it with `Functions.getLambdaFunction` to add it
1024+
// to the compilation queue.
10051025
final w.FunctionBuilder function;
1026+
10061027
final Source functionNodeSource;
10071028

1008-
Lambda(this.functionNode, this.function, this.functionNodeSource);
1029+
/// Index of the function within the enclosing member, based on pre-order
1030+
/// traversal of the member body.
1031+
final int index;
1032+
1033+
Lambda._(
1034+
this.functionNode, this.function, this.functionNodeSource, this.index);
10091035
}
10101036

10111037
/// The context for one or more closures, containing their captured variables.
@@ -1141,7 +1167,10 @@ class Closures {
11411167
/// does not populate [lambdas], [contexts], [captures], and
11421168
/// [closurizedFunctions]. This mode is useful in the code generators that
11431169
/// always have direct access to variables (instead of via a context).
1144-
Closures(this.translator, this._member, {bool findCaptures = true})
1170+
///
1171+
/// When `findCaptures` is `true`, the created [Lambda]s are also added to the
1172+
/// compilation queue.
1173+
Closures(this.translator, this._member, {required bool findCaptures})
11451174
: _nullableThisType = _member is Constructor || _member.isInstanceMember
11461175
? translator.preciseThisFor(_member, nullable: true) as w.RefType
11471176
: null {
@@ -1385,7 +1414,10 @@ class _CaptureFinder extends RecursiveVisitor {
13851414
functionName = "$member closure $functionNodeName at ${node.location}";
13861415
}
13871416
final function = module.functions.define(type, functionName);
1388-
closures.lambdas[node] = Lambda(node, function, _currentSource);
1417+
final lambda =
1418+
Lambda._(node, function, _currentSource, closures.lambdas.length);
1419+
closures.lambdas[node] = lambda;
1420+
translator.functions.getLambdaFunction(lambda, member, closures);
13891421

13901422
functionIsSyncStarOrAsync.add(node.asyncMarker == AsyncMarker.SyncStar ||
13911423
node.asyncMarker == AsyncMarker.Async);

pkg/dart2wasm/lib/code_generator.dart

Lines changed: 72 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -213,15 +213,6 @@ abstract class AstCodeGenerator
213213
translator.membersBeingGenerated.remove(enclosingMember);
214214
}
215215

216-
void addNestedClosuresToCompilationQueue() {
217-
for (Lambda lambda in closures.lambdas.values) {
218-
translator.compilationQueue.add(CompilationTask(
219-
lambda.function,
220-
getLambdaCodeGenerator(
221-
translator, lambda, enclosingMember, closures)));
222-
}
223-
}
224-
225216
// Generate the body.
226217
void generateInternal();
227218

@@ -2388,41 +2379,87 @@ abstract class AstCodeGenerator
23882379
expectedType);
23892380
}
23902381

2391-
final Expression receiver = node.receiver;
2392-
final Arguments arguments = node.arguments;
2393-
2394-
int typeCount = arguments.types.length;
2395-
int posArgCount = arguments.positional.length;
2396-
List<String> argNames = arguments.named.map((a) => a.name).toList()..sort();
2382+
List<String> argNames = node.arguments.named.map((a) => a.name).toList()
2383+
..sort();
23972384
ClosureRepresentation? representation = translator.closureLayouter
2398-
.getClosureRepresentation(typeCount, posArgCount, argNames);
2385+
.getClosureRepresentation(node.arguments.types.length,
2386+
node.arguments.positional.length, argNames);
23992387
if (representation == null) {
24002388
// This is a dynamic function call with a signature that matches no
24012389
// functions in the program.
24022390
b.unreachable();
24032391
return translator.topInfo.nullableType;
24042392
}
24052393

2394+
final SingleClosureTarget? directClosureCall =
2395+
translator.singleClosureTarget(node, representation, typeContext);
2396+
2397+
if (directClosureCall != null) {
2398+
return _generateDirectClosureCall(
2399+
node, representation, directClosureCall);
2400+
}
2401+
2402+
return _generateClosureInvocation(node, representation);
2403+
}
2404+
2405+
w.ValueType _generateDirectClosureCall(FunctionInvocation node,
2406+
ClosureRepresentation representation, SingleClosureTarget closureTarget) {
2407+
final closureStruct = representation.closureStruct;
2408+
final closureStructRef = w.RefType.def(closureStruct, nullable: false);
2409+
final signature = closureTarget.signature;
2410+
final paramInfo = closureTarget.paramInfo;
2411+
final member = closureTarget.member;
2412+
final lambdaFunction = closureTarget.lambdaFunction;
2413+
2414+
if (lambdaFunction == null) {
2415+
if (paramInfo.takesContextOrReceiver) {
2416+
translateExpression(node.receiver, closureStructRef);
2417+
b.struct_get(closureStruct, FieldIndex.closureContext);
2418+
translator.convertType(
2419+
b,
2420+
closureStruct.fields[FieldIndex.closureContext].type.unpacked,
2421+
signature.inputs[0]);
2422+
_visitArguments(node.arguments, signature, paramInfo, 1);
2423+
} else {
2424+
_visitArguments(node.arguments, signature, paramInfo, 0);
2425+
}
2426+
return translator.outputOrVoid(call(member.reference));
2427+
} else {
2428+
assert(paramInfo.takesContextOrReceiver);
2429+
translateExpression(node.receiver, closureStructRef);
2430+
b.struct_get(closureStruct, FieldIndex.closureContext);
2431+
_visitArguments(node.arguments, signature, paramInfo, 1);
2432+
return translator
2433+
.outputOrVoid(translator.callFunction(lambdaFunction, b));
2434+
}
2435+
}
2436+
2437+
w.ValueType _generateClosureInvocation(
2438+
FunctionInvocation node, ClosureRepresentation representation) {
2439+
final closureStruct = representation.closureStruct;
2440+
24062441
// Evaluate receiver
2407-
w.StructType struct = representation.closureStruct;
2408-
w.Local closureLocal = addLocal(w.RefType.def(struct, nullable: false));
2409-
translateExpression(receiver, closureLocal.type);
2442+
w.Local closureLocal =
2443+
addLocal(w.RefType.def(closureStruct, nullable: false));
2444+
translateExpression(node.receiver, closureLocal.type);
24102445
b.local_tee(closureLocal);
2411-
b.struct_get(struct, FieldIndex.closureContext);
2446+
b.struct_get(closureStruct, FieldIndex.closureContext);
24122447

24132448
// Type arguments
2414-
for (DartType typeArg in arguments.types) {
2449+
for (DartType typeArg in node.arguments.types) {
24152450
types.makeType(this, typeArg);
24162451
}
24172452

24182453
// Positional arguments
2419-
for (Expression arg in arguments.positional) {
2454+
for (Expression arg in node.arguments.positional) {
24202455
translateExpression(arg, translator.topInfo.nullableType);
24212456
}
24222457

24232458
// Named arguments
2459+
final List<String> argNames =
2460+
node.arguments.named.map((a) => a.name).toList()..sort();
24242461
final Map<String, w.Local> namedLocals = {};
2425-
for (final namedArg in arguments.named) {
2462+
for (final namedArg in node.arguments.named) {
24262463
final w.Local namedLocal = addLocal(translator.topInfo.nullableType);
24272464
namedLocals[namedArg.name] = namedLocal;
24282465
translateExpression(namedArg.value, namedLocal.type);
@@ -2432,15 +2469,17 @@ abstract class AstCodeGenerator
24322469
b.local_get(namedLocals[name]!);
24332470
}
24342471

2435-
// Call entry point in vtable
2436-
int vtableFieldIndex =
2437-
representation.fieldIndexForSignature(posArgCount, argNames);
2438-
w.FunctionType functionType =
2472+
final int vtableFieldIndex = representation.fieldIndexForSignature(
2473+
node.arguments.positional.length, argNames);
2474+
final w.FunctionType functionType =
24392475
representation.getVtableFieldType(vtableFieldIndex);
2476+
2477+
// Call entry point in vtable
24402478
b.local_get(closureLocal);
2441-
b.struct_get(struct, FieldIndex.closureVtable);
2479+
b.struct_get(closureStruct, FieldIndex.closureVtable);
24422480
b.struct_get(representation.vtableStruct, vtableFieldIndex);
24432481
b.call_ref(functionType);
2482+
24442483
return translator.topInfo.nullableType;
24452484
}
24462485

@@ -3181,7 +3220,7 @@ class SynchronousProcedureCodeGenerator extends AstCodeGenerator {
31813220
return;
31823221
}
31833222

3184-
closures = Closures(translator, member);
3223+
closures = translator.getClosures(member);
31853224

31863225
setupParametersAndContexts(member, useUncheckedEntry: useUncheckedEntry);
31873226

@@ -3192,7 +3231,6 @@ class SynchronousProcedureCodeGenerator extends AstCodeGenerator {
31923231

31933232
_implicitReturn();
31943233
b.end();
3195-
addNestedClosuresToCompilationQueue();
31963234
}
31973235
}
31983236

@@ -3209,7 +3247,7 @@ class TearOffCodeGenerator extends AstCodeGenerator {
32093247
// used by `makeType` below, when generating runtime types of type
32103248
// parameters of the function type, but the type parameters are not
32113249
// captured, always loaded from the `this` struct.
3212-
closures = Closures(translator, member, findCaptures: false);
3250+
closures = translator.getClosures(member, findCaptures: false);
32133251

32143252
_initializeThis(member.reference);
32153253
Procedure procedure = member as Procedure;
@@ -3242,7 +3280,7 @@ class TypeCheckerCodeGenerator extends AstCodeGenerator {
32423280
// Initialize [Closures] without [Closures.captures]: Similar to
32433281
// [TearOffCodeGenerator], type parameters will be loaded from the `this`
32443282
// struct.
3245-
closures = Closures(translator, member, findCaptures: false);
3283+
closures = translator.getClosures(member, findCaptures: false);
32463284
if (member is Field ||
32473285
(member is Procedure && (member as Procedure).isSetter)) {
32483286
_generateFieldSetterTypeCheckerMethod();
@@ -3482,7 +3520,6 @@ class InitializerListCodeGenerator extends AstCodeGenerator {
34823520
generateInitializerList();
34833521
}
34843522
b.end();
3485-
addNestedClosuresToCompilationQueue();
34863523
}
34873524

34883525
// Generates a constructor's initializer list method, and returns:
@@ -3848,7 +3885,7 @@ class StaticFieldInitializerCodeGenerator extends AstCodeGenerator {
38483885
setSourceMapSourceAndFileOffset(source, field.fileOffset);
38493886

38503887
// Static field initializer function
3851-
closures = Closures(translator, field);
3888+
closures = translator.getClosures(field);
38523889

38533890
w.Global global = translator.globals.getGlobalForStaticField(field);
38543891
w.Global? flag = translator.globals.getGlobalInitializedFlag(field);
@@ -3861,7 +3898,6 @@ class StaticFieldInitializerCodeGenerator extends AstCodeGenerator {
38613898
b.global_get(global);
38623899
translator.convertType(b, global.type.type, outputs.single);
38633900
b.end();
3864-
addNestedClosuresToCompilationQueue();
38653901
}
38663902
}
38673903

@@ -3944,7 +3980,7 @@ class ImplicitFieldAccessorCodeGenerator extends AstCodeGenerator {
39443980
// that instantiates types uses closure information to see whether a type
39453981
// parameter was captured (and loads it from context chain) or not (and
39463982
// loads it directly from `this`).
3947-
closures = Closures(translator, field, findCaptures: false);
3983+
closures = translator.getClosures(field, findCaptures: false);
39483984

39493985
final source = field.enclosingComponent!.uriToSource[field.fileUri]!;
39503986
setSourceMapSourceAndFileOffset(source, field.fileOffset);

pkg/dart2wasm/lib/functions.dart

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ class FunctionCollector {
2020

2121
// Wasm function for each Dart function
2222
final Map<Reference, w.BaseFunction> _functions = {};
23+
// Wasm function for each function expression and local function.
24+
final Map<Lambda, w.BaseFunction> _lambdas = {};
2325
// Names of exported functions
2426
final Map<Reference, String> _exports = {};
2527
// Selector IDs that are invoked via GDT.
@@ -129,6 +131,17 @@ class FunctionCollector {
129131
});
130132
}
131133

134+
w.BaseFunction getLambdaFunction(
135+
Lambda lambda, Member enclosingMember, Closures enclosingMemberClosures) {
136+
return _lambdas.putIfAbsent(lambda, () {
137+
translator.compilationQueue.add(CompilationTask(
138+
lambda.function,
139+
getLambdaCodeGenerator(
140+
translator, lambda, enclosingMember, enclosingMemberClosures)));
141+
return lambda.function;
142+
});
143+
}
144+
132145
w.FunctionType getFunctionType(Reference target) {
133146
// We first try to get the function type by seeing if we already
134147
// compiled the [target] function.
@@ -278,7 +291,7 @@ class _FunctionTypeGenerator extends MemberVisitor1<w.FunctionType, Reference> {
278291
// context argument if context must be shared between them. Generate the
279292
// contexts the first time we visit a constructor.
280293
translator.constructorClosures[node.reference] ??=
281-
Closures(translator, node);
294+
translator.getClosures(node);
282295

283296
if (target.isInitializerReference) {
284297
return _getInitializerType(node, target, arguments);

pkg/dart2wasm/lib/state_machine.dart

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -595,7 +595,7 @@ abstract class ProcedureStateMachineEntryCodeGenerator
595595
setSourceMapSource(source);
596596
setSourceMapFileOffset(member.fileOffset);
597597

598-
closures = Closures(translator, member);
598+
closures = translator.getClosures(member);
599599

600600
// We don't support inlining state machine functions atm. Only when we
601601
// inline and have call-site guarantees we would use the unchecked entry.
@@ -605,7 +605,6 @@ abstract class ProcedureStateMachineEntryCodeGenerator
605605
if (context != null && context.isEmpty) context = context.parent;
606606

607607
generateOuter(member.function, context, source);
608-
addNestedClosuresToCompilationQueue();
609608
}
610609
}
611610

0 commit comments

Comments
 (0)