| OLD | NEW |
| 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 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 | 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. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 library container_tracer; | 5 part of type_graph_inferrer; |
| 6 | |
| 7 import '../dart2jslib.dart' hide Selector, TypedSelector; | |
| 8 import '../elements/elements.dart'; | |
| 9 import '../tree/tree.dart'; | |
| 10 import '../universe/universe.dart'; | |
| 11 import '../util/util.dart' show Link; | |
| 12 import 'simple_types_inferrer.dart' | |
| 13 show InferrerEngine, InferrerVisitor, LocalsHandler, TypeMaskSystem; | |
| 14 import '../types/types.dart'; | |
| 15 import 'inferrer_visitor.dart'; | |
| 16 | 6 |
| 17 /** | 7 /** |
| 18 * A set of selector names that [List] implements, that we know do not | 8 * A set of selector names that [List] implements, that we know do not |
| 19 * change the element type of the list, or let the list escape to code | 9 * change the element type of the list, or let the list escape to code |
| 20 * that might change the element type. | 10 * that might change the element type. |
| 21 */ | 11 */ |
| 22 Set<String> okSelectorsSet = new Set<String>.from( | 12 Set<String> okSelectorsSet = new Set<String>.from( |
| 23 const <String>[ | 13 const <String>[ |
| 24 // From Object. | 14 // From Object. |
| 25 '==', | 15 '==', |
| (...skipping 103 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 129 'getRange', | 119 'getRange', |
| 130 'asMap', | 120 'asMap', |
| 131 | 121 |
| 132 // From JSArray. | 122 // From JSArray. |
| 133 'checkMutable', | 123 'checkMutable', |
| 134 'checkGrowable', | 124 'checkGrowable', |
| 135 ]); | 125 ]); |
| 136 | 126 |
| 137 bool _VERBOSE = false; | 127 bool _VERBOSE = false; |
| 138 | 128 |
| 139 class InferrerEngineForContainerTracer | 129 class ContainerTracerVisitor implements TypeInformationVisitor { |
| 140 implements MinimalInferrerEngine<TypeMask> { | 130 final ContainerTypeInformation container; |
| 131 final TypeGraphInferrerEngine inferrer; |
| 141 final Compiler compiler; | 132 final Compiler compiler; |
| 142 | 133 |
| 143 InferrerEngineForContainerTracer(this.compiler); | 134 // The set of [TypeInformation] where the traced container could |
| 135 // flow in, and operations done on them. |
| 136 final Set<TypeInformation> allUsers = new Set<TypeInformation>(); |
| 144 | 137 |
| 145 TypeMask typeOfElement(Element element) { | 138 // The list of found assignments to the container. |
| 146 return compiler.typesTask.getGuaranteedTypeOfElement(element); | 139 final List<TypeInformation> assignments = <TypeInformation>[]; |
| 147 } | |
| 148 | 140 |
| 149 TypeMask returnTypeOfElement(Element element) { | 141 bool enableLengthTracking = true; |
| 150 return compiler.typesTask.getGuaranteedReturnTypeOfElement(element); | 142 bool continueAnalyzing = true; |
| 151 } | |
| 152 | |
| 153 TypeMask returnTypeOfSelector(Selector selector) { | |
| 154 return compiler.typesTask.getGuaranteedTypeOfSelector(selector); | |
| 155 } | |
| 156 | |
| 157 TypeMask typeOfNode(Node node) { | |
| 158 return compiler.typesTask.getGuaranteedTypeOfNode(null, node); | |
| 159 } | |
| 160 | |
| 161 Iterable<Element> getCallersOf(Element element) { | |
| 162 return compiler.typesTask.typesInferrer.getCallersOf(element); | |
| 163 } | |
| 164 | |
| 165 void recordTypeOfNonFinalField(Node node, | |
| 166 Element field, | |
| 167 TypeMask type) {} | |
| 168 } | |
| 169 | |
| 170 /** | |
| 171 * Global analysis phase that traces container instantiations in order to | |
| 172 * find their element type. | |
| 173 */ | |
| 174 class ContainerTracer extends CompilerTask { | |
| 175 ContainerTracer(Compiler compiler) : super(compiler); | |
| 176 | |
| 177 String get name => 'List tracer'; | |
| 178 | |
| 179 bool analyze() { | |
| 180 measure(() { | |
| 181 if (compiler.disableTypeInference) return; | |
| 182 TypesInferrer inferrer = compiler.typesTask.typesInferrer; | |
| 183 InferrerEngineForContainerTracer engine = | |
| 184 new InferrerEngineForContainerTracer(compiler); | |
| 185 | |
| 186 // Walk over all created [ContainerTypeMask]. | |
| 187 inferrer.containerTypes.forEach((ContainerTypeMask mask) { | |
| 188 // The element type has already been set for const containers. | |
| 189 if (mask.elementType != null) return; | |
| 190 new TracerForConcreteContainer(mask, this, compiler, engine).run(); | |
| 191 }); | |
| 192 }); | |
| 193 } | |
| 194 } | |
| 195 | |
| 196 /** | |
| 197 * A tracer for a specific container. | |
| 198 */ | |
| 199 class TracerForConcreteContainer { | |
| 200 final Compiler compiler; | |
| 201 final ContainerTracer tracer; | |
| 202 final InferrerEngineForContainerTracer inferrer; | |
| 203 final ContainerTypeMask mask; | |
| 204 | |
| 205 final Node analyzedNode; | |
| 206 final Element startElement; | |
| 207 | |
| 208 final List<Element> workList = <Element>[]; | |
| 209 | |
| 210 /** | |
| 211 * A set of elements where this list might escape. | |
| 212 */ | |
| 213 final Set<Element> escapingElements = new Set<Element>(); | |
| 214 | |
| 215 /** | |
| 216 * A set of selectors that both use and update the list, for example | |
| 217 * [: list[0]++; :] or [: list[0] |= 42; :]. | |
| 218 */ | |
| 219 final Set<Selector> constraints = new Set<Selector>(); | |
| 220 | |
| 221 /** | |
| 222 * A cache of setters that were already seen. Caching these | |
| 223 * selectors avoid the filtering done in [addSettersToAnalysis]. | |
| 224 */ | |
| 225 final Set<Selector> seenSetterSelectors = new Set<Selector>(); | |
| 226 | 143 |
| 227 static const int MAX_ANALYSIS_COUNT = 11; | 144 static const int MAX_ANALYSIS_COUNT = 11; |
| 145 final Set<Element> analyzedElements = new Set<Element>(); |
| 228 | 146 |
| 229 TypeMask potentialType; | 147 ContainerTracerVisitor(this.container, inferrer) |
| 230 int potentialLength; | 148 : this.inferrer = inferrer, this.compiler = inferrer.compiler; |
| 231 bool isLengthTrackingDisabled = false; | |
| 232 bool continueAnalyzing = true; | |
| 233 | |
| 234 TracerForConcreteContainer(ContainerTypeMask mask, | |
| 235 this.tracer, | |
| 236 this.compiler, | |
| 237 this.inferrer) | |
| 238 : analyzedNode = mask.allocationNode, | |
| 239 startElement = mask.allocationElement, | |
| 240 this.mask = mask; | |
| 241 | 149 |
| 242 void run() { | 150 void run() { |
| 243 int analysisCount = 0; | 151 // Add the assignments found at allocation site. |
| 244 workList.add(startElement); | 152 assignments.addAll(container.elementType.assignments); |
| 153 |
| 154 // Collect the [TypeInformation] where the container can flow in, |
| 155 // as well as the operations done on all these [TypeInformation]s. |
| 156 List<TypeInformation> workList = <TypeInformation>[]; |
| 157 allUsers.add(container); |
| 158 workList.add(container); |
| 245 while (!workList.isEmpty) { | 159 while (!workList.isEmpty) { |
| 246 if (workList.length + analysisCount > MAX_ANALYSIS_COUNT) { | 160 TypeInformation user = workList.removeLast(); |
| 161 user.users.forEach((TypeInformation info) { |
| 162 if (allUsers.contains(info)) return; |
| 163 allUsers.add(info); |
| 164 analyzedElements.add(info.owner); |
| 165 if (info.reachedBy(user, inferrer)) { |
| 166 workList.add(info); |
| 167 } |
| 168 }); |
| 169 if (analyzedElements.length > MAX_ANALYSIS_COUNT) { |
| 247 bailout('Too many users'); | 170 bailout('Too many users'); |
| 248 break; | 171 break; |
| 249 } | 172 } |
| 250 Element currentElement = workList.removeLast().implementation; | |
| 251 new ContainerTracerVisitor(currentElement, this).run(); | |
| 252 if (!continueAnalyzing) break; | |
| 253 analysisCount++; | |
| 254 } | 173 } |
| 255 | 174 |
| 256 if (!continueAnalyzing) { | 175 if (continueAnalyzing) { |
| 257 if (mask.forwardTo == compiler.typesTask.fixedListType) { | 176 for (TypeInformation info in allUsers) { |
| 258 mask.length = potentialLength; | 177 info.accept(this); |
| 178 if (!continueAnalyzing) break; |
| 259 } | 179 } |
| 260 mask.elementType = compiler.typesTask.dynamicType; | |
| 261 return; | |
| 262 } | 180 } |
| 263 | 181 |
| 264 // [potentialType] can be null if we did not find any instruction | 182 ContainerTypeMask mask = container.type; |
| 265 // that adds elements to the list. | 183 if (!enableLengthTracking |
| 266 if (potentialType == null) { | 184 && (mask.forwardTo != compiler.typesTask.fixedListType)) { |
| 267 if (_VERBOSE) { | 185 mask.length = null; |
| 268 print('Found empty type for $analyzedNode $startElement'); | |
| 269 } | |
| 270 mask.elementType = new TypeMask.nonNullEmpty(); | |
| 271 return; | |
| 272 } | 186 } |
| 273 | 187 |
| 274 // Walk over the found constraints and update the type according | 188 TypeMask result = continueAnalyzing |
| 275 // to the selectors of these constraints. | 189 ? inferrer.types.computeTypeMask(assignments) |
| 276 for (Selector constraint in constraints) { | 190 : inferrer.types.dynamicType.type; |
| 277 assert(constraint.isOperator()); | 191 |
| 278 constraint = new TypedSelector(potentialType, constraint); | 192 mask.elementType = result; |
| 279 potentialType = potentialType.union( | |
| 280 inferrer.returnTypeOfSelector(constraint), compiler); | |
| 281 } | |
| 282 if (_VERBOSE) { | 193 if (_VERBOSE) { |
| 283 print('$potentialType and $potentialLength ' | 194 print('$result and ${mask.length} ' |
| 284 'for $analyzedNode $startElement'); | 195 'for ${mask.allocationNode} ${mask.allocationElement}'); |
| 285 } | |
| 286 mask.elementType = potentialType; | |
| 287 mask.length = potentialLength; | |
| 288 } | |
| 289 | |
| 290 void disableLengthTracking() { | |
| 291 if (mask.forwardTo == compiler.typesTask.fixedListType) { | |
| 292 // Bogus update to a fixed list. | |
| 293 return; | |
| 294 } | |
| 295 isLengthTrackingDisabled = true; | |
| 296 potentialLength = null; | |
| 297 } | |
| 298 | |
| 299 void setPotentialLength(int value) { | |
| 300 if (isLengthTrackingDisabled) return; | |
| 301 potentialLength = value; | |
| 302 } | |
| 303 | |
| 304 void unionPotentialTypeWith(TypeMask newType) { | |
| 305 assert(newType != null); | |
| 306 potentialType = potentialType == null | |
| 307 ? newType | |
| 308 : newType.union(potentialType, compiler); | |
| 309 if (potentialType == compiler.typesTask.dynamicType) { | |
| 310 bailout('Moved to dynamic'); | |
| 311 } | 196 } |
| 312 } | 197 } |
| 313 | 198 |
| 314 void addEscapingElement(element) { | 199 void bailout(String reason) { |
| 315 element = element.implementation; | 200 if (_VERBOSE) { |
| 316 if (escapingElements.contains(element)) return; | 201 ContainerTypeMask mask = container.type; |
| 317 escapingElements.add(element); | 202 print('Bailing out on ${mask.allocationNode} ${mask.allocationElement} ' |
| 318 if (element.isField() || element.isGetter() || element.isFunction()) { | 203 'because: $reason'); |
| 319 for (Element e in inferrer.getCallersOf(element)) { | 204 } |
| 320 addElementToAnalysis(e); | 205 continueAnalyzing = false; |
| 321 } | 206 enableLengthTracking = false; |
| 322 } else if (element.isParameter()) { | 207 } |
| 323 addElementToAnalysis(element.enclosingElement); | 208 |
| 324 } else if (element.isFieldParameter()) { | 209 visitNarrowTypeInformation(NarrowTypeInformation info) {} |
| 325 addEscapingElement(element.fieldElement); | 210 visitPhiElementTypeInformation(PhiElementTypeInformation info) {} |
| 211 visitElementInContainerTypeInformation( |
| 212 ElementInContainerTypeInformation info) {} |
| 213 visitContainerTypeInformation(ContainerTypeInformation info) {} |
| 214 visitConcreteTypeInformation(ConcreteTypeInformation info) {} |
| 215 |
| 216 visitClosureCallSiteTypeInformation(ClosureCallSiteTypeInformation info) { |
| 217 bailout('Passed to a closure'); |
| 218 } |
| 219 |
| 220 visitStaticCallSiteTypeInformation(StaticCallSiteTypeInformation info) { |
| 221 analyzedElements.add(info.caller); |
| 222 Element called = info.calledElement; |
| 223 if (called.isForeign(compiler) && called.name == const SourceString('JS')) { |
| 224 bailout('Used in JS ${info.call}'); |
| 326 } | 225 } |
| 327 } | 226 } |
| 328 | 227 |
| 329 void addSettersToAnalysis(Selector selector) { | 228 visitDynamicCallSiteTypeInformation(DynamicCallSiteTypeInformation info) { |
| 330 assert(selector.isSetter()); | 229 Selector selector = info.selector; |
| 331 if (seenSetterSelectors.contains(selector)) return; | 230 String selectorName = selector.name.slowToString(); |
| 332 seenSetterSelectors.add(selector); | 231 if (allUsers.contains(info.receiver)) { |
| 333 for (var e in compiler.world.allFunctions.filter(selector)) { | 232 if (!okSelectorsSet.contains(selectorName)) { |
| 334 e = e.implementation; | 233 if (selector.isCall()) { |
| 335 if (e.isField()) { | 234 int positionalLength = info.arguments.positional.length; |
| 336 addEscapingElement(e); | 235 if (selectorName == 'add') { |
| 337 } else { | 236 if (positionalLength == 1) { |
| 338 FunctionSignature signature = e.computeSignature(compiler); | 237 assignments.add(info.arguments.positional[0]); |
| 339 signature.forEachRequiredParameter((Element e) { | 238 } |
| 340 addEscapingElement(e); | 239 } else if (selectorName == 'insert') { |
| 341 }); | 240 if (positionalLength == 2) { |
| 241 assignments.add(info.arguments.positional[1]); |
| 242 } |
| 243 } else { |
| 244 bailout('Used in a not-ok selector'); |
| 245 return; |
| 246 } |
| 247 } else if (selector.isIndexSet()) { |
| 248 assignments.add(info.arguments.positional[1]); |
| 249 } else if (!selector.isIndex()) { |
| 250 bailout('Used in a not-ok selector'); |
| 251 return; |
| 252 } |
| 342 } | 253 } |
| 254 if (!doNotChangeLengthSelectorsSet.contains(selectorName)) { |
| 255 enableLengthTracking = false; |
| 256 } |
| 257 if (selectorName == 'length' && selector.isSetter()) { |
| 258 enableLengthTracking = false; |
| 259 assignments.add(inferrer.types.nullType); |
| 260 } |
| 261 } else if (selector.isCall() |
| 262 && !info.targets.every((element) => element.isFunction())) { |
| 263 bailout('Passed to a closure'); |
| 264 return; |
| 343 } | 265 } |
| 344 } | 266 } |
| 345 | 267 |
| 346 void addElementToAnalysis(Element element) { | 268 bool isClosure(Element element) { |
| 347 workList.add(element); | 269 if (!element.isFunction()) return false; |
| 270 Element outermost = element.getOutermostEnclosingMemberOrTopLevel(); |
| 271 return outermost.declaration != element.declaration; |
| 348 } | 272 } |
| 349 | 273 |
| 350 TypeMask bailout(String reason) { | 274 visitElementTypeInformation(ElementTypeInformation info) { |
| 351 if (_VERBOSE) { | 275 if (isClosure(info.element)) { |
| 352 print('Bailout on $analyzedNode $startElement because of $reason'); | 276 bailout('Returned from a closure'); |
| 353 } | |
| 354 continueAnalyzing = false; | |
| 355 return compiler.typesTask.dynamicType; | |
| 356 } | |
| 357 | |
| 358 bool couldBeTheList(resolved) { | |
| 359 if (resolved is Selector) { | |
| 360 return escapingElements.any((e) { | |
| 361 return e.isInstanceMember() && resolved.applies(e, compiler); | |
| 362 }); | |
| 363 } else if (resolved is Node) { | |
| 364 return analyzedNode == resolved; | |
| 365 } else { | |
| 366 assert(resolved is Element); | |
| 367 return escapingElements.contains(resolved); | |
| 368 } | 277 } |
| 369 } | 278 } |
| 370 | |
| 371 void recordConstraint(Selector selector) { | |
| 372 constraints.add(selector); | |
| 373 } | |
| 374 } | 279 } |
| 375 | |
| 376 class ContainerTracerVisitor | |
| 377 extends InferrerVisitor<TypeMask, InferrerEngineForContainerTracer> { | |
| 378 final Element analyzedElement; | |
| 379 final TracerForConcreteContainer tracer; | |
| 380 final bool visitingClosure; | |
| 381 | |
| 382 ContainerTracerVisitor(element, tracer, [LocalsHandler<TypeMask> locals]) | |
| 383 : super(element, tracer.inferrer, new TypeMaskSystem(tracer.compiler), | |
| 384 tracer.compiler, locals), | |
| 385 this.analyzedElement = element, | |
| 386 this.tracer = tracer, | |
| 387 visitingClosure = locals != null; | |
| 388 | |
| 389 bool escaping = false; | |
| 390 bool visitingInitializers = false; | |
| 391 | |
| 392 void run() { | |
| 393 compiler.withCurrentElement(analyzedElement, () { | |
| 394 visit(analyzedElement.parseNode(compiler)); | |
| 395 }); | |
| 396 } | |
| 397 | |
| 398 /** | |
| 399 * Executes [f] and returns whether it triggered the list to escape. | |
| 400 */ | |
| 401 bool visitAndCatchEscaping(Function f) { | |
| 402 bool oldEscaping = escaping; | |
| 403 escaping = false; | |
| 404 f(); | |
| 405 bool foundEscaping = escaping; | |
| 406 escaping = oldEscaping; | |
| 407 return foundEscaping; | |
| 408 } | |
| 409 | |
| 410 /** | |
| 411 * Visits the [arguments] of [callee], and records the parameters | |
| 412 * that could hold the container as escaping. | |
| 413 * | |
| 414 * Returns whether the container escaped. | |
| 415 */ | |
| 416 bool visitArguments(Link<Node> arguments, /* Element or Selector */ callee) { | |
| 417 List<int> indices = []; | |
| 418 int index = 0; | |
| 419 for (Node node in arguments) { | |
| 420 if (visitAndCatchEscaping(() { visit(node); })) { | |
| 421 indices.add(index); | |
| 422 } | |
| 423 index++; | |
| 424 } | |
| 425 if (!indices.isEmpty) { | |
| 426 Iterable<Element> callees; | |
| 427 if (callee is Element) { | |
| 428 // No need to go further, we know the call will throw. | |
| 429 if (callee.isErroneous()) return false; | |
| 430 callees = [callee]; | |
| 431 } else { | |
| 432 assert(callee is Selector); | |
| 433 callees = compiler.world.allFunctions.filter(callee); | |
| 434 } | |
| 435 for (var e in callees) { | |
| 436 e = e.implementation; | |
| 437 if (e.isField()) { | |
| 438 tracer.bailout('Passed to a closure'); | |
| 439 break; | |
| 440 } | |
| 441 FunctionSignature signature = e.computeSignature(compiler); | |
| 442 index = 0; | |
| 443 int parameterIndex = 0; | |
| 444 signature.forEachRequiredParameter((Element parameter) { | |
| 445 if (index < indices.length && indices[index] == parameterIndex) { | |
| 446 tracer.addEscapingElement(parameter); | |
| 447 index++; | |
| 448 } | |
| 449 parameterIndex++; | |
| 450 }); | |
| 451 if (index != indices.length) { | |
| 452 tracer.bailout('Used in a named parameter or closure'); | |
| 453 } | |
| 454 } | |
| 455 return true; | |
| 456 } else { | |
| 457 return false; | |
| 458 } | |
| 459 } | |
| 460 | |
| 461 TypeMask visitFunctionExpression(FunctionExpression node) { | |
| 462 FunctionElement function = elements[node]; | |
| 463 if (function != analyzedElement) { | |
| 464 // Visiting a closure. | |
| 465 LocalsHandler closureLocals = new LocalsHandler<TypeMask>.from( | |
| 466 locals, node, useOtherTryBlock: false); | |
| 467 new ContainerTracerVisitor(function, tracer, closureLocals).run(); | |
| 468 return types.functionType; | |
| 469 } else { | |
| 470 // Visiting [analyzedElement]. | |
| 471 FunctionSignature signature = function.computeSignature(compiler); | |
| 472 signature.forEachParameter((element) { | |
| 473 locals.update(element, inferrer.typeOfElement(element), node); | |
| 474 }); | |
| 475 visitingInitializers = true; | |
| 476 visit(node.initializers); | |
| 477 visitingInitializers = false; | |
| 478 visit(node.body); | |
| 479 return null; | |
| 480 } | |
| 481 } | |
| 482 | |
| 483 TypeMask visitLiteralList(LiteralList node) { | |
| 484 if (node.isConst()) { | |
| 485 return inferrer.typeOfNode(node); | |
| 486 } | |
| 487 if (tracer.couldBeTheList(node)) { | |
| 488 escaping = true; | |
| 489 int length = 0; | |
| 490 for (Node element in node.elements.nodes) { | |
| 491 tracer.unionPotentialTypeWith(visit(element)); | |
| 492 length++; | |
| 493 } | |
| 494 tracer.setPotentialLength(length); | |
| 495 } else { | |
| 496 node.visitChildren(this); | |
| 497 } | |
| 498 return types.growableListType; | |
| 499 } | |
| 500 | |
| 501 TypeMask visitSendSet(SendSet node) { | |
| 502 bool isReceiver = visitAndCatchEscaping(() { | |
| 503 visit(node.receiver); | |
| 504 }); | |
| 505 return handleSendSet(node, isReceiver); | |
| 506 } | |
| 507 | |
| 508 TypeMask handleSendSet(SendSet node, bool isReceiver) { | |
| 509 TypeMask rhsType; | |
| 510 TypeMask indexType; | |
| 511 | |
| 512 Selector getterSelector = | |
| 513 elements.getGetterSelectorInComplexSendSet(node); | |
| 514 Selector operatorSelector = | |
| 515 elements.getOperatorSelectorInComplexSendSet(node); | |
| 516 Selector setterSelector = elements.getSelector(node); | |
| 517 | |
| 518 String op = node.assignmentOperator.source.stringValue; | |
| 519 bool isIncrementOrDecrement = op == '++' || op == '--'; | |
| 520 bool isIndexEscaping = false; | |
| 521 bool isValueEscaping = false; | |
| 522 if (isIncrementOrDecrement) { | |
| 523 rhsType = types.intType; | |
| 524 if (node.isIndex) { | |
| 525 isIndexEscaping = visitAndCatchEscaping(() { | |
| 526 indexType = visit(node.arguments.head); | |
| 527 }); | |
| 528 } | |
| 529 } else if (node.isIndex) { | |
| 530 isIndexEscaping = visitAndCatchEscaping(() { | |
| 531 indexType = visit(node.arguments.head); | |
| 532 }); | |
| 533 isValueEscaping = visitAndCatchEscaping(() { | |
| 534 rhsType = visit(node.arguments.tail.head); | |
| 535 }); | |
| 536 } else { | |
| 537 isValueEscaping = visitAndCatchEscaping(() { | |
| 538 rhsType = visit(node.arguments.head); | |
| 539 }); | |
| 540 } | |
| 541 | |
| 542 Element element = elements[node]; | |
| 543 | |
| 544 if (node.isIndex) { | |
| 545 if (isReceiver) { | |
| 546 if (op == '=') { | |
| 547 tracer.unionPotentialTypeWith(rhsType); | |
| 548 } else { | |
| 549 tracer.recordConstraint(operatorSelector); | |
| 550 } | |
| 551 } else if (isIndexEscaping || isValueEscaping) { | |
| 552 // If the index or value is escaping, iterate over all | |
| 553 // potential targets, and mark their parameter as escaping. | |
| 554 for (var e in compiler.world.allFunctions.filter(setterSelector)) { | |
| 555 e = e.implementation; | |
| 556 FunctionSignature signature = e.computeSignature(compiler); | |
| 557 int index = 0; | |
| 558 signature.forEachRequiredParameter((Element parameter) { | |
| 559 if (index == 0 && isIndexEscaping) { | |
| 560 tracer.addEscapingElement(parameter); | |
| 561 } | |
| 562 if (index == 1 && isValueEscaping) { | |
| 563 tracer.addEscapingElement(parameter); | |
| 564 } | |
| 565 index++; | |
| 566 }); | |
| 567 } | |
| 568 } | |
| 569 } else if (isReceiver) { | |
| 570 if (setterSelector.name == const SourceString('length')) { | |
| 571 tracer.disableLengthTracking(); | |
| 572 tracer.unionPotentialTypeWith(compiler.typesTask.nullType); | |
| 573 } | |
| 574 } else if (isValueEscaping) { | |
| 575 if (element != null | |
| 576 && element.isField() | |
| 577 && setterSelector == null | |
| 578 && !visitingInitializers) { | |
| 579 // Initializer at declaration of a field. | |
| 580 assert(analyzedElement.isField()); | |
| 581 tracer.addEscapingElement(analyzedElement); | |
| 582 } else if (element != null | |
| 583 && (!element.isInstanceMember() || visitingInitializers)) { | |
| 584 // A local, a static element, or a field in an initializer. | |
| 585 tracer.addEscapingElement(element); | |
| 586 } else { | |
| 587 tracer.addSettersToAnalysis(setterSelector); | |
| 588 } | |
| 589 } | |
| 590 | |
| 591 TypeMask result; | |
| 592 if (node.isPostfix) { | |
| 593 // We don't check if [getterSelector] could be the container because | |
| 594 // a list++ will always throw. | |
| 595 result = inferrer.returnTypeOfSelector(getterSelector); | |
| 596 } else if (op != '=') { | |
| 597 // We don't check if [getterSelector] could be the container because | |
| 598 // a list += 42 will always throw. | |
| 599 result = inferrer.returnTypeOfSelector(operatorSelector); | |
| 600 } else { | |
| 601 if (isValueEscaping) { | |
| 602 escaping = true; | |
| 603 } | |
| 604 result = rhsType; | |
| 605 } | |
| 606 | |
| 607 if (Elements.isLocal(element)) { | |
| 608 locals.update(element, result, node); | |
| 609 } | |
| 610 | |
| 611 return result; | |
| 612 } | |
| 613 | |
| 614 TypeMask visitSuperSend(Send node) { | |
| 615 Element element = elements[node]; | |
| 616 if (!node.isPropertyAccess) { | |
| 617 visitArguments(node.arguments, element); | |
| 618 } | |
| 619 | |
| 620 if (tracer.couldBeTheList(element)) { | |
| 621 escaping = true; | |
| 622 } | |
| 623 | |
| 624 if (element.isField()) { | |
| 625 return inferrer.typeOfElement(element); | |
| 626 } else if (element.isFunction()) { | |
| 627 return inferrer.returnTypeOfElement(element); | |
| 628 } else { | |
| 629 return types.dynamicType; | |
| 630 } | |
| 631 } | |
| 632 | |
| 633 TypeMask visitStaticSend(Send node) { | |
| 634 Element element = elements[node]; | |
| 635 | |
| 636 if (Elements.isGrowableListConstructorCall(element, node, compiler)) { | |
| 637 visitArguments(node.arguments, element); | |
| 638 if (tracer.couldBeTheList(node)) { | |
| 639 escaping = true; | |
| 640 } | |
| 641 return inferrer.typeOfNode(node); | |
| 642 } else if (Elements.isFixedListConstructorCall(element, node, compiler)) { | |
| 643 visitArguments(node.arguments, element); | |
| 644 if (tracer.couldBeTheList(node)) { | |
| 645 tracer.unionPotentialTypeWith(types.nullType); | |
| 646 escaping = true; | |
| 647 LiteralInt length = node.arguments.head.asLiteralInt(); | |
| 648 if (length != null) { | |
| 649 tracer.setPotentialLength(length.value); | |
| 650 } | |
| 651 } | |
| 652 return inferrer.typeOfNode(node); | |
| 653 } else if (Elements.isFilledListConstructorCall(element, node, compiler)) { | |
| 654 if (tracer.couldBeTheList(node)) { | |
| 655 escaping = true; | |
| 656 visit(node.arguments.head); | |
| 657 TypeMask fillWithType = visit(node.arguments.tail.head); | |
| 658 tracer.unionPotentialTypeWith(fillWithType); | |
| 659 LiteralInt length = node.arguments.head.asLiteralInt(); | |
| 660 if (length != null) { | |
| 661 tracer.setPotentialLength(length.value); | |
| 662 } | |
| 663 } else { | |
| 664 visitArguments(node.arguments, element); | |
| 665 } | |
| 666 return inferrer.typeOfNode(node); | |
| 667 } | |
| 668 | |
| 669 bool isEscaping = visitArguments(node.arguments, element); | |
| 670 | |
| 671 if (element.isForeign(compiler)) { | |
| 672 if (isEscaping) return tracer.bailout('Used in a JS'); | |
| 673 } | |
| 674 | |
| 675 if (tracer.couldBeTheList(element)) { | |
| 676 escaping = true; | |
| 677 } | |
| 678 | |
| 679 if (element.isFunction() || element.isConstructor()) { | |
| 680 return inferrer.returnTypeOfElement(element); | |
| 681 } else { | |
| 682 // Closure call or unresolved. | |
| 683 return types.dynamicType; | |
| 684 } | |
| 685 } | |
| 686 | |
| 687 TypeMask visitGetterSend(Send node) { | |
| 688 Element element = elements[node]; | |
| 689 Selector selector = elements.getSelector(node); | |
| 690 if (Elements.isStaticOrTopLevelField(element)) { | |
| 691 if (tracer.couldBeTheList(element)) { | |
| 692 escaping = true; | |
| 693 } | |
| 694 return inferrer.typeOfElement(element); | |
| 695 } else if (Elements.isInstanceSend(node, elements)) { | |
| 696 return visitDynamicSend(node); | |
| 697 } else if (Elements.isStaticOrTopLevelFunction(element)) { | |
| 698 return types.functionType; | |
| 699 } else if (Elements.isErroneousElement(element)) { | |
| 700 return types.dynamicType; | |
| 701 } else if (Elements.isLocal(element)) { | |
| 702 if (tracer.couldBeTheList(element)) { | |
| 703 escaping = true; | |
| 704 } | |
| 705 return locals.use(element); | |
| 706 } else { | |
| 707 node.visitChildren(this); | |
| 708 return types.dynamicType; | |
| 709 } | |
| 710 } | |
| 711 | |
| 712 TypeMask visitClosureSend(Send node) { | |
| 713 assert(node.receiver == null); | |
| 714 visit(node.selector); | |
| 715 bool isEscaping = | |
| 716 visitArguments(node.arguments, elements.getSelector(node)); | |
| 717 | |
| 718 if (isEscaping) return tracer.bailout('Passed to a closure'); | |
| 719 return types.dynamicType; | |
| 720 } | |
| 721 | |
| 722 TypeMask visitDynamicSend(Send node) { | |
| 723 bool isReceiver = visitAndCatchEscaping(() { | |
| 724 visit(node.receiver); | |
| 725 }); | |
| 726 return handleDynamicSend(node, isReceiver); | |
| 727 } | |
| 728 | |
| 729 TypeMask handleDynamicSend(Send node, bool isReceiver) { | |
| 730 Selector selector = elements.getSelector(node); | |
| 731 String selectorName = selector.name.slowToString(); | |
| 732 if (isReceiver && !okSelectorsSet.contains(selectorName)) { | |
| 733 if (selector.isCall() | |
| 734 && (selectorName == 'add' || selectorName == 'insert')) { | |
| 735 TypeMask argumentType; | |
| 736 if (node.arguments.isEmpty | |
| 737 || (selectorName == 'insert' && node.arguments.tail.isEmpty)) { | |
| 738 return tracer.bailout('Invalid "add" or "insert" call on a list'); | |
| 739 } | |
| 740 bool isEscaping = visitAndCatchEscaping(() { | |
| 741 argumentType = visit(node.arguments.head); | |
| 742 if (selectorName == 'insert') { | |
| 743 argumentType = visit(node.arguments.tail.head); | |
| 744 } | |
| 745 }); | |
| 746 if (isEscaping) { | |
| 747 return tracer.bailout('List containing itself'); | |
| 748 } | |
| 749 tracer.unionPotentialTypeWith(argumentType); | |
| 750 } else { | |
| 751 return tracer.bailout('Send with the node as receiver $node'); | |
| 752 } | |
| 753 } else if (!node.isPropertyAccess) { | |
| 754 visitArguments(node.arguments, selector); | |
| 755 } | |
| 756 if (isReceiver && !doNotChangeLengthSelectorsSet.contains(selectorName)) { | |
| 757 tracer.disableLengthTracking(); | |
| 758 } | |
| 759 if (tracer.couldBeTheList(selector)) { | |
| 760 escaping = true; | |
| 761 } | |
| 762 return inferrer.returnTypeOfSelector(selector); | |
| 763 } | |
| 764 | |
| 765 TypeMask visitReturn(Return node) { | |
| 766 if (node.expression == null) { | |
| 767 return types.nullType; | |
| 768 } | |
| 769 | |
| 770 TypeMask type; | |
| 771 bool isEscaping = visitAndCatchEscaping(() { | |
| 772 type = visit(node.expression); | |
| 773 }); | |
| 774 | |
| 775 if (isEscaping) { | |
| 776 if (visitingClosure) { | |
| 777 tracer.bailout('Return from closure'); | |
| 778 } else { | |
| 779 tracer.addEscapingElement(analyzedElement); | |
| 780 } | |
| 781 } | |
| 782 return type; | |
| 783 } | |
| 784 | |
| 785 TypeMask visitForIn(ForIn node) { | |
| 786 visit(node.expression); | |
| 787 Selector iteratorSelector = elements.getIteratorSelector(node); | |
| 788 Selector currentSelector = elements.getCurrentSelector(node); | |
| 789 | |
| 790 TypeMask iteratorType = inferrer.returnTypeOfSelector(iteratorSelector); | |
| 791 TypeMask currentType = inferrer.returnTypeOfSelector(currentSelector); | |
| 792 | |
| 793 // We nullify the type in case there is no element in the | |
| 794 // iterable. | |
| 795 currentType = currentType.nullable(); | |
| 796 | |
| 797 Node identifier = node.declaredIdentifier; | |
| 798 Element element = elements[identifier]; | |
| 799 if (Elements.isLocal(element)) { | |
| 800 locals.update(element, currentType, node); | |
| 801 } | |
| 802 | |
| 803 return handleLoop(node, () { | |
| 804 visit(node.body); | |
| 805 }); | |
| 806 } | |
| 807 } | |
| OLD | NEW |