blob: 766bfa0e76dd4911a98e5fc10e615d925f361759 [file] [log] [blame]
// Copyright (c) 2014, 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 '../common_elements.dart' show CommonElements, ElementEnvironment;
import '../elements/entities.dart';
import '../elements/types.dart';
import '../js_backend/native_data.dart' show NativeData;
import '../js_emitter/js_emitter.dart' show CodeEmitterTask, NativeEmitter;
import '../options.dart';
import '../universe/use.dart' show TypeUse;
import '../universe/world_impact.dart'
show WorldImpact, WorldImpactBuilder, WorldImpactBuilderImpl;
import 'behavior.dart';
import 'resolver.dart' show NativeClassFinder;
/// This could be an abstract class but we use it as a stub for the
/// dart_backend.
class NativeEnqueuer {
/// Called when a [type] has been instantiated natively.
void onInstantiatedType(InterfaceType type) {}
/// Initial entry point to native enqueuer.
WorldImpact processNativeClasses(Iterable<Uri> libraries) =>
const WorldImpact();
/// Registers the [nativeBehavior]. Adds the liveness of its instantiated
/// types to the world.
void registerNativeBehavior(
WorldImpactBuilder impactBuilder, NativeBehavior nativeBehavior, cause) {}
/// Returns whether native classes are being used.
bool get hasInstantiatedNativeClasses => false;
/// Emits a summary information using the [log] function.
void logSummary(void log(String message)) {}
}
abstract class NativeEnqueuerBase implements NativeEnqueuer {
final Set<ClassEntity> _registeredClasses = new Set<ClassEntity>();
final Set<ClassEntity> _unusedClasses = new Set<ClassEntity>();
@override
bool get hasInstantiatedNativeClasses => !_registeredClasses.isEmpty;
/// Log message reported if all native types are used.
String _allUsedMessage;
final CompilerOptions _options;
final ElementEnvironment _elementEnvironment;
final DartTypes _dartTypes;
final CommonElements _commonElements;
/// Subclasses of [NativeEnqueuerBase] are constructed by the backend.
NativeEnqueuerBase(this._options, this._elementEnvironment,
this._commonElements, this._dartTypes);
bool get enableLiveTypeAnalysis => _options.enableNativeLiveTypeAnalysis;
@override
void onInstantiatedType(InterfaceType type) {
if (_unusedClasses.remove(type.element)) {
_registeredClasses.add(type.element);
}
}
/// Register [classes] as natively instantiated in [impactBuilder].
void _registerTypeUses(
WorldImpactBuilder impactBuilder, Set<ClassEntity> classes, cause) {
for (ClassEntity cls in classes) {
if (!_unusedClasses.contains(cls)) {
// No need to add [classElement] to [impactBuilder]: it has already been
// instantiated and we don't track origins of native instantiations
// precisely.
continue;
}
impactBuilder.registerTypeUse(
new TypeUse.nativeInstantiation(_elementEnvironment.getRawType(cls)));
}
}
@override
void registerNativeBehavior(
WorldImpactBuilder impactBuilder, NativeBehavior nativeBehavior, cause) {
_processNativeBehavior(impactBuilder, nativeBehavior, cause);
}
void _processNativeBehavior(
WorldImpactBuilder impactBuilder, NativeBehavior behavior, cause) {
void registerInstantiation(InterfaceType type) {
impactBuilder.registerTypeUse(new TypeUse.nativeInstantiation(type));
}
int unusedBefore = _unusedClasses.length;
Set<ClassEntity> matchingClasses = new Set<ClassEntity>();
for (var type in behavior.typesInstantiated) {
if (type is SpecialType) {
if (type == SpecialType.JsObject) {
registerInstantiation(_commonElements.objectType);
}
continue;
}
if (type is InterfaceType) {
if (type == _commonElements.numType) {
registerInstantiation(_commonElements.doubleType);
registerInstantiation(_commonElements.intType);
} else if (type == _commonElements.intType ||
type == _commonElements.doubleType ||
type == _commonElements.stringType ||
type == _commonElements.nullType ||
type == _commonElements.boolType ||
_dartTypes.isSubtype(type,
_elementEnvironment.getRawType(_commonElements.jsArrayClass))) {
registerInstantiation(type);
} else if (_dartTypes.isSubtype(
type,
_elementEnvironment
.getRawType(_commonElements.jsJavaScriptObjectClass))) {
matchingClasses.add(type.element);
}
// TODO(johnniwinther): Improve spec string precision to handle type
// arguments and implements relations that preserve generics. Currently
// we cannot distinguish between `List`, `List<dynamic>`, and
// `List<int>` and take all to mean `List<E>`; in effect not including
// any native subclasses of generic classes.
// TODO(johnniwinther,sra): Find and replace uses of `List` with the
// actual implementation classes such as `JSArray` et al.
matchingClasses
.addAll(_findUnusedClassesMatching((ClassEntity nativeClass) {
InterfaceType nativeType =
_elementEnvironment.getRawType(nativeClass);
InterfaceType specType = _elementEnvironment.getRawType(type.element);
return _dartTypes.isSubtype(nativeType, specType);
}));
} else if (type.isDynamic) {
matchingClasses.addAll(_unusedClasses);
} else {
assert(type is VoidType, '$type was ${type.runtimeType}');
}
}
if (matchingClasses.isNotEmpty && _registeredClasses.isEmpty) {
matchingClasses.addAll(_onFirstNativeClass(impactBuilder));
}
_registerTypeUses(impactBuilder, matchingClasses, cause);
// Give an info so that library developers can compile with -v to find why
// all the native classes are included.
if (unusedBefore > 0 && unusedBefore == matchingClasses.length) {
_allUsedMessage ??= 'All native types marked as used due to $cause.';
}
}
Iterable<ClassEntity> _findUnusedClassesMatching(
bool predicate(ClassEntity classElement)) {
return _unusedClasses.where(predicate);
}
Iterable<ClassEntity> _onFirstNativeClass(WorldImpactBuilder impactBuilder) {
return _findNativeExceptions();
}
Iterable<ClassEntity> _findNativeExceptions() {
return _findUnusedClassesMatching((ClassEntity classElement) {
// TODO(sra): Annotate exception classes in dart:html.
String name = classElement.name;
if (name.contains('Exception')) return true;
if (name.contains('Error')) return true;
return false;
});
}
@override
void logSummary(void log(String message)) {
if (_allUsedMessage != null) {
log(_allUsedMessage);
}
}
}
class NativeResolutionEnqueuer extends NativeEnqueuerBase {
final NativeClassFinder _nativeClassFinder;
/// The set of all native classes. Each native class is in [nativeClasses]
/// and exactly one of [unusedClasses] and [registeredClasses].
final Set<ClassEntity> _nativeClasses = new Set<ClassEntity>();
NativeResolutionEnqueuer(
CompilerOptions options,
ElementEnvironment elementEnvironment,
CommonElements commonElements,
DartTypes dartTypes,
this._nativeClassFinder)
: super(options, elementEnvironment, commonElements, dartTypes);
Iterable<ClassEntity> get nativeClassesForTesting => _nativeClasses;
Iterable<ClassEntity> get registeredClassesForTesting => _registeredClasses;
Iterable<ClassEntity> get liveNativeClasses => _registeredClasses;
@override
WorldImpact processNativeClasses(Iterable<Uri> libraries) {
WorldImpactBuilderImpl impactBuilder = new WorldImpactBuilderImpl();
Iterable<ClassEntity> nativeClasses =
_nativeClassFinder.computeNativeClasses(libraries);
_nativeClasses.addAll(nativeClasses);
_unusedClasses.addAll(nativeClasses);
if (!enableLiveTypeAnalysis) {
_registerTypeUses(impactBuilder, _nativeClasses, 'forced');
}
return impactBuilder;
}
@override
void logSummary(void log(String message)) {
super.logSummary(log);
log('Resolved ${_registeredClasses.length} native elements used, '
'${_unusedClasses.length} native elements dead.');
}
}
class NativeCodegenEnqueuer extends NativeEnqueuerBase {
final CodeEmitterTask _emitter;
final Iterable<ClassEntity> _nativeClasses;
final NativeData _nativeData;
final Set<ClassEntity> _doneAddSubtypes = new Set<ClassEntity>();
NativeCodegenEnqueuer(
CompilerOptions options,
ElementEnvironment elementEnvironment,
CommonElements commonElements,
DartTypes dartTypes,
this._emitter,
this._nativeClasses,
this._nativeData)
: super(options, elementEnvironment, commonElements, dartTypes);
@override
WorldImpact processNativeClasses(Iterable<Uri> libraries) {
WorldImpactBuilderImpl impactBuilder = new WorldImpactBuilderImpl();
_unusedClasses.addAll(_nativeClasses);
if (!enableLiveTypeAnalysis) {
_registerTypeUses(impactBuilder, _nativeClasses, 'forced');
}
// HACK HACK - add all the resolved classes.
Set<ClassEntity> matchingClasses = new Set<ClassEntity>();
for (ClassEntity classElement in _nativeClasses) {
if (_unusedClasses.contains(classElement)) {
matchingClasses.add(classElement);
}
}
if (matchingClasses.isNotEmpty && _registeredClasses.isEmpty) {
matchingClasses.addAll(_onFirstNativeClass(impactBuilder));
}
_registerTypeUses(impactBuilder, matchingClasses, 'was resolved');
return impactBuilder;
}
@override
void _registerTypeUses(
WorldImpactBuilder impactBuilder, Set<ClassEntity> classes, cause) {
super._registerTypeUses(impactBuilder, classes, cause);
for (ClassEntity classElement in classes) {
// Add the information that this class is a subtype of its supertypes. The
// code emitter and the ssa builder use that information.
_addSubtypes(classElement, _emitter.nativeEmitter);
}
}
void _addSubtypes(ClassEntity cls, NativeEmitter emitter) {
if (!_nativeData.isNativeClass(cls)) return;
if (_doneAddSubtypes.contains(cls)) return;
_doneAddSubtypes.add(cls);
// Walk the superclass chain since classes on the superclass chain might not
// be instantiated (abstract or simply unused).
_addSubtypes(_elementEnvironment.getSuperClass(cls), emitter);
_elementEnvironment.forEachSupertype(cls, (InterfaceType type) {
List<ClassEntity> subtypes =
emitter.subtypes[type.element] ??= <ClassEntity>[];
subtypes.add(cls);
});
// Skip through all the mixin applications in the super class
// chain. That way, the direct subtypes set only contain the
// natives classes.
ClassEntity superclass = _elementEnvironment.getSuperClass(cls);
while (superclass != null &&
_elementEnvironment.isMixinApplication(superclass)) {
assert(!_nativeData.isNativeClass(superclass));
superclass = _elementEnvironment.getSuperClass(superclass);
}
List<ClassEntity> directSubtypes =
emitter.directSubtypes[superclass] ??= <ClassEntity>[];
directSubtypes.add(cls);
}
@override
void logSummary(void log(String message)) {
super.logSummary(log);
log('Compiled ${_registeredClasses.length} native classes, '
'${_unusedClasses.length} native classes omitted.');
}
}