blob: b8f099c1efca47db2a79d5a66ab1d3897b845099 [file] [log] [blame]
// 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.
// @dart = 2.10
library js_backend.backend;
import '../common.dart';
import '../common/codegen.dart';
import '../elements/entities.dart';
import '../inferrer/types.dart';
import '../js_model/elements.dart';
import '../tracer.dart';
import 'annotations.dart';
import 'checked_mode_helpers.dart';
import 'namer.dart';
import 'runtime_types_codegen.dart';
import 'runtime_types_new.dart';
abstract class FunctionCompiler {
void initialize(
GlobalTypeInferenceResults globalInferenceResults, CodegenInputs codegen);
/// Generates JavaScript code for [member].
CodegenResult compile(MemberEntity member);
Iterable get tasks;
}
/*
* Invariants:
* canInline(function) implies canInline(function, insideLoop:true)
* !canInline(function, insideLoop: true) implies !canInline(function)
*/
class FunctionInlineCache {
static const int _unknown = -1;
static const int _mustNotInline = 0;
// May-inline-in-loop means that the function may not be inlined outside loops
// but may be inlined in a loop.
static const int _mayInlineInLoopMustNotOutside = 1;
// The function can be inlined in a loop, but not outside.
static const int _canInlineInLoopMustNotOutside = 2;
// May-inline means that we know that it can be inlined inside a loop, but
// don't know about the general case yet.
static const int _canInlineInLoopMayInlineOutside = 3;
static const int _canInline = 4;
final Map<FunctionEntity, int> _cachedDecisions = {};
final Set<FunctionEntity> _noInlineFunctions = {};
final Set<FunctionEntity> _tryInlineFunctions = {};
FunctionInlineCache(AnnotationsData annotationsData) {
annotationsData.forEachNoInline((FunctionEntity function) {
markAsNoInline(function);
});
annotationsData.forEachTryInline((FunctionEntity function) {
markAsTryInline(function);
});
}
/// 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 the current cache decision. This should only be used for testing.
int getCurrentCacheDecisionForTesting(FunctionEntity element) {
assert(checkFunction(element), failedAt(element));
return _cachedDecisions[element];
}
// Returns `true`/`false` if we have a cached decision.
// Returns `null` otherwise.
bool canInline(FunctionEntity element, {bool insideLoop}) {
assert(checkFunction(element), failedAt(element));
int decision = _cachedDecisions[element];
if (decision == null) {
// 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.)
decision = _unknown;
}
if (insideLoop) {
switch (decision) {
case _mustNotInline:
return false;
case _unknown:
case _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 _canInlineInLoopMustNotOutside:
case _canInlineInLoopMayInlineOutside:
case _canInline:
return true;
}
} else {
switch (decision) {
case _mustNotInline:
case _mayInlineInLoopMustNotOutside:
case _canInlineInLoopMustNotOutside:
return false;
case _unknown:
case _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 _canInline:
return true;
}
}
// Quiet static checker.
return null;
}
void markAsInlinable(FunctionEntity element, {bool insideLoop}) {
assert(checkFunction(element), failedAt(element));
int oldDecision = _cachedDecisions[element];
if (oldDecision == null) {
oldDecision = _unknown;
}
if (insideLoop) {
switch (oldDecision) {
case _mustNotInline:
throw failedAt(
element,
"Can't mark $element as non-inlinable and inlinable at the "
"same time.");
case _unknown:
// We know that it can be inlined in a loop, but don't know about the
// non-loop case yet.
_cachedDecisions[element] = _canInlineInLoopMayInlineOutside;
break;
case _mayInlineInLoopMustNotOutside:
_cachedDecisions[element] = _canInlineInLoopMustNotOutside;
break;
case _canInlineInLoopMustNotOutside:
case _canInlineInLoopMayInlineOutside:
case _canInline:
// Do nothing.
break;
}
} else {
switch (oldDecision) {
case _mustNotInline:
case _mayInlineInLoopMustNotOutside:
case _canInlineInLoopMustNotOutside:
throw failedAt(
element,
"Can't mark $element as non-inlinable and inlinable at the "
"same time.");
case _unknown:
case _canInlineInLoopMayInlineOutside:
_cachedDecisions[element] = _canInline;
break;
case _canInline:
// Do nothing.
break;
}
}
}
void markAsNonInlinable(FunctionEntity element, {bool insideLoop = true}) {
assert(checkFunction(element), failedAt(element));
int oldDecision = _cachedDecisions[element];
if (oldDecision == null) {
oldDecision = _unknown;
}
if (insideLoop) {
switch (oldDecision) {
case _canInlineInLoopMustNotOutside:
case _canInlineInLoopMayInlineOutside:
case _canInline:
throw failedAt(
element,
"Can't mark $element as non-inlinable and inlinable at the "
"same time.");
case _mayInlineInLoopMustNotOutside:
case _unknown:
_cachedDecisions[element] = _mustNotInline;
break;
case _mustNotInline:
// Do nothing.
break;
}
} else {
switch (oldDecision) {
case _canInline:
throw failedAt(
element,
"Can't mark $element as non-inlinable and inlinable at the "
"same time.");
case _unknown:
// We can't inline outside a loop, but we might still be allowed to do
// so outside.
_cachedDecisions[element] = _mayInlineInLoopMustNotOutside;
break;
case _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] = _canInlineInLoopMustNotOutside;
break;
case _mayInlineInLoopMustNotOutside:
case _canInlineInLoopMustNotOutside:
case _mustNotInline:
// Do nothing.
break;
}
}
}
void markAsNoInline(FunctionEntity element) {
assert(checkFunction(element), failedAt(element));
_noInlineFunctions.add(element);
}
bool markedAsNoInline(FunctionEntity element) {
assert(checkFunction(element), failedAt(element));
return _noInlineFunctions.contains(element);
}
void markAsTryInline(FunctionEntity element) {
assert(checkFunction(element), failedAt(element));
_tryInlineFunctions.add(element);
}
bool markedAsTryInline(FunctionEntity element) {
assert(checkFunction(element), failedAt(element));
return _tryInlineFunctions.contains(element);
}
}
/// Holds resources only used during code generation.
class CodegenInputs {
final CheckedModeHelpers checkedModeHelpers = CheckedModeHelpers();
final RuntimeTypesSubstitutions rtiSubstitutions;
final RecipeEncoder rtiRecipeEncoder;
final Tracer tracer;
final FixedNames fixedNames;
CodegenInputs(this.rtiSubstitutions, this.rtiRecipeEncoder, this.tracer,
this.fixedNames);
}