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