| 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<jsAst.Parameter> parameters = <jsAst.Parameter>[]; | 66 List<String> parameterNames = |
| 67 for (int i = 0; i < selector.argumentCount; i++) { | 67 new List.generate(selector.argumentCount, (i) => '\$$i'); |
| 68 parameters.add(new jsAst.Parameter('\$$i')); | |
| 69 } | |
| 70 | 68 |
| 71 List<jsAst.Expression> argNames = | 69 List<jsAst.Expression> argNames = |
| 72 selector.getOrderedNamedArguments().map((String name) => | 70 selector.getOrderedNamedArguments().map((String name) => |
| 73 js.string(name)).toList(); | 71 js.string(name)).toList(); |
| 74 | 72 |
| 75 String methodName = selector.invocationMirrorMemberName; | 73 String methodName = selector.invocationMirrorMemberName; |
| 76 String internalName = namer.invocationMirrorInternalName(selector); | 74 String internalName = namer.invocationMirrorInternalName(selector); |
| 77 String reflectionName = task.getReflectionName(selector, internalName); | 75 String reflectionName = task.getReflectionName(selector, internalName); |
| 78 if (!haveVeryFewNoSuchMemberHandlers && | 76 if (!haveVeryFewNoSuchMemberHandlers && |
| 79 isTrivialNsmHandler(type, argNames, selector, internalName) && | 77 isTrivialNsmHandler(type, argNames, selector, internalName) && |
| 80 reflectionName == null) { | 78 reflectionName == null) { |
| 81 trivialNsmHandlers.add(selector); | 79 trivialNsmHandlers.add(selector); |
| 82 return null; | 80 return null; |
| 83 } | 81 } |
| 84 | 82 |
| 85 assert(backend.isInterceptedName(Compiler.NO_SUCH_METHOD)); | 83 assert(backend.isInterceptedName(Compiler.NO_SUCH_METHOD)); |
| 86 jsAst.Expression expression = js('this.$noSuchMethodName')( | 84 jsAst.Expression expression = js('this.#(this, #(#, #, #, #, #))', [ |
| 87 [js('this'), | 85 noSuchMethodName, |
| 88 namer.elementAccess(backend.getCreateInvocationMirror())([ | 86 namer.elementAccess(backend.getCreateInvocationMirror()), |
| 89 js.string(compiler.enableMinification ? | 87 js.string(compiler.enableMinification ? |
| 90 internalName : methodName), | 88 internalName : methodName), |
| 91 js.string(internalName), | 89 js.string(internalName), |
| 92 type, | 90 js.number(type), |
| 93 new jsAst.ArrayInitializer.from( | 91 new jsAst.ArrayInitializer.from(parameterNames.map(js)), |
| 94 parameters.map((param) => js(param.name)).toList()), | 92 new jsAst.ArrayInitializer.from(argNames)]); |
| 95 new jsAst.ArrayInitializer.from(argNames)])]); | 93 |
| 96 parameters = backend.isInterceptedName(selector.name) | 94 if (backend.isInterceptedName(selector.name)) { |
| 97 ? ([new jsAst.Parameter('\$receiver')]..addAll(parameters)) | 95 return js(r'function($receiver, #) { return # }', |
| 98 : parameters; | 96 [parameterNames, expression]); |
| 99 return js.fun(parameters, js.return_(expression)); | 97 } else { |
| 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 void addTrivialNsmHandlers(List<jsAst.Node> statements) { | 179 List<jsAst.Statement> buildTrivialNsmHandlers() { |
| 180 if (trivialNsmHandlers.length == 0) return; | 180 List<jsAst.Statement> statements = <jsAst.Statement>[]; |
| 181 if (trivialNsmHandlers.length == 0) return statements; |
| 181 // Sort by calling convention, JS name length and by JS name. | 182 // Sort by calling convention, JS name length and by JS name. |
| 182 trivialNsmHandlers.sort((a, b) { | 183 trivialNsmHandlers.sort((a, b) { |
| 183 bool aIsIntercepted = backend.isInterceptedName(a.name); | 184 bool aIsIntercepted = backend.isInterceptedName(a.name); |
| 184 bool bIsIntercepted = backend.isInterceptedName(b.name); | 185 bool bIsIntercepted = backend.isInterceptedName(b.name); |
| 185 if (aIsIntercepted != bIsIntercepted) return aIsIntercepted ? -1 : 1; | 186 if (aIsIntercepted != bIsIntercepted) return aIsIntercepted ? -1 : 1; |
| 186 String aName = namer.invocationMirrorInternalName(a); | 187 String aName = namer.invocationMirrorInternalName(a); |
| 187 String bName = namer.invocationMirrorInternalName(b); | 188 String bName = namer.invocationMirrorInternalName(b); |
| 188 if (aName.length != bName.length) return aName.length - bName.length; | 189 if (aName.length != bName.length) return aName.length - bName.length; |
| 189 return aName.compareTo(bName); | 190 return aName.compareTo(bName); |
| 190 }); | 191 }); |
| (...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 261 diffEncoding.write(","); | 262 diffEncoding.write(","); |
| 262 } | 263 } |
| 263 diffEncoding.write(short); | 264 diffEncoding.write(short); |
| 264 } | 265 } |
| 265 nameCounter++; | 266 nameCounter++; |
| 266 } | 267 } |
| 267 | 268 |
| 268 // Startup code that loops over the method names and puts handlers on the | 269 // Startup code that loops over the method names and puts handlers on the |
| 269 // Object class to catch noSuchMethod invocations. | 270 // Object class to catch noSuchMethod invocations. |
| 270 ClassElement objectClass = compiler.objectClass; | 271 ClassElement objectClass = compiler.objectClass; |
| 271 String createInvocationMirror = namer.isolateAccess( | 272 jsAst.Expression createInvocationMirror = namer.elementAccess( |
| 272 backend.getCreateInvocationMirror()); | 273 backend.getCreateInvocationMirror()); |
| 273 String noSuchMethodName = namer.publicInstanceMethodNameByArity( | 274 String noSuchMethodName = namer.publicInstanceMethodNameByArity( |
| 274 Compiler.NO_SUCH_METHOD, Compiler.NO_SUCH_METHOD_ARG_COUNT); | 275 Compiler.NO_SUCH_METHOD, Compiler.NO_SUCH_METHOD_ARG_COUNT); |
| 275 var type = 0; | 276 var type = 0; |
| 276 if (useDiffEncoding) { | 277 if (useDiffEncoding) { |
| 277 statements.addAll([ | 278 statements.add(js.statement('''{ |
| 278 js('var objectClassObject = ' | 279 var objectClassObject = |
| 279 ' collectedClasses["${namer.getNameOfClass(objectClass)}"],' | 280 collectedClasses[#], // # is name of class Object. |
| 280 ' shortNames = "$diffEncoding".split(","),' | 281 shortNames = #.split(","), // # is diffEncoding. |
| 281 ' nameNumber = 0,' | 282 nameNumber = 0, |
| 282 ' diffEncodedString = shortNames[0],' | 283 diffEncodedString = shortNames[0], |
| 283 ' calculatedShortNames = [0, 1]'), // 0, 1 are args for splice. | 284 calculatedShortNames = [0, 1]; // 0, 1 are args for splice. |
| 284 // If we are loading a deferred library the object class will not be in | 285 // If we are loading a deferred library the object class will not be i
n |
| 285 // the collectedClasses so objectClassObject is undefined, and we skip | 286 // the collectedClasses so objectClassObject is undefined, and we skip |
| 286 // setting up the names. | 287 // setting up the names. |
| 287 js.if_('objectClassObject', [ | 288 |
| 288 js.if_('objectClassObject instanceof Array', | 289 if (objectClassObject) { |
| 289 js('objectClassObject = objectClassObject[1]')), | 290 if (objectClassObject instanceof Array) |
| 290 js.for_('var i = 0', 'i < diffEncodedString.length', 'i++', [ | 291 objectClassObject = objectClassObject[1]; |
| 291 js('var codes = [],' | 292 for (var i = 0; i < diffEncodedString.length; i++) { |
| 292 ' diff = 0,' | 293 var codes = [], |
| 293 ' digit = diffEncodedString.charCodeAt(i)'), | 294 diff = 0, |
| 294 js.if_('digit == ${$PERIOD}', [ | 295 digit = diffEncodedString.charCodeAt(i); |
| 295 js('nameNumber = 0'), | 296 if (digit == ${$PERIOD}) { |
| 296 js('digit = diffEncodedString.charCodeAt(++i)') | 297 nameNumber = 0; |
| 297 ]), | 298 digit = diffEncodedString.charCodeAt(++i); |
| 298 js.while_('digit <= ${$Z}', [ | 299 } |
| 299 js('diff *= 26'), | 300 for (; digit <= ${$Z};) { |
| 300 js('diff += (digit - ${$A})'), | 301 diff *= 26; |
| 301 js('digit = diffEncodedString.charCodeAt(++i)') | 302 diff += (digit - ${$A}); |
| 302 ]), | 303 digit = diffEncodedString.charCodeAt(++i); |
| 303 js('diff *= 26'), | 304 } |
| 304 js('diff += (digit - ${$a})'), | 305 diff *= 26; |
| 305 js('nameNumber += diff'), | 306 diff += (digit - ${$a}); |
| 306 js.for_('var remaining = nameNumber', | 307 nameNumber += diff; |
| 307 'remaining > 0', | 308 for (var remaining = nameNumber; |
| 308 'remaining = (remaining / 88) | 0', [ | 309 remaining > 0; |
| 309 js('codes.unshift(${$HASH} + remaining % 88)') | 310 remaining = (remaining / 88) | 0) { |
| 310 ]), | 311 codes.unshift(${$HASH} + remaining % 88); |
| 311 js('calculatedShortNames.push(' | 312 } |
| 312 ' String.fromCharCode.apply(String, codes))') | 313 calculatedShortNames.push( |
| 313 ]), | 314 String.fromCharCode.apply(String, codes)); |
| 314 js('shortNames.splice.apply(shortNames, calculatedShortNames)')]) | 315 } |
| 315 ]); | 316 shortNames.splice.apply(shortNames, calculatedShortNames); |
| 317 } |
| 318 }''', [ |
| 319 js.string(namer.getNameOfClass(objectClass)), |
| 320 js.string('$diffEncoding')])); |
| 316 } else { | 321 } else { |
| 317 // No useDiffEncoding version. | 322 // No useDiffEncoding version. |
| 318 Iterable<String> longs = trivialNsmHandlers.map((selector) => | 323 Iterable<String> longs = trivialNsmHandlers.map((selector) => |
| 319 selector.invocationMirrorMemberName); | 324 selector.invocationMirrorMemberName); |
| 320 String longNamesConstant = minify ? "" : | 325 statements.add(js.statement( |
| 321 ',longNames = "${longs.join(",")}".split(",")'; | 326 'var objectClassObject = collectedClasses[#],' |
| 322 statements.add( | 327 ' shortNames = #.split(",")', [ |
| 323 js('var objectClassObject = ' | 328 js.string(namer.getNameOfClass(objectClass)), |
| 324 ' collectedClasses["${namer.getNameOfClass(objectClass)}"],' | 329 js.string('$diffEncoding')])); |
| 325 ' shortNames = "$diffEncoding".split(",")' | 330 if (!minify) { |
| 326 ' $longNamesConstant')); | 331 statements.add(js.statement('var longNames = #.split(",")', |
| 327 statements.add( | 332 js.string(longs.join(',')))); |
| 328 js.if_('objectClassObject instanceof Array', | 333 } |
| 329 js('objectClassObject = objectClassObject[1]'))); | 334 statements.add(js.statement( |
| 335 'if (objectClassObject instanceof Array)' |
| 336 ' objectClassObject = objectClassObject[1];')); |
| 330 } | 337 } |
| 331 | 338 |
| 332 String sliceOffset = ', (j < $firstNormalSelector) ? 1 : 0'; | 339 // TODO(9631): This is no longer valid for native methods. |
| 333 if (firstNormalSelector == 0) sliceOffset = ''; | |
| 334 if (firstNormalSelector == shorts.length) sliceOffset = ', 1'; | |
| 335 | |
| 336 String whatToPatch = task.nativeEmitter.handleNoSuchMethod ? | 340 String whatToPatch = task.nativeEmitter.handleNoSuchMethod ? |
| 337 "Object.prototype" : | 341 "Object.prototype" : |
| 338 "objectClassObject"; | 342 "objectClassObject"; |
| 339 | 343 |
| 340 var params = ['name', 'short', 'type']; | 344 List<jsAst.Expression> sliceOffsetArguments = |
| 341 var sliceOffsetParam = ''; | 345 firstNormalSelector == 0 |
| 342 var slice = 'Array.prototype.slice.call'; | 346 ? [] |
| 343 if (!sliceOffset.isEmpty) { | 347 : (firstNormalSelector == shorts.length |
| 344 sliceOffsetParam = ', sliceOffset'; | 348 ? [js.number(1)] |
| 345 params.add('sliceOffset'); | 349 : [js('(j < #) ? 1 : 0', js.number(firstNormalSelector))]); |
| 346 } | 350 |
| 347 statements.addAll([ | 351 var sliceOffsetParams = sliceOffsetArguments.isEmpty ? [] : ['sliceOffset']; |
| 352 |
| 353 statements.add(js.statement(''' |
| 348 // If we are loading a deferred library the object class will not be in | 354 // If we are loading a deferred library the object class will not be in |
| 349 // the collectedClasses so objectClassObject is undefined, and we skip | 355 // the collectedClasses so objectClassObject is undefined, and we skip |
| 350 // setting up the names. | 356 // setting up the names. |
| 351 js.if_('objectClassObject', [ | 357 if (objectClassObject) { |
| 352 js.for_('var j = 0', 'j < shortNames.length', 'j++', [ | 358 for (var j = 0; j < shortNames.length; j++) { |
| 353 js('var type = 0'), | 359 var type = 0; |
| 354 js('var short = shortNames[j]'), | 360 var short = shortNames[j]; |
| 355 js.if_('short[0] == "${namer.getterPrefix[0]}"', js('type = 1')), | 361 if (short[0] == "${namer.getterPrefix[0]}") type = 1; |
| 356 js.if_('short[0] == "${namer.setterPrefix[0]}"', js('type = 2')), | 362 if (short[0] == "${namer.setterPrefix[0]}") type = 2; |
| 357 // Generate call to: | 363 // Generate call to: |
| 358 // createInvocationMirror(String name, internalName, type, arguments, | 364 // |
| 359 // argumentNames) | 365 // createInvocationMirror(String name, internalName, type, |
| 360 js('$whatToPatch[short] = #(${minify ? "shortNames" : "longNames"}[j],
' | 366 // arguments, argumentNames) |
| 361 'short, type$sliceOffset)', | 367 // |
| 362 js.fun(params, [js.return_(js.fun([], | 368 $whatToPatch[short] = (function(name, short, type, #) { |
| 363 [js.return_(js( | 369 return function() { |
| 364 'this.$noSuchMethodName(' | 370 return this.#(this, |
| 365 'this, ' | 371 #(name, short, type, |
| 366 '$createInvocationMirror(' | 372 Array.prototype.slice.call(arguments, #), |
| 367 'name, short, type, ' | 373 [])); |
| 368 '$slice(arguments$sliceOffsetParam), []))'))]))])) | 374 } |
| 369 ]) | 375 })(#[j], short, type, #); |
| 370 ]) | 376 } |
| 371 ]); | 377 }''', [ |
| 378 sliceOffsetParams, // parameter |
| 379 noSuchMethodName, |
| 380 createInvocationMirror, |
| 381 sliceOffsetParams, // argument to slice |
| 382 minify ? 'shortNames' : 'longNames', |
| 383 sliceOffsetArguments |
| 384 ])); |
| 385 |
| 386 return statements; |
| 372 } | 387 } |
| 373 } | 388 } |
| OLD | NEW |