[vm/compiler] Recognize and propagate "value can be sentinel" property

This change adds can_be_sentinel() flag to CompileType and
prevents unboxing of phis which can be sentinel.
This flag means that set of values can potentially contain
Object::sentinel() which is used as a marker for the uninitialized
value of late variables.

TEST=runtime/tests/vm/dart/regress_46141_test.dart

Fixes https://github.com/dart-lang/sdk/issues/46141

Change-Id: I32f19488f54c6f69932584ecec3094e3b78cc0d0
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/201600
Reviewed-by: Slava Egorov <vegorov@google.com>
Commit-Queue: Alexander Markov <alexmarkov@google.com>
This commit is contained in:
Alexander Markov 2021-06-07 20:56:53 +00:00 committed by commit-bot@chromium.org
parent 48a67a24f5
commit 69167e2bdb
12 changed files with 342 additions and 124 deletions

View file

@ -0,0 +1,43 @@
// Copyright (c) 2021, 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.
// Regression test for https://github.com/dart-lang/sdk/issues/46141.
// Verifies that unboxing of phi corresponding to a late variable doesn't
// happen.
// VMOptions=--deterministic
import 'package:expect/expect.dart';
final values = List.filled(100, 0.0);
double? fn(double day) {
double? last;
late int lastDay;
int i = 0;
while (i < values.length) {
final t = values[i];
if (day > 95) {
print(lastDay);
break;
}
last = t;
lastDay = i;
i++;
}
return last;
}
void main() {
for (int i = 0; i < values.length; i++) {
bool wasError = false;
try {
fn(i.toDouble());
} on Error {
wasError = true;
}
Expect.isTrue(wasError == (i > 95));
}
}

View file

@ -217,6 +217,8 @@ enum ClassId : intptr_t {
kNumPredefinedCids,
};
constexpr ClassId kSentinelCid = kNeverCid;
// Keep these in sync with the cid numbering above.
const int kTypedDataCidRemainderInternal = 0;
const int kTypedDataCidRemainderView = 1;

View file

