blob: 2f18be4d8f7add59900bc7f2f4b9fd01be1deb7e [file] [log] [blame]
// Copyright (c) 2019, 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 <utility>
#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/inliner.h"
#include "vm/compiler/backend/loops.h"
#include "vm/compiler/backend/redundancy_elimination.h"
#include "vm/compiler/backend/type_propagator.h"
#include "vm/compiler/compiler_pass.h"
#include "vm/compiler/frontend/kernel_to_il.h"
#include "vm/compiler/jit/compiler.h"
#include "vm/compiler/jit/jit_call_specializer.h"
#include "vm/log.h"
#include "vm/object.h"
#include "vm/parser.h"
#include "vm/symbols.h"
#include "vm/unit_test.h"
namespace dart {
using compiler::BlockBuilder;
ISOLATE_UNIT_TEST_CASE(TypePropagator_RedefinitionAfterStrictCompareWithNull) {
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);
auto normal_entry = H.flow_graph()->graph_entry()->normal_entry();
// We are going to build the following graph:
//
// B0[graph_entry]:
// B1[function_entry]:
// v0 <- Parameter(0)
// if v0 == null then B2 else B3
// B2:
// Return(v0)
// B3:
// Return(v0)
Definition* v0;
auto b2 = H.TargetEntry();
auto b3 = H.TargetEntry();
{
BlockBuilder builder(H.flow_graph(), normal_entry);
v0 = builder.AddParameter(0, 0, /*with_frame=*/true, kTagged);
builder.AddBranch(
new StrictCompareInstr(
InstructionSource(), Token::kEQ_STRICT, new Value(v0),
new Value(H.flow_graph()->GetConstant(Object::Handle())),
/*needs_number_check=*/false, S.GetNextDeoptId()),
b2, b3);
}
{
BlockBuilder builder(H.flow_graph(), b2);
builder.AddReturn(new Value(v0));
}
{
BlockBuilder builder(H.flow_graph(), b3);
builder.AddReturn(new Value(v0));
}
H.FinishGraph();
FlowGraphTypePropagator::Propagate(H.flow_graph());
// We expect that v0 is inferred to be nullable int because that is what
// static type of an associated variable tells us.
EXPECT(v0->Type()->IsNullableInt());
// In B2 v0 should not have any additional type information so reaching
// type should be still nullable int.
auto b2_value = b2->last_instruction()->AsReturn()->value();
EXPECT(b2_value->Type()->IsNullableInt());
// In B3 v0 is constrained by comparison with null - it should be non-nullable
// integer. There should be a Redefinition inserted to prevent LICM past
// the branch.
auto b3_value = b3->last_instruction()->AsReturn()->value();
EXPECT(b3_value->Type()->IsInt());
EXPECT(b3_value->definition()->IsRedefinition());
EXPECT(b3_value->definition()->GetBlock() == b3);
}
ISOLATE_UNIT_TEST_CASE(
TypePropagator_RedefinitionAfterStrictCompareWithLoadClassId) {
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 <- Parameter(0)
// v1 <- LoadClassId(v0)
// if v1 == kDoubleCid then B2 else B3
// B2:
// Return(v0)
// B3:
// Return(v0)
Definition* v0;
auto b1 = H.flow_graph()->graph_entry()->normal_entry();
auto b2 = H.TargetEntry();
auto b3 = H.TargetEntry();
{
BlockBuilder builder(H.flow_graph(), b1);
v0 = builder.AddParameter(0, 0, /*with_frame=*/true, kTagged);
auto load_cid = builder.AddDefinition(new LoadClassIdInstr(new Value(v0)));
builder.AddBranch(
new StrictCompareInstr(
InstructionSource(), Token::kEQ_STRICT, new Value(load_cid),
new Value(H.IntConstant(kDoubleCid)),
/*needs_number_check=*/false, S.GetNextDeoptId()),
b2, b3);
}
{
BlockBuilder builder(H.flow_graph(), b2);
builder.AddReturn(new Value(v0));
}
{
BlockBuilder builder(H.flow_graph(), b3);
builder.AddReturn(new Value(v0));
}
H.FinishGraph();
FlowGraphTypePropagator::Propagate(H.flow_graph());
// There should be no information available about the incoming type of
// the parameter either on entry or in B3.
EXPECT_PROPERTY(v0->Type()->ToAbstractType(), it.IsDynamicType());
auto b3_value = b3->last_instruction()->AsReturn()->value();
EXPECT(b3_value->Type() == v0->Type());
// In B3 v0 is constrained by comparison of its cid with kDoubleCid - it
// should be non-nullable double. There should be a Redefinition inserted to
// prevent LICM past the branch.
auto b2_value = b2->last_instruction()->AsReturn()->value();
EXPECT_PROPERTY(b2_value->Type(), it.IsDouble());
EXPECT_PROPERTY(b2_value->definition(), it.IsRedefinition());
EXPECT_PROPERTY(b2_value->definition()->GetBlock(), &it == b2);
}
ISOLATE_UNIT_TEST_CASE(TypePropagator_Refinement) {
CompilerState S(thread, /*is_aot=*/false, /*is_optimizing=*/true);
const Class& object_class =
Class::Handle(thread->isolate_group()->object_store()->object_class());
const FunctionType& signature = FunctionType::Handle(FunctionType::New());
const Function& target_func = Function::ZoneHandle(Function::New(
signature, String::Handle(Symbols::New(thread, "dummy2")),
UntaggedFunction::kRegularFunction,
/*is_static=*/true,
/*is_const=*/false,
/*is_abstract=*/false,
/*is_external=*/false,
/*is_native=*/true, object_class, TokenPosition::kNoSource));
signature.set_result_type(AbstractType::Handle(Type::IntType()));
const Field& field = Field::ZoneHandle(
Field::New(String::Handle(Symbols::New(thread, "dummy")),
/*is_static=*/true,
/*is_final=*/false,
/*is_const=*/false,
/*is_reflectable=*/true,
/*is_late=*/false, object_class, Object::dynamic_type(),
TokenPosition::kNoSource, TokenPosition::kNoSource));
{
SafepointWriteRwLocker locker(thread,
thread->isolate_group()->program_lock());
thread->isolate_group()->RegisterStaticField(field, Object::Handle());
}
FlowGraphBuilderHelper H;
// We are going to build the following graph:
//
// B0[graph_entry]
// B1[function_entry]:
// v0 <- Parameter(0)
// v1 <- Constant(0)
// if v0 == 1 then B3 else B2
// B2:
// v2 <- StaticCall(target_func)
// goto B4
// B3:
// goto B4
// B4:
// v3 <- phi(v1, v2)
// return v5
Definition* v0;
Definition* v2;
PhiInstr* v3;
auto b1 = H.flow_graph()->graph_entry()->normal_entry();
auto b2 = H.TargetEntry();
auto b3 = H.TargetEntry();
auto b4 = H.JoinEntry();
{
BlockBuilder builder(H.flow_graph(), b1);
v0 = builder.AddParameter(0, 0, /*with_frame=*/true, kTagged);
builder.AddBranch(new StrictCompareInstr(
InstructionSource(), Token::kEQ_STRICT, new Value(v0),
new Value(H.IntConstant(1)),
/*needs_number_check=*/false, S.GetNextDeoptId()),
b2, b3);
}
{
BlockBuilder builder(H.flow_graph(), b2);
v2 = builder.AddDefinition(
new StaticCallInstr(InstructionSource(), target_func,
/*type_args_len=*/0,
/*argument_names=*/Array::empty_array(),
new InputsArray(0), S.GetNextDeoptId(),
/*call_count=*/0, ICData::RebindRule::kStatic));
builder.AddInstruction(new GotoInstr(b4, S.GetNextDeoptId()));
}
{
BlockBuilder builder(H.flow_graph(), b3);
builder.AddInstruction(new GotoInstr(b4, S.GetNextDeoptId()));
}
{
BlockBuilder builder(H.flow_graph(), b4);
v3 = H.Phi(b4, {{b2, v2}, {b3, H.IntConstant(0)}});
builder.AddPhi(v3);
builder.AddReturn(new Value(v3));
}
H.FinishGraph();
FlowGraphTypePropagator::Propagate(H.flow_graph());
EXPECT_PROPERTY(v2->Type(), it.IsNullableInt());
EXPECT_PROPERTY(v3->Type(), it.IsNullableInt());
auto v4 = new LoadStaticFieldInstr(field, InstructionSource());
H.flow_graph()->InsertBefore(v2, v4, nullptr, FlowGraph::kValue);
v2->ReplaceUsesWith(v4);
v2->RemoveFromGraph();
FlowGraphTypePropagator::Propagate(H.flow_graph());
EXPECT_PROPERTY(v3->Type(), it.IsNullableInt());
}
// This test verifies that mutable compile types are not incorrectly cached
// as reaching types after inference.
ISOLATE_UNIT_TEST_CASE(TypePropagator_Regress36156) {
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 <- Parameter(0)
// v1 <- Constant(42)
// v2 <- Constant(24)
// v4 <- Constant(1.0)
// if v0 == 1 then B6 else B2
// B2:
// if v0 == 2 then B3 else B4
// B3:
// goto B5
// B4:
// goto B5
// B5:
// v3 <- phi(v1, v2)
// goto B7
// B6:
// goto B7
// B7:
// v5 <- phi(v4, v3)
// return v5
Definition* v0;
PhiInstr* v3;
PhiInstr* v5;
auto b1 = H.flow_graph()->graph_entry()->normal_entry();
auto b2 = H.TargetEntry();
auto b3 = H.TargetEntry();
auto b4 = H.TargetEntry();
auto b5 = H.JoinEntry();
auto b6 = H.TargetEntry();
auto b7 = H.JoinEntry();
{
BlockBuilder builder(H.flow_graph(), b1);
v0 = builder.AddParameter(0, 0, /*with_frame=*/true, kTagged);
builder.AddBranch(new StrictCompareInstr(
InstructionSource(), Token::kEQ_STRICT, new Value(v0),
new Value(H.IntConstant(1)),
/*needs_number_check=*/false, S.GetNextDeoptId()),
b6, b2);
}
{
BlockBuilder builder(H.flow_graph(), b2);
builder.AddBranch(new StrictCompareInstr(
InstructionSource(), Token::kEQ_STRICT, new Value(v0),
new Value(H.IntConstant(2)),
/*needs_number_check=*/false, S.GetNextDeoptId()),
b3, b4);
}
{
BlockBuilder builder(H.flow_graph(), b3);
builder.AddInstruction(new GotoInstr(b5, S.GetNextDeoptId()));
}
{
BlockBuilder builder(H.flow_graph(), b4);
builder.AddInstruction(new GotoInstr(b5, S.GetNextDeoptId()));
}
{
BlockBuilder builder(H.flow_graph(), b5);
v3 = H.Phi(b5, {{b3, H.IntConstant(42)}, {b4, H.IntConstant(24)}});
builder.AddPhi(v3);
builder.AddInstruction(new GotoInstr(b7, S.GetNextDeoptId()));
}
{
BlockBuilder builder(H.flow_graph(), b6);
builder.AddInstruction(new GotoInstr(b7, S.GetNextDeoptId()));
}
{
BlockBuilder builder(H.flow_graph(), b7);
v5 = H.Phi(b7, {{b5, v3}, {b6, H.DoubleConstant(1.0)}});
builder.AddPhi(v5);
builder.AddInstruction(new ReturnInstr(InstructionSource(), new Value(v5),
S.GetNextDeoptId()));
}
H.FinishGraph();
FlowGraphTypePropagator::Propagate(H.flow_graph());
// We expect that v3 has an integer type, and v5 is either T{Object} or
// T{num}.
EXPECT_PROPERTY(v3->Type(), it.IsInt());
EXPECT_PROPERTY(v5->Type()->ToAbstractType(),
it.IsObjectType() || it.IsNumberType());
// Now unbox v3 phi by inserting unboxing for both inputs and boxing
// for the result.
{
v3->set_representation(kUnboxedInt64);
for (intptr_t i = 0; i < v3->InputCount(); i++) {
auto input = v3->InputAt(i);
auto unbox =
new UnboxInt64Instr(input->CopyWithType(), S.GetNextDeoptId(),
Instruction::kNotSpeculative);
H.flow_graph()->InsertBefore(
v3->block()->PredecessorAt(i)->last_instruction(), unbox, nullptr,
FlowGraph::kValue);
input->BindTo(unbox);
}
auto box = new BoxInt64Instr(new Value(v3));
v3->ReplaceUsesWith(box);
H.flow_graph()->InsertBefore(b4->last_instruction(), box, nullptr,
FlowGraph::kValue);
}
// Run type propagation again.
FlowGraphTypePropagator::Propagate(H.flow_graph());
// If CompileType of v3 would be cached as a reaching type at its use in
// v5 then we will be incorrect type propagation results.
// We expect that v3 has an integer type, and v5 is either T{Object} or
// T{num}.
EXPECT_PROPERTY(v3->Type(), it.IsInt());
EXPECT_PROPERTY(v5->Type()->ToAbstractType(),
it.IsObjectType() || it.IsNumberType());
}
ISOLATE_UNIT_TEST_CASE(CompileType_CanBeSmi) {
CompilerState S(thread, /*is_aot=*/false, /*is_optimizing=*/true);
const char* late_tag = TestCase::LateTag();
auto script_chars = Utils::CStringUniquePtr(
OS::SCreate(nullptr, R"(
import 'dart:async';
class G<T> {}
class C<NoBound,
NumBound extends num,
ComparableBound extends Comparable,
StringBound extends String> {
// Simple instantiated types.
@pragma('vm-test:can-be-smi') %s int t1;
@pragma('vm-test:can-be-smi') %s num t2;
@pragma('vm-test:can-be-smi') %s Object t3;
%s String t4;
// Type parameters.
@pragma('vm-test:can-be-smi') %s NoBound tp1;
@pragma('vm-test:can-be-smi') %s NumBound tp2;
@pragma('vm-test:can-be-smi') %s ComparableBound tp3;
%s StringBound tp4;
// Comparable<T> instantiations.
@pragma('vm-test:can-be-smi') %s Comparable c1;
%s Comparable<String> c2;
@pragma('vm-test:can-be-smi') %s Comparable<num> c3;
%s Comparable<int> c4; // int is not a subtype of Comparable<int>.
@pragma('vm-test:can-be-smi') %s Comparable<NoBound> c5;
@pragma('vm-test:can-be-smi') %s Comparable<NumBound> c6;
@pragma('vm-test:can-be-smi') %s Comparable<ComparableBound> c7;
%s Comparable<StringBound> c8;
// FutureOr<T> instantiations.
@pragma('vm-test:can-be-smi') %s FutureOr fo1;
%s FutureOr<String> fo2;
@pragma('vm-test:can-be-smi') %s FutureOr<num> fo3;
@pragma('vm-test:can-be-smi') %s FutureOr<int> fo4;
@pragma('vm-test:can-be-smi') %s FutureOr<NoBound> fo5;
@pragma('vm-test:can-be-smi') %s FutureOr<NumBound> fo6;
@pragma('vm-test:can-be-smi') %s FutureOr<ComparableBound> fo7;
%s FutureOr<StringBound> fo8;
// Other generic classes.
%s G<int> g1;
%s G<NoBound> g2;
}
)",
late_tag, late_tag, late_tag, late_tag, late_tag, late_tag,
late_tag, late_tag, late_tag, late_tag, late_tag, late_tag,
late_tag, late_tag, late_tag, late_tag, late_tag, late_tag,
late_tag, late_tag, late_tag, late_tag, late_tag, late_tag,
late_tag, late_tag),
std::free);
const auto& lib = Library::Handle(LoadTestScript(script_chars.get()));
const auto& pragma_can_be_smi =
String::Handle(Symbols::New(thread, "vm-test:can-be-smi"));
auto expected_can_be_smi = [&](const Field& f) {
auto& options = Object::Handle();
return lib.FindPragma(thread, /*only_core=*/false, f, pragma_can_be_smi,
/*multiple=*/false, &options);
};
const auto& cls = Class::Handle(GetClass(lib, "C"));
const auto& err = Error::Handle(cls.EnsureIsFinalized(thread));
EXPECT(err.IsNull());
const auto& fields = Array::Handle(cls.fields());
auto& field = Field::Handle();
auto& type = AbstractType::Handle();
for (intptr_t i = 0; i < fields.Length(); i++) {
field ^= fields.At(i);
type = field.type();
auto compile_type = CompileType::FromAbstractType(
type, CompileType::kCanBeNull, CompileType::kCannotBeSentinel);
if (compile_type.CanBeSmi() != expected_can_be_smi(field)) {
dart::Expect(__FILE__, __LINE__)
.Fail("expected that CanBeSmi() returns %s for compile type %s\n",
expected_can_be_smi(field) ? "true" : "false",
compile_type.ToCString());
}
}
}
// Verifies that Propagate does not crash when running in AOT mode on a graph
// which contains both AssertAssignable and a CheckClass/Smi after
// EliminateEnvironments was called.
ISOLATE_UNIT_TEST_CASE(TypePropagator_RegressFlutter76919) {
CompilerState S(thread, /*is_aot=*/true, /*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::DynamicType()));
v0_var->set_type_check_mode(LocalVariable::kTypeCheckedByCaller);
H.flow_graph()->parsed_function().scope()->AddVariable(v0_var);
auto normal_entry = H.flow_graph()->graph_entry()->normal_entry();
// We are going to build the following graph:
//
// B0[graph_entry]:
// B1[function_entry]:
// v0 <- Parameter(0)
// AssertAssignable(v0, 'int')
// CheckSmi(v0)
// Return(v0)
{
BlockBuilder builder(H.flow_graph(), normal_entry);
Definition* v0 = builder.AddParameter(0, 0, /*with_frame=*/true, kTagged);
auto null_value = builder.AddNullDefinition();
builder.AddDefinition(new AssertAssignableInstr(
InstructionSource(), new Value(v0),
new Value(
H.flow_graph()->GetConstant(Type::ZoneHandle(Type::IntType()))),
new Value(null_value), new Value(null_value), Symbols::Value(),
S.GetNextDeoptId()));
builder.AddInstruction(new CheckSmiInstr(new Value(v0), S.GetNextDeoptId(),
InstructionSource()));
builder.AddReturn(new Value(v0));
}
H.FinishGraph();
H.flow_graph()->EliminateEnvironments();
FlowGraphTypePropagator::Propagate(H.flow_graph()); // Should not crash.
}
#if defined(DART_PRECOMPILER)
// This test verifies that LoadStaticField for non-nullable field
// is non-nullable with sound null safety.
// Regression test for https://github.com/dart-lang/sdk/issues/47119.
ISOLATE_UNIT_TEST_CASE(TypePropagator_NonNullableLoadStaticField) {
if (!IsolateGroup::Current()->null_safety()) {
// This test requires sound null safety.
return;
}
const char* kScript = R"(
const y = 0xDEADBEEF;
final int x = int.parse('0xFEEDFEED');
void main(List<String> args) {
print(x);
print(x + y);
}
)";
const auto& root_library = Library::Handle(LoadTestScript(kScript));
const auto& function = Function::Handle(GetFunction(root_library, "main"));
TestPipeline pipeline(function, CompilerPass::kAOT);
FlowGraph* flow_graph = pipeline.RunPasses({});
auto entry = flow_graph->graph_entry()->normal_entry();
ILMatcher cursor(flow_graph, entry, /*trace=*/true,
ParallelMovesHandling::kSkip);
Instruction* load = nullptr;
RELEASE_ASSERT(cursor.TryMatch({
kMoveGlob,
{kMatchAndMoveLoadStaticField, &load},
kMatchAndMovePushArgument,
kMatchAndMoveStaticCall,
kMatchAndMoveUnboxInt64,
kMatchAndMoveBinaryInt64Op,
kMatchAndMoveBoxInt64,
kMatchAndMovePushArgument,
kMatchAndMoveStaticCall,
kMatchReturn,
}));
EXPECT_PROPERTY(load->AsLoadStaticField()->Type(), !it.is_nullable());
}
ISOLATE_UNIT_TEST_CASE(TypePropagator_RedefineCanBeSentinelWithCannotBe) {
const char* kScript = R"(
late final int x;
)";
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;
// We are going to build the following graph:
//
// B0[graph]:0 {
// v2 <- Constant(#3)
// }
// B1[function entry]:2
// v3 <- LoadStaticField:10(x, ThrowIfSentinel)
// v5 <- Constant(#sentinel)
// Branch if StrictCompare:12(===, v3, v5) goto (2, 3)
// B2[target]:4
// goto:16 B4
// B3[target]:6
// v7 <- Redefinition(v3 ^ T{int?})
// goto:18 B4
// B4[join]:8 pred(B2, B3) {
// v9 <- phi(v2, v7) alive
// }
// Return:20(v9)
Definition* v2 = H.IntConstant(3);
Definition* v3;
Definition* v7;
PhiInstr* v9;
auto b1 = H.flow_graph()->graph_entry()->normal_entry();
auto b2 = H.TargetEntry();
auto b3 = H.TargetEntry();
auto b4 = H.JoinEntry();
{
BlockBuilder builder(H.flow_graph(), b1);
v3 = builder.AddDefinition(new LoadStaticFieldInstr(
field_x, {},
/*calls_initializer=*/false, S.GetNextDeoptId()));
auto v5 = builder.AddDefinition(new ConstantInstr(Object::sentinel()));
builder.AddBranch(new StrictCompareInstr(
{}, Token::kEQ_STRICT, new Value(v3), new Value(v5),
/*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);
v7 = builder.AddDefinition(new RedefinitionInstr(new Value(v3)));
CompileType int_type = CompileType::FromAbstractType(
Type::Handle(Type::IntType()),
/*can_be_null=*/
!IsolateGroup::Current()->use_strict_null_safety_checks(),
/*can_be_sentinel=*/false);
v7->AsRedefinition()->set_constrained_type(new CompileType(int_type));
builder.AddInstruction(new GotoInstr(b4, S.GetNextDeoptId()));
}
{
BlockBuilder builder(H.flow_graph(), b4);
v9 = H.Phi(b4, {{b2, v2}, {b3, v7}});
builder.AddPhi(v9);
builder.AddReturn(new Value(v9));
}
H.FinishGraph();
FlowGraphPrinter::PrintGraph("Before TypePropagator", H.flow_graph());
FlowGraphTypePropagator::Propagate(H.flow_graph());
FlowGraphPrinter::PrintGraph("After TypePropagator", H.flow_graph());
auto& blocks = H.flow_graph()->reverse_postorder();
EXPECT_EQ(5, blocks.length());
EXPECT_PROPERTY(blocks[0], it.IsGraphEntry());
// We expect the following types:
//
// B1[function entry]:2
// v3 <- LoadStaticField:10(x) T{int?~} // T{int~} in null safe mode
// v5 <- Constant(#sentinel) T{Sentinel~}
// Branch if StrictCompare:12(===, v3, v5) goto (2, 3)
EXPECT_PROPERTY(blocks[1], it.IsFunctionEntry());
EXPECT_PROPERTY(blocks[1]->next(), it.IsLoadStaticField());
EXPECT_PROPERTY(blocks[1]->next()->AsLoadStaticField(), it.HasType());
EXPECT_PROPERTY(blocks[1]->next()->AsLoadStaticField()->Type(),
it.can_be_sentinel());
// B3[target]:6
// v7 <- Redefinition(v3 ^ T{int?}) T{int?} // T{int} in null safe mode
// goto:18 B4
EXPECT_PROPERTY(blocks[3], it.IsTargetEntry());
EXPECT_PROPERTY(blocks[3]->next(), it.IsRedefinition());
EXPECT_PROPERTY(blocks[3]->next()->AsRedefinition(), it.HasType());
EXPECT_PROPERTY(blocks[3]->next()->AsRedefinition()->Type(),
!it.can_be_sentinel());
// B4[join]:8 pred(B2, B3) {
// v9 <- phi(v2, v7) alive T{int?} // T{int} in null safe mode
// }
// Return:20(v9)
EXPECT_PROPERTY(blocks[4], it.IsJoinEntry());
EXPECT_PROPERTY(blocks[4], it.AsJoinEntry()->phis() != nullptr);
EXPECT_PROPERTY(blocks[4]->AsJoinEntry()->phis()->At(0), it.HasType());
EXPECT_PROPERTY(blocks[4]->AsJoinEntry()->phis()->At(0)->Type(),
!it.can_be_sentinel());
}
#endif // defined(DART_PRECOMPILER)
} // namespace dart