| 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 class NativeEmitter { | |
| 6 | |
| 7 CodeEmitterTask emitter; | |
| 8 CodeBuffer nativeBuffer; | |
| 9 | |
| 10 // Classes that participate in dynamic dispatch. These are the | |
| 11 // classes that contain used members. | |
| 12 Set<ClassElement> classesWithDynamicDispatch; | |
| 13 | |
| 14 // Native classes found in the application. | |
| 15 Set<ClassElement> nativeClasses; | |
| 16 | |
| 17 // Caches the native subtypes of a native class. | |
| 18 Map<ClassElement, List<ClassElement>> subtypes; | |
| 19 | |
| 20 // Caches the direct native subtypes of a native class. | |
| 21 Map<ClassElement, List<ClassElement>> directSubtypes; | |
| 22 | |
| 23 // Caches the native methods that are overridden by a native class. | |
| 24 // Note that the method that overrides does not have to be native: | |
| 25 // it's the overridden method that must make sure it will dispatch | |
| 26 // to its subclass if it sees an instance whose class is a subclass. | |
| 27 Set<FunctionElement> overriddenMethods; | |
| 28 | |
| 29 // Caches the methods that have a native body. | |
| 30 Set<FunctionElement> nativeMethods; | |
| 31 | |
| 32 // Caches the methods that redirect to a JS method. | |
| 33 Map<FunctionElement, String> redirectingMethods; | |
| 34 | |
| 35 // Do we need the native emitter to take care of handling | |
| 36 // noSuchMethod for us? This flag is set to true in the emitter if | |
| 37 // it finds any native class that needs noSuchMethod handling. | |
| 38 bool handleNoSuchMethod = false; | |
| 39 | |
| 40 NativeEmitter(this.emitter) | |
| 41 : classesWithDynamicDispatch = new Set<ClassElement>(), | |
| 42 nativeClasses = new Set<ClassElement>(), | |
| 43 subtypes = new Map<ClassElement, List<ClassElement>>(), | |
| 44 directSubtypes = new Map<ClassElement, List<ClassElement>>(), | |
| 45 overriddenMethods = new Set<FunctionElement>(), | |
| 46 nativeMethods = new Set<FunctionElement>(), | |
| 47 redirectingMethods = new Map<FunctionElement, String>(), | |
| 48 nativeBuffer = new CodeBuffer(); | |
| 49 | |
| 50 Compiler get compiler() => emitter.compiler; | |
| 51 | |
| 52 void addRedirectingMethod(FunctionElement element, String name) { | |
| 53 redirectingMethods[element] = name; | |
| 54 } | |
| 55 | |
| 56 String get dynamicName() { | |
| 57 Element element = compiler.findHelper( | |
| 58 const SourceString('dynamicFunction')); | |
| 59 return compiler.namer.isolateAccess(element); | |
| 60 } | |
| 61 | |
| 62 String get dynamicSetMetadataName() { | |
| 63 Element element = compiler.findHelper( | |
| 64 const SourceString('dynamicSetMetadata')); | |
| 65 return compiler.namer.isolateAccess(element); | |
| 66 } | |
| 67 | |
| 68 String get typeNameOfName() { | |
| 69 Element element = compiler.findHelper( | |
| 70 const SourceString('getTypeNameOf')); | |
| 71 return compiler.namer.isolateAccess(element); | |
| 72 } | |
| 73 | |
| 74 String get defPropName() { | |
| 75 Element element = compiler.findHelper( | |
| 76 const SourceString('defineProperty')); | |
| 77 return compiler.namer.isolateAccess(element); | |
| 78 } | |
| 79 | |
| 80 String get toStringHelperName() { | |
| 81 Element element = compiler.findHelper( | |
| 82 const SourceString('toStringForNativeObject')); | |
| 83 return compiler.namer.isolateAccess(element); | |
| 84 } | |
| 85 | |
| 86 String get defineNativeClassName() | |
| 87 => '${compiler.namer.CURRENT_ISOLATE}.\$defineNativeClass'; | |
| 88 | |
| 89 String get defineNativeClassFunction() { | |
| 90 return """ | |
| 91 function(cls, fields, methods) { | |
| 92 var generateGetterSetter = ${emitter.generateGetterSetterFunction}; | |
| 93 for (var i = 0; i < fields.length; i++) { | |
| 94 generateGetterSetter(fields[i], methods); | |
| 95 } | |
| 96 for (var method in methods) { | |
| 97 $dynamicName(method)[cls] = methods[method]; | |
| 98 } | |
| 99 }"""; | |
| 100 } | |
| 101 | |
| 102 void generateNativeLiteral(ClassElement classElement) { | |
| 103 String quotedNative = classElement.nativeName.slowToString(); | |
| 104 String nativeCode = quotedNative.substring(2, quotedNative.length - 1); | |
| 105 String className = compiler.namer.getName(classElement); | |
| 106 nativeBuffer.add(className); | |
| 107 nativeBuffer.add(' = '); | |
| 108 nativeBuffer.add(nativeCode); | |
| 109 nativeBuffer.add(';\n'); | |
| 110 | |
| 111 void defineInstanceMember(String name, CodeBuffer value) { | |
| 112 nativeBuffer.add("$className.$name = $value;\n"); | |
| 113 } | |
| 114 | |
| 115 for (Element member in classElement.members) { | |
| 116 if (member.isInstanceMember()) { | |
| 117 emitter.addInstanceMember(member, defineInstanceMember); | |
| 118 } | |
| 119 } | |
| 120 } | |
| 121 | |
| 122 bool isNativeLiteral(String quotedName) { | |
| 123 return quotedName[1] === '='; | |
| 124 } | |
| 125 | |
| 126 bool isNativeGlobal(String quotedName) { | |
| 127 return quotedName[1] === '@'; | |
| 128 } | |
| 129 | |
| 130 String toNativeName(ClassElement cls) { | |
| 131 String quotedName = cls.nativeName.slowToString(); | |
| 132 if (isNativeGlobal(quotedName)) { | |
| 133 // Global object, just be like the other types for now. | |
| 134 return quotedName.substring(3, quotedName.length - 1); | |
| 135 } else { | |
| 136 return quotedName.substring(2, quotedName.length - 1); | |
| 137 } | |
| 138 } | |
| 139 | |
| 140 void generateNativeClass(ClassElement classElement) { | |
| 141 nativeClasses.add(classElement); | |
| 142 | |
| 143 assert(classElement.backendMembers.isEmpty()); | |
| 144 String quotedName = classElement.nativeName.slowToString(); | |
| 145 if (isNativeLiteral(quotedName)) { | |
| 146 generateNativeLiteral(classElement); | |
| 147 // The native literal kind needs to be dealt with specially when | |
| 148 // generating code for it. | |
| 149 return; | |
| 150 } | |
| 151 | |
| 152 CodeBuffer fieldBuffer = new CodeBuffer(); | |
| 153 emitter.emitClassFields(classElement, fieldBuffer); | |
| 154 | |
| 155 CodeBuffer methodBuffer = new CodeBuffer(); | |
| 156 emitter.emitInstanceMembers(classElement, methodBuffer, false); | |
| 157 | |
| 158 if (methodBuffer.isEmpty() && fieldBuffer.isEmpty()) return; | |
| 159 | |
| 160 String nativeName = toNativeName(classElement); | |
| 161 nativeBuffer.add("$defineNativeClassName('$nativeName', ["); | |
| 162 nativeBuffer.add(fieldBuffer); | |
| 163 nativeBuffer.add('], {'); | |
| 164 nativeBuffer.add(methodBuffer); | |
| 165 nativeBuffer.add('\n});\n\n'); | |
| 166 | |
| 167 classesWithDynamicDispatch.add(classElement); | |
| 168 } | |
| 169 | |
| 170 List<ClassElement> getDirectSubclasses(ClassElement cls) { | |
| 171 List<ClassElement> result = directSubtypes[cls]; | |
| 172 return result === null ? const<ClassElement>[] : result; | |
| 173 } | |
| 174 | |
| 175 void potentiallyConvertDartClosuresToJs(CodeBuffer code, | |
| 176 FunctionElement member, | |
| 177 List<String> argumentsBuffer) { | |
| 178 FunctionSignature parameters = member.computeSignature(compiler); | |
| 179 Element converter = | |
| 180 compiler.findHelper(const SourceString('convertDartClosureToJS')); | |
| 181 String closureConverter = compiler.namer.isolateAccess(converter); | |
| 182 parameters.forEachParameter((Element parameter) { | |
| 183 String name = parameter.name.slowToString(); | |
| 184 // If [name] is not in [argumentsBuffer], then the parameter is | |
| 185 // an optional parameter that was not provided for that stub. | |
| 186 if (argumentsBuffer.indexOf(name) == -1) return; | |
| 187 Type type = parameter.computeType(compiler); | |
| 188 if (type is FunctionType) { | |
| 189 int arity = type.computeArity(); | |
| 190 code.add(' $name = $closureConverter($name, $arity);\n'); | |
| 191 } | |
| 192 }); | |
| 193 } | |
| 194 | |
| 195 String generateParameterStub(Element member, | |
| 196 String invocationName, | |
| 197 String stubParameters, | |
| 198 List<String> argumentsBuffer, | |
| 199 int indexOfLastOptionalArgumentInParameters, | |
| 200 CodeBuffer buffer) { | |
| 201 // The target JS function may check arguments.length so we need to | |
| 202 // make sure not to pass any unspecified optional arguments to it. | |
| 203 // For example, for the following Dart method: | |
| 204 // foo([x, y, z]); | |
| 205 // The call: | |
| 206 // foo(y: 1) | |
| 207 // must be turned into a JS call to: | |
| 208 // foo(null, y). | |
| 209 | |
| 210 List<String> nativeArgumentsBuffer = argumentsBuffer.getRange( | |
| 211 0, indexOfLastOptionalArgumentInParameters + 1); | |
| 212 | |
| 213 ClassElement classElement = member.enclosingElement; | |
| 214 String nativeName = classElement.nativeName.slowToString(); | |
| 215 String nativeArguments = Strings.join(nativeArgumentsBuffer, ","); | |
| 216 | |
| 217 CodeBuffer code = new CodeBuffer(); | |
| 218 potentiallyConvertDartClosuresToJs(code, member, argumentsBuffer); | |
| 219 | |
| 220 if (!nativeMethods.contains(member)) { | |
| 221 // When calling a method that has a native body, we call it | |
| 222 // with our calling conventions. | |
| 223 String arguments = Strings.join(argumentsBuffer, ","); | |
| 224 code.add(' return this.${compiler.namer.getName(member)}($arguments)'); | |
| 225 } else { | |
| 226 // When calling a JS method, we call it with the native name. | |
| 227 String name = redirectingMethods[member]; | |
| 228 if (name === null) name = member.name.slowToString(); | |
| 229 code.add(' return this.$name($nativeArguments);'); | |
| 230 } | |
| 231 | |
| 232 if (isNativeLiteral(nativeName) || !overriddenMethods.contains(member)) { | |
| 233 // Call the method directly. | |
| 234 buffer.add(code.toString()); | |
| 235 } else { | |
| 236 native.generateMethodWithPrototypeCheck( | |
| 237 compiler, buffer, invocationName, code.toString(), stubParameters); | |
| 238 } | |
| 239 } | |
| 240 | |
| 241 void emitDynamicDispatchMetadata() { | |
| 242 if (classesWithDynamicDispatch.isEmpty()) return; | |
| 243 int length = classesWithDynamicDispatch.length; | |
| 244 nativeBuffer.add('// $length dynamic classes.\n'); | |
| 245 | |
| 246 // Build a pre-order traversal over all the classes and their subclasses. | |
| 247 Set<ClassElement> seen = new Set<ClassElement>(); | |
| 248 List<ClassElement> classes = <ClassElement>[]; | |
| 249 void visit(ClassElement cls) { | |
| 250 if (seen.contains(cls)) return; | |
| 251 seen.add(cls); | |
| 252 for (final ClassElement subclass in getDirectSubclasses(cls)) { | |
| 253 visit(subclass); | |
| 254 } | |
| 255 classes.add(cls); | |
| 256 } | |
| 257 for (final ClassElement classElement in classesWithDynamicDispatch) { | |
| 258 visit(classElement); | |
| 259 } | |
| 260 | |
| 261 Collection<ClassElement> dispatchClasses = classes.filter( | |
| 262 (cls) => !getDirectSubclasses(cls).isEmpty() && | |
| 263 classesWithDynamicDispatch.contains(cls)); | |
| 264 | |
| 265 nativeBuffer.add('// ${classes.length} classes\n'); | |
| 266 Collection<ClassElement> classesThatHaveSubclasses = classes.filter( | |
| 267 (ClassElement t) => !getDirectSubclasses(t).isEmpty()); | |
| 268 nativeBuffer.add('// ${classesThatHaveSubclasses.length} !leaf\n'); | |
| 269 | |
| 270 // Generate code that builds the map from cls tags used in dynamic dispatch | |
| 271 // to the set of cls tags of classes that extend (TODO: or implement) those | |
| 272 // classes. The set is represented as a string of tags joined with '|'. | |
| 273 // This is easily split into an array of tags, or converted into a regexp. | |
| 274 // | |
| 275 // To reduce the size of the sets, subsets are CSE-ed out into variables. | |
| 276 // The sets could be much smaller if we could make assumptions about the | |
| 277 // cls tags of other classes (which are constructor names or part of the | |
| 278 // result of Object.protocls.toString). For example, if objects that are | |
| 279 // Dart objects could be easily excluded, then we might be able to simplify | |
| 280 // the test, replacing dozens of HTMLxxxElement classes with the regexp | |
| 281 // /HTML.*Element/. | |
| 282 | |
| 283 // Temporary variables for common substrings. | |
| 284 List<String> varNames = <String>[]; | |
| 285 // var -> expression | |
| 286 Map<String, String> varDefns = <String>{}; | |
| 287 // tag -> expression (a string or a variable) | |
| 288 Map<ClassElement, String> tagDefns = new Map<ClassElement, String>(); | |
| 289 | |
| 290 String makeExpression(ClassElement classElement) { | |
| 291 // Expression fragments for this set of cls keys. | |
| 292 List<String> expressions = <String>[]; | |
| 293 // TODO: Remove if cls is abstract. | |
| 294 List<String> subtags = [toNativeName(classElement)]; | |
| 295 void walk(ClassElement cls) { | |
| 296 for (final ClassElement subclass in getDirectSubclasses(cls)) { | |
| 297 ClassElement tag = subclass; | |
| 298 String existing = tagDefns[tag]; | |
| 299 if (existing == null) { | |
| 300 subtags.add(toNativeName(tag)); | |
| 301 walk(subclass); | |
| 302 } else { | |
| 303 if (varDefns.containsKey(existing)) { | |
| 304 expressions.add(existing); | |
| 305 } else { | |
| 306 String varName = 'v${varNames.length}/*${tag}*/'; | |
| 307 varNames.add(varName); | |
| 308 varDefns[varName] = existing; | |
| 309 tagDefns[tag] = varName; | |
| 310 expressions.add(varName); | |
| 311 } | |
| 312 } | |
| 313 } | |
| 314 } | |
| 315 walk(classElement); | |
| 316 String constantPart = "'${Strings.join(subtags, '|')}'"; | |
| 317 if (constantPart != "''") expressions.add(constantPart); | |
| 318 String expression; | |
| 319 if (expressions.length == 1) { | |
| 320 expression = expressions[0]; | |
| 321 } else { | |
| 322 expression = "[${Strings.join(expressions, ',')}].join('|')"; | |
| 323 } | |
| 324 return expression; | |
| 325 } | |
| 326 | |
| 327 for (final ClassElement classElement in dispatchClasses) { | |
| 328 tagDefns[classElement] = makeExpression(classElement); | |
| 329 } | |
| 330 | |
| 331 // Write out a thunk that builds the metadata. | |
| 332 if (!tagDefns.isEmpty()) { | |
| 333 nativeBuffer.add('(function(){\n'); | |
| 334 | |
| 335 for (final String varName in varNames) { | |
| 336 nativeBuffer.add(' var ${varName} = ${varDefns[varName]};\n'); | |
| 337 } | |
| 338 | |
| 339 nativeBuffer.add(' var table = [\n'); | |
| 340 nativeBuffer.add( | |
| 341 ' // [dynamic-dispatch-tag, ' | |
| 342 'tags of classes implementing dynamic-dispatch-tag]'); | |
| 343 bool needsComma = false; | |
| 344 List<String> entries = <String>[]; | |
| 345 for (final ClassElement cls in dispatchClasses) { | |
| 346 String clsName = toNativeName(cls); | |
| 347 entries.add("\n ['$clsName', ${tagDefns[cls]}]"); | |
| 348 } | |
| 349 nativeBuffer.add(Strings.join(entries, ',')); | |
| 350 nativeBuffer.add('];\n'); | |
| 351 nativeBuffer.add('$dynamicSetMetadataName(table);\n'); | |
| 352 | |
| 353 nativeBuffer.add('})();\n'); | |
| 354 } | |
| 355 } | |
| 356 | |
| 357 bool isSupertypeOfNativeClass(Element element) { | |
| 358 if (element.isTypeVariable()) { | |
| 359 compiler.cancel("Is check for type variable", element: element); | |
| 360 return false; | |
| 361 } | |
| 362 if (element.computeType(compiler) is FunctionType) return false; | |
| 363 | |
| 364 if (!element.isClass()) { | |
| 365 compiler.cancel("Is check does not handle element", element: element); | |
| 366 return false; | |
| 367 } | |
| 368 | |
| 369 return subtypes[element] !== null; | |
| 370 } | |
| 371 | |
| 372 bool requiresNativeIsCheck(Element element) { | |
| 373 if (!element.isClass()) return false; | |
| 374 ClassElement cls = element; | |
| 375 if (cls.isNative()) return true; | |
| 376 return isSupertypeOfNativeClass(element); | |
| 377 } | |
| 378 | |
| 379 void emitIsChecks(Map<String, String> objectProperties) { | |
| 380 for (Element type in compiler.codegenWorld.isChecks) { | |
| 381 if (!requiresNativeIsCheck(type)) continue; | |
| 382 String name = compiler.namer.operatorIs(type); | |
| 383 objectProperties[name] = 'function() { return false; }'; | |
| 384 } | |
| 385 } | |
| 386 | |
| 387 void assembleCode(CodeBuffer targetBuffer) { | |
| 388 if (nativeClasses.isEmpty()) return; | |
| 389 emitDynamicDispatchMetadata(); | |
| 390 targetBuffer.add('$defineNativeClassName = ' | |
| 391 '$defineNativeClassFunction;\n\n'); | |
| 392 | |
| 393 // Because of native classes, we have to generate some is checks | |
| 394 // by calling a method, instead of accessing a property. So we | |
| 395 // attach to the JS Object prototype these methods that return | |
| 396 // false, and will be overridden by subclasses when they have to | |
| 397 // return true. | |
| 398 Map<String, String> objectProperties = new Map<String, String>(); | |
| 399 emitIsChecks(objectProperties); | |
| 400 | |
| 401 // In order to have the toString method on every native class, | |
| 402 // we must patch the JS Object prototype with a helper method. | |
| 403 String toStringName = compiler.namer.instanceMethodName( | |
| 404 null, const SourceString('toString'), 0); | |
| 405 objectProperties[toStringName] = | |
| 406 'function() { return $toStringHelperName(this); }'; | |
| 407 | |
| 408 // If the native emitter has been asked to take care of the | |
| 409 // noSuchMethod handlers, we do that now. | |
| 410 if (handleNoSuchMethod) { | |
| 411 emitter.emitNoSuchMethodHandlers((String name, CodeBuffer buffer) { | |
| 412 objectProperties[name] = buffer.toString(); | |
| 413 }); | |
| 414 } | |
| 415 | |
| 416 // If we have any properties to add to Object.prototype, we run | |
| 417 // through them and add them using defineProperty. | |
| 418 if (!objectProperties.isEmpty()) { | |
| 419 targetBuffer.add("(function(table) {\n" | |
| 420 " for (var key in table) {\n" | |
| 421 " $defPropName(Object.prototype, key, table[key]);\n" | |
| 422 " }\n" | |
| 423 "})({\n"); | |
| 424 bool first = true; | |
| 425 objectProperties.forEach((String name, String function) { | |
| 426 if (!first) targetBuffer.add(",\n"); | |
| 427 targetBuffer.add(" $name: $function"); | |
| 428 first = false; | |
| 429 }); | |
| 430 targetBuffer.add("\n});\n\n"); | |
| 431 } | |
| 432 targetBuffer.add('$nativeBuffer'); | |
| 433 targetBuffer.add('\n'); | |
| 434 } | |
| 435 } | |
| OLD | NEW |