blob: 7be3f0d2140193284b7aabf8e20e689d02fca50e [file] [log] [blame]
// Copyright (c) 2012, 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 dart2js.js_emitter.native_emitter;
import '../common.dart';
import '../common_elements.dart' show JCommonElements, JElementEnvironment;
import '../elements/types.dart' show DartType, FunctionType;
import '../elements/entities.dart';
import '../js/js.dart' as jsAst;
import '../js/js.dart' show js;
import '../js_backend/interceptor_data.dart';
import '../js_backend/native_data.dart';
import '../native/enqueue.dart' show NativeCodegenEnqueuer;
import '../world.dart' show JClosedWorld;
import 'code_emitter_task.dart' show CodeEmitterTask;
import 'model.dart';
class NativeEmitter {
final CodeEmitterTask _emitterTask;
final JClosedWorld _closedWorld;
final NativeCodegenEnqueuer _nativeCodegenEnqueuer;
// Whether the application contains native classes.
bool hasNativeClasses = false;
// Caches the native subtypes of a native class.
Map<ClassEntity, List<ClassEntity>> subtypes =
<ClassEntity, List<ClassEntity>>{};
// Caches the direct native subtypes of a native class.
Map<ClassEntity, List<ClassEntity>> directSubtypes =
<ClassEntity, List<ClassEntity>>{};
// Caches the methods that have a native body.
Set<FunctionEntity> nativeMethods = new Set<FunctionEntity>();
NativeEmitter(
this._emitterTask, this._closedWorld, this._nativeCodegenEnqueuer);
JCommonElements get _commonElements => _closedWorld.commonElements;
JElementEnvironment get _elementEnvironment =>
_closedWorld.elementEnvironment;
NativeData get _nativeData => _closedWorld.nativeData;
InterceptorData get _interceptorData => _closedWorld.interceptorData;
/// Prepares native classes for emission. Returns the unneeded classes.
///
/// Removes trivial classes (that can be represented by a super type) and
/// generates properties that have to be added to classes (native or not).
///
/// Updates the `nativeLeafTags`, `nativeNonLeafTags` and `nativeExtensions`
/// fields of the given classes. This data must be emitted with the
/// corresponding classes.
///
/// The interceptors are filtered to avoid emitting trivial interceptors. For
/// example, if the program contains no code that can distinguish between the
/// numerous subclasses of `Element` then we can pretend that `Element` is a
/// leaf class, and all instances of subclasses of `Element` are instances of
/// `Element`.
///
/// There is also a performance benefit (in addition to the obvious code size
/// benefit), due to how [getNativeInterceptor] works. Finding the
/// interceptor of a leaf class in the hierarchy is more efficient that a
/// non-leaf, so it improves performance when more classes can be treated as
/// leaves.
///
/// [classes] contains native classes, mixin applications, and user subclasses
/// of native classes.
///
/// [interceptorClassesNeededByConstants] contains the interceptors that are
/// referenced by constants.
///
/// [classesModifiedByEmitRTISupport] contains the list of classes that must
/// exist, because runtime-type support adds information to the class.
Set<Class> prepareNativeClasses(
List<Class> classes,
Set<ClassEntity> interceptorClassesNeededByConstants,
Iterable<ClassEntity> classesNeededForRti) {
assert(classes.every((Class cls) => cls != null));
hasNativeClasses = classes.isNotEmpty;
// Compute a pre-order traversal of the subclass forest. We actually want a
// post-order traversal but it is easier to compute the pre-order and use it
// in reverse.
List<Class> preOrder = <Class>[];
Set<Class> seen = new Set<Class>();
Class objectClass = null;
Class jsInterceptorClass = null;
void walk(Class cls) {
if (cls.element == _commonElements.objectClass) {
objectClass = cls;
return;
}
if (cls.element == _commonElements.jsInterceptorClass) {
jsInterceptorClass = cls;
return;
}
if (seen.contains(cls)) return;
seen.add(cls);
walk(cls.superclass);
preOrder.add(cls);
}
classes.forEach(walk);
// Find which classes are needed and which are non-leaf classes. Any class
// that is not needed can be treated as a leaf class equivalent to some
// needed class.
Set<Class> neededClasses = new Set<Class>();
Set<Class> nonLeafClasses = new Set<Class>();
Map<Class, List<Class>> extensionPoints = computeExtensionPoints(preOrder);
neededClasses.add(objectClass);
for (Class cls in preOrder.reversed) {
ClassEntity classElement = cls.element;
// Post-order traversal ensures we visit the subclasses before their
// superclass. This makes it easy to tell if a class is needed because a
// subclass is needed.
bool needed = false;
if (!cls.isNative) {
// Mixin applications (native+mixin) are non-native, so [classElement]
// has already been emitted as a regular class. Mark [classElement] as
// 'needed' to ensure the native superclass is needed.
needed = true;
} else if (!isTrivialClass(cls)) {
needed = true;
} else if (interceptorClassesNeededByConstants.contains(classElement)) {
needed = true;
} else if (classesNeededForRti.contains(classElement)) {
needed = true;
} else if (extensionPoints.containsKey(cls)) {
needed = true;
}
if (_nativeData.isJsInteropClass(classElement)) {
needed = true; // TODO(jacobr): we don't need all interop classes.
} else if (cls.isNative &&
_nativeData.hasNativeTagsForcedNonLeaf(classElement)) {
needed = true;
nonLeafClasses.add(cls);
}
if (needed || neededClasses.contains(cls)) {
neededClasses.add(cls);
neededClasses.add(cls.superclass);
nonLeafClasses.add(cls.superclass);
}
}
// Collect all the tags that map to each native class.
Map<Class, Set<String>> leafTags = new Map<Class, Set<String>>();
Map<Class, Set<String>> nonleafTags = new Map<Class, Set<String>>();
for (Class cls in classes) {
if (!cls.isNative) continue;
ClassEntity element = cls.element;
if (_nativeData.isJsInteropClass(element)) continue;
List<String> nativeTags = _nativeData.getNativeTagsOfClass(cls.element);
if (nonLeafClasses.contains(cls) || extensionPoints.containsKey(cls)) {
nonleafTags
.putIfAbsent(cls, () => new Set<String>())
.addAll(nativeTags);
} else {
Class sufficingInterceptor = cls;
while (!neededClasses.contains(sufficingInterceptor)) {
sufficingInterceptor = sufficingInterceptor.superclass;
}
if (sufficingInterceptor == objectClass) {
sufficingInterceptor = jsInterceptorClass;
}
leafTags
.putIfAbsent(sufficingInterceptor, () => new Set<String>())
.addAll(nativeTags);
}
}
void fillNativeInfo(Class cls) {
assert(cls.nativeLeafTags == null &&
cls.nativeNonLeafTags == null &&
cls.nativeExtensions == null);
if (leafTags[cls] != null) {
cls.nativeLeafTags = leafTags[cls].toList(growable: false);
}
if (nonleafTags[cls] != null) {
cls.nativeNonLeafTags = nonleafTags[cls].toList(growable: false);
}
cls.nativeExtensions = extensionPoints[cls];
}
// Add properties containing the information needed to construct maps used
// by getNativeInterceptor and custom elements.
if (_nativeCodegenEnqueuer.hasInstantiatedNativeClasses) {
fillNativeInfo(jsInterceptorClass);
for (Class cls in classes) {
if (!cls.isNative || neededClasses.contains(cls)) {
fillNativeInfo(cls);
}
}
}
// TODO(sra): Issue #13731- this is commented out as part of custom
// element constructor work.
// (floitsch: was run on every native class.)
//assert(!classElement.hasBackendMembers);
return classes
.where((Class cls) => cls.isNative && !neededClasses.contains(cls))
.toSet();
}
/// Computes the native classes that are extended (subclassed) by non-native
/// classes and the set non-mative classes that extend them. (A List is used
/// instead of a Set for out stability).
Map<Class, List<Class>> computeExtensionPoints(List<Class> classes) {
Class nativeSuperclassOf(Class cls) {
if (cls == null) return null;
if (cls.isNative) return cls;
return nativeSuperclassOf(cls.superclass);
}
Class nativeAncestorOf(Class cls) {
return nativeSuperclassOf(cls.superclass);
}
Map<Class, List<Class>> map = new Map<Class, List<Class>>();
for (Class cls in classes) {
if (cls.isNative) continue;
Class nativeAncestor = nativeAncestorOf(cls);
if (nativeAncestor != null) {
map.putIfAbsent(nativeAncestor, () => <Class>[]).add(cls);
}
}
return map;
}
bool isTrivialClass(Class cls) {
bool needsAccessor(Field field) {
return field.needsGetter ||
field.needsUncheckedSetter ||
field.needsCheckedSetter;
}
return cls.methods.isEmpty &&
cls.isChecks.isEmpty &&
cls.callStubs.isEmpty &&
!cls.superclass.isSimpleMixinApplication &&
!cls.fields.any(needsAccessor);
}
void potentiallyConvertDartClosuresToJs(List<jsAst.Statement> statements,
FunctionEntity member, List<jsAst.Parameter> stubParameters) {
jsAst.Expression closureConverter;
_elementEnvironment.forEachParameter(member,
(DartType type, String name, _) {
// If [name] is not in [stubParameters], then the parameter is an optional
// parameter that was not provided for this stub.
for (jsAst.Parameter stubParameter in stubParameters) {
if (stubParameter.name == name) {
type = type.unaliased;
if (type.isFunctionType) {
closureConverter ??= _emitterTask.emitter
.staticFunctionAccess(_commonElements.closureConverter);
// The parameter type is a function type either directly or through
// typedef(s).
FunctionType functionType = type;
int arity = functionType.parameterTypes.length;
statements.add(js
.statement('# = #(#, $arity)', [name, closureConverter, name]));
break;
}
}
}
});
}
List<jsAst.Statement> generateParameterStubStatements(
FunctionEntity member,
bool isInterceptedMethod,
jsAst.Name invocationName,
List<jsAst.Parameter> stubParameters,
List<jsAst.Expression> argumentsBuffer,
int indexOfLastOptionalArgumentInParameters) {
// The target JS function may check arguments.length so we need to
// make sure not to pass any unspecified optional arguments to it.
// For example, for the following Dart method:
// foo({x, y, z});
// The call:
// foo(y: 1)
// must be turned into a JS call to:
// foo(null, y).
List<jsAst.Statement> statements = <jsAst.Statement>[];
potentiallyConvertDartClosuresToJs(statements, member, stubParameters);
String target;
jsAst.Expression receiver;
List<jsAst.Expression> arguments;
assert(nativeMethods.contains(member), failedAt(member));
// When calling a JS method, we call it with the native name, and only the
// arguments up until the last one provided.
target = _nativeData.getFixedBackendName(member);
if (isInterceptedMethod) {
receiver = argumentsBuffer[0];
arguments = argumentsBuffer.sublist(
1, indexOfLastOptionalArgumentInParameters + 1);
} else {
// Native methods that are not intercepted must be static.
assert(member.isStatic, failedAt(member));
arguments = argumentsBuffer.sublist(
0, indexOfLastOptionalArgumentInParameters + 1);
if (_nativeData.isJsInteropMember(member)) {
// fixedBackendPath is allowed to have the form foo.bar.baz for
// interop. This template is uncached to avoid possibly running out of
// memory when Dart2Js is run in server mode. In reality the risk of
// caching these templates causing an issue is very low as each class
// and library that uses typed JavaScript interop will create only 1
// unique template.
receiver = js
.uncachedExpressionTemplate(
_nativeData.getFixedBackendMethodPath(member))
.instantiate([]);
} else {
receiver = js('this');
}
}
statements
.add(js.statement('return #.#(#)', [receiver, target, arguments]));
return statements;
}
bool isSupertypeOfNativeClass(ClassEntity element) {
if (_interceptorData.isMixedIntoInterceptedClass(element)) {
return true;
}
return subtypes[element] != null;
}
bool requiresNativeIsCheck(ClassEntity element) {
// TODO(sra): Remove this function. It determines if a native type may
// satisfy a check against [element], in which case an interceptor must be
// used. We should also use an interceptor if the check can't be satisfied
// by a native class in case we get a native instance that tries to spoof
// the type info. i.e the criteria for whether or not to use an interceptor
// is whether the receiver can be native, not the type of the test.
ClassEntity cls = element;
if (_nativeData.isNativeOrExtendsNative(cls)) return true;
return isSupertypeOfNativeClass(element);
}
}