[vm/compiler] Resolve sentinel comparisons when possible.

For StrictCompare instructions where one side is the sentinel value and
the other side has a CompileType that cannot be the sentinel, propagate
a false (if EQ_STRICT) or true (if NE_STRICT) constant for that
comparison.

TEST=vm/cc/ConstantPropagator_StrictCompare

Change-Id: I96e4bde1a02fa841987964491c5fff176fdf82a6
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/220769
Commit-Queue: Tess Strickland <sstrickl@google.com>
Reviewed-by: Daco Harkes <dacoharkes@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
diff --git a/runtime/vm/compiler/backend/constant_propagator.cc b/runtime/vm/compiler/backend/constant_propagator.cc
index f4f5424..eed551a 100644
--- a/runtime/vm/compiler/backend/constant_propagator.cc
+++ b/runtime/vm/compiler/backend/constant_propagator.cc
@@ -588,9 +588,17 @@
   const Object& left = left_defn->constant_value();
   const Object& right = right_defn->constant_value();
   if (IsNonConstant(left) || IsNonConstant(right)) {
-    // TODO(vegorov): incorporate nullability information into the lattice.
-    if ((left.IsNull() && instr->right()->Type()->HasDecidableNullability()) ||
-        (right.IsNull() && instr->left()->Type()->HasDecidableNullability())) {
+    if ((left.ptr() == Object::sentinel().ptr() &&
+         !instr->right()->Type()->can_be_sentinel()) ||
+        (right.ptr() == Object::sentinel().ptr() &&
+         !instr->left()->Type()->can_be_sentinel())) {
+      // Handle provably false (EQ_STRICT) or true (NE_STRICT) sentinel checks.
+      SetValue(instr, Bool::Get(instr->kind() != Token::kEQ_STRICT));
+    } else if ((left.IsNull() &&
+                instr->right()->Type()->HasDecidableNullability()) ||
+               (right.IsNull() &&
+                instr->left()->Type()->HasDecidableNullability())) {
+      // TODO(vegorov): incorporate nullability information into the lattice.
       bool result = left.IsNull() ? instr->right()->Type()->IsNull()
                                   : instr->left()->Type()->IsNull();
       if (instr->kind() == Token::kNE_STRICT) {
diff --git a/runtime/vm/compiler/backend/constant_propagator_test.cc b/runtime/vm/compiler/backend/constant_propagator_test.cc
index de14881..1ee3b6d 100644
--- a/runtime/vm/compiler/backend/constant_propagator_test.cc
+++ b/runtime/vm/compiler/backend/constant_propagator_test.cc
@@ -295,4 +295,85 @@
 }
 #endif
 
+void StrictCompareSentinel(Thread* thread,
+                           bool negate,
+                           bool non_sentinel_on_left) {
+  const char* kScript = R"(
+    late final int x = 4;
+  )";
+  Zone* const Z = Thread::Current()->zone();
+  const auto& root_library = Library::CheckedHandle(Z, LoadTestScript(kScript));
+  const auto& toplevel = Class::Handle(Z, root_library.toplevel_class());
+  const auto& field_x = Field::Handle(
+      Z, toplevel.LookupStaticField(String::Handle(Z, String::New("x"))));
+
+  using compiler::BlockBuilder;
+  CompilerState S(thread, /*is_aot=*/false, /*is_optimizing=*/true);
+  FlowGraphBuilderHelper H;
+
+  auto b1 = H.flow_graph()->graph_entry()->normal_entry();
+
+  {
+    BlockBuilder builder(H.flow_graph(), b1);
+    auto v_load = builder.AddDefinition(new LoadStaticFieldInstr(
+        field_x, {},
+        /*calls_initializer=*/true, S.GetNextDeoptId()));
+    auto v_sentinel = H.flow_graph()->GetConstant(Object::sentinel());
+    Value* const left_value =
+        non_sentinel_on_left ? new Value(v_load) : new Value(v_sentinel);
+    Value* const right_value =
+        non_sentinel_on_left ? new Value(v_sentinel) : new Value(v_load);
+    auto v_compare = builder.AddDefinition(new StrictCompareInstr(
+        {}, negate ? Token::kNE_STRICT : Token::kEQ_STRICT, left_value,
+        right_value,
+        /*needs_number_check=*/false, S.GetNextDeoptId()));
+    builder.AddReturn(new Value(v_compare));
+  }
+
+  H.FinishGraph();
+
+  FlowGraphPrinter::PrintGraph("Before TypePropagator", H.flow_graph());
+  FlowGraphTypePropagator::Propagate(H.flow_graph());
+  FlowGraphPrinter::PrintGraph("After TypePropagator", H.flow_graph());
+  GrowableArray<BlockEntryInstr*> ignored;
+  ConstantPropagator::Optimize(H.flow_graph());
+  FlowGraphPrinter::PrintGraph("After ConstantPropagator", H.flow_graph());
+
+  ReturnInstr* ret = nullptr;
+
+  ILMatcher cursor(H.flow_graph(),
+                   H.flow_graph()->graph_entry()->normal_entry(), true);
+  RELEASE_ASSERT(cursor.TryMatch({
+      kMatchAndMoveFunctionEntry,
+      kMatchAndMoveLoadStaticField,
+      // The StrictCompare instruction should be removed.
+      {kMatchReturn, &ret},
+  }));
+
+  EXPECT_PROPERTY(ret, it.value()->BindsToConstant());
+  EXPECT_PROPERTY(&ret->value()->BoundConstant(), it.IsBool());
+  EXPECT_PROPERTY(&ret->value()->BoundConstant(),
+                  Bool::Cast(it).value() == negate);
+}
+
+ISOLATE_UNIT_TEST_CASE(ConstantPropagator_StrictCompareEqualsSentinelLeft) {
+  StrictCompareSentinel(thread, /*negate=*/false,
+                        /*non_sentinel_on_left=*/true);
+}
+
+ISOLATE_UNIT_TEST_CASE(ConstantPropagator_StrictCompareEqualsSentinelRightt) {
+  StrictCompareSentinel(thread, /*negate=*/false,
+                        /*non_sentinel_on_left=*/false);
+}
+
+ISOLATE_UNIT_TEST_CASE(ConstantPropagator_StrictCompareNotEqualsSentinelLeft) {
+  StrictCompareSentinel(thread, /*negate=*/true,
+                        /*non_sentinel_on_left=*/true);
+}
+
+ISOLATE_UNIT_TEST_CASE(ConstantPropagator_StrictCompareNotEqualsSentinelRight) {
+  StrictCompareSentinel(thread, /*negate=*/true,
+                        /*non_sentinel_on_left=*/false);
+}
+
 }  // namespace dart