| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2015, 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 library cps_ir.optimization.inline; | |
| 6 | |
| 7 import 'package:js_ast/js_ast.dart' as js; | |
| 8 | |
| 9 import '../dart_types.dart' show DartType, GenericType; | |
| 10 import '../elements/elements.dart'; | |
| 11 import '../js_backend/codegen/task.dart' show CpsFunctionCompiler; | |
| 12 import '../js_backend/js_backend.dart' show JavaScriptBackend; | |
| 13 import '../types/types.dart' show TypeMask; | |
| 14 import '../universe/call_structure.dart' show CallStructure; | |
| 15 import '../universe/selector.dart' show Selector; | |
| 16 import '../world.dart' show World; | |
| 17 import 'cps_fragment.dart'; | |
| 18 import 'cps_ir_builder.dart' show ThisParameterLocal; | |
| 19 import 'cps_ir_nodes.dart'; | |
| 20 import 'optimizers.dart'; | |
| 21 import 'type_mask_system.dart' show TypeMaskSystem; | |
| 22 | |
| 23 /// Inlining stack entries. | |
| 24 /// | |
| 25 /// During inlining, a stack is used to detect cycles in the call graph. | |
| 26 class StackEntry { | |
| 27 // Dynamically resolved calls might be targeting an adapter function that | |
| 28 // fills in optional arguments not passed at the call site. Therefore these | |
| 29 // calls are represented by the eventual target and the call structure at | |
| 30 // the call site, which together identify the target. Statically resolved | |
| 31 // calls are represented by the target element and a null call structure. | |
| 32 final ExecutableElement target; | |
| 33 final CallStructure callStructure; | |
| 34 | |
| 35 StackEntry(this.target, this.callStructure); | |
| 36 | |
| 37 bool match(ExecutableElement otherTarget, CallStructure otherCallStructure) { | |
| 38 if (target != otherTarget) return false; | |
| 39 if (callStructure == null) return otherCallStructure == null; | |
| 40 return otherCallStructure != null && | |
| 41 callStructure.match(otherCallStructure); | |
| 42 } | |
| 43 } | |
| 44 | |
| 45 /// Inlining cache entries. | |
| 46 class CacheEntry { | |
| 47 // The cache maps a function element to a list of entries, where each entry | |
| 48 // is a tuple of (call structure, abstract receiver, abstract arguments) | |
| 49 // along with the inlining decision and optional IR function definition. | |
| 50 final CallStructure callStructure; | |
| 51 final TypeMask receiver; | |
| 52 final List<TypeMask> arguments; | |
| 53 | |
| 54 final bool decision; | |
| 55 final FunctionDefinition function; | |
| 56 | |
| 57 CacheEntry(this.callStructure, this.receiver, this.arguments, this.decision, | |
| 58 this.function); | |
| 59 | |
| 60 bool match(CallStructure otherCallStructure, TypeMask otherReceiver, | |
| 61 List<TypeMask> otherArguments) { | |
| 62 if (callStructure == null) { | |
| 63 if (otherCallStructure != null) return false; | |
| 64 } else if (otherCallStructure == null || | |
| 65 !callStructure.match(otherCallStructure)) { | |
| 66 return false; | |
| 67 } | |
| 68 | |
| 69 if (receiver != otherReceiver) return false; | |
| 70 assert(arguments.length == otherArguments.length); | |
| 71 for (int i = 0; i < arguments.length; ++i) { | |
| 72 if (arguments[i] != otherArguments[i]) return false; | |
| 73 } | |
| 74 return true; | |
| 75 } | |
| 76 } | |
| 77 | |
| 78 /// An inlining cache. | |
| 79 /// | |
| 80 /// During inlining a cache is used to remember inlining decisions for shared | |
| 81 /// parts of the call graph, to avoid exploring them more than once. | |
| 82 /// | |
| 83 /// The cache maps a tuple of (function element, call structure, | |
| 84 /// abstract receiver, abstract arguments) to a boolean inlining decision and | |
| 85 /// an IR function definition if the decision is positive. | |
| 86 class InliningCache { | |
| 87 static const int ABSENT = -1; | |
| 88 static const int NO_INLINE = 0; | |
| 89 | |
| 90 final Map<ExecutableElement, FunctionDefinition> unoptimized = | |
| 91 <ExecutableElement, FunctionDefinition>{}; | |
| 92 | |
| 93 final Map<ExecutableElement, List<CacheEntry>> map = | |
| 94 <ExecutableElement, List<CacheEntry>>{}; | |
| 95 | |
| 96 // When function definitions are put into or removed from the cache, they are | |
| 97 // copied because the compiler passes will mutate them. | |
| 98 final CopyingVisitor copier = new CopyingVisitor(); | |
| 99 | |
| 100 void _putInternal( | |
| 101 ExecutableElement element, | |
| 102 CallStructure callStructure, | |
| 103 TypeMask receiver, | |
| 104 List<TypeMask> arguments, | |
| 105 bool decision, | |
| 106 FunctionDefinition function) { | |
| 107 map.putIfAbsent(element, () => <CacheEntry>[]).add( | |
| 108 new CacheEntry(callStructure, receiver, arguments, decision, function)); | |
| 109 } | |
| 110 | |
| 111 /// Put a positive inlining decision in the cache. | |
| 112 /// | |
| 113 /// A positive inlining decision maps to an IR function definition. | |
| 114 void putPositive( | |
| 115 ExecutableElement element, | |
| 116 CallStructure callStructure, | |
| 117 TypeMask receiver, | |
| 118 List<TypeMask> arguments, | |
| 119 FunctionDefinition function) { | |
| 120 _putInternal(element, callStructure, receiver, arguments, true, | |
| 121 copier.copy(function)); | |
| 122 } | |
| 123 | |
| 124 /// Put a negative inlining decision in the cache. | |
| 125 void putNegative(ExecutableElement element, CallStructure callStructure, | |
| 126 TypeMask receiver, List<TypeMask> arguments) { | |
| 127 _putInternal(element, callStructure, receiver, arguments, false, null); | |
| 128 } | |
| 129 | |
| 130 /// Look up a tuple in the cache. | |
| 131 /// | |
| 132 /// A positive lookup result return the IR function definition. A negative | |
| 133 /// lookup result returns [NO_INLINE]. If there is no cached result, | |
| 134 /// [ABSENT] is returned. | |
| 135 get(ExecutableElement element, CallStructure callStructure, TypeMask receiver, | |
| 136 List<TypeMask> arguments) { | |
| 137 List<CacheEntry> entries = map[element]; | |
| 138 if (entries != null) { | |
| 139 for (CacheEntry entry in entries) { | |
| 140 if (entry.match(callStructure, receiver, arguments)) { | |
| 141 if (entry.decision) { | |
| 142 FunctionDefinition function = copier.copy(entry.function); | |
| 143 ParentVisitor.setParents(function); | |
| 144 return function; | |
| 145 } | |
| 146 return NO_INLINE; | |
| 147 } | |
| 148 } | |
| 149 } | |
| 150 return ABSENT; | |
| 151 } | |
| 152 | |
| 153 /// Cache the unoptimized CPS term for a function. | |
| 154 /// | |
| 155 /// The unoptimized term should not have any inlining-context-specific | |
| 156 /// optimizations applied to it. It will be used to compile the | |
| 157 /// non-specialized version of the function. | |
| 158 void putUnoptimized(ExecutableElement element, FunctionDefinition function) { | |
| 159 unoptimized.putIfAbsent(element, () => copier.copy(function)); | |
| 160 } | |
| 161 | |
| 162 /// Look up the unoptimized CPS term for a function. | |
| 163 /// | |
| 164 /// The unoptimized term will not have any inlining-context-specific | |
| 165 /// optimizations applied to it. It can be used to compile the | |
| 166 /// non-specialized version of the function. | |
| 167 FunctionDefinition getUnoptimized(ExecutableElement element) { | |
| 168 FunctionDefinition function = unoptimized[element]; | |
| 169 if (function != null) { | |
| 170 function = copier.copy(function); | |
| 171 ParentVisitor.setParents(function); | |
| 172 } | |
| 173 return function; | |
| 174 } | |
| 175 } | |
| 176 | |
| 177 class Inliner implements Pass { | |
| 178 get passName => 'Inline calls'; | |
| 179 | |
| 180 final CpsFunctionCompiler functionCompiler; | |
| 181 | |
| 182 final InliningCache cache = new InliningCache(); | |
| 183 | |
| 184 final List<StackEntry> stack = <StackEntry>[]; | |
| 185 | |
| 186 Inliner(this.functionCompiler); | |
| 187 | |
| 188 bool isCalledOnce(Element element) { | |
| 189 if (element is ConstructorBodyElement) { | |
| 190 ClassElement class_ = element.enclosingClass; | |
| 191 return !functionCompiler.compiler.world.hasAnyStrictSubclass(class_) && | |
| 192 class_.constructors.tail?.isEmpty ?? | |
| 193 false; | |
| 194 } | |
| 195 return functionCompiler.compiler.typesTask.typesInferrer | |
| 196 .isCalledOnce(element); | |
| 197 } | |
| 198 | |
| 199 void rewrite(FunctionDefinition node, [CallStructure callStructure]) { | |
| 200 ExecutableElement function = node.element; | |
| 201 | |
| 202 // Inlining in asynchronous or generator functions is disabled. Inlining | |
| 203 // triggers a bug in the async rewriter. | |
| 204 // TODO(kmillikin): Fix the bug and eliminate this restriction if it makes | |
| 205 // sense. | |
| 206 if (function is FunctionElement && | |
| 207 function.asyncMarker != AsyncMarker.SYNC) { | |
| 208 return; | |
| 209 } | |
| 210 | |
| 211 // Do not inline in functions containing try statements. V8 does not | |
| 212 // optimize code in such functions, so inlining will move optimizable code | |
| 213 // into a context where it cannot be optimized. | |
| 214 if (function.resolvedAst.kind == ResolvedAstKind.PARSED && | |
| 215 function.resolvedAst.elements.containsTryStatement) { | |
| 216 return; | |
| 217 } | |
| 218 | |
| 219 stack.add(new StackEntry(function, callStructure)); | |
| 220 new InliningVisitor(this).visit(node); | |
| 221 assert(stack.last.match(function, callStructure)); | |
| 222 stack.removeLast(); | |
| 223 new ShrinkingReducer().rewrite(node); | |
| 224 } | |
| 225 } | |
| 226 | |
| 227 /// Compute an abstract size of an IR function definition. | |
| 228 /// | |
| 229 /// The size represents the cost of inlining at a call site. | |
| 230 class SizeVisitor extends TrampolineRecursiveVisitor { | |
| 231 int size = 0; | |
| 232 | |
| 233 void countArgument(Primitive argument, Parameter parameter) { | |
| 234 // If a parameter is unused and the corresponding argument has only the | |
| 235 // one use at the invocation, then inlining the call might enable | |
| 236 // elimination of the argument. This 'pays for itself' by decreasing the | |
| 237 // cost of inlining at the call site. | |
| 238 if (argument != null && argument.hasExactlyOneUse && parameter.hasNoUses) { | |
| 239 --size; | |
| 240 } | |
| 241 } | |
| 242 | |
| 243 static int sizeOf(InvocationPrimitive invoke, FunctionDefinition function) { | |
| 244 SizeVisitor visitor = new SizeVisitor(); | |
| 245 visitor.visit(function); | |
| 246 if (invoke.callingConvention == CallingConvention.Intercepted) { | |
| 247 // Note that if the invocation is a dummy-intercepted call, then the | |
| 248 // target has an unused interceptor parameter, but the caller provides | |
| 249 // no interceptor argument. | |
| 250 visitor.countArgument(invoke.interceptor, function.interceptorParameter); | |
| 251 } | |
| 252 visitor.countArgument(invoke.receiver, function.receiverParameter); | |
| 253 for (int i = 0; i < invoke.argumentRefs.length; ++i) { | |
| 254 visitor.countArgument(invoke.argument(i), function.parameters[i]); | |
| 255 } | |
| 256 return visitor.size; | |
| 257 } | |
| 258 | |
| 259 // Inlining a function incurs a cost equal to the number of primitives and | |
| 260 // non-jump tail expressions. | |
| 261 // TODO(kmillikin): Tune the size computation and size bound. | |
| 262 processLetPrim(LetPrim node) => ++size; | |
| 263 processLetMutable(LetMutable node) => ++size; | |
| 264 processBranch(Branch node) => ++size; | |
| 265 processThrow(Throw nose) => ++size; | |
| 266 processRethrow(Rethrow node) => ++size; | |
| 267 | |
| 268 // Discount primitives that do not generate code. | |
| 269 processRefinement(Refinement node) => --size; | |
| 270 processBoundsCheck(BoundsCheck node) { | |
| 271 if (node.hasNoChecks) { | |
| 272 --size; | |
| 273 } | |
| 274 } | |
| 275 | |
| 276 processForeignCode(ForeignCode node) { | |
| 277 // Count the number of nodes in the JS fragment, and discount the size | |
| 278 // originally added by LetPrim. | |
| 279 JsSizeVisitor visitor = new JsSizeVisitor(); | |
| 280 node.codeTemplate.ast.accept(visitor); | |
| 281 size += visitor.size - 1; | |
| 282 } | |
| 283 } | |
| 284 | |
| 285 class JsSizeVisitor extends js.BaseVisitor { | |
| 286 int size = 0; | |
| 287 | |
| 288 visitNode(js.Node node) { | |
| 289 ++size; | |
| 290 return super.visitNode(node); | |
| 291 } | |
| 292 | |
| 293 visitInterpolatedExpression(js.InterpolatedExpression node) { | |
| 294 // Suppress call to visitNode. Placeholders should not be counted, because | |
| 295 // the argument has already been counted, and will in most cases be inserted | |
| 296 // directly in the placeholder. | |
| 297 } | |
| 298 } | |
| 299 | |
| 300 class InliningVisitor extends TrampolineRecursiveVisitor { | |
| 301 final Inliner _inliner; | |
| 302 | |
| 303 // A successful inlining attempt returns the [Primitive] that represents the | |
| 304 // result of the inlined call or null. If the result is non-null, the body | |
| 305 // of the inlined function is available in this field. | |
| 306 CpsFragment _fragment; | |
| 307 | |
| 308 InliningVisitor(this._inliner); | |
| 309 | |
| 310 JavaScriptBackend get backend => _inliner.functionCompiler.backend; | |
| 311 TypeMaskSystem get typeSystem => _inliner.functionCompiler.typeSystem; | |
| 312 World get world => _inliner.functionCompiler.compiler.world; | |
| 313 | |
| 314 FunctionDefinition compileToCpsIr(AstElement element) { | |
| 315 return _inliner.functionCompiler.compileToCpsIr(element); | |
| 316 } | |
| 317 | |
| 318 void optimizeBeforeInlining(FunctionDefinition function) { | |
| 319 _inliner.functionCompiler.optimizeCpsBeforeInlining(function); | |
| 320 } | |
| 321 | |
| 322 void applyCpsPass(Pass pass, FunctionDefinition function) { | |
| 323 return _inliner.functionCompiler.applyCpsPass(pass, function); | |
| 324 } | |
| 325 | |
| 326 bool isRecursive(Element target, CallStructure callStructure) { | |
| 327 return _inliner.stack.any((StackEntry s) => s.match(target, callStructure)); | |
| 328 } | |
| 329 | |
| 330 @override | |
| 331 Expression traverseLetPrim(LetPrim node) { | |
| 332 // A successful inlining attempt will set the node's body to null, so it is | |
| 333 // read before visiting the primitive. | |
| 334 Expression next = node.body; | |
| 335 Primitive replacement = visit(node.primitive); | |
| 336 if (replacement != null) { | |
| 337 node.primitive.replaceWithFragment(_fragment, replacement); | |
| 338 } | |
| 339 return next; | |
| 340 } | |
| 341 | |
| 342 TypeMask abstractType(Primitive def) { | |
| 343 return def.type ?? typeSystem.dynamicType; | |
| 344 } | |
| 345 | |
| 346 /// Build the IR term for the function that adapts a call site targeting a | |
| 347 /// function that takes optional arguments not passed at the call site. | |
| 348 FunctionDefinition buildAdapter(InvokeMethod node, FunctionElement target) { | |
| 349 Parameter thisParameter = new Parameter(new ThisParameterLocal(target)) | |
| 350 ..type = node.receiver.type; | |
| 351 Parameter interceptorParameter = | |
| 352 node.interceptorRef != null ? new Parameter(null) : null; | |
| 353 List<Parameter> parameters = | |
| 354 new List<Parameter>.generate(node.argumentRefs.length, (int index) { | |
| 355 // TODO(kmillikin): Use a hint for the parameter names. | |
| 356 return new Parameter(null)..type = node.argument(index).type; | |
| 357 }); | |
| 358 Continuation returnContinuation = new Continuation.retrn(); | |
| 359 CpsFragment cps = new CpsFragment(); | |
| 360 | |
| 361 FunctionSignature signature = target.functionSignature; | |
| 362 int requiredParameterCount = signature.requiredParameterCount; | |
| 363 List<Primitive> arguments = new List<Primitive>.generate( | |
| 364 requiredParameterCount, (int index) => parameters[index]); | |
| 365 | |
| 366 int parameterIndex = requiredParameterCount; | |
| 367 CallStructure newCallStructure; | |
| 368 if (signature.optionalParametersAreNamed) { | |
| 369 List<String> incomingNames = | |
| 370 node.selector.callStructure.getOrderedNamedArguments(); | |
| 371 List<String> outgoingNames = <String>[]; | |
| 372 int nameIndex = 0; | |
| 373 signature.orderedOptionalParameters.forEach((ParameterElement formal) { | |
| 374 if (nameIndex < incomingNames.length && | |
| 375 formal.name == incomingNames[nameIndex]) { | |
| 376 arguments.add(parameters[parameterIndex++]); | |
| 377 ++nameIndex; | |
| 378 } else { | |
| 379 Constant defaultValue = cps.makeConstant( | |
| 380 backend.constants.getConstantValue(formal.constant)); | |
| 381 defaultValue.type = typeSystem.getParameterType(formal); | |
| 382 arguments.add(defaultValue); | |
| 383 } | |
| 384 outgoingNames.add(formal.name); | |
| 385 }); | |
| 386 newCallStructure = | |
| 387 new CallStructure(signature.parameterCount, outgoingNames); | |
| 388 } else { | |
| 389 signature.forEachOptionalParameter((ParameterElement formal) { | |
| 390 if (parameterIndex < parameters.length) { | |
| 391 arguments.add(parameters[parameterIndex++]); | |
| 392 } else { | |
| 393 Constant defaultValue = cps.makeConstant( | |
| 394 backend.constants.getConstantValue(formal.constant)); | |
| 395 defaultValue.type = typeSystem.getParameterType(formal); | |
| 396 arguments.add(defaultValue); | |
| 397 } | |
| 398 }); | |
| 399 newCallStructure = new CallStructure(signature.parameterCount); | |
| 400 } | |
| 401 | |
| 402 Selector newSelector = new Selector( | |
| 403 node.selector.kind, node.selector.memberName, newCallStructure); | |
| 404 Primitive result = cps.invokeMethod( | |
| 405 thisParameter, newSelector, node.mask, arguments, | |
| 406 interceptor: interceptorParameter, | |
| 407 callingConvention: node.callingConvention); | |
| 408 result.type = typeSystem.getInvokeReturnType(node.selector, node.mask); | |
| 409 returnContinuation.parameters.single.type = result.type; | |
| 410 cps.invokeContinuation(returnContinuation, <Primitive>[result]); | |
| 411 return new FunctionDefinition( | |
| 412 target, thisParameter, parameters, returnContinuation, cps.root, | |
| 413 interceptorParameter: interceptorParameter); | |
| 414 } | |
| 415 | |
| 416 // Given an invocation and a known target, possibly perform inlining. | |
| 417 // | |
| 418 // An optional call structure indicates a dynamic call. Calls that are | |
| 419 // already resolved statically have a null call structure. | |
| 420 // | |
| 421 // The [Primitive] representing the result of the inlined call is returned | |
| 422 // if the call was inlined, and the inlined function body is available in | |
| 423 // [_fragment]. If the call was not inlined, null is returned. | |
| 424 Primitive tryInlining(InvocationPrimitive invoke, FunctionElement target, | |
| 425 CallStructure callStructure) { | |
| 426 // Quick checks: do not inline or even cache calls to targets without an | |
| 427 // AST node, targets that are asynchronous or generator functions, or | |
| 428 // targets containing a try statement. | |
| 429 if (!target.hasNode) return null; | |
| 430 if (backend.isJsInterop(target)) return null; | |
| 431 if (target.asyncMarker != AsyncMarker.SYNC) return null; | |
| 432 // V8 does not optimize functions containing a try statement. Inlining | |
| 433 // code containing a try statement will make the optimizable calling code | |
| 434 // become unoptimizable. | |
| 435 if (target.resolvedAst.elements.containsTryStatement) { | |
| 436 return null; | |
| 437 } | |
| 438 | |
| 439 // Don't inline methods that never return. They are usually helper functions | |
| 440 // that throw an exception. | |
| 441 if (invoke.type.isEmpty) { | |
| 442 // TODO(sra): It would be ok to inline if doing so was shrinking. | |
| 443 return null; | |
| 444 } | |
| 445 | |
| 446 if (isBlacklisted(target)) return null; | |
| 447 | |
| 448 if (invoke.callingConvention == CallingConvention.OneShotIntercepted) { | |
| 449 // One-shot interceptor calls with a known target are only inserted on | |
| 450 // uncommon code paths, so they should not be inlined. | |
| 451 return null; | |
| 452 } | |
| 453 | |
| 454 Primitive receiver = invoke.receiver; | |
| 455 TypeMask abstractReceiver = | |
| 456 receiver == null ? null : abstractType(receiver); | |
| 457 // The receiver is non-null in a method body, unless the receiver is known | |
| 458 // to be `null` (isEmpty covers `null` and unreachable). | |
| 459 TypeMask abstractReceiverInMethod = abstractReceiver == null | |
| 460 ? null | |
| 461 : abstractReceiver.isEmptyOrNull | |
| 462 ? abstractReceiver | |
| 463 : abstractReceiver.nonNullable(); | |
| 464 List<TypeMask> abstractArguments = | |
| 465 invoke.arguments.map(abstractType).toList(); | |
| 466 var cachedResult = _inliner.cache.get( | |
| 467 target, callStructure, abstractReceiverInMethod, abstractArguments); | |
| 468 | |
| 469 // Negative inlining result in the cache. | |
| 470 if (cachedResult == InliningCache.NO_INLINE) return null; | |
| 471 | |
| 472 Primitive finish(FunctionDefinition function) { | |
| 473 _fragment = new CpsFragment(invoke.sourceInformation); | |
| 474 Primitive receiver = invoke.receiver; | |
| 475 List<Primitive> arguments = invoke.arguments.toList(); | |
| 476 // Add a null check to the inlined function body if necessary. The | |
| 477 // cached function body does not contain the null check. | |
| 478 if (receiver != null && abstractReceiver.isNullable) { | |
| 479 receiver = | |
| 480 nullReceiverGuard(invoke, _fragment, receiver, abstractReceiver); | |
| 481 } | |
| 482 return _fragment.inlineFunction(function, receiver, arguments, | |
| 483 interceptor: invoke.interceptor, hint: invoke.hint); | |
| 484 } | |
| 485 | |
| 486 // Positive inlining result in the cache. | |
| 487 if (cachedResult is FunctionDefinition) { | |
| 488 return finish(cachedResult); | |
| 489 } | |
| 490 | |
| 491 // We have not seen this combination of target and abstract arguments | |
| 492 // before. Make an inlining decision. | |
| 493 assert(cachedResult == InliningCache.ABSENT); | |
| 494 Primitive doNotInline() { | |
| 495 _inliner.cache.putNegative( | |
| 496 target, callStructure, abstractReceiverInMethod, abstractArguments); | |
| 497 return null; | |
| 498 } | |
| 499 | |
| 500 if (backend.annotations.noInline(target)) return doNotInline(); | |
| 501 if (isRecursive(target, callStructure)) return doNotInline(); | |
| 502 | |
| 503 FunctionDefinition function; | |
| 504 if (callStructure != null && | |
| 505 target.functionSignature.parameterCount != | |
| 506 callStructure.argumentCount) { | |
| 507 // The argument count at the call site does not match the target's | |
| 508 // formal parameter count. Build the IR term for an adapter function | |
| 509 // body. | |
| 510 if (backend.isNative(target)) { | |
| 511 // TODO(25548): Generate correct adaptor for native methods. | |
| 512 return doNotInline(); | |
| 513 } else { | |
| 514 function = buildAdapter(invoke, target); | |
| 515 } | |
| 516 } else { | |
| 517 function = compileToCpsIr(target); | |
| 518 if (function.receiverParameter != null) { | |
| 519 function.receiverParameter.type = abstractReceiverInMethod; | |
| 520 } | |
| 521 for (int i = 0; i < invoke.argumentRefs.length; ++i) { | |
| 522 function.parameters[i].type = invoke.argument(i).type; | |
| 523 } | |
| 524 optimizeBeforeInlining(function); | |
| 525 } | |
| 526 | |
| 527 // Inline calls in the body. | |
| 528 _inliner.rewrite(function, callStructure); | |
| 529 | |
| 530 // Compute the size. | |
| 531 // TODO(kmillikin): Tune the size bound. | |
| 532 int size = SizeVisitor.sizeOf(invoke, function); | |
| 533 if (!_inliner.isCalledOnce(target) && size > 11) return doNotInline(); | |
| 534 | |
| 535 _inliner.cache.putPositive(target, callStructure, abstractReceiverInMethod, | |
| 536 abstractArguments, function); | |
| 537 return finish(function); | |
| 538 } | |
| 539 | |
| 540 Primitive nullReceiverGuard(InvocationPrimitive invoke, CpsFragment fragment, | |
| 541 Primitive dartReceiver, TypeMask abstractReceiver) { | |
| 542 if (invoke is! InvokeMethod) return dartReceiver; | |
| 543 InvokeMethod invokeMethod = invoke; | |
| 544 Selector selector = invokeMethod.selector; | |
| 545 if (typeSystem.isDefinitelyNum(abstractReceiver, allowNull: true)) { | |
| 546 Primitive condition = _fragment.letPrim(new ApplyBuiltinOperator( | |
| 547 BuiltinOperator.IsNotNumber, | |
| 548 <Primitive>[dartReceiver], | |
| 549 invoke.sourceInformation)); | |
| 550 condition.type = typeSystem.boolType; | |
| 551 Primitive check = _fragment.letPrim(new ReceiverCheck.nullCheck( | |
| 552 dartReceiver, selector, invoke.sourceInformation, | |
| 553 condition: condition)); | |
| 554 check.type = abstractReceiver.nonNullable(); | |
| 555 return check; | |
| 556 } | |
| 557 | |
| 558 Primitive check = _fragment.letPrim(new ReceiverCheck.nullCheck( | |
| 559 dartReceiver, selector, invoke.sourceInformation)); | |
| 560 check.type = abstractReceiver.nonNullable(); | |
| 561 return check; | |
| 562 } | |
| 563 | |
| 564 @override | |
| 565 Primitive visitInvokeStatic(InvokeStatic node) { | |
| 566 return tryInlining(node, node.target, null); | |
| 567 } | |
| 568 | |
| 569 @override | |
| 570 Primitive visitInvokeMethod(InvokeMethod node) { | |
| 571 Primitive receiver = node.receiver; | |
| 572 Element element = world.locateSingleElement(node.selector, receiver.type); | |
| 573 if (element == null || element is! FunctionElement) return null; | |
| 574 if (node.selector.isGetter != element.isGetter) return null; | |
| 575 if (node.selector.isSetter != element.isSetter) return null; | |
| 576 if (node.selector.name != element.name) return null; | |
| 577 | |
| 578 return tryInlining( | |
| 579 node, element.asFunctionElement(), node.selector.callStructure); | |
| 580 } | |
| 581 | |
| 582 @override | |
| 583 Primitive visitInvokeMethodDirectly(InvokeMethodDirectly node) { | |
| 584 if (node.selector.isGetter != node.target.isGetter) return null; | |
| 585 if (node.selector.isSetter != node.target.isSetter) return null; | |
| 586 return tryInlining(node, node.target, null); | |
| 587 } | |
| 588 | |
| 589 @override | |
| 590 Primitive visitInvokeConstructor(InvokeConstructor node) { | |
| 591 if (node.dartType is GenericType) { | |
| 592 // We cannot inline a constructor invocation containing type arguments | |
| 593 // because CreateInstance in the body does not know the type arguments. | |
| 594 // We would incorrectly instantiate a class like A instead of A<B>. | |
| 595 // TODO(kmillikin): try to fix this. | |
| 596 GenericType generic = node.dartType; | |
| 597 if (generic.typeArguments.any((DartType t) => !t.isDynamic)) return null; | |
| 598 } | |
| 599 return tryInlining(node, node.target, null); | |
| 600 } | |
| 601 | |
| 602 bool isBlacklisted(FunctionElement target) { | |
| 603 ClassElement enclosingClass = target.enclosingClass; | |
| 604 if (target.isOperator && | |
| 605 (enclosingClass == backend.helpers.jsNumberClass || | |
| 606 enclosingClass == backend.helpers.jsDoubleClass || | |
| 607 enclosingClass == backend.helpers.jsIntClass)) { | |
| 608 // These should be handled by operator specialization. | |
| 609 return true; | |
| 610 } | |
| 611 if (target == backend.helpers.stringInterpolationHelper) return true; | |
| 612 return false; | |
| 613 } | |
| 614 } | |
| OLD | NEW |