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 |