Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1141)

Unified Diff: sdk/lib/_internal/compiler/implementation/native_handler.dart

Issue 11304021: Add NativeEnqueuer to work with the Enqueuer. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 8 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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) {

Powered by Google App Engine
This is Rietveld 408576698