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

Side by Side Diff: sdk/lib/_internal/compiler/implementation/js_emitter/old_emitter/nsm_emitter.dart

Issue 694353007: Move dart2js from sdk/lib/_internal/compiler to pkg/compiler (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 6 years, 1 month 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 | Annotate | Revision Log
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 // Do not generate no such method handlers if there is no class.
23 if (compiler.codegenWorld.directlyInstantiatedClasses.isEmpty) return;
24
25 String noSuchMethodName = namer.publicInstanceMethodNameByArity(
26 Compiler.NO_SUCH_METHOD, Compiler.NO_SUCH_METHOD_ARG_COUNT);
27
28 // Keep track of the JavaScript names we've already added so we
29 // do not introduce duplicates (bad for code size).
30 Map<String, Selector> addedJsNames = new Map<String, Selector>();
31
32 void addNoSuchMethodHandlers(String ignore, Set<Selector> selectors) {
33 // Cache the object class and type.
34 ClassElement objectClass = compiler.objectClass;
35 DartType objectType = objectClass.rawType;
36
37 for (Selector selector in selectors) {
38 TypeMask mask = selector.mask;
39 if (mask == null) {
40 mask = new TypeMask.subclass(compiler.objectClass, compiler.world);
41 }
42
43 if (!mask.needsNoSuchMethodHandling(selector, compiler.world)) continue;
44 String jsName = namer.invocationMirrorInternalName(selector);
45 addedJsNames[jsName] = selector;
46 String reflectionName = emitter.getReflectionName(selector, jsName);
47 if (reflectionName != null) {
48 emitter.mangledFieldNames[jsName] = reflectionName;
49 }
50 }
51 }
52
53 compiler.codegenWorld.invokedNames.forEach(addNoSuchMethodHandlers);
54 compiler.codegenWorld.invokedGetters.forEach(addNoSuchMethodHandlers);
55 compiler.codegenWorld.invokedSetters.forEach(addNoSuchMethodHandlers);
56
57 // Set flag used by generateMethod helper below. If we have very few
58 // handlers we use addProperty for them all, rather than try to generate
59 // them at runtime.
60 bool haveVeryFewNoSuchMemberHandlers =
61 (addedJsNames.length < VERY_FEW_NO_SUCH_METHOD_HANDLERS);
62
63 jsAst.Expression generateMethod(String jsName, Selector selector) {
64 // Values match JSInvocationMirror in js-helper library.
65 int type = selector.invocationMirrorKind;
66 List<String> parameterNames =
67 new List.generate(selector.argumentCount, (i) => '\$$i');
68
69 List<jsAst.Expression> argNames =
70 selector.getOrderedNamedArguments().map((String name) =>
71 js.string(name)).toList();
72
73 String methodName = selector.invocationMirrorMemberName;
74 String internalName = namer.invocationMirrorInternalName(selector);
75 String reflectionName = emitter.getReflectionName(selector, internalName);
76 if (!haveVeryFewNoSuchMemberHandlers &&
77 isTrivialNsmHandler(type, argNames, selector, internalName) &&
78 reflectionName == null) {
79 trivialNsmHandlers.add(selector);
80 return null;
81 }
82
83 assert(backend.isInterceptedName(Compiler.NO_SUCH_METHOD));
84 jsAst.Expression expression = js('this.#(this, #(#, #, #, #, #))', [
85 noSuchMethodName,
86 namer.elementAccess(backend.getCreateInvocationMirror()),
87 js.string(compiler.enableMinification ?
88 internalName : methodName),
89 js.string(internalName),
90 js.number(type),
91 new jsAst.ArrayInitializer.from(parameterNames.map(js)),
92 new jsAst.ArrayInitializer.from(argNames)]);
93
94 if (backend.isInterceptedName(selector.name)) {
95 return js(r'function($receiver, #) { return # }',
96 [parameterNames, expression]);
97 } else {
98 return js(r'function(#) { return # }', [parameterNames, expression]);
99 }
100 }
101
102 for (String jsName in addedJsNames.keys.toList()..sort()) {
103 Selector selector = addedJsNames[jsName];
104 jsAst.Expression method = generateMethod(jsName, selector);
105 if (method != null) {
106 addProperty(jsName, method);
107 String reflectionName = emitter.getReflectionName(selector, jsName);
108 if (reflectionName != null) {
109 bool accessible = compiler.world.allFunctions.filter(selector).any(
110 (Element e) => backend.isAccessibleByReflection(e));
111 addProperty('+$reflectionName', js(accessible ? '2' : '0'));
112 }
113 }
114 }
115 }
116
117 // Identify the noSuchMethod handlers that are so simple that we can
118 // generate them programatically.
119 bool isTrivialNsmHandler(
120 int type, List argNames, Selector selector, String internalName) {
121 if (!generateTrivialNsmHandlers) return false;
122 // Check for interceptor calling convention.
123 if (backend.isInterceptedName(selector.name)) {
124 // We can handle the calling convention used by intercepted names in the
125 // diff encoding, but we don't use that for non-minified code.
126 if (!compiler.enableMinification) return false;
127 String shortName = namer.invocationMirrorInternalName(selector);
128 if (shortName.length > MAX_MINIFIED_LENGTH_FOR_DIFF_ENCODING) {
129 return false;
130 }
131 }
132 // Check for named arguments.
133 if (argNames.length != 0) return false;
134 // Check for unexpected name (this doesn't really happen).
135 if (internalName.startsWith(namer.getterPrefix[0])) return type == 1;
136 if (internalName.startsWith(namer.setterPrefix[0])) return type == 2;
137 return type == 0;
138 }
139
140 /**
141 * Adds (at runtime) the handlers to the Object class which catch calls to
142 * methods that the object does not have. The handlers create an invocation
143 * mirror object.
144 *
145 * The current version only gives you the minified name when minifying (when
146 * not minifying this method is not called).
147 *
148 * In order to generate the noSuchMethod handlers we only need the minified
149 * name of the method. We test the first character of the minified name to
150 * determine if it is a getter or a setter, and we use the arguments array at
151 * runtime to get the number of arguments and their values. If the method
152 * involves named arguments etc. then we don't handle it here, but emit the
153 * handler method directly on the Object class.
154 *
155 * The minified names are mostly 1-4 character names, which we emit in sorted
156 * order (primary key is length, secondary ordering is lexicographic). This
157 * gives an order like ... dD dI dX da ...
158 *
159 * Gzip is good with repeated text, but it can't diff-encode, so we do that
160 * for it. We encode the minified names in a comma-separated string, but all
161 * the 1-4 character names are encoded before the first comma as a series of
162 * base 26 numbers. The last digit of each number is lower case, the others
163 * are upper case, so 1 is "b" and 26 is "Ba".
164 *
165 * We think of the minified names as base 88 numbers using the ASCII
166 * characters from # to z. The base 26 numbers each encode the delta from
167 * the previous minified name to the next. So if there is a minified name
168 * called Df and the next is Dh, then they are 2971 and 2973 when thought of
169 * as base 88 numbers. The difference is 2, which is "c" in lower-case-
170 * terminated base 26.
171 *
172 * The reason we don't encode long minified names with this method is that
173 * decoding the base 88 numbers would overflow JavaScript's puny integers.
174 *
175 * There are some selectors that have a special calling convention (because
176 * they are called with the receiver as the first argument). They need a
177 * slightly different noSuchMethod handler, so we handle these first.
178 */
179 List<jsAst.Statement> buildTrivialNsmHandlers() {
180 List<jsAst.Statement> statements = <jsAst.Statement>[];
181 if (trivialNsmHandlers.length == 0) return statements;
182 // Sort by calling convention, JS name length and by JS name.
183 trivialNsmHandlers.sort((a, b) {
184 bool aIsIntercepted = backend.isInterceptedName(a.name);
185 bool bIsIntercepted = backend.isInterceptedName(b.name);
186 if (aIsIntercepted != bIsIntercepted) return aIsIntercepted ? -1 : 1;
187 String aName = namer.invocationMirrorInternalName(a);
188 String bName = namer.invocationMirrorInternalName(b);
189 if (aName.length != bName.length) return aName.length - bName.length;
190 return aName.compareTo(bName);
191 });
192
193 // Find out how many selectors there are with the special calling
194 // convention.
195 int firstNormalSelector = trivialNsmHandlers.length;
196 for (int i = 0; i < trivialNsmHandlers.length; i++) {
197 if (!backend.isInterceptedName(trivialNsmHandlers[i].name)) {
198 firstNormalSelector = i;
199 break;
200 }
201 }
202
203 // Get the short names (JS names, perhaps minified).
204 Iterable<String> shorts = trivialNsmHandlers.map((selector) =>
205 namer.invocationMirrorInternalName(selector));
206 final diffShorts = <String>[];
207 var diffEncoding = new StringBuffer();
208
209 // Treat string as a number in base 88 with digits in ASCII order from # to
210 // z. The short name sorting is based on length, and uses ASCII order for
211 // equal length strings so this means that names are ascending. The hash
212 // character, #, is never given as input, but we need it because it's the
213 // implicit leading zero (otherwise we could not code names with leading
214 // dollar signs).
215 int fromBase88(String x) {
216 int answer = 0;
217 for (int i = 0; i < x.length; i++) {
218 int c = x.codeUnitAt(i);
219 // No support for Unicode minified identifiers in JS.
220 assert(c >= $$ && c <= $z);
221 answer *= 88;
222 answer += c - $HASH;
223 }
224 return answer;
225 }
226
227 // Big endian encoding, A = 0, B = 1...
228 // A lower case letter terminates the number.
229 String toBase26(int x) {
230 int c = x;
231 var encodingChars = <int>[];
232 encodingChars.add($a + (c % 26));
233 while (true) {
234 c ~/= 26;
235 if (c == 0) break;
236 encodingChars.add($A + (c % 26));
237 }
238 return new String.fromCharCodes(encodingChars.reversed.toList());
239 }
240
241 bool minify = compiler.enableMinification;
242 bool useDiffEncoding = minify && shorts.length > 30;
243
244 int previous = 0;
245 int nameCounter = 0;
246 for (String short in shorts) {
247 // Emit period that resets the diff base to zero when we switch to normal
248 // calling convention (this avoids the need to code negative diffs).
249 if (useDiffEncoding && nameCounter == firstNormalSelector) {
250 diffEncoding.write(".");
251 previous = 0;
252 }
253 if (short.length <= MAX_MINIFIED_LENGTH_FOR_DIFF_ENCODING &&
254 useDiffEncoding) {
255 int base63 = fromBase88(short);
256 int diff = base63 - previous;
257 previous = base63;
258 String base26Diff = toBase26(diff);
259 diffEncoding.write(base26Diff);
260 } else {
261 if (useDiffEncoding || diffEncoding.length != 0) {
262 diffEncoding.write(",");
263 }
264 diffEncoding.write(short);
265 }
266 nameCounter++;
267 }
268
269 // Startup code that loops over the method names and puts handlers on the
270 // Object class to catch noSuchMethod invocations.
271 ClassElement objectClass = compiler.objectClass;
272 jsAst.Expression createInvocationMirror = namer.elementAccess(
273 backend.getCreateInvocationMirror());
274 String noSuchMethodName = namer.publicInstanceMethodNameByArity(
275 Compiler.NO_SUCH_METHOD, Compiler.NO_SUCH_METHOD_ARG_COUNT);
276 var type = 0;
277 if (useDiffEncoding) {
278 statements.add(js.statement('''{
279 var objectClassObject =
280 collectedClasses[#], // # is name of class Object.
281 shortNames = #.split(","), // # is diffEncoding.
282 nameNumber = 0,
283 diffEncodedString = shortNames[0],
284 calculatedShortNames = [0, 1]; // 0, 1 are args for splice.
285 // If we are loading a deferred library the object class will not be i n
286 // the collectedClasses so objectClassObject is undefined, and we skip
287 // setting up the names.
288
289 if (objectClassObject) {
290 if (objectClassObject instanceof Array)
291 objectClassObject = objectClassObject[1];
292 for (var i = 0; i < diffEncodedString.length; i++) {
293 var codes = [],
294 diff = 0,
295 digit = diffEncodedString.charCodeAt(i);
296 if (digit == ${$PERIOD}) {
297 nameNumber = 0;
298 digit = diffEncodedString.charCodeAt(++i);
299 }
300 for (; digit <= ${$Z};) {
301 diff *= 26;
302 diff += (digit - ${$A});
303 digit = diffEncodedString.charCodeAt(++i);
304 }
305 diff *= 26;
306 diff += (digit - ${$a});
307 nameNumber += diff;
308 for (var remaining = nameNumber;
309 remaining > 0;
310 remaining = (remaining / 88) | 0) {
311 codes.unshift(${$HASH} + remaining % 88);
312 }
313 calculatedShortNames.push(
314 String.fromCharCode.apply(String, codes));
315 }
316 shortNames.splice.apply(shortNames, calculatedShortNames);
317 }
318 }''', [
319 js.string(namer.getNameOfClass(objectClass)),
320 js.string('$diffEncoding')]));
321 } else {
322 // No useDiffEncoding version.
323 Iterable<String> longs = trivialNsmHandlers.map((selector) =>
324 selector.invocationMirrorMemberName);
325 statements.add(js.statement(
326 'var objectClassObject = collectedClasses[#],'
327 ' shortNames = #.split(",")', [
328 js.string(namer.getNameOfClass(objectClass)),
329 js.string('$diffEncoding')]));
330 if (!minify) {
331 statements.add(js.statement('var longNames = #.split(",")',
332 js.string(longs.join(','))));
333 }
334 statements.add(js.statement(
335 'if (objectClassObject instanceof Array)'
336 ' objectClassObject = objectClassObject[1];'));
337 }
338
339 // TODO(9631): This is no longer valid for native methods.
340 String whatToPatch = emitter.nativeEmitter.handleNoSuchMethod ?
341 "Object.prototype" :
342 "objectClassObject";
343
344 List<jsAst.Expression> sliceOffsetArguments =
345 firstNormalSelector == 0
346 ? []
347 : (firstNormalSelector == shorts.length
348 ? [js.number(1)]
349 : [js('(j < #) ? 1 : 0', js.number(firstNormalSelector))]);
350
351 var sliceOffsetParams = sliceOffsetArguments.isEmpty ? [] : ['sliceOffset'];
352
353 statements.add(js.statement('''
354 // If we are loading a deferred library the object class will not be in
355 // the collectedClasses so objectClassObject is undefined, and we skip
356 // setting up the names.
357 if (objectClassObject) {
358 for (var j = 0; j < shortNames.length; j++) {
359 var type = 0;
360 var short = shortNames[j];
361 if (short[0] == "${namer.getterPrefix[0]}") type = 1;
362 if (short[0] == "${namer.setterPrefix[0]}") type = 2;
363 // Generate call to:
364 //
365 // createInvocationMirror(String name, internalName, type,
366 // arguments, argumentNames)
367 //
368 $whatToPatch[short] = (function(name, short, type, #) {
369 return function() {
370 return this.#(this,
371 #(name, short, type,
372 Array.prototype.slice.call(arguments, #),
373 []));
374 }
375 })(#[j], short, type, #);
376 }
377 }''', [
378 sliceOffsetParams, // parameter
379 noSuchMethodName,
380 createInvocationMirror,
381 sliceOffsetParams, // argument to slice
382 minify ? 'shortNames' : 'longNames',
383 sliceOffsetArguments
384 ]));
385
386 return statements;
387 }
388 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698