Skip to content

Commit 92e61ea

Browse files
dcharkesCommit Queue
authored and
Commit Queue
committed
Revert "Reland "[pkg/vm] Handle switch statements in unreachable code eliminator.""
This reverts commit 2623117. Reason for revert: Breaks legacy mode. Bug: #54029 Original change's description: > Reland "[pkg/vm] Handle switch statements in unreachable code eliminator." > > This is a reland of commit 92bf76d > > In the original CL, the changes to the UCE assumed all > SwitchStatements were exhaustive. Now if a SwitchStatement isn't > explicitly or implicitly (via a default case) exhaustive and no case > matches the tested constant, the SwitchStatement is properly removed. > > In addition, if a guaranteed to match case is found, more than one > cases remain (thus the SwitchStatement is not removed or replaced with > the single case's body), and the default case is removed, then the > resulting SwitchStatement is marked as explicitly exhaustive, as this > serves as a signal to backends that they do not need to handle the > possibility of no case matching in the absence of a default case. > > Original change's description: > > [pkg/vm] Handle switch statements in unreachable code eliminator. > > > > Namely, if the tested expression for a switch statement is constant, > > then we can remove any constant cases where the constants differ, > > and if all but a single case is removed, we can replace the switch > > with the case body. > > > > If constant functions are not enabled, then getters annotated with > > @pragma("vm:platform-const") are still evaluated with the constant > > function evaluation machinery, but only those and no others (including > > any functions called within an annotated getter). This way, functions > > can be annotated with @pragma("vm:platform-const") without having to > > rewrite them to be a single returned expression. > > > > TEST=pkg/vm/test/transformations/unreachable_code_elimination > > pkg/vm/test/transformations/vm_constant_evaluator > > > > Issue: #50473 > > Issue: #31969 > > Change-Id: Ie290d2f1f469326238d66c3d9631f8e696685ff0 > > Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/332760 > > Commit-Queue: Tess Strickland <[email protected]> > > Reviewed-by: Chloe Stefantsova <[email protected]> > > Reviewed-by: Alexander Markov <[email protected]> > > TEST=pkg/vm/test/transformations/unreachable_code_elimination > pkg/vm/test/transformations/vm_constant_evaluator > > Issue: #50473 > Issue: #31969 > Cq-Include-Trybots: luci.dart.try:vm-aot-linux-release-x64-try,vm-aot-mac-release-arm64-try > Change-Id: I557ca933808012e670e306f2d880221a0d7dd670 > Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/334224 > Reviewed-by: Chloe Stefantsova <[email protected]> > Reviewed-by: Alexander Markov <[email protected]> > Commit-Queue: Tess Strickland <[email protected]> Issue: #50473 Issue: #31969 Change-Id: I56373c7a6feac76e23c1800ae83eb013c5856cba Cq-Include-Trybots: luci.dart.try:vm-aot-linux-release-x64-try,vm-aot-mac-release-arm64-try Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/335820 Reviewed-by: Chloe Stefantsova <[email protected]> Commit-Queue: Daco Harkes <[email protected]> Bot-Commit: Rubber Stamper <[email protected]>
1 parent 489b2c3 commit 92e61ea

12 files changed

+156
-1199
lines changed

