| // 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. |
| |
| part of js_backend; |
| |
| class NativeEmitter { |
| |
| CodeEmitterTask emitter; |
| CodeBuffer nativeBuffer; |
| |
| // Native classes found in the application. |
| Set<ClassElement> nativeClasses = new Set<ClassElement>(); |
| |
| // Caches the native subtypes of a native class. |
| Map<ClassElement, List<ClassElement>> subtypes; |
| |
| // Caches the direct native subtypes of a native class. |
| Map<ClassElement, List<ClassElement>> directSubtypes; |
| |
| // Caches the methods that have a native body. |
| Set<FunctionElement> nativeMethods; |
| |
| // Do we need the native emitter to take care of handling |
| // noSuchMethod for us? This flag is set to true in the emitter if |
| // it finds any native class that needs noSuchMethod handling. |
| bool handleNoSuchMethod = false; |
| |
| NativeEmitter(this.emitter) |
| : subtypes = new Map<ClassElement, List<ClassElement>>(), |
| directSubtypes = new Map<ClassElement, List<ClassElement>>(), |
| nativeMethods = new Set<FunctionElement>(), |
| nativeBuffer = new CodeBuffer(); |
| |
| Compiler get compiler => emitter.compiler; |
| JavaScriptBackend get backend => compiler.backend; |
| |
| String get _ => emitter._; |
| String get n => emitter.n; |
| String get N => emitter.N; |
| |
| String get dynamicName { |
| Element element = compiler.findHelper( |
| const SourceString('dynamicFunction')); |
| return backend.namer.isolateAccess(element); |
| } |
| |
| String get dynamicFunctionTableName { |
| Element element = compiler.findHelper( |
| const SourceString('dynamicFunctionTable')); |
| return backend.namer.isolateAccess(element); |
| } |
| |
| String get typeNameOfName { |
| Element element = compiler.findHelper( |
| const SourceString('getTypeNameOf')); |
| return backend.namer.isolateAccess(element); |
| } |
| |
| String get defPropName { |
| Element element = compiler.findHelper( |
| const SourceString('defineProperty')); |
| return backend.namer.isolateAccess(element); |
| } |
| |
| String get toStringHelperName { |
| Element element = compiler.findHelper( |
| const SourceString('toStringForNativeObject')); |
| return backend.namer.isolateAccess(element); |
| } |
| |
| String get hashCodeHelperName { |
| Element element = compiler.findHelper( |
| const SourceString('hashCodeForNativeObject')); |
| return backend.namer.isolateAccess(element); |
| } |
| |
| String get dispatchPropertyNameVariable { |
| Element element = compiler.findInterceptor( |
| const SourceString('dispatchPropertyName')); |
| return backend.namer.isolateAccess(element); |
| } |
| |
| String get defineNativeMethodsName { |
| Element element = compiler.findHelper( |
| const SourceString('defineNativeMethods')); |
| return backend.namer.isolateAccess(element); |
| } |
| |
| String get defineNativeMethodsNonleafName { |
| Element element = compiler.findHelper( |
| const SourceString('defineNativeMethodsNonleaf')); |
| return backend.namer.isolateAccess(element); |
| } |
| |
| String get defineNativeMethodsFinishName { |
| Element element = compiler.findHelper( |
| const SourceString('defineNativeMethodsFinish')); |
| return backend.namer.isolateAccess(element); |
| } |
| |
| List<String> nativeTagsOfClass(ClassElement cls) { |
| String quotedName = cls.nativeTagInfo.slowToString(); |
| return quotedName.substring(1, quotedName.length - 1).split(','); |
| } |
| |
| /** |
| * Writes the class definitions for the interceptors to [mainBuffer]. |
| * Writes code to associate dispatch tags with interceptors to [nativeBuffer]. |
| * |
| * 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. |
| */ |
| void generateNativeClasses(List<ClassElement> classes, |
| CodeBuffer mainBuffer) { |
| // 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<ClassElement> preOrder = <ClassElement>[]; |
| Set<ClassElement> seen = new Set<ClassElement>(); |
| void walk(ClassElement element) { |
| if (seen.contains(element) || element == compiler.objectClass) return; |
| seen.add(element); |
| walk(element.superclass); |
| preOrder.add(element); |
| } |
| classes.forEach(walk); |
| |
| // Generate code for each native class into [ClassBuilder]s. |
| |
| Map<ClassElement, ClassBuilder> builders = |
| new Map<ClassElement, ClassBuilder>(); |
| for (ClassElement classElement in classes) { |
| ClassBuilder builder = generateNativeClass(classElement); |
| builders[classElement] = builder; |
| } |
| |
| // 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<ClassElement> neededClasses = new Set<ClassElement>(); |
| Set<ClassElement> nonleafClasses = new Set<ClassElement>(); |
| neededClasses.add(compiler.objectClass); |
| |
| Set<ClassElement> neededByConstant = |
| emitter.interceptorsReferencedFromConstants(); |
| Set<ClassElement> modifiedClasses = |
| emitter.classesModifiedByEmitRuntimeTypeSupport(); |
| |
| for (ClassElement classElement in preOrder.reversed) { |
| // 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. |
| ClassBuilder builder = builders[classElement]; |
| bool needed = false; |
| if (builder == null) { |
| // 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 (!builder.isTrivial) { |
| needed = true; |
| } else if (neededByConstant.contains(classElement)) { |
| needed = true; |
| } else if (modifiedClasses.contains(classElement)) { |
| // TODO(9556): Remove this test when [emitRuntimeTypeSupport] no longer |
| // adds information to a class prototype or constructor. |
| needed = true; |
| } |
| |
| if (needed || neededClasses.contains(classElement)) { |
| neededClasses.add(classElement); |
| neededClasses.add(classElement.superclass); |
| nonleafClasses.add(classElement.superclass); |
| } |
| } |
| |
| // Collect all the tags that map to each class. |
| |
| Map<ClassElement, Set<String>> leafTags = |
| new Map<ClassElement, Set<String>>(); |
| Map<ClassElement, Set<String>> nonleafTags = |
| new Map<ClassElement, Set<String>>(); |
| |
| for (ClassElement classElement in classes) { |
| List<String> nativeTags = nativeTagsOfClass(classElement); |
| |
| if (nonleafClasses.contains(classElement)) { |
| nonleafTags |
| .putIfAbsent(classElement, () => new Set<String>()) |
| .addAll(nativeTags); |
| } else { |
| ClassElement sufficingInterceptor = classElement; |
| while (!neededClasses.contains(sufficingInterceptor)) { |
| sufficingInterceptor = sufficingInterceptor.superclass; |
| } |
| if (sufficingInterceptor == compiler.objectClass) { |
| sufficingInterceptor = backend.jsInterceptorClass; |
| } |
| leafTags |
| .putIfAbsent(sufficingInterceptor, () => new Set<String>()) |
| .addAll(nativeTags); |
| } |
| } |
| |
| // Emit code to set up dispatch data that maps tags to the interceptors, but |
| // only if native classes are actually instantiated. |
| if (compiler.enqueuer.codegen.nativeEnqueuer |
| .hasInstantiatedNativeClasses()) { |
| void generateDefines(ClassElement classElement) { |
| generateDefineNativeMethods(leafTags[classElement], classElement, |
| defineNativeMethodsName); |
| generateDefineNativeMethods(nonleafTags[classElement], classElement, |
| defineNativeMethodsNonleafName); |
| } |
| generateDefines(backend.jsInterceptorClass); |
| for (ClassElement classElement in classes) { |
| generateDefines(classElement); |
| } |
| } |
| |
| // Emit the native class interceptors that were actually used. |
| for (ClassElement classElement in classes) { |
| if (neededClasses.contains(classElement)) { |
| // Define interceptor class for [classElement]. |
| emitter.emitClassBuilderWithReflectionData( |
| backend.namer.getName(classElement), |
| classElement, builders[classElement], |
| emitter.bufferForElement(classElement, mainBuffer)); |
| emitter.needsDefineClass = true; |
| } |
| } |
| } |
| |
| ClassBuilder generateNativeClass(ClassElement classElement) { |
| assert(!classElement.hasBackendMembers); |
| nativeClasses.add(classElement); |
| |
| ClassElement superclass = classElement.superclass; |
| assert(superclass != null); |
| // Fix superclass. TODO(sra): make native classes inherit from Interceptor. |
| if (superclass == compiler.objectClass) { |
| superclass = backend.jsInterceptorClass; |
| } |
| |
| String superName = backend.namer.getName(superclass); |
| |
| ClassBuilder builder = new ClassBuilder(); |
| emitter.emitClassConstructor(classElement, builder); |
| emitter.emitSuper(superName, builder); |
| bool hasFields = emitter.emitClassFields( |
| classElement, builder, superName, classIsNative: true); |
| int propertyCount = builder.properties.length; |
| emitter.emitClassGettersSetters(classElement, builder); |
| emitter.emitInstanceMembers(classElement, builder); |
| emitter.emitIsTests(classElement, builder); |
| |
| if (!hasFields && |
| builder.properties.length == propertyCount && |
| superclass is! MixinApplicationElement) { |
| builder.isTrivial = true; |
| } |
| |
| return builder; |
| } |
| |
| void generateDefineNativeMethods( |
| Set<String> tags, ClassElement classElement, String definer) { |
| if (tags == null) return; |
| String tagsString = (tags.toList()..sort()).join('|'); |
| jsAst.Expression definition = |
| js(definer)( |
| [js.string(tagsString), |
| js(backend.namer.isolateAccess(classElement))]); |
| |
| nativeBuffer.add(jsAst.prettyPrint(definition, compiler)); |
| nativeBuffer.add('$N$n'); |
| } |
| |
| |
| void finishGenerateNativeClasses() { |
| // TODO(sra): Put specialized version of getNativeMethods on |
| // `Object.prototype` to avoid checking in `getInterceptor` and |
| // specializations. |
| |
| // jsAst.Expression call = js(defineNativeMethodsFinishName)([]); |
| // nativeBuffer.add(jsAst.prettyPrint(call, compiler)); |
| // nativeBuffer.add('$N$n'); |
| } |
| |
| void potentiallyConvertDartClosuresToJs( |
| List<jsAst.Statement> statements, |
| FunctionElement member, |
| List<jsAst.Parameter> stubParameters) { |
| FunctionSignature parameters = member.computeSignature(compiler); |
| Element converter = |
| compiler.findHelper(const SourceString('convertDartClosureToJS')); |
| String closureConverter = backend.namer.isolateAccess(converter); |
| Set<String> stubParameterNames = new Set<String>.from( |
| stubParameters.map((param) => param.name)); |
| parameters.forEachParameter((Element parameter) { |
| String name = parameter.name.slowToString(); |
| // 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) { |
| DartType type = parameter.computeType(compiler).unalias(compiler); |
| if (type is FunctionType) { |
| // The parameter type is a function type either directly or through |
| // typedef(s). |
| FunctionType functionType = type; |
| int arity = functionType.computeArity(); |
| statements.add( |
| js('$name = $closureConverter($name, $arity)').toStatement()); |
| break; |
| } |
| } |
| } |
| }); |
| } |
| |
| List<jsAst.Statement> generateParameterStubStatements( |
| Element member, |
| bool isInterceptedMethod, |
| String 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). |
| |
| ClassElement classElement = member.enclosingElement; |
| |
| List<jsAst.Statement> statements = <jsAst.Statement>[]; |
| potentiallyConvertDartClosuresToJs(statements, member, stubParameters); |
| |
| String target; |
| jsAst.Expression receiver; |
| List<jsAst.Expression> arguments; |
| |
| if (!nativeMethods.contains(member)) { |
| // When calling a method that has a native body, we call it with our |
| // calling conventions. |
| target = backend.namer.getName(member); |
| arguments = argumentsBuffer; |
| } else { |
| // When calling a JS method, we call it with the native name, and only the |
| // arguments up until the last one provided. |
| target = member.fixedBackendName(); |
| |
| if (isInterceptedMethod) { |
| receiver = argumentsBuffer[0]; |
| arguments = argumentsBuffer.sublist(1, |
| indexOfLastOptionalArgumentInParameters + 1); |
| } else { |
| receiver = js('this'); |
| arguments = argumentsBuffer.sublist(0, |
| indexOfLastOptionalArgumentInParameters + 1); |
| } |
| } |
| statements.add(new jsAst.Return(receiver[target](arguments))); |
| |
| return statements; |
| } |
| |
| bool isSupertypeOfNativeClass(Element element) { |
| if (element.isTypeVariable()) { |
| compiler.cancel("Is check for type variable", element: element); |
| return false; |
| } |
| if (element.computeType(compiler).unalias(compiler) is FunctionType) { |
| // The element type is a function type either directly or through |
| // typedef(s). |
| return false; |
| } |
| |
| if (!element.isClass()) { |
| compiler.cancel("Is check does not handle element", element: element); |
| return false; |
| } |
| |
| return subtypes[element] != null; |
| } |
| |
| bool requiresNativeIsCheck(Element element) { |
| // TODO(sra): Remove this function. It determines if a native type may |
| // satisfy a check against [element], in whcih 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 natibe 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. |
| if (!element.isClass()) return false; |
| ClassElement cls = element; |
| if (cls.isNative()) return true; |
| return isSupertypeOfNativeClass(element); |
| } |
| |
| void assembleCode(CodeBuffer targetBuffer) { |
| List<jsAst.Property> objectProperties = <jsAst.Property>[]; |
| |
| void addProperty(String name, jsAst.Expression value) { |
| objectProperties.add(new jsAst.Property(js.string(name), value)); |
| } |
| |
| if (!nativeClasses.isEmpty) { |
| // If the native emitter has been asked to take care of the |
| // noSuchMethod handlers, we do that now. |
| if (handleNoSuchMethod) { |
| emitter.emitNoSuchMethodHandlers(addProperty); |
| } |
| } |
| |
| // If we have any properties to add to Object.prototype, we run |
| // through them and add them using defineProperty. |
| if (!objectProperties.isEmpty) { |
| jsAst.Expression init = |
| js.fun(['table'], |
| new jsAst.ForIn( |
| new jsAst.VariableDeclarationList( |
| [new jsAst.VariableInitialization( |
| new jsAst.VariableDeclaration('key'), |
| null)]), |
| js('table'), |
| new jsAst.ExpressionStatement( |
| js('$defPropName(Object.prototype, key, table[key])'))))( |
| new jsAst.ObjectInitializer(objectProperties)); |
| |
| if (emitter.compiler.enableMinification) targetBuffer.add(';'); |
| targetBuffer.add(jsAst.prettyPrint( |
| new jsAst.ExpressionStatement(init), compiler)); |
| targetBuffer.add('\n'); |
| } |
| |
| targetBuffer.add(nativeBuffer); |
| targetBuffer.add('\n'); |
| } |
| } |