Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(165)

Side by Side Diff: pkg/compiler/lib/src/cps_ir/inline.dart

Issue 2246623002: Delete CPS IR (Closed) Base URL: git@github.com:dart-lang/sdk.git@master
Patch Set: Created 4 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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 }
OLDNEW
« no previous file with comments | « pkg/compiler/lib/src/cps_ir/gvn.dart ('k') | pkg/compiler/lib/src/cps_ir/insert_refinements.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698