Skip to content

Commit 29b6a03

Browse files
nshahanCommit Queue
authored andcommitted
[ddc] Refactor visitDynamicInvocation()
Create a new helper `_emitArgumentGroups()` used for the calling conventions where arguments are not passed in a flattened list. Towards reducing the variety of code that flows through `_emitMethodCall()` so hot reload soundness checks can be added when they are appropriate. Change-Id: I50ef9c4d54d71408d71b76fc2ca0acb73ef2af08 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/422367 Reviewed-by: Mark Zhou <[email protected]> Reviewed-by: Nate Biggs <[email protected]> Commit-Queue: Nicholas Shahan <[email protected]>
1 parent 04b178d commit 29b6a03

File tree

2 files changed

+218
-125
lines changed

2 files changed

+218
-125
lines changed

pkg/dev_compiler/lib/src/kernel/compiler.dart

Lines changed: 109 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,15 @@ import 'type_environment.dart';
4848
import 'type_recipe_generator.dart';
4949
import 'type_table.dart';
5050

51+
/// The groups of compiled invocation arguments ready to be flattened into a
52+
/// single [js_ast.Array] for statically sound calls or into separate packets
53+
/// for runtime checked calls.
54+
typedef _ArgumentGroups = ({
55+
List<js_ast.Expression>? typeArguments,
56+
List<js_ast.Expression>? positionalArguments,
57+
List<js_ast.Property>? namedArguments
58+
});
59+
5160
/// Public API for DDC compilers.
5261
abstract class Compiler {
5362
js_ast.Program emitModule(Component component);
@@ -5263,22 +5272,23 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
52635272

52645273
@override
52655274
js_ast.Expression visitDynamicInvocation(DynamicInvocation node) {
5266-
return _emitMethodCall(node.receiver, null, node.arguments, node);
5275+
return _emitDynamicInvocation(
5276+
node.receiver, node.name.text, node.arguments);
52675277
}
52685278

52695279
@override
52705280
js_ast.Expression visitFunctionInvocation(FunctionInvocation node) {
5271-
assert(node.name.text == 'call');
5272-
var function = _visitExpression(node.receiver);
5273-
var arguments = _emitArgumentList(node.arguments);
5281+
var name = node.name.text;
5282+
assert(name == 'call');
52745283
if (node.functionType == null) {
52755284
// A `null` here implies the receiver is typed as `Function`. There isn't
52765285
// any more type information available at compile time to know this
52775286
// invocation is sound so a dynamic call will handle the checks at
52785287
// runtime.
5279-
return _emitDynamicInvoke(function, null, arguments, node.arguments);
5288+
return _emitDynamicInvocation(node.receiver, name, node.arguments);
52805289
}
5281-
return js_ast.Call(function, arguments);
5290+
return js_ast.Call(
5291+
_visitExpression(node.receiver), _emitArgumentList(node.arguments));
52825292
}
52835293

52845294
@override
@@ -5320,7 +5330,7 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
53205330
negated: false);
53215331
}
53225332

