Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(19)

Side by Side Diff: pkg/compiler/lib/src/js_emitter/old_emitter/nsm_emitter.dart

Issue 1198293002: dart2js: Use an abstract Name class for names in the generated JavaScript ast. (Closed) Base URL: git@github.com:dart-lang/sdk.git@master
Patch Set: Fix new emitter. Created 5 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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
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
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
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 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698