[ddc] Avoid emitting dead branches in conditionals

Don't emit dead code branches that are guarded by boolean literals.

Add a single level of simplification for !true -> false, and
!false -> true.

While this might not be very common in code, it does allow for a
useful pattern in the runtime libraries. Branches can be included
into the compiled code or not based on a compile time flag.

Change-Id: Ib90e1e951cea3ef8c75b944635776b292759594a
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/243363
Commit-Queue: Nicholas Shahan <nshahan@google.com>
Reviewed-by: Anna Gringauze <annagrin@google.com>
This commit is contained in:
Nicholas Shahan 2022-05-03 20:17:44 +00:00 committed by Commit Bot
parent c27086d567
commit 651350bc3b
2 changed files with 99 additions and 6 deletions

View file

@ -4396,11 +4396,32 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
@override
js_ast.Statement visitIfStatement(IfStatement node) {
var condition = _visitTest(node.condition);
var then = _visitScope(node.then);
if (node.otherwise != null) {
return js_ast.If(condition, then, _visitScope(node.otherwise));
if (condition is js_ast.LiteralBool) {
// Avoid emitting the branch with code that will never execute.
if (condition.value) {
return _visitScope(node.then).toStatement();
} else {
return _visitScope(node.otherwise).toStatement();
}
}
return js_ast.If(
condition, _visitScope(node.then), _visitScope(node.otherwise));
}
return js_ast.If.noElse(condition, then);
if (condition is js_ast.LiteralBool) {
if (condition.value) {
// Avoid emitting conditional when it is always true.
// ex: `if (true) {abc...}` -> `{abc...}`
return _visitScope(node.then).toStatement();
} else {
// Avoid emitting conditional and then when it will never execute.
// ex: `if (false) {abc...}` -> `;`
return js_ast.EmptyStatement();
}
}
return js_ast.If.noElse(condition, _visitScope(node.then));
}
/// Visits a statement, and ensures the resulting AST handles block scope
@ -5905,11 +5926,19 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
negated: true);
}
var jsOperand = _visitTest(operand);
if (jsOperand is js_ast.LiteralBool) {
// Flipping the value here for `!true` or `!false` allows for simpler
// `if (true)` or `if (false)` detection and optimization.
return js_ast.LiteralBool(!jsOperand.value)
.withSourceInformation(jsOperand.sourceInformation)
as js_ast.LiteralBool;
}
// Logical negation, `!e`, is a boolean conversion context since it is
// defined as `e ? false : true`.
return js
.call('!#', _visitTest(operand))
.withSourceInformation(continueSourceMap) as js_ast.Expression;
return js.call('!#', jsOperand).withSourceInformation(continueSourceMap)
as js_ast.Expression;
}
@override
@ -5930,6 +5959,16 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
@override
js_ast.Expression visitConditionalExpression(ConditionalExpression node) {
var condition = _visitTest(node.condition);
if (condition is js_ast.LiteralBool) {
if (condition.value) {
// Avoid emitting conditional when one branch is effectively dead code.
// ex: `true ? foo : bar` -> `foo`
return _visitExpression(node.then);
} else {
// ex: `false ? foo : bar` -> `bar`
return _visitExpression(node.otherwise);
}
}
var then = _visitExpression(node.then);
var otherwise = _visitExpression(node.otherwise);
return js.call('# ? # : #', [condition, then, otherwise])

View file

@ -0,0 +1,54 @@
// 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 'dart:_foreign_helper' show JS;
import 'package:expect/expect.dart';
void main() async {
var count = 0;
if (JS<bool>('!', 'false')) {
// Should be eliminated from the output based on the condition above.
JS('', 'syntax error here!');
}
count++;
if (JS<bool>('!', 'true')) {
count++;
} else {
// Should be eliminated from the output based on the condition above.
JS('', 'syntax error here!');
}
if (JS<bool>('!', 'false')) {
// Should be eliminated from the output based on the condition above.
JS('', 'syntax error here!');
} else {
count++;
}
if (!JS<bool>('!', 'true')) {
// Should be eliminated from the output based on the condition above.
JS('', 'syntax error here!');
}
count++;
if (!JS<bool>('!', 'false')) {
count++;
} else {
// Should be eliminated from the output based on the condition above.
JS('', 'syntax error here!');
}
if (!JS<bool>('!', 'true')) {
// Should be eliminated from the output based on the condition above.
JS('', 'syntax error here!');
} else {
count++;
}
JS<bool>('!', 'true') ? count++ : JS('', 'syntax error here!');
JS<bool>('!', 'false') ? JS('', 'syntax error here!') : count++;
!JS<bool>('!', 'true') ? JS('', 'syntax error here!') : count++;
!JS<bool>('!', 'false') ? count++ : JS('', 'syntax error here!');
// All expected branches are evaluated, and none of the syntax errors where
// compiled at all.
Expect.equals(10, count);
}