Skip to content

Commit 667c6af

Browse files
committed
[dart2wasm] Implement missing features in dynamic invocations:
This reimplements dynamic call code generation to add support for type checking, named parameters (optional and required), and fixes a few related bugs on the way. Currently we do not try to be as efficient as possible. The goal with this patch to implement it correctly. Summary of the changes: - For every dynamic access kind and member name, we generate a new "forwarder" function. Dynamic gets, sets, and invocations are compiled to calls to the forwarders with the right access kind (invocation, get, set) and member name. For example, if the program has dynamic invocation of a member "f", we create an "invocation forwarder for f". If it has a dynamic get of a member "x", we generate "getter forwarder for x". - Forwarder functions take 4 arguments: - Receiver of the invocation, get, or set. - A Dart list for type arguments in the invocation. For gets and sets the list is empty. - A Dart list for positional arguments in the invocation. For gets the list is empty. For sets, the list only has one element. - A Dart list for named arguments. For gets and sets the list is empty. The list has alternating elements of type `Symbol` and `Object?`, for the name and value of the named parameters. - A forwarder function compares receiver class ID with the potential targets of the call. When it finds a match, if compares the callee "shape" with the parameters passed in the call site. As it compares the shapes it adjusts argument lists: - Creates default values for missing optional and named arguments - Reorders the named argument list to match order expected by the callee If it can't find a matching class ID and a member with the right name and shape, it calls `noSuchMethod` on the receiver. If it finds a matching class ID and a member, it calls the "type checker" for the member, passing the original receiver and adjusted argument lists. - A "type checker" implements argument type checking for a member, and it's a member of the same class as the member it's checking types for. This is to allow accessing class-bound type parameters when generating type checking code. - Type checking is implemented using `_isSubtype` on arguments in the lists. - When type checking is successful a type checker calls the original member, passing the arguments as expected by the member. If type checking is unsuccessful it throws a type error. Most of the changes are for generating Wasm functions that compare shapes, adjusts argument lists, and checks types. Changes to members: - `Translator.dynamics` fields is renamed to `Translator.forwarders` - New field `Translator.forwarderFunctionType` added for the Wasm function type of forwarder and type checker functions. - Two new code gen utilities added: - `Translator.indexArray`: generates code that indexes a Dart array - `Translator.getArrayLength`: generates code that gets length of a Dart array - New `Reference` extensions added to get type checker function reference of members - New library `named_parameters` implements two helper functions for dealing with named argument lists - The library `dynamic_dispatch` is rewritten and now consists of two classes: - `Forwarders`: maintains mapping from call kind (get, set, invocation) and member name to forwarder functions. - `Forwarder`: a single forwarder, implements code generation for forwarder functions. - `CodeGenerator` gets 3 new members: - `_callForwader` generates call to a forwarder - `generateSetterTypeCheckerMethod` generates code for a type checker of a setter function. - `generateInvocationTypeCheckerMethod` generates code for a type checker of a method. (TODO: This should be renamed to `generateMethodTypeCheckerMethod` for consistency) Fixes dart-lang#50367 Change-Id: I2b9d84237c8517bd217166d8acb67e025f0498fb
1 parent 14d553a commit 667c6af

10 files changed

+1046
-512
lines changed

pkg/dart2wasm/lib/code_generator.dart

+361-5
Large diffs are not rendered by default.

pkg/dart2wasm/lib/dispatch_table.dart

+10-57
Original file line numberDiff line numberDiff line change
@@ -177,51 +177,6 @@ class SelectorInfo {
177177
return m.addFunctionType(
178178
[inputs[0], ...typeParameters, ...inputs.sublist(1)], outputs);
179179
}
180-
181-
/// Whether the selector can be applied in a [DynamicGet], [DynamicSet], or
182-
/// [DynamicInvocation]. This only checks the argument counts and names, not
183-
/// their types.
184-
bool canApply(Expression dynamicExpression) {
185-
if (dynamicExpression is DynamicGet || dynamicExpression is DynamicSet) {
186-
// Dynamic get or set can always apply.
187-
return true;
188-
} else if (dynamicExpression is DynamicInvocation) {
189-
Procedure member = paramInfo.member as Procedure;
190-
Arguments arguments = dynamicExpression.arguments;
191-
FunctionNode function = member.function;
192-
if (arguments.types.isNotEmpty &&
193-
arguments.types.length != function.typeParameters.length) {
194-
return false;
195-
}
196-
197-
if (arguments.positional.length < function.requiredParameterCount ||
198-
arguments.positional.length > function.positionalParameters.length) {
199-
return false;
200-
}
201-
202-
Set<String> namedParameters = {};
203-
Set<String> requiredNamedParameters = {};
204-
for (VariableDeclaration v in function.namedParameters) {
205-
if (v.isRequired) {
206-
requiredNamedParameters.add(v.name!);
207-
} else {
208-
namedParameters.add(v.name!);
209-
}
210-
}
211-
212-
int requiredFound = 0;
213-
for (NamedExpression namedArgument in arguments.named) {
214-
bool found = requiredNamedParameters.contains(namedArgument.name);
215-
if (found) {
216-
requiredFound++;
217-
} else if (!namedParameters.contains(namedArgument.name)) {
218-
return false;
219-
}
220-
}
221-
return requiredFound == requiredNamedParameters.length;
222-
}
223-
throw '"canApply" should only be used for procedures';
224-
}
225180
}
226181

227182
/// Builds the dispatch table for member calls.
@@ -313,18 +268,16 @@ class DispatchTable {
313268
return selector;
314269
}
315270

316-
/// Returns a possibly null list of [SelectorInfo]s for a given dynamic
317-
/// call.
318-
Iterable<SelectorInfo>? selectorsForDynamicNode(Expression node) {
319-
if (node is DynamicGet) {
320-
return _dynamicGets[node.name.text];
321-
} else if (node is DynamicSet) {
322-
return _dynamicSets[node.name.text];
323-
} else if (node is DynamicInvocation) {
324-
return _dynamicMethods[node.name.text];
325-
} else {
326-
throw 'Dynamic invocation of $node not supported';
327-
}
271+
Iterable<SelectorInfo>? selectorsForDynamicGet(String memberName) {
272+
return _dynamicGets[memberName];
273+
}
274+
275+
Iterable<SelectorInfo>? selectorsForDynamicSet(String memberName) {
276+
return _dynamicSets[memberName];
277+
}
278+
279+
Iterable<SelectorInfo>? selectorsForDynamicInvocation(String memberName) {
280+
return _dynamicMethods[memberName];
328281
}
329282

330283
void build() {

0 commit comments

Comments
 (0)