5323-
js_ast.Expression _emitMethodCall(Expression receiver, Member? target,
5333+
js_ast.Expression _emitMethodCall(Expression receiver, Member target,
53245334
Arguments arguments, InvocationExpression node) {
53255335
var name = node.name.text;
53265336

@@ -5380,22 +5390,22 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
53805390
return js.call('#.push(#)', [jsReceiver, args]);
53815391
}
53825392

5383-
var isCallingDynamicField = target is Member &&
5384-
target.hasGetter &&
5393+
var isCallingDynamicField = target.hasGetter &&
53855394
// Erasing extension types here doesn't make sense. If there is an
53865395
// extension type on dynamic or Function it will only be callable if it
53875396
// defines a call method which would be invoked statically.
53885397
_isDynamicOrFunction(target.getterType);
53895398
if (name == 'call') {
5390-
// Erasing the extension types here to support existing callable behaivor
5399+
// Erasing the extension types here to support existing callable behavior
53915400
// on the old style JS interop types that are callable. This should be
53925401
// safe as it is a compile time error to try to dynamically invoke a call
53935402
// method that is inherited from an extension type.
53945403
var receiverType =
53955404
receiver.getStaticType(_staticTypeContext).extensionTypeErasure;
5396-
if (isCallingDynamicField || _isDynamicOrFunction(receiverType)) {
5397-
return _emitDynamicInvoke(jsReceiver, null, args, arguments);
5398-
} else if (_isDirectCallable(receiverType)) {
5405+
if (isCallingDynamicField) {
5406+
return _emitDynamicInvocation(receiver, name, arguments);
5407+
}
5408+
if (_isDirectCallable(receiverType)) {
53995409
// Call methods on function types should be handled as function calls.
54005410
return js_ast.Call(jsReceiver, args);
54015411
}
@@ -5412,8 +5422,8 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
54125422
return _runtimeCall('#(#, #)', [name, jsReceiver, args]);
54135423
}
54145424
// Otherwise generate this as a normal typed method call.
5415-
} else if (target == null || isCallingDynamicField) {
5416-
return _emitDynamicInvoke(jsReceiver, jsName, args, arguments);
5425+
} else if (isCallingDynamicField) {
5426+
return _emitDynamicInvocation(receiver, name, arguments);
54175427
}
54185428
// TODO(jmesserly): remove when Kernel desugars this for us.
54195429
// Handle `o.m(a)` where `o.m` is a getter returning a class with `call`.
@@ -5428,12 +5438,12 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
54285438
// }
54295439
// ```
54305440
//
5431-
// We can always erase eagerly becuase:
5432-
// - Extension types that do not implment an interface that exposes a
5441+
// We can always erase eagerly because:
5442+
// - Extension types that do not implement an interface that exposes a
54335443
// `call` method will result in a static error at the call site.
54345444
// - Calls to extension types that implement their own call method are
54355445
// lowered by the CFE to top level static method calls.
5436-
var fromType = target!.getterType.extensionTypeErasure;
5446+
var fromType = target.getterType.extensionTypeErasure;
54375447
if (fromType is InterfaceType) {
54385448
var callName = _implicitCallTarget(fromType);
54395449
if (callName != null) {
@@ -5444,41 +5454,42 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
54445454
return js.call('#.#(#)', [jsReceiver, jsName, args]);
54455455
}
54465456

5447-
js_ast.Expression _emitDynamicInvoke(
5448-
js_ast.Expression fn,
5449-
js_ast.Expression? methodName,
5450-
Iterable<js_ast.Expression> args,
5451-
Arguments arguments) {
5452-
var jsArgs = <Object>[fn];
5457+
/// Returns an invocation of the runtime helpers `dcall`, `dgcall`, `dsend`,
5458+
/// or `dgsend` to perform dynamic checks before invoking [memberName] on
5459+
/// [receiver] and passing [arguments].
5460+
js_ast.Expression _emitDynamicInvocation(
5461+
Expression receiver, String memberName, Arguments arguments) {
5462+
var jsArgs = [_visitExpression(receiver)];
54535463
String jsCode;
5454-
5455-
var typeArgs = arguments.types;
5456-
if (typeArgs.isNotEmpty) {
5457-
jsArgs.add(args.take(typeArgs.length));
5458-
args = args.skip(typeArgs.length);
5459-
if (methodName != null) {
5460-
jsCode = 'dgsend$_replSuffix(#, [#], #';
5461-
jsArgs.add(methodName);
5464+
var (:typeArguments, :positionalArguments, :namedArguments) =
5465+
_emitArgumentGroups(arguments, isJSInterop: false);
5466+
if (memberName == 'call') {
5467+
if (typeArguments != null) {
5468+
jsCode = 'dgcall(#, #';
5469+
jsArgs.add(js_ast.ArrayInitializer(typeArguments));
54625470
} else {
5463-
jsCode = 'dgcall(#, [#]';
5471+
jsCode = 'dcall(#';
54645472
}
5465-
} else if (methodName != null) {
5466-
jsCode = 'dsend$_replSuffix(#, #';
5467-
jsArgs.add(methodName);
54685473
} else {
5469-
jsCode = 'dcall(#';
5474+
if (typeArguments != null) {
5475+
jsCode = 'dgsend$_replSuffix(#, #, #';
5476+
jsArgs.add(js_ast.ArrayInitializer(typeArguments));
5477+
} else {
5478+
jsCode = 'dsend$_replSuffix(#, #';
5479+
}
5480+
jsArgs.add(_emitMemberName(memberName));
54705481
}
5471-
5472-
var hasNamed = arguments.named.isNotEmpty;
5473-
if (hasNamed) {
5474-
jsCode += ', [#], #)';
5475-
jsArgs.add(args.take(args.length - 1));
5476-
jsArgs.add(args.last);
5482+
if (positionalArguments != null) {
5483+
jsCode += ', #';
5484+
jsArgs.add(js_ast.ArrayInitializer(positionalArguments));
54775485
} else {
5478-
jsArgs.add(args);
5479-
jsCode += ', [#])';
5486+
jsCode += ', []';
54805487
}
5481-
5488+
if (namedArguments != null) {
5489+
jsCode += ', #';
5490+
jsArgs.add(js_ast.ObjectInitializer(namedArguments));
5491+
}
5492+
jsCode += ')';
54825493
return _runtimeCall(jsCode, jsArgs);
54835494
}
54845495

@@ -6384,29 +6395,65 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
63846395
return _emitTopLevelName(target);
63856396
}
63866397

6398+
/// Returns all parts of [arguments] flattened into a list so they can be
6399+
/// passed in the calling convention for calls with no runtime checks.
6400+
///
6401+
/// When [types] is `false` any type arguments present in [arguments] will be
6402+
/// omitted.
6403+
///
6404+
/// Passing [target] when applicable allows for detection of an annotation to
6405+
/// omit passing type parameters and to detect if positional arguments should
6406+
/// be packaged to be passed to a JavaScript using an interop call.
63876407
List<js_ast.Expression> _emitArgumentList(Arguments node,
63886408
{bool types = true, Member? target}) {
63896409
types = types && _reifyGenericFunction(target);
6390-
final isJsInterop = target != null && isJsMember(target);
6410+
var (:typeArguments, :positionalArguments, :namedArguments) =
6411+
_emitArgumentGroups(node,
6412+
isJSInterop: target != null && isJsMember(target));
63916413
return [
6392-
if (types)
6393-
for (var typeArg in node.types) _emitType(typeArg),
6394-
for (var arg in node.positional)
6395-
if (arg is StaticInvocation &&
6396-
isJSSpreadInvocation(arg.target) &&
6397-
arg.arguments.positional.length == 1)
6398-
js_ast.Spread(_visitExpression(arg.arguments.positional[0]))
6399-
else if (isJsInterop)
6400-
_visitExpression(_assertInterop(arg))
6401-
else
6402-
_visitExpression(arg),
6403-
if (node.named.isNotEmpty)
6404-
js_ast.ObjectInitializer([
6405-
for (var arg in node.named) _emitNamedExpression(arg, isJsInterop)
6406-
]),
6414+
if (types && typeArguments != null) ...typeArguments,
6415+
if (positionalArguments != null) ...positionalArguments,
6416+
if (namedArguments != null) js_ast.ObjectInitializer([...namedArguments]),
64076417
];
64086418
}
64096419

6420+
/// Returns all [arguments] but kept in separate packets so they can be
6421+
/// further processed.
6422+
///
6423+
/// Facilitates passing arguments in the calling convention used by runtime
6424+
/// helpers that check arguments.
6425+
///
6426+
/// When [isJSInterop] is `true` the positional arguments are packaged to
6427+
/// be passed to JavaScript via an interop call.
6428+
_ArgumentGroups _emitArgumentGroups(Arguments node,
6429+
{required bool isJSInterop}) {
6430+
var typeArguments = node.types.isEmpty
6431+
? null
6432+
: [for (var typeArgument in node.types) _emitType(typeArgument)];
6433+
var positionalArguments = node.positional.isEmpty
6434+
? null
6435+
: [
6436+
for (var arg in node.positional)
6437+
if (arg is StaticInvocation &&
6438+
isJSSpreadInvocation(arg.target) &&
6439+
arg.arguments.positional.length == 1)
6440+
js_ast.Spread(_visitExpression(arg.arguments.positional[0]))
6441+
else if (isJSInterop)
6442+
_visitExpression(_assertInterop(arg))
6443+
else
6444+
_visitExpression(arg),
6445+
];
6446+
var namedArguments = node.named.isEmpty
6447+
? null
6448+
: [for (var arg in node.named) _emitNamedExpression(arg, isJSInterop)];
6449+
6450+
return (
6451+
typeArguments: typeArguments,
6452+
positionalArguments: positionalArguments,
6453+
namedArguments: namedArguments
6454+
);
6455+
}
6456+
64106457
js_ast.Property _emitNamedExpression(NamedExpression arg,
64116458
[bool isJsInterop = false]) {
64126459
var value = isJsInterop ? _assertInterop(arg.value) : arg.value;

0 commit comments

Comments
 (0)