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 // A set of selectors we know do not escape the elements inside the | |
8 // list. | |
9 Set<String> doesNotEscapeListSet = new Set<String>.from( | |
10 const <String>[ | |
11 // From Object. | |
12 '==', | |
13 'hashCode', | |
14 'toString', | |
15 'noSuchMethod', | |
16 'runtimeType', | |
17 | |
18 // From Iterable. | |
19 'isEmpty', | |
20 'isNotEmpty', | |
21 'length', | |
22 'any', | |
23 'contains', | |
24 'every', | |
25 'join', | |
26 | |
27 // From List. | |
28 'add', | |
29 'addAll', | |
30 'clear', | |
31 'fillRange', | |
32 'indexOf', | |
33 'insert', | |
34 'insertAll', | |
35 'lastIndexOf', | |
36 'remove', | |
37 'removeRange', | |
38 'replaceRange', | |
39 'setAll', | |
40 'setRange', | |
41 'shuffle', | |
42 '[]=', | |
43 | |
44 // From JSArray. | |
45 'checkMutable', | |
46 'checkGrowable', | |
47 ]); | |
48 | |
49 Set<String> doesNotEscapeMapSet = new Set<String>.from( | |
50 const <String>[ | |
51 // From Object. | |
52 '==', | |
53 'hashCode', | |
54 'toString', | |
55 'noSuchMethod', | |
56 'runtimeType', | |
57 // from Map. | |
58 'isEmpty', | |
59 'isNotEmpty', | |
60 'length', | |
61 'clear', | |
62 'containsKey', | |
63 'containsValue', | |
64 '[]=', | |
65 // [keys] only allows key values to escape, which we do not track. | |
66 'keys' | |
67 ]); | |
68 | |
69 abstract class TracerVisitor<T extends TypeInformation> | |
70 implements TypeInformationVisitor { | |
71 final T tracedType; | |
72 final TypeGraphInferrerEngine inferrer; | |
73 final Compiler compiler; | |
74 | |
75 static const int MAX_ANALYSIS_COUNT = 16; | |
76 final Setlet<Element> analyzedElements = new Setlet<Element>(); | |
77 | |
78 TracerVisitor(this.tracedType, inferrer) | |
79 : this.inferrer = inferrer, this.compiler = inferrer.compiler; | |
80 | |
81 // Work list that gets populated with [TypeInformation] that could | |
82 // contain the container. | |
83 final List<TypeInformation> workList = <TypeInformation>[]; | |
84 | |
85 // Work list of lists to analyze after analyzing the users of a | |
86 // [TypeInformation]. We know the [tracedType] has been stored in these | |
87 // lists and we must check how it escapes from these lists. | |
88 final List<ListTypeInformation> listsToAnalyze = | |
89 <ListTypeInformation>[]; | |
90 // Work list of maps to analyze after analyzing the users of a | |
91 // [TypeInformation]. We know the [tracedType] has been stored in these | |
92 // maps and we must check how it escapes from these maps. | |
93 final List<MapTypeInformation> mapsToAnalyze = <MapTypeInformation>[]; | |
94 | |
95 final Setlet<TypeInformation> flowsInto = new Setlet<TypeInformation>(); | |
96 | |
97 // The current [TypeInformation] in the analysis. | |
98 TypeInformation currentUser; | |
99 bool continueAnalyzing = true; | |
100 | |
101 void addNewEscapeInformation(TypeInformation info) { | |
102 if (flowsInto.contains(info)) return; | |
103 flowsInto.add(info); | |
104 workList.add(info); | |
105 } | |
106 | |
107 void analyze() { | |
108 // Collect the [TypeInformation] where the list can flow in, | |
109 // as well as the operations done on all these [TypeInformation]s. | |
110 addNewEscapeInformation(tracedType); | |
111 while (!workList.isEmpty) { | |
112 currentUser = workList.removeLast(); | |
113 int expectedWork = analyzedElements.length + currentUser.users.length; | |
114 if (expectedWork > MAX_ANALYSIS_COUNT) { | |
115 bailout('Too many users'); | |
116 break; | |
117 } | |
118 for (TypeInformation info in currentUser.users) { | |
119 analyzedElements.add(info.owner); | |
120 info.accept(this); | |
121 } | |
122 while (!listsToAnalyze.isEmpty) { | |
123 analyzeStoredIntoList(listsToAnalyze.removeLast()); | |
124 } | |
125 while (!mapsToAnalyze.isEmpty) { | |
126 analyzeStoredIntoMap(mapsToAnalyze.removeLast()); | |
127 } | |
128 if (!continueAnalyzing) break; | |
129 } | |
130 } | |
131 | |
132 void bailout(String reason) { | |
133 if (_VERBOSE) { | |
134 print('Bailing out on $tracedType because: $reason'); | |
135 } | |
136 continueAnalyzing = false; | |
137 } | |
138 | |
139 void visitNarrowTypeInformation(NarrowTypeInformation info) { | |
140 addNewEscapeInformation(info); | |
141 } | |
142 | |
143 void visitPhiElementTypeInformation(PhiElementTypeInformation info) { | |
144 addNewEscapeInformation(info); | |
145 } | |
146 | |
147 void visitElementInContainerTypeInformation( | |
148 ElementInContainerTypeInformation info) { | |
149 addNewEscapeInformation(info); | |
150 } | |
151 | |
152 void visitKeyInMapTypeInformation(KeyInMapTypeInformation info) { | |
153 // We do not track the use of keys from a map, so we have to bail. | |
154 bailout('Used as key in Map'); | |
155 } | |
156 | |
157 void visitValueInMapTypeInformation(ValueInMapTypeInformation info) { | |
158 addNewEscapeInformation(info); | |
159 } | |
160 | |
161 void visitListTypeInformation(ListTypeInformation info) { | |
162 listsToAnalyze.add(info); | |
163 } | |
164 | |
165 void visitMapTypeInformation(MapTypeInformation info) { | |
166 mapsToAnalyze.add(info); | |
167 } | |
168 void visitConcreteTypeInformation(ConcreteTypeInformation info) {} | |
169 | |
170 void visitStringLiteralTypeInformation(StringLiteralTypeInformation info) {} | |
171 | |
172 void visitClosureTypeInformation(ClosureTypeInformation info) {} | |
173 | |
174 void visitClosureCallSiteTypeInformation( | |
175 ClosureCallSiteTypeInformation info) {} | |
176 | |
177 visitStaticCallSiteTypeInformation(StaticCallSiteTypeInformation info) { | |
178 Element called = info.calledElement; | |
179 if (inferrer.types.getInferredTypeOf(called) == currentUser) { | |
180 addNewEscapeInformation(info); | |
181 } | |
182 } | |
183 | |
184 void analyzeStoredIntoList(ListTypeInformation list) { | |
185 inferrer.analyzeListAndEnqueue(list); | |
186 if (list.bailedOut) { | |
187 bailout('Stored in a list that bailed out'); | |
188 } else { | |
189 list.flowsInto.forEach((flow) { | |
190 flow.users.forEach((user) { | |
191 if (user is !DynamicCallSiteTypeInformation) return; | |
192 if (user.receiver != flow) return; | |
193 if (inferrer._returnsListElementTypeSet.contains(user.selector)) { | |
194 addNewEscapeInformation(user); | |
195 } else if (!doesNotEscapeListSet.contains(user.selector.name)) { | |
196 bailout('Escape from a list via [${user.selector.name}]'); | |
197 } | |
198 }); | |
199 }); | |
200 } | |
201 } | |
202 | |
203 void analyzeStoredIntoMap(MapTypeInformation map) { | |
204 inferrer.analyzeMapAndEnqueue(map); | |
205 if (map.bailedOut) { | |
206 bailout('Stored in a map that bailed out'); | |
207 } else { | |
208 map.flowsInto.forEach((flow) { | |
209 flow.users.forEach((user) { | |
210 if (user is !DynamicCallSiteTypeInformation) return; | |
211 if (user.receiver != flow) return; | |
212 if (user.selector.isIndex) { | |
213 addNewEscapeInformation(user); | |
214 } else if (!doesNotEscapeMapSet.contains(user.selector.name)) { | |
215 bailout('Escape from a map via [${user.selector.name}]'); | |
216 } | |
217 }); | |
218 }); | |
219 } | |
220 } | |
221 | |
222 /** | |
223 * Checks whether this is a call to a list adding method. The definition | |
224 * of what list adding means has to stay in sync with | |
225 * [isParameterOfListAddingMethod]. | |
226 */ | |
227 bool isAddedToContainer(DynamicCallSiteTypeInformation info) { | |
228 if (info.arguments == null) return false; | |
229 var receiverType = info.receiver.type; | |
230 if (!receiverType.isContainer) return false; | |
231 String selectorName = info.selector.name; | |
232 List<TypeInformation> arguments = info.arguments.positional; | |
233 return (selectorName == '[]=' && currentUser == arguments[1]) | |
234 || (selectorName == 'insert' && currentUser == arguments[1]) | |
235 || (selectorName == 'add' && currentUser == arguments[0]); | |
236 } | |
237 | |
238 bool isIndexSetOnMap(DynamicCallSiteTypeInformation info) { | |
239 if (info.arguments == null) return false; | |
240 var receiverType = info.receiver.type; | |
241 if (!receiverType.isMap) return false; | |
242 return info.selector.name == '[]='; | |
243 } | |
244 | |
245 /** | |
246 * Checks whether this is a call to a map adding method for values. The | |
247 * definition of map adding method has to stay in sync with | |
248 * [isParameterOfMapAddingMethod]. | |
249 */ | |
250 bool isValueAddedToMap(DynamicCallSiteTypeInformation info) { | |
251 return isIndexSetOnMap(info) && | |
252 currentUser == info.arguments.positional[1]; | |
253 } | |
254 | |
255 /** | |
256 * Checks whether this is a call to a map adding method for keys. The | |
257 * definition of map adding method has to stay in sync with | |
258 * [isParameterOfMapAddingMethod]. | |
259 */ | |
260 bool isKeyAddedToMap(DynamicCallSiteTypeInformation info) { | |
261 return isIndexSetOnMap(info) && | |
262 currentUser == info.arguments.positional[0]; | |
263 } | |
264 | |
265 void visitDynamicCallSiteTypeInformation( | |
266 DynamicCallSiteTypeInformation info) { | |
267 if (isAddedToContainer(info)) { | |
268 ContainerTypeMask mask = info.receiver.type; | |
269 | |
270 if (mask.allocationNode != null) { | |
271 ListTypeInformation list = | |
272 inferrer.types.allocatedLists[mask.allocationNode]; | |
273 listsToAnalyze.add(list); | |
274 } else { | |
275 // The [ContainerTypeMask] is a union of two containers, and | |
276 // we lose track of where these containers have been allocated | |
277 // at this point. | |
278 bailout('Stored in too many containers'); | |
279 } | |
280 } else if (isValueAddedToMap(info)) { | |
281 MapTypeMask mask = info.receiver.type; | |
282 if (mask.allocationNode != null) { | |
283 MapTypeInformation map = | |
284 inferrer.types.allocatedMaps[mask.allocationNode]; | |
285 mapsToAnalyze.add(map); | |
286 } else { | |
287 // The [MapTypeMask] is a union. See comment for | |
288 // [ContainerTypeMask] above. | |
289 bailout('Stored in too many maps'); | |
290 } | |
291 } else if (isKeyAddedToMap(info)) { | |
292 // We do not track the use of keys from a map, so we have to bail. | |
293 bailout('Used as key in Map'); | |
294 } | |
295 | |
296 if (info.targetsIncludeNoSuchMethod && | |
297 info.arguments != null && | |
298 info.arguments.contains(currentUser)) { | |
299 bailout('Passed to noSuchMethod'); | |
300 } | |
301 | |
302 Iterable<Element> inferredTargetTypes = info.targets.map((element) { | |
303 return inferrer.types.getInferredTypeOf(element); | |
304 }); | |
305 if (inferredTargetTypes.any((user) => user == currentUser)) { | |
306 addNewEscapeInformation(info); | |
307 } | |
308 } | |
309 | |
310 /** | |
311 * Check whether element is the parameter of a list adding method. | |
312 * The definition of what a list adding method is has to stay in sync with | |
313 * [isAddedToContainer]. | |
314 */ | |
315 bool isParameterOfListAddingMethod(Element element) { | |
316 if (!element.isParameter) return false; | |
317 if (element.enclosingClass != compiler.backend.listImplementation) { | |
318 return false; | |
319 } | |
320 Element method = element.enclosingElement; | |
321 return (method.name == '[]=') | |
322 || (method.name == 'add') | |
323 || (method.name == 'insert'); | |
324 } | |
325 | |
326 /** | |
327 * Check whether element is the parameter of a list adding method. | |
328 * The definition of what a list adding method is has to stay in sync with | |
329 * [isValueAddedToMap] and [isKeyAddedToMap]. | |
330 */ | |
331 bool isParameterOfMapAddingMethod(Element element) { | |
332 if (!element.isParameter) return false; | |
333 if (element.enclosingClass != compiler.backend.mapImplementation) { | |
334 return false; | |
335 } | |
336 Element method = element.enclosingElement; | |
337 return (method.name == '[]='); | |
338 } | |
339 | |
340 bool isClosure(Element element) { | |
341 if (!element.isFunction) return false; | |
342 /// Creating an instance of a class that implements [Function] also | |
343 /// closurizes the corresponding [call] member. We do not currently | |
344 /// track these, thus the check for [isClosurized] on such a method will | |
345 /// return false. Instead we catch that case here for now. | |
346 // TODO(herhut): Handle creation of closures from instances of Function. | |
347 if (element.isInstanceMember && | |
348 element.name == Compiler.CALL_OPERATOR_NAME) { | |
349 return true; | |
350 } | |
351 Element outermost = element.outermostEnclosingMemberOrTopLevel; | |
352 return outermost.declaration != element.declaration; | |
353 } | |
354 | |
355 void visitMemberTypeInformation(MemberTypeInformation info) { | |
356 Element element = info.element; | |
357 if (info.isClosurized) { | |
358 bailout('Returned from a closurized method'); | |
359 } | |
360 if (isClosure(info.element)) { | |
361 bailout('Returned from a closure'); | |
362 } | |
363 if (!inferrer.compiler.backend | |
364 .canBeUsedForGlobalOptimizations(info.element)) { | |
365 bailout('Escape to code that has special backend treatment'); | |
366 } | |
367 addNewEscapeInformation(info); | |
368 } | |
369 | |
370 void visitParameterTypeInformation(ParameterTypeInformation info) { | |
371 ParameterElement element = info.element; | |
372 if (inferrer.isNativeElement(element.functionDeclaration)) { | |
373 bailout('Passed to a native method'); | |
374 } | |
375 if (!inferrer.compiler.backend | |
376 .canBeUsedForGlobalOptimizations(info.element)) { | |
377 bailout('Escape to code that has special backend treatment'); | |
378 } | |
379 if (isParameterOfListAddingMethod(info.element) || | |
380 isParameterOfMapAddingMethod(info.element)) { | |
381 // These elements are being handled in | |
382 // [visitDynamicCallSiteTypeInformation]. | |
383 return; | |
384 } | |
385 addNewEscapeInformation(info); | |
386 } | |
387 } | |
OLD | NEW |