| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | |
| 2 // for details. All rights reserved. Use of this source code is governed by a | |
| 3 // BSD-style license that can be found in the LICENSE file. | |
| 4 | |
| 5 #library('native'); | |
| 6 #import('../../lib/uri/uri.dart'); | |
| 7 #import('leg.dart'); | |
| 8 #import('elements/elements.dart'); | |
| 9 #import('scanner/scannerlib.dart'); | |
| 10 #import('ssa/ssa.dart'); | |
| 11 #import('tree/tree.dart'); | |
| 12 #import('util/util.dart'); | |
| 13 | |
| 14 void processNativeClasses(Compiler compiler, | |
| 15 Collection<LibraryElement> libraries) { | |
| 16 for (LibraryElement library in libraries) { | |
| 17 processNativeClassesInLibrary(compiler, library); | |
| 18 } | |
| 19 } | |
| 20 | |
| 21 void addSubtypes(ClassElement cls, | |
| 22 NativeEmitter emitter) { | |
| 23 for (Type type in cls.allSupertypes) { | |
| 24 List<Element> subtypes = emitter.subtypes.putIfAbsent( | |
| 25 type.element, | |
| 26 () => <ClassElement>[]); | |
| 27 subtypes.add(cls); | |
| 28 } | |
| 29 } | |
| 30 | |
| 31 void processNativeClassesInLibrary(Compiler compiler, | |
| 32 LibraryElement library) { | |
| 33 bool hasNativeClass = false; | |
| 34 for (Link<Element> link = library.topLevelElements; | |
| 35 !link.isEmpty(); link = link.tail) { | |
| 36 Element element = link.head; | |
| 37 if (element.kind == ElementKind.CLASS) { | |
| 38 ClassElement classElement = element; | |
| 39 if (classElement.isNative()) { | |
| 40 hasNativeClass = true; | |
| 41 compiler.registerInstantiatedClass(classElement); | |
| 42 // Also parse the node to know all its methods because | |
| 43 // otherwise it will only be parsed if there is a call to | |
| 44 // one of its constructor. | |
| 45 classElement.parseNode(compiler); | |
| 46 // Resolve to setup the inheritance. | |
| 47 classElement.ensureResolved(compiler); | |
| 48 // Add the information that this class is a subtype of | |
| 49 // its supertypes. The code emitter and the ssa builder use that | |
| 50 // information. | |
| 51 NativeEmitter emitter = compiler.emitter.nativeEmitter; | |
| 52 addSubtypes(classElement, emitter); | |
| 53 } | |
| 54 } | |
| 55 } | |
| 56 if (hasNativeClass) { | |
| 57 compiler.registerStaticUse(compiler.findHelper( | |
| 58 const SourceString('dynamicFunction'))); | |
| 59 compiler.registerStaticUse(compiler.findHelper( | |
| 60 const SourceString('dynamicSetMetadata'))); | |
| 61 compiler.registerStaticUse(compiler.findHelper( | |
| 62 const SourceString('defineProperty'))); | |
| 63 compiler.registerStaticUse(compiler.findHelper( | |
| 64 const SourceString('toStringForNativeObject'))); | |
| 65 } | |
| 66 } | |
| 67 | |
| 68 void maybeEnableNative(Compiler compiler, | |
| 69 LibraryElement library, | |
| 70 Uri uri) { | |
| 71 String libraryName = uri.toString(); | |
| 72 if (library.script.name.contains('dart/frog/tests/native/src') | |
| 73 || libraryName == 'dart:dom' | |
| 74 || libraryName == 'dart:isolate' | |
| 75 || libraryName == 'dart:html') { | |
| 76 library.define(new ForeignElement( | |
| 77 const SourceString('native'), library), compiler); | |
| 78 library.canUseNative = true; | |
| 79 } | |
| 80 | |
| 81 // Additionaly, if this is a test, we allow access to foreign functions. | |
| 82 if (library.script.name.contains('dart/frog/tests/native/src')) { | |
| 83 library.define(compiler.findHelper(const SourceString('JS')), compiler); | |
| 84 } | |
| 85 } | |
| 86 | |
| 87 void checkAllowedLibrary(ElementListener listener, Token token) { | |
| 88 LibraryElement currentLibrary = listener.compilationUnitElement.getLibrary(); | |
| 89 if (!currentLibrary.canUseNative) { | |
| 90 listener.recoverableError("Unexpected token", token: token); | |
| 91 } | |
| 92 } | |
| 93 | |
| 94 Token handleNativeBlockToSkip(Listener listener, Token token) { | |
| 95 checkAllowedLibrary(listener, token); | |
| 96 token = token.next; | |
| 97 if (token.kind === STRING_TOKEN) { | |
| 98 token = token.next; | |
| 99 } | |
| 100 if (token.stringValue === '{') { | |
| 101 BeginGroupToken beginGroupToken = token; | |
| 102 token = beginGroupToken.endGroup; | |
| 103 } | |
| 104 return token; | |
| 105 } | |
| 106 | |
| 107 Token handleNativeClassBodyToSkip(Listener listener, Token token) { | |
| 108 checkAllowedLibrary(listener, token); | |
| 109 listener.handleIdentifier(token); | |
| 110 token = token.next; | |
| 111 if (token.kind !== STRING_TOKEN) { | |
| 112 return listener.unexpected(token); | |
| 113 } | |
| 114 token = token.next; | |
| 115 if (token.stringValue !== '{') { | |
| 116 return listener.unexpected(token); | |
| 117 } | |
| 118 BeginGroupToken beginGroupToken = token; | |
| 119 token = beginGroupToken.endGroup; | |
| 120 return token; | |
| 121 } | |
| 122 | |
| 123 Token handleNativeClassBody(Listener listener, Token token) { | |
| 124 checkAllowedLibrary(listener, token); | |
| 125 token = token.next; | |
| 126 if (token.kind !== STRING_TOKEN) { | |
| 127 listener.unexpected(token); | |
| 128 } else { | |
| 129 token = token.next; | |
| 130 } | |
| 131 return token; | |
| 132 } | |
| 133 | |
| 134 Token handleNativeFunctionBody(ElementListener listener, Token token) { | |
| 135 checkAllowedLibrary(listener, token); | |
| 136 Token begin = token; | |
| 137 listener.beginExpressionStatement(token); | |
| 138 listener.handleIdentifier(token); | |
| 139 token = token.next; | |
| 140 if (token.kind === STRING_TOKEN) { | |
| 141 listener.beginLiteralString(token); | |
| 142 listener.endLiteralString(0); | |
| 143 listener.pushNode(new NodeList.singleton(listener.popNode())); | |
| 144 listener.endSend(token); | |
| 145 token = token.next; | |
| 146 listener.endExpressionStatement(token); | |
| 147 } else { | |
| 148 listener.pushNode(new NodeList.empty()); | |
| 149 listener.endSend(token); | |
| 150 listener.endReturnStatement(true, begin, token); | |
| 151 } | |
| 152 listener.endFunctionBody(1, begin, token); | |
| 153 // TODO(ngeoffray): expect a ';'. | |
| 154 return token.next; | |
| 155 } | |
| 156 | |
| 157 SourceString checkForNativeClass(ElementListener listener) { | |
| 158 SourceString nativeName; | |
| 159 Node node = listener.nodes.head; | |
| 160 if (node != null | |
| 161 && node.asIdentifier() != null | |
| 162 && node.asIdentifier().source.stringValue == 'native') { | |
| 163 nativeName = node.asIdentifier().token.next.value; | |
| 164 listener.popNode(); | |
| 165 } | |
| 166 return nativeName; | |
| 167 } | |
| 168 | |
| 169 bool isOverriddenMethod(FunctionElement element, | |
| 170 ClassElement cls, | |
| 171 NativeEmitter nativeEmitter) { | |
| 172 List<ClassElement> subtypes = nativeEmitter.subtypes[cls]; | |
| 173 if (subtypes == null) return false; | |
| 174 for (ClassElement subtype in subtypes) { | |
| 175 if (subtype.lookupLocalMember(element.name) != null) return true; | |
| 176 } | |
| 177 return false; | |
| 178 } | |
| 179 | |
| 180 void handleSsaNative(SsaBuilder builder, Send node) { | |
| 181 // Register NoSuchMethodException and captureStackTrace in the compiler | |
| 182 // because the dynamic dispatch for native classes may use them. | |
| 183 Compiler compiler = builder.compiler; | |
| 184 ClassElement cls = compiler.coreLibrary.find( | |
| 185 Compiler.NO_SUCH_METHOD_EXCEPTION); | |
| 186 cls.ensureResolved(compiler); | |
| 187 compiler.addToWorkList(cls.lookupConstructor(cls.name)); | |
| 188 compiler.registerStaticUse( | |
| 189 compiler.findHelper(new SourceString('captureStackTrace'))); | |
| 190 | |
| 191 FunctionElement element = builder.work.element; | |
| 192 element.setNative(); | |
| 193 NativeEmitter nativeEmitter = compiler.emitter.nativeEmitter; | |
| 194 // If what we're compiling is a getter named 'typeName' and the native | |
| 195 // class is named 'DOMType', we generate a call to the typeNameOf | |
| 196 // function attached on the isolate. | |
| 197 // The DOM classes assume that their 'typeName' property, which is | |
| 198 // not a JS property on the DOM types, returns the type name. | |
| 199 if (element.name == const SourceString('typeName') | |
| 200 && element.isGetter() | |
| 201 && nativeEmitter.toNativeName(element.enclosingElement) == 'DOMType') { | |
| 202 DartString jsCode = new DartString.literal( | |
| 203 '${nativeEmitter.typeNameOfName}(#)'); | |
| 204 List<HInstruction> inputs = | |
| 205 <HInstruction>[builder.localsHandler.readThis()]; | |
| 206 builder.push(new HForeign( | |
| 207 jsCode, const LiteralDartString('String'), inputs)); | |
| 208 return; | |
| 209 } | |
| 210 | |
| 211 HInstruction convertDartClosure(Element parameter) { | |
| 212 HInstruction local = builder.localsHandler.readLocal(parameter); | |
| 213 // TODO(ngeoffray): by better analyzing the function type and | |
| 214 // its formal parameters, we could just pass, eg closure.$call$0. | |
| 215 builder.push(new HStatic(builder.interceptors.getClosureConverter())); | |
| 216 List<HInstruction> callInputs = <HInstruction>[builder.pop(), local]; | |
| 217 HInstruction closure = new HInvokeStatic(Selector.INVOCATION_1, callInputs); | |
| 218 builder.add(closure); | |
| 219 return closure; | |
| 220 } | |
| 221 | |
| 222 FunctionParameters parameters = element.computeParameters(builder.compiler); | |
| 223 if (node.arguments.isEmpty()) { | |
| 224 List<String> arguments = <String>[]; | |
| 225 List<HInstruction> inputs = <HInstruction>[]; | |
| 226 String receiver = ''; | |
| 227 if (element.isInstanceMember()) { | |
| 228 receiver = '#.'; | |
| 229 inputs.add(builder.localsHandler.readThis()); | |
| 230 } | |
| 231 parameters.forEachParameter((Element parameter) { | |
| 232 Type type = parameter.computeType(compiler); | |
| 233 HInstruction input = builder.localsHandler.readLocal(parameter); | |
| 234 if (type is FunctionType) input = convertDartClosure(parameter); | |
| 235 inputs.add(input); | |
| 236 arguments.add('#'); | |
| 237 }); | |
| 238 String foreignParameters = Strings.join(arguments, ','); | |
| 239 | |
| 240 String dartMethodName; | |
| 241 String nativeMethodName = element.name.slowToString(); | |
| 242 String nativeMethodCall; | |
| 243 | |
| 244 if (element.kind == ElementKind.FUNCTION) { | |
| 245 dartMethodName = builder.compiler.namer.instanceMethodName( | |
| 246 element.getLibrary(), element.name, parameters.parameterCount); | |
| 247 nativeMethodCall = '$receiver$nativeMethodName($foreignParameters)'; | |
| 248 } else if (element.kind == ElementKind.GETTER) { | |
| 249 dartMethodName = builder.compiler.namer.getterName( | |
| 250 element.getLibrary(), element.name); | |
| 251 nativeMethodCall = '$receiver$nativeMethodName'; | |
| 252 } else if (element.kind == ElementKind.SETTER) { | |
| 253 dartMethodName = builder.compiler.namer.setterName( | |
| 254 element.getLibrary(), element.name); | |
| 255 nativeMethodCall = '$receiver$nativeMethodName = $foreignParameters'; | |
| 256 } else { | |
| 257 builder.compiler.internalError('unexpected kind: "${element.kind}"', | |
| 258 element: element); | |
| 259 } | |
| 260 | |
| 261 HInstruction thenInstruction; | |
| 262 void visitThen() { | |
| 263 DartString jsCode = new DartString.literal(nativeMethodCall); | |
| 264 thenInstruction = | |
| 265 new HForeign(jsCode, const LiteralDartString('Object'), inputs); | |
| 266 builder.add(thenInstruction); | |
| 267 } | |
| 268 | |
| 269 bool isNativeLiteral = false; | |
| 270 bool isOverridden = false; | |
| 271 NativeEmitter nativeEmitter = builder.compiler.emitter.nativeEmitter; | |
| 272 if (element.enclosingElement.kind == ElementKind.CLASS) { | |
| 273 ClassElement classElement = element.enclosingElement; | |
| 274 String nativeName = classElement.nativeName.slowToString(); | |
| 275 isNativeLiteral = nativeEmitter.isNativeLiteral(nativeName); | |
| 276 isOverridden = isOverriddenMethod(element, classElement, nativeEmitter); | |
| 277 } | |
| 278 if (!element.isInstanceMember() || isNativeLiteral || !isOverridden) { | |
| 279 // We generate a direct call to the native method. | |
| 280 visitThen(); | |
| 281 builder.stack.add(thenInstruction); | |
| 282 } else { | |
| 283 // Record that this method is overridden. In case of optional | |
| 284 // arguments, the emitter will generate stubs to handle them, | |
| 285 // and needs to know if the method is overridden. | |
| 286 nativeEmitter.overriddenMethods.add(element); | |
| 287 | |
| 288 // If the method is an instance method that is overridden, we | |
| 289 // generate the following code: | |
| 290 // function(params) { | |
| 291 // return Object.getPrototypeOf(this).hasOwnProperty(dartMethodName)) | |
| 292 // ? this.methodName(params) | |
| 293 // : Object.prototype.methodName.call(this, params); | |
| 294 // } | |
| 295 // | |
| 296 // The property check at the beginning is to make sure we won't | |
| 297 // call the method from the super class, in case the prototype of | |
| 298 // 'this' does not have the method yet. | |
| 299 HInstruction elseInstruction; | |
| 300 void visitElse() { | |
| 301 String params = arguments.isEmpty() ? '' : ', $foreignParameters'; | |
| 302 DartString jsCode = new DartString.literal( | |
| 303 'Object.prototype.$dartMethodName.call(#$params)'); | |
| 304 elseInstruction = | |
| 305 new HForeign(jsCode, const LiteralDartString('Object'), inputs); | |
| 306 builder.add(elseInstruction); | |
| 307 } | |
| 308 | |
| 309 HConstant constant = builder.graph.addConstantString( | |
| 310 new DartString.literal('$dartMethodName')); | |
| 311 DartString jsCode = new DartString.literal( | |
| 312 'Object.getPrototypeOf(#).hasOwnProperty(#)'); | |
| 313 builder.push(new HForeign( | |
| 314 jsCode, const LiteralDartString('Object'), | |
| 315 <HInstruction>[builder.localsHandler.readThis(), constant])); | |
| 316 | |
| 317 builder.handleIf(visitThen, visitElse); | |
| 318 | |
| 319 HPhi phi = new HPhi.manyInputs( | |
| 320 null, <HInstruction>[thenInstruction, elseInstruction]); | |
| 321 builder.current.addPhi(phi); | |
| 322 builder.stack.add(phi); | |
| 323 } | |
| 324 | |
| 325 } else if (!node.arguments.tail.isEmpty()) { | |
| 326 builder.compiler.cancel('More than one argument to native'); | |
| 327 } else { | |
| 328 // This is JS code written in a Dart file with the construct | |
| 329 // native """ ... """;. It does not work well with mangling, | |
| 330 // but there should currently be no clash between leg mangling | |
| 331 // and the library where this construct is being used. This | |
| 332 // mangling problem will go away once we switch these libraries | |
| 333 // to use Leg's 'JS' function. | |
| 334 parameters.forEachParameter((Element parameter) { | |
| 335 Type type = parameter.computeType(compiler); | |
| 336 if (type is FunctionType) { | |
| 337 HInstruction jsClosure = convertDartClosure(parameter); | |
| 338 // Because the JS code references the argument name directly, | |
| 339 // we must keep the name and assign the JS closure to it. | |
| 340 builder.add(new HForeign( | |
| 341 new DartString.literal('${parameter.name.slowToString()} = #'), | |
| 342 const LiteralDartString('void'), | |
| 343 <HInstruction>[jsClosure])); | |
| 344 } | |
| 345 }); | |
| 346 LiteralString jsCode = node.arguments.head; | |
| 347 builder.push(new HForeign(jsCode.dartString, | |
| 348 const LiteralDartString('Object'), | |
| 349 <HInstruction>[])); | |
| 350 } | |
| 351 } | |
| OLD | NEW |