OLD | NEW |
| (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 type_graph_inferrer; | |
6 | |
7 /** | |
8 * A set of selector names that [List] implements, that we know do not | |
9 * change the element type of the list, or let the list escape to code | |
10 * that might change the element type. | |
11 */ | |
12 Set<String> okSelectorsSet = new Set<String>.from( | |
13 const <String>[ | |
14 // From Object. | |
15 '==', | |
16 'hashCode', | |
17 'toString', | |
18 'noSuchMethod', | |
19 'runtimeType', | |
20 | |
21 // From Iterable. | |
22 'iterator', | |
23 'map', | |
24 'where', | |
25 'expand', | |
26 'contains', | |
27 'forEach', | |
28 'reduce', | |
29 'fold', | |
30 'every', | |
31 'join', | |
32 'any', | |
33 'toList', | |
34 'toSet', | |
35 'length', | |
36 'isEmpty', | |
37 'isNotEmpty', | |
38 'take', | |
39 'takeWhile', | |
40 'skip', | |
41 'skipWhile', | |
42 'first', | |
43 'last', | |
44 'single', | |
45 'firstWhere', | |
46 'lastWhere', | |
47 'singleWhere', | |
48 'elementAt', | |
49 | |
50 // From List. | |
51 '[]', | |
52 'length', | |
53 'reversed', | |
54 'sort', | |
55 'indexOf', | |
56 'lastIndexOf', | |
57 'clear', | |
58 'remove', | |
59 'removeAt', | |
60 'removeLast', | |
61 'removeWhere', | |
62 'retainWhere', | |
63 'sublist', | |
64 'getRange', | |
65 'removeRange', | |
66 'asMap', | |
67 | |
68 // From JSArray. | |
69 'checkMutable', | |
70 'checkGrowable', | |
71 ]); | |
72 | |
73 Set<String> doNotChangeLengthSelectorsSet = new Set<String>.from( | |
74 const <String>[ | |
75 // From Object. | |
76 '==', | |
77 'hashCode', | |
78 'toString', | |
79 'noSuchMethod', | |
80 'runtimeType', | |
81 | |
82 // From Iterable. | |
83 'iterator', | |
84 'map', | |
85 'where', | |
86 'expand', | |
87 'contains', | |
88 'forEach', | |
89 'reduce', | |
90 'fold', | |
91 'every', | |
92 'join', | |
93 'any', | |
94 'toList', | |
95 'toSet', | |
96 'length', | |
97 'isEmpty', | |
98 'isNotEmpty', | |
99 'take', | |
100 'takeWhile', | |
101 'skip', | |
102 'skipWhile', | |
103 'first', | |
104 'last', | |
105 'single', | |
106 'firstWhere', | |
107 'lastWhere', | |
108 'singleWhere', | |
109 'elementAt', | |
110 | |
111 // From List. | |
112 '[]', | |
113 '[]=', | |
114 'length', | |
115 'reversed', | |
116 'sort', | |
117 'indexOf', | |
118 'lastIndexOf', | |
119 'sublist', | |
120 'getRange', | |
121 'asMap', | |
122 | |
123 // From JSArray. | |
124 'checkMutable', | |
125 'checkGrowable', | |
126 ]); | |
127 | |
128 // A set of selectors we know do not escape the elements inside the | |
129 // list. | |
130 Set<String> doesNotEscapeElementSet = new Set<String>.from( | |
131 const <String>[ | |
132 // From Object. | |
133 '==', | |
134 'hashCode', | |
135 'toString', | |
136 'noSuchMethod', | |
137 'runtimeType', | |
138 | |
139 // From Iterable. | |
140 'isEmpty', | |
141 'isNotEmpty', | |
142 'length', | |
143 'any', | |
144 'contains', | |
145 'every', | |
146 'join', | |
147 | |
148 // From List. | |
149 'add', | |
150 'addAll', | |
151 'clear', | |
152 'fillRange', | |
153 'indexOf', | |
154 'insert', | |
155 'insertAll', | |
156 'lastIndexOf', | |
157 'remove', | |
158 'removeRange', | |
159 'replaceRange', | |
160 'setAll', | |
161 'setRange', | |
162 'shuffle', | |
163 '[]=', | |
164 | |
165 // From JSArray. | |
166 'checkMutable', | |
167 'checkGrowable', | |
168 ]); | |
169 | |
170 bool _VERBOSE = false; | |
171 | |
172 abstract class TracerVisitor implements TypeInformationVisitor { | |
173 final TypeInformation tracedType; | |
174 final TypeGraphInferrerEngine inferrer; | |
175 final Compiler compiler; | |
176 | |
177 static const int MAX_ANALYSIS_COUNT = 16; | |
178 final Setlet<Element> analyzedElements = new Setlet<Element>(); | |
179 | |
180 TracerVisitor(this.tracedType, inferrer) | |
181 : this.inferrer = inferrer, this.compiler = inferrer.compiler; | |
182 | |
183 // Work list that gets populated with [TypeInformation] that could | |
184 // contain the container. | |
185 final List<TypeInformation> workList = <TypeInformation>[]; | |
186 | |
187 // Work list of containers to analyze after analyzing the users of a | |
188 // [TypeInformation] that may be [container]. We know [container] | |
189 // has been stored in these containers and we must check how | |
190 // [container] escapes from these containers. | |
191 final List<ListTypeInformation> containersToAnalyze = | |
192 <ListTypeInformation>[]; | |
193 | |
194 final Setlet<TypeInformation> flowsInto = new Setlet<TypeInformation>(); | |
195 | |
196 // The current [TypeInformation] in the analysis. | |
197 TypeInformation currentUser; | |
198 bool continueAnalyzing = true; | |
199 | |
200 void addNewEscapeInformation(TypeInformation info) { | |
201 if (flowsInto.contains(info)) return; | |
202 flowsInto.add(info); | |
203 workList.add(info); | |
204 } | |
205 | |
206 void analyze() { | |
207 // Collect the [TypeInformation] where the container can flow in, | |
208 // as well as the operations done on all these [TypeInformation]s. | |
209 addNewEscapeInformation(tracedType); | |
210 while (!workList.isEmpty) { | |
211 currentUser = workList.removeLast(); | |
212 currentUser.users.forEach((TypeInformation info) { | |
213 analyzedElements.add(info.owner); | |
214 info.accept(this); | |
215 }); | |
216 while (!containersToAnalyze.isEmpty) { | |
217 analyzeStoredIntoContainer(containersToAnalyze.removeLast()); | |
218 } | |
219 if (!continueAnalyzing) break; | |
220 if (analyzedElements.length > MAX_ANALYSIS_COUNT) { | |
221 bailout('Too many users'); | |
222 break; | |
223 } | |
224 } | |
225 } | |
226 | |
227 void bailout(String reason) { | |
228 if (_VERBOSE) { | |
229 print('Bailing out on $tracedType because: $reason'); | |
230 } | |
231 continueAnalyzing = false; | |
232 } | |
233 | |
234 void visitNarrowTypeInformation(NarrowTypeInformation info) { | |
235 addNewEscapeInformation(info); | |
236 } | |
237 | |
238 void visitPhiElementTypeInformation(PhiElementTypeInformation info) { | |
239 addNewEscapeInformation(info); | |
240 } | |
241 | |
242 void visitElementInContainerTypeInformation( | |
243 ElementInContainerTypeInformation info) { | |
244 addNewEscapeInformation(info); | |
245 } | |
246 | |
247 visitListTypeInformation(ListTypeInformation info) { | |
248 containersToAnalyze.add(info); | |
249 } | |
250 | |
251 void visitConcreteTypeInformation(ConcreteTypeInformation info) {} | |
252 | |
253 void visitClosureTypeInformation(ClosureTypeInformation info) {} | |
254 | |
255 void visitClosureCallSiteTypeInformation( | |
256 ClosureCallSiteTypeInformation info) {} | |
257 | |
258 visitStaticCallSiteTypeInformation(StaticCallSiteTypeInformation info) { | |
259 Element called = info.calledElement; | |
260 if (inferrer.types.getInferredTypeOf(called) == currentUser) { | |
261 addNewEscapeInformation(info); | |
262 } | |
263 } | |
264 | |
265 void analyzeStoredIntoContainer(ListTypeInformation container) { | |
266 inferrer.analyzeContainer(container); | |
267 if (container.bailedOut) { | |
268 bailout('Stored in a container that bailed out'); | |
269 } else { | |
270 container.flowsInto.forEach((flow) { | |
271 flow.users.forEach((user) { | |
272 if (user is !DynamicCallSiteTypeInformation) return; | |
273 if (user.receiver != flow) return; | |
274 if (returnsElementTypeSet.contains(user.selector)) { | |
275 addNewEscapeInformation(user); | |
276 } else if (!doesNotEscapeElementSet.contains(user.selector.name)) { | |
277 bailout('Escape from a container'); | |
278 } | |
279 }); | |
280 }); | |
281 } | |
282 } | |
283 | |
284 bool isAddedToContainer(DynamicCallSiteTypeInformation info) { | |
285 if (info.arguments == null) return false; | |
286 var receiverType = info.receiver.type; | |
287 if (!receiverType.isContainer) return false; | |
288 String selectorName = info.selector.name; | |
289 List<TypeInformation> arguments = info.arguments.positional; | |
290 return (selectorName == '[]=' && currentUser == arguments[1]) | |
291 || (selectorName == 'insert' && currentUser == arguments[0]) | |
292 || (selectorName == 'add' && currentUser == arguments[0]); | |
293 } | |
294 | |
295 void visitDynamicCallSiteTypeInformation( | |
296 DynamicCallSiteTypeInformation info) { | |
297 if (isAddedToContainer(info)) { | |
298 ContainerTypeMask mask = info.receiver.type; | |
299 if (mask.allocationNode != null) { | |
300 ListTypeInformation container = | |
301 inferrer.types.allocatedLists[mask.allocationNode]; | |
302 containersToAnalyze.add(container); | |
303 } else { | |
304 // The [ContainerTypeMask] is a union of two containers, and | |
305 // we lose track of where these containers have been allocated | |
306 // at this point. | |
307 bailout('Stored in too many containers'); | |
308 } | |
309 } | |
310 | |
311 Iterable<Element> inferredTargetTypes = info.targets.map((element) { | |
312 return inferrer.types.getInferredTypeOf(element); | |
313 }); | |
314 if (inferredTargetTypes.any((user) => user == currentUser)) { | |
315 addNewEscapeInformation(info); | |
316 } | |
317 } | |
318 | |
319 bool isParameterOfListAddingMethod(Element element) { | |
320 if (!element.isParameter()) return false; | |
321 if (element.getEnclosingClass() != compiler.backend.listImplementation) { | |
322 return false; | |
323 } | |
324 Element method = element.enclosingElement; | |
325 return (method.name == '[]=') | |
326 || (method.name == 'add') | |
327 || (method.name == 'insert'); | |
328 } | |
329 | |
330 bool isClosure(Element element) { | |
331 if (!element.isFunction()) return false; | |
332 Element outermost = element.getOutermostEnclosingMemberOrTopLevel(); | |
333 return outermost.declaration != element.declaration; | |
334 } | |
335 | |
336 void visitElementTypeInformation(ElementTypeInformation info) { | |
337 Element element = info.element; | |
338 if (element.isParameter() | |
339 && inferrer.isNativeElement(element.enclosingElement)) { | |
340 bailout('Passed to a native method'); | |
341 } | |
342 if (info.isClosurized()) { | |
343 bailout('Returned from a closurized method'); | |
344 } | |
345 if (isClosure(info.element)) { | |
346 bailout('Returned from a closure'); | |
347 } | |
348 if (compiler.backend.isNeededForReflection(info.element)) { | |
349 bailout('Escape in reflection'); | |
350 } | |
351 if (isParameterOfListAddingMethod(info.element)) { | |
352 // These elements are being handled in | |
353 // [visitDynamicCallSiteTypeInformation]. | |
354 return; | |
355 } | |
356 addNewEscapeInformation(info); | |
357 } | |
358 } | |
359 | |
360 class ContainerTracerVisitor extends TracerVisitor { | |
361 // The list of found assignments to the container. | |
362 final List<TypeInformation> assignments = <TypeInformation>[]; | |
363 bool callsGrowableMethod = false; | |
364 | |
365 ContainerTracerVisitor(tracedType, inferrer) : super(tracedType, inferrer); | |
366 | |
367 List<TypeInformation> run() { | |
368 analyze(); | |
369 ListTypeInformation container = tracedType; | |
370 if (continueAnalyzing) { | |
371 if (!callsGrowableMethod && container.inferredLength == null) { | |
372 container.inferredLength = container.originalLength; | |
373 } | |
374 container.flowsInto.addAll(flowsInto); | |
375 return assignments; | |
376 } else { | |
377 callsGrowableMethod = true; | |
378 return null; | |
379 } | |
380 } | |
381 | |
382 visitMapTypeInformation(MapTypeInformation info) { | |
383 bailout('Stored in a map'); | |
384 } | |
385 | |
386 visitClosureCallSiteTypeInformation(ClosureCallSiteTypeInformation info) { | |
387 bailout('Passed to a closure'); | |
388 } | |
389 | |
390 visitStaticCallSiteTypeInformation(StaticCallSiteTypeInformation info) { | |
391 super.visitStaticCallSiteTypeInformation(info); | |
392 Element called = info.calledElement; | |
393 if (called.isForeign(compiler) && called.name == 'JS') { | |
394 bailout('Used in JS ${info.call}'); | |
395 } | |
396 } | |
397 | |
398 visitDynamicCallSiteTypeInformation(DynamicCallSiteTypeInformation info) { | |
399 super.visitDynamicCallSiteTypeInformation(info); | |
400 Selector selector = info.selector; | |
401 String selectorName = selector.name; | |
402 if (currentUser == info.receiver) { | |
403 if (!okSelectorsSet.contains(selectorName)) { | |
404 if (selector.isCall()) { | |
405 int positionalLength = info.arguments.positional.length; | |
406 if (selectorName == 'add') { | |
407 if (positionalLength == 1) { | |
408 assignments.add(info.arguments.positional[0]); | |
409 } | |
410 } else if (selectorName == 'insert') { | |
411 if (positionalLength == 2) { | |
412 assignments.add(info.arguments.positional[1]); | |
413 } | |
414 } else { | |
415 bailout('Used in a not-ok selector'); | |
416 return; | |
417 } | |
418 } else if (selector.isIndexSet()) { | |
419 assignments.add(info.arguments.positional[1]); | |
420 } else if (!selector.isIndex()) { | |
421 bailout('Used in a not-ok selector'); | |
422 return; | |
423 } | |
424 } | |
425 if (!doNotChangeLengthSelectorsSet.contains(selectorName)) { | |
426 callsGrowableMethod = true; | |
427 } | |
428 if (selectorName == 'length' && selector.isSetter()) { | |
429 callsGrowableMethod = true; | |
430 assignments.add(inferrer.types.nullType); | |
431 } | |
432 } else if (selector.isCall() | |
433 && !info.targets.every((element) => element.isFunction())) { | |
434 bailout('Passed to a closure'); | |
435 return; | |
436 } | |
437 } | |
438 } | |
439 | |
440 class ClosureTracerVisitor extends TracerVisitor { | |
441 ClosureTracerVisitor(tracedType, inferrer) : super(tracedType, inferrer); | |
442 | |
443 void run() { | |
444 ClosureTypeInformation closure = tracedType; | |
445 FunctionElement element = closure.element; | |
446 element.functionSignature.forEachParameter((Element parameter) { | |
447 ElementTypeInformation info = inferrer.types.getInferredTypeOf(parameter); | |
448 info.abandonInferencing = false; | |
449 }); | |
450 analyze(); | |
451 element.functionSignature.forEachParameter((Element parameter) { | |
452 ElementTypeInformation info = inferrer.types.getInferredTypeOf(parameter); | |
453 if (continueAnalyzing) { | |
454 info.disableHandleSpecialCases = true; | |
455 } else { | |
456 info.giveUp(inferrer); | |
457 } | |
458 }); | |
459 } | |
460 | |
461 visitMapTypeInformation(MapTypeInformation info) { | |
462 bailout('Stored in a map'); | |
463 } | |
464 | |
465 void analyzeCall(CallSiteTypeInformation info) { | |
466 ClosureTypeInformation closure = tracedType; | |
467 FunctionElement element = closure.element; | |
468 Selector selector = info.selector; | |
469 if (!selector.signatureApplies(element, compiler)) return; | |
470 inferrer.updateParameterAssignments( | |
471 info, element, info.arguments, selector, remove: false, | |
472 addToQueue: false); | |
473 } | |
474 | |
475 visitClosureCallSiteTypeInformation(ClosureCallSiteTypeInformation info) { | |
476 super.visitClosureCallSiteTypeInformation(info); | |
477 if (info.closure == currentUser) { | |
478 analyzeCall(info); | |
479 } else { | |
480 bailout('Passed to a closure'); | |
481 } | |
482 } | |
483 | |
484 visitStaticCallSiteTypeInformation(StaticCallSiteTypeInformation info) { | |
485 super.visitStaticCallSiteTypeInformation(info); | |
486 Element called = info.calledElement; | |
487 if (called.isForeign(compiler) && called.name == 'JS') { | |
488 bailout('Used in JS ${info.call}'); | |
489 } | |
490 } | |
491 | |
492 bool checkIfCurrentUser(element) { | |
493 return inferrer.types.getInferredTypeOf(element) == currentUser; | |
494 } | |
495 | |
496 visitDynamicCallSiteTypeInformation(DynamicCallSiteTypeInformation info) { | |
497 super.visitDynamicCallSiteTypeInformation(info); | |
498 if (info.selector.isCall()) { | |
499 if (info.arguments.contains(currentUser) | |
500 && !info.targets.every((element) => element.isFunction())) { | |
501 bailout('Passed to a closure'); | |
502 } else if (info.targets.any((element) => checkIfCurrentUser(element))) { | |
503 analyzeCall(info); | |
504 } | |
505 } | |
506 } | |
507 } | |
OLD | NEW |