@ -27,6 +27,8 @@ class GrowableArray;
// It captures the following properties:
// - whether the value can potentially be null or if it is definitely not
// null;
// - whether the value can potentially be sentinel or if it is definitely
// not sentinel;
// - concrete class id of the value or kDynamicCid if unknown statically;
// - abstract super type of the value, where the concrete type of the value
// in runtime is guaranteed to be sub type of this type.
@ -36,27 +38,42 @@ class GrowableArray;
// operation for the lattice.
class CompileType : public ZoneAllocated {
public:
static const bool kNullable = true;
static const bool kNonNullable = false;
static constexpr bool kCanBeNull = true;
static constexpr bool kCannotBeNull = false;
CompileType(bool is_nullable, intptr_t cid, const AbstractType* type)
: is_nullable_(is_nullable), cid_(cid), type_(type) {}
static constexpr bool kCanBeSentinel = true;
static constexpr bool kCannotBeSentinel = false;
CompileType(bool can_be_null,
bool can_be_sentinel,
intptr_t cid,
const AbstractType* type)
: can_be_null_(can_be_null),
can_be_sentinel_(can_be_sentinel),
cid_(cid),
type_(type) {}
CompileType(const CompileType& other)
: ZoneAllocated(),
is_nullable_(other.is_nullable_),
can_be_null_(other.can_be_null_),
can_be_sentinel_(other.can_be_sentinel_),
cid_(other.cid_),
type_(other.type_) {}
CompileType& operator=(const CompileType& other) {
// This intentionally does not change the owner of this type.
is_nullable_ = other.is_nullable_;
can_be_null_ = other.can_be_null_;
can_be_sentinel_ = other.can_be_sentinel_;
cid_ = other.cid_;
type_ = other.type_;
return *this;
}
bool is_nullable() const { return is_nullable_; }
bool is_nullable() const { return can_be_null_; }
// Return true if value of this type can be Object::sentinel().
// Such values cannot be unboxed.
bool can_be_sentinel() const { return can_be_sentinel_; }
// Return type such that concrete value's type in runtime is guaranteed to
// be subtype of it.
@ -88,10 +105,6 @@ class CompileType : public ZoneAllocated {
// against given type.
bool IsInstanceOf(const AbstractType& other);
// Create a new CompileType representing given combination of class id and
// abstract type. The pair is assumed to be coherent.
static CompileType Create(intptr_t cid, const AbstractType& type);
// Return the non-nullable version of this type.
CompileType CopyNonNullable() {
if (IsNull()) {
@ -100,28 +113,32 @@ class CompileType : public ZoneAllocated {
return None();
}
return CompileType(kNonNullable, cid_, type_);
return CompileType(kCannotBeNull, can_be_sentinel_, cid_, type_);
}
static CompileType CreateNullable(bool is_nullable, intptr_t cid) {
return CompileType(is_nullable, cid, nullptr);
// Return the non-sentinel version of this type.
CompileType CopyNonSentinel() {
return CompileType(can_be_null_, kCannotBeSentinel, cid_, type_);
}
// Create a new CompileType representing given abstract type.
// By default nullability of values is determined by type.
// CompileType can be further constrained to non-nullable values by
// passing kNonNullable as an optional parameter.
// passing kCannotBeNull as |can_be_null| parameter.
static CompileType FromAbstractType(const AbstractType& type,
bool is_nullable = kNullable);
bool can_be_null,
bool can_be_sentinel);
// Create a new CompileType representing a value with the given class id.
// Resulting CompileType is nullable only if cid is kDynamicCid or kNullCid.
// Resulting CompileType can be null only if cid is kDynamicCid or kNullCid.
// Resulting CompileType can be sentinel only if cid is kDynamicCid or
// kSentinelCid.
static CompileType FromCid(intptr_t cid);
// Create None CompileType. It is the bottom of the lattice and is used to
// represent type of the phi that was not yet inferred.
static CompileType None() {
return CompileType(kNullable, kIllegalCid, nullptr);
return CompileType(kCanBeNull, kCanBeSentinel, kIllegalCid, nullptr);
}
// Create Dynamic CompileType. It is the top of the lattice and is used to
@ -147,12 +164,12 @@ class CompileType : public ZoneAllocated {
// Create nullable Smi type.
static CompileType NullableSmi() {
return CreateNullable(kNullable, kSmiCid);
return CompileType(kCanBeNull, kCannotBeSentinel, kSmiCid, nullptr);
}
// Create nullable Mint type.
static CompileType NullableMint() {
return CreateNullable(kNullable, kMintCid);
return CompileType(kCanBeNull, kCannotBeSentinel, kMintCid, nullptr);
}
// Create non-nullable Double type.
@ -175,7 +192,8 @@ class CompileType : public ZoneAllocated {
// Return true if this and other types are the same.
bool IsEqualTo(CompileType* other) {
return (is_nullable_ == other->is_nullable_) &&
return (can_be_null_ == other->can_be_null_) &&
(can_be_sentinel_ == other->can_be_sentinel_) &&
(ToNullableCid() == other->ToNullableCid()) &&
(compiler::IsEqualType(*ToAbstractType(), *other->ToAbstractType()));
}
@ -258,7 +276,8 @@ class CompileType : public ZoneAllocated {
Definition* owner() const { return owner_; }
private:
bool is_nullable_;
bool can_be_null_;
bool can_be_sentinel_;
classid_t cid_;
const AbstractType* type_;
Definition* owner_ = nullptr;

View file

@ -1479,8 +1479,9 @@ void FlowGraph::RenameRecursive(
// Check if phi corresponds to the same slot.
auto* phis = phi->block()->phis();
if ((index < phis->length()) && (*phis)[index] == phi) {
phi->UpdateType(
CompileType::FromAbstractType(load->local().type()));
phi->UpdateType(CompileType::FromAbstractType(
load->local().type(), CompileType::kCanBeNull,
/*can_be_sentinel=*/load->local().is_late()));
} else {
ASSERT(IsCompiledForOsr() && (phi->block()->stack_depth() > 0));
}
@ -2006,7 +2007,8 @@ static void UnboxPhi(PhiInstr* phi, bool is_aot) {
}
}
if ((unboxed == kTagged) && phi->Type()->IsInt()) {
if ((unboxed == kTagged) && phi->Type()->IsInt() &&
!phi->Type()->can_be_sentinel()) {
// Conservatively unbox phis that:
// - are proven to be of type Int;
// - fit into 64bits range;
@ -2059,6 +2061,7 @@ static void UnboxPhi(PhiInstr* phi, bool is_aot) {
// to how we treat doubles and other boxed numeric types).
// In JIT mode only unbox phis which are not fully known to be Smi.
if ((unboxed == kTagged) && phi->Type()->IsInt() &&
!phi->Type()->can_be_sentinel() &&
(is_aot || phi->Type()->ToCid() != kSmiCid)) {
unboxed = kUnboxedInt64;
}

View file

@ -81,4 +81,69 @@ ISOLATE_UNIT_TEST_CASE(FlowGraph_UnboxInt64Phi) {
}
#endif // defined(TARGET_ARCH_IS_64_BIT)
ISOLATE_UNIT_TEST_CASE(FlowGraph_LateVariablePhiUnboxing) {
using compiler::BlockBuilder;
CompilerState S(thread, /*is_aot=*/true, /*is_optimizing=*/true);
FlowGraphBuilderHelper H;
auto normal_entry = H.flow_graph()->graph_entry()->normal_entry();
auto loop_header = H.JoinEntry();
auto loop_body = H.TargetEntry();
auto loop_exit = H.TargetEntry();
ConstantInstr* sentinel = H.flow_graph()->GetConstant(Object::sentinel());
PhiInstr* loop_var;
PhiInstr* late_var;
Definition* add1;
{
BlockBuilder builder(H.flow_graph(), normal_entry);
builder.AddInstruction(new GotoInstr(loop_header, S.GetNextDeoptId()));
}
{
BlockBuilder builder(H.flow_graph(), loop_header);
loop_var = H.Phi(loop_header,
{{normal_entry, H.IntConstant(0)}, {loop_body, &add1}});
builder.AddPhi(loop_var);
loop_var->UpdateType(CompileType::Int());
loop_var->UpdateType(CompileType::FromAbstractType(
Type::ZoneHandle(Type::IntType()), CompileType::kCannotBeNull,
CompileType::kCanBeSentinel));
late_var =
H.Phi(loop_header, {{normal_entry, sentinel}, {loop_body, &add1}});
builder.AddPhi(late_var);
builder.AddBranch(new RelationalOpInstr(
InstructionSource(), Token::kLT, new Value(loop_var),
new Value(H.IntConstant(10)), kMintCid,
S.GetNextDeoptId(), Instruction::kNotSpeculative),
loop_body, loop_exit);
}
{
BlockBuilder builder(H.flow_graph(), loop_body);
add1 = builder.AddDefinition(new BinaryInt64OpInstr(
Token::kADD, new Value(loop_var), new Value(H.IntConstant(1)),
S.GetNextDeoptId(), Instruction::kNotSpeculative));
builder.AddInstruction(new GotoInstr(loop_header, S.GetNextDeoptId()));
}
{
BlockBuilder builder(H.flow_graph(), loop_exit);
builder.AddReturn(new Value(late_var));
}
H.FinishGraph();
FlowGraphTypePropagator::Propagate(H.flow_graph());
H.flow_graph()->SelectRepresentations();
#if defined(TARGET_ARCH_IS_64_BIT)
EXPECT_PROPERTY(loop_var, it.representation() == kUnboxedInt64);
#endif
EXPECT_PROPERTY(late_var, it.representation() == kTagged);
}
} // namespace dart

View file

@ -299,7 +299,8 @@ const Slot& Slot::GetContextVariableSlotFor(Thread* thread,
Slot(Kind::kCapturedVariable,
IsImmutableBit::encode(variable.is_final() && !variable.is_late()) |
IsNullableBit::encode(true) |
IsCompressedBit::encode(Context::ContainsCompressedPointers()),
IsCompressedBit::encode(Context::ContainsCompressedPointers()) |
IsSentinelVisibleBit::encode(variable.is_late()),
kDynamicCid,
compiler::target::Context::variable_offset(variable.index().value()),
&variable.name(), &variable.type(), kTagged));
@ -382,16 +383,18 @@ const Slot& Slot::Get(const Field& field,
}
Class& owner = Class::Handle(zone, field.Owner());
const Slot& slot = SlotCache::Instance(thread).Canonicalize(
Slot(Kind::kDartField,
IsImmutableBit::encode((field.is_final() && !field.is_late()) ||
field.is_const()) |
IsNullableBit::encode(is_nullable) |
IsGuardedBit::encode(used_guarded_state) |
IsCompressedBit::encode(
compiler::target::Class::HasCompressedPointers(owner)),
nullable_cid, compiler::target::Field::OffsetOf(field), &field,
&type, rep));
const Slot& slot = SlotCache::Instance(thread).Canonicalize(Slot(
Kind::kDartField,
IsImmutableBit::encode((field.is_final() && !field.is_late()) ||
field.is_const()) |
IsNullableBit::encode(is_nullable) |
IsGuardedBit::encode(used_guarded_state) |
IsCompressedBit::encode(
compiler::target::Class::HasCompressedPointers(owner)) |
IsSentinelVisibleBit::encode(field.is_late() && field.is_final() &&
!field.has_initializer()),
nullable_cid, compiler::target::Field::OffsetOf(field), &field, &type,
rep));
// If properties of this slot were based on the guarded state make sure
// to add the field to the list of guarded fields. Note that during background
@ -452,7 +455,7 @@ CompileType Slot::ComputeCompileType() const {
break;
}
return CompileType(is_nullable(), nullable_cid(),
return CompileType(is_nullable(), is_sentinel_visible(), nullable_cid(),
nullable_cid() == kDynamicCid ? static_type_ : nullptr);
}

View file

@ -261,6 +261,11 @@ class Slot : public ZoneAllocated {
bool is_compressed() const { return IsCompressedBit::decode(flags_); }
// Returns true if load from this slot can return sentinel value.
bool is_sentinel_visible() const {
return IsSentinelVisibleBit::decode(flags_);
}
// Static type of the slots if any.
//
// A value that is read from the slot is guaranteed to be assignable to its
@ -316,6 +321,8 @@ class Slot : public ZoneAllocated {
using IsNullableBit = BitField<int8_t, bool, IsImmutableBit::kNextBit, 1>;
using IsGuardedBit = BitField<int8_t, bool, IsNullableBit::kNextBit, 1>;
using IsCompressedBit = BitField<int8_t, bool, IsGuardedBit::kNextBit, 1>;
using IsSentinelVisibleBit =
BitField<int8_t, bool, IsCompressedBit::kNextBit, 1>;
template <typename T>
const T* DataAs() const {

View file

@ -361,7 +361,9 @@ void FlowGraphTypePropagator::VisitGuardFieldClass(
(current->is_nullable() && !guard->field().is_nullable())) {
const bool is_nullable =
guard->field().is_nullable() && current->is_nullable();
SetTypeOf(def, new (zone()) CompileType(is_nullable, cid, NULL));
SetTypeOf(def,
new (zone()) CompileType(
is_nullable, CompileType::kCannotBeSentinel, cid, nullptr));
}
}
@ -422,11 +424,12 @@ void FlowGraphTypePropagator::VisitBranch(BranchInstr* instr) {
if (!type->IsTopTypeForInstanceOf()) {
const bool is_nullable = (type->IsNullable() || type->IsTypeParameter() ||
(type->IsNeverType() && type->IsLegacy()))
? CompileType::kNullable
: CompileType::kNonNullable;
? CompileType::kCanBeNull
: CompileType::kCannotBeNull;
EnsureMoreAccurateRedefinition(
true_successor, left,
CompileType::FromAbstractType(*type, is_nullable));
CompileType::FromAbstractType(*type, is_nullable,
CompileType::kCannotBeSentinel));
}
} else if (comparison->InputAt(0)->BindsToConstant() &&
comparison->InputAt(0)->BoundConstant().IsNull()) {
@ -445,6 +448,25 @@ void FlowGraphTypePropagator::VisitBranch(BranchInstr* instr) {
EnsureMoreAccurateRedefinition(
true_successor, comparison->InputAt(0)->definition(),
comparison->InputAt(0)->Type()->CopyNonNullable());
} else if (comparison->InputAt(0)->BindsToConstant() &&
comparison->InputAt(0)->BoundConstant().ptr() ==
Object::sentinel().ptr()) {
// Handle for expr != sentinel.
BlockEntryInstr* true_successor =
negated ? instr->true_successor() : instr->false_successor();
EnsureMoreAccurateRedefinition(
true_successor, comparison->InputAt(1)->definition(),
comparison->InputAt(1)->Type()->CopyNonSentinel());
} else if (comparison->InputAt(1)->BindsToConstant() &&
comparison->InputAt(1)->BoundConstant().ptr() ==
Object::sentinel().ptr()) {
// Handle for sentinel != expr.
BlockEntryInstr* true_successor =
negated ? instr->true_successor() : instr->false_successor();
EnsureMoreAccurateRedefinition(
true_successor, comparison->InputAt(0)->definition(),
comparison->InputAt(0)->Type()->CopyNonSentinel());
}
// TODO(fschneider): Add propagation for generic is-tests.
}
@ -543,21 +565,23 @@ void CompileType::Union(CompileType* other) {
return;
}
is_nullable_ = is_nullable_ || other->is_nullable_;
can_be_null_ = can_be_null_ || other->can_be_null_;
can_be_sentinel_ = can_be_sentinel_ || other->can_be_sentinel_;
if (ToNullableCid() == kNullCid) {
ToNullableCid(); // Ensure cid_ is set.
if ((cid_ == kNullCid) || (cid_ == kSentinelCid)) {
cid_ = other->cid_;
type_ = other->type_;
return;
}
if (other->ToNullableCid() == kNullCid) {
other->ToNullableCid(); // Ensure other->cid_ is set.
if ((other->cid_ == kNullCid) || (other->cid_ == kSentinelCid)) {
return;
}
const AbstractType* abstract_type = ToAbstractType();
if (ToNullableCid() != other->ToNullableCid()) {
ASSERT(cid_ != kNullCid);
if (cid_ != other->cid_) {
cid_ = kDynamicCid;
}
@ -625,50 +649,53 @@ CompileType* CompileType::ComputeRefinedType(CompileType* old_type,
preferred_type = new_type;
}
// Refine non-nullability.
bool is_nullable = old_type->is_nullable() && new_type->is_nullable();
// Refine non-nullability and whether it can be sentinel.
const bool can_be_null = old_type->is_nullable() && new_type->is_nullable();
const bool can_be_sentinel =
old_type->can_be_sentinel() && new_type->can_be_sentinel();
if (preferred_type->is_nullable() && !is_nullable) {
return new CompileType(preferred_type->CopyNonNullable());
if ((preferred_type->is_nullable() && !can_be_null) ||
(preferred_type->can_be_sentinel() && !can_be_sentinel)) {
return new CompileType(can_be_null, can_be_sentinel, preferred_type->cid_,
preferred_type->type_);
} else {
ASSERT(preferred_type->is_nullable() == is_nullable);
ASSERT(preferred_type->is_nullable() == can_be_null);
ASSERT(preferred_type->can_be_sentinel() == can_be_sentinel);
return preferred_type;
}
}
static bool IsNullableCid(intptr_t cid) {
ASSERT(cid != kIllegalCid);
return cid == kNullCid || cid == kDynamicCid;
}
CompileType CompileType::Create(intptr_t cid, const AbstractType& type) {
return CompileType(IsNullableCid(cid), cid, &type);
}
CompileType CompileType::FromAbstractType(const AbstractType& type,
bool is_nullable) {
return CompileType(is_nullable && !type.IsStrictlyNonNullable(), kIllegalCid,
&type);
bool can_be_null,
bool can_be_sentinel) {
return CompileType(can_be_null && !type.IsStrictlyNonNullable(),
can_be_sentinel, kIllegalCid, &type);
}
CompileType CompileType::FromCid(intptr_t cid) {
return CompileType(IsNullableCid(cid), cid, NULL);
ASSERT(cid != kIllegalCid);
ASSERT(cid != kDynamicCid);
return CompileType(cid == kNullCid, cid == kSentinelCid, cid, nullptr);
}
CompileType CompileType::Dynamic() {
return Create(kDynamicCid, Object::dynamic_type());
return CompileType(kCanBeNull, kCannotBeSentinel, kDynamicCid,
&Object::dynamic_type());
}
CompileType CompileType::Null() {
return Create(kNullCid, Type::ZoneHandle(Type::NullType()));
return CompileType(kCanBeNull, kCannotBeSentinel, kNullCid,
&Type::ZoneHandle(Type::NullType()));
}
CompileType CompileType::Bool() {
return Create(kBoolCid, Type::ZoneHandle(Type::BoolType()));
return CompileType(kCannotBeNull, kCannotBeSentinel, kBoolCid,
&Type::ZoneHandle(Type::BoolType()));
}
CompileType CompileType::Int() {
return FromAbstractType(Type::ZoneHandle(Type::IntType()), kNonNullable);
return FromAbstractType(Type::ZoneHandle(Type::IntType()), kCannotBeNull,
kCannotBeSentinel);
}
CompileType CompileType::Int32() {
@ -680,23 +707,28 @@ CompileType CompileType::Int32() {
}
CompileType CompileType::NullableInt() {
return FromAbstractType(Type::ZoneHandle(Type::NullableIntType()), kNullable);
return FromAbstractType(Type::ZoneHandle(Type::NullableIntType()), kCanBeNull,
kCannotBeSentinel);
}
CompileType CompileType::Smi() {
return Create(kSmiCid, Type::ZoneHandle(Type::SmiType()));
return CompileType(kCannotBeNull, kCannotBeSentinel, kSmiCid,
&Type::ZoneHandle(Type::SmiType()));
}
CompileType CompileType::Double() {
return Create(kDoubleCid, Type::ZoneHandle(Type::Double()));
return CompileType(kCannotBeNull, kCannotBeSentinel, kDoubleCid,
&Type::ZoneHandle(Type::Double()));
}
CompileType CompileType::NullableDouble() {
return FromAbstractType(Type::ZoneHandle(Type::NullableDouble()), kNullable);
return FromAbstractType(Type::ZoneHandle(Type::NullableDouble()), kCanBeNull,
kCannotBeSentinel);
}
CompileType CompileType::String() {
return FromAbstractType(Type::ZoneHandle(Type::StringType()), kNonNullable);
return FromAbstractType(Type::ZoneHandle(Type::StringType()), kCannotBeNull,
kCannotBeSentinel);
}
intptr_t CompileType::ToCid() {
@ -706,13 +738,18 @@ intptr_t CompileType::ToCid() {
if ((type_ != NULL) && type_->IsNullType()) {
cid_ = kNullCid;
}
// Same for sentinel.
if ((type_ != NULL) && type_->IsNeverType()) {
cid_ = kNeverCid;
}
}
if ((cid_ == kNullCid) || (cid_ == kDynamicCid)) {
return cid_;
if ((cid_ == kDynamicCid) || (can_be_null_ && (cid_ != kNullCid)) ||
(can_be_sentinel_ && (cid_ != kSentinelCid))) {
return kDynamicCid;
}
return is_nullable_ ? static_cast<intptr_t>(kDynamicCid) : ToNullableCid();
return ToNullableCid();
}
intptr_t CompileType::ToNullableCid() {
@ -724,6 +761,8 @@ intptr_t CompileType::ToNullableCid() {
cid_ = kDynamicCid;
} else if (type_->IsNullType()) {
cid_ = kNullCid;
} else if (type_->IsNeverType()) {
cid_ = kNeverCid;
} else if (type_->IsFunctionType() || type_->IsDartFunctionType()) {
cid_ = kClosureCid;
} else if (type_->type_class_id() != kIllegalCid) {
@ -758,11 +797,15 @@ intptr_t CompileType::ToNullableCid() {
}
}
if (can_be_sentinel_ && (cid_ != kSentinelCid)) {
return kDynamicCid;
}
return cid_;
}
bool CompileType::HasDecidableNullability() {
return !is_nullable_ || IsNull();
return !can_be_null_ || IsNull();
}
bool CompileType::IsNull() {
@ -838,9 +881,12 @@ bool CompileType::Specialize(GrowableArray<intptr_t>* class_ids) {
if (type_ != nullptr && type_->type_class_id() != kIllegalCid) {
const Class& type_class = Class::Handle(type_->type_class());
if (!CHA::ConcreteSubclasses(type_class, class_ids)) return false;
if (is_nullable_) {
if (can_be_null_) {
class_ids->Add(kNullCid);
}
if (can_be_sentinel_) {
class_ids->Add(kSentinelCid);
}
}
return false;
}
@ -905,7 +951,8 @@ void CompileType::PrintTo(BaseTextBuffer* f) const {
type_name = "!null";
}
f->Printf("T{%s%s}", type_name, is_nullable_ ? "?" : "");
f->Printf("T{%s%s%s}", type_name, can_be_null_ ? "?" : "",
can_be_sentinel_ ? "~" : "");
}
const char* CompileType::ToCString() const {
@ -974,15 +1021,17 @@ CompileType RedefinitionInstr::ComputeType() const {
// If either type is non-nullable, the resulting type is non-nullable.
const bool is_nullable =
value()->Type()->is_nullable() && constrained_type_->is_nullable();
const bool can_be_sentinel = value()->Type()->can_be_sentinel() &&
constrained_type_->can_be_sentinel();
// If either type has a concrete cid, stick with it.
if (value()->Type()->ToNullableCid() != kDynamicCid) {
return CompileType::CreateNullable(is_nullable,
value()->Type()->ToNullableCid());
return CompileType(is_nullable, can_be_sentinel,
value()->Type()->ToNullableCid(), nullptr);
}
if (constrained_type_->ToNullableCid() != kDynamicCid) {
return CompileType::CreateNullable(is_nullable,
constrained_type_->ToNullableCid());
return CompileType(is_nullable, can_be_sentinel,
constrained_type_->ToNullableCid(), nullptr);
}
if (value()->Type()->IsSubtypeOf(*constrained_type_->ToAbstractType())) {
return is_nullable ? *value()->Type()
@ -1088,7 +1137,8 @@ CompileType ParameterInstr::ComputeType() const {
graph_entry->parsed_function().RawParameterVariable(0)->type();
if (type.IsObjectType() || type.IsNullType()) {
// Receiver can be null.
return CompileType::FromAbstractType(type);
return CompileType::FromAbstractType(type, CompileType::kCanBeNull,
CompileType::kCannotBeSentinel);
}
// Receiver can't be null but can be an instance of a subclass.
@ -1121,7 +1171,8 @@ CompileType ParameterInstr::ComputeType() const {
}
}
return CompileType(CompileType::kNonNullable, cid, &type);
return CompileType(CompileType::kCannotBeNull,
CompileType::kCannotBeSentinel, cid, &type);
}
const bool is_unchecked_entry_param =
@ -1160,7 +1211,9 @@ CompileType ParameterInstr::ComputeType() const {
const bool is_nullable =
(inferred_type == NULL) || inferred_type->is_nullable();
TraceStrongModeType(this, param->type());
return CompileType::FromAbstractType(param->type(), is_nullable);
return CompileType::FromAbstractType(
param->type(), is_nullable,
block_->IsCatchBlockEntry() && param->is_late());
}
// Last resort: use inferred non-nullability.
if (inferred_type != NULL) {
@ -1184,16 +1237,17 @@ CompileType ConstantInstr::ComputeType() const {
intptr_t cid = value().GetClassId();
if (cid == kSmiCid && !compiler::target::IsSmi(Smi::Cast(value()).Value())) {
return CompileType::Create(kMintCid,
AbstractType::ZoneHandle(Type::MintType()));
return CompileType(CompileType::kCannotBeNull,
CompileType::kCannotBeSentinel, kMintCid,
&AbstractType::ZoneHandle(Type::MintType()));
}
if ((cid != kTypeArgumentsCid) && value().IsInstance()) {
// Allocate in old-space since this may be invoked from the
// background compiler.
return CompileType::Create(
cid,
AbstractType::ZoneHandle(Instance::Cast(value()).GetType(Heap::kOld)));
return CompileType(
cid == kNullCid, cid == kSentinelCid, cid,
&AbstractType::ZoneHandle(Instance::Cast(value()).GetType(Heap::kOld)));
} else {
// Type info for non-instance objects.
return CompileType::FromCid(cid);
@ -1211,7 +1265,8 @@ CompileType AssertAssignableInstr::ComputeType() const {
return *value_type;
}
}
return CompileType::FromAbstractType(*abs_type, value_type->is_nullable());
return CompileType::FromAbstractType(*abs_type, value_type->is_nullable(),
CompileType::kCannotBeSentinel);
}
bool AssertAssignableInstr::RecomputeType() {
@ -1261,7 +1316,8 @@ CompileType SpecialParameterInstr::ComputeType() const {
case kArgDescriptor:
return CompileType::FromCid(kImmutableArrayCid);
case kException:
return CompileType(CompileType::kNonNullable, kDynamicCid,
return CompileType(CompileType::kCannotBeNull,
CompileType::kCannotBeSentinel, kDynamicCid,
&Object::dynamic_type());
case kStackTrace:
// We cannot use [kStackTraceCid] here because any kind of object can be
@ -1273,18 +1329,18 @@ CompileType SpecialParameterInstr::ComputeType() const {
}
CompileType CloneContextInstr::ComputeType() const {
return CompileType(CompileType::kNonNullable, kContextCid,
&Object::dynamic_type());
return CompileType(CompileType::kCannotBeNull, CompileType::kCannotBeSentinel,
kContextCid, &Object::dynamic_type());
}
CompileType AllocateContextInstr::ComputeType() const {
return CompileType(CompileType::kNonNullable, kContextCid,
&Object::dynamic_type());
return CompileType(CompileType::kCannotBeNull, CompileType::kCannotBeSentinel,
kContextCid, &Object::dynamic_type());
}
CompileType AllocateUninitializedContextInstr::ComputeType() const {
return CompileType(CompileType::kNonNullable, kContextCid,
&Object::dynamic_type());
return CompileType(CompileType::kCannotBeNull, CompileType::kCannotBeSentinel,
kContextCid, &Object::dynamic_type());
}
CompileType InstanceCallBaseInstr::ComputeType() const {
@ -1313,7 +1369,8 @@ CompileType InstanceCallBaseInstr::ComputeType() const {
TraceStrongModeType(this, result_type);
const bool is_nullable =
(inferred_type == NULL) || inferred_type->is_nullable();
return CompileType::FromAbstractType(result_type, is_nullable);
return CompileType::FromAbstractType(result_type, is_nullable,
CompileType::kCannotBeSentinel);
}
}
@ -1327,14 +1384,15 @@ CompileType DispatchTableCallInstr::ComputeType() const {
const auto& result_type = AbstractType::ZoneHandle(target.result_type());
if (result_type.IsInstantiated()) {
TraceStrongModeType(this, result_type);
return CompileType::FromAbstractType(result_type);
return CompileType::FromAbstractType(result_type, CompileType::kCanBeNull,
CompileType::kCannotBeSentinel);
}
return CompileType::Dynamic();
}
CompileType PolymorphicInstanceCallInstr::ComputeType() const {
bool is_nullable = CompileType::kNullable;
bool is_nullable = CompileType::kCanBeNull;
if (IsSureToCallSingleRecognizedTarget()) {
const Function& target = *targets_.TargetAt(0)->target;
if (target.has_pragma()) {
@ -1342,7 +1400,7 @@ CompileType PolymorphicInstanceCallInstr::ComputeType() const {
if (cid != kDynamicCid) {
return CompileType::FromCid(cid);
} else if (MethodRecognizer::HasNonNullableResultTypeFromPragma(target)) {
is_nullable = CompileType::kNonNullable;
is_nullable = CompileType::kCannotBeNull;
}
}
}
@ -1369,7 +1427,8 @@ static CompileType ComputeListFactoryType(CompileType* inferred_type,
Type::ZoneHandle(Type::New(cls, type_args, Nullability::kNonNullable));
ASSERT(type.IsInstantiated());
type.SetIsFinalized();
return CompileType(CompileType::kNonNullable, cid, &type);
return CompileType(CompileType::kCannotBeNull,
CompileType::kCannotBeSentinel, cid, &type);
}
return *inferred_type;
}
@ -1386,14 +1445,14 @@ CompileType StaticCallInstr::ComputeType() const {
return *inferred_type;
}
bool is_nullable = CompileType::kNullable;
bool is_nullable = CompileType::kCanBeNull;
if (function_.has_pragma()) {
const intptr_t cid = MethodRecognizer::ResultCidFromPragma(function_);
if (cid != kDynamicCid) {
return CompileType::FromCid(cid);
}
if (MethodRecognizer::HasNonNullableResultTypeFromPragma(function_)) {
is_nullable = CompileType::kNonNullable;
is_nullable = CompileType::kCannotBeNull;
}
}
@ -1406,7 +1465,8 @@ CompileType StaticCallInstr::ComputeType() const {
TraceStrongModeType(this, result_type);
is_nullable = is_nullable &&
(inferred_type == nullptr || inferred_type->is_nullable());
return CompileType::FromAbstractType(result_type, is_nullable);
return CompileType::FromAbstractType(result_type, is_nullable,
CompileType::kCannotBeSentinel);
}
return CompileType::Dynamic();
@ -1417,11 +1477,12 @@ CompileType LoadLocalInstr::ComputeType() const {
// We may not yet have checked the actual type of the parameter value.
// Assuming that the value has the required type can lead to unsound
// optimizations. See dartbug.com/43464.
return CompileType::FromCid(kDynamicCid);
return CompileType::Dynamic();
}
const AbstractType& local_type = local().type();
TraceStrongModeType(this, local_type);
return CompileType::FromAbstractType(local_type);
return CompileType::FromAbstractType(local_type, CompileType::kCanBeNull,
local().is_late());
}
CompileType DropTempsInstr::ComputeType() const {
@ -1448,7 +1509,7 @@ CompileType StringInterpolateInstr::ComputeType() const {
CompileType LoadStaticFieldInstr::ComputeType() const {
const Field& field = this->field();
bool is_nullable = CompileType::kNullable;
bool is_nullable = CompileType::kCanBeNull;
intptr_t cid = kIllegalCid; // Abstract type is known, calculate cid lazily.
AbstractType* abstract_type = &AbstractType::ZoneHandle(field.type());
TraceStrongModeType(this, *abstract_type);
@ -1457,7 +1518,7 @@ CompileType LoadStaticFieldInstr::ComputeType() const {
const bool is_initialized = IsFieldInitialized(&obj);
if (field.is_final() && is_initialized) {
if (!obj.IsNull()) {
is_nullable = CompileType::kNonNullable;
is_nullable = CompileType::kCannotBeNull;
cid = obj.GetClassId();
abstract_type = nullptr; // Cid is known, calculate abstract type lazily.
}
@ -1473,7 +1534,9 @@ CompileType LoadStaticFieldInstr::ComputeType() const {
DEBUG_ASSERT(IsolateGroup::Current()->HasAttemptedReload());
return CompileType::Dynamic();
}
return CompileType(is_nullable, cid, abstract_type);
const bool can_be_sentinel = !calls_initializer() && field.is_late() &&
field.is_final() && !field.has_initializer();
return CompileType(is_nullable, can_be_sentinel, cid, abstract_type);
}
CompileType CreateArrayInstr::ComputeType() const {
@ -1494,7 +1557,8 @@ CompileType AllocateClosureInstr::ComputeType() const {
const auto& func = known_function();
if (!func.IsNull()) {
const auto& sig = FunctionType::ZoneHandle(func.signature());
return CompileType(CompileType::kNonNullable, kClosureCid, &sig);
return CompileType(CompileType::kCannotBeNull,
CompileType::kCannotBeSentinel, kClosureCid, &sig);
}
return CompileType::FromCid(kClosureCid);
}
@ -1509,6 +1573,9 @@ CompileType LoadClassIdInstr::ComputeType() const {
CompileType LoadFieldInstr::ComputeType() const {
CompileType type = slot().ComputeCompileType();
if (calls_initializer()) {
type = type.CopyNonSentinel();
}
TraceStrongModeType(this, &type);
return type;
}
@ -1754,7 +1821,8 @@ static CompileType ComputeArrayElementType(Value* array) {
// 1. Try to extract element type from array value.
auto& elem_type = AbstractType::Handle(GetElementTypeFromArray(array));
if (!elem_type.IsDynamicType()) {
return CompileType::FromAbstractType(elem_type);
return CompileType::FromAbstractType(elem_type, CompileType::kCanBeNull,
CompileType::kCannotBeSentinel);
}
// 2. Array value may be loaded from GrowableObjectArray.data.
@ -1764,7 +1832,8 @@ static CompileType ComputeArrayElementType(Value* array) {
array = load_field->instance();
elem_type = GetElementTypeFromArray(array);
if (!elem_type.IsDynamicType()) {
return CompileType::FromAbstractType(elem_type);
return CompileType::FromAbstractType(elem_type, CompileType::kCanBeNull,
CompileType::kCannotBeSentinel);
}
}
}
@ -1778,7 +1847,8 @@ static CompileType ComputeArrayElementType(Value* array) {
ExtractElementTypeFromArrayType(load_field->slot().static_type());
}
}
return CompileType::FromAbstractType(elem_type);
return CompileType::FromAbstractType(elem_type, CompileType::kCanBeNull,
CompileType::kCannotBeSentinel);
}
CompileType LoadIndexedInstr::ComputeType() const {
@ -1793,7 +1863,8 @@ CompileType LoadIndexedInstr::ComputeType() const {
case kTypeArgumentsCid:
return CompileType::FromAbstractType(Object::dynamic_type(),
/*is_nullable=*/false);
CompileType::kCannotBeNull,
CompileType::kCannotBeSentinel);
case kTypedDataFloat32ArrayCid:
case kTypedDataFloat64ArrayCid:

View file

@ -481,7 +481,8 @@ class C<NoBound,
field ^= fields.At(i);
type = field.type();
auto compile_type = CompileType::FromAbstractType(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",

View file

@ -576,7 +576,8 @@ Fragment BaseFlowGraphBuilder::LoadStaticField(const Field& field,
Fragment BaseFlowGraphBuilder::RedefinitionWithType(const AbstractType& type) {
auto redefinition = new (Z) RedefinitionInstr(Pop());
redefinition->set_constrained_type(
new (Z) CompileType(CompileType::FromAbstractType(type)));
new (Z) CompileType(CompileType::FromAbstractType(
type, CompileType::kCanBeNull, CompileType::kCannotBeSentinel)));
Push(redefinition);
return Fragment(redefinition);
}

View file

@ -976,9 +976,10 @@ struct InferredTypeMetadata {
return CompileType::FromAbstractType(
Type::ZoneHandle(
zone, (IsNullable() ? Type::NullableIntType() : Type::IntType())),
IsNullable());
IsNullable(), CompileType::kCannotBeSentinel);
} else {
return CompileType::CreateNullable(IsNullable(), cid);
return CompileType(IsNullable(), CompileType::kCannotBeSentinel, cid,
nullptr);
}
}
};

View file

@ -26,10 +26,11 @@ static CompileType ParameterType(LocalVariable* param,
Representation representation = kTagged) {
return param->was_type_checked_by_caller()
? CompileType::FromAbstractType(param->type(),
representation == kTagged)
representation == kTagged,
CompileType::kCannotBeSentinel)
: ((representation == kTagged)
? CompileType::Dynamic()
: CompileType::FromCid(kDynamicCid).CopyNonNullable());
: CompileType::Dynamic().CopyNonNullable());
}
bool PrologueBuilder::PrologueSkippableOnUncheckedEntry(
@ -357,7 +358,8 @@ Fragment PrologueBuilder::BuildTypeArgumentsHandling() {
store_type_args += LoadFpRelativeSlot(
compiler::target::kWordSize *
(1 + compiler::target::frame_layout.param_end_from_fp),
CompileType::CreateNullable(/*is_nullable=*/true, kTypeArgumentsCid));
CompileType(CompileType::kCanBeNull, CompileType::kCannotBeSentinel,
kTypeArgumentsCid, nullptr));
store_type_args += StoreLocal(TokenPosition::kNoSource, type_args_var);
store_type_args += Drop();