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

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

Issue 602663002: Split interceptor emitter. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 6 years, 2 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 | 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 InterceptorEmitter extends CodeEmitterHelper {
8 final Set<String> interceptorInvocationNames = new Set<String>();
9
10 void recordMangledNameOfMemberMethod(FunctionElement member, String name) {
11 if (backend.isInterceptedMethod(member)) {
12 interceptorInvocationNames.add(name);
13 }
14 }
15
16 Set<ClassElement> interceptorsReferencedFromConstants() {
17 Set<ClassElement> classes = new Set<ClassElement>();
18 JavaScriptConstantCompiler handler = backend.constants;
19 List<Constant> constants = handler.getConstantsForEmission();
20 for (Constant constant in constants) {
21 if (constant is InterceptorConstant) {
22 InterceptorConstant interceptorConstant = constant;
23 classes.add(interceptorConstant.dispatchedType.element);
24 }
25 }
26 return classes;
27 }
28
29 void emitGetInterceptorMethod(CodeBuffer buffer,
30 String key,
31 Set<ClassElement> classes) {
32 jsAst.Expression interceptorFor(ClassElement cls) {
33 return js('#.prototype', namer.elementAccess(cls));
34 }
35
36 /**
37 * Build a JavaScrit AST node for doing a type check on
38 * [cls]. [cls] must be a non-native interceptor class.
39 */
40 jsAst.Statement buildInterceptorCheck(ClassElement cls) {
41 jsAst.Expression condition;
42 assert(backend.isInterceptorClass(cls));
43 if (cls == backend.jsBoolClass) {
44 condition = js('(typeof receiver) == "boolean"');
45 } else if (cls == backend.jsIntClass ||
46 cls == backend.jsDoubleClass ||
47 cls == backend.jsNumberClass) {
48 throw 'internal error';
49 } else if (cls == backend.jsArrayClass ||
50 cls == backend.jsMutableArrayClass ||
51 cls == backend.jsFixedArrayClass ||
52 cls == backend.jsExtendableArrayClass) {
53 condition = js('receiver.constructor == Array');
54 } else if (cls == backend.jsStringClass) {
55 condition = js('(typeof receiver) == "string"');
56 } else if (cls == backend.jsNullClass) {
57 condition = js('receiver == null');
58 } else {
59 throw 'internal error';
60 }
61 return js.statement('if (#) return #', [condition, interceptorFor(cls)]);
62 }
63
64 bool hasArray = false;
65 bool hasBool = false;
66 bool hasDouble = false;
67 bool hasInt = false;
68 bool hasNull = false;
69 bool hasNumber = false;
70 bool hasString = false;
71 bool hasNative = false;
72 bool anyNativeClasses = compiler.enqueuer.codegen.nativeEnqueuer
73 .hasInstantiatedNativeClasses();
74
75 for (ClassElement cls in classes) {
76 if (cls == backend.jsArrayClass ||
77 cls == backend.jsMutableArrayClass ||
78 cls == backend.jsFixedArrayClass ||
79 cls == backend.jsExtendableArrayClass) hasArray = true;
80 else if (cls == backend.jsBoolClass) hasBool = true;
81 else if (cls == backend.jsDoubleClass) hasDouble = true;
82 else if (cls == backend.jsIntClass) hasInt = true;
83 else if (cls == backend.jsNullClass) hasNull = true;
84 else if (cls == backend.jsNumberClass) hasNumber = true;
85 else if (cls == backend.jsStringClass) hasString = true;
86 else {
87 // The set of classes includes classes mixed-in to interceptor classes
88 // and user extensions of native classes.
89 //
90 // The set of classes also includes the 'primitive' interceptor
91 // PlainJavaScriptObject even when it has not been resolved, since it is
92 // only resolved through the reference in getNativeInterceptor when
93 // getNativeInterceptor is marked as used. Guard against probing
94 // unresolved PlainJavaScriptObject by testing for anyNativeClasses.
95
96 if (anyNativeClasses) {
97 if (Elements.isNativeOrExtendsNative(cls)) hasNative = true;
98 }
99 }
100 }
101 if (hasDouble) {
102 hasNumber = true;
103 }
104 if (hasInt) hasNumber = true;
105
106 if (classes.containsAll(backend.interceptedClasses)) {
107 // I.e. this is the general interceptor.
108 hasNative = anyNativeClasses;
109 }
110
111 List<jsAst.Statement> statements = <jsAst.Statement>[];
112
113 if (hasNumber) {
114 jsAst.Statement whenNumber;
115
116 /// Note: there are two number classes in play: Dart's [num],
117 /// and JavaScript's Number (typeof receiver == 'number'). This
118 /// is the fallback used when we have determined that receiver
119 /// is a JavaScript Number.
120 jsAst.Expression interceptorForNumber = interceptorFor(
121 hasDouble ? backend.jsDoubleClass : backend.jsNumberClass);
122
123 if (hasInt) {
124 whenNumber = js.statement('''{
125 if (Math.floor(receiver) == receiver) return #;
126 return #;
127 }''', [interceptorFor(backend.jsIntClass), interceptorForNumber]);
128 } else {
129 whenNumber = js.statement('return #', interceptorForNumber);
130 }
131 statements.add(
132 js.statement('if (typeof receiver == "number") #;', whenNumber));
133 }
134
135 if (hasString) {
136 statements.add(buildInterceptorCheck(backend.jsStringClass));
137 }
138 if (hasNull) {
139 statements.add(buildInterceptorCheck(backend.jsNullClass));
140 } else {
141 // Returning "undefined" or "null" here will provoke a JavaScript
142 // TypeError which is later identified as a null-error by
143 // [unwrapException] in js_helper.dart.
144 statements.add(
145 js.statement('if (receiver == null) return receiver'));
146 }
147 if (hasBool) {
148 statements.add(buildInterceptorCheck(backend.jsBoolClass));
149 }
150 // TODO(ahe): It might be faster to check for Array before
151 // function and bool.
152 if (hasArray) {
153 statements.add(buildInterceptorCheck(backend.jsArrayClass));
154 }
155
156 if (hasNative) {
157 statements.add(js.statement(r'''{
158 if (typeof receiver != "object") return receiver;
159 if (receiver instanceof #) return receiver;
160 return #(receiver);
161 }''', [
162 namer.elementAccess(compiler.objectClass),
163 namer.elementAccess(backend.getNativeInterceptorMethod)]));
164
165 } else {
166 ClassElement jsUnknown = backend.jsUnknownJavaScriptObjectClass;
167 if (compiler.codegenWorld.instantiatedClasses.contains(jsUnknown)) {
168 statements.add(
169 js.statement('if (!(receiver instanceof #)) return #;',
170 [namer.elementAccess(compiler.objectClass),
171 interceptorFor(jsUnknown)]));
172 }
173
174 statements.add(js.statement('return receiver'));
175 }
176
177 buffer.write(jsAst.prettyPrint(
178 js('''${namer.globalObjectFor(backend.interceptorsLibrary)}.# =
179 function(receiver) { #; }''',
180 [key, statements]),
181 compiler));
182 buffer.write(N);
183 }
184
185 /**
186 * Emit all versions of the [:getInterceptor:] method.
187 */
188 void emitGetInterceptorMethods(CodeBuffer buffer) {
189 emitter.addComment('getInterceptor methods', buffer);
190 Map<String, Set<ClassElement>> specializedGetInterceptors =
191 backend.specializedGetInterceptors;
192 for (String name in specializedGetInterceptors.keys.toList()..sort()) {
193 Set<ClassElement> classes = specializedGetInterceptors[name];
194 emitGetInterceptorMethod(buffer, name, classes);
195 }
196 }
197
198 // Returns a statement that takes care of performance critical
199 // common case for a one-shot interceptor, or null if there is no
200 // fast path.
201 jsAst.Statement fastPathForOneShotInterceptor(Selector selector,
202 Set<ClassElement> classes) {
203
204 if (selector.isOperator) {
205 String name = selector.name;
206 if (name == '==') {
207 return js.statement('''{
208 if (receiver == null) return a0 == null;
209 if (typeof receiver != "object")
210 return a0 != null && receiver === a0;
211 }''');
212 }
213 if (!classes.contains(backend.jsIntClass)
214 && !classes.contains(backend.jsNumberClass)
215 && !classes.contains(backend.jsDoubleClass)) {
216 return null;
217 }
218 if (selector.argumentCount == 1) {
219 // The following operators do not map to a JavaScript operator.
220 if (name == '~/' || name == '<<' || name == '%' || name == '>>') {
221 return null;
222 }
223 jsAst.Expression result = js('receiver $name a0');
224 if (name == '&' || name == '|' || name == '^') {
225 result = js('# >>> 0', result);
226 }
227 return js.statement(
228 'if (typeof receiver == "number" && typeof a0 == "number")'
229 ' return #;',
230 result);
231 } else if (name == 'unary-') {
232 return js.statement(
233 'if (typeof receiver == "number") return -receiver');
234 } else {
235 assert(name == '~');
236 return js.statement('''
237 if (typeof receiver == "number" && Math.floor(receiver) == receiver)
238 return (~receiver) >>> 0;
239 ''');
240 }
241 } else if (selector.isIndex || selector.isIndexSet) {
242 // For an index operation, this code generates:
243 //
244 // if (receiver.constructor == Array || typeof receiver == "string") {
245 // if (a0 >>> 0 === a0 && a0 < receiver.length) {
246 // return receiver[a0];
247 // }
248 // }
249 //
250 // For an index set operation, this code generates:
251 //
252 // if (receiver.constructor == Array && !receiver.immutable$list) {
253 // if (a0 >>> 0 === a0 && a0 < receiver.length) {
254 // return receiver[a0] = a1;
255 // }
256 // }
257 bool containsArray = classes.contains(backend.jsArrayClass);
258 bool containsString = classes.contains(backend.jsStringClass);
259 bool containsJsIndexable =
260 backend.jsIndexingBehaviorInterface.isResolved && classes.any((cls) {
261 return compiler.world.isSubtypeOf(cls,
262 backend.jsIndexingBehaviorInterface);
263 });
264 // The index set operator requires a check on its set value in
265 // checked mode, so we don't optimize the interceptor if the
266 // compiler has type assertions enabled.
267 if (selector.isIndexSet
268 && (compiler.enableTypeAssertions || !containsArray)) {
269 return null;
270 }
271 if (!containsArray && !containsString) {
272 return null;
273 }
274 jsAst.Expression arrayCheck = js('receiver.constructor == Array');
275 jsAst.Expression indexableCheck =
276 backend.generateIsJsIndexableCall(js('receiver'), js('receiver'));
277
278 jsAst.Expression orExp(left, right) {
279 return left == null ? right : js('# || #', [left, right]);
280 }
281
282 if (selector.isIndex) {
283 jsAst.Expression typeCheck;
284 if (containsArray) {
285 typeCheck = arrayCheck;
286 }
287
288 if (containsString) {
289 typeCheck = orExp(typeCheck, js('typeof receiver == "string"'));
290 }
291
292 if (containsJsIndexable) {
293 typeCheck = orExp(typeCheck, indexableCheck);
294 }
295
296 return js.statement('''
297 if (#)
298 if ((a0 >>> 0) === a0 && a0 < receiver.length)
299 return receiver[a0];
300 ''', typeCheck);
301 } else {
302 jsAst.Expression typeCheck;
303 if (containsArray) {
304 typeCheck = arrayCheck;
305 }
306
307 if (containsJsIndexable) {
308 typeCheck = orExp(typeCheck, indexableCheck);
309 }
310
311 return js.statement(r'''
312 if (# && !receiver.immutable$list &&
313 (a0 >>> 0) === a0 && a0 < receiver.length)
314 return receiver[a0] = a1;
315 ''', typeCheck);
316 }
317 }
318 return null;
319 }
320
321 void emitOneShotInterceptors(CodeBuffer buffer) {
322 List<String> names = backend.oneShotInterceptors.keys.toList();
323 names.sort();
324 for (String name in names) {
325 Selector selector = backend.oneShotInterceptors[name];
326 Set<ClassElement> classes =
327 backend.getInterceptedClassesOn(selector.name);
328 String getInterceptorName =
329 namer.getInterceptorName(backend.getInterceptorMethod, classes);
330
331 List<String> parameterNames = <String>[];
332 parameterNames.add('receiver');
333
334 if (selector.isSetter) {
335 parameterNames.add('value');
336 } else {
337 for (int i = 0; i < selector.argumentCount; i++) {
338 parameterNames.add('a$i');
339 }
340 }
341
342 String invocationName = backend.namer.invocationName(selector);
343 String globalObject = namer.globalObjectFor(backend.interceptorsLibrary);
344
345 jsAst.Statement optimizedPath =
346 fastPathForOneShotInterceptor(selector, classes);
347 if (optimizedPath == null) optimizedPath = js.statement(';');
348
349 jsAst.Expression assignment = js('${globalObject}.# = function(#) {'
350 ' #;'
351 ' return #.#(receiver).#(#) }',
352 [name, parameterNames,
353 optimizedPath,
354 globalObject, getInterceptorName, invocationName, parameterNames]);
355
356 buffer.write(jsAst.prettyPrint(assignment, compiler));
357 buffer.write(N);
358 }
359 }
360
361 /**
362 * If [JSInvocationMirror._invokeOn] has been compiled, emit all the
363 * possible selector names that are intercepted into the
364 * [interceptedNames] top-level variable. The implementation of
365 * [_invokeOn] will use it to determine whether it should call the
366 * method with an extra parameter.
367 */
368 void emitInterceptedNames(CodeBuffer buffer) {
369 // TODO(ahe): We should not generate the list of intercepted names at
370 // compile time, it can be generated automatically at runtime given
371 // subclasses of Interceptor (which can easily be identified).
372 if (!compiler.enabledInvokeOn) return;
373
374 // TODO(ahe): We should roll this into
375 // [emitStaticNonFinalFieldInitializations].
376 String name = backend.namer.getNameOfGlobalField(backend.interceptedNames);
377
378 int index = 0;
379 var invocationNames = interceptorInvocationNames.toList()..sort();
380 List<jsAst.ArrayElement> elements = invocationNames.map(
381 (String invocationName) {
382 jsAst.Literal str = js.string(invocationName);
383 return new jsAst.ArrayElement(index++, str);
384 }).toList();
385 jsAst.ArrayInitializer array =
386 new jsAst.ArrayInitializer(invocationNames.length, elements);
387
388 jsAst.Expression assignment =
389 js('${emitter.isolateProperties}.# = #', [name, array]);
390
391 buffer.write(jsAst.prettyPrint(assignment, compiler));
392 buffer.write(N);
393 }
394
395 /**
396 * Emit initializer for [mapTypeToInterceptor] data structure used by
397 * [findInterceptorForType]. See declaration of [mapTypeToInterceptor] in
398 * `interceptors.dart`.
399 */
400 void emitMapTypeToInterceptor(CodeBuffer buffer) {
401 // TODO(sra): Perhaps inject a constant instead?
402 CustomElementsAnalysis analysis = backend.customElementsAnalysis;
403 if (!analysis.needsTable) return;
404
405 List<jsAst.Expression> elements = <jsAst.Expression>[];
406 JavaScriptConstantCompiler handler = backend.constants;
407 List<Constant> constants =
408 handler.getConstantsForEmission(emitter.compareConstants);
409 for (Constant constant in constants) {
410 if (constant is TypeConstant) {
411 TypeConstant typeConstant = constant;
412 Element element = typeConstant.representedType.element;
413 if (element is ClassElement) {
414 ClassElement classElement = element;
415 if (!analysis.needsClass(classElement)) continue;
416
417 elements.add(emitter.constantReference(constant));
418 elements.add(namer.elementAccess(classElement));
419
420 // Create JavaScript Object map for by-name lookup of generative
421 // constructors. For example, the class A has three generative
422 // constructors
423 //
424 // class A {
425 // A() {}
426 // A.foo() {}
427 // A.bar() {}
428 // }
429 //
430 // Which are described by the map
431 //
432 // {"": A.A$, "foo": A.A$foo, "bar": A.A$bar}
433 //
434 // We expect most of the time the map will be a singleton.
435 var properties = [];
436 for (Element member in analysis.constructors(classElement)) {
437 properties.add(
438 new jsAst.Property(
439 js.string(member.name),
440 backend.namer.elementAccess(member)));
441 }
442
443 var map = new jsAst.ObjectInitializer(properties);
444 elements.add(map);
445 }
446 }
447 }
448
449 jsAst.ArrayInitializer array = new jsAst.ArrayInitializer.from(elements);
450 String name =
451 backend.namer.getNameOfGlobalField(backend.mapTypeToInterceptor);
452 jsAst.Expression assignment =
453 js('${emitter.isolateProperties}.# = #', [name, array]);
454
455 buffer.write(jsAst.prettyPrint(assignment, compiler));
456 buffer.write(N);
457 }
458 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698