Chromium Code Reviews| Index: sdk/lib/_internal/compiler/implementation/native_handler.dart |
| diff --git a/sdk/lib/_internal/compiler/implementation/native_handler.dart b/sdk/lib/_internal/compiler/implementation/native_handler.dart |
| index db579d390c26495a605b23c06d6d02738e870184..874e8296c0ad9b1ca09ef483e462ccf75f7d1441 100644 |
| --- a/sdk/lib/_internal/compiler/implementation/native_handler.dart |
| +++ b/sdk/lib/_internal/compiler/implementation/native_handler.dart |
| @@ -8,70 +8,331 @@ import 'dart:uri'; |
| import 'dart2jslib.dart' hide SourceString; |
| import 'elements/elements.dart'; |
| import 'js_backend/js_backend.dart'; |
| +import 'resolution/resolution.dart' show ResolverVisitor; |
| import 'scanner/scannerlib.dart'; |
| import 'ssa/ssa.dart'; |
| import 'tree/tree.dart'; |
| import 'util/util.dart'; |
| -void processNativeClasses(Enqueuer world, |
| - CodeEmitterTask emitter, |
| - Collection<LibraryElement> libraries) { |
| - for (LibraryElement library in libraries) { |
| - processNativeClassesInLibrary(world, emitter, library); |
| - } |
| + |
| +/// This class is a temporary work-around until we get a more powerful DartType. |
| +class SpecialType { |
| + final String name; |
| + const SpecialType._(this.name); |
| + |
| + /// The type Object, but no subtypes: |
| + static const JsObject = const SpecialType._('=Object'); |
| + |
| + /// The specific implementation of List that is JavaScript Array: |
| + static const JsArray = const SpecialType._('=List'); |
| } |
| -void addSubtypes(ClassElement cls, |
| - NativeEmitter emitter) { |
| - for (DartType type in cls.allSupertypes) { |
| - List<Element> subtypes = emitter.subtypes.putIfAbsent( |
| - type.element, |
| - () => <ClassElement>[]); |
| - subtypes.add(cls); |
| + |
| +/** |
| + * This could be an abstract class but we use it as a stub for the dart_backend. |
| + */ |
| +class NativeEnqueuer { |
| + /// Initial entry point to native enqueuer. |
| + void processNativeClasses(Collection<LibraryElement> libraries) {} |
| + |
| + void registerElement(Element element) {} |
| + |
| + /// Method is a member of a native class. |
| + void registerMethod(Element method) {} |
| + |
| + /// Compute types instantiated due to getting a native field. |
| + void registerFieldLoad(Element field) {} |
| + |
| + /// Compute types instantiated due to setting a native field. |
| + void registerFieldStore(Element field) {} |
| + |
| + /** |
| + * Handles JS-calls, which can be an instantiation point for types. |
| + * |
| + * For example, the following code instantiates and returns native classes |
| + * that are `_DOMWindowImpl` or a subtype. |
| + * |
| + * JS('_DOMWindowImpl', 'window') |
| + * |
| + */ |
| + // TODO(sra): The entry from codegen will not have a resolver. |
| + void registerJsCall(Send node, ResolverVisitor resolver) {} |
| + |
| + /// Emits a summary information using the [log] function. |
| + void logSummary(log(message)) {} |
| +} |
| + |
| + |
| +class NativeEnqueuerBase implements NativeEnqueuer { |
| + |
| + /** |
| + * The set of all native classes. Each native class is in [nativeClasses] and |
| + * exactly one of [unusedClasses], [pendingClasses] and [registeredClasses]. |
| + */ |
| + final Set<ClassElement> nativeClasses = new Set<ClassElement>(); |
| + |
| + final Set<ClassElement> registeredClasses = new Set<ClassElement>(); |
| + final Set<ClassElement> pendingClasses = new Set<ClassElement>(); |
| + final Set<ClassElement> unusedClasses = new Set<ClassElement>(); |
| + |
| + /** |
| + * Records matched constraints ([SpecialType] or [DartType]). Once a type |
| + * constraint has been matched, there is no need to match it again. |
| + */ |
| + final Set matchedTypeConstraints = new Set(); |
| + |
| + /// Pending actions. Classes in [pendingClasses] have action thunks in |
| + /// [queue] to register the class. |
| + final queue = new Queue(); |
| + bool flushing = false; |
| + |
| + |
| + final Enqueuer world; |
| + final Compiler compiler; |
| + final bool enableLiveTypeAnalysis; |
| + |
| + ClassElement _annotationCreatesClass; |
| + ClassElement _annotationReturnsClass; |
| + |
| + /// Subclasses of [NativeEnqueuerBase] are constructed by the backend. |
| + NativeEnqueuerBase(this.world, this.compiler, this.enableLiveTypeAnalysis); |
| + |
| + void processNativeClasses(Collection<LibraryElement> libraries) { |
| + libraries.forEach(processNativeClassesInLibrary); |
| + if (!enableLiveTypeAnalysis) { |
| + nativeClasses.forEach((c) => enqueueClass(c, 'forced')); |
| + flushQueue(); |
| + } |
| + } |
| + |
| + void processNativeClassesInLibrary(LibraryElement library) { |
| + // Use implementation to ensure the inclusion of injected members. |
| + library.implementation.forEachLocalMember((Element element) { |
| + if (element.kind == ElementKind.CLASS) { |
| + ClassElement classElement = element; |
| + if (classElement.isNative()) { |
| + nativeClasses.add(classElement); |
| + unusedClasses.add(classElement); |
| + |
| + // Resolve class to ensure the class has valid inheritance info. |
| + classElement.ensureResolved(compiler); |
| + } |
| + } |
| + }); |
| + } |
| + |
| + ClassElement get annotationCreatesClass { |
| + if (_annotationCreatesClass == null) findAnnotationClasses(); |
| + return _annotationCreatesClass; |
| + } |
| + |
| + ClassElement get annotationReturnsClass { |
| + if (_annotationReturnsClass == null) findAnnotationClasses(); |
| + return _annotationReturnsClass; |
| + } |
| + |
| + void findAnnotationClasses() { |
| + ClassElement find(name) { |
| + Element e = compiler.findHelper(name); |
| + if (e == null || e is! ClassElement) { |
| + compiler.cancel("Could not find implementation class '${name}'"); |
| + } |
| + return e; |
| + } |
| + _annotationCreatesClass = find(const SourceString('Creates')); |
| + _annotationReturnsClass = find(const SourceString('Returns')); |
| + } |
| + |
| + |
| + enqueueClass(ClassElement classElement, cause) { |
| + assert(unusedClasses.contains(classElement)); |
| + unusedClasses.remove(classElement); |
| + pendingClasses.add(classElement); |
| + queue.add(() { processClass(classElement, cause); }); |
| + } |
| + |
| + void flushQueue() { |
| + if (flushing) return; |
| + flushing = true; |
| + while (!queue.isEmpty) { |
| + (queue.removeFirst())(); |
| + } |
| + flushing = false; |
| + } |
| + |
| + processClass(ClassElement classElement, cause) { |
| + assert(!registeredClasses.contains(classElement)); |
| + |
| + bool firstTime = registeredClasses.isEmpty; |
| + pendingClasses.remove(classElement); |
| + registeredClasses.add(classElement); |
| + |
| + world.registerInstantiatedClass(classElement); |
| + |
| + // Also parse the node to know all its methods because otherwise it will |
| + // only be parsed if there is a call to one of its constructors. |
| + classElement.parseNode(compiler); |
| + |
| + if (firstTime) { |
| + queue.add(onFirstNativeClass); |
| + } |
| + } |
| + |
| + registerElement(Element element) { |
| + if (element.isFunction()) return registerMethod(element); |
| + } |
| + |
| + registerMethod(Element method) { |
| + if (isNativeMethod(method)) { |
| + processNativeBehavior( |
| + NativeBehavior.ofMethod(method, compiler), |
| + method); |
| + flushQueue(); |
| + } |
| + } |
| + |
| + bool isNativeMethod(Element element) { |
| + // Native method? |
| + Node node = element.parseNode(compiler); |
| + if (node is! FunctionExpression) return false; |
| + node = node.body; |
| + Token token = node.getBeginToken(); |
| + if (token.stringValue == 'native') return true; |
| + return false; |
| + } |
| + |
| + void registerFieldLoad(Element field) { |
| + processNativeBehavior( |
| + NativeBehavior.ofFieldLoad(field, compiler), |
| + field); |
| + flushQueue(); |
| + } |
| + |
| + void registerFieldStore(Element field) { |
| + processNativeBehavior( |
| + NativeBehavior.ofFieldStore(field, compiler), |
| + field); |
| + flushQueue(); |
| + } |
| + |
| + void registerJsCall(Send node, ResolverVisitor resolver) { |
| + processNativeBehavior( |
| + NativeBehavior.ofJsCall(node, compiler, resolver), |
| + node); |
| + flushQueue(); |
| + } |
| + |
| + processNativeBehavior(NativeBehavior behavior, cause) { |
| + bool allUsedBefore = unusedClasses.isEmpty; |
| + for (var type in behavior.typesInstantiated) { |
|
ahe
2012/12/04 09:15:19
When I see code like this, I assume this is equiva
|
| + if (matchedTypeConstraints.contains(type)) continue; |
| + matchedTypeConstraints.add(type); |
| + if (type is SpecialType) { |
| + // The two special types (=Object, =List) are always instantiated. |
| + continue; |
| + } |
| + assert(type is DartType); |
|
ahe
2012/12/04 09:15:19
There is a more natural way to express this in Dar
|
| + enqueueUnusedClassesMatching( |
| + (nativeClass) => compiler.types.isSubtype(nativeClass.type, type), |
| + cause, |
| + 'subtypeof($type)'); |
| + } |
| + |
| + // Give an info so that library developers can compile with -v to find why |
| + // all the native classes are included. |
| + if (unusedClasses.isEmpty && !allUsedBefore) { |
| + compiler.log('All native types marked as used due to $cause.'); |
| + } |
| + } |
| + |
| + enqueueUnusedClassesMatching(bool predicate(classElement), |
| + cause, |
| + [String reason]) { |
| + Collection matches = unusedClasses.filter(predicate); |
| + matches.forEach((c) => enqueueClass(c, cause)); |
| + } |
| + |
| + onFirstNativeClass() { |
| + staticUse(name) => world.registerStaticUse(compiler.findHelper(name)); |
| + |
| + staticUse(const SourceString('dynamicFunction')); |
| + staticUse(const SourceString('dynamicSetMetadata')); |
| + staticUse(const SourceString('defineProperty')); |
| + staticUse(const SourceString('toStringForNativeObject')); |
| + staticUse(const SourceString('hashCodeForNativeObject')); |
| + |
| + addNativeExceptions(); |
| } |
| - List<Element> directSubtypes = emitter.directSubtypes.putIfAbsent( |
| - cls.superclass, |
| - () => <ClassElement>[]); |
| - directSubtypes.add(cls); |
| + addNativeExceptions() { |
| + enqueueUnusedClassesMatching((classElement) { |
| + // TODO(sra): Annotate exception classes in dart:html. |
| + String name = classElement.name.slowToString(); |
| + if (name.contains('Exception')) return true; |
| + if (name.contains('Error')) return true; |
| + return false; |
| + }, |
| + 'native exception'); |
| + } |
| +} |
| + |
| + |
| +class NativeResolutionEnqueuer extends NativeEnqueuerBase { |
| + |
| + NativeResolutionEnqueuer(Enqueuer world, Compiler compiler) |
| + : super(world, compiler, compiler.enableNativeLiveTypeAnalysis); |
| + |
| + void logSummary(log(message)) { |
| + log('Resolved ${registeredClasses.length} native elements used, ' |
| + '${unusedClasses.length} native elements dead.'); |
| + } |
| } |
| -void processNativeClassesInLibrary(Enqueuer world, |
| - CodeEmitterTask emitter, |
| - LibraryElement library) { |
| - bool hasNativeClass = false; |
| - final compiler = emitter.compiler; |
| - // Use implementation to ensure the inclusion of injected members. |
| - library.implementation.forEachLocalMember((Element element) { |
| - if (element.kind == ElementKind.CLASS) { |
| - ClassElement classElement = element; |
| - if (classElement.isNative()) { |
| - hasNativeClass = true; |
| - world.registerInstantiatedClass(classElement); |
| - // Also parse the node to know all its methods because |
| - // otherwise it will only be parsed if there is a call to |
| - // one of its constructor. |
| - classElement.parseNode(compiler); |
| - // Resolve to setup the inheritance. |
| - classElement.ensureResolved(compiler); |
| - // 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); |
| + |
| +class NativeCodegenEnqueuer extends NativeEnqueuerBase { |
| + |
| + final CodeEmitterTask emitter; |
| + |
| + NativeCodegenEnqueuer(Enqueuer world, Compiler compiler, this.emitter) |
| + : super(world, compiler, compiler.enableNativeLiveTypeAnalysis); |
| + |
| + void processNativeClasses(Collection<LibraryElement> libraries) { |
| + super.processNativeClasses(libraries); |
| + |
| + // HACK HACK - add all the resolved classes. |
| + for (final classElement |
| + in compiler.enqueuer.resolution.nativeEnqueuer.registeredClasses) { |
| + if (unusedClasses.contains(classElement)) { |
| + enqueueClass(classElement, 'was resolved'); |
| } |
| } |
| - }); |
| - if (hasNativeClass) { |
| - world.registerStaticUse(compiler.findHelper( |
| - const SourceString('dynamicFunction'))); |
| - world.registerStaticUse(compiler.findHelper( |
| - const SourceString('dynamicSetMetadata'))); |
| - world.registerStaticUse(compiler.findHelper( |
| - const SourceString('defineProperty'))); |
| - world.registerStaticUse(compiler.findHelper( |
| - const SourceString('toStringForNativeObject'))); |
| - world.registerStaticUse(compiler.findHelper( |
| - const SourceString('hashCodeForNativeObject'))); |
| + flushQueue(); |
| + } |
| + |
| + processClass(ClassElement classElement, cause) { |
| + super.processClass(classElement, cause); |
| + // 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(ClassElement cls, NativeEmitter emitter) { |
| + for (DartType type in cls.allSupertypes) { |
| + List<Element> subtypes = emitter.subtypes.putIfAbsent( |
| + type.element, |
| + () => <ClassElement>[]); |
| + subtypes.add(cls); |
| + } |
| + |
| + List<Element> directSubtypes = emitter.directSubtypes.putIfAbsent( |
| + cls.superclass, |
| + () => <ClassElement>[]); |
| + directSubtypes.add(cls); |
| + } |
| + |
| + void logSummary(log(message)) { |
| + log('Compiled ${registeredClasses.length} native classes, ' |
| + '${unusedClasses.length} native classes omitted.'); |
| } |
| } |
| @@ -88,6 +349,246 @@ void maybeEnableNative(Compiler compiler, |
| } |
| } |
| +/** |
| + * A summary of the behavior of a native element. |
| + * |
| + * Native code can return values of one type and cause native subtypes of |
| + * another type to be instantiated. By default, we compute both from the |
| + * declared type. |
| + * |
| + * A field might yield any native type that 'is' the field type. |
| + * |
| + * A method might create and return instances of native subclasses of its |
| + * declared return type, and a callback argument may be called with instances of |
| + * the callback parameter type (e.g. Event). |
| + * |
| + * If there is one or more @Creates annotations, the union of the named types |
| + * replaces the inferred instantiated type, and the return type is ignored for |
| + * the purpose of inferring instantiated types. |
| + * |
| + * @Creates(IDBCursor) // Created asynchronously. |
| + * @Creates(IDBRequest) // Created synchronously (for return value). |
| + * IDBRequest request = objectStore.openCursor(); |
| + * |
| + * If there is one or more @Returns annotations, the union of the named types |
| + * replaces the declared return type. |
| + * |
| + * @Returns(IDBRequest) |
| + * IDBRequest request = objectStore.openCursor(); |
| + */ |
| +class NativeBehavior { |
| + |
| + /// [DartType]s or [SpecialType]s returned or yielded by the native element. |
| + final Collection typesReturned = []; |
| + |
| + /// [DartType]s or [SpecialType]s instantiated by the native element. |
| + final Collection typesInstantiated = []; |
| + |
| + static final NativeBehavior NONE = new NativeBehavior(); |
| + |
| + //NativeBehavior(); |
| + |
| + static NativeBehavior ofJsCall(Send jsCall, Compiler compiler, resolver) { |
| + // The first argument of a JS-call is a string encoding various attributes |
| + // of the code. |
| + // |
| + // 'Type1|Type2'. A union type. |
| + // '=Object'. A JavaScript Object, no subtype. |
| + // '=List'. A JavaScript Array, no subtype. |
| + |
| + var argNodes = jsCall.arguments; |
| + if (argNodes.isEmpty) { |
| + compiler.cancel("JS expression has no type", node: jsCall); |
| + } |
| + |
| + var firstArg = argNodes.head; |
| + LiteralString specLiteral = firstArg.asLiteralString(); |
| + if (specLiteral != null) { |
| + String specString = specLiteral.dartString.slowToString(); |
| + // Various things that are not in fact types. |
| + if (specString == 'void') return NativeBehavior.NONE; |
| + if (specString == '' || specString == 'var') { |
| + var behavior = new NativeBehavior(); |
| + behavior.typesReturned.add(compiler.objectClass.computeType(compiler)); |
| + return behavior; |
| + } |
| + var behavior = new NativeBehavior(); |
| + for (final typeString in specString.split('|')) { |
| + var type = _parseType(typeString, compiler, |
| + (name) => resolver.resolveTypeFromString(name), |
| + jsCall); |
| + behavior.typesInstantiated.add(type); |
| + behavior.typesReturned.add(type); |
| + } |
| + return behavior; |
| + } |
| + |
| + // TODO(sra): We could accept a type identifier? e.g. JS(bool, '1<2'). It |
| + // is not very satisfactory because it does not work for void, dynamic. |
| + |
| + compiler.cancel("Unexpected JS first argument", node: firstArg); |
| + } |
| + |
| + static NativeBehavior ofMethod(Element method, Compiler compiler) { |
| + DartType type = method.computeType(compiler); |
| + var behavior = new NativeBehavior(); |
| + behavior.typesReturned.add(type.returnType); |
| + behavior._capture(type, compiler); |
| + |
| + // TODO(sra): Optional arguments are currently missing from the |
| + // DartType. This should be fixed so the following work-around can be |
| + // removed. |
| + method.computeSignature(compiler).forEachOptionalParameter( |
| + (Element parameter) { |
| + behavior._escape(parameter.computeType(compiler), compiler); |
| + }); |
| + |
| + behavior._overrideWithAnnotations(method, compiler); |
| + return behavior; |
| + } |
| + |
| + static NativeBehavior ofFieldLoad(Element field, Compiler compiler) { |
| + DartType type = field.computeType(compiler); |
| + var behavior = new NativeBehavior(); |
| + behavior.typesReturned.add(type); |
| + behavior._capture(type, compiler); |
| + behavior._overrideWithAnnotations(field, compiler); |
| + return behavior; |
| + } |
| + |
| + static NativeBehavior ofFieldStore(Element field, Compiler compiler) { |
| + DartType type = field.computeType(compiler); |
| + var behavior = new NativeBehavior(); |
| + behavior._escape(type, compiler); |
| + // We don't override the default behaviour - the annotations apply to |
| + // loading the field. |
| + return behavior; |
| + } |
| + |
| + void _overrideWithAnnotations(Element element, Compiler compiler) { |
| + if (element.metadata.isEmpty) return; |
| + |
| + DartType lookup(String name) { |
| + Element e = element.buildScope().lookup(new SourceString(name)); |
| + if (e == null) return null; |
| + if (e is! ClassElement) return null; |
| + e.ensureResolved(compiler); |
| + return e.computeType(compiler); |
| + } |
| + |
| + var creates = |
| + _collect(element, compiler, |
| + compiler.enqueuer.resolution.nativeEnqueuer.annotationCreatesClass, |
| + lookup); |
| + var returns = |
| + _collect(element, compiler, |
| + compiler.enqueuer.resolution.nativeEnqueuer.annotationReturnsClass, |
| + lookup); |
| + |
| + if (creates != null) { |
| + typesInstantiated..clear()..addAll(creates); |
| + } |
| + if (returns != null) { |
| + typesReturned..clear()..addAll(returns); |
| + } |
| + } |
| + |
| + /** |
| + * Returns a list of type constraints from the annotations of |
| + * [annotationClass]. |
| + * Returns `null` if no constraints. |
| + */ |
| + static _collect(Element element, Compiler compiler, Element annotationClass, |
| + lookup(str)) { |
| + var types = null; |
| + for (Link<MetadataAnnotation> link = element.metadata; |
| + !link.isEmpty; |
| + link = link.tail) { |
| + MetadataAnnotation annotation = link.head.ensureResolved(compiler); |
| + var value = annotation.value; |
| + if (value is! ConstructedConstant) continue; |
| + if (value.type is! InterfaceType) continue; |
| + if (!identical(value.type.element, annotationClass)) continue; |
| + |
| + var fields = value.fields; |
| + // TODO(sra): Better validation of the constant. |
| + if (fields.length != 1 || fields[0] is! StringConstant) { |
| + compiler.cancel( |
| + 'Annotations needs one string: ${annotation.parseNode(compiler)}'); |
| + } |
| + String specString = fields[0].toDartString().slowToString(); |
| + for (final typeString in specString.split('|')) { |
| + var type = _parseType(typeString, compiler, lookup, annotation); |
| + if (types == null) types = []; |
| + types.add(type); |
| + } |
| + } |
| + return types; |
| + } |
| + |
| + /// Models the behavior of having intances of [type] escape from Dart code |
| + /// into native code. |
| + void _escape(DartType type, Compiler compiler) { |
| + type = type.unalias(compiler); |
| + if (type is FunctionType) { |
| + // A function might be called from native code, passing us novel |
| + // parameters. |
| + _escape(type.returnType, compiler); |
| + for (Link<DartType> parameters = type.parameterTypes; |
| + !parameters.isEmpty; |
| + parameters = parameters.tail) { |
| + _capture(parameters.head, compiler); |
| + } |
| + } |
| + } |
| + |
| + /// Models the behavior of Dart code receiving instances and methods of [type] |
| + /// from native code. We usually start the analysis by capturing a native |
| + /// method that has been used. |
| + void _capture(DartType type, Compiler compiler) { |
| + type = type.unalias(compiler); |
| + if (type is FunctionType) { |
| + _capture(type.returnType, compiler); |
| + for (Link<DartType> parameters = type.parameterTypes; |
| + !parameters.isEmpty; |
| + parameters = parameters.tail) { |
| + _escape(parameters.head, compiler); |
| + } |
| + } else { |
| + typesInstantiated.add(type); |
| + } |
| + } |
| + |
| + static _parseType(String typeString, Compiler compiler, |
| + lookup(name), locationNodeOrElement) { |
| + if (typeString == '=Object') return SpecialType.JsObject; |
| + if (typeString == '=List') return SpecialType.JsArray; |
| + if (typeString == 'dynamic') { |
| + return compiler.dynamicClass.computeType(compiler); |
| + } |
| + DartType type = lookup(typeString); |
| + if (type != null) return type; |
| + |
| + int index = typeString.indexOf('<'); |
| + if (index < 1) { |
| + compiler.cancel("Type '$typeString' not found", |
| + node: _errorNode(locationNodeOrElement, compiler)); |
| + } |
| + type = lookup(typeString.substring(0, index)); |
| + if (type != null) { |
| + // TODO(sra): Parse type parameters. |
| + return type; |
| + } |
| + compiler.cancel("Type '$typeString' not found", |
| + node: _errorNode(locationNodeOrElement, compiler)); |
| + } |
| + |
| + static _errorNode(locationNodeOrElement, compiler) { |
| + if (locationNodeOrElement is Node) return locationNodeOrElement; |
| + return locationNodeOrElement.parseNode(compiler); |
| + } |
| +} |
| + |
| void checkAllowedLibrary(ElementListener listener, Token token) { |
| LibraryElement currentLibrary = listener.compilationUnitElement.getLibrary(); |
| if (!currentLibrary.canUseNative) { |