| OLD | NEW |
| 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 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 | 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. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 part of js_backend; | 5 part of js_backend; |
| 6 | 6 |
| 7 class NativeEmitter { | 7 class NativeEmitter { |
| 8 | 8 |
| 9 CodeEmitterTask emitter; | 9 CodeEmitterTask emitter; |
| 10 CodeBuffer nativeBuffer; | 10 CodeBuffer nativeBuffer; |
| (...skipping 107 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 118 String quotedName = cls.nativeTagInfo.slowToString(); | 118 String quotedName = cls.nativeTagInfo.slowToString(); |
| 119 if (isNativeGlobal(quotedName)) { | 119 if (isNativeGlobal(quotedName)) { |
| 120 // Global object, just be like the other types for now. | 120 // Global object, just be like the other types for now. |
| 121 return quotedName.substring(3, quotedName.length - 1); | 121 return quotedName.substring(3, quotedName.length - 1); |
| 122 } else { | 122 } else { |
| 123 return quotedName.substring(2, quotedName.length - 1); | 123 return quotedName.substring(2, quotedName.length - 1); |
| 124 } | 124 } |
| 125 } | 125 } |
| 126 | 126 |
| 127 void generateNativeClass(ClassElement classElement) { | 127 void generateNativeClass(ClassElement classElement) { |
| 128 assert(classElement.backendMembers.isEmpty); |
| 128 nativeClasses.add(classElement); | 129 nativeClasses.add(classElement); |
| 129 | 130 |
| 130 assert(classElement.backendMembers.isEmpty); | 131 ClassBuilder builder = new ClassBuilder(); |
| 131 String quotedName = classElement.nativeTagInfo.slowToString(); | 132 emitter.emitClassFields(classElement, builder, classIsNative: true); |
| 133 emitter.emitClassGettersSetters(classElement, builder); |
| 134 emitter.emitInstanceMembers(classElement, builder); |
| 132 | 135 |
| 133 CodeBuffer fieldBuffer = new CodeBuffer(); | 136 // An empty native class may be omitted since the superclass methods can be |
| 134 CodeBuffer getterSetterBuffer = new CodeBuffer(); | 137 // located via the dispatch metadata. |
| 135 CodeBuffer methodBuffer = new CodeBuffer(); | 138 if (builder.properties.isEmpty) return; |
| 136 | |
| 137 emitter.emitClassFields(classElement, fieldBuffer, false, | |
| 138 classIsNative: true); | |
| 139 emitter.emitClassGettersSetters(classElement, getterSetterBuffer, false); | |
| 140 emitter.emitInstanceMembers(classElement, methodBuffer, false); | |
| 141 | |
| 142 if (methodBuffer.isEmpty | |
| 143 && fieldBuffer.isEmpty | |
| 144 && getterSetterBuffer.isEmpty) { | |
| 145 return; | |
| 146 } | |
| 147 | 139 |
| 148 String nativeTag = toNativeTag(classElement); | 140 String nativeTag = toNativeTag(classElement); |
| 149 nativeBuffer.add("$defineNativeClassName('$nativeTag',$_"); | 141 js.Expression definition = |
| 150 nativeBuffer.add('{'); | 142 js.call(js.use(defineNativeClassName), |
| 151 bool firstInMap = true; | 143 [js.string(nativeTag), builder.toObjectInitializer()]); |
| 152 if (!fieldBuffer.isEmpty) { | 144 |
| 153 firstInMap = false; | 145 nativeBuffer.add(js.prettyPrint(definition, compiler)); |
| 154 nativeBuffer.add(fieldBuffer); | 146 nativeBuffer.add('$N$n'); |
| 155 } | |
| 156 if (!getterSetterBuffer.isEmpty) { | |
| 157 if (!firstInMap) nativeBuffer.add(","); | |
| 158 firstInMap = false; | |
| 159 nativeBuffer.add("\n$_"); | |
| 160 nativeBuffer.add(getterSetterBuffer); | |
| 161 } | |
| 162 if (!methodBuffer.isEmpty) { | |
| 163 if (!firstInMap) nativeBuffer.add(","); | |
| 164 nativeBuffer.add(methodBuffer); | |
| 165 } | |
| 166 nativeBuffer.add('$n})$N$n'); | |
| 167 | 147 |
| 168 classesWithDynamicDispatch.add(classElement); | 148 classesWithDynamicDispatch.add(classElement); |
| 169 } | 149 } |
| 170 | 150 |
| 171 List<ClassElement> getDirectSubclasses(ClassElement cls) { | 151 List<ClassElement> getDirectSubclasses(ClassElement cls) { |
| 172 List<ClassElement> result = directSubtypes[cls]; | 152 List<ClassElement> result = directSubtypes[cls]; |
| 173 return result == null ? const<ClassElement>[] : result; | 153 return result == null ? const<ClassElement>[] : result; |
| 174 } | 154 } |
| 175 | 155 |
| 176 void potentiallyConvertDartClosuresToJs(List<js.Statement> statements, | 156 void potentiallyConvertDartClosuresToJs(List<js.Statement> statements, |
| (...skipping 12 matching lines...) Expand all Loading... |
| 189 for (js.Parameter stubParameter in stubParameters) { | 169 for (js.Parameter stubParameter in stubParameters) { |
| 190 if (stubParameter.name == name) { | 170 if (stubParameter.name == name) { |
| 191 DartType type = parameter.computeType(compiler).unalias(compiler); | 171 DartType type = parameter.computeType(compiler).unalias(compiler); |
| 192 if (type is FunctionType) { | 172 if (type is FunctionType) { |
| 193 // The parameter type is a function type either directly or through | 173 // The parameter type is a function type either directly or through |
| 194 // typedef(s). | 174 // typedef(s). |
| 195 int arity = type.computeArity(); | 175 int arity = type.computeArity(); |
| 196 | 176 |
| 197 statements.add( | 177 statements.add( |
| 198 new js.ExpressionStatement( | 178 new js.ExpressionStatement( |
| 199 new js.Assignment( | 179 js.assign( |
| 200 new js.VariableUse(name), | 180 js.use(name), |
| 201 new js.VariableUse(closureConverter) | 181 js.use(closureConverter).callWith( |
| 202 .callWith([new js.VariableUse(name), | 182 [js.use(name), new js.LiteralNumber('$arity')])))); |
| 203 new js.LiteralNumber('$arity')])))); | |
| 204 break; | 183 break; |
| 205 } | 184 } |
| 206 } | 185 } |
| 207 } | 186 } |
| 208 }); | 187 }); |
| 209 } | 188 } |
| 210 | 189 |
| 211 List<js.Statement> generateParameterStubStatements( | 190 List<js.Statement> generateParameterStubStatements( |
| 212 Element member, | 191 Element member, |
| 213 String invocationName, | 192 String invocationName, |
| 214 List<js.Parameter> stubParameters, | 193 List<js.Parameter> stubParameters, |
| 215 List<js.Expression> argumentsBuffer, | 194 List<js.Expression> argumentsBuffer, |
| 216 int indexOfLastOptionalArgumentInParameters) { | 195 int indexOfLastOptionalArgumentInParameters) { |
| 217 // The target JS function may check arguments.length so we need to | 196 // The target JS function may check arguments.length so we need to |
| 218 // make sure not to pass any unspecified optional arguments to it. | 197 // make sure not to pass any unspecified optional arguments to it. |
| 219 // For example, for the following Dart method: | 198 // For example, for the following Dart method: |
| 220 // foo([x, y, z]); | 199 // foo([x, y, z]); |
| 221 // The call: | 200 // The call: |
| 222 // foo(y: 1) | 201 // foo(y: 1) |
| 223 // must be turned into a JS call to: | 202 // must be turned into a JS call to: |
| 224 // foo(null, y). | 203 // foo(null, y). |
| 225 | 204 |
| 226 ClassElement classElement = member.enclosingElement; | 205 ClassElement classElement = member.enclosingElement; |
| 227 //String nativeTagInfo = classElement.nativeName.slowToString(); | |
| 228 String nativeTagInfo = classElement.nativeTagInfo.slowToString(); | 206 String nativeTagInfo = classElement.nativeTagInfo.slowToString(); |
| 229 | 207 |
| 230 List<js.Statement> statements = <js.Statement>[]; | 208 List<js.Statement> statements = <js.Statement>[]; |
| 231 potentiallyConvertDartClosuresToJs(statements, member, stubParameters); | 209 potentiallyConvertDartClosuresToJs(statements, member, stubParameters); |
| 232 | 210 |
| 233 String target; | 211 String target; |
| 234 List<js.Expression> arguments; | 212 List<js.Expression> arguments; |
| 235 | 213 |
| 236 if (!nativeMethods.contains(member)) { | 214 if (!nativeMethods.contains(member)) { |
| 237 // When calling a method that has a native body, we call it with our | 215 // When calling a method that has a native body, we call it with our |
| (...skipping 23 matching lines...) Expand all Loading... |
| 261 | 239 |
| 262 // If a method is overridden, we must check if the prototype of 'this' has the | 240 // If a method is overridden, we must check if the prototype of 'this' has the |
| 263 // method available. Otherwise, we may end up calling the method from the | 241 // method available. Otherwise, we may end up calling the method from the |
| 264 // super class. If the method is not available, we make a direct call to | 242 // super class. If the method is not available, we make a direct call to |
| 265 // Object.prototype.$methodName. This method will patch the prototype of | 243 // Object.prototype.$methodName. This method will patch the prototype of |
| 266 // 'this' to the real method. | 244 // 'this' to the real method. |
| 267 js.Statement generateMethodBodyWithPrototypeCheck( | 245 js.Statement generateMethodBodyWithPrototypeCheck( |
| 268 String methodName, | 246 String methodName, |
| 269 js.Statement body, | 247 js.Statement body, |
| 270 List<js.Parameter> parameters) { | 248 List<js.Parameter> parameters) { |
| 271 return new js.If( | 249 return js.if_( |
| 272 new js.VariableUse('Object') | 250 js.use('Object').dot('getPrototypeOf') |
| 273 .dot('getPrototypeOf') | 251 .callWith([js.use('this')]) |
| 274 .callWith([new js.VariableUse('this')]) | 252 .dot('hasOwnProperty').callWith([js.string(methodName)]), |
| 275 .dot('hasOwnProperty') | |
| 276 .callWith([new js.LiteralString("'$methodName'")]), | |
| 277 body, | 253 body, |
| 278 new js.Block( | 254 js.return_( |
| 279 <js.Statement>[ | 255 js.use('Object').dot('prototype').dot(methodName).dot('call') |
| 280 new js.Return( | 256 .callWith( |
| 281 new js.VariableUse('Object') | 257 <js.Expression>[js.use('this')]..addAll( |
| 282 .dot('prototype').dot(methodName).dot('call') | 258 parameters.map((param) => js.use(param.name)))))); |
| 283 .callWith( | |
| 284 <js.Expression>[new js.VariableUse('this')] | |
| 285 ..addAll(parameters.map((param) => | |
| 286 new js.VariableUse(param.name))))) | |
| 287 ])); | |
| 288 } | 259 } |
| 289 | 260 |
| 290 js.Block generateMethodBodyWithPrototypeCheckForElement( | 261 js.Block generateMethodBodyWithPrototypeCheckForElement( |
| 291 FunctionElement element, | 262 FunctionElement element, |
| 292 js.Block body, | 263 js.Block body, |
| 293 List<js.Parameter> parameters) { | 264 List<js.Parameter> parameters) { |
| 294 String methodName; | 265 String methodName; |
| 295 Namer namer = backend.namer; | 266 Namer namer = backend.namer; |
| 296 if (element.kind == ElementKind.FUNCTION) { | 267 if (element.kind == ElementKind.FUNCTION) { |
| 297 methodName = namer.instanceMethodName(element); | 268 methodName = namer.instanceMethodName(element); |
| 298 } else if (element.kind == ElementKind.GETTER) { | 269 } else if (element.kind == ElementKind.GETTER) { |
| 299 methodName = namer.getterName(element.getLibrary(), element.name); | 270 methodName = namer.getterName(element.getLibrary(), element.name); |
| 300 } else if (element.kind == ElementKind.SETTER) { | 271 } else if (element.kind == ElementKind.SETTER) { |
| 301 methodName = namer.setterName(element.getLibrary(), element.name); | 272 methodName = namer.setterName(element.getLibrary(), element.name); |
| 302 } else { | 273 } else { |
| 303 compiler.internalError('unexpected kind: "${element.kind}"', | 274 compiler.internalError("unexpected kind: '${element.kind}'", |
| 304 element: element); | 275 element: element); |
| 305 } | 276 } |
| 306 | 277 |
| 307 return new js.Block( | 278 return new js.Block( |
| 308 [generateMethodBodyWithPrototypeCheck(methodName, body, parameters)]); | 279 [generateMethodBodyWithPrototypeCheck(methodName, body, parameters)]); |
| 309 } | 280 } |
| 310 | 281 |
| 311 | 282 |
| 312 void emitDynamicDispatchMetadata() { | 283 void emitDynamicDispatchMetadata() { |
| 313 if (classesWithDynamicDispatch.isEmpty) return; | 284 if (classesWithDynamicDispatch.isEmpty) return; |
| (...skipping 79 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 393 varDefns[varName] = existing; | 364 varDefns[varName] = existing; |
| 394 tagDefns[tag] = new js.VariableUse(varName); | 365 tagDefns[tag] = new js.VariableUse(varName); |
| 395 expressions.add(new js.VariableUse(varName)); | 366 expressions.add(new js.VariableUse(varName)); |
| 396 } | 367 } |
| 397 } | 368 } |
| 398 } | 369 } |
| 399 } | 370 } |
| 400 walk(classElement); | 371 walk(classElement); |
| 401 | 372 |
| 402 if (!subtags.isEmpty) { | 373 if (!subtags.isEmpty) { |
| 403 expressions.add( | 374 expressions.add(js.string(Strings.join(subtags, '|'))); |
| 404 new js.LiteralString("'${Strings.join(subtags, '|')}'")); | |
| 405 } | 375 } |
| 406 js.Expression expression; | 376 js.Expression expression; |
| 407 if (expressions.length == 1) { | 377 if (expressions.length == 1) { |
| 408 expression = expressions[0]; | 378 expression = expressions[0]; |
| 409 } else { | 379 } else { |
| 410 js.Expression array = new js.ArrayInitializer.from(expressions); | 380 js.Expression array = new js.ArrayInitializer.from(expressions); |
| 411 expression = new js.Call( | 381 expression = js.call(array.dot('join'), [js.string('|')]); |
| 412 new js.PropertyAccess.field(array, 'join'), | |
| 413 [new js.LiteralString("'|'")]); | |
| 414 } | 382 } |
| 415 return expression; | 383 return expression; |
| 416 } | 384 } |
| 417 | 385 |
| 418 for (final ClassElement classElement in preorderDispatchClasses) { | 386 for (final ClassElement classElement in preorderDispatchClasses) { |
| 419 tagDefns[classElement] = makeExpression(classElement); | 387 tagDefns[classElement] = makeExpression(classElement); |
| 420 } | 388 } |
| 421 | 389 |
| 422 // Write out a thunk that builds the metadata. | 390 // Write out a thunk that builds the metadata. |
| 423 if (!tagDefns.isEmpty) { | 391 if (!tagDefns.isEmpty) { |
| (...skipping 14 matching lines...) Expand all Loading... |
| 438 } | 406 } |
| 439 | 407 |
| 440 // [table] is a list of lists, each inner list of the form: | 408 // [table] is a list of lists, each inner list of the form: |
| 441 // [dynamic-dispatch-tag, tags-of-classes-implementing-dispatch-tag] | 409 // [dynamic-dispatch-tag, tags-of-classes-implementing-dispatch-tag] |
| 442 // E.g. | 410 // E.g. |
| 443 // [['Node', 'Text|HTMLElement|HTMLDivElement|...'], ...] | 411 // [['Node', 'Text|HTMLElement|HTMLDivElement|...'], ...] |
| 444 js.Expression table = | 412 js.Expression table = |
| 445 new js.ArrayInitializer.from( | 413 new js.ArrayInitializer.from( |
| 446 preorderDispatchClasses.map((cls) => | 414 preorderDispatchClasses.map((cls) => |
| 447 new js.ArrayInitializer.from([ | 415 new js.ArrayInitializer.from([ |
| 448 new js.LiteralString("'${toNativeTag(cls)}'"), | 416 js.string(toNativeTag(cls)), |
| 449 tagDefns[cls]]))); | 417 tagDefns[cls]]))); |
| 450 | 418 |
| 451 // $.dynamicSetMetadata(table); | 419 // $.dynamicSetMetadata(table); |
| 452 statements.add( | 420 statements.add( |
| 453 new js.ExpressionStatement( | 421 new js.ExpressionStatement( |
| 454 new js.Call( | 422 new js.Call( |
| 455 new js.VariableUse(dynamicSetMetadataName), | 423 new js.VariableUse(dynamicSetMetadataName), |
| 456 [table]))); | 424 [table]))); |
| 457 | 425 |
| 458 // (function(){statements})(); | 426 // (function(){statements})(); |
| (...skipping 25 matching lines...) Expand all Loading... |
| 484 return subtypes[element] != null; | 452 return subtypes[element] != null; |
| 485 } | 453 } |
| 486 | 454 |
| 487 bool requiresNativeIsCheck(Element element) { | 455 bool requiresNativeIsCheck(Element element) { |
| 488 if (!element.isClass()) return false; | 456 if (!element.isClass()) return false; |
| 489 ClassElement cls = element; | 457 ClassElement cls = element; |
| 490 if (cls.isNative()) return true; | 458 if (cls.isNative()) return true; |
| 491 return isSupertypeOfNativeClass(element); | 459 return isSupertypeOfNativeClass(element); |
| 492 } | 460 } |
| 493 | 461 |
| 494 void emitIsChecks(Map<String, String> objectProperties) { | |
| 495 for (Element element in emitter.checkedClasses) { | |
| 496 if (!requiresNativeIsCheck(element)) continue; | |
| 497 if (element.isObject(compiler)) continue; | |
| 498 String name = backend.namer.operatorIs(element); | |
| 499 objectProperties[name] = 'function()$_{${_}return false;$_}'; | |
| 500 } | |
| 501 } | |
| 502 | |
| 503 void assembleCode(CodeBuffer targetBuffer) { | 462 void assembleCode(CodeBuffer targetBuffer) { |
| 504 if (nativeClasses.isEmpty) return; | 463 if (nativeClasses.isEmpty) return; |
| 505 emitDynamicDispatchMetadata(); | 464 emitDynamicDispatchMetadata(); |
| 506 targetBuffer.add('$defineNativeClassName = ' | 465 targetBuffer.add('$defineNativeClassName = ' |
| 507 '$defineNativeClassFunction$N$n'); | 466 '$defineNativeClassFunction$N$n'); |
| 508 | 467 |
| 468 List<js.Property> objectProperties = <js.Property>[]; |
| 469 |
| 470 void addProperty(String name, js.Expression value) { |
| 471 objectProperties.add(new js.Property(js.string(name), value)); |
| 472 } |
| 473 |
| 509 // Because of native classes, we have to generate some is checks | 474 // Because of native classes, we have to generate some is checks |
| 510 // by calling a method, instead of accessing a property. So we | 475 // by calling a method, instead of accessing a property. So we |
| 511 // attach to the JS Object prototype these methods that return | 476 // attach to the JS Object prototype these methods that return |
| 512 // false, and will be overridden by subclasses when they have to | 477 // false, and will be overridden by subclasses when they have to |
| 513 // return true. | 478 // return true. |
| 514 Map<String, String> objectProperties = new Map<String, String>(); | 479 void emitIsChecks() { |
| 515 emitIsChecks(objectProperties); | 480 for (Element element in |
| 481 Elements.sortedByPosition(emitter.checkedClasses)) { |
| 482 if (!requiresNativeIsCheck(element)) continue; |
| 483 if (element.isObject(compiler)) continue; |
| 484 String name = backend.namer.operatorIs(element); |
| 485 addProperty(name, |
| 486 js.fun([], js.block1(js.return_(new js.LiteralBool(false))))); |
| 487 } |
| 488 } |
| 489 emitIsChecks(); |
| 490 |
| 491 js.Expression makeCallOnThis(String functionName) => |
| 492 js.fun([], |
| 493 js.block1( |
| 494 js.return_( |
| 495 js.call(js.use(functionName), [js.use('this')])))); |
| 516 | 496 |
| 517 // In order to have the toString method on every native class, | 497 // In order to have the toString method on every native class, |
| 518 // we must patch the JS Object prototype with a helper method. | 498 // we must patch the JS Object prototype with a helper method. |
| 519 String toStringName = backend.namer.publicInstanceMethodNameByArity( | 499 String toStringName = backend.namer.publicInstanceMethodNameByArity( |
| 520 const SourceString('toString'), 0); | 500 const SourceString('toString'), 0); |
| 521 objectProperties[toStringName] = | 501 addProperty(toStringName, makeCallOnThis(toStringHelperName)); |
| 522 'function() { return $toStringHelperName(this); }'; | |
| 523 | 502 |
| 524 // Same as above, but for hashCode. | 503 // Same as above, but for hashCode. |
| 525 String hashCodeName = | 504 String hashCodeName = |
| 526 backend.namer.publicGetterName(const SourceString('hashCode')); | 505 backend.namer.publicGetterName(const SourceString('hashCode')); |
| 527 objectProperties[hashCodeName] = | 506 addProperty(hashCodeName, makeCallOnThis(hashCodeHelperName)); |
| 528 'function() { return $hashCodeHelperName(this); }'; | |
| 529 | 507 |
| 530 // If the native emitter has been asked to take care of the | 508 // If the native emitter has been asked to take care of the |
| 531 // noSuchMethod handlers, we do that now. | 509 // noSuchMethod handlers, we do that now. |
| 532 if (handleNoSuchMethod) { | 510 if (handleNoSuchMethod) { |
| 533 emitter.emitNoSuchMethodHandlers((String name, CodeBuffer buffer) { | 511 emitter.emitNoSuchMethodHandlers(addProperty); |
| 534 objectProperties[name] = buffer.toString(); | |
| 535 }); | |
| 536 } | 512 } |
| 537 | 513 |
| 538 // If we have any properties to add to Object.prototype, we run | 514 // If we have any properties to add to Object.prototype, we run |
| 539 // through them and add them using defineProperty. | 515 // through them and add them using defineProperty. |
| 540 if (!objectProperties.isEmpty) { | 516 if (!objectProperties.isEmpty) { |
| 541 if (emitter.compiler.enableMinification) targetBuffer.add(";"); | 517 js.Expression init = |
| 542 targetBuffer.add("(function(table) {\n" | 518 js.call( |
| 543 " for (var key in table) {\n" | 519 js.fun(['table'], |
| 544 " $defPropName(Object.prototype, key, table[key]);\n" | 520 js.block1( |
| 545 " }\n" | 521 new js.ForIn( |
| 546 "})({\n"); | 522 new js.VariableDeclarationList( |
| 547 bool first = true; | 523 [new js.VariableInitialization( |
| 548 objectProperties.forEach((String name, String function) { | 524 new js.VariableDeclaration('key'), |
| 549 if (!first) targetBuffer.add(",\n"); | 525 null)]), |
| 550 targetBuffer.add("$_$name:$_$function"); | 526 js.use('table'), |
| 551 first = false; | 527 new js.ExpressionStatement( |
| 552 }); | 528 js.call( |
| 553 targetBuffer.add("\n})$N$n"); | 529 js.use(defPropName), |
| 530 [js.use('Object').dot('prototype'), |
| 531 js.use('key'), |
| 532 new js.PropertyAccess(js.use('table'), |
| 533 js.use('key'))]))))), |
| 534 [new js.ObjectInitializer(objectProperties)]); |
| 535 |
| 536 if (emitter.compiler.enableMinification) targetBuffer.add(';'); |
| 537 targetBuffer.add(js.prettyPrint( |
| 538 new js.ExpressionStatement(init), compiler)); |
| 539 targetBuffer.add('\n'); |
| 554 } | 540 } |
| 541 |
| 555 targetBuffer.add(nativeBuffer); | 542 targetBuffer.add(nativeBuffer); |
| 556 targetBuffer.add('\n'); | 543 targetBuffer.add('\n'); |
| 557 } | 544 } |
| 558 } | 545 } |
| OLD | NEW |