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 |
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 | 22 |
23 ClassStubGenerator generator = | 23 ClassStubGenerator generator = |
24 new ClassStubGenerator(compiler, namer, backend); | 24 new ClassStubGenerator(compiler, namer, backend); |
25 | 25 |
26 // Keep track of the JavaScript names we've already added so we | 26 // Keep track of the JavaScript names we've already added so we |
27 // do not introduce duplicates (bad for code size). | 27 // do not introduce duplicates (bad for code size). |
28 Map<String, Selector> addedJsNames | 28 Map<jsAst.Name, Selector> addedJsNames |
29 = generator.computeSelectorsForNsmHandlers(); | 29 = generator.computeSelectorsForNsmHandlers(); |
30 | 30 |
31 // Set flag used by generateMethod helper below. If we have very few | 31 // 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 | 32 // handlers we use addProperty for them all, rather than try to generate |
33 // them at runtime. | 33 // them at runtime. |
34 bool haveVeryFewNoSuchMemberHandlers = | 34 bool haveVeryFewNoSuchMemberHandlers = |
35 (addedJsNames.length < VERY_FEW_NO_SUCH_METHOD_HANDLERS); | 35 (addedJsNames.length < VERY_FEW_NO_SUCH_METHOD_HANDLERS); |
36 for (String jsName in addedJsNames.keys.toList()..sort()) { | 36 List<jsAst.Name> names = addedJsNames.keys.toList()..sort(); |
floitsch
2015/06/22 17:43:45
sort on a different line?
herhut
2015/06/23 13:26:32
Done.
| |
37 for (jsAst.Name jsName in names) { | |
37 Selector selector = addedJsNames[jsName]; | 38 Selector selector = addedJsNames[jsName]; |
38 String reflectionName = emitter.getReflectionName(selector, jsName); | 39 String reflectionName = emitter.getReflectionName(selector, jsName); |
39 | 40 |
40 if (reflectionName != null) { | 41 if (reflectionName != null) { |
41 emitter.mangledFieldNames[jsName] = reflectionName; | 42 emitter.mangledFieldNames[jsName] = reflectionName; |
42 } | 43 } |
43 | 44 |
44 List<jsAst.Expression> argNames = | 45 List<jsAst.Expression> argNames = |
45 selector.callStructure.getOrderedNamedArguments().map((String name) => | 46 selector.callStructure.getOrderedNamedArguments().map((String name) => |
46 js.string(name)).toList(); | 47 js.string(name)).toList(); |
47 int type = selector.invocationMirrorKind; | 48 int type = selector.invocationMirrorKind; |
48 if (!haveVeryFewNoSuchMemberHandlers && | 49 if (!haveVeryFewNoSuchMemberHandlers && |
49 isTrivialNsmHandler(type, argNames, selector, jsName) && | 50 isTrivialNsmHandler(type, argNames, selector, jsName) && |
50 reflectionName == null) { | 51 reflectionName == null) { |
51 trivialNsmHandlers.add(selector); | 52 trivialNsmHandlers.add(selector); |
52 } else { | 53 } else { |
53 StubMethod method = | 54 StubMethod method = |
54 generator.generateStubForNoSuchMethod(jsName, selector); | 55 generator.generateStubForNoSuchMethod(jsName, selector); |
55 addProperty(method.name, method.code); | 56 addProperty(method.name, method.code); |
56 if (reflectionName != null) { | 57 if (reflectionName != null) { |
57 bool accessible = | 58 bool accessible = |
58 compiler.world.allFunctions.filter(selector, null).any( | 59 compiler.world.allFunctions.filter(selector, null).any( |
59 (Element e) => backend.isAccessibleByReflection(e)); | 60 (Element e) => backend.isAccessibleByReflection(e)); |
60 addProperty('+$reflectionName', js(accessible ? '2' : '0')); | 61 addProperty(namer.asName('+$reflectionName'), |
62 js(accessible ? '2' : '0')); | |
61 } | 63 } |
62 } | 64 } |
63 } | 65 } |
64 } | 66 } |
65 | 67 |
66 // Identify the noSuchMethod handlers that are so simple that we can | 68 // Identify the noSuchMethod handlers that are so simple that we can |
67 // generate them programatically. | 69 // generate them programatically. |
68 bool isTrivialNsmHandler( | 70 bool isTrivialNsmHandler( |
69 int type, List argNames, Selector selector, String internalName) { | 71 int type, List argNames, Selector selector, jsAst.Name internalName) { |
70 if (!generateTrivialNsmHandlers) return false; | 72 if (!generateTrivialNsmHandlers) return false; |
71 // Check for interceptor calling convention. | 73 // Check for interceptor calling convention. |
72 if (backend.isInterceptedName(selector.name)) { | 74 if (backend.isInterceptedName(selector.name)) { |
floitsch
2015/06/22 17:43:45
TBH I don't see why we do special casing for the i
herhut
2015/06/23 13:26:31
This is probably an artifact. I checked the decode
| |
73 // We can handle the calling convention used by intercepted names in the | 75 // We can handle the calling convention used by intercepted names in the |
74 // diff encoding, but we don't use that for non-minified code. | 76 // diff encoding, but we don't use that for non-minified code. |
75 if (!compiler.enableMinification) return false; | 77 if (!compiler.enableMinification) return false; |
76 String shortName = namer.invocationMirrorInternalName(selector); | 78 // TODO(herhut): We can no longer answer the below with the late bound |
77 if (shortName.length > MAX_MINIFIED_LENGTH_FOR_DIFF_ENCODING) { | 79 // names. However, names longer than 4 are bad anyway. |
78 return false; | 80 // jsAst.Name shortName = namer.invocationMirrorInternalName(selector); |
79 } | 81 // if (shortName.length > MAX_MINIFIED_LENGTH_FOR_DIFF_ENCODING) { |
82 // return false; | |
83 // } | |
80 } | 84 } |
81 // Check for named arguments. | 85 // Check for named arguments. |
82 if (argNames.length != 0) return false; | 86 if (argNames.length != 0) return false; |
83 // Check for unexpected name (this doesn't really happen). | 87 // Check for unexpected name (this doesn't really happen). |
84 if (internalName.startsWith(namer.getterPrefix[0])) return type == 1; | 88 if (internalName is GetterName) return type == 1; |
85 if (internalName.startsWith(namer.setterPrefix[0])) return type == 2; | 89 if (internalName is SetterName) return type == 2; |
86 return type == 0; | 90 return type == 0; |
87 } | 91 } |
88 | 92 |
89 /** | 93 /** |
90 * Adds (at runtime) the handlers to the Object class which catch calls to | 94 * Adds (at runtime) the handlers to the Object class which catch calls to |
91 * methods that the object does not have. The handlers create an invocation | 95 * methods that the object does not have. The handlers create an invocation |
92 * mirror object. | 96 * mirror object. |
93 * | 97 * |
94 * The current version only gives you the minified name when minifying (when | 98 * The current version only gives you the minified name when minifying (when |
95 * not minifying this method is not called). | 99 * not minifying this method is not called). |
(...skipping 25 matching lines...) Expand all Loading... | |
121 * The reason we don't encode long minified names with this method is that | 125 * The reason we don't encode long minified names with this method is that |
122 * decoding the base 88 numbers would overflow JavaScript's puny integers. | 126 * decoding the base 88 numbers would overflow JavaScript's puny integers. |
123 * | 127 * |
124 * There are some selectors that have a special calling convention (because | 128 * There are some selectors that have a special calling convention (because |
125 * they are called with the receiver as the first argument). They need a | 129 * they are called with the receiver as the first argument). They need a |
126 * slightly different noSuchMethod handler, so we handle these first. | 130 * slightly different noSuchMethod handler, so we handle these first. |
127 */ | 131 */ |
128 List<jsAst.Statement> buildTrivialNsmHandlers() { | 132 List<jsAst.Statement> buildTrivialNsmHandlers() { |
129 List<jsAst.Statement> statements = <jsAst.Statement>[]; | 133 List<jsAst.Statement> statements = <jsAst.Statement>[]; |
130 if (trivialNsmHandlers.length == 0) return statements; | 134 if (trivialNsmHandlers.length == 0) return statements; |
131 // Sort by calling convention, JS name length and by JS name. | 135 // Sort by calling convention and by JS name. |
132 trivialNsmHandlers.sort((a, b) { | 136 trivialNsmHandlers.sort((a, b) { |
133 bool aIsIntercepted = backend.isInterceptedName(a.name); | 137 bool aIsIntercepted = backend.isInterceptedName(a.name); |
134 bool bIsIntercepted = backend.isInterceptedName(b.name); | 138 bool bIsIntercepted = backend.isInterceptedName(b.name); |
135 if (aIsIntercepted != bIsIntercepted) return aIsIntercepted ? -1 : 1; | 139 if (aIsIntercepted != bIsIntercepted) return aIsIntercepted ? -1 : 1; |
136 String aName = namer.invocationMirrorInternalName(a); | 140 jsAst.Name aName = namer.invocationMirrorInternalName(a); |
137 String bName = namer.invocationMirrorInternalName(b); | 141 jsAst.Name bName = namer.invocationMirrorInternalName(b); |
138 if (aName.length != bName.length) return aName.length - bName.length; | |
139 return aName.compareTo(bName); | 142 return aName.compareTo(bName); |
sra1
2015/06/23 04:47:54
How does this work?
jsAst.Name does not implement
herhut
2015/06/23 13:26:32
Done.
| |
140 }); | 143 }); |
141 | 144 |
142 // Find out how many selectors there are with the special calling | 145 // Find out how many selectors there are with the special calling |
143 // convention. | 146 // convention. |
144 int firstNormalSelector = trivialNsmHandlers.length; | 147 int firstNormalSelector = trivialNsmHandlers.length; |
145 for (int i = 0; i < trivialNsmHandlers.length; i++) { | 148 for (int i = 0; i < trivialNsmHandlers.length; i++) { |
146 if (!backend.isInterceptedName(trivialNsmHandlers[i].name)) { | 149 if (!backend.isInterceptedName(trivialNsmHandlers[i].name)) { |
147 firstNormalSelector = i; | 150 firstNormalSelector = i; |
sra1
2015/06/23 04:47:54
You can find this without sorting by counting:
tr
herhut
2015/06/23 13:26:32
Done.
| |
148 break; | 151 break; |
149 } | 152 } |
150 } | 153 } |
151 | 154 |
152 // Get the short names (JS names, perhaps minified). | 155 // Get the short names (JS names, perhaps minified). |
153 Iterable<String> shorts = trivialNsmHandlers.map((selector) => | 156 Iterable<jsAst.Name> shorts = trivialNsmHandlers.map((selector) => |
154 namer.invocationMirrorInternalName(selector)); | 157 namer.invocationMirrorInternalName(selector)); |
floitsch
2015/06/22 17:43:45
indentation.
herhut
2015/06/23 13:26:32
Done.
| |
155 var diffEncoding = new StringBuffer(); | |
156 | |
157 // Treat string as a number in base 88 with digits in ASCII order from # to | |
158 // z. The short name sorting is based on length, and uses ASCII order for | |
159 // equal length strings so this means that names are ascending. The hash | |
160 // character, #, is never given as input, but we need it because it's the | |
161 // implicit leading zero (otherwise we could not code names with leading | |
162 // dollar signs). | |
163 int fromBase88(String x) { | |
164 int answer = 0; | |
165 for (int i = 0; i < x.length; i++) { | |
166 int c = x.codeUnitAt(i); | |
167 // No support for Unicode minified identifiers in JS. | |
168 assert(c >= $$ && c <= $z); | |
169 answer *= 88; | |
170 answer += c - $HASH; | |
171 } | |
172 return answer; | |
173 } | |
174 | |
175 // Big endian encoding, A = 0, B = 1... | |
176 // A lower case letter terminates the number. | |
177 String toBase26(int x) { | |
178 int c = x; | |
179 var encodingChars = <int>[]; | |
180 encodingChars.add($a + (c % 26)); | |
181 while (true) { | |
182 c ~/= 26; | |
183 if (c == 0) break; | |
184 encodingChars.add($A + (c % 26)); | |
185 } | |
186 return new String.fromCharCodes(encodingChars.reversed.toList()); | |
187 } | |
188 | 158 |
189 bool minify = compiler.enableMinification; | 159 bool minify = compiler.enableMinification; |
190 bool useDiffEncoding = minify && shorts.length > 30; | 160 bool useDiffEncoding = minify && shorts.length > 30; |
191 | 161 |
192 int previous = 0; | 162 jsAst.Expression diffEncoding; |
193 int nameCounter = 0; | 163 if (useDiffEncoding) { |
194 for (String short in shorts) { | 164 diffEncoding = new _DiffEncodedListOfNames(shorts, firstNormalSelector); |
195 // Emit period that resets the diff base to zero when we switch to normal | 165 } else { |
196 // calling convention (this avoids the need to code negative diffs). | 166 diffEncoding = jsAst.concatenateStrings( |
197 if (useDiffEncoding && nameCounter == firstNormalSelector) { | 167 jsAst.joinLiterals(shorts, jsAst.stringPart(",")), |
sra1
2015/06/23 04:47:54
This also needs to be deferred, since the sort ord
herhut
2015/06/23 13:26:31
Done.
| |
198 diffEncoding.write("."); | 168 addQuotes: true); |
199 previous = 0; | |
200 } | |
201 if (short.length <= MAX_MINIFIED_LENGTH_FOR_DIFF_ENCODING && | |
202 useDiffEncoding) { | |
203 int base63 = fromBase88(short); | |
204 int diff = base63 - previous; | |
205 previous = base63; | |
206 String base26Diff = toBase26(diff); | |
207 diffEncoding.write(base26Diff); | |
208 } else { | |
209 if (useDiffEncoding || diffEncoding.length != 0) { | |
210 diffEncoding.write(","); | |
211 } | |
212 diffEncoding.write(short); | |
213 } | |
214 nameCounter++; | |
215 } | 169 } |
216 | 170 |
217 // Startup code that loops over the method names and puts handlers on the | 171 // Startup code that loops over the method names and puts handlers on the |
218 // Object class to catch noSuchMethod invocations. | 172 // Object class to catch noSuchMethod invocations. |
219 ClassElement objectClass = compiler.objectClass; | 173 ClassElement objectClass = compiler.objectClass; |
220 jsAst.Expression createInvocationMirror = backend.emitter | 174 jsAst.Expression createInvocationMirror = backend.emitter |
221 .staticFunctionAccess(backend.getCreateInvocationMirror()); | 175 .staticFunctionAccess(backend.getCreateInvocationMirror()); |
222 if (useDiffEncoding) { | 176 if (useDiffEncoding) { |
223 statements.add(js.statement('''{ | 177 statements.add(js.statement('''{ |
224 var objectClassObject = processedClasses.collected[#objectClass], | 178 var objectClassObject = processedClasses.collected[#objectClass], |
(...skipping 27 matching lines...) Expand all Loading... | |
252 for (var remaining = nameNumber; | 206 for (var remaining = nameNumber; |
253 remaining > 0; | 207 remaining > 0; |
254 remaining = (remaining / 88) | 0) { | 208 remaining = (remaining / 88) | 0) { |
255 codes.unshift(${$HASH} + remaining % 88); | 209 codes.unshift(${$HASH} + remaining % 88); |
256 } | 210 } |
257 calculatedShortNames.push( | 211 calculatedShortNames.push( |
258 String.fromCharCode.apply(String, codes)); | 212 String.fromCharCode.apply(String, codes)); |
259 } | 213 } |
260 shortNames.splice.apply(shortNames, calculatedShortNames); | 214 shortNames.splice.apply(shortNames, calculatedShortNames); |
261 } | 215 } |
262 }''', {'objectClass': js.string(namer.className(objectClass)), | 216 }''', {'objectClass': js.quoteName(namer.className(objectClass)), |
263 'diffEncoding': js.string('$diffEncoding')})); | 217 'diffEncoding': diffEncoding})); |
264 } else { | 218 } else { |
265 // No useDiffEncoding version. | 219 // No useDiffEncoding version. |
266 Iterable<String> longs = trivialNsmHandlers.map((selector) => | 220 Iterable<String> longs = trivialNsmHandlers.map((selector) => |
267 selector.invocationMirrorMemberName); | 221 selector.invocationMirrorMemberName); |
268 statements.add(js.statement( | 222 statements.add(js.statement( |
269 'var objectClassObject = processedClasses.collected[#objectClass],' | 223 'var objectClassObject = processedClasses.collected[#objectClass],' |
270 ' shortNames = #diffEncoding.split(",")', | 224 ' shortNames = #diffEncoding.split(",")', |
271 {'objectClass': js.string(namer.className(objectClass)), | 225 {'objectClass': js.quoteName(namer.className(objectClass)), |
272 'diffEncoding': js.string('$diffEncoding')})); | 226 'diffEncoding': diffEncoding})); |
273 if (!minify) { | 227 if (!minify) { |
274 statements.add(js.statement('var longNames = #longs.split(",")', | 228 statements.add(js.statement('var longNames = #longs.split(",")', |
275 {'longs': js.string(longs.join(','))})); | 229 {'longs': js.string(longs.join(','))})); |
276 } | 230 } |
277 statements.add(js.statement( | 231 statements.add(js.statement( |
278 'if (objectClassObject instanceof Array)' | 232 'if (objectClassObject instanceof Array)' |
279 ' objectClassObject = objectClassObject[1];')); | 233 ' objectClassObject = objectClassObject[1];')); |
280 } | 234 } |
281 | 235 |
282 List<jsAst.Expression> sliceOffsetArguments = | 236 List<jsAst.Expression> sliceOffsetArguments = |
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
317 }''', { | 271 }''', { |
318 'sliceOffsetParams': sliceOffsetParams, | 272 'sliceOffsetParams': sliceOffsetParams, |
319 'noSuchMethodName': namer.noSuchMethodName, | 273 'noSuchMethodName': namer.noSuchMethodName, |
320 'createInvocationMirror': createInvocationMirror, | 274 'createInvocationMirror': createInvocationMirror, |
321 'names': minify ? 'shortNames' : 'longNames', | 275 'names': minify ? 'shortNames' : 'longNames', |
322 'sliceOffsetArguments': sliceOffsetArguments})); | 276 'sliceOffsetArguments': sliceOffsetArguments})); |
323 | 277 |
324 return statements; | 278 return statements; |
325 } | 279 } |
326 } | 280 } |
281 | |
282 /// When pretty printed, this node computes a diff-encoded string for the list | |
283 /// of given names. | |
floitsch
2015/06/22 17:43:45
Refer or copy the documentation from buildTrivialN
herhut
2015/06/23 13:26:32
Done.
| |
284 class _DiffEncodedListOfNames extends jsAst.DeferredString | |
285 implements AstContainer { | |
286 String _cachedValue; | |
287 jsAst.ArrayInitializer ast; | |
288 int firstNormalSelector; | |
289 | |
290 _DiffEncodedListOfNames(Iterable<jsAst.Name> names, | |
291 this.firstNormalSelector) { | |
292 ast = new jsAst.ArrayInitializer(names.toList()); | |
293 } | |
294 | |
295 _computeDiffEncoding() { | |
296 var diffEncoding = new StringBuffer(); | |
297 | |
298 // Treat string as a number in base 88 with digits in ASCII order from # to | |
299 // z. The short name sorting is based on length, and uses ASCII order for | |
300 // equal length strings so this means that names are ascending. The hash | |
301 // character, #, is never given as input, but we need it because it's the | |
302 // implicit leading zero (otherwise we could not code names with leading | |
303 // dollar signs). | |
304 int fromBase88(String x) { | |
305 int answer = 0; | |
306 for (int i = 0; i < x.length; i++) { | |
307 int c = x.codeUnitAt(i); | |
308 // No support for Unicode minified identifiers in JS. | |
309 assert(c >= $$ && c <= $z); | |
310 answer *= 88; | |
311 answer += c - $HASH; | |
312 } | |
313 return answer; | |
314 } | |
315 | |
316 // Big endian encoding, A = 0, B = 1... | |
317 // A lower case letter terminates the number. | |
318 String toBase26(int x) { | |
319 int c = x; | |
320 var encodingChars = <int>[]; | |
321 encodingChars.add($a + (c % 26)); | |
322 while (true) { | |
323 c ~/= 26; | |
324 if (c == 0) break; | |
325 encodingChars.add($A + (c % 26)); | |
326 } | |
327 return new String.fromCharCodes(encodingChars.reversed.toList()); | |
328 } | |
329 | |
330 Iterable<String> shorts = ast.elements.map((jsAst.Name name) => name.name); | |
331 | |
332 int previous = 0; | |
333 int nameCounter = 0; | |
334 for (String short in shorts) { | |
335 // Emit period that resets the diff base to zero when we switch to normal | |
336 // calling convention (this avoids the need to code negative diffs). | |
337 if (nameCounter == firstNormalSelector) { | |
338 diffEncoding.write('.'); | |
339 previous = 0; | |
340 } | |
341 if (short.length <= NsmEmitter.MAX_MINIFIED_LENGTH_FOR_DIFF_ENCODING) { | |
342 int base63 = fromBase88(short); | |
343 int diff = base63 - previous; | |
344 previous = base63; | |
345 String base26Diff = toBase26(diff); | |
346 diffEncoding.write(base26Diff); | |
347 } else { | |
348 if (diffEncoding.length != 0) { | |
349 diffEncoding.write(','); | |
350 } | |
351 diffEncoding.write(short); | |
352 } | |
353 nameCounter++; | |
354 } | |
355 | |
356 _cachedValue = diffEncoding.toString(); | |
357 } | |
358 | |
359 String get value { | |
360 if (_cachedValue == null) { | |
361 _cachedValue = _computeDiffEncoding(); | |
362 } | |
363 | |
364 return _cachedValue; | |
365 } | |
366 } | |
OLD | NEW |