blob: b63dae70d97894325973d1c3507add6b6e191ed8 [file] [log] [blame]
// Copyright (c) 2017, 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.
library js_backend.interceptor_data;
import '../common/names.dart' show Identifiers;
import '../common_elements.dart'
show CommonElements, KCommonElements, KElementEnvironment;
import '../elements/entities.dart';
import '../elements/types.dart';
import '../inferrer/abstract_value_domain.dart';
import '../js/js.dart' as jsAst;
import '../serialization/serialization.dart';
import '../universe/selector.dart';
import '../world.dart' show JClosedWorld;
import 'namer.dart' show ModularNamer, suffixForGetInterceptor;
import 'native_data.dart';
abstract class InterceptorData {
/// Deserializes a [InterceptorData] object from [source].
factory InterceptorData.readFromDataSource(
DataSource source,
NativeData nativeData,
CommonElements commonElements) = InterceptorDataImpl.readFromDataSource;
/// Serializes this [InterceptorData] to [sink].
void writeToDataSink(DataSink sink);
/// Returns `true` if [cls] is an intercepted class.
bool isInterceptedClass(ClassEntity element);
bool isInterceptedMethod(MemberEntity element);
bool fieldHasInterceptedGetter(FieldEntity element);
bool fieldHasInterceptedSetter(FieldEntity element);
bool isInterceptedName(String name);
bool isInterceptedSelector(Selector selector);
/// Returns `true` iff [selector] matches an element defined in a class mixed
/// into an intercepted class.
///
/// These selectors are not eligible for the 'dummy explicit receiver'
/// optimization.
bool isInterceptedMixinSelector(
Selector selector, AbstractValue mask, JClosedWorld closedWorld);
/// Set of classes whose methods are intercepted.
Iterable<ClassEntity> get interceptedClasses;
bool isMixedIntoInterceptedClass(ClassEntity element);
/// Returns a set of interceptor classes that contain a member named [name]
///
/// Returns an empty set if there is no class. Do not modify the returned set.
Set<ClassEntity> getInterceptedClassesOn(
String name, JClosedWorld closedWorld);
/// Whether the compiler can use the native `instanceof` check to test for
/// instances of [type]. This is true for types that are not used as mixins or
/// interfaces.
bool mayGenerateInstanceofCheck(DartType type, JClosedWorld closedWorld);
}
abstract class InterceptorDataBuilder {
void addInterceptors(ClassEntity cls);
void addInterceptorsForNativeClassMembers(ClassEntity cls);
InterceptorData close();
}
class InterceptorDataImpl implements InterceptorData {
/// Tag used for identifying serialized [InterceptorData] objects in a
/// debugging data stream.
static const String tag = 'interceptor-data';
final NativeBasicData _nativeData;
final CommonElements _commonElements;
/// The members of instantiated interceptor classes: maps a member name to the
/// list of members that have that name. This map is used by the codegen to
/// know whether a send must be intercepted or not.
final Map<String, Set<MemberEntity>> interceptedMembers;
@override
final Set<ClassEntity> interceptedClasses;
/// Set of classes used as mixins on intercepted (native and primitive)
/// classes. Methods on these classes might also be mixed in to regular Dart
/// (unintercepted) classes.
final Set<ClassEntity> classesMixedIntoInterceptedClasses;
/// The members of mixin classes that are mixed into an instantiated
/// interceptor class. This is a cached subset of [_interceptedElements].
///
/// Mixin methods are not specialized for the class they are mixed into.
/// Methods mixed into intercepted classes thus always make use of the
/// explicit receiver argument, even when mixed into non-interceptor classes.
///
/// These members must be invoked with a correct explicit receiver even when
/// the receiver is not an intercepted class.
final Map<String, Set<MemberEntity>> _interceptedMixinElements =
new Map<String, Set<MemberEntity>>();
final Map<String, Set<ClassEntity>> _interceptedClassesCache =
new Map<String, Set<ClassEntity>>();
final Set<ClassEntity> _noClasses = new Set<ClassEntity>();
InterceptorDataImpl(
this._nativeData,
this._commonElements,
this.interceptedMembers,
this.interceptedClasses,
this.classesMixedIntoInterceptedClasses);
factory InterceptorDataImpl.readFromDataSource(
DataSource source, NativeData nativeData, CommonElements commonElements) {
source.begin(tag);
int interceptedMembersCount = source.readInt();
Map<String, Set<MemberEntity>> interceptedMembers = {};
for (int i = 0; i < interceptedMembersCount; i++) {
String name = source.readString();
Set<MemberEntity> members = source.readMembers().toSet();
interceptedMembers[name] = members;
}
Set<ClassEntity> interceptedClasses = source.readClasses().toSet();
Set<ClassEntity> classesMixedIntoInterceptedClasses =
source.readClasses().toSet();
source.end(tag);
return new InterceptorDataImpl(
nativeData,
commonElements,
interceptedMembers,
interceptedClasses,
classesMixedIntoInterceptedClasses);
}
@override
void writeToDataSink(DataSink sink) {
sink.begin(tag);
sink.writeInt(interceptedMembers.length);
interceptedMembers.forEach((String name, Set<MemberEntity> members) {
sink.writeString(name);
sink.writeMembers(members);
});
sink.writeClasses(interceptedClasses);
sink.writeClasses(classesMixedIntoInterceptedClasses);
sink.end(tag);
}
@override
bool isInterceptedMethod(MemberEntity element) {
if (!element.isInstanceMember) return false;
// TODO(johnniwinther): Avoid this hack.
if (element is ConstructorBodyEntity) {
return _nativeData.isNativeOrExtendsNative(element.enclosingClass);
}
return interceptedMembers[element.name] != null;
}
@override
bool fieldHasInterceptedGetter(FieldEntity element) {
return interceptedMembers[element.name] != null;
}
@override
bool fieldHasInterceptedSetter(FieldEntity element) {
return interceptedMembers[element.name] != null;
}
@override
bool isInterceptedName(String name) {
return interceptedMembers[name] != null;
}
@override
bool isInterceptedSelector(Selector selector) {
return interceptedMembers[selector.name] != null;
}
@override
bool isInterceptedMixinSelector(
Selector selector, AbstractValue mask, JClosedWorld closedWorld) {
Set<MemberEntity> elements =
_interceptedMixinElements.putIfAbsent(selector.name, () {
Set<MemberEntity> elements = interceptedMembers[selector.name];
if (elements == null) return null;
return elements
.where((element) => classesMixedIntoInterceptedClasses
.contains(element.enclosingClass))
.toSet();
});
if (elements == null) return false;
if (elements.isEmpty) return false;
return elements.any((element) {
return selector.applies(element) &&
(mask == null ||
closedWorld.abstractValueDomain
.isTargetingMember(mask, element, selector.memberName)
.isPotentiallyTrue);
});
}
/// True if the given class is an internal class used for type inference
/// and never exists at runtime.
bool _isCompileTimeOnlyClass(ClassEntity class_) {
return class_ == _commonElements.jsPositiveIntClass ||
class_ == _commonElements.jsUInt32Class ||
class_ == _commonElements.jsUInt31Class ||
class_ == _commonElements.jsFixedArrayClass ||
class_ == _commonElements.jsUnmodifiableArrayClass ||
class_ == _commonElements.jsMutableArrayClass ||
class_ == _commonElements.jsExtendableArrayClass;
}
/// Returns a set of interceptor classes that contain a member named [name]
///
/// Returns an empty set if there is no class. Do not modify the returned set.
@override
Set<ClassEntity> getInterceptedClassesOn(
String name, JClosedWorld closedWorld) {
Set<MemberEntity> intercepted = interceptedMembers[name];
if (intercepted == null) return _noClasses;
return _interceptedClassesCache.putIfAbsent(name, () {
// Populate the cache by running through all the elements and
// determine if the given selector applies to them.
Set<ClassEntity> result = new Set<ClassEntity>();
for (MemberEntity element in intercepted) {
ClassEntity classElement = element.enclosingClass;
if (_isCompileTimeOnlyClass(classElement)) continue;
if (_nativeData.isNativeOrExtendsNative(classElement) ||
interceptedClasses.contains(classElement)) {
result.add(classElement);
}
if (classesMixedIntoInterceptedClasses.contains(classElement)) {
Set<ClassEntity> nativeSubclasses =
nativeSubclassesOfMixin(classElement, closedWorld);
if (nativeSubclasses != null) result.addAll(nativeSubclasses);
}
}
return result;
});
}
Set<ClassEntity> nativeSubclassesOfMixin(
ClassEntity mixin, JClosedWorld closedWorld) {
Iterable<ClassEntity> uses = closedWorld.mixinUsesOf(mixin);
Set<ClassEntity> result = null;
for (ClassEntity use in uses) {
closedWorld.classHierarchy.forEachStrictSubclassOf(use,
(ClassEntity subclass) {
if (_nativeData.isNativeOrExtendsNative(subclass)) {
if (result == null) result = new Set<ClassEntity>();
result.add(subclass);
}
});
}
return result;
}
@override
bool isInterceptedClass(ClassEntity element) {
if (element == null) return false;
if (_nativeData.isNativeOrExtendsNative(element)) return true;
if (interceptedClasses.contains(element)) return true;
if (classesMixedIntoInterceptedClasses.contains(element)) return true;
return false;
}
@override
bool isMixedIntoInterceptedClass(ClassEntity element) =>
classesMixedIntoInterceptedClasses.contains(element);
@override
bool mayGenerateInstanceofCheck(DartType type, JClosedWorld closedWorld) {
// We can use an instanceof check for raw types that have no subclass that
// is mixed-in or in an implements clause.
if (!type.treatAsRaw) return false;
if (type.isFutureOr) return false;
InterfaceType interfaceType = type;
ClassEntity classElement = interfaceType.element;
if (isInterceptedClass(classElement)) return false;
return closedWorld.classHierarchy.hasOnlySubclasses(classElement);
}
}
class InterceptorDataBuilderImpl implements InterceptorDataBuilder {
final NativeBasicData _nativeData;
final KElementEnvironment _elementEnvironment;
final KCommonElements _commonElements;
/// The members of instantiated interceptor classes: maps a member name to the
/// list of members that have that name. This map is used by the codegen to
/// know whether a send must be intercepted or not.
final Map<String, Set<MemberEntity>> _interceptedElements =
<String, Set<MemberEntity>>{};
/// Set of classes whose methods are intercepted.
final Set<ClassEntity> _interceptedClasses = new Set<ClassEntity>();
/// Set of classes used as mixins on intercepted (native and primitive)
/// classes. Methods on these classes might also be mixed in to regular Dart
/// (unintercepted) classes.
final Set<ClassEntity> _classesMixedIntoInterceptedClasses =
new Set<ClassEntity>();
InterceptorDataBuilderImpl(
this._nativeData, this._elementEnvironment, this._commonElements);
@override
InterceptorData close() {
return new InterceptorDataImpl(
_nativeData,
_commonElements,
_interceptedElements,
_interceptedClasses,
_classesMixedIntoInterceptedClasses);
}
@override
void addInterceptorsForNativeClassMembers(ClassEntity cls) {
_elementEnvironment.forEachClassMember(cls,
(ClassEntity cls, MemberEntity member) {
if (member.name == Identifiers.call) return;
// All methods on [Object] are shadowed by [Interceptor].
if (cls == _commonElements.objectClass) return;
Set<MemberEntity> set =
_interceptedElements[member.name] ??= new Set<MemberEntity>();
set.add(member);
});
// Walk superclass chain to find mixins.
_elementEnvironment.forEachMixin(cls, (ClassEntity mixin) {
_classesMixedIntoInterceptedClasses.add(mixin);
});
}
@override
void addInterceptors(ClassEntity cls) {
if (_interceptedClasses.add(cls)) {
_elementEnvironment.forEachClassMember(cls,
(ClassEntity cls, MemberEntity member) {
// All methods on [Object] are shadowed by [Interceptor].
if (cls == _commonElements.objectClass) return;
Set<MemberEntity> set =
_interceptedElements[member.name] ??= new Set<MemberEntity>();
set.add(member);
});
}
_interceptedClasses.add(_commonElements.jsInterceptorClass);
}
}
class OneShotInterceptorData {
final InterceptorData _interceptorData;
final CommonElements _commonElements;
final NativeData _nativeData;
OneShotInterceptorData(
this._interceptorData, this._commonElements, this._nativeData);
Iterable<OneShotInterceptor> get oneShotInterceptors {
List<OneShotInterceptor> interceptors = [];
for (var map in _oneShotInterceptors.values) {
interceptors.addAll(map.values);
}
return interceptors;
}
Map<Selector, Map<String, OneShotInterceptor>> _oneShotInterceptors = {};
/// A set of specialized versions of the [getInterceptorMethod].
///
/// Since [getInterceptorMethod] is a hot method at runtime, we're always
/// specializing it based on the incoming type. The keys in the map are the
/// names of these specialized versions. Note that the generic version that
/// contains all possible type checks is also stored in this map.
Iterable<SpecializedGetInterceptor> get specializedGetInterceptors =>
_specializedGetInterceptors.values;
Map<String, SpecializedGetInterceptor> _specializedGetInterceptors = {};
jsAst.Name registerOneShotInterceptor(
Selector selector, ModularNamer namer, JClosedWorld closedWorld) {
selector = selector.toNormalized();
Set<ClassEntity> classes =
_interceptorData.getInterceptedClassesOn(selector.name, closedWorld);
String key = suffixForGetInterceptor(_commonElements, _nativeData, classes);
Map<String, OneShotInterceptor> interceptors =
_oneShotInterceptors[selector] ??= {};
OneShotInterceptor interceptor = interceptors.putIfAbsent(
key, () => new OneShotInterceptor(key, selector));
interceptor.classes.addAll(classes);
registerSpecializedGetInterceptor(classes, namer);
return namer.nameForGetOneShotInterceptor(selector, classes);
}
void registerSpecializedGetInterceptor(
Set<ClassEntity> classes, ModularNamer namer) {
if (classes.contains(_commonElements.jsInterceptorClass)) {
// We can't use a specialized [getInterceptorMethod], so we make
// sure we emit the one with all checks.
classes = _interceptorData.interceptedClasses;
}
String key = suffixForGetInterceptor(_commonElements, _nativeData, classes);
SpecializedGetInterceptor interceptor =
_specializedGetInterceptors[key] ??= new SpecializedGetInterceptor(key);
interceptor.classes.addAll(classes);
}
}
class OneShotInterceptor {
final Selector selector;
final String key;
final Set<ClassEntity> classes = {};
OneShotInterceptor(this.key, this.selector);
@override
String toString() =>
'OneShotInterceptor(selector=$selector,key=$key,classes=$classes)';
}
class SpecializedGetInterceptor {
final String key;
final Set<ClassEntity> classes = {};
SpecializedGetInterceptor(this.key);
@override
String toString() => 'SpecializedGetInterceptor(key=$key,classes=$classes)';
}