blob: bde3bac1c9383adc3f2e2dba6a6163090df0d555 [file] [log] [blame] [edit]
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
library;
import '../common.dart';
import '../common/codegen.dart';
import '../common/tasks.dart';
import '../elements/entities.dart';
import '../inferrer/types.dart';
import '../js_model/elements.dart';
import 'annotations.dart';
import 'codegen_inputs.dart';
abstract class FunctionCompiler {
void initialize(
GlobalTypeInferenceResults globalInferenceResults,
CodegenInputs codegen,
);
/// Generates JavaScript code for [member].
CodegenResult compile(MemberEntity member);
List<CompilerTask> get tasks;
}
enum _Decision {
unknown,
mustNotInline,
mayInlineInLoopMustNotOutside,
canInlineInLoopMustNotOutside,
canInlineInLoopMayInlineOutside,
canInline,
}
/*
* Invariants:
* canInline(function) implies canInline(function, insideLoop:true)
* !canInline(function, insideLoop: true) implies !canInline(function)
*/
class FunctionInlineCache {
final AnnotationsData _annotationsData;
final Map<FunctionEntity, _Decision> _cachedDecisions = {};
FunctionInlineCache(this._annotationsData);
/// Checks that [method] is the canonical representative for this method.
///
/// For a [MethodElement] this means it must be the declaration element.
bool checkFunction(FunctionEntity method) {
return '$method'.startsWith(jsElementPrefix);
}
// Returns `true`/`false` if we have a cached decision.
// Returns `null` otherwise.
bool? canInline(FunctionEntity element, {required bool insideLoop}) {
assert(checkFunction(element), failedAt(element));
// TODO(sra): Have annotations for mustInline / noInline for constructor
// bodies. (There used to be some logic here to have constructor bodies,
// inherit the settings from annotations on the generative
// constructor. This was conflated with the heuristic decisions, leading
// to lack of inlining where it was beneficial.)
final decision = _cachedDecisions[element] ?? _Decision.unknown;
if (insideLoop) {
switch (decision) {
case _Decision.mustNotInline:
return false;
case _Decision.unknown:
case _Decision.mayInlineInLoopMustNotOutside:
// We know we can't inline outside a loop, but don't know for the
// loop case. Return `null` to indicate that we don't know yet.
return null;
case _Decision.canInlineInLoopMustNotOutside:
case _Decision.canInlineInLoopMayInlineOutside:
case _Decision.canInline:
return true;
}
} else {
switch (decision) {
case _Decision.mustNotInline:
case _Decision.mayInlineInLoopMustNotOutside:
case _Decision.canInlineInLoopMustNotOutside:
return false;
case _Decision.unknown:
case _Decision.canInlineInLoopMayInlineOutside:
// We know we can inline inside a loop, but don't know for the
// non-loop case. Return `null` to indicate that we don't know yet.
return null;
case _Decision.canInline:
return true;
}
}
}
void markAsInlinable(FunctionEntity element, {required bool insideLoop}) {
assert(checkFunction(element), failedAt(element));
final oldDecision = _cachedDecisions[element] ?? _Decision.unknown;
if (insideLoop) {
switch (oldDecision) {
case _Decision.mustNotInline:
throw failedAt(
element,
"Can't mark $element as non-inlinable and inlinable at the "
"same time.",
);
case _Decision.unknown:
// We know that it can be inlined in a loop, but don't know about the
// non-loop case yet.
_cachedDecisions[element] = _Decision.canInlineInLoopMayInlineOutside;
break;
case _Decision.mayInlineInLoopMustNotOutside:
_cachedDecisions[element] = _Decision.canInlineInLoopMustNotOutside;
break;
case _Decision.canInlineInLoopMustNotOutside:
case _Decision.canInlineInLoopMayInlineOutside:
case _Decision.canInline:
// Do nothing.
break;
}
} else {
switch (oldDecision) {
case _Decision.mustNotInline:
case _Decision.mayInlineInLoopMustNotOutside:
case _Decision.canInlineInLoopMustNotOutside:
throw failedAt(
element,
"Can't mark $element as non-inlinable and inlinable at the "
"same time.",
);
case _Decision.unknown:
case _Decision.canInlineInLoopMayInlineOutside:
_cachedDecisions[element] = _Decision.canInline;
break;
case _Decision.canInline:
// Do nothing.
break;
}
}
}
void markAsNonInlinable(FunctionEntity element, {bool insideLoop = true}) {
assert(checkFunction(element), failedAt(element));
final oldDecision = _cachedDecisions[element] ?? _Decision.unknown;
if (insideLoop) {
switch (oldDecision) {
case _Decision.canInlineInLoopMustNotOutside:
case _Decision.canInlineInLoopMayInlineOutside:
case _Decision.canInline:
throw failedAt(
element,
"Can't mark $element as non-inlinable and inlinable at the "
"same time.",
);
case _Decision.mayInlineInLoopMustNotOutside:
case _Decision.unknown:
_cachedDecisions[element] = _Decision.mustNotInline;
break;
case _Decision.mustNotInline:
// Do nothing.
break;
}
} else {
switch (oldDecision) {
case _Decision.canInline:
throw failedAt(
element,
"Can't mark $element as non-inlinable and inlinable at the "
"same time.",
);
case _Decision.unknown:
// We can't inline outside a loop, but we might still be allowed to do
// so outside.
_cachedDecisions[element] = _Decision.mayInlineInLoopMustNotOutside;
break;
case _Decision.canInlineInLoopMayInlineOutside:
// We already knew that the function could be inlined inside a loop,
// but didn't have information about the non-loop case. Now we know
// that it can't be inlined outside a loop.
_cachedDecisions[element] = _Decision.canInlineInLoopMustNotOutside;
break;
case _Decision.mayInlineInLoopMustNotOutside:
case _Decision.canInlineInLoopMustNotOutside:
case _Decision.mustNotInline:
// Do nothing.
break;
}
}
}
bool markedAsNoInline(FunctionEntity element) {
assert(checkFunction(element), failedAt(element));
return _annotationsData.hasNoInline(element);
}
bool markedAsTryInline(FunctionEntity element) {
assert(checkFunction(element), failedAt(element));
return _annotationsData.hasTryInline(element);
}
}