blob: 1ee3b6d171775d0942c5fde0feabe0b7acc7d8e2 [file] [log] [blame]
// Copyright (c) 2020, 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.
#include "vm/compiler/backend/constant_propagator.h"
#include "vm/compiler/backend/block_builder.h"
#include "vm/compiler/backend/il_printer.h"
#include "vm/compiler/backend/il_test_helper.h"
#include "vm/compiler/backend/type_propagator.h"
#include "vm/unit_test.h"
namespace dart {
// Test issue https://github.com/flutter/flutter/issues/53903.
//
// If graph contains a cyclic phi which participates in an EqualityCompare
// or StrictCompare with its input like phi(x, ...) == x then constant
// propagation might fail to converge by constantly revisiting this phi and
// its uses (which includes comparison and the phi itself).
ISOLATE_UNIT_TEST_CASE(ConstantPropagation_PhiUnwrappingAndConvergence) {
using compiler::BlockBuilder;
CompilerState S(thread, /*is_aot=*/false, /*is_optimizing=*/true);
FlowGraphBuilderHelper H;
// We are going to build the following graph:
//
// B0[graph_entry]
// B1[function_entry]:
// v0 <- Constant(0)
// goto B2
// B2:
// v1 <- phi(v0, v1)
// v2 <- EqualityCompare(v1 == v0)
// if v2 == true then B4 else B3
// B3:
// goto B2
// B4:
// Return(v1)
PhiInstr* v1;
ConstantInstr* v0 = H.IntConstant(0);
auto b1 = H.flow_graph()->graph_entry()->normal_entry();
auto b2 = H.JoinEntry();
auto b3 = H.TargetEntry();
auto b4 = H.TargetEntry();
{
BlockBuilder builder(H.flow_graph(), b1);
builder.AddInstruction(new GotoInstr(b2, S.GetNextDeoptId()));
}
{
BlockBuilder builder(H.flow_graph(), b2);
v1 = H.Phi(b2, {{b1, v0}, {b3, &v1}});
builder.AddPhi(v1);
auto v2 = builder.AddDefinition(
new EqualityCompareInstr(InstructionSource(), Token::kEQ, new Value(v1),
new Value(v0), kSmiCid, S.GetNextDeoptId()));
builder.AddBranch(new StrictCompareInstr(
InstructionSource(), Token::kEQ_STRICT, new Value(v2),
new Value(H.flow_graph()->GetConstant(Bool::True())),
/*needs_number_check=*/false, S.GetNextDeoptId()),
b4, b3);
}
{
BlockBuilder builder(H.flow_graph(), b3);
builder.AddInstruction(new GotoInstr(b2, S.GetNextDeoptId()));
}
{
BlockBuilder builder(H.flow_graph(), b4);
builder.AddReturn(new Value(v1));
}
H.FinishGraph();
// Graph transformations will attempt to copy deopt information from
// branches and block entries which we did not assign.
// To disable copying we mark graph to disable LICM.
H.flow_graph()->disallow_licm();
FlowGraphPrinter::PrintGraph("Before ConstantPropagator", H.flow_graph());
ConstantPropagator::Optimize(H.flow_graph());
FlowGraphPrinter::PrintGraph("After ConstantPropagator", H.flow_graph());
auto& blocks = H.flow_graph()->reverse_postorder();
EXPECT_EQ(2, blocks.length());
EXPECT_PROPERTY(blocks[0], it.IsGraphEntry());
EXPECT_PROPERTY(blocks[1], it.IsFunctionEntry());
EXPECT_PROPERTY(blocks[1]->next(), it.IsReturn());
EXPECT_PROPERTY(blocks[1]->next()->AsReturn(),
it.value()->definition() == v0);
}
// This test does not work on 32-bit platforms because it requires
// kUnboxedInt64 constants.
#if defined(ARCH_IS_64_BIT)
namespace {
struct FoldingResult {
static FoldingResult NoFold() { return {false, 0}; }
static FoldingResult FoldsTo(int64_t result) { return {true, result}; }
bool should_fold;
int64_t result;
};
static void ConstantPropagatorUnboxedOpTest(
Thread* thread,
int64_t lhs,
int64_t rhs,
std::function<Definition*(Definition*, Definition*, intptr_t)> make_op,
bool redundant_phi,
FoldingResult expected) {
using compiler::BlockBuilder;
CompilerState S(thread, /*is_aot=*/false, /*is_optimizing=*/true);
FlowGraphBuilderHelper H;
// Add a variable into the scope which would provide static type for the
// parameter.
LocalVariable* v0_var =
new LocalVariable(TokenPosition::kNoSource, TokenPosition::kNoSource,
String::Handle(Symbols::New(thread, "v0")),
AbstractType::ZoneHandle(Type::IntType()));
v0_var->set_type_check_mode(LocalVariable::kTypeCheckedByCaller);
H.flow_graph()->parsed_function().scope()->AddVariable(v0_var);
// We are going to build the following graph:
//
// B0[graph_entry]
// B1[function_entry]:
// v0 <- Parameter(0)
// if 1 == ${redundant_phi ? 1 : v0} then B2 else B3
// B2:
// goto B4
// B3:
// goto B4
// B4:
// v1 <- Phi(lhs, ${redundant_phi ? -1 : lhs}) repr
// v2 <- Constant(rhs)
// v3 <- make_op(v1, v2)
// Return(v3)
//
// Note that we test both the case when v1 is fully redundant (has a single
// live predecessor) and when it is not redundant but has a constant value.
// These two cases are handled by different code paths - so we need to cover
// them both to ensure that we properly insert any unbox operations
// which are needed.
auto b1 = H.flow_graph()->graph_entry()->normal_entry();
auto b2 = H.TargetEntry();
auto b3 = H.TargetEntry();
auto b4 = H.JoinEntry();
ReturnInstr* ret;
{
BlockBuilder builder(H.flow_graph(), b1);
auto v0 = builder.AddParameter(/*index=*/0, /*param_offset=*/0,
/*with_frame=*/true, kTagged);
builder.AddBranch(
new StrictCompareInstr(
InstructionSource(), Token::kEQ_STRICT, new Value(H.IntConstant(1)),
new Value(redundant_phi ? H.IntConstant(1) : v0),
/*needs_number_check=*/false, S.GetNextDeoptId()),
b2, b3);
}
{
BlockBuilder builder(H.flow_graph(), b2);
builder.AddInstruction(new GotoInstr(b4, S.GetNextDeoptId()));
}
{
BlockBuilder builder(H.flow_graph(), b3);
builder.AddInstruction(new GotoInstr(b4, S.GetNextDeoptId()));
}
PhiInstr* v1;
Definition* op;
{
BlockBuilder builder(H.flow_graph(), b4);
v1 = H.Phi(b4, {{b2, H.IntConstant(lhs)},
{b3, H.IntConstant(redundant_phi ? -1 : lhs)}});
builder.AddPhi(v1);
op = builder.AddDefinition(
make_op(v1, H.IntConstant(rhs), S.GetNextDeoptId()));
ret = builder.AddReturn(new Value(op));
}
H.FinishGraph();
FlowGraphPrinter::PrintGraph("Before Optimization", H.flow_graph());
FlowGraphTypePropagator::Propagate(H.flow_graph());
FlowGraphPrinter::PrintGraph("After Propagate", H.flow_graph());
// Force phi unboxing independent of heuristics.
v1->set_representation(op->representation());
H.flow_graph()->SelectRepresentations();
FlowGraphPrinter::PrintGraph("After SelectRepresentations", H.flow_graph());
H.flow_graph()->Canonicalize();
FlowGraphPrinter::PrintGraph("After Canonicalize", H.flow_graph());
if (!expected.should_fold) {
EXPECT_PROPERTY(ret->value()->definition(),
it.IsBoxInteger() && it.RequiredInputRepresentation(0) ==
op->representation());
}
ConstantPropagator::Optimize(H.flow_graph());
FlowGraphPrinter::PrintGraph("After ConstantPropagator", H.flow_graph());
// If |should_fold| then check that resulting graph is
//
// Return(Constant(result))
//
// otherwise check that the graph is
//
// Return(Box(op))
//
{
auto ret_val = ret->value()->definition();
if (expected.should_fold) {
EXPECT_PROPERTY(ret_val,
it.IsConstant() && it.representation() == kTagged);
EXPECT_EQ(expected.result,
Integer::Cast(ret_val->AsConstant()->value()).AsInt64Value());
} else {
EXPECT_PROPERTY(ret_val,
it.IsBoxInteger() && it.RequiredInputRepresentation(0) ==
op->representation());
auto boxed_value = ret_val->AsBoxInteger()->value()->definition();
EXPECT_PROPERTY(boxed_value, &it == op);
}
}
}
void ConstantPropagatorUnboxedOpTest(
Thread* thread,
int64_t lhs,
int64_t rhs,
std::function<Definition*(Definition*, Definition*, intptr_t)> make_op,
FoldingResult expected) {
ConstantPropagatorUnboxedOpTest(thread, lhs, rhs, make_op,
/*redundant_phi=*/false, expected);
ConstantPropagatorUnboxedOpTest(thread, lhs, rhs, make_op,
/*redundant_phi=*/true, expected);
}
} // namespace
// This test verifies that constant propagation respects representations when
// replacing unboxed operations.
ISOLATE_UNIT_TEST_CASE(ConstantPropagator_Regress35371) {
auto make_int64_add = [](Definition* lhs, Definition* rhs,
intptr_t deopt_id) {
return new BinaryInt64OpInstr(Token::kADD, new Value(lhs), new Value(rhs),
deopt_id, Instruction::kNotSpeculative);
};
auto make_int32_add = [](Definition* lhs, Definition* rhs,
intptr_t deopt_id) {
return new BinaryInt32OpInstr(Token::kADD, new Value(lhs), new Value(rhs),
deopt_id);
};
auto make_int32_truncating_add = [](Definition* lhs, Definition* rhs,
intptr_t deopt_id) {
auto op = new BinaryInt32OpInstr(Token::kADD, new Value(lhs),
new Value(rhs), deopt_id);
op->mark_truncating();
return op;
};
ConstantPropagatorUnboxedOpTest(thread, /*lhs=*/1, /*lhs=*/2, make_int64_add,
FoldingResult::FoldsTo(3));
ConstantPropagatorUnboxedOpTest(thread, /*lhs=*/kMaxInt64, /*lhs=*/1,
make_int64_add,
FoldingResult::FoldsTo(kMinInt64));
ConstantPropagatorUnboxedOpTest(thread, /*lhs=*/1, /*lhs=*/2, make_int32_add,
FoldingResult::FoldsTo(3));
ConstantPropagatorUnboxedOpTest(thread, /*lhs=*/kMaxInt32 - 1, /*lhs=*/1,
make_int32_add,
FoldingResult::FoldsTo(kMaxInt32));
// Overflow of int32 representation and operation is not marked as
// truncating.
ConstantPropagatorUnboxedOpTest(thread, /*lhs=*/kMaxInt32, /*lhs=*/1,
make_int32_add, FoldingResult::NoFold());
// Overflow of int32 representation and operation is marked as truncating.
ConstantPropagatorUnboxedOpTest(thread, /*lhs=*/kMaxInt32, /*lhs=*/1,
make_int32_truncating_add,
FoldingResult::FoldsTo(kMinInt32));
}
#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