blob: bf1f85820853539cba7a075f9ef4fc5ee6d5cad9 [file] [log] [blame]
// Copyright (c) 2020, 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 'package:kernel/ast.dart';
import 'utils.dart' show assertx, UnionFind;
import '../../metadata/procedure_attributes.dart';
import '../../metadata/table_selector.dart';
// Assigns dispatch table selector IDs to interface targets.
class TableSelectorAssigner {
final TableSelectorMetadata metadata = TableSelectorMetadata();
final Map<Class, Map<Name, int>> _getterMemberIds = {};
final Map<Class, Map<Name, int>> _methodOrSetterMemberIds = {};
final UnionFind _unionFind = UnionFind();
List<int> _selectorIdForMemberId;
TableSelectorAssigner(Component component) {
for (Library library in component.libraries) {
for (Class cls in library.classes) {
_memberIdsForClass(cls, getter: false);
_memberIdsForClass(cls, getter: true);
}
}
_selectorIdForMemberId = List(_unionFind.size);
// Assign all selector IDs eagerly to make them independent of how they are
// queried in later phases. This makes TFA test expectation files (which
// contain selector IDs) more stable under changes to how selector IDs are
// used in TFA phases.
for (Library library in component.libraries) {
for (Class cls in library.classes) {
for (Member member in cls.members) {
if (member.isInstanceMember) {
_selectorIdForMember(member, getter: false);
_selectorIdForMember(member, getter: true);
}
}
}
}
}
Map<Name, int> _memberIdsForClass(Class cls, {bool getter}) {
if (cls == null) return {};
final cache = getter ? _getterMemberIds : _methodOrSetterMemberIds;
// Already computed for this class?
Map<Name, int> memberIds = cache[cls];
if (memberIds != null) return memberIds;
// Merge maps from supertypes.
memberIds = Map.from(_memberIdsForClass(cls.superclass, getter: getter));
for (Supertype impl in cls.implementedTypes) {
_memberIdsForClass(impl.classNode, getter: getter).forEach((name, id) {
final int firstId = memberIds[name];
if (firstId == null) {
memberIds[name] = id;
} else if (firstId != id) {
_unionFind.union(firstId, id);
}
});
}
// Add declared instance members.
for (Member member in cls.members) {
if (member.isInstanceMember) {
bool addToMap;
if (member is Procedure) {
switch (member.kind) {
case ProcedureKind.Method:
addToMap = true;
break;
case ProcedureKind.Operator:
case ProcedureKind.Setter:
addToMap = !getter;
break;
case ProcedureKind.Getter:
addToMap = getter;
break;
default:
throw "Unexpected procedure kind '${member.kind}'";
}
} else if (member is Field) {
addToMap = getter || member.hasSetter;
} else {
throw "Unexpected member kind '${member.runtimeType}'";
}
if (addToMap && !memberIds.containsKey(member.name)) {
memberIds[member.name] = _unionFind.add();
}
}
}
return cache[cls] = memberIds;
}
int _selectorIdForMember(Member member, {bool getter}) {
final map = getter ? _getterMemberIds : _methodOrSetterMemberIds;
int memberId = map[member.enclosingClass][member.name];
if (memberId == null) {
assertx(member is Procedure &&
((identical(map, _getterMemberIds) &&
(member.kind == ProcedureKind.Operator ||
member.kind == ProcedureKind.Setter)) ||
identical(map, _methodOrSetterMemberIds) &&
member.kind == ProcedureKind.Getter) ||
member is Field &&
identical(map, _methodOrSetterMemberIds) &&
!member.hasSetter);
return ProcedureAttributesMetadata.kInvalidSelectorId;
}
memberId = _unionFind.find(memberId);
int selectorId = _selectorIdForMemberId[memberId];
if (selectorId == null) {
_selectorIdForMemberId[memberId] = selectorId = metadata.addSelector();
}
return selectorId;
}
int methodOrSetterSelectorId(Member member) {
return _selectorIdForMember(member, getter: false);
}
int getterSelectorId(Member member) {
return _selectorIdForMember(member, getter: true);
}
void registerMethodOrSetterCall(Member member, bool calledOnNull) {
final TableSelectorInfo selector =
metadata.selectors[methodOrSetterSelectorId(member)];
selector.callCount++;
selector.calledOnNull |= calledOnNull;
}
void registerGetterCall(Member member, bool calledOnNull) {
final TableSelectorInfo selector =
metadata.selectors[getterSelectorId(member)];
selector.callCount++;
selector.calledOnNull |= calledOnNull;
if (member is Procedure && member.kind == ProcedureKind.Method) {
final TableSelectorInfo methodSelector =
metadata.selectors[methodOrSetterSelectorId(member)];
methodSelector.tornOff = true;
}
}
}