| // 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); | |
| } | |
| } |