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

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: Rebaseline test expectations and fix a bug (typo). Created 4 years, 12 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 '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 }
OLDNEW
« no previous file with comments | « pkg/compiler/lib/src/cps_ir/cps_ir_nodes_sexpr.dart ('k') | pkg/compiler/lib/src/cps_ir/optimizers.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698