[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