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

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

Issue 1220333003: dart2js: Rename emitters. (Closed) Base URL: git@github.com:dart-lang/sdk.git@master
Patch Set: Created 5 years, 5 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
(Empty)
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
3 // BSD-style license that can be found in the LICENSE file.
4
5 part of dart2js.js_emitter;
6
7 class NsmEmitter extends CodeEmitterHelper {
8 final List<Selector> trivialNsmHandlers = <Selector>[];
9
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.
12 bool get generateTrivialNsmHandlers => true;
13
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
16 // them at run time.
17 static const VERY_FEW_NO_SUCH_METHOD_HANDLERS = 10;
18
19 static const MAX_MINIFIED_LENGTH_FOR_DIFF_ENCODING = 4;
20
21 void emitNoSuchMethodHandlers(AddPropertyFunction addProperty) {
22
23 ClassStubGenerator generator =
24 new ClassStubGenerator(compiler, namer, backend);
25
26 // Keep track of the JavaScript names we've already added so we
27 // do not introduce duplicates (bad for code size).
28 Map<jsAst.Name, Selector> addedJsNames
29 = generator.computeSelectorsForNsmHandlers();
30
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
33 // them at runtime.
34 bool haveVeryFewNoSuchMemberHandlers =
35 (addedJsNames.length < VERY_FEW_NO_SUCH_METHOD_HANDLERS);
36 List<jsAst.Name> names = addedJsNames.keys.toList()
37 ..sort();
38 for (jsAst.Name jsName in names) {
39 Selector selector = addedJsNames[jsName];
40 String reflectionName = emitter.getReflectionName(selector, jsName);
41
42 if (reflectionName != null) {
43 emitter.mangledFieldNames[jsName] = reflectionName;
44 }
45
46 List<jsAst.Expression> argNames =
47 selector.callStructure.getOrderedNamedArguments().map((String name) =>
48 js.string(name)).toList();
49 int type = selector.invocationMirrorKind;
50 if (!haveVeryFewNoSuchMemberHandlers &&
51 isTrivialNsmHandler(type, argNames, selector, jsName) &&
52 reflectionName == null) {
53 trivialNsmHandlers.add(selector);
54 } else {
55 StubMethod method =
56 generator.generateStubForNoSuchMethod(jsName, selector);
57 addProperty(method.name, method.code);
58 if (reflectionName != null) {
59 bool accessible =
60 compiler.world.allFunctions.filter(selector, null).any(
61 (Element e) => backend.isAccessibleByReflection(e));
62 addProperty(namer.asName('+$reflectionName'),
63 js(accessible ? '2' : '0'));
64 }
65 }
66 }
67 }
68
69 // Identify the noSuchMethod handlers that are so simple that we can
70 // generate them programatically.
71 bool isTrivialNsmHandler(
72 int type, List argNames, Selector selector, jsAst.Name internalName) {
73 if (!generateTrivialNsmHandlers) return false;
74 // Check for named arguments.
75 if (argNames.length != 0) return false;
76 // Check for unexpected name (this doesn't really happen).
77 if (internalName is GetterName) return type == 1;
78 if (internalName is SetterName) return type == 2;
79 return type == 0;
80 }
81
82 /**
83 * Adds (at runtime) the handlers to the Object class which catch calls to
84 * methods that the object does not have. The handlers create an invocation
85 * mirror object.
86 *
87 * The current version only gives you the minified name when minifying (when
88 * not minifying this method is not called).
89 *
90 * In order to generate the noSuchMethod handlers we only need the minified
91 * name of the method. We test the first character of the minified name to
92 * determine if it is a getter or a setter, and we use the arguments array at
93 * runtime to get the number of arguments and their values. If the method
94 * involves named arguments etc. then we don't handle it here, but emit the
95 * handler method directly on the Object class.
96 *
97 * The minified names are mostly 1-4 character names, which we emit in sorted
98 * order (primary key is length, secondary ordering is lexicographic). This
99 * gives an order like ... dD dI dX da ...
100 *
101 * Gzip is good with repeated text, but it can't diff-encode, so we do that
102 * for it. We encode the minified names in a comma-separated string, but all
103 * the 1-4 character names are encoded before the first comma as a series of
104 * base 26 numbers. The last digit of each number is lower case, the others
105 * are upper case, so 1 is "b" and 26 is "Ba".
106 *
107 * We think of the minified names as base 88 numbers using the ASCII
108 * characters from # to z. The base 26 numbers each encode the delta from
109 * the previous minified name to the next. So if there is a minified name
110 * called Df and the next is Dh, then they are 2971 and 2973 when thought of
111 * as base 88 numbers. The difference is 2, which is "c" in lower-case-
112 * terminated base 26.
113 *
114 * The reason we don't encode long minified names with this method is that
115 * decoding the base 88 numbers would overflow JavaScript's puny integers.
116 *
117 * There are some selectors that have a special calling convention (because
118 * they are called with the receiver as the first argument). They need a
119 * slightly different noSuchMethod handler, so we handle these first.
120 */
121 List<jsAst.Statement> buildTrivialNsmHandlers() {
122 List<jsAst.Statement> statements = <jsAst.Statement>[];
123 if (trivialNsmHandlers.length == 0) return statements;
124
125 bool minify = compiler.enableMinification;
126 bool useDiffEncoding = minify && trivialNsmHandlers.length > 30;
127
128 // Find out how many selectors there are with the special calling
129 // convention.
130 Iterable<Selector> interceptedSelectors = trivialNsmHandlers.where(
131 (Selector s) => backend.isInterceptedName(s.name));
132 Iterable<Selector> ordinarySelectors = trivialNsmHandlers.where(
133 (Selector s) => !backend.isInterceptedName(s.name));
134
135 // Get the short names (JS names, perhaps minified).
136 Iterable<jsAst.Name> interceptedShorts =
137 interceptedSelectors.map(namer.invocationMirrorInternalName);
138 Iterable<jsAst.Name> ordinaryShorts =
139 ordinarySelectors.map(namer.invocationMirrorInternalName);
140
141 jsAst.Expression sortedShorts;
142 Iterable<String> sortedLongs;
143 if (useDiffEncoding) {
144 assert(minify);
145 sortedShorts = new _DiffEncodedListOfNames(
146 [interceptedShorts, ordinaryShorts]);
147 } else {
148 Iterable<Selector> sorted =
149 [interceptedSelectors, ordinarySelectors].expand((e) => (e));
150 sortedShorts = js.concatenateStrings(
151 js.joinLiterals(
152 sorted.map(namer.invocationMirrorInternalName),
153 js.stringPart(",")),
154 addQuotes: true);
155
156 if (!minify) {
157 sortedLongs = sorted.map((selector) =>
158 selector.invocationMirrorMemberName);
159 }
160 }
161 // Startup code that loops over the method names and puts handlers on the
162 // Object class to catch noSuchMethod invocations.
163 ClassElement objectClass = compiler.objectClass;
164 jsAst.Expression createInvocationMirror = backend.emitter
165 .staticFunctionAccess(backend.getCreateInvocationMirror());
166 if (useDiffEncoding) {
167 statements.add(js.statement('''{
168 var objectClassObject = processedClasses.collected[#objectClass],
169 nameSequences = #diffEncoding.split("."),
170 shortNames = [];
171 if (objectClassObject instanceof Array)
172 objectClassObject = objectClassObject[1];
173 for (var j = 0; j < nameSequences.length; ++j) {
174 var sequence = nameSequences[j].split(","),
175 nameNumber = 0;
176 // If we are loading a deferred library the object class will not be
177 // in the collectedClasses so objectClassObject is undefined, and we
178 // skip setting up the names.
179 if (!objectClassObject) break;
180 // Likewise, if the current sequence is empty, we don't process it.
181 if (sequence.length == 0) continue;
182 var diffEncodedString = sequence[0];
183 for (var i = 0; i < diffEncodedString.length; i++) {
184 var codes = [],
185 diff = 0,
186 digit = diffEncodedString.charCodeAt(i);
187 for (; digit <= ${$Z};) {
188 diff *= 26;
189 diff += (digit - ${$A});
190 digit = diffEncodedString.charCodeAt(++i);
191 }
192 diff *= 26;
193 diff += (digit - ${$a});
194 nameNumber += diff;
195 for (var remaining = nameNumber;
196 remaining > 0;
197 remaining = (remaining / 88) | 0) {
198 codes.unshift(${$HASH} + remaining % 88);
199 }
200 shortNames.push(
201 String.fromCharCode.apply(String, codes));
202 }
203 if (sequence.length > 1) {
204 Array.prototype.push.apply(shortNames, sequence.shift());
205 }
206 }
207 }''', {'objectClass': js.quoteName(namer.className(objectClass)),
208 'diffEncoding': sortedShorts}));
209 } else {
210 // No useDiffEncoding version.
211 statements.add(js.statement(
212 'var objectClassObject = processedClasses.collected[#objectClass],'
213 ' shortNames = #diffEncoding.split(",")',
214 {'objectClass': js.quoteName(namer.className(objectClass)),
215 'diffEncoding': sortedShorts}));
216 if (!minify) {
217 statements.add(js.statement('var longNames = #longs.split(",")',
218 {'longs': js.string(sortedLongs.join(','))}));
219 }
220 statements.add(js.statement(
221 'if (objectClassObject instanceof Array)'
222 ' objectClassObject = objectClassObject[1];'));
223 }
224
225 dynamic isIntercepted = // jsAst.Expression or bool.
226 interceptedSelectors.isEmpty
227 ? false
228 : ordinarySelectors.isEmpty
229 ? true
230 : js('j < #', js.number(interceptedSelectors.length));
231
232 statements.add(js.statement('''
233 // If we are loading a deferred library the object class will not be in
234 // the collectedClasses so objectClassObject is undefined, and we skip
235 // setting up the names.
236 if (objectClassObject) {
237 for (var j = 0; j < shortNames.length; j++) {
238 var type = 0;
239 var shortName = shortNames[j];
240 if (shortName[0] == "${namer.getterPrefix[0]}") type = 1;
241 if (shortName[0] == "${namer.setterPrefix[0]}") type = 2;
242 // Generate call to:
243 //
244 // createInvocationMirror(String name, internalName, type,
245 // arguments, argumentNames)
246 //
247
248 // This 'if' is either a static choice or dynamic choice depending on
249 // [isIntercepted].
250 if (#isIntercepted) {
251 objectClassObject[shortName] =
252 (function(name, shortName, type) {
253 return function(receiver) {
254 return this.#noSuchMethodName(
255 receiver,
256 #createInvocationMirror(name, shortName, type,
257 // Create proper Array with all arguments except first
258 // (receiver).
259 Array.prototype.slice.call(arguments, 1),
260 []));
261 }
262 })(#names[j], shortName, type);
263 } else {
264 objectClassObject[shortName] =
265 (function(name, shortName, type) {
266 return function() {
267 return this.#noSuchMethodName(
268 // Object.noSuchMethodName ignores the explicit receiver
269 // argument. We could pass anything in place of [this].
270 this,
271 #createInvocationMirror(name, shortName, type,
272 // Create proper Array with all arguments.
273 Array.prototype.slice.call(arguments, 0),
274 []));
275 }
276 })(#names[j], shortName, type);
277 }
278 }
279 }''', {
280 'noSuchMethodName': namer.noSuchMethodName,
281 'createInvocationMirror': createInvocationMirror,
282 'names': minify ? 'shortNames' : 'longNames',
283 'isIntercepted': isIntercepted}));
284
285 return statements;
286 }
287 }
288
289 /// When pretty printed, this node computes a diff-encoded string for the list
290 /// of given names.
291 ///
292 /// See [buildTrivialNsmHandlers].
293 class _DiffEncodedListOfNames extends jsAst.DeferredString
294 implements jsAst.AstContainer {
295 String _cachedValue;
296 List<jsAst.ArrayInitializer> ast;
297
298 Iterable<jsAst.Node> get containedNodes => ast;
299
300 _DiffEncodedListOfNames(Iterable<Iterable<jsAst.Name>> names) {
301 // Store the names in ArrayInitializer nodes to make them discoverable
302 // by traversals of the ast.
303 ast = names.map((Iterable i) => new jsAst.ArrayInitializer(i.toList()))
304 .toList();
305 }
306
307 void _computeDiffEncodingForList(Iterable<jsAst.Name> names,
308 StringBuffer diffEncoding) {
309 // 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
311 // 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
313 // implicit leading zero (otherwise we could not code names with leading
314 // dollar signs).
315 int fromBase88(String x) {
316 int answer = 0;
317 for (int i = 0; i < x.length; i++) {
318 int c = x.codeUnitAt(i);
319 // No support for Unicode minified identifiers in JS.
320 assert(c >= $$ && c <= $z);
321 answer *= 88;
322 answer += c - $HASH;
323 }
324 return answer;
325 }
326
327 // Big endian encoding, A = 0, B = 1...
328 // A lower case letter terminates the number.
329 String toBase26(int x) {
330 int c = x;
331 var encodingChars = <int>[];
332 encodingChars.add($a + (c % 26));
333 while (true) {
334 c ~/= 26;
335 if (c == 0) break;
336 encodingChars.add($A + (c % 26));
337 }
338 return new String.fromCharCodes(encodingChars.reversed.toList());
339 }
340
341 // Sort by length, then lexicographic.
342 int compare(String a, String b) {
343 if (a.length != b.length) return a.length - b.length;
344 return a.compareTo(b);
345 }
346
347 List<String> shorts =
348 names.map((jsAst.Name name) => name.name)
349 .toList()
350 ..sort(compare);
351
352 int previous = 0;
353 for (String short in shorts) {
354 if (short.length <= NsmEmitter.MAX_MINIFIED_LENGTH_FOR_DIFF_ENCODING) {
355 int base63 = fromBase88(short);
356 int diff = base63 - previous;
357 previous = base63;
358 String base26Diff = toBase26(diff);
359 diffEncoding.write(base26Diff);
360 } else {
361 if (diffEncoding.length != 0) {
362 diffEncoding.write(',');
363 }
364 diffEncoding.write(short);
365 }
366 }
367 }
368
369 String _computeDiffEncoding() {
370 StringBuffer buffer = new StringBuffer();
371 for (jsAst.ArrayInitializer list in ast) {
372 if (buffer.isNotEmpty) {
373 // Emit period that resets the diff base to zero when we switch to
374 // normal calling convention (this avoids the need to code negative
375 // diffs).
376 buffer.write(".");
377 }
378 List<jsAst.Name> names = list.elements;
379 _computeDiffEncodingForList(names, buffer);
380 }
381 return '"${buffer.toString()}"';
382 }
383
384 String get value {
385 if (_cachedValue == null) {
386 _cachedValue = _computeDiffEncoding();
387 }
388
389 return _cachedValue;
390 }
391 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698