| 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 |