06a73eb14d
TEST=ci Change-Id: Ic8126305f293b1af9a393822e45004a949d023fd Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/195055 Commit-Queue: Ryan Macnak <rmacnak@google.com> Reviewed-by: Alexander Markov <alexmarkov@google.com>
299 lines
10 KiB
C++
299 lines
10 KiB
C++
// 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
|
|
|
|
} // namespace dart
|