blob: d58559459fe383612e018ced02e7d83443bd215e [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.
part of js_backend;
class NativeEmitter {
final Map<Element, ClassBuilder> cachedBuilders;
final CodeEmitterTask emitterTask;
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(CodeEmitterTask emitterTask)
: this.emitterTask = emitterTask,
subtypes = new Map<ClassElement, List<ClassElement>>(),
directSubtypes = new Map<ClassElement, List<ClassElement>>(),
nativeMethods = new Set<FunctionElement>(),
nativeBuffer = new CodeBuffer(),
cachedBuilders = emitterTask.compiler.cacheStrategy.newMap();
Compiler get compiler => emitterTask.compiler;
JavaScriptBackend get backend => compiler.backend;
jsAst.Expression get defPropFunction {
Element element = backend.findHelper('defineProperty');
return backend.namer.elementAccess(element);
}
/**
* 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.
*
* [classes] contains native classes, mixin applications, and user subclasses
* of native classes. ONLY the native classes are generated here. [classes]
* is sorted in desired output order.
*
* [additionalProperties] is used to collect properties that are pushed up
* from the above optimizations onto a non-native class, e.g, `Interceptor`.
*/
void generateNativeClasses(
List<ClassElement> classes,
CodeBuffer mainBuffer,
Map<ClassElement, Map<String, jsAst.Expression>> additionalProperties) {
// 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>();
seen..add(compiler.objectClass)
..add(backend.jsInterceptorClass);
void walk(ClassElement element) {
if (seen.contains(element)) 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) {
if (classElement.isNative) {
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>();
Map<ClassElement, List<ClassElement>> extensionPoints =
computeExtensionPoints(preOrder);
neededClasses.add(compiler.objectClass);
Set<ClassElement> neededByConstant =
emitterTask.interceptorsReferencedFromConstants();
Set<ClassElement> modifiedClasses =
emitterTask.typeTestEmitter.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;
} else if (extensionPoints.containsKey(classElement)) {
needed = true;
}
if (classElement.isNative &&
native.nativeTagsForcedNonLeaf(classElement)) {
needed = true;
nonleafClasses.add(classElement);
}
if (needed || neededClasses.contains(classElement)) {
neededClasses.add(classElement);
neededClasses.add(classElement.superclass);
nonleafClasses.add(classElement.superclass);
}
}
// Collect all the tags that map to each native 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) {
if (!classElement.isNative) continue;
List<String> nativeTags = native.nativeTagsOfClass(classElement);
if (nonleafClasses.contains(classElement) ||
extensionPoints.containsKey(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);
}
}
// Add properties containing the information needed to construct maps used
// by getNativeInterceptor and custom elements.
if (compiler.enqueuer.codegen.nativeEnqueuer
.hasInstantiatedNativeClasses()) {
void generateClassInfo(ClassElement classElement) {
// Property has the form:
//
// "%": "leafTag1|leafTag2|...;nonleafTag1|...;Class1|Class2|...",
//
// If there is no data following a semicolon, the semicolon can be
// omitted.
String formatTags(Iterable<String> tags) {
if (tags == null) return '';
return (tags.toList()..sort()).join('|');
}
List<ClassElement> extensions = extensionPoints[classElement];
String leafStr = formatTags(leafTags[classElement]);
String nonleafStr = formatTags(nonleafTags[classElement]);
StringBuffer sb = new StringBuffer(leafStr);
if (nonleafStr != '') {
sb..write(';')..write(nonleafStr);
}
if (extensions != null) {
sb..write(';')
..writeAll(extensions.map(backend.namer.getNameOfClass), '|');
}
String encoding = sb.toString();
ClassBuilder builder = builders[classElement];
if (builder == null) {
// No builder because this is an intermediate mixin application or
// Interceptor - these are not direct native classes.
if (encoding != '') {
Map<String, jsAst.Expression> properties =
additionalProperties.putIfAbsent(classElement,
() => new LinkedHashMap<String, jsAst.Expression>());
properties[backend.namer.nativeSpecProperty] = js.string(encoding);
}
} else {
builder.addProperty(
backend.namer.nativeSpecProperty, js.string(encoding));
}
}
generateClassInfo(backend.jsInterceptorClass);
for (ClassElement classElement in classes) {
generateClassInfo(classElement);
}
}
// Emit the native class interceptors that were actually used.
for (ClassElement classElement in classes) {
if (!classElement.isNative) continue;
if (neededClasses.contains(classElement)) {
// Define interceptor class for [classElement].
emitterTask.oldEmitter.classEmitter.emitClassBuilderWithReflectionData(
backend.namer.getNameOfClass(classElement),
classElement, builders[classElement],
emitterTask.oldEmitter.getElementDescriptor(classElement));
emitterTask.oldEmitter.needsDefineClass = true;
}
}
}
/**
* 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<ClassElement, List<ClassElement>> computeExtensionPoints(
List<ClassElement> classes) {
ClassElement nativeSuperclassOf(ClassElement element) {
if (element == null) return null;
if (element.isNative) return element;
return nativeSuperclassOf(element.superclass);
}
ClassElement nativeAncestorOf(ClassElement element) {
return nativeSuperclassOf(element.superclass);
}
Map<ClassElement, List<ClassElement>> map =
new Map<ClassElement, List<ClassElement>>();
for (ClassElement classElement in classes) {
if (classElement.isNative) continue;
ClassElement nativeAncestor = nativeAncestorOf(classElement);
if (nativeAncestor != null) {
map
.putIfAbsent(nativeAncestor, () => <ClassElement>[])
.add(classElement);
}
}
return map;
}
ClassBuilder generateNativeClass(ClassElement classElement) {
ClassBuilder builder;
if (compiler.hasIncrementalSupport) {
builder = cachedBuilders[classElement];
if (builder != null) return builder;
builder = new ClassBuilder(classElement, backend.namer);
cachedBuilders[classElement] = builder;
} else {
builder = new ClassBuilder(classElement, backend.namer);
}
// TODO(sra): Issue #13731- this is commented out as part of custom element
// constructor work.
//assert(!classElement.hasBackendMembers);
nativeClasses.add(classElement);
ClassElement superclass = classElement.superclass;
assert(superclass != null);
// Fix superclass. TODO(sra): make native classes inherit from Interceptor.
assert(superclass != compiler.objectClass);
if (superclass == compiler.objectClass) {
superclass = backend.jsInterceptorClass;
}
String superName = backend.namer.getNameOfClass(superclass);
emitterTask.oldEmitter.classEmitter.emitClassConstructor(
classElement, builder);
bool hasFields = emitterTask.oldEmitter.classEmitter.emitFields(
classElement, builder, superName, classIsNative: true);
int propertyCount = builder.properties.length;
emitterTask.oldEmitter.classEmitter.emitClassGettersSetters(
classElement, builder);
emitterTask.oldEmitter.classEmitter.emitInstanceMembers(
classElement, builder);
emitterTask.typeTestEmitter.emitIsTests(classElement, builder);
if (!hasFields &&
builder.properties.length == propertyCount &&
superclass is! MixinApplicationElement) {
builder.isTrivial = true;
}
return builder;
}
void finishGenerateNativeClasses() {
// TODO(sra): Put specialized version of getNativeMethods on
// `Object.prototype` to avoid checking in `getInterceptor` and
// specializations.
}
void potentiallyConvertDartClosuresToJs(
List<jsAst.Statement> statements,
FunctionElement member,
List<jsAst.Parameter> stubParameters) {
FunctionSignature parameters = member.functionSignature;
Element converter = backend.findHelper('convertDartClosureToJS');
jsAst.Expression closureConverter = backend.namer.elementAccess(converter);
parameters.forEachParameter((ParameterElement parameter) {
String name = parameter.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) {
DartType type = parameter.type.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.statement('# = #(#, $arity)',
[name, closureConverter, name]));
break;
}
}
}
});
}
List<jsAst.Statement> generateParameterStubStatements(
FunctionElement 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.enclosingClass;
List<jsAst.Statement> statements = <jsAst.Statement>[];
potentiallyConvertDartClosuresToJs(statements, member, stubParameters);
String target;
jsAst.Expression receiver;
List<jsAst.Expression> arguments;
assert(invariant(member, nativeMethods.contains(member)));
// 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(
js.statement('return #.#(#)', [receiver, target, arguments]));
return statements;
}
bool isSupertypeOfNativeClass(Element element) {
if (element.isTypeVariable) {
compiler.internalError(element, "Is check for type variable.");
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.internalError(element, "Is check does not handle element.");
return false;
}
if (backend.classesMixedIntoInterceptedClasses.contains(element)) {
return true;
}
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 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.
if (element == null || !element.isClass) return false;
ClassElement cls = element;
if (Elements.isNativeOrExtendsNative(cls)) return true;
return isSupertypeOfNativeClass(element);
}
void assembleCode(CodeBuffer targetBuffer) {
List<jsAst.Property> objectProperties = <jsAst.Property>[];
jsAst.Property addProperty(String name, jsAst.Expression value) {
jsAst.Property prop = new jsAst.Property(js.string(name), value);
objectProperties.add(prop);
return prop;
}
if (!nativeClasses.isEmpty) {
// If the native emitter has been asked to take care of the
// noSuchMethod handlers, we do that now.
if (handleNoSuchMethod) {
emitterTask.oldEmitter.nsmEmitter.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(r'''
(function(table) {
for(var key in table)
#(Object.prototype, key, table[key]);
})(#)''',
[ defPropFunction,
new jsAst.ObjectInitializer(objectProperties)]);
if (emitterTask.compiler.enableMinification) targetBuffer.add(';');
targetBuffer.add(jsAst.prettyPrint(
new jsAst.ExpressionStatement(init), compiler));
targetBuffer.add('\n');
}
targetBuffer.add(nativeBuffer);
targetBuffer.add('\n');
}
}