| 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 Compiler compiler; | |
| 8 StringBuffer buffer; | |
| 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 direct native subtypes of a native class. | |
| 18 Map<ClassElement, List<ClassElement>> subtypes; | |
| 19 | |
| 20 // Caches the native methods that are overridden by a native class. | |
| 21 // Note that the method that overrides does not have to be native: | |
| 22 // it's the overridden method that must make sure it will dispatch | |
| 23 // to its subclass if it sees an instance whose class is a subclass. | |
| 24 Set<FunctionElement> overriddenMethods; | |
| 25 | |
| 26 NativeEmitter(this.compiler) | |
| 27 : classesWithDynamicDispatch = new Set<ClassElement>(), | |
| 28 nativeClasses = new Set<ClassElement>(), | |
| 29 subtypes = new Map<ClassElement, List<ClassElement>>(), | |
| 30 overriddenMethods = new Set<FunctionElement>(), | |
| 31 buffer = new StringBuffer(); | |
| 32 | |
| 33 String get dynamicName() { | |
| 34 Element element = compiler.findHelper( | |
| 35 const SourceString('dynamicFunction')); | |
| 36 return compiler.namer.isolateAccess(element); | |
| 37 } | |
| 38 | |
| 39 String get dynamicSetMetadataName() { | |
| 40 Element element = compiler.findHelper( | |
| 41 const SourceString('dynamicSetMetadata')); | |
| 42 return compiler.namer.isolateAccess(element); | |
| 43 } | |
| 44 | |
| 45 String get typeNameOfName() { | |
| 46 Element element = compiler.findHelper( | |
| 47 const SourceString('getTypeNameOf')); | |
| 48 return compiler.namer.isolateAccess(element); | |
| 49 } | |
| 50 | |
| 51 String get defPropName() { | |
| 52 Element element = compiler.findHelper( | |
| 53 const SourceString('defineProperty')); | |
| 54 return compiler.namer.isolateAccess(element); | |
| 55 } | |
| 56 | |
| 57 String get toStringHelperName() { | |
| 58 Element element = compiler.findHelper( | |
| 59 const SourceString('toStringForNativeObject')); | |
| 60 return compiler.namer.isolateAccess(element); | |
| 61 } | |
| 62 | |
| 63 void generateNativeLiteral(ClassElement classElement) { | |
| 64 String quotedNative = classElement.nativeName.slowToString(); | |
| 65 String nativeCode = quotedNative.substring(2, quotedNative.length - 1); | |
| 66 String className = compiler.namer.getName(classElement); | |
| 67 buffer.add(className); | |
| 68 buffer.add(' = '); | |
| 69 buffer.add(nativeCode); | |
| 70 buffer.add(';\n'); | |
| 71 | |
| 72 String attachTo(name) => "$className.$name"; | |
| 73 | |
| 74 for (Element member in classElement.members) { | |
| 75 if (member.isInstanceMember()) { | |
| 76 compiler.emitter.addInstanceMember( | |
| 77 member, attachTo, buffer, isNative: true); | |
| 78 } | |
| 79 } | |
| 80 } | |
| 81 | |
| 82 bool isNativeLiteral(String quotedName) { | |
| 83 return quotedName[1] === '='; | |
| 84 } | |
| 85 | |
| 86 bool isNativeGlobal(String quotedName) { | |
| 87 return quotedName[1] === '@'; | |
| 88 } | |
| 89 | |
| 90 String toNativeName(ClassElement cls) { | |
| 91 String quotedName = cls.nativeName.slowToString(); | |
| 92 if (isNativeGlobal(quotedName)) { | |
| 93 // Global object, just be like the other types for now. | |
| 94 return quotedName.substring(3, quotedName.length - 1); | |
| 95 } else { | |
| 96 return quotedName.substring(2, quotedName.length - 1); | |
| 97 } | |
| 98 } | |
| 99 | |
| 100 void generateNativeClass(ClassElement classElement) { | |
| 101 nativeClasses.add(classElement); | |
| 102 | |
| 103 assert(classElement.backendMembers.isEmpty()); | |
| 104 String quotedName = classElement.nativeName.slowToString(); | |
| 105 if (isNativeLiteral(quotedName)) { | |
| 106 generateNativeLiteral(classElement); | |
| 107 // The native literal kind needs to be dealt with specially when | |
| 108 // generating code for it. | |
| 109 return; | |
| 110 } | |
| 111 | |
| 112 String nativeName = toNativeName(classElement); | |
| 113 bool hasUsedSelectors = false; | |
| 114 | |
| 115 String attachTo(String name) { | |
| 116 hasUsedSelectors = true; | |
| 117 return "$dynamicName('$name').$nativeName"; | |
| 118 } | |
| 119 | |
| 120 for (Element member in classElement.members) { | |
| 121 if (member.isInstanceMember()) { | |
| 122 compiler.emitter.addInstanceMember( | |
| 123 member, attachTo, buffer, isNative: true); | |
| 124 } | |
| 125 } | |
| 126 | |
| 127 compiler.emitter.generateTypeTests(classElement, (Element other) { | |
| 128 assert(requiresNativeIsCheck(other)); | |
| 129 buffer.add('${attachTo(compiler.namer.operatorIs(other))} = '); | |
| 130 buffer.add('function() { return true; };\n'); | |
| 131 }); | |
| 132 | |
| 133 if (hasUsedSelectors) classesWithDynamicDispatch.add(classElement); | |
| 134 } | |
| 135 | |
| 136 List<ClassElement> getDirectSubclasses(ClassElement cls) { | |
| 137 List<ClassElement> result = subtypes[cls]; | |
| 138 if (result === null) result = const<ClassElement>[]; | |
| 139 return result; | |
| 140 } | |
| 141 | |
| 142 void emitParameterStub(Element member, | |
| 143 String invocationName, | |
| 144 String stubParameters, | |
| 145 List<String> argumentsBuffer, | |
| 146 int indexOfLastOptionalArgumentInParameters) { | |
| 147 // The target JS function may check arguments.length so we need to | |
| 148 // make sure not to pass any unspecified optional arguments to it. | |
| 149 // For example, for the following Dart method: | |
| 150 // foo([x, y, z]); | |
| 151 // The call: | |
| 152 // foo(y: 1) | |
| 153 // must be turned into a JS call to: | |
| 154 // foo(null, y). | |
| 155 | |
| 156 List<String> nativeArgumentsBuffer = argumentsBuffer.getRange( | |
| 157 0, indexOfLastOptionalArgumentInParameters + 1); | |
| 158 | |
| 159 ClassElement classElement = member.enclosingElement; | |
| 160 String nativeName = classElement.nativeName.slowToString(); | |
| 161 String nativeArguments = Strings.join(nativeArgumentsBuffer, ","); | |
| 162 | |
| 163 if (isNativeLiteral(nativeName) || !overriddenMethods.contains(member)) { | |
| 164 // Call the method directly. | |
| 165 buffer.add(' return this.${member.name.slowToString()}'); | |
| 166 buffer.add('($nativeArguments)'); | |
| 167 return; | |
| 168 } | |
| 169 | |
| 170 // If the method is overridden, we must check if the prototype of | |
| 171 // 'this' has the method available. Otherwise, we may end up | |
| 172 // calling the method from the super class. If the method is not | |
| 173 // available, we make a direct call to | |
| 174 // Object.prototype.$invocationName. This method will patch the | |
| 175 // prototype of 'this' to the real method. | |
| 176 | |
| 177 buffer.add(' if (Object.getPrototypeOf(this).hasOwnProperty('); | |
| 178 buffer.add("'$invocationName')) {\n"); | |
| 179 buffer.add(' return this.${member.name.slowToString()}'); | |
| 180 buffer.add('($nativeArguments)'); | |
| 181 buffer.add('\n }\n'); | |
| 182 buffer.add(' return Object.prototype.$invocationName.call(this'); | |
| 183 buffer.add(stubParameters == '' ? '' : ', $stubParameters'); | |
| 184 buffer.add(');'); | |
| 185 } | |
| 186 | |
| 187 void emitDynamicDispatchMetadata() { | |
| 188 if (classesWithDynamicDispatch.isEmpty()) return; | |
| 189 buffer.add('// ${classesWithDynamicDispatch.length} dynamic classes.\n'); | |
| 190 | |
| 191 // Build a pre-order traversal over all the classes and their subclasses. | |
| 192 Set<ClassElement> seen = new Set<ClassElement>(); | |
| 193 List<ClassElement> classes = <ClassElement>[]; | |
| 194 void visit(ClassElement cls) { | |
| 195 if (seen.contains(cls)) return; | |
| 196 seen.add(cls); | |
| 197 for (final ClassElement subclass in getDirectSubclasses(cls)) { | |
| 198 visit(subclass); | |
| 199 } | |
| 200 classes.add(cls); | |
| 201 } | |
| 202 for (final ClassElement cls in classesWithDynamicDispatch) { | |
| 203 visit(cls); | |
| 204 } | |
| 205 | |
| 206 Collection<ClassElement> dispatchClasses = classes.filter( | |
| 207 (cls) => !getDirectSubclasses(cls).isEmpty() && | |
| 208 classesWithDynamicDispatch.contains(cls)); | |
| 209 | |
| 210 buffer.add('// ${classes.length} classes\n'); | |
| 211 Collection<ClassElement> classesThatHaveSubclasses = classes.filter( | |
| 212 (ClassElement t) => !getDirectSubclasses(t).isEmpty()); | |
| 213 buffer.add('// ${classesThatHaveSubclasses.length} !leaf\n'); | |
| 214 | |
| 215 // Generate code that builds the map from cls tags used in dynamic dispatch | |
| 216 // to the set of cls tags of classes that extend (TODO: or implement) those | |
| 217 // classes. The set is represented as a string of tags joined with '|'. | |
| 218 // This is easily split into an array of tags, or converted into a regexp. | |
| 219 // | |
| 220 // To reduce the size of the sets, subsets are CSE-ed out into variables. | |
| 221 // The sets could be much smaller if we could make assumptions about the | |
| 222 // cls tags of other classes (which are constructor names or part of the | |
| 223 // result of Object.protocls.toString). For example, if objects that are | |
| 224 // Dart objects could be easily excluded, then we might be able to simplify | |
| 225 // the test, replacing dozens of HTMLxxxElement classes with the regexp | |
| 226 // /HTML.*Element/. | |
| 227 | |
| 228 // Temporary variables for common substrings. | |
| 229 List<String> varNames = <String>[]; | |
| 230 // var -> expression | |
| 231 Map<String, String> varDefns = <String>{}; | |
| 232 // tag -> expression (a string or a variable) | |
| 233 Map<ClassElement, String> tagDefns = new Map<ClassElement, String>(); | |
| 234 | |
| 235 String makeExpression(ClassElement cls) { | |
| 236 // Expression fragments for this set of cls keys. | |
| 237 List<String> expressions = <String>[]; | |
| 238 // TODO: Remove if cls is abstract. | |
| 239 List<String> subtags = [toNativeName(cls)]; | |
| 240 void walk(ClassElement cls) { | |
| 241 for (final ClassElement subclass in getDirectSubclasses(cls)) { | |
| 242 ClassElement tag = subclass; | |
| 243 String existing = tagDefns[tag]; | |
| 244 if (existing == null) { | |
| 245 subtags.add(toNativeName(tag)); | |
| 246 walk(subclass); | |
| 247 } else { | |
| 248 if (varDefns.containsKey(existing)) { | |
| 249 expressions.add(existing); | |
| 250 } else { | |
| 251 String varName = 'v${varNames.length}/*${tag}*/'; | |
| 252 varNames.add(varName); | |
| 253 varDefns[varName] = existing; | |
| 254 tagDefns[tag] = varName; | |
| 255 expressions.add(varName); | |
| 256 } | |
| 257 } | |
| 258 } | |
| 259 } | |
| 260 walk(cls); | |
| 261 String constantPart = "'${Strings.join(subtags, '|')}'"; | |
| 262 if (constantPart != "''") expressions.add(constantPart); | |
| 263 String expression; | |
| 264 if (expressions.length == 1) { | |
| 265 expression = expressions[0]; | |
| 266 } else { | |
| 267 expression = "[${Strings.join(expressions, ',')}].join('|')"; | |
| 268 } | |
| 269 return expression; | |
| 270 } | |
| 271 | |
| 272 for (final ClassElement cls in dispatchClasses) { | |
| 273 tagDefns[cls] = makeExpression(cls); | |
| 274 } | |
| 275 | |
| 276 // Write out a thunk that builds the metadata. | |
| 277 | |
| 278 if (!tagDefns.isEmpty()) { | |
| 279 buffer.add('(function(){\n'); | |
| 280 | |
| 281 for (final String varName in varNames) { | |
| 282 buffer.add(' var ${varName} = ${varDefns[varName]};\n'); | |
| 283 } | |
| 284 | |
| 285 buffer.add(' var table = [\n'); | |
| 286 buffer.add( | |
| 287 ' // [dynamic-dispatch-tag, ' | |
| 288 'tags of classes implementing dynamic-dispatch-tag]'); | |
| 289 bool needsComma = false; | |
| 290 List<String> entries = <String>[]; | |
| 291 for (final ClassElement cls in dispatchClasses) { | |
| 292 String clsName = toNativeName(cls); | |
| 293 entries.add("\n ['$clsName', ${tagDefns[cls]}]"); | |
| 294 } | |
| 295 buffer.add(Strings.join(entries, ',')); | |
| 296 buffer.add('];\n'); | |
| 297 buffer.add('$dynamicSetMetadataName(table);\n'); | |
| 298 | |
| 299 buffer.add('})();\n'); | |
| 300 } | |
| 301 } | |
| 302 | |
| 303 bool isSupertypeOfNativeClass(Element element) { | |
| 304 if (element.isTypeVariable()) { | |
| 305 compiler.cancel("Is check for type variable", element: work.element); | |
| 306 return false; | |
| 307 } | |
| 308 if (element.computeType(compiler) is FunctionType) return false; | |
| 309 | |
| 310 if (!element.isClass()) { | |
| 311 compiler.cancel("Is check does not handle element", element: element); | |
| 312 return false; | |
| 313 } | |
| 314 | |
| 315 return subtypes[element] !== null; | |
| 316 } | |
| 317 | |
| 318 bool requiresNativeIsCheck(Element element) { | |
| 319 if (!element.isClass()) return false; | |
| 320 ClassElement cls = element; | |
| 321 if (cls.isNative()) return true; | |
| 322 return isSupertypeOfNativeClass(element); | |
| 323 } | |
| 324 | |
| 325 void emitIsChecks(StringBuffer buffer) { | |
| 326 for (Element type in compiler.universe.isChecks) { | |
| 327 if (!requiresNativeIsCheck(type)) continue; | |
| 328 String name = compiler.namer.operatorIs(type); | |
| 329 buffer.add("$defPropName(Object.prototype, '$name', "); | |
| 330 buffer.add('function() { return false; });\n'); | |
| 331 } | |
| 332 } | |
| 333 | |
| 334 void assembleCode(StringBuffer other) { | |
| 335 if (nativeClasses.isEmpty()) return; | |
| 336 | |
| 337 // Because of native classes, we have to generate some is checks | |
| 338 // by calling a method, instead of accessing a property. So we | |
| 339 // attach to the JS Object prototype these methods that return | |
| 340 // false, and will be overridden by subclasses when they have to | |
| 341 // return true. | |
| 342 StringBuffer objectProperties = new StringBuffer(); | |
| 343 emitIsChecks(objectProperties); | |
| 344 | |
| 345 // In order to have the toString method on every native class, | |
| 346 // we must patch the JS Object prototype with a helper method. | |
| 347 String toStringName = compiler.namer.instanceMethodName( | |
| 348 null, const SourceString('toString'), 0); | |
| 349 objectProperties.add("$defPropName(Object.prototype, '$toStringName', "); | |
| 350 objectProperties.add( | |
| 351 'function() { return $toStringHelperName(this); });\n'); | |
| 352 | |
| 353 // Finally, emit the code in the main buffer. | |
| 354 other.add('(function() {\n$objectProperties$buffer\n})();\n'); | |
| 355 } | |
| 356 } | |
| OLD | NEW |