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