[dart2js] Add phase 1 kernel transformer to simplify const conditionals.
Suppose we have code like
if (isDevMode) {
// do foo
} else {
// do bar
}
where isDevMode is const (or can be evaluated as const). In particular,
isDevMode can be controlled by compile-time options, like
bool.fromEnvironment.
We currently eliminate the dead branch during SSA, but that means we do
the work of compiling dead code in the previous phases. Instead, we can
recognize that the condition is const (or effectively const) and
eliminate the dead AST subtree directly.
Change-Id: Ia91da5ebc7fa496a1b963308c6e02d572cab936e
Bug: b/254543452
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/270281
Reviewed-by: Sigmund Cherem <sigmund@google.com>
This commit is contained in:
committed by
Commit Queue
parent
b028da6407
commit
397ed8673c
@@ -52,6 +52,7 @@ import 'js_model/locals.dart';
|
||||
import 'kernel/front_end_adapter.dart' show CompilerFileSystem;
|
||||
import 'kernel/kernel_strategy.dart';
|
||||
import 'kernel/kernel_world.dart';
|
||||
import 'kernel/transformations/const_conditional_simplifier.dart';
|
||||
import 'null_compiler_output.dart' show NullCompilerOutput;
|
||||
import 'options.dart' show CompilerOptions;
|
||||
import 'phase/load_kernel.dart' as load_kernel;
|
||||
@@ -442,6 +443,19 @@ class Compiler {
|
||||
bool shouldStopAfterLoadKernel(load_kernel.Output? output) =>
|
||||
output == null || compilationFailed || options.cfeOnly;
|
||||
|
||||
void simplifyConstConditionals(load_kernel.Output output) {
|
||||
if (options.readClosedWorldUri == null) {
|
||||
// No existing closed world means we're in phase 1, so run the
|
||||
// transformer.
|
||||
ConstConditionalSimplifier(
|
||||
output.component, environment, reporter, options)
|
||||
.run();
|
||||
}
|
||||
|
||||
// If the closed world is deserialized instead, then the input .dill should
|
||||
// already have the modified AST.
|
||||
}
|
||||
|
||||
Future<ModuleData> runModularAnalysis(
|
||||
load_kernel.Output output, Set<Uri> moduleLibraries) async {
|
||||
ir.Component component = output.component;
|
||||
@@ -685,17 +699,19 @@ class Compiler {
|
||||
final output = await produceKernel();
|
||||
if (shouldStopAfterLoadKernel(output)) return;
|
||||
|
||||
simplifyConstConditionals(output!);
|
||||
|
||||
// Run modular analysis. This may be null if modular analysis was not
|
||||
// requested for this pipeline.
|
||||
ModuleData? moduleData;
|
||||
if (options.modularMode || options.hasModularAnalysisInputs) {
|
||||
moduleData = await produceModuleData(output!);
|
||||
moduleData = await produceModuleData(output);
|
||||
}
|
||||
if (shouldStopAfterModularAnalysis) return;
|
||||
|
||||
// Compute closed world.
|
||||
DataAndIndices<JClosedWorld>? closedWorldAndIndices =
|
||||
await produceClosedWorld(output!, moduleData);
|
||||
await produceClosedWorld(output, moduleData);
|
||||
if (shouldStopAfterClosedWorld(closedWorldAndIndices)) return;
|
||||
|
||||
// Run global analysis.
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
// 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:front_end/src/api_prototype/constant_evaluator.dart';
|
||||
import 'package:front_end/src/api_unstable/dart2js.dart' show LocatedMessage;
|
||||
import 'package:kernel/ast.dart';
|
||||
import 'package:kernel/class_hierarchy.dart';
|
||||
import 'package:kernel/core_types.dart';
|
||||
import 'package:kernel/type_environment.dart';
|
||||
|
||||
import '../../diagnostics/diagnostic_listener.dart';
|
||||
import '../../environment.dart';
|
||||
import '../../ir/constants.dart';
|
||||
import '../../kernel/element_map.dart';
|
||||
import '../../options.dart';
|
||||
|
||||
class ConstConditionalSimplifier extends RemovingTransformer {
|
||||
final Component _component;
|
||||
final Environment _environment;
|
||||
final DiagnosticReporter _reporter;
|
||||
final CompilerOptions _options;
|
||||
|
||||
late final TypeEnvironment _typeEnvironment;
|
||||
late final Dart2jsConstantEvaluator _constantEvaluator;
|
||||
late StaticTypeContext _staticTypeContext;
|
||||
|
||||
ConstConditionalSimplifier(
|
||||
this._component, this._environment, this._reporter, this._options) {
|
||||
final coreTypes = CoreTypes(_component);
|
||||
final classHierarchy = ClassHierarchy(_component, coreTypes);
|
||||
_typeEnvironment = TypeEnvironment(coreTypes, classHierarchy);
|
||||
_constantEvaluator = Dart2jsConstantEvaluator(_component, _typeEnvironment,
|
||||
(LocatedMessage message, List<LocatedMessage>? context) {
|
||||
reportLocatedMessage(_reporter, message, context);
|
||||
},
|
||||
environment: _environment,
|
||||
evaluationMode: _options.useLegacySubtyping
|
||||
? EvaluationMode.weak
|
||||
: EvaluationMode.strong);
|
||||
}
|
||||
|
||||
TreeNode run() => transform(_component);
|
||||
|
||||
@override
|
||||
TreeNode defaultMember(Member node, TreeNode? removalSentinel) {
|
||||
_staticTypeContext = StaticTypeContext(node, _typeEnvironment);
|
||||
return super.defaultMember(node, removalSentinel);
|
||||
}
|
||||
|
||||
@override
|
||||
TreeNode visitConditionalExpression(
|
||||
ConditionalExpression node, TreeNode? removalSentinel) {
|
||||
super.visitConditionalExpression(node, removalSentinel);
|
||||
final condition = _constantEvaluator.evaluateOrNull(
|
||||
_staticTypeContext, node.condition,
|
||||
requireConstant: false);
|
||||
if (condition is! BoolConstant) return node;
|
||||
return condition.value ? node.then : node.otherwise;
|
||||
}
|
||||
|
||||
@override
|
||||
TreeNode visitIfStatement(IfStatement node, TreeNode? removalSentinel) {
|
||||
super.visitIfStatement(node, removalSentinel);
|
||||
final condition = _constantEvaluator.evaluateOrNull(
|
||||
_staticTypeContext, node.condition,
|
||||
requireConstant: false);
|
||||
if (condition is! BoolConstant) return node;
|
||||
if (condition.value) {
|
||||
return node.then;
|
||||
} else {
|
||||
return node.otherwise ?? removalSentinel ?? EmptyStatement();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -260,7 +260,7 @@ main() {
|
||||
await test(TEST_12, 'return 6');
|
||||
await test(TEST_13, 'return 6');
|
||||
await test(TEST_14, 'return t1[0]');
|
||||
await test(TEST_15, 'return 42');
|
||||
await test(TEST_15, 'return \$.a = 42');
|
||||
await test(TEST_16, 'return \$.a');
|
||||
await test(TEST_17,
|
||||
RegExp(r'return (t1|\$\.x === 0 \? \$\.a = true : \$\.a = false);'));
|
||||
|
||||
@@ -33,33 +33,82 @@ main() {
|
||||
testAssertWithMessage();
|
||||
}
|
||||
|
||||
/*member: testIfThen:type=[
|
||||
/*member: flag:
|
||||
static=[
|
||||
Rti._bind(1),
|
||||
Rti._eval(1),
|
||||
_arrayInstanceType(1),
|
||||
_asBool(1),
|
||||
_asBoolQ(1),
|
||||
_asBoolS(1),
|
||||
_asDouble(1),
|
||||
_asDoubleQ(1),
|
||||
_asDoubleS(1),
|
||||
_asInt(1),
|
||||
_asIntQ(1),
|
||||
_asIntS(1),
|
||||
_asNum(1),
|
||||
_asNumQ(1),
|
||||
_asNumS(1),
|
||||
_asObject(1),
|
||||
_asString(1),
|
||||
_asStringQ(1),
|
||||
_asStringS(1),
|
||||
_asTop(1),
|
||||
_generalAsCheckImplementation(1),
|
||||
_generalIsTestImplementation(1),
|
||||
_generalNullableAsCheckImplementation(1),
|
||||
_generalNullableIsTestImplementation(1),
|
||||
_installSpecializedAsCheck(1),
|
||||
_installSpecializedIsTest(1),
|
||||
_instanceType(1),
|
||||
_isBool(1),
|
||||
_isInt(1),
|
||||
_isNum(1),
|
||||
_isObject(1),
|
||||
_isString(1),
|
||||
_isTop(1),
|
||||
findType(1),
|
||||
instanceType(1)],
|
||||
type=[
|
||||
inst:Closure,
|
||||
inst:JSBool,
|
||||
inst:JSNull,
|
||||
param:bool*]
|
||||
*/
|
||||
bool flag; // late
|
||||
|
||||
/*member: testIfThen:
|
||||
static=[set:flag],
|
||||
type=[
|
||||
inst:JSBool,
|
||||
inst:JSInt,
|
||||
inst:JSNumNotInt,
|
||||
inst:JSNumber,
|
||||
inst:JSPositiveInt,
|
||||
inst:JSUInt31,
|
||||
inst:JSUInt32]*/
|
||||
inst:JSUInt32]
|
||||
*/
|
||||
testIfThen() {
|
||||
// ignore: DEAD_CODE
|
||||
if (false) return 42;
|
||||
if (flag = false) return 42;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*member: testIfThenElse:type=[
|
||||
/*member: testIfThenElse:
|
||||
static=[set:flag],
|
||||
type=[
|
||||
inst:JSBool,
|
||||
inst:JSInt,
|
||||
inst:JSNumNotInt,
|
||||
inst:JSNumber,
|
||||
inst:JSPositiveInt,
|
||||
inst:JSUInt31,
|
||||
inst:JSUInt32]*/
|
||||
inst:JSUInt32]
|
||||
*/
|
||||
testIfThenElse() {
|
||||
if (true)
|
||||
if (flag = true)
|
||||
return 42;
|
||||
else
|
||||
// ignore: DEAD_CODE
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -560,12 +560,15 @@ testDoWhile2() {
|
||||
return a;
|
||||
}
|
||||
|
||||
/*member: flag:Value([null|exact=JSBool], value: true)*/
|
||||
bool flag; // late
|
||||
|
||||
/*member: testDoWhile3:Value([exact=JSBool], value: false)*/
|
||||
testDoWhile3() {
|
||||
dynamic a = 42;
|
||||
do {
|
||||
a = 'foo';
|
||||
if (true) continue;
|
||||
if (flag = true) continue;
|
||||
return false;
|
||||
} while (true);
|
||||
// ignore: dead_code
|
||||
@@ -577,7 +580,7 @@ testDoWhile4() {
|
||||
dynamic a = 'foo';
|
||||
do {
|
||||
a = 54;
|
||||
if (true) break;
|
||||
if (flag = true) break;
|
||||
return 3.5;
|
||||
} while (true);
|
||||
return a;
|
||||
@@ -686,11 +689,12 @@ class A {
|
||||
set myField(/*[subclass=JSUInt32]*/ a) {}
|
||||
|
||||
/*member: A.returnInt1:[subclass=JSUInt32]*/
|
||||
returnInt1() => /*invoke: [exact=JSUInt31]*/ ++ /*[subclass=A]*/ /*update: [subclass=A]*/ myField;
|
||||
returnInt1() => /*invoke: [exact=JSUInt31]*/
|
||||
++ /*[subclass=A]*/ /*update: [subclass=A]*/ myField;
|
||||
|
||||
/*member: A.returnInt2:[subclass=JSUInt32]*/
|
||||
returnInt2() => /*invoke: [exact=JSUInt31]*/ ++this
|
||||
. /*[subclass=A]*/ /*update: [subclass=A]*/ myField;
|
||||
returnInt2() => /*invoke: [exact=JSUInt31]*/
|
||||
++this. /*[subclass=A]*/ /*update: [subclass=A]*/ myField;
|
||||
|
||||
/*member: A.returnInt3:[subclass=JSUInt32]*/
|
||||
returnInt3() =>
|
||||
@@ -698,8 +702,8 @@ class A {
|
||||
42;
|
||||
|
||||
/*member: A.returnInt4:[subclass=JSUInt32]*/
|
||||
returnInt4() => /*[subclass=A]*/ /*update: [subclass=A]*/ myField /*invoke: [exact=JSUInt31]*/ +=
|
||||
42;
|
||||
returnInt4() => /*[subclass=A]*/ /*update: [subclass=A]*/
|
||||
myField /*invoke: [exact=JSUInt31]*/ += 42;
|
||||
|
||||
/*member: A.[]:[exact=JSUInt31]*/
|
||||
operator [](/*[exact=JSUInt31]*/ index) => 42;
|
||||
@@ -708,8 +712,8 @@ class A {
|
||||
operator []=(/*[exact=JSUInt31]*/ index, /*[subclass=JSUInt32]*/ value) {}
|
||||
|
||||
/*member: A.returnInt5:[subclass=JSUInt32]*/
|
||||
returnInt5() => /*invoke: [exact=JSUInt31]*/ ++this /*[subclass=A]*/ /*update: [subclass=A]*/ [
|
||||
0];
|
||||
returnInt5() => /*invoke: [exact=JSUInt31]*/
|
||||
++this /*[subclass=A]*/ /*update: [subclass=A]*/ [0];
|
||||
|
||||
/*member: A.returnInt6:[subclass=JSUInt32]*/
|
||||
returnInt6() => this /*[subclass=A]*/ /*update: [subclass=A]*/ [
|
||||
@@ -724,16 +728,16 @@ class B extends A {
|
||||
B() : super.generative();
|
||||
|
||||
/*member: B.returnInt1:[subclass=JSUInt32]*/
|
||||
returnInt1() => /*invoke: [exact=JSUInt31]*/ ++new A()
|
||||
. /*[exact=A]*/ /*update: [exact=A]*/ myField;
|
||||
returnInt1() => /*invoke: [exact=JSUInt31]*/
|
||||
++new A(). /*[exact=A]*/ /*update: [exact=A]*/ myField;
|
||||
|
||||
/*member: B.returnInt2:[subclass=JSUInt32]*/
|
||||
returnInt2() => new A()
|
||||
. /*[exact=A]*/ /*update: [exact=A]*/ myField /*invoke: [exact=JSUInt31]*/ += 4;
|
||||
|
||||
/*member: B.returnInt3:[subclass=JSUInt32]*/
|
||||
returnInt3() => /*invoke: [exact=JSUInt31]*/ ++new A() /*[exact=A]*/ /*update: [exact=A]*/ [
|
||||
0];
|
||||
returnInt3() => /*invoke: [exact=JSUInt31]*/
|
||||
++new A() /*[exact=A]*/ /*update: [exact=A]*/ [0];
|
||||
|
||||
/*member: B.returnInt4:[subclass=JSUInt32]*/
|
||||
returnInt4() => new A() /*[exact=A]*/ /*update: [exact=A]*/ [
|
||||
@@ -763,11 +767,12 @@ class C {
|
||||
C();
|
||||
|
||||
/*member: C.returnInt1:[subclass=JSPositiveInt]*/
|
||||
returnInt1() => /*invoke: [subclass=JSPositiveInt]*/ ++ /*update: [exact=C]*/ /*[exact=C]*/ myField;
|
||||
returnInt1() => /*invoke: [subclass=JSPositiveInt]*/
|
||||
++ /*update: [exact=C]*/ /*[exact=C]*/ myField;
|
||||
|
||||
/*member: C.returnInt2:[subclass=JSPositiveInt]*/
|
||||
returnInt2() => /*invoke: [subclass=JSPositiveInt]*/ ++this
|
||||
. /*[exact=C]*/ /*update: [exact=C]*/ myField;
|
||||
returnInt2() => /*invoke: [subclass=JSPositiveInt]*/
|
||||
++this. /*[exact=C]*/ /*update: [exact=C]*/ myField;
|
||||
|
||||
/*member: C.returnInt3:[subclass=JSPositiveInt]*/
|
||||
returnInt3() =>
|
||||
@@ -775,8 +780,8 @@ class C {
|
||||
42;
|
||||
|
||||
/*member: C.returnInt4:[subclass=JSPositiveInt]*/
|
||||
returnInt4() => /*[exact=C]*/ /*update: [exact=C]*/ myField /*invoke: [subclass=JSPositiveInt]*/ +=
|
||||
42;
|
||||
returnInt4() => /*[exact=C]*/ /*update: [exact=C]*/
|
||||
myField /*invoke: [subclass=JSPositiveInt]*/ += 42;
|
||||
|
||||
/*member: C.[]:[subclass=JSPositiveInt]*/
|
||||
operator [](/*[exact=JSUInt31]*/ index) => /*[exact=C]*/ myField;
|
||||
@@ -786,8 +791,8 @@ class C {
|
||||
/*[exact=JSUInt31]*/ index, /*[subclass=JSPositiveInt]*/ value) {}
|
||||
|
||||
/*member: C.returnInt5:[subclass=JSPositiveInt]*/
|
||||
returnInt5() => /*invoke: [subclass=JSPositiveInt]*/ ++this /*[exact=C]*/ /*update: [exact=C]*/ [
|
||||
0];
|
||||
returnInt5() => /*invoke: [subclass=JSPositiveInt]*/
|
||||
++this /*[exact=C]*/ /*update: [exact=C]*/ [0];
|
||||
|
||||
/*member: C.returnInt6:[subclass=JSPositiveInt]*/
|
||||
returnInt6() => this /*[exact=C]*/ /*update: [exact=C]*/ [
|
||||
|
||||
@@ -136,11 +136,14 @@ conditionalCondition(/*[null|subclass=Object]*/ o) {
|
||||
// Access both branches of a conditional expression.
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/*member: flag:Value([null|exact=JSBool], value: true)*/
|
||||
bool flag; // late
|
||||
|
||||
/*member: conditionalBothBranches:[null]*/
|
||||
@pragma('dart2js:assumeDynamic')
|
||||
conditionalBothBranches(/*[null|subclass=Object]*/ o) {
|
||||
// ignore: DEAD_CODE
|
||||
true ? o.field : o.field;
|
||||
(flag = true) ? o.field : o.field;
|
||||
o. /*[subclass=Object]*/ field;
|
||||
}
|
||||
|
||||
@@ -152,7 +155,7 @@ conditionalBothBranches(/*[null|subclass=Object]*/ o) {
|
||||
@pragma('dart2js:assumeDynamic')
|
||||
conditionalOneBranchOnly(/*[null|subclass=Object]*/ o) {
|
||||
// ignore: DEAD_CODE
|
||||
true ? o.field : null;
|
||||
(flag = true) ? o.field : null;
|
||||
o.field;
|
||||
o. /*[subclass=Object]*/ field;
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ main() {
|
||||
await runTest('tests/web_2/jsinterop_test.dart', '', {
|
||||
'Class': Kind.regular,
|
||||
'JsInteropClass': Kind.jsInterop,
|
||||
'flag': Kind.regular,
|
||||
'topLevelField': Kind.regular,
|
||||
'topLevelGetter': Kind.regular,
|
||||
'topLevelSetter': Kind.regular,
|
||||
@@ -81,6 +82,7 @@ main() {
|
||||
await runTest('tests/web_2/non_jsinterop_test.dart', '', {
|
||||
'Class': Kind.regular,
|
||||
'JsInteropClass': Kind.jsInterop,
|
||||
'flag': Kind.regular,
|
||||
'topLevelField': Kind.regular,
|
||||
'topLevelGetter': Kind.regular,
|
||||
'topLevelSetter': Kind.regular,
|
||||
|
||||
@@ -239,8 +239,10 @@ class JsInteropClass {
|
||||
external static externalStaticJsInteropMethod();
|
||||
}
|
||||
|
||||
late bool flag;
|
||||
|
||||
main() {
|
||||
if (false) {
|
||||
if (flag = false) {
|
||||
topLevelSetter = topLevelField = topLevelGetter;
|
||||
topLevelFunction();
|
||||
externalTopLevelSetter = externalTopLevelGetter;
|
||||
|
||||
@@ -238,8 +238,10 @@ class JsInteropClass {
|
||||
external static externalStaticJsInteropMethod();
|
||||
}
|
||||
|
||||
late bool flag;
|
||||
|
||||
main() {
|
||||
if (false) {
|
||||
if (flag = false) {
|
||||
topLevelSetter = topLevelField = topLevelGetter;
|
||||
topLevelFunction();
|
||||
externalTopLevelJsInteropSetter = externalTopLevelJsInteropGetter;
|
||||
|
||||
@@ -241,8 +241,10 @@ class JsInteropClass {
|
||||
external static externalStaticJsInteropMethod();
|
||||
}
|
||||
|
||||
bool flag;
|
||||
|
||||
main() {
|
||||
if (false) {
|
||||
if (flag = false) {
|
||||
topLevelSetter = topLevelField = topLevelGetter;
|
||||
topLevelFunction();
|
||||
externalTopLevelSetter = externalTopLevelGetter;
|
||||
|
||||
@@ -240,8 +240,10 @@ class JsInteropClass {
|
||||
external static externalStaticJsInteropMethod();
|
||||
}
|
||||
|
||||
bool flag;
|
||||
|
||||
main() {
|
||||
if (false) {
|
||||
if (flag = false) {
|
||||
topLevelSetter = topLevelField = topLevelGetter;
|
||||
topLevelFunction();
|
||||
externalTopLevelJsInteropSetter = externalTopLevelJsInteropGetter;
|
||||
|
||||
Reference in New Issue
Block a user