blob: 0ba42aea08f475ea08ac938302867d44a04d2ed6 [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.
library native;
import 'dart:uri';
import 'dart2jslib.dart' hide SourceString;
import 'elements/elements.dart';
import 'js_backend/js_backend.dart';
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);
}
}
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 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);
}
}
});
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')));
}
}
void maybeEnableNative(Compiler compiler,
LibraryElement library,
Uri uri) {
String libraryName = uri.toString();
if (library.entryCompilationUnit.script.name.contains(
'dart/tests/compiler/dart2js_native')
|| libraryName == 'dart:isolate'
|| libraryName == 'dart:html') {
library.canUseNative = true;
}
}
void checkAllowedLibrary(ElementListener listener, Token token) {
LibraryElement currentLibrary = listener.compilationUnitElement.getLibrary();
if (!currentLibrary.canUseNative) {
listener.recoverableError("Unexpected token", token: token);
}
}
Token handleNativeBlockToSkip(Listener listener, Token token) {
checkAllowedLibrary(listener, token);
token = token.next;
if (identical(token.kind, STRING_TOKEN)) {
token = token.next;
}
if (identical(token.stringValue, '{')) {
BeginGroupToken beginGroupToken = token;
token = beginGroupToken.endGroup;
}
return token;
}
Token handleNativeClassBodyToSkip(Listener listener, Token token) {
checkAllowedLibrary(listener, token);
listener.handleIdentifier(token);
token = token.next;
if (!identical(token.kind, STRING_TOKEN)) {
return listener.unexpected(token);
}
token = token.next;
if (!identical(token.stringValue, '{')) {
return listener.unexpected(token);
}
BeginGroupToken beginGroupToken = token;
token = beginGroupToken.endGroup;
return token;
}
Token handleNativeClassBody(Listener listener, Token token) {
checkAllowedLibrary(listener, token);
token = token.next;
if (!identical(token.kind, STRING_TOKEN)) {
listener.unexpected(token);
} else {
token = token.next;
}
return token;
}
Token handleNativeFunctionBody(ElementListener listener, Token token) {
checkAllowedLibrary(listener, token);
Token begin = token;
listener.beginReturnStatement(token);
token = token.next;
bool hasExpression = false;
if (identical(token.kind, STRING_TOKEN)) {
hasExpression = true;
listener.beginLiteralString(token);
listener.endLiteralString(0);
token = token.next;
}
listener.endReturnStatement(hasExpression, begin, token);
// TODO(ngeoffray): expect a ';'.
// Currently there are method with both native marker and Dart body.
return token.next;
}
SourceString checkForNativeClass(ElementListener listener) {
SourceString nativeName;
Node node = listener.nodes.head;
if (node != null
&& node.asIdentifier() != null
&& node.asIdentifier().source.stringValue == 'native') {
nativeName = node.asIdentifier().token.next.value;
listener.popNode();
}
return nativeName;
}
bool isOverriddenMethod(FunctionElement element,
ClassElement cls,
NativeEmitter nativeEmitter) {
List<ClassElement> subtypes = nativeEmitter.subtypes[cls];
if (subtypes == null) return false;
for (ClassElement subtype in subtypes) {
if (subtype.lookupLocalMember(element.name) != null) return true;
}
return false;
}
void handleSsaNative(SsaBuilder builder, Expression nativeBody) {
Compiler compiler = builder.compiler;
FunctionElement element = builder.work.element;
element.setNative();
NativeEmitter nativeEmitter = builder.emitter.nativeEmitter;
// If what we're compiling is a getter named 'typeName' and the native
// class is named 'DOMType', we generate a call to the typeNameOf
// function attached on the isolate.
// The DOM classes assume that their 'typeName' property, which is
// not a JS property on the DOM types, returns the type name.
if (element.name == const SourceString('typeName')
&& element.isGetter()
&& nativeEmitter.toNativeName(element.getEnclosingClass()) == 'DOMType') {
Element helper =
compiler.findHelper(const SourceString('getTypeNameOf'));
builder.pushInvokeHelper1(helper, builder.localsHandler.readThis());
builder.close(new HReturn(builder.pop())).addSuccessor(builder.graph.exit);
}
HInstruction convertDartClosure(Element parameter, FunctionType type) {
HInstruction local = builder.localsHandler.readLocal(parameter);
Constant arityConstant =
builder.constantSystem.createInt(type.computeArity());
HInstruction arity = builder.graph.addConstant(arityConstant);
// TODO(ngeoffray): For static methods, we could pass a method with a
// defined arity.
Element helper = builder.interceptors.getClosureConverter();
builder.pushInvokeHelper2(helper, local, arity);
HInstruction closure = builder.pop();
return closure;
}
// Check which pattern this native method follows:
// 1) foo() native; hasBody = false, isRedirecting = false
// 2) foo() native "bar"; hasBody = false, isRedirecting = true
// 3) foo() native "return 42"; hasBody = true, isRedirecting = false
RegExp nativeRedirectionRegExp = const RegExp(r'^[a-zA-Z][a-zA-Z_$0-9]*$');
bool hasBody = false;
bool isRedirecting = false;
String nativeMethodName = element.name.slowToString();
if (nativeBody != null) {
LiteralString jsCode = nativeBody.asLiteralString();
String str = jsCode.dartString.slowToString();
if (nativeRedirectionRegExp.hasMatch(str)) {
nativeMethodName = str;
isRedirecting = true;
nativeEmitter.addRedirectingMethod(element, nativeMethodName);
} else {
hasBody = true;
}
}
if (!hasBody) {
nativeEmitter.nativeMethods.add(element);
}
FunctionSignature parameters = element.computeSignature(builder.compiler);
if (!hasBody) {
List<String> arguments = <String>[];
List<HInstruction> inputs = <HInstruction>[];
String receiver = '';
if (element.isInstanceMember()) {
receiver = '#.';
inputs.add(builder.localsHandler.readThis());
}
parameters.forEachParameter((Element parameter) {
DartType type = parameter.computeType(compiler).unalias(compiler);
HInstruction input = builder.localsHandler.readLocal(parameter);
if (type is FunctionType) {
// The parameter type is a function type either directly or through
// typedef(s).
input = convertDartClosure(parameter, type);
}
inputs.add(input);
arguments.add('#');
});
String foreignParameters = Strings.join(arguments, ',');
String nativeMethodCall;
if (element.kind == ElementKind.FUNCTION) {
nativeMethodCall = '$receiver$nativeMethodName($foreignParameters)';
} else if (element.kind == ElementKind.GETTER) {
nativeMethodCall = '$receiver$nativeMethodName';
} else if (element.kind == ElementKind.SETTER) {
nativeMethodCall = '$receiver$nativeMethodName = $foreignParameters';
} else {
builder.compiler.internalError('unexpected kind: "${element.kind}"',
element: element);
}
DartString jsCode = new DartString.literal(nativeMethodCall);
builder.push(
new HForeign(jsCode, const LiteralDartString('Object'), inputs));
builder.close(new HReturn(builder.pop())).addSuccessor(builder.graph.exit);
} else {
// This is JS code written in a Dart file with the construct
// native """ ... """;. It does not work well with mangling,
// but there should currently be no clash between leg mangling
// and the library where this construct is being used. This
// mangling problem will go away once we switch these libraries
// to use Leg's 'JS' function.
parameters.forEachParameter((Element parameter) {
DartType type = parameter.computeType(compiler).unalias(compiler);
if (type is FunctionType) {
// The parameter type is a function type either directly or through
// typedef(s).
HInstruction jsClosure = convertDartClosure(parameter, type);
// Because the JS code references the argument name directly,
// we must keep the name and assign the JS closure to it.
builder.add(new HForeign(
new DartString.literal('${parameter.name.slowToString()} = #'),
const LiteralDartString('void'),
<HInstruction>[jsClosure]));
}
});
LiteralString jsCode = nativeBody.asLiteralString();
builder.push(new HForeign.statement(jsCode.dartString, <HInstruction>[]));
}
}
void generateMethodWithPrototypeCheckForElement(Compiler compiler,
StringBuffer buffer,
FunctionElement element,
String code,
String parameters) {
String methodName;
JavaScriptBackend backend = compiler.backend;
Namer namer = backend.namer;
if (element.kind == ElementKind.FUNCTION) {
methodName = namer.instanceMethodName(element);
} else if (element.kind == ElementKind.GETTER) {
methodName = namer.getterName(element.getLibrary(), element.name);
} else if (element.kind == ElementKind.SETTER) {
methodName = namer.setterName(element.getLibrary(), element.name);
} else {
compiler.internalError('unexpected kind: "${element.kind}"',
element: element);
}
generateMethodWithPrototypeCheck(
compiler, buffer, methodName, code, parameters);
}
// If a method is overridden, we must check if the prototype of
// 'this' has the method available. Otherwise, we may end up
// calling the method from the super class. If the method is not
// available, we make a direct call to Object.prototype.$methodName.
// This method will patch the prototype of 'this' to the real method.
void generateMethodWithPrototypeCheck(Compiler compiler,
StringBuffer buffer,
String methodName,
String code,
String parameters) {
buffer.add(" if (Object.getPrototypeOf(this).hasOwnProperty");
buffer.add("('$methodName')) {\n");
buffer.add(" $code");
buffer.add(" } else {\n");
buffer.add(" return Object.prototype.$methodName.call(this");
buffer.add(parameters == '' ? '' : ', $parameters');
buffer.add(");\n");
buffer.add(" }\n");
}