mirror of
https://github.com/dart-lang/sdk
synced 2024-09-16 00:09:49 +00:00
b85443e333
This fixes a case of an empty selector in the dispatch table in `lib/js/static_interop_test/mock/mockito_test`. Change-Id: Ia4e409b4d45c71de8691f1290a853017bc56399f Cq-Include-Trybots: luci.dart.try:dart2wasm-linux-x64-d8-try Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/259741 Reviewed-by: Srujan Gaddam <srujzs@google.com> Commit-Queue: Aske Simon Christensen <askesc@google.com>
394 lines
14 KiB
Dart
394 lines
14 KiB
Dart
// Copyright (c) 2022, 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 'dart:math';
|
|
|
|
import 'package:dart2wasm/class_info.dart';
|
|
import 'package:dart2wasm/dynamic_dispatch.dart';
|
|
import 'package:dart2wasm/param_info.dart';
|
|
import 'package:dart2wasm/reference_extensions.dart';
|
|
import 'package:dart2wasm/translator.dart';
|
|
|
|
import 'package:kernel/ast.dart';
|
|
|
|
import 'package:vm/metadata/procedure_attributes.dart';
|
|
import 'package:vm/metadata/table_selector.dart';
|
|
|
|
import 'package:wasm_builder/wasm_builder.dart' as w;
|
|
|
|
/// Information for a dispatch table selector.
|
|
class SelectorInfo {
|
|
final Translator translator;
|
|
|
|
final int id;
|
|
final int callCount;
|
|
final bool tornOff;
|
|
final ParameterInfo paramInfo;
|
|
int returnCount;
|
|
|
|
final Map<int, Reference> targets = {};
|
|
late final w.FunctionType signature = computeSignature();
|
|
|
|
late final List<int> classIds;
|
|
late final int targetCount;
|
|
final bool calledDynamically;
|
|
Reference? singularTarget;
|
|
int? offset;
|
|
|
|
w.Module get m => translator.m;
|
|
|
|
String get name => paramInfo.member!.name.text;
|
|
|
|
bool get alive =>
|
|
calledDynamically ? targetCount > 0 : callCount > 0 && targetCount > 1;
|
|
|
|
int get sortWeight => classIds.length * 10 + callCount;
|
|
|
|
SelectorInfo(this.translator, this.id, this.callCount, this.tornOff,
|
|
this.paramInfo, this.returnCount, this.calledDynamically);
|
|
|
|
/// Compute the signature for the functions implementing members targeted by
|
|
/// this selector.
|
|
///
|
|
/// When the selector has multiple targets, the type of each parameter/return
|
|
/// is the upper bound across all targets, such that all targets have the
|
|
/// same signature, and the actual representation types of the parameters and
|
|
/// returns are subtypes (resp. supertypes) of the types in the signature.
|
|
w.FunctionType computeSignature() {
|
|
var nameIndex = paramInfo.nameIndex;
|
|
List<Set<ClassInfo>> inputSets =
|
|
List.generate(1 + paramInfo.paramCount, (_) => {});
|
|
List<Set<ClassInfo>> outputSets = List.generate(returnCount, (_) => {});
|
|
List<bool> inputNullable = List.filled(1 + paramInfo.paramCount, false);
|
|
List<bool> ensureBoxed = List.filled(1 + paramInfo.paramCount, false);
|
|
List<bool> outputNullable = List.filled(returnCount, false);
|
|
targets.forEach((id, target) {
|
|
ClassInfo receiver = translator.classes[id];
|
|
List<DartType> positional;
|
|
Map<String, DartType> named;
|
|
List<DartType> returns;
|
|
Member member = target.asMember;
|
|
if (member is Field) {
|
|
if (target.isImplicitGetter) {
|
|
positional = const [];
|
|
named = const {};
|
|
returns = [member.getterType];
|
|
} else {
|
|
positional = [member.setterType];
|
|
named = const {};
|
|
returns = const [];
|
|
}
|
|
} else {
|
|
FunctionNode function = member.function!;
|
|
if (target.isTearOffReference) {
|
|
positional = const [];
|
|
named = const {};
|
|
returns = [function.computeFunctionType(Nullability.nonNullable)];
|
|
} else {
|
|
positional = [
|
|
for (VariableDeclaration param in function.positionalParameters)
|
|
param.type
|
|
];
|
|
named = {
|
|
for (VariableDeclaration param in function.namedParameters)
|
|
param.name!: param.type
|
|
};
|
|
returns = function.returnType is VoidType
|
|
? const []
|
|
: [function.returnType];
|
|
}
|
|
}
|
|
assert(returns.length <= outputSets.length);
|
|
inputSets[0].add(receiver);
|
|
ensureBoxed[0] = true;
|
|
for (int i = 0; i < positional.length; i++) {
|
|
DartType type = positional[i];
|
|
inputSets[1 + i]
|
|
.add(translator.classInfo[translator.classForType(type)]!);
|
|
inputNullable[1 + i] |= type.isPotentiallyNullable;
|
|
ensureBoxed[1 + i] |=
|
|
paramInfo.positional[i] == ParameterInfo.defaultValueSentinel;
|
|
}
|
|
for (String name in named.keys) {
|
|
int i = nameIndex[name]!;
|
|
DartType type = named[name]!;
|
|
inputSets[1 + i]
|
|
.add(translator.classInfo[translator.classForType(type)]!);
|
|
inputNullable[1 + i] |= type.isPotentiallyNullable;
|
|
ensureBoxed[1 + i] |=
|
|
paramInfo.named[name] == ParameterInfo.defaultValueSentinel;
|
|
}
|
|
for (int i = 0; i < returnCount; i++) {
|
|
if (i < returns.length) {
|
|
outputSets[i]
|
|
.add(translator.classInfo[translator.classForType(returns[i])]!);
|
|
outputNullable[i] |= returns[i].isPotentiallyNullable;
|
|
} else {
|
|
outputNullable[i] = true;
|
|
}
|
|
}
|
|
});
|
|
|
|
List<w.ValueType> typeParameters = List.filled(paramInfo.typeParamCount,
|
|
translator.classInfo[translator.typeClass]!.nonNullableType);
|
|
List<w.ValueType> inputs = List.generate(
|
|
inputSets.length,
|
|
(i) => translator.typeForInfo(
|
|
upperBound(inputSets[i]), inputNullable[i],
|
|
ensureBoxed: ensureBoxed[i]) as w.ValueType);
|
|
if (name == '==') {
|
|
// == can't be called with null
|
|
inputs[1] = inputs[1].withNullability(false);
|
|
}
|
|
List<w.ValueType> outputs = List.generate(
|
|
outputSets.length,
|
|
(i) => translator.typeForInfo(
|
|
upperBound(outputSets[i]), outputNullable[i]) as w.ValueType);
|
|
return m.addFunctionType(
|
|
[inputs[0], ...typeParameters, ...inputs.sublist(1)], outputs);
|
|
}
|
|
|
|
// Returns a bool indicating whether or not a given selector can be applied to
|
|
// a given [Arguments] object. This only checks the argument counts / names,
|
|
// not their types.
|
|
bool canApply(Expression dynamicExpression) {
|
|
if (dynamicExpression is DynamicGet || dynamicExpression is DynamicSet) {
|
|
// Dynamic get or set can always apply.
|
|
return true;
|
|
} else if (dynamicExpression is DynamicInvocation) {
|
|
Procedure member = paramInfo.member as Procedure;
|
|
Arguments arguments = dynamicExpression.arguments;
|
|
FunctionNode function = member.function;
|
|
if (arguments.types.isNotEmpty &&
|
|
arguments.types.length != function.typeParameters.length) {
|
|
return false;
|
|
}
|
|
|
|
if (arguments.positional.length < function.requiredParameterCount ||
|
|
arguments.positional.length > function.positionalParameters.length) {
|
|
return false;
|
|
}
|
|
|
|
Set<String> namedParameters = {};
|
|
Set<String> requiredNamedParameters = {};
|
|
for (VariableDeclaration v in function.namedParameters) {
|
|
if (v.isRequired) {
|
|
requiredNamedParameters.add(v.name!);
|
|
} else {
|
|
namedParameters.add(v.name!);
|
|
}
|
|
}
|
|
|
|
int requiredFound = 0;
|
|
for (NamedExpression namedArgument in arguments.named) {
|
|
bool found = requiredNamedParameters.contains(namedArgument.name);
|
|
if (found) {
|
|
requiredFound++;
|
|
} else if (!namedParameters.contains(namedArgument.name)) {
|
|
return false;
|
|
}
|
|
}
|
|
return requiredFound == requiredNamedParameters.length;
|
|
}
|
|
throw '"canApply" should only be used for procedures';
|
|
}
|
|
}
|
|
|
|
// Build dispatch table for member calls.
|
|
class DispatchTable {
|
|
final Translator translator;
|
|
final List<TableSelectorInfo> selectorMetadata;
|
|
final Map<TreeNode, ProcedureAttributesMetadata> procedureAttributeMetadata;
|
|
|
|
final Map<int, SelectorInfo> selectorInfo = {};
|
|
final Map<String, List<SelectorInfo>> dynamicGets = {};
|
|
final Map<String, List<SelectorInfo>> dynamicSets = {};
|
|
final Map<String, List<SelectorInfo>> dynamicMethods = {};
|
|
late final List<Reference?> table;
|
|
late final w.DefinedTable wasmTable;
|
|
|
|
w.Module get m => translator.m;
|
|
|
|
DispatchTable(this.translator)
|
|
: selectorMetadata =
|
|
(translator.component.metadata["vm.table-selector.metadata"]
|
|
as TableSelectorMetadataRepository)
|
|
.mapping[translator.component]!
|
|
.selectors,
|
|
procedureAttributeMetadata =
|
|
(translator.component.metadata["vm.procedure-attributes.metadata"]
|
|
as ProcedureAttributesMetadataRepository)
|
|
.mapping;
|
|
|
|
SelectorInfo selectorForTarget(Reference target) {
|
|
Member member = target.asMember;
|
|
bool isGetter = target.isGetter || target.isTearOffReference;
|
|
bool isSetter = target.isSetter;
|
|
ProcedureAttributesMetadata metadata = procedureAttributeMetadata[member]!;
|
|
int selectorId = isGetter
|
|
? metadata.getterSelectorId
|
|
: metadata.methodOrSetterSelectorId;
|
|
ParameterInfo paramInfo = ParameterInfo.fromMember(target);
|
|
int returnCount = isGetter ||
|
|
member is Procedure && member.function.returnType is! VoidType
|
|
? 1
|
|
: 0;
|
|
|
|
DynamicSelectorType selectorType = isGetter
|
|
? DynamicSelectorType.getter
|
|
: isSetter
|
|
? DynamicSelectorType.setter
|
|
: DynamicSelectorType.method;
|
|
bool calledDynamically = translator.dynamics
|
|
.maybeCalledDynamically(member, metadata, selectorType);
|
|
var selector = selectorInfo.putIfAbsent(
|
|
selectorId,
|
|
() => SelectorInfo(
|
|
translator,
|
|
selectorId,
|
|
selectorMetadata[selectorId].callCount,
|
|
selectorMetadata[selectorId].tornOff,
|
|
paramInfo,
|
|
returnCount,
|
|
calledDynamically));
|
|
selector.paramInfo.merge(paramInfo);
|
|
selector.returnCount = max(selector.returnCount, returnCount);
|
|
if (calledDynamically) {
|
|
if (isGetter) {
|
|
(dynamicGets[member.name.text] ??= []).add(selector);
|
|
} else if (isSetter) {
|
|
(dynamicSets[member.name.text] ??= []).add(selector);
|
|
} else {
|
|
(dynamicMethods[member.name.text] ??= []).add(selector);
|
|
}
|
|
}
|
|
return selector;
|
|
}
|
|
|
|
/// Returns a possibly null list of [SelectorInfo]s for a given dynamic
|
|
/// call.
|
|
Iterable<SelectorInfo>? selectorsForDynamicNode(Expression node) {
|
|
if (node is DynamicGet) {
|
|
return dynamicGets[node.name.text];
|
|
} else if (node is DynamicSet) {
|
|
return dynamicSets[node.name.text];
|
|
} else if (node is DynamicInvocation) {
|
|
return dynamicMethods[node.name.text];
|
|
} else {
|
|
throw 'Dynamic invocation of $node not supported';
|
|
}
|
|
}
|
|
|
|
void build() {
|
|
// Collect class/selector combinations
|
|
List<List<int>> selectorsInClass = [];
|
|
for (ClassInfo info in translator.classes) {
|
|
List<int> selectorIds = [];
|
|
ClassInfo? superInfo = info.superInfo;
|
|
if (superInfo != null) {
|
|
int superId = superInfo.classId;
|
|
selectorIds = List.of(selectorsInClass[superId]);
|
|
for (int selectorId in selectorIds) {
|
|
SelectorInfo selector = selectorInfo[selectorId]!;
|
|
selector.targets[info.classId] = selector.targets[superId]!;
|
|
}
|
|
}
|
|
|
|
SelectorInfo addMember(Reference reference) {
|
|
SelectorInfo selector = selectorForTarget(reference);
|
|
if (reference.asMember.isAbstract) {
|
|
selector.targets[info.classId] ??= reference;
|
|
} else {
|
|
selector.targets[info.classId] = reference;
|
|
}
|
|
selectorIds.add(selector.id);
|
|
return selector;
|
|
}
|
|
|
|
for (Member member
|
|
in info.cls?.members ?? translator.coreTypes.objectClass.members) {
|
|
if (member.isInstanceMember) {
|
|
if (member is Field) {
|
|
addMember(member.getterReference);
|
|
if (member.hasSetter) addMember(member.setterReference!);
|
|
} else if (member is Procedure) {
|
|
SelectorInfo method = addMember(member.reference);
|
|
if (method.tornOff) {
|
|
addMember(member.tearOffReference);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
selectorsInClass.add(selectorIds);
|
|
}
|
|
|
|
// Build lists of class IDs and count targets
|
|
for (SelectorInfo selector in selectorInfo.values) {
|
|
selector.classIds = selector.targets.keys
|
|
.where((id) => !(translator.classes[id].cls?.isAbstract ?? true))
|
|
.toList()
|
|
..sort();
|
|
Set<Reference> targets =
|
|
selector.targets.values.where((t) => !t.asMember.isAbstract).toSet();
|
|
selector.targetCount = targets.length;
|
|
if (targets.length == 1) selector.singularTarget = targets.single;
|
|
}
|
|
|
|
// Assign selector offsets
|
|
List<SelectorInfo> selectors = selectorInfo.values
|
|
.where((s) => s.alive)
|
|
.toList()
|
|
..sort((a, b) => b.sortWeight - a.sortWeight);
|
|
int firstAvailable = 0;
|
|
table = [];
|
|
bool first = true;
|
|
for (SelectorInfo selector in selectors) {
|
|
int offset = first ? 0 : firstAvailable - selector.classIds.first;
|
|
first = false;
|
|
bool fits;
|
|
do {
|
|
fits = true;
|
|
for (int classId in selector.classIds) {
|
|
int entry = offset + classId;
|
|
if (entry >= table.length) {
|
|
// Fits
|
|
break;
|
|
}
|
|
if (table[entry] != null) {
|
|
fits = false;
|
|
break;
|
|
}
|
|
}
|
|
if (!fits) offset++;
|
|
} while (!fits);
|
|
selector.offset = offset;
|
|
for (int classId in selector.classIds) {
|
|
int entry = offset + classId;
|
|
while (table.length <= entry) {
|
|
table.add(null);
|
|
}
|
|
assert(table[entry] == null);
|
|
table[entry] = selector.targets[classId];
|
|
}
|
|
while (firstAvailable < table.length && table[firstAvailable] != null) {
|
|
firstAvailable++;
|
|
}
|
|
}
|
|
|
|
wasmTable = m.addTable(w.RefType.func(nullable: true), table.length);
|
|
}
|
|
|
|
void output() {
|
|
for (int i = 0; i < table.length; i++) {
|
|
Reference? target = table[i];
|
|
if (target != null) {
|
|
w.BaseFunction? fun = translator.functions.getExistingFunction(target);
|
|
if (fun != null) {
|
|
wasmTable.setElement(i, fun);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|