blob: 22fdbc16bb0c40479c5affc734077dd94a4714ae [file] [log] [blame]
// Copyright (c) 2019, 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';
/// Simple unreachable code elimination: removes asserts and if statements
/// with constant conditions. Does a very limited constant folding of
/// logical expressions.
Component transformComponent(Component component, bool enableAsserts) {
new SimpleUnreachableCodeElimination(enableAsserts).visitComponent(component);
return component;
}
class SimpleUnreachableCodeElimination extends Transformer {
final bool enableAsserts;
SimpleUnreachableCodeElimination(this.enableAsserts);
bool _isBoolConstant(Expression node) =>
node is BoolLiteral ||
(node is ConstantExpression && node.constant is BoolConstant);
bool _getBoolConstantValue(Expression node) {
if (node is BoolLiteral) {
return node.value;
}
if (node is ConstantExpression) {
final constant = node.constant;
if (constant is BoolConstant) {
return constant.value;
}
}
throw 'Expected bool constant: $node';
}
Expression _createBoolLiteral(bool value, int fileOffset) =>
new BoolLiteral(value)..fileOffset = fileOffset;
Statement _makeEmptyBlockIfNull(Statement node) =>
node == null ? Block(<Statement>[]) : node;
@override
TreeNode visitIfStatement(IfStatement node) {
node.transformChildren(this);
final condition = node.condition;
if (_isBoolConstant(condition)) {
final value = _getBoolConstantValue(condition);
return value ? node.then : node.otherwise;
}
node.then = _makeEmptyBlockIfNull(node.then);
return node;
}
@override
visitConditionalExpression(ConditionalExpression node) {
node.transformChildren(this);
final condition = node.condition;
if (_isBoolConstant(condition)) {
final value = _getBoolConstantValue(condition);
return value ? node.then : node.otherwise;
}
return node;
}
@override
TreeNode visitNot(Not node) {
node.transformChildren(this);
final operand = node.operand;
if (_isBoolConstant(operand)) {
return _createBoolLiteral(
!_getBoolConstantValue(operand), node.fileOffset);
}
return node;
}
@override
TreeNode visitLogicalExpression(LogicalExpression node) {
node.transformChildren(this);
final left = node.left;
final right = node.right;
final operator = node.operator;
if (_isBoolConstant(left)) {
final leftValue = _getBoolConstantValue(left);
if (_isBoolConstant(right)) {
final rightValue = _getBoolConstantValue(right);
if (operator == '||') {
return _createBoolLiteral(leftValue || rightValue, node.fileOffset);
} else if (operator == '&&') {
return _createBoolLiteral(leftValue && rightValue, node.fileOffset);
} else {
throw 'Unexpected LogicalExpression operator ${operator}: $node';
}
} else {
if (leftValue && operator == '||') {
return _createBoolLiteral(true, node.fileOffset);
} else if (!leftValue && operator == '&&') {
return _createBoolLiteral(false, node.fileOffset);
}
}
}
return node;
}
@override
visitStaticGet(StaticGet node) {
node.transformChildren(this);
final target = node.target;
if (target is Field && target.isConst) {
throw 'StaticGet from const field $target should be evaluated by front-end: $node';
}
return node;
}
@override
TreeNode visitAssertStatement(AssertStatement node) {
if (!enableAsserts) {
return null;
}
return super.visitAssertStatement(node);
}
@override
TreeNode visitAssertBlock(AssertBlock node) {
if (!enableAsserts) {
return null;
}
return super.visitAssertBlock(node);
}
@override
TreeNode visitAssertInitializer(AssertInitializer node) {
if (!enableAsserts) {
return null;
}
return super.visitAssertInitializer(node);
}
@override
TreeNode visitTryFinally(TryFinally node) {
node.transformChildren(this);
final fin = node.finalizer;
if (fin == null || (fin is Block && fin.statements.isEmpty)) {
return node.body;
}
return node;
}
bool _isRethrow(Statement body) {
if (body is ExpressionStatement && body.expression is Rethrow) {
return true;
} else if (body is Block && body.statements.length == 1) {
return _isRethrow(body.statements.single);
}
return false;
}
@override
TreeNode visitTryCatch(TryCatch node) {
node.transformChildren(this);
// Can replace try/catch with its body if all catches are just rethow.
for (Catch catchClause in node.catches) {
if (!_isRethrow(catchClause.body)) {
return node;
}
}
return node.body;
}
// Make sure we're not generating `null` bodies.
// Try/catch, try/finally and switch/case statements
// always have a Block in a body, so there is no
// need to guard against null.
@override
TreeNode visitWhileStatement(WhileStatement node) {
node.transformChildren(this);
node.body = _makeEmptyBlockIfNull(node.body);
return node;
}
@override
TreeNode visitDoStatement(DoStatement node) {
node.transformChildren(this);
node.body = _makeEmptyBlockIfNull(node.body);
return node;
}
@override
TreeNode visitForStatement(ForStatement node) {
node.transformChildren(this);
node.body = _makeEmptyBlockIfNull(node.body);
return node;
}
@override
TreeNode visitForInStatement(ForInStatement node) {
node.transformChildren(this);
node.body = _makeEmptyBlockIfNull(node.body);
return node;
}
}