blob: f7adf4a7f18fc28e21d1d32f5758ced6a4efcb28 [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 {
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);
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 {
// TODO(9556): We can't remove any unneeded classes until the class
// builders contain all the information. [emitRuntimeTypeSupport] must
// no longer add information to a class definition.
needed = true;
}
// BUG. There is a missing proto in the picture the DOM gives of the
// proto chain.
// TODO(9907): Fix DOM generation. We might need an annotation.
if (classElement.isNative()) {
List<String> nativeTags = nativeTagsOfClass(classElement);
if (nativeTags.contains('HTMLElement')) {
nonleafClasses.add(classElement);
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.
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)) {
ClassBuilder builder = builders[classElement];
// Define interceptor class for [classElement].
String className = backend.namer.getName(classElement);
jsAst.Expression init =
js(emitter.classesCollector)[className].assign(
builder.toObjectInitializer());
mainBuffer.write(jsAst.prettyPrint(init, compiler));
mainBuffer.write('$N$n');
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) {
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');
}
}