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

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

Issue 1537663002: dart2js: Initial implementation of inlining. (Closed) Base URL: git@github.com:dart-lang/sdk.git@master
Patch Set: Created 5 years 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 '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 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698