[vm/compiler] Handle _GrowableList.length as a symbolic boundary in range analysis
TEST=runtime/tests/vm/dart/regress_56099_il_test.dart
Fixes https://github.com/dart-lang/sdk/issues/56099
Change-Id: Ie078bfbc547e2aaa078144802c2ddfd00e0ec3aa
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/377060
Reviewed-by: Tess Strickland <sstrickl@google.com>
Commit-Queue: Alexander Markov <alexmarkov@google.com>
diff --git a/runtime/tests/vm/dart/regress_56099_il_test.dart b/runtime/tests/vm/dart/regress_56099_il_test.dart
new file mode 100644
index 0000000..fa34a16
--- /dev/null
+++ b/runtime/tests/vm/dart/regress_56099_il_test.dart
@@ -0,0 +1,69 @@
+// Copyright (c) 2024, 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.
+
+// Verifies that bounds check is removed from _GrowableList.add.
+
+import 'package:vm/testing/il_matchers.dart';
+
+@pragma('vm:never-inline')
+@pragma('vm:testing:print-flow-graph')
+void addElement(List<int> list, int value) {
+ list.add(value);
+}
+
+void main() {
+ addElement([], int.parse('42'));
+}
+
+void matchIL$addElement(FlowGraph graph) {
+ if (is32BitConfiguration) {
+ return;
+ }
+ graph.dump();
+ graph.match([
+ match.block('Graph', [
+ 'c_null' << match.Constant(value: null),
+ 'c_one' << match.UnboxedConstant(value: 1),
+ ]),
+ match.block('Function', [
+ 'list' << match.Parameter(index: 0),
+ 'value' << match.Parameter(index: 1),
+ match.CheckStackOverflow(),
+ 'list.length' <<
+ match.LoadField('list', slot: 'GrowableObjectArray.length'),
+ 'list.data' << match.LoadField('list', slot: 'GrowableObjectArray.data'),
+ 'capacity' << match.LoadField('list.data', slot: 'Array.length'),
+ 'length_unboxed' << match.UnboxInt64('list.length'),
+ 'capacity_unboxed' << match.UnboxInt64('capacity'),
+ match.Branch(
+ match.EqualityCompare('length_unboxed', 'capacity_unboxed',
+ kind: '=='),
+ ifTrue: 'B5',
+ ifFalse: 'B6'),
+ ]),
+ 'B5' <<
+ match.block('Target', [
+ match.StaticCall(),
+ match.Goto('B7'),
+ ]),
+ 'B6' <<
+ match.block('Target', [
+ match.Goto('B7'),
+ ]),
+ 'B7' <<
+ match.block('Join', [
+ 'length_plus_1' << match.BinaryInt64Op('length_unboxed', 'c_one'),
+ 'length_plus_1_boxed' << match.BoxInt64('length_plus_1'),
+ match.StoreField('list', 'length_plus_1_boxed',
+ slot: 'GrowableObjectArray.length'),
+ // No bounds check here.
+ 'list.data_v2' <<
+ match.LoadField('list',
+ slot: 'GrowableObjectArray.data', skipUntilMatched: false),
+ 'value_boxed' << match.BoxInt64('value', skipUntilMatched: false),
+ match.StoreIndexed('list.data_v2', 'length_unboxed', 'value_boxed'),
+ match.DartReturn('c_null'),
+ ]),
+ ]);
+}
diff --git a/runtime/vm/compiler/backend/il.cc b/runtime/vm/compiler/backend/il.cc
index 0316283..3889614 100644
--- a/runtime/vm/compiler/backend/il.cc
+++ b/runtime/vm/compiler/backend/il.cc
@@ -582,11 +582,11 @@
}
}
-bool Definition::IsArrayLength(Definition* def) {
+bool Definition::IsLengthLoad(Definition* def) {
if (def != nullptr) {
if (auto load = def->OriginalDefinitionIgnoreBoxingAndConstraints()
->AsLoadField()) {
- return load->IsImmutableLengthLoad();
+ return load->slot().IsLengthSlot();
}
}
return false;
diff --git a/runtime/vm/compiler/backend/il.h b/runtime/vm/compiler/backend/il.h
index 49220279..303c592 100644
--- a/runtime/vm/compiler/backend/il.h
+++ b/runtime/vm/compiler/backend/il.h
@@ -2677,8 +2677,9 @@
// boxing/unboxing and constraint instructions.
Definition* OriginalDefinitionIgnoreBoxingAndConstraints();
- // Helper method to determine if definition denotes an array length.
- static bool IsArrayLength(Definition* def);
+ // Helper method to determine if definition denotes a load of
+ // length of array/growable array/string/typed data/type arguments vector.
+ static bool IsLengthLoad(Definition* def);
virtual Definition* AsDefinition() { return this; }
virtual const Definition* AsDefinition() const { return this; }
diff --git a/runtime/vm/compiler/backend/loops.cc b/runtime/vm/compiler/backend/loops.cc
index 6dd395c..ede1273 100644
--- a/runtime/vm/compiler/backend/loops.cc
+++ b/runtime/vm/compiler/backend/loops.cc
@@ -149,7 +149,7 @@
} else if (InductionVar::IsConstant(max, &end)) {
return end < kMaxInt64;
} else if (InductionVar::IsInvariant(max) && max->mult() == 1 &&
- Definition::IsArrayLength(max->def())) {
+ Definition::IsLengthLoad(max->def())) {
return max->offset() < 0; // a.length - C, C > 0
}
}
@@ -184,7 +184,7 @@
success = (l <= u);
} else if (InductionVar::IsInvariant(upper_bound) &&
upper_bound->mult() == 1 &&
- Definition::IsArrayLength(upper_bound->def())) {
+ Definition::IsLengthLoad(upper_bound->def())) {
// No arithmetic wrap-around on the lower bound, and a properly
// non-positive offset on an array length, which is always >= 0.
const int64_t c = upper_bound->offset() + upper_bound_offset;
diff --git a/runtime/vm/compiler/backend/range_analysis.cc b/runtime/vm/compiler/backend/range_analysis.cc
index 3c4a0bf..c8886c0 100644
--- a/runtime/vm/compiler/backend/range_analysis.cc
+++ b/runtime/vm/compiler/backend/range_analysis.cc
@@ -2372,11 +2372,11 @@
ASSERT(result_min != nullptr);
ASSERT(result_max != nullptr);
- RangeBoundary left_min = Definition::IsArrayLength(left_defn)
+ RangeBoundary left_min = Definition::IsLengthLoad(left_defn)
? RangeBoundary::FromDefinition(left_defn)
: left_range->min();
- RangeBoundary left_max = Definition::IsArrayLength(left_defn)
+ RangeBoundary left_max = Definition::IsLengthLoad(left_defn)
? RangeBoundary::FromDefinition(left_defn)
: left_range->max();
@@ -2417,11 +2417,11 @@
ASSERT(result_min != nullptr);
ASSERT(result_max != nullptr);
- RangeBoundary left_min = Definition::IsArrayLength(left_defn)
+ RangeBoundary left_min = Definition::IsLengthLoad(left_defn)
? RangeBoundary::FromDefinition(left_defn)
: left_range->min();
- RangeBoundary left_max = Definition::IsArrayLength(left_defn)
+ RangeBoundary left_max = Definition::IsLengthLoad(left_defn)
? RangeBoundary::FromDefinition(left_defn)
: left_range->max();
diff --git a/runtime/vm/compiler/backend/slot.cc b/runtime/vm/compiler/backend/slot.cc
index 522bd78..5092771 100644
--- a/runtime/vm/compiler/backend/slot.cc
+++ b/runtime/vm/compiler/backend/slot.cc
@@ -205,6 +205,19 @@
return SlotCache::Instance(Thread::Current()).GetNativeSlot(kind);
}
+bool Slot::IsLengthSlot() const {
+ switch (kind()) {
+ case Slot::Kind::kArray_length:
+ case Slot::Kind::kTypedDataBase_length:
+ case Slot::Kind::kString_length:
+ case Slot::Kind::kTypeArguments_length:
+ case Slot::Kind::kGrowableObjectArray_length:
+ return true;
+ default:
+ return false;
+ }
+}
+
bool Slot::IsImmutableLengthSlot() const {
switch (kind()) {
case Slot::Kind::kArray_length:
@@ -212,37 +225,9 @@
case Slot::Kind::kString_length:
case Slot::Kind::kTypeArguments_length:
return true;
- case Slot::Kind::kGrowableObjectArray_length:
- return false;
-
- // Not length loads.
- case Slot::Kind::kArrayElement:
- case Slot::Kind::kCapturedVariable:
- case Slot::Kind::kDartField:
- case Slot::Kind::kRecordField:
- case Slot::Kind::kTypeArguments:
- case Slot::Kind::kTypeArgumentsIndex:
-#define NOT_TAGGED_INT_NATIVE_SLOT_CASE(Class, __, Field, ___, ____) \
- case Slot::Kind::k##Class##_##Field:
- NOT_INT_NATIVE_SLOTS_LIST(NOT_TAGGED_INT_NATIVE_SLOT_CASE)
- UNBOXED_NATIVE_SLOTS_LIST(NOT_TAGGED_INT_NATIVE_SLOT_CASE)
-#undef NONTAGGED_NATIVE_DART_SLOT_CASE
- case Slot::Kind::kLinkedHashBase_hash_mask:
- case Slot::Kind::kLinkedHashBase_used_data:
- case Slot::Kind::kLinkedHashBase_deleted_keys:
- case Slot::Kind::kArgumentsDescriptor_type_args_len:
- case Slot::Kind::kArgumentsDescriptor_positional_count:
- case Slot::Kind::kArgumentsDescriptor_count:
- case Slot::Kind::kArgumentsDescriptor_size:
- case Slot::Kind::kTypeArguments_hash:
- case Slot::Kind::kTypedDataView_offset_in_bytes:
- case Slot::Kind::kClosure_hash:
- case Slot::Kind::kRecord_shape:
- case Slot::Kind::kAbstractType_hash:
+ default:
return false;
}
- UNREACHABLE();
- return false;
}
// Note: should only be called with cids of array-like classes.
diff --git a/runtime/vm/compiler/backend/slot.h b/runtime/vm/compiler/backend/slot.h
index c6bb983..51fd780 100644
--- a/runtime/vm/compiler/backend/slot.h
+++ b/runtime/vm/compiler/backend/slot.h
@@ -506,6 +506,7 @@
bool IsArgumentOfType() const { return kind() == Kind::kTypeArgumentsIndex; }
bool IsArrayElement() const { return kind() == Kind::kArrayElement; }
bool IsRecordField() const { return kind() == Kind::kRecordField; }
+ bool IsLengthSlot() const;
bool IsImmutableLengthSlot() const;
const char* Name() const;