| // 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; |
| |
| import '../common/elements.dart' show JCommonElements; |
| import '../elements/entities.dart'; |
| import '../js_backend/native_data.dart'; |
| import '../js_model/js_world.dart' show JClosedWorld; |
| import '../native/enqueue.dart' show NativeCodegenEnqueuer; |
| |
| import 'model.dart'; |
| |
| class NativeEmitter { |
| 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 = {}; |
| |
| // Caches the direct native subtypes of a native class. |
| Map<ClassEntity, List<ClassEntity>> directSubtypes = {}; |
| |
| // Caches the methods that have a native body. |
| Set<FunctionEntity> nativeMethods = {}; |
| |
| // Type metadata redirections, where the key is the class type data being |
| // redirected to and the value is the list of class type data being |
| // redirected. |
| final Map<ClassTypeData, List<ClassTypeData>> typeRedirections = {}; |
| |
| NativeEmitter(this._closedWorld, this._nativeCodegenEnqueuer); |
| |
| JCommonElements get _commonElements => _closedWorld.commonElements; |
| NativeData get _nativeData => _closedWorld.nativeData; |
| |
| /// 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, |
| ) { |
| 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 = []; |
| Set<Class> seen = {}; |
| |
| Class? objectClass; |
| Class? jsInterceptorClass; |
| Class? jsJavaScriptObjectClass; |
| |
| void walk(Class cls) { |
| if (cls.element == _commonElements.objectClass) { |
| objectClass = cls; |
| return; |
| } |
| if (cls.element == _commonElements.jsInterceptorClass) { |
| jsInterceptorClass = cls; |
| return; |
| } |
| // Native classes may inherit either `Interceptor` e.g. `JSBool` or |
| // `JavaScriptObject` e.g. `dart:html` classes. |
| if (cls.element == _commonElements.jsJavaScriptObjectClass) { |
| jsJavaScriptObjectClass = cls; |
| } |
| if (seen.contains(cls)) return; |
| seen.add(cls); |
| // Note: only the superclass of `Object` is expected to be null, but that |
| // would already be handled in line 102. |
| 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. |
| // We may still need to include type metadata for some unneeded classes. |
| |
| Set<Class> neededClasses = {}; |
| Set<Class> nonLeafClasses = {}; |
| |
| Map<Class, List<Class>> extensionPoints = computeExtensionPoints(preOrder); |
| |
| if (objectClass != null) 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)) { |
| // @staticInterop classes don't need to be emitted as they're purely |
| // static classes whose runtime type is an Interceptor type. |
| if (!_nativeData.isStaticInteropClass(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!); |
| } else if (!cls.typeData.isTriviallyChecked(_commonElements) || |
| cls.typeData.namedTypeVariables.isNotEmpty) { |
| // The class is not marked 'needed', but we still need it in the type |
| // metadata. |
| |
| // Redirect this class type data (and all class type data which would |
| // have redirected to this class type data) to its superclass. Because |
| // we have a post-order visit, this eventually causes all such native |
| // classes to redirect to their leaf interceptors. |
| List<ClassTypeData> redirectedClasses = |
| typeRedirections[cls.typeData] ?? []; |
| redirectedClasses.add(cls.typeData); |
| typeRedirections[cls.superclass!.typeData] = redirectedClasses; |
| typeRedirections.remove(cls.typeData); |
| } |
| } |
| |
| // Collect all the tags that map to each native class. |
| |
| Map<Class, Set<String>> leafTags = {}; |
| Map<Class, Set<String>> nonleafTags = {}; |
| |
| 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, () => {}).addAll(nativeTags); |
| } else { |
| Class? sufficingInterceptor = cls; |
| while (sufficingInterceptor != null && |
| !neededClasses.contains(sufficingInterceptor)) { |
| sufficingInterceptor = sufficingInterceptor.superclass; |
| } |
| if (sufficingInterceptor == null || |
| sufficingInterceptor == objectClass) { |
| sufficingInterceptor = jsInterceptorClass!; |
| } |
| leafTags.putIfAbsent(sufficingInterceptor, () => {}).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!); |
| if (jsJavaScriptObjectClass != null) { |
| fillNativeInfo(jsJavaScriptObjectClass!); |
| } |
| 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 = {}; |
| |
| for (Class cls in classes) { |
| if (cls.isNative) continue; |
| Class? nativeAncestor = nativeAncestorOf(cls); |
| if (nativeAncestor != null) { |
| map.putIfAbsent(nativeAncestor, () => []).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); |
| } |
| } |