Version 3.9.0-190.0.dev
Merge c3496701e4ad0f8ec26050982aab860d25bbe8f3 into dev
diff --git a/DEPS b/DEPS
index b73c1ed..77a1e36 100644
--- a/DEPS
+++ b/DEPS
@@ -78,13 +78,13 @@
"clang_version": "git_revision:8c7a2ce01a77c96028fe2c8566f65c45ad9408d3",
# https://chrome-infra-packages.appspot.com/p/gn/gn
- "gn_version": "git_revision:ebc8f16ca7b0d36a3e532ee90896f9eb48e5423b",
+ "gn_version": "git_revision:afd24ed11bc5fbef775a3ffe46c72e6bdca0fa60",
"reclient_version": "re_client_version:0.178.0.5ee9d3e8-gomaip",
"download_reclient": True,
# Update from https://chrome-infra-packages.appspot.com/p/fuchsia/sdk/core
- "fuchsia_sdk_version": "version:28.20250519.5.1",
+ "fuchsia_sdk_version": "version:28.20250528.4.1",
"download_fuchsia_deps": False,
# Ninja, runs the build based on files generated by GN.
@@ -577,7 +577,7 @@
"packages": [
{
"package": "chromium/fuchsia/test-scripts",
- "version": "L6SMsr0eTyQlN6KR4VWrJ1Kx7DLrq0yWL1u8XbmfzuAC",
+ "version": "DG11ez0PRGu8loi2mYSO_7_iW5KrUS9IEvwM9ZynXH4C",
}
],
"condition": 'download_fuchsia_deps',
diff --git a/pkg/compiler/lib/src/kernel/transformations/modular/late_lowering.dart b/pkg/compiler/lib/src/kernel/transformations/modular/late_lowering.dart
index 4dd98cb..1d3ee75 100644
--- a/pkg/compiler/lib/src/kernel/transformations/modular/late_lowering.dart
+++ b/pkg/compiler/lib/src/kernel/transformations/modular/late_lowering.dart
@@ -76,6 +76,10 @@
final ExtensionTable _extensionTable = ExtensionTable();
+ late final InstanceConstant pragmaAllowCSE = _pragmaConstant(
+ 'dart2js:allow-cse',
+ );
+
LateLowering(this._coreTypes, CompilerOptions? _options)
: _omitLateNames = _options?.omitLateNames ?? false,
_readLocal = _Reader(_coreTypes.cellReadLocal),
@@ -674,6 +678,12 @@
// transformer flags to reflect whether the getter contains super calls.
getter.transformerFlags = field.transformerFlags;
_copyAnnotations(getter, field);
+ if (initializer != null && field.isFinal) {
+ getter.addAnnotation(
+ ConstantExpression(pragmaAllowCSE, _coreTypes.pragmaNonNullableRawType)
+ ..fileOffset = field.fileOffset,
+ );
+ }
enclosingClass.addProcedure(getter);
VariableDeclaration setterValue = VariableDeclaration(
@@ -750,6 +760,13 @@
}
}
+ InstanceConstant _pragmaConstant(String pragmaName) {
+ return InstanceConstant(_coreTypes.pragmaClass.reference, [], {
+ _coreTypes.pragmaName.fieldReference: StringConstant(pragmaName),
+ _coreTypes.pragmaOptions.fieldReference: NullConstant(),
+ });
+ }
+
TreeNode transformField(Field field, Member contextMember) {
_contextMember = contextMember;
diff --git a/pkg/compiler/lib/src/ssa/builder.dart b/pkg/compiler/lib/src/ssa/builder.dart
index 56ece5e..fe211a7 100644
--- a/pkg/compiler/lib/src/ssa/builder.dart
+++ b/pkg/compiler/lib/src/ssa/builder.dart
@@ -7873,11 +7873,19 @@
return false;
}
- // Don't inline functions marked with 'allow-cse' and 'allow-dce' since we
- // need the call instruction to do these optimizations. We might be able to
+ // Don't inline functions marked with 'allow-dce' since we need the call
+ // instruction to recognize the whole call as unused. We might be able to
// inline simple methods afterwards.
- if (closedWorld.annotationsData.allowCSE(function) ||
- closedWorld.annotationsData.allowDCE(function)) {
+ if (closedWorld.annotationsData.allowDCE(function)) {
+ return false;
+ }
+
+ // Don't inline functions marked with 'allow-cse' since we need the call
+ // instructions to recognize repeated calls. We might be able to inline
+ // simple methods afterwards. If this is the only call site, we will never
+ // find the repeated call, so we should consider inlining here.
+ if (closedWorld.annotationsData.allowCSE(function) &&
+ !_isCalledOnce(function)) {
return false;
}
diff --git a/pkg/compiler/lib/src/ssa/nodes.dart b/pkg/compiler/lib/src/ssa/nodes.dart
index 636be70..ee060bd 100644
--- a/pkg/compiler/lib/src/ssa/nodes.dart
+++ b/pkg/compiler/lib/src/ssa/nodes.dart
@@ -1344,6 +1344,9 @@
bool hasSameType = typeEquals(other);
assert(hasSameType == (_gvnType == other._gvnType));
if (!hasSameType) return false;
+ // Check the data first to ensure we are considering the same element or
+ // selector.
+ if (!dataEquals(other)) return false;
assert((useGvn() && other.useGvn()) || (allowCSE && other.allowCSE));
if (sideEffects != other.sideEffects) return false;
// Check that the inputs match.
@@ -1355,8 +1358,7 @@
return false;
}
}
- // Check that the data in the instruction matches.
- return dataEquals(other);
+ return true;
}
int gvnHashCode() {
@@ -2092,13 +2094,8 @@
@override
bool dataEquals(HInvokeDynamic other) {
- // Use the name and the kind instead of [Selector.operator==]
- // because we don't need to check the arity (already checked in
- // [gvnEquals]), and the receiver types may not be in sync.
- // TODO(sra): If we GVN calls with named (optional) arguments then the
- // selector needs a deeper check for the same subset of named arguments.
- return selector.name == other.selector.name &&
- selector.kind == other.selector.kind;
+ return selector == other.selector &&
+ (useGvn() == other.useGvn() || allowCSE == other.allowCSE);
}
}
diff --git a/pkg/compiler/lib/src/ssa/optimize.dart b/pkg/compiler/lib/src/ssa/optimize.dart
index 7eebfbf..792d797 100644
--- a/pkg/compiler/lib/src/ssa/optimize.dart
+++ b/pkg/compiler/lib/src/ssa/optimize.dart
@@ -2,6 +2,8 @@
// 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:collection' show Queue;
+
import 'package:js_runtime/synced/array_flags.dart' show ArrayFlags;
import '../common.dart';
@@ -4150,15 +4152,11 @@
HInstruction input = node.checkedInput;
if (input.usedBy.length <= 1) return; // No other uses to refine.
- List<HBasicBlock> trueTargets = [];
- List<HBasicBlock> falseTargets = [];
-
- collectTargets(node, trueTargets, falseTargets);
-
- if (trueTargets.isEmpty && falseTargets.isEmpty) return;
+ final targets = ConditionTargets(node);
+ if (targets.isEmpty) return;
AbstractValue whenTrueType = node.checkedAbstractValue.abstractValue;
- insertTypeRefinements(trueTargets, input, whenTrueType);
+ insertTypeRefinements(targets.whenTrue, input, whenTrueType);
// TODO(sra): Also strengthen uses for when the condition is precise and
// known false (e.g. int? x; ... if (x is! int) use(x)). Avoid strengthening
// to `null`.
@@ -4169,15 +4167,11 @@
HInstruction input = node.checkedInput;
if (input.usedBy.length <= 1) return; // No other uses to refine.
- List<HBasicBlock> trueTargets = [];
- List<HBasicBlock> falseTargets = [];
-
- collectTargets(node, trueTargets, falseTargets);
-
- if (trueTargets.isEmpty && falseTargets.isEmpty) return;
+ final targets = ConditionTargets(node);
+ if (targets.isEmpty) return;
AbstractValue whenTrueType = node.checkedAbstractValue.abstractValue;
- insertTypeRefinements(trueTargets, input, whenTrueType);
+ insertTypeRefinements(targets.whenTrue, input, whenTrueType);
// TODO(sra): Also strengthen uses for when the condition is precise and
// known false (e.g. int? x; ... if (x is! int) use(x)). Avoid strengthening
// to `null`.
@@ -4204,17 +4198,13 @@
return;
}
- List<HBasicBlock> trueTargets = [];
- List<HBasicBlock> falseTargets = [];
-
- collectTargets(node, trueTargets, falseTargets);
-
- if (trueTargets.isEmpty && falseTargets.isEmpty) return;
+ final targets = ConditionTargets(node);
+ if (targets.isEmpty) return;
AbstractValue nonNullType = _abstractValueDomain.excludeNull(
input.instructionType,
);
- insertTypeRefinements(falseTargets, input, nonNullType);
+ insertTypeRefinements(targets.whenFalse, input, nonNullType);
// We don't strengthen the known-true references. It doesn't happen often
// and we don't want "if (x==null) return x;" to convert between JavaScript
// 'null' and 'undefined'.
@@ -4225,26 +4215,92 @@
final input = node.inputs.single;
if (input.usedBy.length <= 1) return; // No other uses to refine.
- List<HBasicBlock> trueTargets = [];
- List<HBasicBlock> falseTargets = [];
-
- collectTargets(node, trueTargets, falseTargets);
-
- if (trueTargets.isEmpty && falseTargets.isEmpty) return;
+ final targets = ConditionTargets(node);
+ if (targets.isEmpty) return;
final sentinelType = _abstractValueDomain.lateSentinelType;
final nonSentinelType = _abstractValueDomain.excludeLateSentinel(
input.instructionType,
);
- insertTypeRefinements(trueTargets, input, sentinelType);
- insertTypeRefinements(falseTargets, input, nonSentinelType);
+ insertTypeRefinements(targets.whenTrue, input, sentinelType);
+ insertTypeRefinements(targets.whenFalse, input, nonSentinelType);
+ }
+}
+
+typedef _Step = (HInstruction, {_Targets? whenTrue, _Targets? whenFalse});
+
+/// [ConditionTargets] collects the target blocks for a condition. The target
+/// blocks are blocks reachable only when the condition is true (`whenTrue`) and
+/// blocks reachable only when the condition is false (`whenFalse`).
+///
+/// The algorithm starts with an initial condition instruction and two empty
+/// sets of targets, [_trueTargets] and [_falseTargets]. If the condition is
+/// used in HIf control flow, we know the condition is true in the then-block
+/// and false in the else-block.
+///
+/// If the condition is used by HNot, we can infer that when the HNot is true,
+/// the condition was false and the trueTargets of the HNot are the falseTargets
+/// of the original condition. The HNot is added to a work queue with flipped
+/// targets.
+///
+/// If the condition is used in a phi, or a HIf controlling a phi, depending on
+/// how the condition is used, sometimes when the phi is true or false, we can
+/// infer something about the original condition's targets.
+///
+/// `whenTrue:` and `whenFalse` each have three possible values, (1)
+/// _trueTargets, (2) _falseTargets, and (3) `null`.
+class ConditionTargets {
+ /// For the visited set, only one of `whenTrue:` and `whenFalse:` used, the
+ /// other is `null`. It is unusual, but possible to visit both of:
+ ///
+ /// (cond, whenTrue: _trueTargets, whenFalse: null)
+ /// (cond, whenTrue: _falseTargets, whenFalse: null)
+ ///
+ /// This is a contradition (cond both must be true and also must be
+ /// false). Contradictions happen only in unreachable code.
+ final Set<_Step> _visited = {};
+
+ final Queue<_Step> _queue = Queue();
+
+ final _Targets _trueTargets = _Targets();
+ final _Targets _falseTargets = _Targets();
+
+ ConditionTargets(HInstruction condition) {
+ _add(condition, _trueTargets, _falseTargets);
+ while (_queue.isNotEmpty) {
+ _step();
+ }
}
- void collectTargets(
- HInstruction instruction,
- List<HBasicBlock>? trueTargets,
- List<HBasicBlock>? falseTargets,
- ) {
+ bool get isEmpty => _trueTargets.isEmpty && _falseTargets.isEmpty;
+
+ List<HBasicBlock> get whenTrue => _trueTargets.blocks;
+ List<HBasicBlock> get whenFalse => _falseTargets.blocks;
+
+ void _add(HInstruction node, _Targets? trueTargets, _Targets? falseTargets) {
+ if (trueTargets == null && falseTargets == null) return;
+ _queue.add((node, whenTrue: trueTargets, whenFalse: falseTargets));
+ }
+
+ void _step() {
+ var (instruction, whenTrue: trueTargets, whenFalse: falseTargets) = _queue
+ .removeFirst();
+
+ // The same instruction (I) can be reached on different paths with different
+ // combinations of trueTargets (T) and falseTargets (F). Filling in the
+ // targets for problem (I,T,F) is separable into processing (I,T,null) and
+ // (I,null,F), so we check if we have visited this instruction before by
+ // querying trueTargets and falseTargets independently, and remove the
+ // separable subproblem that already has been solved.
+ if (trueTargets != null &&
+ !_visited.add((instruction, whenTrue: trueTargets, whenFalse: null))) {
+ trueTargets = null;
+ }
+ if (falseTargets != null &&
+ !_visited.add((instruction, whenTrue: null, whenFalse: falseTargets))) {
+ falseTargets = null;
+ }
+
if (trueTargets == null && falseTargets == null) return;
for (HInstruction user in instruction.usedBy) {
@@ -4278,22 +4334,22 @@
if (right.isConstantFalse()) {
// When `c ? x : false` is true, `c` must be true.
// So pass `c`'s trueTargets as the phi's trueTargets.
- collectTargets(phi, trueTargets, null);
+ _add(phi, trueTargets, null);
} else if (right.isConstantTrue()) {
// When `c ? x : true` is false, `c` must be true.
// So pass `c`'s trueTargets as the phi's falseTargets.
- collectTargets(phi, null, trueTargets);
+ _add(phi, null, trueTargets);
}
final left = phi.inputs[0];
if (left.isConstantFalse()) {
// When `c ? false : x` is true, `c` must be false.
// So pass `c`'s falseTargets as the phi's trueTargets.
- collectTargets(phi, falseTargets, null);
+ _add(phi, falseTargets, null);
} else if (left.isConstantTrue()) {
// When `c ? true : x` is false, `c` must be false.
// So pass `c`'s falseTargets as the phi's falseTargets.
- collectTargets(phi, null, falseTargets);
+ _add(phi, null, falseTargets);
}
// Sanity checks:
@@ -4301,14 +4357,14 @@
// For `c ? true : false`, we pass both `c`'s trueTargets and
// falseTargets as the same targets of the phi.
//
- // For `c ? false : true`, we pass the targets reversed, like we
- // for `HNot`.
+ // For `c ? false : true`, we pass the targets reversed, like
+ // we do for `HNot`.
//
// For `c ? false : false`, we pass both `c`'s trueTargets and
// falseTargets to the unreachable trueTargets of the phi. We
- // might insert contradictory strengthenings, which might refine
- // a value to Never, i.e. we potentially 'prove' the code is
- // unreachable.
+ // might insert contradictory strengthenings, which might
+ // refine a value to Never, i.e. we potentially 'prove' the
+ // code is unreachable.
}
}
}
@@ -4318,21 +4374,21 @@
// Don't insert refinements on else-branch - may be a critical edge
// block which we currently need to keep empty (except for phis).
} else if (user is HNot) {
- collectTargets(user, falseTargets, trueTargets);
+ _add(user, falseTargets, trueTargets);
} else if (user is HPhi) {
List<HInstruction> inputs = user.inputs;
if (inputs.length == 2) {
assert(inputs.contains(instruction));
HInstruction other = inputs[(inputs[0] == instruction) ? 1 : 0];
if (other.isConstantTrue()) {
- // The condition flows to `HPhi(true, user)` or `HPhi(user, true)`,
+ // The condition flows to `HPhi(true,user)` or `HPhi(user,true)`,
// which means that a downstream HIf has true-branch control flow
// that does not depend on the original instruction, so stop
// collecting [trueTargets].
- collectTargets(user, null, falseTargets);
+ _add(user, null, falseTargets);
} else if (other.isConstantFalse()) {
// Ditto for false.
- collectTargets(user, trueTargets, null);
+ _add(user, trueTargets, null);
}
}
}
@@ -4340,6 +4396,13 @@
}
}
+class _Targets {
+ final List<HBasicBlock> _blocks = [];
+ bool get isEmpty => _blocks.isEmpty;
+ void add(HBasicBlock block) => _blocks.add(block);
+ List<HBasicBlock> get blocks => _blocks;
+}
+
/// Optimization phase that tries to eliminate memory loads (for example
/// [HFieldGet]), when it knows the value stored in that memory location, and
/// stores that overwrite with the same value.
diff --git a/pkg/compiler/test/codegen/data/promotion2.dart b/pkg/compiler/test/codegen/data/promotion2.dart
new file mode 100644
index 0000000..bbf30a6
--- /dev/null
+++ b/pkg/compiler/test/codegen/data/promotion2.dart
@@ -0,0 +1,30 @@
+// Copyright (c) 2025, 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.
+
+// Examples where null checks should be removed.
+
+/*member: test1:function(a) {
+ var b, b0, i;
+ for (b = a != null, b0 = false, i = 0; ++i, i < 3; b0 = b)
+ if (b0)
+ B.JSArray_methods.get$first(a);
+}*/
+void test1(List<Object>? a) {
+ bool b = false;
+ int i = 0;
+ // The null check is guarded by `b = false;` in the initial iteration.
+ while (++i < 3) {
+ if (b) sink = a!.first;
+ b = a != null;
+ }
+}
+
+Object? sink;
+
+/*member: main:ignore*/
+main() {
+ test1(null);
+ test1([1, 2]);
+ test1(['x']);
+}
diff --git a/pkg/front_end/testcases/dart2js/late_fields.dart.strong.transformed.expect b/pkg/front_end/testcases/dart2js/late_fields.dart.strong.transformed.expect
index cabb345..590ece1 100644
--- a/pkg/front_end/testcases/dart2js/late_fields.dart.strong.transformed.expect
+++ b/pkg/front_end/testcases/dart2js/late_fields.dart.strong.transformed.expect
@@ -32,6 +32,7 @@
}
set c(synthesized core::int value) → void
this.{self::C::_#C#c#AI} = value;
+ @#C3
get d() → core::int {
synthesized core::int value = this.{self::C::_#C#d#FI}{core::int};
if(_in::isSentinel(value)) {
@@ -68,6 +69,11 @@
core::print(self::c.{self::C::d}{core::int});
}
+constants {
+ #C1 = "dart2js:allow-cse"
+ #C2 = null
+ #C3 = core::pragma {name:#C1, options:#C2}
+}
Extra constant evaluation status:
Evaluated: InstanceInvocation @ org-dartlang-testcase:///late_fields.dart:15:16 -> DoubleConstant(-1.0)
diff --git a/pkg/front_end/testcases/dart2js/late_fields_with_annotation.dart.strong.transformed.expect b/pkg/front_end/testcases/dart2js/late_fields_with_annotation.dart.strong.transformed.expect
index 1228214..cb5c17d 100644
--- a/pkg/front_end/testcases/dart2js/late_fields_with_annotation.dart.strong.transformed.expect
+++ b/pkg/front_end/testcases/dart2js/late_fields_with_annotation.dart.strong.transformed.expect
@@ -42,6 +42,7 @@
this.{self::C::_#C#c#AI} = value;
@#C5
@#C9
+ @#C11
get d() → core::int {
synthesized core::int value = this.{self::C::_#C#d#FI}{core::int};
if(_in::isSentinel(value)) {
@@ -88,6 +89,8 @@
#C7 = core::pragma {name:#C6, options:#C2}
#C8 = "dart2js:noInline"
#C9 = core::pragma {name:#C8, options:#C2}
+ #C10 = "dart2js:allow-cse"
+ #C11 = core::pragma {name:#C10, options:#C2}
}
Extra constant evaluation status:
diff --git a/pkg/front_end/testcases/dart2js/late_from_dill/main.dart.strong.expect b/pkg/front_end/testcases/dart2js/late_from_dill/main.dart.strong.expect
index 2d935e1..053ab4b 100644
--- a/pkg/front_end/testcases/dart2js/late_from_dill/main.dart.strong.expect
+++ b/pkg/front_end/testcases/dart2js/late_from_dill/main.dart.strong.expect
@@ -119,6 +119,7 @@
}
set c(synthesized core::int value) → void
this.{mai::C::_#C#c#AI} = value;
+ @#C3
get d() → core::int {
synthesized core::int value = this.{mai::C::_#C#d#FI}{core::int};
if(_in::isSentinel(value)) {
@@ -212,3 +213,9 @@
return mai2::_#b.{_la::_Cell::readField}<core::int>(){() → core::int};
static set b(synthesized core::int value) → void
return mai2::_#b.{_la::_Cell::finalFieldValue} = value;
+
+constants {
+ #C1 = "dart2js:allow-cse"
+ #C2 = null
+ #C3 = core::pragma {name:#C1, options:#C2}
+}
diff --git a/pkg/front_end/testcases/dart2js/late_from_dill/main.dart.strong.transformed.expect b/pkg/front_end/testcases/dart2js/late_from_dill/main.dart.strong.transformed.expect
index 4128801..0d24073 100644
--- a/pkg/front_end/testcases/dart2js/late_from_dill/main.dart.strong.transformed.expect
+++ b/pkg/front_end/testcases/dart2js/late_from_dill/main.dart.strong.transformed.expect
@@ -119,6 +119,7 @@
}
set c(synthesized core::int value) → void
this.{mai::C::_#C#c#AI} = value;
+ @#C3
get d() → core::int {
synthesized core::int value = this.{mai::C::_#C#d#FI}{core::int};
if(_in::isSentinel(value)) {
@@ -213,6 +214,11 @@
static set b(synthesized core::int value) → void
return mai2::_#b.{_la::_Cell::finalFieldValue} = value;
+constants {
+ #C1 = "dart2js:allow-cse"
+ #C2 = null
+ #C3 = core::pragma {name:#C1, options:#C2}
+}
Extra constant evaluation status:
Evaluated: InstanceInvocation @ org-dartlang-testcase:///main_lib1.dart:8:16 -> DoubleConstant(-1.0)
diff --git a/tools/VERSION b/tools/VERSION
index 91c446d..bcce01c 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
MAJOR 3
MINOR 9
PATCH 0
-PRERELEASE 189
+PRERELEASE 190
PRERELEASE_PATCH 0