[vm] Common constant-folding of StaticCalls

TEST=il_test.cc, ConstantFold_bitLength

Change-Id: I13cf413c220e7b7c4db9867aec7716d4abfb0c09
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/233843
Reviewed-by: Slava Egorov <vegorov@google.com>
Commit-Queue: Stephen Adams <sra@google.com>
diff --git a/runtime/vm/compiler/backend/constant_propagator.cc b/runtime/vm/compiler/backend/constant_propagator.cc
index 9cf2ade..d3c855b 100644
--- a/runtime/vm/compiler/backend/constant_propagator.cc
+++ b/runtime/vm/compiler/backend/constant_propagator.cc
@@ -489,6 +489,35 @@
 
 void ConstantPropagator::VisitStaticCall(StaticCallInstr* instr) {
   const auto kind = instr->function().recognized_kind();
+  if (kind != MethodRecognizer::kUnknown) {
+    if (instr->ArgumentCount() == 1) {
+      const Object& argument = instr->ArgumentAt(0)->constant_value();
+      if (IsUnknown(argument)) {
+        return;
+      }
+      if (IsConstant(argument)) {
+        Object& value = Object::ZoneHandle(Z);
+        if (instr->Evaluate(graph_, argument, &value)) {
+          SetValue(instr, value);
+          return;
+        }
+      }
+    } else if (instr->ArgumentCount() == 2) {
+      const Object& argument1 = instr->ArgumentAt(0)->constant_value();
+      const Object& argument2 = instr->ArgumentAt(1)->constant_value();
+      if (IsUnknown(argument1) || IsUnknown(argument2)) {
+        return;
+      }
+      if (IsConstant(argument1) && IsConstant(argument2)) {
+        Object& value = Object::ZoneHandle(Z);
+        if (instr->Evaluate(graph_, argument1, argument2, &value)) {
+          SetValue(instr, value);
+          return;
+        }
+      }
+    }
+  }
+
   switch (kind) {
     case MethodRecognizer::kOneByteString_equality:
     case MethodRecognizer::kTwoByteString_equality: {
@@ -499,29 +528,6 @@
         SetValue(instr, Bool::True());
         return;
       }
-      // Otherwise evaluate string compare with propagated constants.
-      const Object& o1 = instr->ArgumentAt(0)->constant_value();
-      const Object& o2 = instr->ArgumentAt(1)->constant_value();
-      if (o1.IsString() && o2.IsString()) {
-        SetValue(instr, Bool::Get(String::Cast(o1).Equals(String::Cast(o2))));
-        return;
-      }
-      break;
-    }
-    case MethodRecognizer::kStringBaseLength:
-    case MethodRecognizer::kStringBaseIsEmpty: {
-      ASSERT(instr->FirstArgIndex() == 0);
-      // Otherwise evaluate string length with propagated constants.
-      const Object& o = instr->ArgumentAt(0)->constant_value();
-      if (o.IsString()) {
-        const auto& str = String::Cast(o);
-        if (kind == MethodRecognizer::kStringBaseLength) {
-          SetValue(instr, Integer::ZoneHandle(Z, Integer::New(str.Length())));
-        } else {
-          SetValue(instr, Bool::Get(str.Length() == 0));
-        }
-        return;
-      }
       break;
     }
     default:
diff --git a/runtime/vm/compiler/backend/evaluator.cc b/runtime/vm/compiler/backend/evaluator.cc
index fd8c0e8..9ab7d39 100644
--- a/runtime/vm/compiler/backend/evaluator.cc
+++ b/runtime/vm/compiler/backend/evaluator.cc
@@ -68,6 +68,16 @@
   return Integer::null();
 }
 
