blob: e16224d23662b2a9c6f29752e9d7b2ad52d604ab [file] [log] [blame]
// Copyright (c) 2023, 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.
import 'package:kernel/ast.dart';
import 'package:kernel/class_hierarchy.dart';
import 'package:kernel/core_types.dart';
import 'package:kernel/target/targets.dart';
import 'package:kernel/type_environment.dart';
import 'try_constant_evaluator.dart';
class ConstConditionalSimplifier extends RemovingTransformer {
final Component _component;
late final TypeEnvironment _typeEnvironment;
late final _ConstantEvaluator constantEvaluator;
final bool _removeAsserts;
ConstConditionalSimplifier(
DartLibrarySupport librarySupport,
ConstantsBackend constantsBackend,
this._component,
ReportErrorFunction _reportError, {
Map<String, String>? environmentDefines,
bool Function(TreeNode)? shouldNotInline,
CoreTypes? coreTypes,
ClassHierarchy? classHierarchy,
bool removeAsserts = false,
}) : _removeAsserts = removeAsserts {
// Coverage-ignore(suite): Not run.
coreTypes ??= new CoreTypes(_component);
// Coverage-ignore(suite): Not run.
classHierarchy ??= new ClassHierarchy(_component, coreTypes);
_typeEnvironment = new TypeEnvironment(coreTypes, classHierarchy);
constantEvaluator = new _ConstantEvaluator(
librarySupport,
constantsBackend,
_component,
_typeEnvironment,
_reportError,
environmentDefines: environmentDefines,
shouldNotInline: shouldNotInline,
);
}
Constant? _evaluate(Expression node) => constantEvaluator._evaluate(node);
TreeNode run() => transform(_component);
@override
TreeNode defaultMember(Member node, TreeNode? removalSentinel) {
return constantEvaluator.inStaticTypeContext(
new StaticTypeContext(node, _typeEnvironment),
() {
constantEvaluator._clearLocalCaches();
return super.defaultMember(node, removalSentinel);
},
);
}
@override
TreeNode visitConditionalExpression(
ConditionalExpression node,
TreeNode? removalSentinel,
) {
super.visitConditionalExpression(node, removalSentinel);
Constant? condition = _evaluate(node.condition);
if (condition is! BoolConstant) return node;
return condition.value
? node.then
:
// Coverage-ignore(suite): Not run.
node.otherwise;
}
@override
TreeNode visitIfStatement(IfStatement node, TreeNode? removalSentinel) {
super.visitIfStatement(node, removalSentinel);
Constant? condition = _evaluate(node.condition);
if (condition is! BoolConstant) return node;
if (condition.value) {
return node.then;
} else {
return node.otherwise ??
removalSentinel ?? // Coverage-ignore(suite): Not run.
new EmptyStatement();
}
}
@override
// Coverage-ignore(suite): Not run.
TreeNode visitAssertBlock(AssertBlock node, TreeNode? removalSentinel) {
if (_removeAsserts) {
return removalSentinel ?? new EmptyStatement();
} else {
return super.visitAssertBlock(node, removalSentinel);
}
}
@override
// Coverage-ignore(suite): Not run.
TreeNode visitAssertInitializer(
AssertInitializer node,
TreeNode? removalSentinel,
) {
if (_removeAsserts) {
// Initializers only occur in the initializer list, where they are always
// removable.
return removalSentinel!;
} else {
return super.visitAssertInitializer(node, removalSentinel);
}
}
@override
// Coverage-ignore(suite): Not run.
TreeNode visitAssertStatement(
AssertStatement node,
TreeNode? removalSentinel,
) {
if (_removeAsserts) {
return removalSentinel ?? new EmptyStatement();
} else {
return super.visitAssertStatement(node, removalSentinel);
}
}
}
class _ConstantEvaluator extends TryConstantEvaluator {
// TODO(fishythefish): Do caches need to be invalidated when the static type
// context changes?
/// Cache for local variables in the current method.
Map<VariableDeclaration, Constant?> _variableCache = {};
final Map<Field, Constant?> _staticFieldCache = {};
final Map<FunctionNode, Constant?> _functionCache = {};
final Map<FunctionNode, Constant?> _localFunctionCache = {};
// TODO(fishythefish): Make this more granular than [TreeNode].
final bool Function(TreeNode) _shouldNotInline;
_ConstantEvaluator(
super.librarySupport,
super.constantsBackend,
super.component,
super.typeEnvironment,
super.reportError, {
super.environmentDefines,
bool Function(TreeNode)? shouldNotInline,
}) : _shouldNotInline = shouldNotInline ?? ((_) => false);
void _clearLocalCaches() {
_variableCache.clear();
_localFunctionCache.clear();
}
Constant? _evaluate(Expression node) =>
evaluateOrNull(staticTypeContext, node, requireConstant: false);
Constant? _evaluateFunctionInvocation(FunctionNode node) {
if (node.typeParameters.isNotEmpty ||
node.requiredParameterCount != 0 ||
node.positionalParameters.isNotEmpty ||
node.namedParameters.isNotEmpty) {
return null;
}
Statement? body = node.body;
if (body is! ReturnStatement) return null;
// Coverage-ignore-block(suite): Not run.
Expression? expression = body.expression;
if (expression == null) return null;
return _evaluate(expression);
}
Constant? _evaluateVariableGet(VariableDeclaration variable) {
// A function parameter can be declared final with an initializer, but
// doesn't necessarily have the initializer's value.
if (variable.parent is FunctionNode) return null;
if (_shouldNotInline(variable)) return null;
if (!variable.isFinal) return null;
Expression? initializer = variable.initializer;
if (initializer == null) return null;
return _evaluate(initializer);
}
Constant? _lookupVariableGet(VariableDeclaration variable) => _variableCache
.putIfAbsent(variable, () => _evaluateVariableGet(variable));
@override
Constant visitVariableGet(VariableGet node) =>
_lookupVariableGet(node.variable) ?? super.visitVariableGet(node);
Constant? _evaluateStaticFieldGet(Field field) {
if (_shouldNotInline(field)) return null;
if (!field.isFinal) return null;
// Coverage-ignore-block(suite): Not run.
Expression? initializer = field.initializer;
if (initializer == null) return null;
return _evaluate(initializer);
}
Constant? _lookupStaticFieldGet(Field field) => _staticFieldCache.putIfAbsent(
field,
() => _evaluateStaticFieldGet(field),
);
// Coverage-ignore(suite): Not run.
Constant? _evaluateStaticGetter(Procedure getter) {
if (_shouldNotInline(getter)) return null;
return _evaluateFunctionInvocation(getter.function);
}
// Coverage-ignore(suite): Not run.
Constant? _lookupStaticGetter(Procedure getter) => _functionCache.putIfAbsent(
getter.function,
() => _evaluateStaticGetter(getter),
);
Constant? _lookupStaticGet(Member target) {
if (target is Field) return _lookupStaticFieldGet(target);
// Coverage-ignore(suite): Not run.
return _lookupStaticGetter(target as Procedure);
}
@override
Constant visitStaticGet(StaticGet node) =>
_lookupStaticGet(node.target) ?? super.visitStaticGet(node);
// Coverage-ignore(suite): Not run.
Constant? _evaluateLocalFunctionInvocation(LocalFunctionInvocation node) {
if (_shouldNotInline(node.variable)) return null;
return _evaluateFunctionInvocation(node.localFunction.function);
}
// Coverage-ignore(suite): Not run.
Constant? _lookupLocalFunctionInvocation(LocalFunctionInvocation node) =>
_localFunctionCache.putIfAbsent(
node.localFunction.function,
() => _evaluateLocalFunctionInvocation(node),
);
@override
// Coverage-ignore(suite): Not run.
Constant visitLocalFunctionInvocation(LocalFunctionInvocation node) =>
_lookupLocalFunctionInvocation(node) ??
super.visitLocalFunctionInvocation(node);
Constant? _evaluateStaticInvocation(Procedure target) {
if (_shouldNotInline(target)) return null;
return _evaluateFunctionInvocation(target.function);
}
Constant? _lookupStaticInvocation(Procedure target) => _functionCache
.putIfAbsent(target.function, () => _evaluateStaticInvocation(target));
@override
Constant visitStaticInvocation(StaticInvocation node) {
// Clear variable cache static invocations to avoid looking up cached
// variables from another call stack.
//
// This can occur when calling const extension type constructors since these
// are lowered into top level functions.
Map<VariableDeclaration, Constant?> oldCache = _variableCache;
_variableCache = {};
Constant result =
_lookupStaticInvocation(node.target) ??
super.visitStaticInvocation(node);
_variableCache = oldCache;
return result;
}
}