Chromium Code Reviews| Index: sdk/lib/_internal/compiler/implementation/js_backend/emitter.dart |
| diff --git a/sdk/lib/_internal/compiler/implementation/js_backend/emitter.dart b/sdk/lib/_internal/compiler/implementation/js_backend/emitter.dart |
| index 8d4ba2e1a54d92268aecf2af9bd776e7c3c8313c..5cafedb63a0c84b936ae181ece2257341c08045c 100644 |
| --- a/sdk/lib/_internal/compiler/implementation/js_backend/emitter.dart |
| +++ b/sdk/lib/_internal/compiler/implementation/js_backend/emitter.dart |
| @@ -69,6 +69,7 @@ class CodeEmitterTask extends CompilerTask { |
| final List<ClassElement> regularClasses = <ClassElement>[]; |
| final List<ClassElement> deferredClasses = <ClassElement>[]; |
| final List<ClassElement> nativeClasses = <ClassElement>[]; |
| + final List<Selector> trivialNsmHandlers = <Selector>[]; |
| // TODO(ngeoffray): remove this field. |
| Set<ClassElement> instantiatedClasses; |
| @@ -277,9 +278,9 @@ class CodeEmitterTask extends CompilerTask { |
| js['var body = ""'], |
| // for (var i = 0; i < fields.length; i++) { |
| - js.for_(js['var i = 0'], js['i < fields.length'], js['i++'], [ |
| + js.for_('var i = 0', 'i < fields.length', 'i++', [ |
| // if (i != 0) str += ", "; |
| - js.if_(js['i != 0'], js['str += ", "']), |
| + js.if_('i != 0', js['str += ", "']), |
| js['var field = fields[i]'], |
| js['field = generateAccessor(field, prototype)'], |
| @@ -332,6 +333,223 @@ class CodeEmitterTask extends CompilerTask { |
| ]; |
| } |
| + const MAX_MINIFIED_LENGTH_FOR_DIFF_ENCODING = 4; |
| + |
| + // If we need fewer than this many noSuchMethod handlers we can save space by |
|
ngeoffray
2013/03/13 15:03:27
indentation off by one
erikcorry
2013/03/14 10:06:57
Fixed
|
| + // just emitting them in JS, rather than emitting the JS needed to generate |
| + // them at run time. |
| + const VERY_FEW_NO_SUCH_METHOD_HANDLERS = 10; |
| + |
| + /** |
| + * Adds (at runtime) the handlers to the Object class which catch calls to |
| + * methods that the object does not have. The handlers create an invocation |
| + * mirror object. |
| + * |
| + * The current version only gives you the minified name when minifying (when |
| + * not minifying this method is not called). |
| + * |
| + * In order to generate the noSuchMethod handlers we only need the minified |
| + * name of the method. We test the first character of the minified name to |
| + * determine if it is a getter or a setter, and we use the arguments array at |
| + * runtime to get the number of arguments and their values. If the method |
| + * involves named arguments etc. then we don't handle it here, but emit the |
| + * handler method directly on the Object class. |
| + * |
| + * The minified names are mostly 1-4 character names, which we emit in sorted |
| + * order (primary key is length, secondary ordering is lexicographic). This |
| + * gives an order like ... dD dI dX da ... |
| + * |
| + * Gzip is good with repeated text, but it can't diff-encode, so we do that |
| + * for it. We encode the minified names in a comma-separated string, but all |
| + * the 1-4 character names are encoded before the first comma as a series of |
| + * base 26 numbers. The last digit of each number is lower case, the others |
| + * are upper case, so 1 is "b" and 26 is "Ba". |
| + * |
| + * We think of the minified names as base 88 numbers using the ASCII |
| + * characters from # to z. The base 26 numbers each encode the delta from |
| + * the previous minified name to the next. So if there is a minified name |
| + * called Df and the next is Dh, then they are 2971 and 2973 when thought of |
| + * as base 88 numbers. The difference is 2, which is "c" in lower-case- |
| + * terminated base 26. |
| + * |
| + * The reason we don't encode long minified names with this method is that |
| + * decoding the base 88 numbers would overflow JavaScript's puny integers. |
| + * |
| + * There are some selectors that have a special calling convention (because |
| + * they are called with the receiver as the first argument). They need a |
| + * slightly different noSuchMethod handler, so we handle these first. |
| + */ |
| + void addTrivialNsmHandlers(List<jsAst.Node> statements) { |
| + if (trivialNsmHandlers.length == 0) return; |
| + // Sort by calling convention, JS name length and by JS name. |
| + trivialNsmHandlers.sort((a, b) { |
| + bool aIsIntercepted = backend.isInterceptedName(a.name); |
| + bool bIsIntercepted = backend.isInterceptedName(b.name); |
| + if (aIsIntercepted != bIsIntercepted) return aIsIntercepted ? -1 : 1; |
| + String aName = namer.invocationMirrorInternalName(a); |
| + String bName = namer.invocationMirrorInternalName(b); |
| + if (aName.length != bName.length) return aName.length - bName.length; |
| + return aName.compareTo(bName); |
| + }); |
| + |
| + // Find out how many selectors there are with the special calling |
| + // convention. |
| + int firstNormalSelector = trivialNsmHandlers.length; |
| + for (int i = 0; i < trivialNsmHandlers.length; i++) { |
| + if (!backend.isInterceptedName(trivialNsmHandlers[i].name)) { |
| + firstNormalSelector = i; |
| + break; |
| + } |
| + } |
| + |
| + // Get the short names (JS names, perhaps minified). |
| + Iterable<String> shorts = trivialNsmHandlers.map((selector) => |
| + namer.invocationMirrorInternalName(selector)); |
| + final diffShorts = <String>[]; |
| + var diffEncoding = new StringBuffer(); |
| + |
| + // Treat string as a number in base 88 with digits in ASCII order from # to |
| + // z. The short name sorting is based on length, and uses ASCII order for |
| + // equal length strings so this means that names are ascending. The hash |
| + // character, #, is never given as input, but we need it because it's the |
| + // implicit leading zero (otherwise we could not code names with leading |
| + // dollar signs). |
| + int fromBase88(String x) { |
| + int answer = 0; |
| + for (int i = 0; i < x.length; i++) { |
| + int c = x.codeUnitAt(i); |
| + // No support for Unicode minified identifiers in JS. |
| + assert(c >= $$ && c <= $z); |
| + answer *= 88; |
| + answer += c - $HASH; |
| + } |
| + return answer; |
| + } |
| + |
| + // Big endian encoding, A = 0, B = 1, etc. terminate with lower case. |
|
ngeoffray
2013/03/13 15:03:27
terminate -> terminates
erikcorry
2013/03/14 10:06:57
This was an imperative verb, not a singular one.
|
| + String toBase26(int x) { |
| + int c = x; |
| + var encodingChars = <int>[]; |
| + encodingChars.add($a + (c % 26)); |
| + while (true) { |
| + c ~/= 26; |
| + if (c == 0) break; |
| + encodingChars.add($A + (c % 26)); |
| + } |
| + return new String.fromCharCodes(encodingChars.reversed.toList()); |
| + } |
| + |
| + bool minify = compiler.enableMinification; |
|
ngeoffray
2013/03/13 15:03:27
Isn't that always true in this method?
erikcorry
2013/03/14 10:06:57
No, but we only diff encode if we are minifying.
|
| + bool useDiffEncoding = minify && shorts.length > 30; |
| + |
| + int previous = 0; |
| + int nameCounter = 0; |
| + for (String short in shorts) { |
| + // Emit period that resets the diff base to zero when we switch to normal |
| + // calling convention (this avoids the need to code negative diffs). |
| + if (useDiffEncoding && nameCounter == firstNormalSelector) { |
| + diffEncoding.write("."); |
| + previous = 0; |
| + } |
| + if (short.length <= MAX_MINIFIED_LENGTH_FOR_DIFF_ENCODING && |
| + useDiffEncoding) { |
| + int base63 = fromBase88(short); |
| + int diff = base63 - previous; |
| + previous = base63; |
| + String base26Diff = toBase26(diff); |
| + diffEncoding.write(base26Diff); |
| + } else { |
| + if (useDiffEncoding || diffEncoding.length != 0) { |
| + diffEncoding.write(","); |
| + } |
| + diffEncoding.write(short); |
| + } |
| + nameCounter++; |
| + } |
| + |
| + // Startup code that loops over the method names and puts handlers on the |
| + // Object class to catch noSuchMethod invocations. |
| + ClassElement objectClass = compiler.objectClass; |
| + String createInvocationMirror = namer.getName( |
| + compiler.createInvocationMirrorElement); |
| + String noSuchMethodName = namer.publicInstanceMethodNameByArity( |
| + Compiler.NO_SUCH_METHOD, Compiler.NO_SUCH_METHOD_ARG_COUNT); |
| + var type = 0; |
| + if (useDiffEncoding) { |
| + statements.addAll([ |
| + js['var objectClassObject = ' |
| + ' collectedClasses["${namer.getName(objectClass)}"],' |
| + ' shortNames = "$diffEncoding".split(","),' |
| + ' nameNumber = 0,' |
| + ' diffEncodedString = shortNames[0],' |
| + ' calculatedShortNames = [0, 1]'], // 0, 1 are args for splice. |
| + js.for_('var i = 0', 'i < diffEncodedString.length', 'i++', [ |
| + js['var codes = [],' |
| + ' diff = 0,' |
| + ' digit = diffEncodedString.charCodeAt(i)'], |
| + js.if_('digit == ${$PERIOD}', [ |
| + js['nameNumber = 0'], |
| + js['digit = diffEncodedString.charCodeAt(++i)'] |
| + ]), |
| + js.while_('digit <= ${$Z}', [ |
| + js['diff *= 26'], |
| + js['diff += (digit - ${$A})'], |
| + js['digit = diffEncodedString.charCodeAt(++i)'] |
| + ]), |
| + js['diff *= 26'], |
| + js['diff += (digit - ${$a})'], |
| + js['nameNumber += diff'], |
| + js.for_('var remaining = nameNumber', |
| + 'remaining > 0', |
| + 'remaining = ((remaining / 88) | 0)', [ |
| + js['codes.unshift(${$HASH} + (remaining % 88))'] |
| + ]), |
| + js['calculatedShortNames.push(' |
| + ' String.fromCharCode.apply(String, codes))'] |
| + ]), |
| + js['shortNames.splice.apply(shortNames, calculatedShortNames)'] |
| + ]); |
| + } else { |
| + // No useDiffEncoding version. |
| + Iterable<String> longs = trivialNsmHandlers.map((selector) => |
| + selector.invocationMirrorMemberName); |
| + String longNamesConstant = minify ? "" : |
| + ',longNames = "${longs.join(",")}".split(",")'; |
| + statements.add( |
| + js['var objectClassObject = ' |
| + ' collectedClasses["${namer.getName(objectClass)}"],' |
| + ' shortNames = "$diffEncoding".split(",")' |
| + ' $longNamesConstant']); |
| + } |
| + |
| + String sliceOffset = '," + (j < $firstNormalSelector ? 1 : 0)'; |
| + if (firstNormalSelector == 0) sliceOffset = '"'; |
| + if (firstNormalSelector == shorts.length) sliceOffset = ', 1"'; |
| + |
| + String whatToPatch = nativeEmitter.handleNoSuchMethod ? |
| + "Object.prototype" : |
| + "objectClassObject"; |
|
ngeoffray
2013/03/13 15:03:27
What is objectClassObject?
erikcorry
2013/03/14 10:06:57
It's the JS object corresponding to the Dart Objec
|
| + |
| + statements.addAll([ |
| + js.for_('var j = 0', 'j < shortNames.length', 'j++', [ |
| + js['var type = 0'], |
| + js['var short = shortNames[j]'], |
| + js.if_('short[0] == "${namer.getterPrefix[0]}"', js['type = 1']), |
| + js.if_('short[0] == "${namer.setterPrefix[0]}"', js['type = 2']), |
| + js['$whatToPatch[short] = Function("' |
| + 'return this.$noSuchMethodName(' |
| + 'this,' |
| + '${namer.CURRENT_ISOLATE}.$createInvocationMirror(\'"' |
| + ' + ${minify ? "shortNames" : "longNames"}[j]' |
| + ' + "\',\'" + short + "\',"' |
| + ' + type' |
| + ' + ",Array.prototype.slice.call(arguments' |
| + '$sliceOffset' |
| + ' + "),[]))")'] |
| + ]) |
| + ]); |
| + } |
| + |
| jsAst.Fun get finishClassesFunction { |
| // Class descriptions are collected in a JS object. |
| // 'finishClasses' takes all collected descriptions and sets up |
| @@ -346,11 +564,7 @@ class CodeEmitterTask extends CompilerTask { |
| // the object literal directly. For other engines we have to create a new |
| // object and copy over the members. |
| - // function(collectedClasses, |
| - // isolateProperties, |
| - // existingIsolateProperties) { |
| - return js.fun(['collectedClasses', 'isolateProperties', |
| - 'existingIsolateProperties'], [ |
| + List<jsAst.Node> statements = [ |
| js['var pendingClasses = {}'], |
| js['var hasOwnProperty = Object.prototype.hasOwnProperty'], |
| @@ -358,7 +572,7 @@ class CodeEmitterTask extends CompilerTask { |
| // for (var cls in collectedClasses) { |
| js.forIn('cls', 'collectedClasses', [ |
| // if (hasOwnProperty.call(collectedClasses, cls)) { |
| - js.if_(js['hasOwnProperty.call(collectedClasses, cls)'], [ |
| + js.if_('hasOwnProperty.call(collectedClasses, cls)', [ |
| js['var desc = collectedClasses[cls]'], |
| /* The 'fields' are either a constructor function or a |
| @@ -370,7 +584,7 @@ class CodeEmitterTask extends CompilerTask { |
| // var fields = desc[""], supr; |
| js['var fields = desc[""], supr'], |
| - js.if_(js['typeof fields == "string"'], [ |
| + js.if_('typeof fields == "string"', [ |
| js['var s = fields.split(";")'], |
| js['supr = s[0]'], |
| js['fields = s[1] == "" ? [] : s[1].split(",")'], |
| @@ -381,7 +595,7 @@ class CodeEmitterTask extends CompilerTask { |
| js['isolateProperties[cls] = defineClass(cls, fields, desc)'], |
| // if (supr) pendingClasses[cls] = supr; |
| - js.if_(js['supr'], js['pendingClasses[cls] = supr']) |
| + js.if_('supr', js['pendingClasses[cls] = supr']) |
| ]) |
| ]), |
| @@ -389,10 +603,19 @@ class CodeEmitterTask extends CompilerTask { |
| // function finishClass(cls) { ... } |
| buildFinishClass(), |
| + ]; |
| + addTrivialNsmHandlers(statements); |
| + |
| + statements.add( |
| // for (var cls in pendingClasses) finishClass(cls); |
| - js.forIn('cls', 'pendingClasses', js['finishClass']('cls')) |
| - ]); |
| + js.forIn('cls', 'pendingClasses', js['finishClass(cls)']) |
| + ); |
| + // function(collectedClasses, |
| + // isolateProperties, |
| + // existingIsolateProperties) { |
| + return js.fun(['collectedClasses', 'isolateProperties', |
| + 'existingIsolateProperties'], statements); |
| } |
| jsAst.FunctionDeclaration buildFinishClass() { |
| @@ -405,14 +628,18 @@ class CodeEmitterTask extends CompilerTask { |
| js['var hasOwnProperty = Object.prototype.hasOwnProperty'], |
| // if (hasOwnProperty.call(finishedClasses, cls)) return; |
| - js.if_(js['hasOwnProperty.call(finishedClasses, cls)'], |
| + js.if_('hasOwnProperty.call(finishedClasses, cls)', |
| js.return_()), |
| js['finishedClasses[cls] = true'], |
| + |
| js['var superclass = pendingClasses[cls]'], |
| - /* The superclass is only false (empty string) for Dart's Object class. */ |
| - js.if_(js['!superclass'], js.return_()), |
| + // The superclass is only false (empty string) for Dart's Object class. |
| + // The minifier together with noSuchMethod can put methods on the |
| + // Object.prototype object, and they show through here, so we check that |
| + // we have a string. |
| + js.if_('!superclass || typeof superclass != "string"', js.return_()), |
| js['finishClass(superclass)'], |
| js['var constructor = isolateProperties[cls]'], |
| js['var superConstructor = isolateProperties[superclass]'], |
| @@ -446,10 +673,10 @@ class CodeEmitterTask extends CompilerTask { |
| js.forIn('member', 'prototype', [ |
| /* Short version of: if (member == '') */ |
| // if (!member) continue; |
| - js.if_(js['!member'], new jsAst.Continue(null)), |
| + js.if_('!member', new jsAst.Continue(null)), |
| // if (hasOwnProperty.call(prototype, member)) { |
| - js.if_(js['hasOwnProperty.call(prototype, member)'], [ |
| + js.if_('hasOwnProperty.call(prototype, member)', [ |
| js['newPrototype[member] = prototype[member]'] |
| ]) |
| ]) |
| @@ -511,7 +738,7 @@ class CodeEmitterTask extends CompilerTask { |
| // for (var staticName in isolateProperties) { |
| js.forIn('staticName', 'isolateProperties', [ |
| - js.if_(js['hasOwnProperty.call(isolateProperties, staticName)'], [ |
| + js.if_('hasOwnProperty.call(isolateProperties, staticName)', [ |
| js['str += ("this." + staticName + "= properties." + staticName + ' |
| '";\\n")'] |
| ]) |
| @@ -558,7 +785,7 @@ class CodeEmitterTask extends CompilerTask { |
| // try { |
| js.try_([ |
| - js.if_(js['result === sentinelUndefined'], [ |
| + js.if_('result === sentinelUndefined', [ |
| js['$isolate[fieldName] = sentinelInProgress'], |
| // try { |
| @@ -570,15 +797,15 @@ class CodeEmitterTask extends CompilerTask { |
| // stack trace. |
| // if (result === sentinelUndefined) { |
| - js.if_(js['result === sentinelUndefined'], [ |
| + js.if_('result === sentinelUndefined', [ |
| // if ($isolate[fieldName] === sentinelInProgress) { |
| - js.if_(js['$isolate[fieldName] === sentinelInProgress'], [ |
| + js.if_('$isolate[fieldName] === sentinelInProgress', [ |
| js['$isolate[fieldName] = null'], |
| ]) |
| ]) |
| ]) |
| ], /* else */ [ |
| - js.if_(js['result === sentinelInProgress'], |
| + js.if_('result === sentinelInProgress', |
| js['$cyclicThrow(staticName)'] |
| ) |
| ]), |
| @@ -1156,7 +1383,7 @@ class CodeEmitterTask extends CompilerTask { |
| assert(!backend.isInterceptorClass(member)); |
| String getterName = namer.getterNameFromAccessorName(accessorName); |
| builder.addProperty(getterName, |
| - js.fun([], js.return_(js['this'][fieldName]))); |
| + js.fun([], js.return_(js['this.$fieldName']))); |
| } |
| void generateSetter(Element member, String fieldName, String accessorName, |
| @@ -1346,6 +1573,10 @@ class CodeEmitterTask extends CompilerTask { |
| bool get getterAndSetterCanBeImplementedByFieldSpec => true; |
| + /// If this is true then we can generate the noSuchMethod handlers at startup |
| + /// time, instead of them being emitted as part of the Object class. |
| + bool get generateTrivialNsmHandlers => true; |
| + |
| int _selectorRank(Selector selector) { |
| int arity = selector.argumentCount * 3; |
| if (selector.isGetter()) return arity + 2; |
| @@ -1967,6 +2198,29 @@ class CodeEmitterTask extends CompilerTask { |
| } |
| } |
| + // Identify the noSuchMethod handlers that are so simple that we can |
| + // generate them programatically. |
| + bool isTrivialNsmHandler( |
| + int type, List argNames, Selector selector, String internalName) { |
| + if (!generateTrivialNsmHandlers) return false; |
| + // Check for wrong calling convention. |
|
ngeoffray
2013/03/13 15:03:27
wrong :-)? maybe change it to "interceptor"?
erikcorry
2013/03/14 10:06:57
Done.
|
| + if (backend.isInterceptedName(selector.name)) { |
| + // We can handle the strange calling convention used by intercepted names |
|
ngeoffray
2013/03/13 15:03:27
strange -> interceptor
erikcorry
2013/03/14 10:06:57
Removed word 'strange'.
|
| + // in the diff encoding, but we don't use that for non-minified code. |
| + if (!compiler.enableMinification) return false; |
|
ngeoffray
2013/03/13 15:03:27
Isn't this code only used by the minifier?
erikcorry
2013/03/14 10:06:57
no
|
| + String shortName = namer.invocationMirrorInternalName(selector); |
| + if (shortName.length > MAX_MINIFIED_LENGTH_FOR_DIFF_ENCODING) { |
| + return false; |
| + } |
| + } |
| + // Check for named arguments. |
| + if (argNames.length != 0) return false; |
| + // Check for unexpected name (this doesn't really happen). |
| + if (internalName.startsWith(namer.getterPrefix[0])) return type == 1; |
| + if (internalName.startsWith(namer.setterPrefix[0])) return type == 2; |
| + return type == 0; |
| + } |
| + |
| void emitNoSuchMethodHandlers(DefineStubFunction defineStub) { |
| // Do not generate no such method handlers if there is no class. |
| if (compiler.codegenWorld.instantiatedClasses.isEmpty) return; |
| @@ -1981,42 +2235,7 @@ class CodeEmitterTask extends CompilerTask { |
| // Keep track of the JavaScript names we've already added so we |
| // do not introduce duplicates (bad for code size). |
| - Set<String> addedJsNames = new Set<String>(); |
| - |
| - jsAst.Expression generateMethod(String jsName, Selector selector) { |
| - // Values match JSInvocationMirror in js-helper library. |
| - int type = selector.invocationMirrorKind; |
| - String methodName = selector.invocationMirrorMemberName; |
| - List<jsAst.Parameter> parameters = <jsAst.Parameter>[]; |
| - CodeBuffer args = new CodeBuffer(); |
| - for (int i = 0; i < selector.argumentCount; i++) { |
| - parameters.add(new jsAst.Parameter('\$$i')); |
| - } |
| - |
| - List<jsAst.Expression> argNames = |
| - selector.getOrderedNamedArguments().map((SourceString name) => |
| - js.string(name.slowToString())).toList(); |
| - |
| - String internalName = namer.invocationMirrorInternalName(selector); |
| - |
| - String createInvocationMirror = namer.getName( |
| - compiler.createInvocationMirrorElement); |
| - |
| - assert(backend.isInterceptedName(Compiler.NO_SUCH_METHOD)); |
| - jsAst.Expression expression = js['this.$noSuchMethodName']( |
| - [js['this'], |
| - js[namer.CURRENT_ISOLATE][createInvocationMirror]([ |
| - js.string(methodName), |
| - js.string(internalName), |
| - type, |
| - new jsAst.ArrayInitializer.from( |
| - parameters.map((param) => js[param.name]).toList()), |
| - new jsAst.ArrayInitializer.from(argNames)])]); |
| - parameters = backend.isInterceptedName(selector.name) |
| - ? ([new jsAst.Parameter('\$receiver')]..addAll(parameters)) |
| - : parameters; |
| - return js.fun(parameters, js.return_(expression)); |
| - } |
| + Map<String, Selector> addedJsNames = new Map<String, Selector>(); |
| void addNoSuchMethodHandlers(SourceString ignore, Set<Selector> selectors) { |
| // Cache the object class and type. |
| @@ -2095,17 +2314,66 @@ class CodeEmitterTask extends CompilerTask { |
| compiler.world.locateNoSuchMethodHolders(selector); |
| if (holders.every(hasMatchingMember)) continue; |
| String jsName = namer.invocationMirrorInternalName(selector); |
| - if (!addedJsNames.contains(jsName)) { |
| - jsAst.Expression method = generateMethod(jsName, selector); |
| - defineStub(jsName, method); |
| - addedJsNames.add(jsName); |
| - } |
| + addedJsNames[jsName] = selector; |
| } |
| } |
| compiler.codegenWorld.invokedNames.forEach(addNoSuchMethodHandlers); |
| compiler.codegenWorld.invokedGetters.forEach(addNoSuchMethodHandlers); |
| compiler.codegenWorld.invokedSetters.forEach(addNoSuchMethodHandlers); |
| + |
| + // Set flag used by generateMethod helper below. If we have very few |
| + // handlers we use defineStub for them all, rather than try to generate them |
| + // at runtime. |
| + bool haveVeryFewNoSuchMemberHandlers = |
| + (addedJsNames.length < VERY_FEW_NO_SUCH_METHOD_HANDLERS); |
| + |
| + jsAst.Expression generateMethod(String jsName, Selector selector) { |
| + // Values match JSInvocationMirror in js-helper library. |
| + int type = selector.invocationMirrorKind; |
| + List<jsAst.Parameter> parameters = <jsAst.Parameter>[]; |
| + CodeBuffer args = new CodeBuffer(); |
| + for (int i = 0; i < selector.argumentCount; i++) { |
| + parameters.add(new jsAst.Parameter('\$$i')); |
| + } |
| + |
| + List<jsAst.Expression> argNames = |
| + selector.getOrderedNamedArguments().map((SourceString name) => |
| + js.string(name.slowToString())).toList(); |
| + |
| + String methodName = selector.invocationMirrorMemberName; |
| + String internalName = namer.invocationMirrorInternalName(selector); |
| + if (!haveVeryFewNoSuchMemberHandlers && |
| + isTrivialNsmHandler(type, argNames, selector, internalName)) { |
| + trivialNsmHandlers.add(selector); |
| + return null; |
| + } |
| + |
| + String createInvocationMirror = namer.getName( |
| + compiler.createInvocationMirrorElement); |
| + |
| + assert(backend.isInterceptedName(Compiler.NO_SUCH_METHOD)); |
| + jsAst.Expression expression = js['this.$noSuchMethodName']( |
| + [js['this'], |
| + js[namer.CURRENT_ISOLATE][createInvocationMirror]([ |
| + js.string(compiler.enableMinification ? |
| + internalName : methodName), |
| + js.string(internalName), |
| + type, |
| + new jsAst.ArrayInitializer.from( |
| + parameters.map((param) => js[param.name]).toList()), |
| + new jsAst.ArrayInitializer.from(argNames)])]); |
| + parameters = backend.isInterceptedName(selector.name) |
| + ? ([new jsAst.Parameter('\$receiver')]..addAll(parameters)) |
| + : parameters; |
| + return js.fun(parameters, js.return_(expression)); |
| + } |
| + |
| + for (String jsName in addedJsNames.keys.toList()..sort()) { |
| + Selector selector = addedJsNames[jsName]; |
| + jsAst.Expression method = generateMethod(jsName, selector); |
| + if (method != null) defineStub(jsName, method); |
| + } |
| } |
| String buildIsolateSetup(CodeBuffer buffer, |
| @@ -2164,7 +2432,6 @@ if (typeof document !== "undefined" && document.readyState !== "complete") { |
| return js.return_(js[namer.isolateAccess(cls)]['prototype']); |
| } |
| - jsAst.VariableUse receiver = js['receiver']; |
| /** |
| * Build a JavaScrit AST node for doing a type check on |
| * [cls]. [cls] must be an interceptor class. |
| @@ -2173,7 +2440,7 @@ if (typeof document !== "undefined" && document.readyState !== "complete") { |
| jsAst.Expression condition; |
| assert(backend.isInterceptorClass(cls)); |
| if (cls == backend.jsBoolClass) { |
| - condition = receiver.typeof.equals(js.string('boolean')); |
| + condition = js['(typeof receiver) == "boolean"']; |
| } else if (cls == backend.jsIntClass || |
| cls == backend.jsDoubleClass || |
| cls == backend.jsNumberClass) { |
| @@ -2182,13 +2449,13 @@ if (typeof document !== "undefined" && document.readyState !== "complete") { |
| cls == backend.jsMutableArrayClass || |
| cls == backend.jsFixedArrayClass || |
| cls == backend.jsExtendableArrayClass) { |
| - condition = receiver['constructor'].equals('Array'); |
| + condition = js['receiver.constructor == Array']; |
| } else if (cls == backend.jsStringClass) { |
| - condition = receiver.typeof.equals(js.string('string')); |
| + condition = js['(typeof receiver) == "string"']; |
| } else if (cls == backend.jsNullClass) { |
| - condition = receiver.equals(new jsAst.LiteralNull()); |
| + condition = js['receiver == null']; |
| } else if (cls == backend.jsFunctionClass) { |
| - condition = receiver.typeof.equals(js.string('function')); |
| + condition = js['(typeof receiver) == "function"']; |
| } else { |
| throw 'internal error'; |
| } |
| @@ -2237,7 +2504,7 @@ if (typeof document !== "undefined" && document.readyState !== "complete") { |
| hasDouble ? backend.jsDoubleClass : backend.jsNumberClass); |
| if (hasInt) { |
| - jsAst.Expression isInt = js['Math']['floor'](receiver).equals(receiver); |
| + jsAst.Expression isInt = js['Math.floor(receiver) == receiver']; |
| whenNumber = js.block([ |
| js.if_(isInt, buildReturnInterceptor(backend.jsIntClass)), |
| returnNumberClass]); |
| @@ -2245,7 +2512,7 @@ if (typeof document !== "undefined" && document.readyState !== "complete") { |
| whenNumber = returnNumberClass; |
| } |
| block.statements.add( |
| - js.if_(receiver.typeof.equals(js.string('number')), |
| + js.if_('(typeof receiver) == "number"', |
| whenNumber)); |
| } |
| @@ -2258,7 +2525,7 @@ if (typeof document !== "undefined" && document.readyState !== "complete") { |
| // Returning "undefined" here will provoke a JavaScript |
| // TypeError which is later identified as a null-error by |
| // [unwrapException] in js_helper.dart. |
| - block.statements.add(js.if_(receiver.equals(new jsAst.LiteralNull()), |
| + block.statements.add(js.if_('receiver == null', |
| js.return_(js.undefined()))); |
| } |
| if (hasFunction) { |
| @@ -2272,7 +2539,7 @@ if (typeof document !== "undefined" && document.readyState !== "complete") { |
| if (hasArray) { |
| block.statements.add(buildInterceptorCheck(backend.jsArrayClass)); |
| } |
| - block.statements.add(js.return_(receiver)); |
| + block.statements.add(js.return_(js['receiver'])); |
| buffer.write(jsAst.prettyPrint( |
| js[isolateProperties][key].assign(js.fun(['receiver'], block)), |
| @@ -2382,7 +2649,7 @@ if (typeof document !== "undefined" && document.readyState !== "complete") { |
| // } |
| // :]. |
| List<jsAst.Statement> body = <jsAst.Statement>[]; |
| - body.add(js.if_(js['receiver'].equals(new jsAst.LiteralNull()), |
| + body.add(js.if_('receiver == null', |
| js.return_(js['a0'].equals(new jsAst.LiteralNull())))); |
| body.add(js.if_( |
| isNotObject('receiver'), |