+static IntegerPtr BitLengthEvaluateRaw(const Integer& value, Zone* zone) {
+  if (value.IsSmi()) {
+    return Integer::New(Utils::BitLength(Smi::Cast(value).Value()), Heap::kOld);
+  } else if (value.IsMint()) {
+    return Integer::New(Utils::BitLength(Mint::Cast(value).value()),
+                        Heap::kOld);
+  }
+  return Integer::null();
+}
+
 int64_t Evaluator::TruncateTo(int64_t v, Representation r) {
   switch (r) {
     case kTagged: {
@@ -151,6 +161,34 @@
   return result.ptr();
 }
 
+IntegerPtr Evaluator::BitLengthEvaluate(const Object& value,
+                                        Representation representation,
+                                        Thread* thread) {
+  if (!value.IsInteger()) {
+    return Integer::null();
+  }
+  Zone* zone = thread->zone();
+  const Integer& value_int = Integer::Cast(value);
+  Integer& result =
+      Integer::Handle(zone, BitLengthEvaluateRaw(value_int, zone));
+
+  if (!result.IsNull()) {
+    if (!FlowGraph::IsConstantRepresentable(
+            result, representation,
+            /*tagged_value_must_be_smi=*/true)) {
+      // If this operation is not truncating it would deoptimize on overflow.
+      // Check that we match this behavior and don't produce a value that is
+      // larger than something this operation can produce. We could have
+      // specialized instructions that use this value under this assumption.
+      return Integer::null();
+    }
+
+    result ^= result.Canonicalize(thread);
+  }
+
+  return result.ptr();
+}
+
 double Evaluator::EvaluateDoubleOp(const double left,
                                    const double right,
                                    Token::Kind token_kind) {
diff --git a/runtime/vm/compiler/backend/evaluator.h b/runtime/vm/compiler/backend/evaluator.h
index e1ab8ea..dca583a 100644
--- a/runtime/vm/compiler/backend/evaluator.h
+++ b/runtime/vm/compiler/backend/evaluator.h
@@ -38,6 +38,12 @@
                                          Representation representation,
                                          Thread* thread);
 
+  // Evaluates an `int.bitLength` operation and returns a pointer to a
+  // canonicalized RawInteger.
+  static IntegerPtr BitLengthEvaluate(const Object& value,
+                                      Representation representation,
+                                      Thread* thread);
+
   // Evaluates a binary double operation and returns the result.
   static double EvaluateDoubleOp(const double left,
                                  const double right,
diff --git a/runtime/vm/compiler/backend/il.cc b/runtime/vm/compiler/backend/il.cc
index e69dc63..e93d920 100644
--- a/runtime/vm/compiler/backend/il.cc
+++ b/runtime/vm/compiler/backend/il.cc
@@ -5274,11 +5274,35 @@
     return CanonicalizeStringInterpolateSingle(this, flow_graph);
   }
 
+  const auto kind = function().recognized_kind();
+
+  if (kind != MethodRecognizer::kUnknown) {
+    if (ArgumentCount() == 1) {
+      const auto argument = ArgumentValueAt(0);
+      if (argument->BindsToConstant()) {
+        Object& result = Object::Handle();
+        if (Evaluate(flow_graph, argument->BoundConstant(), &result)) {
+          return flow_graph->TryCreateConstantReplacementFor(this, result);
+        }
+      }
+    } else if (ArgumentCount() == 2) {
+      const auto argument1 = ArgumentValueAt(0);
+      const auto argument2 = ArgumentValueAt(1);
+      if (argument1->BindsToConstant() && argument2->BindsToConstant()) {
+        Object& result = Object::Handle();
+        if (Evaluate(flow_graph, argument1->BoundConstant(),
+                     argument2->BoundConstant(), &result)) {
+          return flow_graph->TryCreateConstantReplacementFor(this, result);
+        }
+      }
+    }
+  }
+
   if (!compiler_state.is_aot()) {
     return this;
   }
 
-  if (function().recognized_kind() == MethodRecognizer::kObjectRuntimeType) {
+  if (kind == MethodRecognizer::kObjectRuntimeType) {
     if (input_use_list() == NULL) {
       // This function has only environment uses. In precompiled mode it is
       // fine to remove it - because we will never deoptimize.
@@ -5289,6 +5313,68 @@
   return this;
 }
 
+bool StaticCallInstr::Evaluate(FlowGraph* flow_graph,
+                               const Object& argument,
+                               Object* result) {
+  const auto kind = function().recognized_kind();
+  switch (kind) {
+    case MethodRecognizer::kSmi_bitLength: {
+      ASSERT(FirstArgIndex() == 0);
+      if (argument.IsInteger()) {
+        const Integer& value = Integer::Handle(
+            flow_graph->zone(),
+            Evaluator::BitLengthEvaluate(argument, representation(),
+                                         flow_graph->thread()));
+        if (!value.IsNull()) {
+          *result = value.ptr();
+          return true;
+        }
+      }
+      break;
+    }
+    case MethodRecognizer::kStringBaseLength:
+    case MethodRecognizer::kStringBaseIsEmpty: {
+      ASSERT(FirstArgIndex() == 0);
+      if (argument.IsString()) {
+        const auto& str = String::Cast(argument);
+        if (kind == MethodRecognizer::kStringBaseLength) {
+          *result = Integer::New(str.Length());
+        } else {
+          *result = Bool::Get(str.Length() == 0).ptr();
+          break;
+        }
+        return true;
+      }
+      break;
+    }
+    default:
+      break;
+  }
+  return false;
+}
+
+bool StaticCallInstr::Evaluate(FlowGraph* flow_graph,
+                               const Object& argument1,
+                               const Object& argument2,
+                               Object* result) {
+  const auto kind = function().recognized_kind();
+  switch (kind) {
+    case MethodRecognizer::kOneByteString_equality:
+    case MethodRecognizer::kTwoByteString_equality: {
+      if (argument1.IsString() && argument2.IsString()) {
+        *result =
+            Bool::Get(String::Cast(argument1).Equals(String::Cast(argument2)))
+                .ptr();
+        return true;
+      }
+      break;
+    }
+    default:
+      break;
+  }
+  return false;
+}
+
 LocationSummary* StaticCallInstr::MakeLocationSummary(Zone* zone,
                                                       bool optimizing) const {
   return MakeCallSummary(zone, this);
diff --git a/runtime/vm/compiler/backend/il.h b/runtime/vm/compiler/backend/il.h
index 6e916cd..51996c9 100644
--- a/runtime/vm/compiler/backend/il.h
+++ b/runtime/vm/compiler/backend/il.h
@@ -4889,6 +4889,11 @@
   DECLARE_INSTRUCTION(StaticCall)
   virtual CompileType ComputeType() const;
   virtual Definition* Canonicalize(FlowGraph* flow_graph);
+  bool Evaluate(FlowGraph* flow_graph, const Object& argument, Object* result);
+  bool Evaluate(FlowGraph* flow_graph,
+                const Object& argument1,
+                const Object& argument2,
+                Object* result);
 
   // Accessors forwarded to the AST node.
   const Function& function() const { return function_; }
diff --git a/runtime/vm/compiler/backend/il_test.cc b/runtime/vm/compiler/backend/il_test.cc
index 65dadc0..a03d0e86 100644
--- a/runtime/vm/compiler/backend/il_test.cc
+++ b/runtime/vm/compiler/backend/il_test.cc
@@ -952,4 +952,76 @@
   EXPECT_EQ(reinterpret_cast<intptr_t>(thread), result_int);
 }
 
+static void TestConstantFoldToSmi(const Library& root_library,
+                                  const char* function_name,
+                                  CompilerPass::PipelineMode mode,
+                                  intptr_t expected_value) {
+  const auto& function =
+      Function::Handle(GetFunction(root_library, function_name));
+
+  TestPipeline pipeline(function, mode);
+  FlowGraph* flow_graph = pipeline.RunPasses({});
+
+  auto entry = flow_graph->graph_entry()->normal_entry();
+  EXPECT(entry != nullptr);
+
+  ReturnInstr* ret = nullptr;
+
+  ILMatcher cursor(flow_graph, entry, true, ParallelMovesHandling::kSkip);
+  RELEASE_ASSERT(cursor.TryMatch({
+      kMoveGlob,
+      {kMatchReturn, &ret},
+  }));
+
+  ConstantInstr* constant = ret->value()->definition()->AsConstant();
+  EXPECT(constant != nullptr);
+  if (constant != nullptr) {
+    const Object& value = constant->value();
+    EXPECT(value.IsSmi());
+    if (value.IsSmi()) {
+      const intptr_t int_value = Smi::Cast(value).Value();
+      EXPECT_EQ(expected_value, int_value);
+    }
+  }
+}
+
+ISOLATE_UNIT_TEST_CASE(ConstantFold_bitLength) {
+  // clang-format off
+  auto kScript = R"(
+      b0() => 0. bitLength;  // 0...00000
+      b1() => 1. bitLength;  // 0...00001
+      b100() => 100. bitLength;
+      b200() => 200. bitLength;
+      bffff() => 0xffff. bitLength;
+      m1() => (-1).bitLength;  // 1...11111
+      m2() => (-2).bitLength;  // 1...11110
+
+      main() {
+        b0();
+        b1();
+        b100();
+        b200();
+        bffff();
+        m1();
+        m2();
+      }
+    )";
+  // clang-format on
+
+  const auto& root_library = Library::Handle(LoadTestScript(kScript));
+  Invoke(root_library, "main");
+
+  auto test = [&](const char* function, intptr_t expected) {
+    TestConstantFoldToSmi(root_library, function, CompilerPass::kJIT, expected);
+    TestConstantFoldToSmi(root_library, function, CompilerPass::kAOT, expected);
+  };
+
+  test("b0", 0);
+  test("b1", 1);
+  test("b100", 7);
+  test("b200", 8);
+  test("bffff", 16);
+  test("m1", 0);
+  test("m2", 1);
+}
 }  // namespace dart