diff --git a/runtime/tests/vm/dart/regress_46141_test.dart b/runtime/tests/vm/dart/regress_46141_test.dart new file mode 100644 index 00000000000..5ffc588ed5a --- /dev/null +++ b/runtime/tests/vm/dart/regress_46141_test.dart @@ -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)); + } +} diff --git a/runtime/vm/class_id.h b/runtime/vm/class_id.h index 5e2b4c70a1e..bfa05f517cd 100644 --- a/runtime/vm/class_id.h +++ b/runtime/vm/class_id.h @@ -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; diff --git a/runtime/vm/compiler/backend/compile_type.h b/runtime/vm/compiler/backend/compile_type.h index 9d81af6eb43..4b6bffdcc21 100644 --- a/runtime/vm/compiler/backend/compile_type.h +++ b/runtime/vm/compiler/backend/compile_type.h @@ -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; diff --git a/runtime/vm/compiler/backend/flow_graph.cc b/runtime/vm/compiler/backend/flow_graph.cc index 52c25a25d52..0ccabb12097 100644 --- a/runtime/vm/compiler/backend/flow_graph.cc +++ b/runtime/vm/compiler/backend/flow_graph.cc @@ -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; } diff --git a/runtime/vm/compiler/backend/flow_graph_test.cc b/runtime/vm/compiler/backend/flow_graph_test.cc index 00baa8547d5..8f82bb12c68 100644 --- a/runtime/vm/compiler/backend/flow_graph_test.cc +++ b/runtime/vm/compiler/backend/flow_graph_test.cc @@ -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 diff --git a/runtime/vm/compiler/backend/slot.cc b/runtime/vm/compiler/backend/slot.cc index aefb2d4197f..ece5752a838 100644 --- a/runtime/vm/compiler/backend/slot.cc +++ b/runtime/vm/compiler/backend/slot.cc @@ -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); } diff --git a/runtime/vm/compiler/backend/slot.h b/runtime/vm/compiler/backend/slot.h index 221e371fc16..43a27cc375c 100644 --- a/runtime/vm/compiler/backend/slot.h +++ b/runtime/vm/compiler/backend/slot.h @@ -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; using IsGuardedBit = BitField; using IsCompressedBit = BitField; + using IsSentinelVisibleBit = + BitField; template const T* DataAs() const { diff --git a/runtime/vm/compiler/backend/type_propagator.cc b/runtime/vm/compiler/backend/type_propagator.cc index 554ea11dfb2..8ff233988da 100644 --- a/runtime/vm/compiler/backend/type_propagator.cc +++ b/runtime/vm/compiler/backend/type_propagator.cc @@ -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(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* 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: diff --git a/runtime/vm/compiler/backend/type_propagator_test.cc b/runtime/vm/compiler/backend/type_propagator_test.cc index 60d58c4c82b..4cb966e23ab 100644 --- a/runtime/vm/compiler/backend/type_propagator_test.cc +++ b/runtime/vm/compiler/backend/type_propagator_test.cc @@ -481,7 +481,8 @@ class Cset_constrained_type( - new (Z) CompileType(CompileType::FromAbstractType(type))); + new (Z) CompileType(CompileType::FromAbstractType( + type, CompileType::kCanBeNull, CompileType::kCannotBeSentinel))); Push(redefinition); return Fragment(redefinition); } diff --git a/runtime/vm/compiler/frontend/kernel_translation_helper.h b/runtime/vm/compiler/frontend/kernel_translation_helper.h index 1bed6071041..7dc03841849 100644 --- a/runtime/vm/compiler/frontend/kernel_translation_helper.h +++ b/runtime/vm/compiler/frontend/kernel_translation_helper.h @@ -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); } } }; diff --git a/runtime/vm/compiler/frontend/prologue_builder.cc b/runtime/vm/compiler/frontend/prologue_builder.cc index 6b1fee952f6..8125ba03ad7 100644 --- a/runtime/vm/compiler/frontend/prologue_builder.cc +++ b/runtime/vm/compiler/frontend/prologue_builder.cc @@ -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();