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.full_emitter; | 5 part of dart2js.js_emitter.full_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 |
11 /// time, instead of them being emitted as part of the Object class. | 11 /// time, instead of them being emitted as part of the Object class. |
12 bool get generateTrivialNsmHandlers => true; | 12 bool get generateTrivialNsmHandlers => true; |
13 | 13 |
14 // If we need fewer than this many noSuchMethod handlers we can save space by | 14 // If we need fewer than this many noSuchMethod handlers we can save space by |
15 // just emitting them in JS, rather than emitting the JS needed to generate | 15 // just emitting them in JS, rather than emitting the JS needed to generate |
16 // them at run time. | 16 // them at run time. |
17 static const VERY_FEW_NO_SUCH_METHOD_HANDLERS = 10; | 17 static const VERY_FEW_NO_SUCH_METHOD_HANDLERS = 10; |
18 | 18 |
19 static const MAX_MINIFIED_LENGTH_FOR_DIFF_ENCODING = 4; | 19 static const MAX_MINIFIED_LENGTH_FOR_DIFF_ENCODING = 4; |
20 | 20 |
21 void emitNoSuchMethodHandlers(AddPropertyFunction addProperty) { | 21 void emitNoSuchMethodHandlers(AddPropertyFunction addProperty) { |
22 | |
23 ClassStubGenerator generator = | 22 ClassStubGenerator generator = |
24 new ClassStubGenerator(compiler, namer, backend); | 23 new ClassStubGenerator(compiler, namer, backend); |
25 | 24 |
26 // Keep track of the JavaScript names we've already added so we | 25 // Keep track of the JavaScript names we've already added so we |
27 // do not introduce duplicates (bad for code size). | 26 // do not introduce duplicates (bad for code size). |
28 Map<jsAst.Name, Selector> addedJsNames | 27 Map<jsAst.Name, Selector> addedJsNames = |
29 = generator.computeSelectorsForNsmHandlers(); | 28 generator.computeSelectorsForNsmHandlers(); |
30 | 29 |
31 // Set flag used by generateMethod helper below. If we have very few | 30 // Set flag used by generateMethod helper below. If we have very few |
32 // handlers we use addProperty for them all, rather than try to generate | 31 // handlers we use addProperty for them all, rather than try to generate |
33 // them at runtime. | 32 // them at runtime. |
34 bool haveVeryFewNoSuchMemberHandlers = | 33 bool haveVeryFewNoSuchMemberHandlers = |
35 (addedJsNames.length < VERY_FEW_NO_SUCH_METHOD_HANDLERS); | 34 (addedJsNames.length < VERY_FEW_NO_SUCH_METHOD_HANDLERS); |
36 List<jsAst.Name> names = addedJsNames.keys.toList() | 35 List<jsAst.Name> names = addedJsNames.keys.toList()..sort(); |
37 ..sort(); | |
38 for (jsAst.Name jsName in names) { | 36 for (jsAst.Name jsName in names) { |
39 Selector selector = addedJsNames[jsName]; | 37 Selector selector = addedJsNames[jsName]; |
40 String reflectionName = emitter.getReflectionName(selector, jsName); | 38 String reflectionName = emitter.getReflectionName(selector, jsName); |
41 | 39 |
42 if (reflectionName != null) { | 40 if (reflectionName != null) { |
43 emitter.mangledFieldNames[jsName] = reflectionName; | 41 emitter.mangledFieldNames[jsName] = reflectionName; |
44 } | 42 } |
45 | 43 |
46 List<jsAst.Expression> argNames = | 44 List<jsAst.Expression> argNames = selector.callStructure |
47 selector.callStructure.getOrderedNamedArguments().map((String name) => | 45 .getOrderedNamedArguments() |
48 js.string(name)).toList(); | 46 .map((String name) => js.string(name)) |
| 47 .toList(); |
49 int type = selector.invocationMirrorKind; | 48 int type = selector.invocationMirrorKind; |
50 if (!haveVeryFewNoSuchMemberHandlers && | 49 if (!haveVeryFewNoSuchMemberHandlers && |
51 isTrivialNsmHandler(type, argNames, selector, jsName) && | 50 isTrivialNsmHandler(type, argNames, selector, jsName) && |
52 reflectionName == null) { | 51 reflectionName == null) { |
53 trivialNsmHandlers.add(selector); | 52 trivialNsmHandlers.add(selector); |
54 } else { | 53 } else { |
55 StubMethod method = | 54 StubMethod method = |
56 generator.generateStubForNoSuchMethod(jsName, selector); | 55 generator.generateStubForNoSuchMethod(jsName, selector); |
57 addProperty(method.name, method.code); | 56 addProperty(method.name, method.code); |
58 if (reflectionName != null) { | 57 if (reflectionName != null) { |
59 bool accessible = | 58 bool accessible = compiler.world.allFunctions |
60 compiler.world.allFunctions.filter(selector, null).any( | 59 .filter(selector, null) |
61 (Element e) => backend.isAccessibleByReflection(e)); | 60 .any((Element e) => backend.isAccessibleByReflection(e)); |
62 addProperty(namer.asName('+$reflectionName'), | 61 addProperty( |
63 js(accessible ? '2' : '0')); | 62 namer.asName('+$reflectionName'), js(accessible ? '2' : '0')); |
64 } | 63 } |
65 } | 64 } |
66 } | 65 } |
67 } | 66 } |
68 | 67 |
69 // Identify the noSuchMethod handlers that are so simple that we can | 68 // Identify the noSuchMethod handlers that are so simple that we can |
70 // generate them programatically. | 69 // generate them programatically. |
71 bool isTrivialNsmHandler( | 70 bool isTrivialNsmHandler( |
72 int type, List argNames, Selector selector, jsAst.Name internalName) { | 71 int type, List argNames, Selector selector, jsAst.Name internalName) { |
73 if (!generateTrivialNsmHandlers) return false; | 72 if (!generateTrivialNsmHandlers) return false; |
(...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
120 */ | 119 */ |
121 List<jsAst.Statement> buildTrivialNsmHandlers() { | 120 List<jsAst.Statement> buildTrivialNsmHandlers() { |
122 List<jsAst.Statement> statements = <jsAst.Statement>[]; | 121 List<jsAst.Statement> statements = <jsAst.Statement>[]; |
123 if (trivialNsmHandlers.length == 0) return statements; | 122 if (trivialNsmHandlers.length == 0) return statements; |
124 | 123 |
125 bool minify = compiler.options.enableMinification; | 124 bool minify = compiler.options.enableMinification; |
126 bool useDiffEncoding = minify && trivialNsmHandlers.length > 30; | 125 bool useDiffEncoding = minify && trivialNsmHandlers.length > 30; |
127 | 126 |
128 // Find out how many selectors there are with the special calling | 127 // Find out how many selectors there are with the special calling |
129 // convention. | 128 // convention. |
130 Iterable<Selector> interceptedSelectors = trivialNsmHandlers.where( | 129 Iterable<Selector> interceptedSelectors = trivialNsmHandlers |
131 (Selector s) => backend.isInterceptedName(s.name)); | 130 .where((Selector s) => backend.isInterceptedName(s.name)); |
132 Iterable<Selector> ordinarySelectors = trivialNsmHandlers.where( | 131 Iterable<Selector> ordinarySelectors = trivialNsmHandlers |
133 (Selector s) => !backend.isInterceptedName(s.name)); | 132 .where((Selector s) => !backend.isInterceptedName(s.name)); |
134 | 133 |
135 // Get the short names (JS names, perhaps minified). | 134 // Get the short names (JS names, perhaps minified). |
136 Iterable<jsAst.Name> interceptedShorts = | 135 Iterable<jsAst.Name> interceptedShorts = |
137 interceptedSelectors.map(namer.invocationMirrorInternalName); | 136 interceptedSelectors.map(namer.invocationMirrorInternalName); |
138 Iterable<jsAst.Name> ordinaryShorts = | 137 Iterable<jsAst.Name> ordinaryShorts = |
139 ordinarySelectors.map(namer.invocationMirrorInternalName); | 138 ordinarySelectors.map(namer.invocationMirrorInternalName); |
140 | 139 |
141 jsAst.Expression sortedShorts; | 140 jsAst.Expression sortedShorts; |
142 Iterable<String> sortedLongs; | 141 Iterable<String> sortedLongs; |
143 if (useDiffEncoding) { | 142 if (useDiffEncoding) { |
144 assert(minify); | 143 assert(minify); |
145 sortedShorts = new _DiffEncodedListOfNames( | 144 sortedShorts = |
146 [interceptedShorts, ordinaryShorts]); | 145 new _DiffEncodedListOfNames([interceptedShorts, ordinaryShorts]); |
147 } else { | 146 } else { |
148 Iterable<Selector> sorted = | 147 Iterable<Selector> sorted = |
149 [interceptedSelectors, ordinarySelectors].expand((e) => (e)); | 148 [interceptedSelectors, ordinarySelectors].expand((e) => (e)); |
150 sortedShorts = js.concatenateStrings( | 149 sortedShorts = js.concatenateStrings( |
151 js.joinLiterals( | 150 js.joinLiterals(sorted.map(namer.invocationMirrorInternalName), |
152 sorted.map(namer.invocationMirrorInternalName), | |
153 js.stringPart(",")), | 151 js.stringPart(",")), |
154 addQuotes: true); | 152 addQuotes: true); |
155 | 153 |
156 if (!minify) { | 154 if (!minify) { |
157 sortedLongs = sorted.map((selector) => | 155 sortedLongs = |
158 selector.invocationMirrorMemberName); | 156 sorted.map((selector) => selector.invocationMirrorMemberName); |
159 } | 157 } |
160 } | 158 } |
161 // Startup code that loops over the method names and puts handlers on the | 159 // Startup code that loops over the method names and puts handlers on the |
162 // Object class to catch noSuchMethod invocations. | 160 // Object class to catch noSuchMethod invocations. |
163 ClassElement objectClass = compiler.coreClasses.objectClass; | 161 ClassElement objectClass = compiler.coreClasses.objectClass; |
164 jsAst.Expression createInvocationMirror = backend.emitter | 162 jsAst.Expression createInvocationMirror = backend.emitter |
165 .staticFunctionAccess(backend.helpers.createInvocationMirror); | 163 .staticFunctionAccess(backend.helpers.createInvocationMirror); |
166 if (useDiffEncoding) { | 164 if (useDiffEncoding) { |
167 statements.add(js.statement('''{ | 165 statements.add(js.statement( |
| 166 '''{ |
168 var objectClassObject = processedClasses.collected[#objectClass], | 167 var objectClassObject = processedClasses.collected[#objectClass], |
169 nameSequences = #diffEncoding.split("."), | 168 nameSequences = #diffEncoding.split("."), |
170 shortNames = []; | 169 shortNames = []; |
171 if (objectClassObject instanceof Array) | 170 if (objectClassObject instanceof Array) |
172 objectClassObject = objectClassObject[1]; | 171 objectClassObject = objectClassObject[1]; |
173 for (var j = 0; j < nameSequences.length; ++j) { | 172 for (var j = 0; j < nameSequences.length; ++j) { |
174 var sequence = nameSequences[j].split(","), | 173 var sequence = nameSequences[j].split(","), |
175 nameNumber = 0; | 174 nameNumber = 0; |
176 // If we are loading a deferred library the object class will not be | 175 // If we are loading a deferred library the object class will not be |
177 // in the collectedClasses so objectClassObject is undefined, and we | 176 // in the collectedClasses so objectClassObject is undefined, and we |
(...skipping 19 matching lines...) Expand all Loading... |
197 remaining = (remaining / 88) | 0) { | 196 remaining = (remaining / 88) | 0) { |
198 codes.unshift(${$HASH} + remaining % 88); | 197 codes.unshift(${$HASH} + remaining % 88); |
199 } | 198 } |
200 shortNames.push( | 199 shortNames.push( |
201 String.fromCharCode.apply(String, codes)); | 200 String.fromCharCode.apply(String, codes)); |
202 } | 201 } |
203 if (sequence.length > 1) { | 202 if (sequence.length > 1) { |
204 Array.prototype.push.apply(shortNames, sequence.shift()); | 203 Array.prototype.push.apply(shortNames, sequence.shift()); |
205 } | 204 } |
206 } | 205 } |
207 }''', {'objectClass': js.quoteName(namer.className(objectClass)), | 206 }''', |
208 'diffEncoding': sortedShorts})); | 207 { |
| 208 'objectClass': js.quoteName(namer.className(objectClass)), |
| 209 'diffEncoding': sortedShorts |
| 210 })); |
209 } else { | 211 } else { |
210 // No useDiffEncoding version. | 212 // No useDiffEncoding version. |
211 statements.add(js.statement( | 213 statements.add(js.statement( |
212 'var objectClassObject = processedClasses.collected[#objectClass],' | 214 'var objectClassObject = processedClasses.collected[#objectClass],' |
213 ' shortNames = #diffEncoding.split(",")', | 215 ' shortNames = #diffEncoding.split(",")', |
214 {'objectClass': js.quoteName(namer.className(objectClass)), | 216 { |
215 'diffEncoding': sortedShorts})); | 217 'objectClass': js.quoteName(namer.className(objectClass)), |
| 218 'diffEncoding': sortedShorts |
| 219 })); |
216 if (!minify) { | 220 if (!minify) { |
217 statements.add(js.statement('var longNames = #longs.split(",")', | 221 statements.add(js.statement('var longNames = #longs.split(",")', |
218 {'longs': js.string(sortedLongs.join(','))})); | 222 {'longs': js.string(sortedLongs.join(','))})); |
219 } | 223 } |
220 statements.add(js.statement( | 224 statements.add(js.statement('if (objectClassObject instanceof Array)' |
221 'if (objectClassObject instanceof Array)' | |
222 ' objectClassObject = objectClassObject[1];')); | 225 ' objectClassObject = objectClassObject[1];')); |
223 } | 226 } |
224 | 227 |
225 dynamic isIntercepted = // jsAst.Expression or bool. | 228 dynamic isIntercepted = // jsAst.Expression or bool. |
226 interceptedSelectors.isEmpty | 229 interceptedSelectors.isEmpty |
227 ? false | 230 ? false |
228 : ordinarySelectors.isEmpty | 231 : ordinarySelectors.isEmpty |
229 ? true | 232 ? true |
230 : js('j < #', js.number(interceptedSelectors.length)); | 233 : js('j < #', js.number(interceptedSelectors.length)); |
231 | 234 |
232 statements.add(js.statement(''' | 235 statements.add(js.statement( |
| 236 ''' |
233 // If we are loading a deferred library the object class will not be in | 237 // If we are loading a deferred library the object class will not be in |
234 // the collectedClasses so objectClassObject is undefined, and we skip | 238 // the collectedClasses so objectClassObject is undefined, and we skip |
235 // setting up the names. | 239 // setting up the names. |
236 if (objectClassObject) { | 240 if (objectClassObject) { |
237 for (var j = 0; j < shortNames.length; j++) { | 241 for (var j = 0; j < shortNames.length; j++) { |
238 var type = 0; | 242 var type = 0; |
239 var shortName = shortNames[j]; | 243 var shortName = shortNames[j]; |
240 if (shortName.indexOf("${namer.getterPrefix}") == 0) type = 1; | 244 if (shortName.indexOf("${namer.getterPrefix}") == 0) type = 1; |
241 if (shortName.indexOf("${namer.setterPrefix}") == 0) type = 2; | 245 if (shortName.indexOf("${namer.setterPrefix}") == 0) type = 2; |
242 // Generate call to: | 246 // Generate call to: |
(...skipping 26 matching lines...) Expand all Loading... |
269 // argument. We could pass anything in place of [this]. | 273 // argument. We could pass anything in place of [this]. |
270 this, | 274 this, |
271 #createInvocationMirror(name, shortName, type, | 275 #createInvocationMirror(name, shortName, type, |
272 // Create proper Array with all arguments. | 276 // Create proper Array with all arguments. |
273 Array.prototype.slice.call(arguments, 0), | 277 Array.prototype.slice.call(arguments, 0), |
274 [])); | 278 [])); |
275 } | 279 } |
276 })(#names[j], shortName, type); | 280 })(#names[j], shortName, type); |
277 } | 281 } |
278 } | 282 } |
279 }''', { | 283 }''', |
| 284 { |
280 'noSuchMethodName': namer.noSuchMethodName, | 285 'noSuchMethodName': namer.noSuchMethodName, |
281 'createInvocationMirror': createInvocationMirror, | 286 'createInvocationMirror': createInvocationMirror, |
282 'names': minify ? 'shortNames' : 'longNames', | 287 'names': minify ? 'shortNames' : 'longNames', |
283 'isIntercepted': isIntercepted})); | 288 'isIntercepted': isIntercepted |
| 289 })); |
284 | 290 |
285 return statements; | 291 return statements; |
286 } | 292 } |
287 } | 293 } |
288 | 294 |
289 /// When pretty printed, this node computes a diff-encoded string for the list | 295 /// When pretty printed, this node computes a diff-encoded string for the list |
290 /// of given names. | 296 /// of given names. |
291 /// | 297 /// |
292 /// See [buildTrivialNsmHandlers]. | 298 /// See [buildTrivialNsmHandlers]. |
293 class _DiffEncodedListOfNames extends jsAst.DeferredString | 299 class _DiffEncodedListOfNames extends jsAst.DeferredString |
294 implements jsAst.AstContainer { | 300 implements jsAst.AstContainer { |
295 String _cachedValue; | 301 String _cachedValue; |
296 List<jsAst.ArrayInitializer> ast; | 302 List<jsAst.ArrayInitializer> ast; |
297 | 303 |
298 Iterable<jsAst.Node> get containedNodes => ast; | 304 Iterable<jsAst.Node> get containedNodes => ast; |
299 | 305 |
300 _DiffEncodedListOfNames(Iterable<Iterable<jsAst.Name>> names) { | 306 _DiffEncodedListOfNames(Iterable<Iterable<jsAst.Name>> names) { |
301 // Store the names in ArrayInitializer nodes to make them discoverable | 307 // Store the names in ArrayInitializer nodes to make them discoverable |
302 // by traversals of the ast. | 308 // by traversals of the ast. |
303 ast = names.map((Iterable i) => new jsAst.ArrayInitializer(i.toList())) | 309 ast = names |
304 .toList(); | 310 .map((Iterable i) => new jsAst.ArrayInitializer(i.toList())) |
| 311 .toList(); |
305 } | 312 } |
306 | 313 |
307 void _computeDiffEncodingForList(Iterable<jsAst.Name> names, | 314 void _computeDiffEncodingForList( |
308 StringBuffer diffEncoding) { | 315 Iterable<jsAst.Name> names, StringBuffer diffEncoding) { |
309 // Treat string as a number in base 88 with digits in ASCII order from # to | 316 // Treat string as a number in base 88 with digits in ASCII order from # to |
310 // z. The short name sorting is based on length, and uses ASCII order for | 317 // z. The short name sorting is based on length, and uses ASCII order for |
311 // equal length strings so this means that names are ascending. The hash | 318 // equal length strings so this means that names are ascending. The hash |
312 // character, #, is never given as input, but we need it because it's the | 319 // character, #, is never given as input, but we need it because it's the |
313 // implicit leading zero (otherwise we could not code names with leading | 320 // implicit leading zero (otherwise we could not code names with leading |
314 // dollar signs). | 321 // dollar signs). |
315 int fromBase88(String x) { | 322 int fromBase88(String x) { |
316 int answer = 0; | 323 int answer = 0; |
317 for (int i = 0; i < x.length; i++) { | 324 for (int i = 0; i < x.length; i++) { |
318 int c = x.codeUnitAt(i); | 325 int c = x.codeUnitAt(i); |
(...skipping 18 matching lines...) Expand all Loading... |
337 } | 344 } |
338 return new String.fromCharCodes(encodingChars.reversed.toList()); | 345 return new String.fromCharCodes(encodingChars.reversed.toList()); |
339 } | 346 } |
340 | 347 |
341 // Sort by length, then lexicographic. | 348 // Sort by length, then lexicographic. |
342 int compare(String a, String b) { | 349 int compare(String a, String b) { |
343 if (a.length != b.length) return a.length - b.length; | 350 if (a.length != b.length) return a.length - b.length; |
344 return a.compareTo(b); | 351 return a.compareTo(b); |
345 } | 352 } |
346 | 353 |
347 List<String> shorts = | 354 List<String> shorts = names.map((jsAst.Name name) => name.name).toList() |
348 names.map((jsAst.Name name) => name.name) | 355 ..sort(compare); |
349 .toList() | |
350 ..sort(compare); | |
351 | 356 |
352 int previous = 0; | 357 int previous = 0; |
353 for (String short in shorts) { | 358 for (String short in shorts) { |
354 if (short.length <= NsmEmitter.MAX_MINIFIED_LENGTH_FOR_DIFF_ENCODING) { | 359 if (short.length <= NsmEmitter.MAX_MINIFIED_LENGTH_FOR_DIFF_ENCODING) { |
355 int base63 = fromBase88(short); | 360 int base63 = fromBase88(short); |
356 int diff = base63 - previous; | 361 int diff = base63 - previous; |
357 previous = base63; | 362 previous = base63; |
358 String base26Diff = toBase26(diff); | 363 String base26Diff = toBase26(diff); |
359 diffEncoding.write(base26Diff); | 364 diffEncoding.write(base26Diff); |
360 } else { | 365 } else { |
(...skipping 21 matching lines...) Expand all Loading... |
382 } | 387 } |
383 | 388 |
384 String get value { | 389 String get value { |
385 if (_cachedValue == null) { | 390 if (_cachedValue == null) { |
386 _cachedValue = _computeDiffEncoding(); | 391 _cachedValue = _computeDiffEncoding(); |
387 } | 392 } |
388 | 393 |
389 return _cachedValue; | 394 return _cachedValue; |
390 } | 395 } |
391 } | 396 } |
OLD | NEW |