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 'cps_fragment.dart'; |
| 8 import 'cps_ir_builder.dart' show ThisParameterLocal; |
| 9 import 'cps_ir_nodes.dart'; |
| 10 import 'optimizers.dart'; |
| 11 import 'type_mask_system.dart' show TypeMaskSystem; |
| 12 import '../compiler.dart' show Compiler; |
| 13 import '../dart_types.dart' show DartType, GenericType; |
| 14 import '../io/source_information.dart' show SourceInformation; |
| 15 import '../world.dart' show World; |
| 16 import '../constants/values.dart' show ConstantValue; |
| 17 import '../elements/elements.dart'; |
| 18 import '../js_backend/js_backend.dart' show JavaScriptBackend; |
| 19 import '../js_backend/codegen/task.dart' show CpsFunctionCompiler; |
| 20 import '../types/types.dart' show |
| 21 FlatTypeMask, ForwardingTypeMask, TypeMask, UnionTypeMask; |
| 22 import '../universe/call_structure.dart' show CallStructure; |
| 23 import '../universe/selector.dart' show Selector; |
| 24 |
| 25 /// Inlining stack entries. |
| 26 /// |
| 27 /// During inlining, a stack is used to detect cycles in the call graph. |
| 28 class StackEntry { |
| 29 // Dynamically resolved calls might be targeting an adapter function that |
| 30 // fills in optional arguments not passed at the call site. Therefore these |
| 31 // calls are represented by the eventual target and the call structure at |
| 32 // the call site, which together identify the target. Statically resolved |
| 33 // calls are represented by the target element and a null call structure. |
| 34 final ExecutableElement target; |
| 35 final CallStructure callStructure; |
| 36 |
| 37 StackEntry(this.target, this.callStructure); |
| 38 |
| 39 bool match(ExecutableElement otherTarget, CallStructure otherCallStructure) { |
| 40 if (target != otherTarget) return false; |
| 41 if (callStructure == null) return otherCallStructure == null; |
| 42 return otherCallStructure != null && |
| 43 callStructure.match(otherCallStructure); |
| 44 } |
| 45 } |
| 46 |
| 47 /// Inlining cache entries. |
| 48 class CacheEntry { |
| 49 // The cache maps a function element to a list of entries, where each entry |
| 50 // is a tuple of (call structure, abstract receiver, abstract arguments) |
| 51 // along with the inlining decision and optional IR function definition. |
| 52 final CallStructure callStructure; |
| 53 final TypeMask receiver; |
| 54 final List<TypeMask> arguments; |
| 55 |
| 56 final bool decision; |
| 57 final FunctionDefinition function; |
| 58 |
| 59 CacheEntry(this.callStructure, this.receiver, this.arguments, this.decision, |
| 60 this.function); |
| 61 |
| 62 bool match(CallStructure otherCallStructure, TypeMask otherReceiver, |
| 63 List<TypeMask> otherArguments) { |
| 64 if (callStructure == null) { |
| 65 if (otherCallStructure != null) return false; |
| 66 } else if (otherCallStructure == null || |
| 67 !callStructure.match(otherCallStructure)) { |
| 68 return false; |
| 69 } |
| 70 |
| 71 if (receiver != otherReceiver) return false; |
| 72 assert(arguments.length == otherArguments.length); |
| 73 for (int i = 0; i < arguments.length; ++i) { |
| 74 if (arguments[i] != otherArguments[i]) return false; |
| 75 } |
| 76 return true; |
| 77 } |
| 78 } |
| 79 |
| 80 /// An inlining cache. |
| 81 /// |
| 82 /// During inlining a cache is used to remember inlining decisions for shared |
| 83 /// parts of the call graph, to avoid exploring them more than once. |
| 84 /// |
| 85 /// The cache maps a tuple of (function element, call structure, |
| 86 /// abstract receiver, abstract arguments) to a boolean inlining decision and |
| 87 /// an IR function definition if the decision is positive. |
| 88 class InliningCache { |
| 89 static const int ABSENT = -1; |
| 90 static const int NO_INLINE = 0; |
| 91 |
| 92 final Map<ExecutableElement, List<CacheEntry>> map = |
| 93 <ExecutableElement, List<CacheEntry>>{}; |
| 94 |
| 95 // When function definitions are put into or removed from the cache, they are |
| 96 // copied because the compiler passes will mutate them. |
| 97 final CopyingVisitor copier = new CopyingVisitor(); |
| 98 |
| 99 void _putInternal(ExecutableElement element, CallStructure callStructure, |
| 100 TypeMask receiver, |
| 101 List<TypeMask> arguments, |
| 102 bool decision, |
| 103 FunctionDefinition function) { |
| 104 map.putIfAbsent(element, () => <CacheEntry>[]) |
| 105 .add(new CacheEntry(callStructure, receiver, arguments, decision, |
| 106 function)); |
| 107 } |
| 108 |
| 109 /// Put a positive inlining decision in the cache. |
| 110 /// |
| 111 /// A positive inlining decision maps to an IR function definition. |
| 112 void putPositive(ExecutableElement element, CallStructure callStructure, |
| 113 TypeMask receiver, |
| 114 List<TypeMask> arguments, |
| 115 FunctionDefinition function) { |
| 116 _putInternal(element, callStructure, receiver, arguments, true, |
| 117 copier.copy(function)); |
| 118 } |
| 119 |
| 120 /// Put a negative inlining decision in the cache. |
| 121 void putNegative(ExecutableElement element, |
| 122 CallStructure callStructure, |
| 123 TypeMask receiver, |
| 124 List<TypeMask> arguments) { |
| 125 _putInternal(element, callStructure, receiver, arguments, false, null); |
| 126 } |
| 127 |
| 128 /// Look up a tuple in the cache. |
| 129 /// |
| 130 /// A positive lookup result return the IR function definition. A negative |
| 131 /// lookup result returns [NO_INLINE]. If there is no cached result, |
| 132 /// [ABSENT] is returned. |
| 133 get(ExecutableElement element, CallStructure callStructure, TypeMask receiver, |
| 134 List<TypeMask> arguments) { |
| 135 List<CacheEntry> entries = map[element]; |
| 136 if (entries != null) { |
| 137 for (CacheEntry entry in entries) { |
| 138 if (entry.match(callStructure, receiver, arguments)) { |
| 139 if (entry.decision) { |
| 140 FunctionDefinition function = copier.copy(entry.function); |
| 141 ParentVisitor.setParents(function); |
| 142 return function; |
| 143 } |
| 144 return NO_INLINE; |
| 145 } |
| 146 } |
| 147 } |
| 148 return ABSENT; |
| 149 } |
| 150 } |
| 151 |
| 152 class Inliner implements Pass { |
| 153 get passName => 'Inline calls'; |
| 154 |
| 155 final CpsFunctionCompiler functionCompiler; |
| 156 |
| 157 final InliningCache cache = new InliningCache(); |
| 158 |
| 159 final List<StackEntry> stack = <StackEntry>[]; |
| 160 |
| 161 Inliner(this.functionCompiler); |
| 162 |
| 163 bool isCalledOnce(Element element) { |
| 164 return functionCompiler.compiler.typesTask.typesInferrer.isCalledOnce( |
| 165 element); |
| 166 } |
| 167 |
| 168 void rewrite(FunctionDefinition node, [CallStructure callStructure]) { |
| 169 Element function = node.element; |
| 170 |
| 171 // Inlining in asynchronous or generator functions is disabled. Inlining |
| 172 // triggers a bug in the async rewriter. |
| 173 // TODO(kmillikin): Fix the bug and eliminate this restriction if it makes |
| 174 // sense. |
| 175 if (function is FunctionElement && |
| 176 function.asyncMarker != AsyncMarker.SYNC) { |
| 177 return; |
| 178 } |
| 179 |
| 180 stack.add(new StackEntry(function, callStructure)); |
| 181 new InliningVisitor(this).visit(node); |
| 182 assert(stack.last.match(function, callStructure)); |
| 183 stack.removeLast(); |
| 184 new ShrinkingReducer().rewrite(node); |
| 185 } |
| 186 } |
| 187 |
| 188 /// Compute an abstract size of an IR function definition. |
| 189 /// |
| 190 /// The size represents the cost of inlining at a call site. |
| 191 class SizeVisitor extends TrampolineRecursiveVisitor { |
| 192 int size = 0; |
| 193 |
| 194 void countArgument(Reference<Primitive> argument, Parameter parameter) { |
| 195 // If a parameter is unused and the corresponding argument has only the |
| 196 // one use at the invocation, then inlining the call might enable |
| 197 // elimination of the argument. This 'pays for itself' by decreasing the |
| 198 // cost of inlining at the call site. |
| 199 if (argument != null && |
| 200 argument.definition.hasExactlyOneUse && |
| 201 parameter.hasNoUses) { |
| 202 --size; |
| 203 } |
| 204 } |
| 205 |
| 206 static int sizeOf(InvocationPrimitive invoke, FunctionDefinition function) { |
| 207 SizeVisitor visitor = new SizeVisitor(); |
| 208 visitor.visit(function); |
| 209 visitor.countArgument(invoke.receiver, function.thisParameter); |
| 210 for (int i = 0; i < invoke.arguments.length; ++i) { |
| 211 visitor.countArgument(invoke.arguments[i], function.parameters[i]); |
| 212 } |
| 213 return visitor.size; |
| 214 } |
| 215 |
| 216 // Inlining a function incurs a cost equal to the number of primitives and |
| 217 // non-jump tail expressions. |
| 218 // TODO(kmillikin): Tune the size computation and size bound. |
| 219 processLetPrim(LetPrim node) => ++size; |
| 220 processLetMutable(LetMutable node) => ++size; |
| 221 processBranch(Branch node) => ++size; |
| 222 processThrow(Throw nose) => ++size; |
| 223 processRethrow(Rethrow node) => ++size; |
| 224 } |
| 225 |
| 226 class InliningVisitor extends TrampolineRecursiveVisitor { |
| 227 final Inliner _inliner; |
| 228 |
| 229 // A successful inlining attempt returns the [Primitive] that represents the |
| 230 // result of the inlined call or null. If the result is non-null, the body |
| 231 // of the inlined function is available in this field. |
| 232 CpsFragment _fragment; |
| 233 |
| 234 InliningVisitor(this._inliner); |
| 235 |
| 236 JavaScriptBackend get backend => _inliner.functionCompiler.backend; |
| 237 TypeMaskSystem get typeSystem => _inliner.functionCompiler.typeSystem; |
| 238 World get world => _inliner.functionCompiler.compiler.world; |
| 239 |
| 240 FunctionDefinition compileToCpsIr(AstElement element) { |
| 241 return _inliner.functionCompiler.compileToCpsIr(element); |
| 242 } |
| 243 |
| 244 void optimizeBeforeInlining(FunctionDefinition function) { |
| 245 _inliner.functionCompiler.optimizeCpsBeforeInlining(function); |
| 246 } |
| 247 |
| 248 void applyCpsPass(Pass pass, FunctionDefinition function) { |
| 249 return _inliner.functionCompiler.applyCpsPass(pass, function); |
| 250 } |
| 251 |
| 252 bool isRecursive(Element target, CallStructure callStructure) { |
| 253 return _inliner.stack.any((StackEntry s) => s.match(target, callStructure)); |
| 254 } |
| 255 |
| 256 @override |
| 257 Expression traverseLetPrim(LetPrim node) { |
| 258 // A successful inlining attempt will set the node's body to null, so it is |
| 259 // read before visiting the primitive. |
| 260 Expression next = node.body; |
| 261 Primitive replacement = visit(node.primitive); |
| 262 if (replacement != null) { |
| 263 node.primitive.replaceWithFragment(_fragment, replacement); |
| 264 } |
| 265 return next; |
| 266 } |
| 267 |
| 268 TypeMask abstractType(Reference<Primitive> ref) { |
| 269 return ref.definition.type ?? typeSystem.dynamicType; |
| 270 } |
| 271 |
| 272 /// Build the IR term for the function that adapts a call site targeting a |
| 273 /// function that takes optional arguments not passed at the call site. |
| 274 FunctionDefinition buildAdapter(InvokeMethod node, FunctionElement target) { |
| 275 Parameter thisParameter = new Parameter(new ThisParameterLocal(target)) |
| 276 ..type = node.receiver.definition.type; |
| 277 List<Parameter> parameters = new List<Parameter>.generate( |
| 278 node.arguments.length, |
| 279 (int index) { |
| 280 // TODO(kmillikin): Use a hint for the parameter names. |
| 281 return new Parameter(null) |
| 282 ..type = node.arguments[index].definition.type; |
| 283 }); |
| 284 Continuation returnContinuation = new Continuation.retrn(); |
| 285 CpsFragment cps = new CpsFragment(); |
| 286 |
| 287 FunctionSignature signature = target.functionSignature; |
| 288 int requiredParameterCount = signature.requiredParameterCount; |
| 289 if (node.callingConvention == CallingConvention.Intercepted || |
| 290 node.callingConvention == CallingConvention.DummyIntercepted) { |
| 291 ++requiredParameterCount; |
| 292 } |
| 293 List<Primitive> arguments = new List<Primitive>.generate( |
| 294 requiredParameterCount, |
| 295 (int index) => parameters[index]); |
| 296 |
| 297 int parameterIndex = requiredParameterCount; |
| 298 CallStructure newCallStructure; |
| 299 if (signature.optionalParametersAreNamed) { |
| 300 List<String> incomingNames = |
| 301 node.selector.callStructure.getOrderedNamedArguments(); |
| 302 List<String> outgoingNames = <String>[]; |
| 303 int nameIndex = 0; |
| 304 signature.orderedOptionalParameters.forEach((ParameterElement formal) { |
| 305 if (nameIndex < incomingNames.length && |
| 306 formal.name == incomingNames[nameIndex]) { |
| 307 arguments.add(parameters[parameterIndex++]); |
| 308 ++nameIndex; |
| 309 } else { |
| 310 Constant defaultValue = cps.makeConstant( |
| 311 backend.constants.getConstantValueForVariable(formal)); |
| 312 defaultValue.type = typeSystem.getParameterType(formal); |
| 313 arguments.add(defaultValue); |
| 314 } |
| 315 outgoingNames.add(formal.name); |
| 316 }); |
| 317 newCallStructure = |
| 318 new CallStructure(signature.parameterCount, outgoingNames); |
| 319 } else { |
| 320 signature.forEachOptionalParameter((ParameterElement formal) { |
| 321 if (parameterIndex < parameters.length) { |
| 322 arguments.add(parameters[parameterIndex++]); |
| 323 } else { |
| 324 Constant defaultValue = cps.makeConstant( |
| 325 backend.constants.getConstantValueForVariable(formal)); |
| 326 defaultValue.type = typeSystem.getParameterType(formal); |
| 327 arguments.add(defaultValue); |
| 328 } |
| 329 }); |
| 330 newCallStructure = new CallStructure(signature.parameterCount); |
| 331 } |
| 332 |
| 333 Selector newSelector = |
| 334 new Selector(node.selector.kind, node.selector.memberName, |
| 335 newCallStructure); |
| 336 Primitive result = cps.invokeMethod(thisParameter, newSelector, node.mask, |
| 337 arguments, node.callingConvention); |
| 338 result.type = typeSystem.getInvokeReturnType(node.selector, node.mask); |
| 339 cps.invokeContinuation(returnContinuation, <Primitive>[result]); |
| 340 return new FunctionDefinition(target, thisParameter, parameters, |
| 341 returnContinuation, |
| 342 cps.root); |
| 343 } |
| 344 |
| 345 // Given an invocation and a known target, possibly perform inlining. |
| 346 // |
| 347 // An optional call structure indicates a dynamic call. Calls that are |
| 348 // already resolved statically have a null call structure. |
| 349 // |
| 350 // The [Primitive] representing the result of the inlined call is returned |
| 351 // if the call was inlined, and the inlined function body is available in |
| 352 // [_fragment]. If the call was not inlined, null is returned. |
| 353 Primitive tryInlining(InvocationPrimitive invoke, FunctionElement target, |
| 354 CallStructure callStructure) { |
| 355 // Quick checks: do not inline or even cache calls to targets without an |
| 356 // AST node or targets that are asynchronous or generator functions. |
| 357 if (!target.hasNode) return null; |
| 358 if (target.asyncMarker != AsyncMarker.SYNC) return null; |
| 359 |
| 360 Reference<Primitive> dartReceiver = invoke.dartReceiverReference; |
| 361 TypeMask abstractReceiver = |
| 362 dartReceiver == null ? null : abstractType(dartReceiver); |
| 363 List<TypeMask> abstractArguments = |
| 364 invoke.arguments.map(abstractType).toList(); |
| 365 var cachedResult = _inliner.cache.get(target, callStructure, |
| 366 abstractReceiver, |
| 367 abstractArguments); |
| 368 |
| 369 // Negative inlining result in the cache. |
| 370 if (cachedResult == InliningCache.NO_INLINE) return null; |
| 371 |
| 372 // Positive inlining result in the cache. |
| 373 if (cachedResult is FunctionDefinition) { |
| 374 FunctionDefinition function = cachedResult; |
| 375 _fragment = new CpsFragment(invoke.sourceInformation); |
| 376 Primitive receiver = invoke.receiver?.definition; |
| 377 List<Primitive> arguments = |
| 378 invoke.arguments.map((Reference ref) => ref.definition).toList(); |
| 379 // Add a null check to the inlined function body if necessary. The |
| 380 // cached function body does not contain the null check. |
| 381 if (dartReceiver != null && abstractReceiver.isNullable) { |
| 382 Primitive check = |
| 383 _fragment.letPrim(new NullCheck(dartReceiver.definition, |
| 384 invoke.sourceInformation)); |
| 385 check.type = abstractReceiver.nonNullable(); |
| 386 if (invoke.callingConvention == CallingConvention.Intercepted) { |
| 387 arguments[0] = check; |
| 388 } else { |
| 389 receiver = check; |
| 390 } |
| 391 } |
| 392 return _fragment.inlineFunction(function, receiver, arguments, |
| 393 hint: invoke.hint); |
| 394 } |
| 395 |
| 396 // We have not seen this combination of target and abstract arguments |
| 397 // before. Make an inlining decision. |
| 398 assert(cachedResult == InliningCache.ABSENT); |
| 399 Primitive doNotInline() { |
| 400 _inliner.cache.putNegative(target, callStructure, abstractReceiver, |
| 401 abstractArguments); |
| 402 return null; |
| 403 } |
| 404 if (backend.annotations.noInline(target)) return doNotInline(); |
| 405 if (isRecursive(target, callStructure)) return doNotInline(); |
| 406 |
| 407 FunctionDefinition function; |
| 408 if (callStructure != null && |
| 409 target.functionSignature.parameterCount != |
| 410 callStructure.argumentCount) { |
| 411 // The argument count at the call site does not match the target's |
| 412 // formal parameter count. Build the IR term for an adapter function |
| 413 // body. |
| 414 function = buildAdapter(invoke, target); |
| 415 } else { |
| 416 function = _inliner.functionCompiler.compileToCpsIr(target); |
| 417 void setValue(Variable variable, Reference<Primitive> value) { |
| 418 variable.type = value.definition.type; |
| 419 } |
| 420 if (invoke.receiver != null) { |
| 421 setValue(function.thisParameter, invoke.receiver); |
| 422 } |
| 423 for (int i = 0; i < invoke.arguments.length; ++i) { |
| 424 setValue(function.parameters[i], invoke.arguments[i]); |
| 425 } |
| 426 optimizeBeforeInlining(function); |
| 427 } |
| 428 |
| 429 // Inline calls in the body. |
| 430 _inliner.rewrite(function, callStructure); |
| 431 |
| 432 // Compute the size. |
| 433 // TODO(kmillikin): Tune the size bound. |
| 434 int size = SizeVisitor.sizeOf(invoke, function); |
| 435 if (!_inliner.isCalledOnce(target) && size > 11) return doNotInline(); |
| 436 |
| 437 _inliner.cache.putPositive(target, callStructure, abstractReceiver, |
| 438 abstractArguments, function); |
| 439 _fragment = new CpsFragment(invoke.sourceInformation); |
| 440 Primitive receiver = invoke.receiver?.definition; |
| 441 List<Primitive> arguments = |
| 442 invoke.arguments.map((Reference ref) => ref.definition).toList(); |
| 443 if (dartReceiver != null && abstractReceiver.isNullable) { |
| 444 Primitive check = |
| 445 _fragment.letPrim(new NullCheck(dartReceiver.definition, |
| 446 invoke.sourceInformation)); |
| 447 check.type = abstractReceiver.nonNullable(); |
| 448 if (invoke.callingConvention == CallingConvention.Intercepted) { |
| 449 arguments[0] = check; |
| 450 } else { |
| 451 receiver = check; |
| 452 } |
| 453 } |
| 454 return _fragment.inlineFunction(function, receiver, arguments, |
| 455 hint: invoke.hint); |
| 456 } |
| 457 |
| 458 @override |
| 459 Primitive visitInvokeStatic(InvokeStatic node) { |
| 460 return tryInlining(node, node.target, null); |
| 461 } |
| 462 |
| 463 @override |
| 464 Primitive visitInvokeMethod(InvokeMethod node) { |
| 465 Primitive receiver = node.dartReceiver; |
| 466 Element element = world.locateSingleElement(node.selector, receiver.type); |
| 467 if (element == null || element is! FunctionElement) return null; |
| 468 if (node.selector.isGetter != element.isGetter) return null; |
| 469 if (node.selector.isSetter != element.isSetter) return null; |
| 470 if (node.selector.name != element.name) return null; |
| 471 |
| 472 return tryInlining(node, element.asFunctionElement(), |
| 473 node.selector.callStructure); |
| 474 } |
| 475 |
| 476 @override |
| 477 Primitive visitInvokeMethodDirectly(InvokeMethodDirectly node) { |
| 478 if (node.selector.isGetter != node.target.isGetter) return null; |
| 479 if (node.selector.isSetter != node.target.isSetter) return null; |
| 480 return tryInlining(node, node.target, null); |
| 481 } |
| 482 |
| 483 @override |
| 484 Primitive visitInvokeConstructor(InvokeConstructor node) { |
| 485 if (node.dartType is GenericType) { |
| 486 // We cannot inline a constructor invocation containing type arguments |
| 487 // because CreateInstance in the body does not know the type arguments. |
| 488 // We would incorrectly instantiate a class like A instead of A<B>. |
| 489 // TODO(kmillikin): try to fix this. |
| 490 GenericType generic = node.dartType; |
| 491 if (generic.typeArguments.any((DartType t) => !t.isDynamic)) return null; |
| 492 } |
| 493 return tryInlining(node, node.target, null); |
| 494 } |
| 495 } |
OLD | NEW |