pkg/front_end/lib/src/fasta/kernel/constant_evaluator.dart

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2631,11 +2631,6 @@ class ConstantEvaluator implements ExpressionVisitor<Constant> {
26312631

26322632
/// Execute a function body using the [StatementConstantEvaluator].
26332633
Constant executeBody(Statement statement) {
2634-
if (!enableConstFunctions && !inExtensionTypeConstConstructor) {
2635-
throw new UnsupportedError("Statement evaluation is only supported when "
2636-
"in extension type const constructors or when the const functions "
2637-
"feature is enabled.");
2638-
}
26392634
StatementConstantEvaluator statementEvaluator =
26402635
new StatementConstantEvaluator(this);
26412636
ExecutionStatus status = statement.accept(statementEvaluator);
@@ -2662,11 +2657,6 @@ class ConstantEvaluator implements ExpressionVisitor<Constant> {
26622657
/// Returns [null] on success and an error-"constant" on failure, as such the
26632658
/// return value should be checked.
26642659
AbortConstant? executeConstructorBody(Constructor constructor) {
2665-
if (!enableConstFunctions && !inExtensionTypeConstConstructor) {
2666-
throw new UnsupportedError("Statement evaluation is only supported when "
2667-
"in extension type const constructors or when the const functions "
2668-
"feature is enabled.");
2669-
}
26702660
final Statement body = constructor.function.body!;
26712661
StatementConstantEvaluator statementEvaluator =
26722662
new StatementConstantEvaluator(this);
@@ -5488,7 +5478,14 @@ class ConstantEvaluator implements ExpressionVisitor<Constant> {
54885478
class StatementConstantEvaluator implements StatementVisitor<ExecutionStatus> {
54895479
ConstantEvaluator exprEvaluator;
54905480

5491-
StatementConstantEvaluator(this.exprEvaluator);
5481+
StatementConstantEvaluator(this.exprEvaluator) {
5482+
if (!exprEvaluator.enableConstFunctions &&
5483+
!exprEvaluator.inExtensionTypeConstConstructor) {
5484+
throw new UnsupportedError("Statement evaluation is only supported when "
5485+
"in inline class const constructors or when the const functions "
5486+
"feature is enabled.");
5487+
}
5488+
}
54925489

54935490
/// Evaluate the expression using the [ConstantEvaluator].
54945491
Constant evaluate(Expression expr) => expr.accept(exprEvaluator);

pkg/vm/lib/transformations/unreachable_code_elimination.dart

Lines changed: 51 additions & 144 deletions
Original file line numberDiff line numberDiff line change
@@ -36,32 +36,25 @@ class SimpleUnreachableCodeElimination extends RemovingTransformer {
3636
return result;
3737
}
3838

39-
bool? _getBoolConstantValue(Expression node) {
40-
if (node is BoolLiteral) return node.value;
41-
if (node is! ConstantExpression) return null;
42-
final constant = node.constant;
43-
return constant is BoolConstant ? constant.value : null;
44-
}
45-
46-
Expression _makeConstantExpression(Constant constant, Expression node) {
47-
if (constant is UnevaluatedConstant &&
48-
constant.expression is InvalidExpression) {
49-
return constant.expression;
50-
}
51-
ConstantExpression constantExpression = new ConstantExpression(
52-
constant, node.getStaticType(_staticTypeContext!))
53-
..fileOffset = node.fileOffset;
54-
if (node is FileUriExpression) {
55-
return new FileUriConstantExpression(constantExpression.constant,
56-
type: constantExpression.type, fileUri: node.fileUri)
57-
..fileOffset = node.fileOffset;
39+
bool _isBoolConstant(Expression node) =>
40+
node is BoolLiteral ||
41+
(node is ConstantExpression && node.constant is BoolConstant);
42+
43+
bool _getBoolConstantValue(Expression node) {
44+
if (node is BoolLiteral) {
45+
return node.value;
46+
}
47+
if (node is ConstantExpression) {
48+
final constant = node.constant;
49+
if (constant is BoolConstant) {
50+
return constant.value;
51+
}
5852
}
59-
return constantExpression;
53+
throw 'Expected bool constant: $node';
6054
}
6155

62-
Expression _createBoolConstantExpression(bool value, Expression node) =>
63-
_makeConstantExpression(
64-
constantEvaluator.canonicalize(BoolConstant(value)), node);
56+
Expression _createBoolLiteral(bool value, int fileOffset) =>
57+
new BoolLiteral(value)..fileOffset = fileOffset;
6558

6659
Statement _makeEmptyBlockIfEmptyStatement(Statement node, TreeNode parent) =>
6760
node is EmptyStatement ? (Block(<Statement>[])..parent = parent) : node;
@@ -70,8 +63,8 @@ class SimpleUnreachableCodeElimination extends RemovingTransformer {
7063
TreeNode visitIfStatement(IfStatement node, TreeNode? removalSentinel) {
7164
node.transformOrRemoveChildren(this);
7265
final condition = node.condition;
73-
final value = _getBoolConstantValue(condition);
74-
if (value != null) {
66+
if (_isBoolConstant(condition)) {
67+
final value = _getBoolConstantValue(condition);
7568
return value
7669
? node.then
7770
: (node.otherwise ?? removalSentinel ?? new EmptyStatement());
@@ -85,8 +78,8 @@ class SimpleUnreachableCodeElimination extends RemovingTransformer {
8578
ConditionalExpression node, TreeNode? removalSentinel) {
8679
node.transformOrRemoveChildren(this);
8780
final condition = node.condition;
88-
final value = _getBoolConstantValue(condition);
89-
if (value != null) {
81+
if (_isBoolConstant(condition)) {
82+
final value = _getBoolConstantValue(condition);
9083
return value ? node.then : node.otherwise;
9184
}
9285
return node;
@@ -96,9 +89,9 @@ class SimpleUnreachableCodeElimination extends RemovingTransformer {
9689
TreeNode visitNot(Not node, TreeNode? removalSentinel) {
9790
node.transformOrRemoveChildren(this);
9891
final operand = node.operand;
99-
final value = _getBoolConstantValue(operand);
100-
if (value != null) {
101-
return _createBoolConstantExpression(!value, node);
92+
if (_isBoolConstant(operand)) {
93+
return _createBoolLiteral(
94+
!_getBoolConstantValue(operand), node.fileOffset);
10295
}
10396
return node;
10497
}
@@ -107,111 +100,47 @@ class SimpleUnreachableCodeElimination extends RemovingTransformer {
107100
TreeNode visitLogicalExpression(
108101
LogicalExpression node, TreeNode? removalSentinel) {
109102
node.transformOrRemoveChildren(this);
110-
bool? value = _getBoolConstantValue(node.left);
111-
// Because of short-circuiting, these operators cannot be treated as
112-
// symmetric, so a non-constant left and a constant right is left as-is.
113-
if (value == null) return node;
114-
switch (node.operatorEnum) {
115-
case LogicalExpressionOperator.OR:
116-
return value ? node.left : node.right;
117-
case LogicalExpressionOperator.AND:
118-
return value ? node.right : node.left;
119-
}
120-
}
121-
122-
@override
123-
TreeNode visitSwitchStatement(
124-
SwitchStatement node, TreeNode? removalSentinel) {
125-
node.transformOrRemoveChildren(this);
126-
final tested = node.expression;
127-
if (tested is! ConstantExpression) return node;
128-
129-
// First, keep any reachable case. As a side effect, any expressions that
130-
// cannot match in the SwitchCases are removed. An expression cannot match
131-
// if it is a non-matching constant expression or it follows a constant
132-
// expression that is guaranteed to match.
133-
final toKeep = <SwitchCase>{};
134-
bool foundMatchingCase = false;
135-
for (final c in node.cases) {
136-
if (foundMatchingCase) {
137-
c.expressions.clear();
138-
continue;
139-
}
140-
c.expressions.retainWhere((e) {
141-
if (foundMatchingCase) return false;
142-
if (e is! ConstantExpression) return true;
143-
foundMatchingCase = e.constant == tested.constant;
144-
return foundMatchingCase;
145-
});
146-
if (c.isDefault || c.expressions.isNotEmpty) {
147-
toKeep.add(c);
148-
}
149-
}
150-
151-
if (toKeep.isEmpty) {
152-
if (node.isExhaustive) {
153-
throw 'Expected at least one kept case from exhaustive switch: $node';
154-
}
155-
return removalSentinel ?? new EmptyStatement();
156-
}
157-
158-
// Now iteratively find additional cases to keep by following targets of
159-
// continue statements in kept cases.
160-
final worklist = [...toKeep];
161-
final collector = ContinueSwitchStatementTargetCollector(node);
162-
while (worklist.isNotEmpty) {
163-
final next = worklist.removeLast();
164-
final targets = collector.collectTargets(next);
165-
for (final target in targets) {
166-
if (toKeep.add(target)) {
167-
worklist.add(target);
103+
final left = node.left;
104+
final right = node.right;
105+
final operatorEnum = node.operatorEnum;
106+
if (_isBoolConstant(left)) {
107+
final leftValue = _getBoolConstantValue(left);
108+
if (_isBoolConstant(right)) {
109+
final rightValue = _getBoolConstantValue(right);
110+
if (operatorEnum == LogicalExpressionOperator.OR) {
111+
return _createBoolLiteral(leftValue || rightValue, node.fileOffset);
112+
} else if (operatorEnum == LogicalExpressionOperator.AND) {
113+
return _createBoolLiteral(leftValue && rightValue, node.fileOffset);
114+
} else {
115+
throw 'Unexpected LogicalExpression operator ${operatorEnum}: $node';
116+
}
117+
} else {
118+
if (leftValue && operatorEnum == LogicalExpressionOperator.OR) {
119+
return _createBoolLiteral(true, node.fileOffset);
120+
} else if (!leftValue &&
121+
operatorEnum == LogicalExpressionOperator.AND) {
122+
return _createBoolLiteral(false, node.fileOffset);
168123
}
169124
}
170125
}
171-
172-
// Finally, remove any cases not marked for keeping. If only one case
173-
// is kept, then the switch statement can be replaced with its body.
174-
if (toKeep.length == 1) {
175-
return toKeep.first.body;
176-
}
177-
node.cases.retainWhere(toKeep.contains);
178-
if (foundMatchingCase && !node.hasDefault) {
179-
// While the expression may not be explicitly exhaustive for the type
180-
// of the tested expression, it is guaranteed to execute at least one
181-
// of the remaining cases, so the backends don't need to handle the case
182-
// where no listed case is hit for this switch.
183-
//
184-
// If the original program has the matching case directly falls through
185-
// to the default case for some reason:
186-
//
187-
// switch (4) {
188-
// ...
189-
// case 4:
190-
// default:
191-
// ...
192-
// }
193-
//
194-
// this means the default case is kept despite finding a guaranteed to
195-
// match expression, as it contains that matching expression. If that
196-
// happens, then we don't do this, to keep the invariant that
197-
// isExplicitlyExhaustive is false if there is a default case.
198-
node.isExplicitlyExhaustive = true;
199-
}
200126
return node;
201127
}
202128

203129
@override
204-
TreeNode visitStaticGet(StaticGet node, TreeNode? removalSentinel) {
130+
visitStaticGet(StaticGet node, TreeNode? removalSentinel) {
205131
node.transformOrRemoveChildren(this);
206132
final target = node.target;
207133
if (target is Field && target.isConst) {
208134
throw 'StaticGet from const field $target should be evaluated by front-end: $node';
209135
}
210-
if (!constantEvaluator.transformerShouldEvaluateExpression(node)) {
211-
return node;
136+
if (constantEvaluator.transformerShouldEvaluateExpression(node)) {
137+
final context = _staticTypeContext!;
138+
final result = constantEvaluator.evaluate(context, node);
139+
assert(result is! UnevaluatedConstant);
140+
return new ConstantExpression(result, node.getStaticType(context))
141+
..fileOffset = node.fileOffset;
212142
}
213-
final result = constantEvaluator.evaluate(_staticTypeContext!, node);
214-
return _makeConstantExpression(result, node);
143+
return node;
215144
}
216145

217146
@override
@@ -306,25 +235,3 @@ class SimpleUnreachableCodeElimination extends RemovingTransformer {
306235
return node;
307236
}
308237
}
309-
310-
class ContinueSwitchStatementTargetCollector extends RecursiveVisitor {
311-
final SwitchStatement parent;
312-
late Set<SwitchCase> collected;
313-
314-
ContinueSwitchStatementTargetCollector(this.parent);
315-
316-
Set<SwitchCase> collectTargets(SwitchCase node) {
317-
collected = {};
318-
node.accept(this);
319-
return collected;
320-
}
321-
322-
@override
323-
void visitContinueSwitchStatement(ContinueSwitchStatement node) {
324-
node.visitChildren(this);
325-
// Only keep targets that are within the original node being checked.
326-
if (node.target.parent == parent) {
327-
collected.add(node.target);
328-
}
329-
}
330-
}

pkg/vm/lib/transformations/vm_constant_evaluator.dart

Lines changed: 17 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,10 @@ import 'package:front_end/src/base/nnbd_mode.dart';
1313
import 'package:front_end/src/fasta/kernel/constant_evaluator.dart'
1414
show
1515
AbortConstant,
16-
AbortStatus,
1716
ConstantEvaluator,
1817
ErrorReporter,
1918
EvaluationMode,
20-
ProceedStatus,
21-
ReturnStatus,
22-
SimpleErrorReporter,
23-
StatementConstantEvaluator;
19+
SimpleErrorReporter;
2420

2521
import '../target_os.dart';
2622

@@ -30,10 +26,8 @@ import '../target_os.dart';
3026
/// [targetOS] represents the target operating system and is used when
3127
/// evaluating static fields and getters annotated with "vm:platform-const".
3228
///
33-
/// To avoid restricting getters annotated with "vm:platform-const" to be just
34-
/// a single return statement whose body is evaluated, we treat annotated
35-
/// getters as const functions. If [enableConstFunctions] is false, then
36-
/// only annotated getters are treated this way.
29+
/// If [enableConstFunctions] is false, then only getters that return the
30+
/// result of a single expression can be evaluated.
3731
class VMConstantEvaluator extends ConstantEvaluator {
3832
final TargetOS? _targetOS;
3933
final Map<String, Constant> _constantFields = {};
@@ -130,19 +124,6 @@ class VMConstantEvaluator extends ConstantEvaluator {
130124
bool transformerShouldEvaluateExpression(Expression node) =>
131125
_targetOS != null && node is StaticGet && _isPlatformConst(node.target);
132126

133-
Constant _executePlatformConstBody(Statement statement) {
134-
final status = statement.accept(StatementConstantEvaluator(this));
135-
if (status is AbortStatus) return status.error;
136-
// Happens if there is no return statement in a void Function(...) body.
137-
if (status is ProceedStatus) return canonicalize(NullConstant());
138-
if (status is ReturnStatus) {
139-
final value = status.value;
140-
return value != null ? value : canonicalize(NullConstant());
141-
}
142-
// Other statuses denote intermediate states and not final ones.
143-
throw 'No valid constant returned after executing $statement';
144-
}
145-
146127
@override
147128
Constant visitStaticGet(StaticGet node) {
148129
assert(_targetOS != null);
@@ -172,7 +153,20 @@ class VMConstantEvaluator extends ConstantEvaluator {
172153
result = evaluateExpressionInContext(target, target.initializer!);
173154
} else if (target is Procedure && target.isGetter) {
174155
final body = target.function.body!;
175-
result = _executePlatformConstBody(body);
156+
// If const functions are enabled, execute the getter as if it were
157+
// a const function. Otherwise the annotated getter must be a single
158+
// return statement whose expression is evaluated.
159+
if (enableConstFunctions) {
160+
result = executeBody(body);
161+
} else if (body is ReturnStatement) {
162+
if (body.expression == null) {
163+
return canonicalize(NullConstant());
164+
}
165+
result = evaluateExpressionInContext(target, body.expression!);
166+
} else {
167+
throw "Cannot evaluate method '$nameText' since it contains more "
168+
"than a single return statement.";
169+
}
176170
}
177171
if (result is AbortConstant) {
178172
throw "The body or initialization of member '$nameText' does not "

0 commit comments

Comments
 (0)