Index: pkg/compiler/lib/src/js_emitter/old_emitter/nsm_emitter.dart |
diff --git a/pkg/compiler/lib/src/js_emitter/old_emitter/nsm_emitter.dart b/pkg/compiler/lib/src/js_emitter/old_emitter/nsm_emitter.dart |
index 201a369d717df3cb931a2a8b1f67da996eb09f4c..3303d965cd3840efe105c84af9971ef3205bd475 100644 |
--- a/pkg/compiler/lib/src/js_emitter/old_emitter/nsm_emitter.dart |
+++ b/pkg/compiler/lib/src/js_emitter/old_emitter/nsm_emitter.dart |
@@ -25,7 +25,7 @@ class NsmEmitter extends CodeEmitterHelper { |
// Keep track of the JavaScript names we've already added so we |
// do not introduce duplicates (bad for code size). |
- Map<String, Selector> addedJsNames |
+ Map<jsAst.Name, Selector> addedJsNames |
= generator.computeSelectorsForNsmHandlers(); |
// Set flag used by generateMethod helper below. If we have very few |
@@ -33,7 +33,9 @@ class NsmEmitter extends CodeEmitterHelper { |
// them at runtime. |
bool haveVeryFewNoSuchMemberHandlers = |
(addedJsNames.length < VERY_FEW_NO_SUCH_METHOD_HANDLERS); |
- for (String jsName in addedJsNames.keys.toList()..sort()) { |
+ List<jsAst.Name> names = addedJsNames.keys.toList() |
+ ..sort(); |
+ for (jsAst.Name jsName in names) { |
Selector selector = addedJsNames[jsName]; |
String reflectionName = emitter.getReflectionName(selector, jsName); |
@@ -56,7 +58,8 @@ class NsmEmitter extends CodeEmitterHelper { |
if (reflectionName != null) { |
bool accessible = compiler.world.allFunctions.filter(selector).any( |
(Element e) => backend.isAccessibleByReflection(e)); |
- addProperty('+$reflectionName', js(accessible ? '2' : '0')); |
+ addProperty(namer.asName('+$reflectionName'), |
+ js(accessible ? '2' : '0')); |
} |
} |
} |
@@ -65,23 +68,13 @@ class NsmEmitter extends CodeEmitterHelper { |
// Identify the noSuchMethod handlers that are so simple that we can |
// generate them programatically. |
bool isTrivialNsmHandler( |
- int type, List argNames, Selector selector, String internalName) { |
+ int type, List argNames, Selector selector, jsAst.Name internalName) { |
if (!generateTrivialNsmHandlers) return false; |
- // Check for interceptor calling convention. |
- if (backend.isInterceptedName(selector.name)) { |
- // We can handle the calling convention used by intercepted names in the |
- // diff encoding, but we don't use that for non-minified code. |
- if (!compiler.enableMinification) return false; |
- 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; |
+ if (internalName is GetterName) return type == 1; |
+ if (internalName is SetterName) return type == 2; |
return type == 0; |
} |
@@ -127,91 +120,29 @@ class NsmEmitter extends CodeEmitterHelper { |
List<jsAst.Statement> buildTrivialNsmHandlers() { |
List<jsAst.Statement> statements = <jsAst.Statement>[]; |
if (trivialNsmHandlers.length == 0) return statements; |
- // 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; |
- } |
+ bool hasSpecialCallingConvention(Selector selector) { |
+ return backend.isInterceptedName(selector.name); |
} |
+ Iterable<Selector> specialSelectors = trivialNsmHandlers.where( |
+ (Selector s) => backend.isInterceptedName(s.name)); |
+ Iterable<Selector> ordinarySelectors = trivialNsmHandlers.where( |
+ (Selector s) => !backend.isInterceptedName(s.name)); |
// Get the short names (JS names, perhaps minified). |
- Iterable<String> shorts = trivialNsmHandlers.map((selector) => |
- namer.invocationMirrorInternalName(selector)); |
- 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... |
- // A lower case letter terminates the number. |
- 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()); |
- } |
+ Iterable<jsAst.Name> specialShorts = |
+ specialSelectors.map(namer.invocationMirrorInternalName); |
+ Iterable<jsAst.Name> ordinaryShorts = |
+ ordinarySelectors.map(namer.invocationMirrorInternalName); |
bool minify = compiler.enableMinification; |
- bool useDiffEncoding = minify && shorts.length > 30; |
+ bool useDiffEncoding = minify && trivialNsmHandlers.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++; |
- } |
+ jsAst.Expression diffEncoding = new _DiffEncodedListOfNames( |
+ [specialShorts, ordinaryShorts], |
+ useDiffEncoding); |
// Startup code that loops over the method names and puts handlers on the |
// Object class to catch noSuchMethod invocations. |
@@ -221,25 +152,24 @@ class NsmEmitter extends CodeEmitterHelper { |
if (useDiffEncoding) { |
statements.add(js.statement('''{ |
var objectClassObject = processedClasses.collected[#objectClass], |
- shortNames = #diffEncoding.split(","), |
- nameNumber = 0, |
- diffEncodedString = shortNames[0], |
- calculatedShortNames = [0, 1]; // 0, 1 are args for splice. |
- // If we are loading a deferred library the object class will not be in |
- // the collectedClasses so objectClassObject is undefined, and we skip |
- // setting up the names. |
- |
- if (objectClassObject) { |
- if (objectClassObject instanceof Array) |
+ nameSequences = #diffEncoding.split("."), |
+ shortNames = []; |
+ if (objectClassObject instanceof Array) |
objectClassObject = objectClassObject[1]; |
+ for (var j = 0; j < nameSequences.length; ++j) { |
+ var sequence = nameSequences[j].split(","), |
+ nameNumber = 0; |
+ // If we are loading a deferred library the object class will not be |
+ // in the collectedClasses so objectClassObject is undefined, and we |
+ // skip setting up the names. |
+ if (!objectClassObject) break; |
+ // Likewise, if the current sequence is empty, we don't process it. |
+ if (sequence.length == 0) continue; |
+ var diffEncodedString = sequence[0]; |
for (var i = 0; i < diffEncodedString.length; i++) { |
var codes = [], |
diff = 0, |
digit = diffEncodedString.charCodeAt(i); |
- if (digit == ${$PERIOD}) { |
- nameNumber = 0; |
- digit = diffEncodedString.charCodeAt(++i); |
- } |
for (; digit <= ${$Z};) { |
diff *= 26; |
diff += (digit - ${$A}); |
@@ -253,13 +183,15 @@ class NsmEmitter extends CodeEmitterHelper { |
remaining = (remaining / 88) | 0) { |
codes.unshift(${$HASH} + remaining % 88); |
} |
- calculatedShortNames.push( |
+ shortNames.push( |
String.fromCharCode.apply(String, codes)); |
} |
- shortNames.splice.apply(shortNames, calculatedShortNames); |
+ if (sequence.length > 1) { |
+ Array.prototype.push.apply(shortNames, sequence.shift()); |
+ } |
} |
- }''', {'objectClass': js.string(namer.className(objectClass)), |
- 'diffEncoding': js.string('$diffEncoding')})); |
+ }''', {'objectClass': js.quoteName(namer.className(objectClass)), |
+ 'diffEncoding': diffEncoding})); |
} else { |
// No useDiffEncoding version. |
Iterable<String> longs = trivialNsmHandlers.map((selector) => |
@@ -267,8 +199,8 @@ class NsmEmitter extends CodeEmitterHelper { |
statements.add(js.statement( |
'var objectClassObject = processedClasses.collected[#objectClass],' |
' shortNames = #diffEncoding.split(",")', |
- {'objectClass': js.string(namer.className(objectClass)), |
- 'diffEncoding': js.string('$diffEncoding')})); |
+ {'objectClass': js.quoteName(namer.className(objectClass)), |
+ 'diffEncoding': diffEncoding})); |
if (!minify) { |
statements.add(js.statement('var longNames = #longs.split(",")', |
{'longs': js.string(longs.join(','))})); |
@@ -279,11 +211,11 @@ class NsmEmitter extends CodeEmitterHelper { |
} |
dynamic isIntercepted = // jsAst.Expression or bool. |
- firstNormalSelector == 0 |
+ specialSelectors.isEmpty |
? false |
- : firstNormalSelector == shorts.length |
+ : ordinarySelectors.isEmpty |
? true |
- : js('j < #', js.number(firstNormalSelector)); |
+ : js('j < #', js.number(specialSelectors.length)); |
statements.add(js.statement(''' |
// If we are loading a deferred library the object class will not be in |
@@ -341,3 +273,114 @@ class NsmEmitter extends CodeEmitterHelper { |
return statements; |
} |
} |
+ |
+/// When pretty printed, this node computes a diff-encoded string for the list |
+/// of given names. |
+/// |
+/// See [buildTrivialNsmHandlers]. |
+class _DiffEncodedListOfNames extends jsAst.DeferredString |
+ implements AstContainer { |
+ String _cachedValue; |
+ jsAst.ArrayInitializer ast; |
+ bool useDiffEncoding; |
+ |
+ _DiffEncodedListOfNames(Iterable<Iterable<jsAst.Name>> names, |
+ this.useDiffEncoding) { |
+ // Store the names in ArrayInitializer nodes to make them discoverable |
+ // by traversals of the ast. |
+ ast = new jsAst.ArrayInitializer( |
+ names.map((Iterable i) => new jsAst.ArrayInitializer(i.toList())) |
+ .toList()); |
+ } |
+ |
+ void _computeDiffEncodingForList(Iterable<jsAst.Name> names, |
+ StringBuffer diffEncoding) { |
+ // 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... |
+ // A lower case letter terminates the number. |
+ 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()); |
+ } |
+ |
+ // Sort by length, then lexicographic. |
+ int compare(String a, String b) { |
+ if (a.length != b.length) return a.length - b.length; |
+ return a.compareTo(b); |
+ } |
+ |
+ List<String> shorts = |
+ names.map((jsAst.Name name) => name.name) |
+ .toList() |
+ ..sort(compare); |
+ |
+ int previous = 0; |
+ for (String short in shorts) { |
+ if (useDiffEncoding && |
+ short.length <= NsmEmitter.MAX_MINIFIED_LENGTH_FOR_DIFF_ENCODING) { |
+ 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); |
+ } |
+ } |
+ } |
+ |
+ String _computeDiffEncoding() { |
+ StringBuffer buffer = new StringBuffer(); |
+ for (jsAst.ArrayInitializer list in ast.elements) { |
+ if (buffer.isNotEmpty) { |
+ if (useDiffEncoding) { |
+ // Emit period that resets the diff base to zero when we switch to |
+ // normal calling convention (this avoids the need to code negative |
+ // diffs). |
+ buffer.write("."); |
+ } else { |
+ // Write a separator for the next sequence. |
+ buffer.write(","); |
+ } |
+ } |
+ List<jsAst.Name> names = list.elements; |
+ _computeDiffEncodingForList(names, buffer); |
+ } |
+ return '"${buffer.toString()}"'; |
+ } |
+ |
+ String get value { |
+ if (_cachedValue == null) { |
+ _cachedValue = _computeDiffEncoding(); |
+ } |
+ |
+ return _cachedValue; |
+ } |
+} |