[turbofan] Introduce aborting bounds checks.
Instead of eliminating bounds checks based on types, we introduce
an aborting bounds check that crashes rather than deopts.
Bug: v8:8806
Change-Id: Icbd9c4554b6ad20fe4135b8622590093679dac3f
Reviewed-on: https://chromium-review.googlesource.com/c/1460461
Commit-Queue: Jaroslav Sevcik <jarin@chromium.org>
Reviewed-by: Tobias Tebbi <tebbi@chromium.org>
Cr-Commit-Position: refs/heads/master@{#59467}
diff --git a/src/compiler/effect-control-linearizer.cc b/src/compiler/effect-control-linearizer.cc
index 7b8c215..d641bd9 100644
--- a/src/compiler/effect-control-linearizer.cc
+++ b/src/compiler/effect-control-linearizer.cc
@@ -2061,11 +2061,30 @@
Node* frame_state) {
Node* index = node->InputAt(0);
Node* limit = node->InputAt(1);
- const CheckParameters& params = CheckParametersOf(node->op());
+ const CheckBoundsParameters& params = CheckBoundsParametersOf(node->op());
Node* check = __ Uint32LessThan(index, limit);
- __ DeoptimizeIfNot(DeoptimizeReason::kOutOfBounds, params.feedback(), check,
- frame_state, IsSafetyCheck::kCriticalSafetyCheck);
+ switch (params.mode()) {
+ case CheckBoundsParameters::kDeoptOnOutOfBounds:
+ __ DeoptimizeIfNot(DeoptimizeReason::kOutOfBounds,
+ params.check_parameters().feedback(), check,
+ frame_state, IsSafetyCheck::kCriticalSafetyCheck);
+ break;
+ case CheckBoundsParameters::kAbortOnOutOfBounds: {
+ auto if_abort = __ MakeDeferredLabel();
+ auto done = __ MakeLabel();
+
+ __ Branch(check, &done, &if_abort);
+
+ __ Bind(&if_abort);
+ __ Unreachable();
+ __ Goto(&done);
+
+ __ Bind(&done);
+ break;
+ }
+ }
+
return index;
}
diff --git a/src/compiler/simplified-lowering.cc b/src/compiler/simplified-lowering.cc
index 8dbb7d9..18c930f 100644
--- a/src/compiler/simplified-lowering.cc
+++ b/src/compiler/simplified-lowering.cc
@@ -1566,6 +1566,8 @@
VisitBinop(node, UseInfo::TruncatingWord32(),
MachineRepresentation::kWord32);
if (lower()) {
+ CheckBoundsParameters::Mode mode =
+ CheckBoundsParameters::kDeoptOnOutOfBounds;
if (lowering->poisoning_level_ ==
PoisoningMitigationLevel::kDontPoison &&
(index_type.IsNone() || length_type.IsNone() ||
@@ -1573,11 +1575,10 @@
index_type.Max() < length_type.Min()))) {
// The bounds check is redundant if we already know that
// the index is within the bounds of [0.0, length[.
- DeferReplacement(node, node->InputAt(0));
- } else {
- NodeProperties::ChangeOp(
- node, simplified()->CheckedUint32Bounds(p.feedback()));
+ mode = CheckBoundsParameters::kAbortOnOutOfBounds;
}
+ NodeProperties::ChangeOp(
+ node, simplified()->CheckedUint32Bounds(p.feedback(), mode));
}
} else {
VisitBinop(
@@ -1586,7 +1587,9 @@
UseInfo::TruncatingWord32(), MachineRepresentation::kWord32);
if (lower()) {
NodeProperties::ChangeOp(
- node, simplified()->CheckedUint32Bounds(p.feedback()));
+ node,
+ simplified()->CheckedUint32Bounds(
+ p.feedback(), CheckBoundsParameters::kDeoptOnOutOfBounds));
}
}
} else {
diff --git a/src/compiler/simplified-operator.cc b/src/compiler/simplified-operator.cc
index 9a2039e..8ea98a3 100644
--- a/src/compiler/simplified-operator.cc
+++ b/src/compiler/simplified-operator.cc
@@ -812,13 +812,14 @@
V(CheckedTaggedSignedToInt32, 1, 1) \
V(CheckedTaggedToTaggedPointer, 1, 1) \
V(CheckedTaggedToTaggedSigned, 1, 1) \
- V(CheckedUint32Bounds, 2, 1) \
V(CheckedUint32ToInt32, 1, 1) \
V(CheckedUint32ToTaggedSigned, 1, 1) \
V(CheckedUint64Bounds, 2, 1) \
V(CheckedUint64ToInt32, 1, 1) \
V(CheckedUint64ToTaggedSigned, 1, 1)
+#define CHECKED_BOUNDS_OP_LIST(V) V(CheckedUint32Bounds)
+
struct SimplifiedOperatorGlobalCache final {
#define PURE(Name, properties, value_input_count, control_input_count) \
struct Name##Operator final : public Operator { \
@@ -867,6 +868,21 @@
CHECKED_WITH_FEEDBACK_OP_LIST(CHECKED_WITH_FEEDBACK)
#undef CHECKED_WITH_FEEDBACK
+#define CHECKED_BOUNDS(Name) \
+ struct Name##Operator final : public Operator1<CheckBoundsParameters> { \
+ Name##Operator(VectorSlotPair feedback, CheckBoundsParameters::Mode mode) \
+ : Operator1<CheckBoundsParameters>( \
+ IrOpcode::k##Name, Operator::kFoldable | Operator::kNoThrow, \
+ #Name, 2, 1, 1, 1, 1, 0, \
+ CheckBoundsParameters(feedback, mode)) {} \
+ }; \
+ Name##Operator k##Name##Deopting = { \
+ VectorSlotPair(), CheckBoundsParameters::kDeoptOnOutOfBounds}; \
+ Name##Operator k##Name##Aborting = { \
+ VectorSlotPair(), CheckBoundsParameters::kAbortOnOutOfBounds};
+ CHECKED_BOUNDS_OP_LIST(CHECKED_BOUNDS)
+#undef CHECKED_BOUNDS
+
template <DeoptimizeReason kDeoptimizeReason>
struct CheckIfOperator final : public Operator1<CheckIfParameters> {
CheckIfOperator()
@@ -1185,6 +1201,23 @@
CHECKED_WITH_FEEDBACK_OP_LIST(GET_FROM_CACHE_WITH_FEEDBACK)
#undef GET_FROM_CACHE_WITH_FEEDBACK
+#define GET_FROM_CACHE_WITH_FEEDBACK(Name) \
+ const Operator* SimplifiedOperatorBuilder::Name( \
+ const VectorSlotPair& feedback, CheckBoundsParameters::Mode mode) { \
+ if (!feedback.IsValid()) { \
+ switch (mode) { \
+ case CheckBoundsParameters::kDeoptOnOutOfBounds: \
+ return &cache_.k##Name##Deopting; \
+ case CheckBoundsParameters::kAbortOnOutOfBounds: \
+ return &cache_.k##Name##Aborting; \
+ } \
+ } \
+ return new (zone()) \
+ SimplifiedOperatorGlobalCache::Name##Operator(feedback, mode); \
+ }
+CHECKED_BOUNDS_OP_LIST(GET_FROM_CACHE_WITH_FEEDBACK)
+#undef GET_FROM_CACHE_WITH_FEEDBACK
+
bool IsCheckedWithFeedback(const Operator* op) {
#define CASE(Name, ...) case IrOpcode::k##Name:
switch (op->opcode()) {
@@ -1509,12 +1542,43 @@
}
CheckParameters const& CheckParametersOf(Operator const* op) {
+ if (op->opcode() == IrOpcode::kCheckedUint32Bounds) {
+ return OpParameter<CheckBoundsParameters>(op).check_parameters();
+ }
#define MAKE_OR(name, arg2, arg3) op->opcode() == IrOpcode::k##name ||
CHECK((CHECKED_WITH_FEEDBACK_OP_LIST(MAKE_OR) false));
#undef MAKE_OR
return OpParameter<CheckParameters>(op);
}
+bool operator==(CheckBoundsParameters const& lhs,
+ CheckBoundsParameters const& rhs) {
+ return lhs.check_parameters() == rhs.check_parameters() &&
+ lhs.mode() == rhs.mode();
+}
+
+size_t hash_value(CheckBoundsParameters const& p) {
+ return base::hash_combine(hash_value(p.check_parameters()), p.mode());
+}
+
+std::ostream& operator<<(std::ostream& os, CheckBoundsParameters const& p) {
+ os << p.check_parameters() << ",";
+ switch (p.mode()) {
+ case CheckBoundsParameters::kDeoptOnOutOfBounds:
+ os << "deopt";
+ break;
+ case CheckBoundsParameters::kAbortOnOutOfBounds:
+ os << "abort";
+ break;
+ }
+ return os;
+}
+
+CheckBoundsParameters const& CheckBoundsParametersOf(Operator const* op) {
+ CHECK_EQ(op->opcode(), IrOpcode::kCheckedUint32Bounds);
+ return OpParameter<CheckBoundsParameters>(op);
+}
+
bool operator==(CheckIfParameters const& lhs, CheckIfParameters const& rhs) {
return lhs.reason() == rhs.reason() && lhs.feedback() == rhs.feedback();
}
@@ -1698,6 +1762,7 @@
#undef EFFECT_DEPENDENT_OP_LIST
#undef SPECULATIVE_NUMBER_BINOP_LIST
#undef CHECKED_WITH_FEEDBACK_OP_LIST
+#undef CHECKED_BOUNDS_OP_LIST
#undef CHECKED_OP_LIST
#undef ACCESS_OP_LIST
diff --git a/src/compiler/simplified-operator.h b/src/compiler/simplified-operator.h
index c0e4672..ef86130 100644
--- a/src/compiler/simplified-operator.h
+++ b/src/compiler/simplified-operator.h
@@ -163,6 +163,30 @@
CheckParameters const& CheckParametersOf(Operator const*) V8_WARN_UNUSED_RESULT;
+class CheckBoundsParameters final {
+ public:
+ enum Mode { kAbortOnOutOfBounds, kDeoptOnOutOfBounds };
+
+ CheckBoundsParameters(const VectorSlotPair& feedback, Mode mode)
+ : check_parameters_(feedback), mode_(mode) {}
+
+ Mode mode() const { return mode_; }
+ const CheckParameters& check_parameters() const { return check_parameters_; }
+
+ private:
+ CheckParameters check_parameters_;
+ Mode mode_;
+};
+
+bool operator==(CheckBoundsParameters const&, CheckBoundsParameters const&);
+
+size_t hash_value(CheckBoundsParameters const&);
+
+std::ostream& operator<<(std::ostream&, CheckBoundsParameters const&);
+
+CheckBoundsParameters const& CheckBoundsParametersOf(Operator const*)
+ V8_WARN_UNUSED_RESULT;
+
class CheckIfParameters final {
public:
explicit CheckIfParameters(DeoptimizeReason reason,
@@ -709,7 +733,8 @@
const VectorSlotPair& feedback);
const Operator* CheckedUint32Div();
const Operator* CheckedUint32Mod();
- const Operator* CheckedUint32Bounds(const VectorSlotPair& feedback);
+ const Operator* CheckedUint32Bounds(const VectorSlotPair& feedback,
+ CheckBoundsParameters::Mode mode);
const Operator* CheckedUint32ToInt32(const VectorSlotPair& feedback);
const Operator* CheckedUint32ToTaggedSigned(const VectorSlotPair& feedback);
const Operator* CheckedUint64Bounds(const VectorSlotPair& feedback);
diff --git a/test/unittests/compiler/redundancy-elimination-unittest.cc b/test/unittests/compiler/redundancy-elimination-unittest.cc
index 079cc4b..a9cf326 100644
--- a/test/unittests/compiler/redundancy-elimination-unittest.cc
+++ b/test/unittests/compiler/redundancy-elimination-unittest.cc
@@ -668,16 +668,18 @@
Node* effect = graph()->start();
Node* control = graph()->start();
- Node* check1 = effect =
- graph()->NewNode(simplified()->CheckedUint32Bounds(feedback1), index,
- length, effect, control);
+ Node* check1 = effect = graph()->NewNode(
+ simplified()->CheckedUint32Bounds(
+ feedback1, CheckBoundsParameters::kDeoptOnOutOfBounds),
+ index, length, effect, control);
Reduction r1 = Reduce(check1);
ASSERT_TRUE(r1.Changed());
EXPECT_EQ(r1.replacement(), check1);
- Node* check2 = effect =
- graph()->NewNode(simplified()->CheckedUint32Bounds(feedback2), index,
- length, effect, control);
+ Node* check2 = effect = graph()->NewNode(
+ simplified()->CheckedUint32Bounds(
+ feedback2, CheckBoundsParameters::kDeoptOnOutOfBounds),
+ index, length, effect, control);
Reduction r2 = Reduce(check2);
ASSERT_TRUE(r2.Changed());
EXPECT_EQ(r2.replacement(), check1);