| OLD | NEW |
| 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2013, 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 dart2js.js_emitter; | 5 part of dart2js.js_emitter; |
| 6 | 6 |
| 7 class NsmEmitter extends CodeEmitterHelper { | 7 class NsmEmitter extends CodeEmitterHelper { |
| 8 final List<Selector> trivialNsmHandlers = <Selector>[]; | 8 final List<Selector> trivialNsmHandlers = <Selector>[]; |
| 9 | 9 |
| 10 /// If this is true then we can generate the noSuchMethod handlers at startup | 10 /// If this is true then we can generate the noSuchMethod handlers at startup |
| (...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 56 | 56 |
| 57 // Set flag used by generateMethod helper below. If we have very few | 57 // Set flag used by generateMethod helper below. If we have very few |
| 58 // handlers we use addProperty for them all, rather than try to generate | 58 // handlers we use addProperty for them all, rather than try to generate |
| 59 // them at runtime. | 59 // them at runtime. |
| 60 bool haveVeryFewNoSuchMemberHandlers = | 60 bool haveVeryFewNoSuchMemberHandlers = |
| 61 (addedJsNames.length < VERY_FEW_NO_SUCH_METHOD_HANDLERS); | 61 (addedJsNames.length < VERY_FEW_NO_SUCH_METHOD_HANDLERS); |
| 62 | 62 |
| 63 jsAst.Expression generateMethod(String jsName, Selector selector) { | 63 jsAst.Expression generateMethod(String jsName, Selector selector) { |
| 64 // Values match JSInvocationMirror in js-helper library. | 64 // Values match JSInvocationMirror in js-helper library. |
| 65 int type = selector.invocationMirrorKind; | 65 int type = selector.invocationMirrorKind; |
| 66 List<String> parameterNames = | 66 List<jsAst.Parameter> parameters = <jsAst.Parameter>[]; |
| 67 new List.generate(selector.argumentCount, (i) => '\$$i'); | 67 for (int i = 0; i < selector.argumentCount; i++) { |
| 68 parameters.add(new jsAst.Parameter('\$$i')); |
| 69 } |
| 68 | 70 |
| 69 List<jsAst.Expression> argNames = | 71 List<jsAst.Expression> argNames = |
| 70 selector.getOrderedNamedArguments().map((String name) => | 72 selector.getOrderedNamedArguments().map((String name) => |
| 71 js.string(name)).toList(); | 73 js.string(name)).toList(); |
| 72 | 74 |
| 73 String methodName = selector.invocationMirrorMemberName; | 75 String methodName = selector.invocationMirrorMemberName; |
| 74 String internalName = namer.invocationMirrorInternalName(selector); | 76 String internalName = namer.invocationMirrorInternalName(selector); |
| 75 String reflectionName = task.getReflectionName(selector, internalName); | 77 String reflectionName = task.getReflectionName(selector, internalName); |
| 76 if (!haveVeryFewNoSuchMemberHandlers && | 78 if (!haveVeryFewNoSuchMemberHandlers && |
| 77 isTrivialNsmHandler(type, argNames, selector, internalName) && | 79 isTrivialNsmHandler(type, argNames, selector, internalName) && |
| 78 reflectionName == null) { | 80 reflectionName == null) { |
| 79 trivialNsmHandlers.add(selector); | 81 trivialNsmHandlers.add(selector); |
| 80 return null; | 82 return null; |
| 81 } | 83 } |
| 82 | 84 |
| 83 assert(backend.isInterceptedName(Compiler.NO_SUCH_METHOD)); | 85 assert(backend.isInterceptedName(Compiler.NO_SUCH_METHOD)); |
| 84 jsAst.Expression expression = js('this.#(this, #(#, #, #, #, #))', [ | 86 jsAst.Expression expression = js('this.$noSuchMethodName')( |
| 85 noSuchMethodName, | 87 [js('this'), |
| 86 namer.elementAccess(backend.getCreateInvocationMirror()), | 88 namer.elementAccess(backend.getCreateInvocationMirror())([ |
| 87 js.string(compiler.enableMinification ? | 89 js.string(compiler.enableMinification ? |
| 88 internalName : methodName), | 90 internalName : methodName), |
| 89 js.string(internalName), | 91 js.string(internalName), |
| 90 js.number(type), | 92 type, |
| 91 new jsAst.ArrayInitializer.from(parameterNames.map(js)), | 93 new jsAst.ArrayInitializer.from( |
| 92 new jsAst.ArrayInitializer.from(argNames)]); | 94 parameters.map((param) => js(param.name)).toList()), |
| 93 | 95 new jsAst.ArrayInitializer.from(argNames)])]); |
| 94 if (backend.isInterceptedName(selector.name)) { | 96 parameters = backend.isInterceptedName(selector.name) |
| 95 return js(r'function($receiver, #) { return # }', | 97 ? ([new jsAst.Parameter('\$receiver')]..addAll(parameters)) |
| 96 [parameterNames, expression]); | 98 : parameters; |
| 97 } else { | 99 return js.fun(parameters, js.return_(expression)); |
| 98 return js(r'function(#) { return # }', [parameterNames, expression]); | |
| 99 } | |
| 100 } | 100 } |
| 101 | 101 |
| 102 for (String jsName in addedJsNames.keys.toList()..sort()) { | 102 for (String jsName in addedJsNames.keys.toList()..sort()) { |
| 103 Selector selector = addedJsNames[jsName]; | 103 Selector selector = addedJsNames[jsName]; |
| 104 jsAst.Expression method = generateMethod(jsName, selector); | 104 jsAst.Expression method = generateMethod(jsName, selector); |
| 105 if (method != null) { | 105 if (method != null) { |
| 106 addProperty(jsName, method); | 106 addProperty(jsName, method); |
| 107 String reflectionName = task.getReflectionName(selector, jsName); | 107 String reflectionName = task.getReflectionName(selector, jsName); |
| 108 if (reflectionName != null) { | 108 if (reflectionName != null) { |
| 109 bool accessible = compiler.world.allFunctions.filter(selector).any( | 109 bool accessible = compiler.world.allFunctions.filter(selector).any( |
| (...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 169 * as base 88 numbers. The difference is 2, which is "c" in lower-case- | 169 * as base 88 numbers. The difference is 2, which is "c" in lower-case- |
| 170 * terminated base 26. | 170 * terminated base 26. |
| 171 * | 171 * |
| 172 * The reason we don't encode long minified names with this method is that | 172 * The reason we don't encode long minified names with this method is that |
| 173 * decoding the base 88 numbers would overflow JavaScript's puny integers. | 173 * decoding the base 88 numbers would overflow JavaScript's puny integers. |
| 174 * | 174 * |
| 175 * There are some selectors that have a special calling convention (because | 175 * There are some selectors that have a special calling convention (because |
| 176 * they are called with the receiver as the first argument). They need a | 176 * they are called with the receiver as the first argument). They need a |
| 177 * slightly different noSuchMethod handler, so we handle these first. | 177 * slightly different noSuchMethod handler, so we handle these first. |
| 178 */ | 178 */ |
| 179 List<jsAst.Statement> buildTrivialNsmHandlers() { | 179 void addTrivialNsmHandlers(List<jsAst.Node> statements) { |
| 180 List<jsAst.Statement> statements = <jsAst.Statement>[]; | 180 if (trivialNsmHandlers.length == 0) return; |
| 181 if (trivialNsmHandlers.length == 0) return statements; | |
| 182 // Sort by calling convention, JS name length and by JS name. | 181 // Sort by calling convention, JS name length and by JS name. |
| 183 trivialNsmHandlers.sort((a, b) { | 182 trivialNsmHandlers.sort((a, b) { |
| 184 bool aIsIntercepted = backend.isInterceptedName(a.name); | 183 bool aIsIntercepted = backend.isInterceptedName(a.name); |
| 185 bool bIsIntercepted = backend.isInterceptedName(b.name); | 184 bool bIsIntercepted = backend.isInterceptedName(b.name); |
| 186 if (aIsIntercepted != bIsIntercepted) return aIsIntercepted ? -1 : 1; | 185 if (aIsIntercepted != bIsIntercepted) return aIsIntercepted ? -1 : 1; |
| 187 String aName = namer.invocationMirrorInternalName(a); | 186 String aName = namer.invocationMirrorInternalName(a); |
| 188 String bName = namer.invocationMirrorInternalName(b); | 187 String bName = namer.invocationMirrorInternalName(b); |
| 189 if (aName.length != bName.length) return aName.length - bName.length; | 188 if (aName.length != bName.length) return aName.length - bName.length; |
| 190 return aName.compareTo(bName); | 189 return aName.compareTo(bName); |
| 191 }); | 190 }); |
| (...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 262 diffEncoding.write(","); | 261 diffEncoding.write(","); |
| 263 } | 262 } |
| 264 diffEncoding.write(short); | 263 diffEncoding.write(short); |
| 265 } | 264 } |
| 266 nameCounter++; | 265 nameCounter++; |
| 267 } | 266 } |
| 268 | 267 |
| 269 // Startup code that loops over the method names and puts handlers on the | 268 // Startup code that loops over the method names and puts handlers on the |
| 270 // Object class to catch noSuchMethod invocations. | 269 // Object class to catch noSuchMethod invocations. |
| 271 ClassElement objectClass = compiler.objectClass; | 270 ClassElement objectClass = compiler.objectClass; |
| 272 jsAst.Expression createInvocationMirror = namer.elementAccess( | 271 String createInvocationMirror = namer.isolateAccess( |
| 273 backend.getCreateInvocationMirror()); | 272 backend.getCreateInvocationMirror()); |
| 274 String noSuchMethodName = namer.publicInstanceMethodNameByArity( | 273 String noSuchMethodName = namer.publicInstanceMethodNameByArity( |
| 275 Compiler.NO_SUCH_METHOD, Compiler.NO_SUCH_METHOD_ARG_COUNT); | 274 Compiler.NO_SUCH_METHOD, Compiler.NO_SUCH_METHOD_ARG_COUNT); |
| 276 var type = 0; | 275 var type = 0; |
| 277 if (useDiffEncoding) { | 276 if (useDiffEncoding) { |
| 278 statements.add(js.statement('''{ | 277 statements.addAll([ |
| 279 var objectClassObject = | 278 js('var objectClassObject = ' |
| 280 collectedClasses[#], // # is name of class Object. | 279 ' collectedClasses["${namer.getNameOfClass(objectClass)}"],' |
| 281 shortNames = #.split(","), // # is diffEncoding. | 280 ' shortNames = "$diffEncoding".split(","),' |
| 282 nameNumber = 0, | 281 ' nameNumber = 0,' |
| 283 diffEncodedString = shortNames[0], | 282 ' diffEncodedString = shortNames[0],' |
| 284 calculatedShortNames = [0, 1]; // 0, 1 are args for splice. | 283 ' calculatedShortNames = [0, 1]'), // 0, 1 are args for splice. |
| 285 // If we are loading a deferred library the object class will not be i
n | 284 // If we are loading a deferred library the object class will not be in |
| 286 // the collectedClasses so objectClassObject is undefined, and we skip | 285 // the collectedClasses so objectClassObject is undefined, and we skip |
| 287 // setting up the names. | 286 // setting up the names. |
| 288 | 287 js.if_('objectClassObject', [ |
| 289 if (objectClassObject) { | 288 js.if_('objectClassObject instanceof Array', |
| 290 if (objectClassObject instanceof Array) | 289 js('objectClassObject = objectClassObject[1]')), |
| 291 objectClassObject = objectClassObject[1]; | 290 js.for_('var i = 0', 'i < diffEncodedString.length', 'i++', [ |
| 292 for (var i = 0; i < diffEncodedString.length; i++) { | 291 js('var codes = [],' |
| 293 var codes = [], | 292 ' diff = 0,' |
| 294 diff = 0, | 293 ' digit = diffEncodedString.charCodeAt(i)'), |
| 295 digit = diffEncodedString.charCodeAt(i); | 294 js.if_('digit == ${$PERIOD}', [ |
| 296 if (digit == ${$PERIOD}) { | 295 js('nameNumber = 0'), |
| 297 nameNumber = 0; | 296 js('digit = diffEncodedString.charCodeAt(++i)') |
| 298 digit = diffEncodedString.charCodeAt(++i); | 297 ]), |
| 299 } | 298 js.while_('digit <= ${$Z}', [ |
| 300 for (; digit <= ${$Z};) { | 299 js('diff *= 26'), |
| 301 diff *= 26; | 300 js('diff += (digit - ${$A})'), |
| 302 diff += (digit - ${$A}); | 301 js('digit = diffEncodedString.charCodeAt(++i)') |
| 303 digit = diffEncodedString.charCodeAt(++i); | 302 ]), |
| 304 } | 303 js('diff *= 26'), |
| 305 diff *= 26; | 304 js('diff += (digit - ${$a})'), |
| 306 diff += (digit - ${$a}); | 305 js('nameNumber += diff'), |
| 307 nameNumber += diff; | 306 js.for_('var remaining = nameNumber', |
| 308 for (var remaining = nameNumber; | 307 'remaining > 0', |
| 309 remaining > 0; | 308 'remaining = (remaining / 88) | 0', [ |
| 310 remaining = (remaining / 88) | 0) { | 309 js('codes.unshift(${$HASH} + remaining % 88)') |
| 311 codes.unshift(${$HASH} + remaining % 88); | 310 ]), |
| 312 } | 311 js('calculatedShortNames.push(' |
| 313 calculatedShortNames.push( | 312 ' String.fromCharCode.apply(String, codes))') |
| 314 String.fromCharCode.apply(String, codes)); | 313 ]), |
| 315 } | 314 js('shortNames.splice.apply(shortNames, calculatedShortNames)')]) |
| 316 shortNames.splice.apply(shortNames, calculatedShortNames); | 315 ]); |
| 317 } | |
| 318 }''', [ | |
| 319 js.string(namer.getNameOfClass(objectClass)), | |
| 320 js.string('$diffEncoding')])); | |
| 321 } else { | 316 } else { |
| 322 // No useDiffEncoding version. | 317 // No useDiffEncoding version. |
| 323 Iterable<String> longs = trivialNsmHandlers.map((selector) => | 318 Iterable<String> longs = trivialNsmHandlers.map((selector) => |
| 324 selector.invocationMirrorMemberName); | 319 selector.invocationMirrorMemberName); |
| 325 statements.add(js.statement( | 320 String longNamesConstant = minify ? "" : |
| 326 'var objectClassObject = collectedClasses[#],' | 321 ',longNames = "${longs.join(",")}".split(",")'; |
| 327 ' shortNames = #.split(",")', [ | 322 statements.add( |
| 328 js.string(namer.getNameOfClass(objectClass)), | 323 js('var objectClassObject = ' |
| 329 js.string('$diffEncoding')])); | 324 ' collectedClasses["${namer.getNameOfClass(objectClass)}"],' |
| 330 if (!minify) { | 325 ' shortNames = "$diffEncoding".split(",")' |
| 331 statements.add(js.statement('var longNames = #.split(",")', | 326 ' $longNamesConstant')); |
| 332 js.string(longs.join(',')))); | 327 statements.add( |
| 333 } | 328 js.if_('objectClassObject instanceof Array', |
| 334 statements.add(js.statement( | 329 js('objectClassObject = objectClassObject[1]'))); |
| 335 'if (objectClassObject instanceof Array)' | |
| 336 ' objectClassObject = objectClassObject[1];')); | |
| 337 } | 330 } |
| 338 | 331 |
| 339 // TODO(9631): This is no longer valid for native methods. | 332 String sliceOffset = ', (j < $firstNormalSelector) ? 1 : 0'; |
| 333 if (firstNormalSelector == 0) sliceOffset = ''; |
| 334 if (firstNormalSelector == shorts.length) sliceOffset = ', 1'; |
| 335 |
| 340 String whatToPatch = task.nativeEmitter.handleNoSuchMethod ? | 336 String whatToPatch = task.nativeEmitter.handleNoSuchMethod ? |
| 341 "Object.prototype" : | 337 "Object.prototype" : |
| 342 "objectClassObject"; | 338 "objectClassObject"; |
| 343 | 339 |
| 344 List<jsAst.Expression> sliceOffsetArguments = | 340 var params = ['name', 'short', 'type']; |
| 345 firstNormalSelector == 0 | 341 var sliceOffsetParam = ''; |
| 346 ? [] | 342 var slice = 'Array.prototype.slice.call'; |
| 347 : (firstNormalSelector == shorts.length | 343 if (!sliceOffset.isEmpty) { |
| 348 ? [js.number(1)] | 344 sliceOffsetParam = ', sliceOffset'; |
| 349 : [js('(j < #) ? 1 : 0', js.number(firstNormalSelector))]); | 345 params.add('sliceOffset'); |
| 350 | 346 } |
| 351 var sliceOffsetParams = sliceOffsetArguments.isEmpty ? [] : ['sliceOffset']; | 347 statements.addAll([ |
| 352 | |
| 353 statements.add(js.statement(''' | |
| 354 // If we are loading a deferred library the object class will not be in | 348 // If we are loading a deferred library the object class will not be in |
| 355 // the collectedClasses so objectClassObject is undefined, and we skip | 349 // the collectedClasses so objectClassObject is undefined, and we skip |
| 356 // setting up the names. | 350 // setting up the names. |
| 357 if (objectClassObject) { | 351 js.if_('objectClassObject', [ |
| 358 for (var j = 0; j < shortNames.length; j++) { | 352 js.for_('var j = 0', 'j < shortNames.length', 'j++', [ |
| 359 var type = 0; | 353 js('var type = 0'), |
| 360 var short = shortNames[j]; | 354 js('var short = shortNames[j]'), |
| 361 if (short[0] == "${namer.getterPrefix[0]}") type = 1; | 355 js.if_('short[0] == "${namer.getterPrefix[0]}"', js('type = 1')), |
| 362 if (short[0] == "${namer.setterPrefix[0]}") type = 2; | 356 js.if_('short[0] == "${namer.setterPrefix[0]}"', js('type = 2')), |
| 363 // Generate call to: | 357 // Generate call to: |
| 364 // | 358 // createInvocationMirror(String name, internalName, type, arguments, |
| 365 // createInvocationMirror(String name, internalName, type, | 359 // argumentNames) |
| 366 // arguments, argumentNames) | 360 js('$whatToPatch[short] = #(${minify ? "shortNames" : "longNames"}[j],
' |
| 367 // | 361 'short, type$sliceOffset)', |
| 368 $whatToPatch[short] = (function(name, short, type, #) { | 362 js.fun(params, [js.return_(js.fun([], |
| 369 return function() { | 363 [js.return_(js( |
| 370 return this.#(this, | 364 'this.$noSuchMethodName(' |
| 371 #(name, short, type, | 365 'this, ' |
| 372 Array.prototype.slice.call(arguments, #), | 366 '$createInvocationMirror(' |
| 373 [])); | 367 'name, short, type, ' |
| 374 } | 368 '$slice(arguments$sliceOffsetParam), []))'))]))])) |
| 375 })(#[j], short, type, #); | 369 ]) |
| 376 } | 370 ]) |
| 377 }''', [ | 371 ]); |
| 378 sliceOffsetParams, // parameter | |
| 379 noSuchMethodName, | |
| 380 createInvocationMirror, | |
| 381 sliceOffsetParams, // argument to slice | |
| 382 minify ? 'shortNames' : 'longNames', | |
| 383 sliceOffsetArguments | |
| 384 ])); | |
| 385 | |
| 386 return statements; | |
| 387 } | 372 } |
| 388 } | 373 } |
| OLD | NEW |