[vm] Remove transition_sentinel and detection of cyclic initialization of legacy static fields

Since null safety, all static fields with initializers are implicitly
late. This change cleans up transition_sentinel which was used in
the detection of cyclic initialization of legacy static fields.

TEST=ci

Change-Id: I6a990dc8ba030f5bd40eb0b86706cbfb0f725e33
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/373520
Reviewed-by: Alexander Aprelev <aam@google.com>
Commit-Queue: Alexander Markov <alexmarkov@google.com>
This commit is contained in:
Alexander Markov 2024-06-27 18:29:58 +00:00 committed by Commit Queue
parent caffef2942
commit 00eac588c2
23 changed files with 24 additions and 150 deletions

View file

@ -2389,7 +2389,7 @@ abstract class SentinelKind {
/// Indicates that a variable or field has not been initialized.
static const String kNotInitialized = 'NotInitialized';
/// Indicates that a variable or field is in the process of being initialized.
/// Deprecated, no longer used.
static const String kBeingInitialized = 'BeingInitialized';
/// Indicates that a variable has been eliminated by the optimizing compiler.
@ -2513,8 +2513,6 @@ class AllocationProfile extends Response {
/// If the field is uninitialized, the `value` will be the `NotInitialized`
/// [Sentinel].
///
/// If the field is being initialized, the `value` will be the
/// `BeingInitialized` [Sentinel].
class BoundField {
static BoundField? parse(Map<String, dynamic>? json) =>
json == null ? null : BoundField._fromJson(json);
@ -2567,9 +2565,6 @@ class BoundField {
/// If the variable is uninitialized, the `value` will be the `NotInitialized`
/// [Sentinel].
///
/// If the variable is being initialized, the `value` will be the
/// `BeingInitialized` [Sentinel].
///
/// If the variable has been optimized out by the compiler, the `value` will be
/// the `OptimizedOut` [Sentinel].
class BoundVariable extends Response {

View file

@ -55,8 +55,6 @@ class SentinelValueElement extends CustomElement implements Renderable {
case M.SentinelKind.notInitialized:
return 'This object will be initialized once it is accessed by '
'the program.';
case M.SentinelKind.initializing:
return 'This object is currently being initialized.';
case M.SentinelKind.optimizedOut:
return 'This object is no longer needed and has been removed by the '
'optimizing compiler.';

View file

@ -90,8 +90,6 @@ class SentinelViewElement extends CustomElement implements Renderable {
case M.SentinelKind.notInitialized:
return 'This object will be initialized once it is accessed by '
'the program.';
case M.SentinelKind.initializing:
return 'This object is currently being initialized.';
case M.SentinelKind.optimizedOut:
return 'This object is no longer needed and has been removed by the '
'optimizing compiler.';

View file

@ -14,9 +14,6 @@ enum SentinelKind {
/// Indicates that a variable or field has not been initialized.
notInitialized,
/// Indicates that a variable or field is in the process of being initialized.
initializing,
/// Indicates that a variable has been eliminated by the optimizing compiler.
optimizedOut,

View file

@ -3319,8 +3319,6 @@ M.SentinelKind stringToSentinelKind(String s) {
return M.SentinelKind.expired;
case 'NotInitialized':
return M.SentinelKind.notInitialized;
case 'BeingInitialized':
return M.SentinelKind.initializing;
case 'OptimizedOut':
return M.SentinelKind.optimizedOut;
case 'Free':

View file

@ -6815,8 +6815,6 @@ class VMSerializationRoots : public SerializationRoots {
s->AddBaseObject(Object::null(), "Null", "null");
s->AddBaseObject(Object::sentinel().ptr(), "Null", "sentinel");
s->AddBaseObject(Object::transition_sentinel().ptr(), "Null",
"transition_sentinel");
s->AddBaseObject(Object::optimized_out().ptr(), "Null", "<optimized out>");
s->AddBaseObject(Object::empty_array().ptr(), "Array", "<empty_array>");
s->AddBaseObject(Object::empty_instantiations_cache_array().ptr(), "Array",
@ -6940,7 +6938,6 @@ class VMDeserializationRoots : public DeserializationRoots {
d->AddBaseObject(Object::null());
d->AddBaseObject(Object::sentinel().ptr());
d->AddBaseObject(Object::transition_sentinel().ptr());
d->AddBaseObject(Object::optimized_out().ptr());
d->AddBaseObject(Object::empty_array().ptr());
d->AddBaseObject(Object::empty_instantiations_cache_array().ptr());

View file

@ -1217,8 +1217,7 @@ void Precompiler::AddConstObject(const class Instance& instance) {
return;
}
if (instance.ptr() == Object::sentinel().ptr() ||
instance.ptr() == Object::transition_sentinel().ptr()) {
if (instance.ptr() == Object::sentinel().ptr()) {
return;
}
@ -1332,8 +1331,6 @@ void Precompiler::AddField(const Field& field) {
auto field_table = field.is_shared() ? IG->shared_initial_field_table()
: IG->initial_field_table();
const Object& value = Object::Handle(Z, field_table->At(field.field_id()));
// Should not be in the middle of initialization while precompiling.
ASSERT(value.ptr() != Object::transition_sentinel().ptr());
if (value.ptr() != Object::sentinel().ptr() &&
value.ptr() != Object::null()) {

View file

@ -4571,22 +4571,18 @@ void LoadStaticFieldInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
return;
}
ASSERT(field().has_initializer());
ASSERT(field().is_late());
auto object_store = compiler->isolate_group()->object_store();
const Field& original_field = Field::ZoneHandle(field().Original());
compiler::Label no_call, call_initializer;
compiler::Label no_call;
__ CompareObject(result, Object::sentinel());
if (!field().is_late()) {
__ BranchIf(EQUAL, &call_initializer);
__ CompareObject(result, Object::transition_sentinel());
}
__ BranchIf(NOT_EQUAL, &no_call);
auto& stub = Code::ZoneHandle(compiler->zone());
__ Bind(&call_initializer);
if (field().needs_load_guard()) {
stub = object_store->init_static_field_stub();
} else if (field().is_late()) {
} else {
// The stubs below call the initializer function directly, so make sure
// one is created.
original_field.EnsureInitializerFunction();
@ -4598,11 +4594,6 @@ void LoadStaticFieldInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
: (field().is_final()
? object_store->init_late_final_static_field_stub()
: object_store->init_late_static_field_stub());
} else {
// We call to runtime for non-late fields because the stub would need to
// catch any exception generated by the initialization function to change
// the value of the static field from the transition sentinel to null.
stub = object_store->init_static_field_stub();
}
__ LoadObject(InitStaticFieldABI::kFieldReg, original_field);

View file

@ -1692,8 +1692,6 @@ void FlowGraphSerializer::WriteObjectImpl(const Object& x,
case kSentinelCid:
if (x.ptr() == Object::sentinel().ptr()) {
Write<uint8_t>(0);
} else if (x.ptr() == Object::transition_sentinel().ptr()) {
Write<uint8_t>(1);
} else if (x.ptr() == Object::optimized_out().ptr()) {
Write<uint8_t>(2);
} else {
@ -1978,8 +1976,6 @@ const Object& FlowGraphDeserializer::ReadObjectImpl(intptr_t cid,
switch (Read<uint8_t>()) {
case 0:
return Object::sentinel();
case 1:
return Object::transition_sentinel();
case 2:
return Object::optimized_out();
default:

View file

@ -1202,10 +1202,6 @@ ObjectPtr Exceptions::Create(ExceptionType type, const Array& arguments) {
constructor_name = &Symbols::DotCreate();
break;
#endif
case kCyclicInitializationError:
library = Library::CoreLibrary();
class_name = &Symbols::_CyclicInitializationError();
break;
case kCompileTimeError:
library = Library::CoreLibrary();
class_name = &Symbols::_CompileTimeError();

View file

@ -67,7 +67,6 @@ class Exceptions : AllStatic {
kAssertion,
kType,
kAbstractClassInstantiation,
kCyclicInitializationError,
kCompileTimeError,
kLateFieldAssignedDuringInitialization,
kLateFieldNotInitialized,

View file

@ -100,7 +100,7 @@ struct WeakAcqRelStorageTraits : WeakArrayStorageTraits {
class HashTableBase : public ValueObject {
public:
static const Object& UnusedMarker() { return Object::transition_sentinel(); }
static const Object& UnusedMarker() { return Object::sentinel(); }
static const Object& DeletedMarker() { return Object::null_object(); }
};
@ -156,7 +156,7 @@ class HashTableBase : public ValueObject {
// Each entry contains a key, followed by zero or more payload components,
// and has 3 possible states: unused, occupied, or deleted.
// The header tracks the number of entries in each state.
// Any object except the backing storage array and Object::transition_sentinel()
// Any object except the backing storage array and Object::sentinel()
// may be stored as a key. Any object may be stored in a payload.
//
// Parameters

View file

@ -2208,8 +2208,7 @@ class FieldInvalidator {
// At that point it doesn't have the field table setup yet.
if (field_table->IsReadyToUse()) {
value_ = field_table->At(field_id);
if ((value_.ptr() != Object::sentinel().ptr()) &&
(value_.ptr() != Object::transition_sentinel().ptr())) {
if (value_.ptr() != Object::sentinel().ptr()) {
CheckValueType(value_, field);
}
}

View file

@ -27,8 +27,6 @@
namespace dart {
static Dart_CObject cobj_sentinel = {Dart_CObject_kUnsupported, {false}};
static Dart_CObject cobj_transition_sentinel = {Dart_CObject_kUnsupported,
{false}};
static Dart_CObject cobj_dynamic_type = {Dart_CObject_kUnsupported, {false}};
static Dart_CObject cobj_void_type = {Dart_CObject_kUnsupported, {false}};
static Dart_CObject cobj_empty_type_arguments = {Dart_CObject_kUnsupported,
@ -3216,7 +3214,6 @@ MessageDeserializationCluster* BaseDeserializer::ReadCluster() {
void MessageSerializer::AddBaseObjects() {
AddBaseObject(Object::null());
AddBaseObject(Object::sentinel().ptr());
AddBaseObject(Object::transition_sentinel().ptr());
AddBaseObject(Object::empty_array().ptr());
AddBaseObject(Object::dynamic_type().ptr());
AddBaseObject(Object::void_type().ptr());
@ -3228,7 +3225,6 @@ void MessageSerializer::AddBaseObjects() {
void MessageDeserializer::AddBaseObjects() {
AddBaseObject(Object::null());
AddBaseObject(Object::sentinel().ptr());
AddBaseObject(Object::transition_sentinel().ptr());
AddBaseObject(Object::empty_array().ptr());
AddBaseObject(Object::dynamic_type().ptr());
AddBaseObject(Object::void_type().ptr());
@ -3240,7 +3236,6 @@ void MessageDeserializer::AddBaseObjects() {
void ApiMessageSerializer::AddBaseObjects() {
AddBaseObject(PredefinedCObjects::cobj_null());
AddBaseObject(&cobj_sentinel);
AddBaseObject(&cobj_transition_sentinel);
AddBaseObject(PredefinedCObjects::cobj_empty_array());
AddBaseObject(&cobj_dynamic_type);
AddBaseObject(&cobj_void_type);
@ -3252,7 +3247,6 @@ void ApiMessageSerializer::AddBaseObjects() {
void ApiMessageDeserializer::AddBaseObjects() {
AddBaseObject(PredefinedCObjects::cobj_null());
AddBaseObject(&cobj_sentinel);
AddBaseObject(&cobj_transition_sentinel);
AddBaseObject(PredefinedCObjects::cobj_empty_array());
AddBaseObject(&cobj_dynamic_type);
AddBaseObject(&cobj_void_type);

View file

@ -830,7 +830,6 @@ void Object::Init(IsolateGroup* isolate_group) {
// Allocate and initialize the sentinel values.
{
*sentinel_ ^= Sentinel::New();
*transition_sentinel_ ^= Sentinel::New();
}
// Allocate and initialize optimizing compiler constants.
@ -1316,8 +1315,6 @@ void Object::Init(IsolateGroup* isolate_group) {
ASSERT(empty_async_exception_handlers_->IsExceptionHandlers());
ASSERT(!sentinel_->IsSmi());
ASSERT(sentinel_->IsSentinel());
ASSERT(!transition_sentinel_->IsSmi());
ASSERT(transition_sentinel_->IsSentinel());
ASSERT(!unknown_constant_->IsSmi());
ASSERT(unknown_constant_->IsSentinel());
ASSERT(!non_constant_->IsSmi());
@ -12311,7 +12308,6 @@ bool Field::IsUninitialized() const {
Thread* thread = Thread::Current();
const FieldTable* field_table = thread->isolate()->field_table();
const ObjectPtr raw_value = field_table->At(field_id());
ASSERT(raw_value != Object::transition_sentinel().ptr());
return raw_value == Object::sentinel().ptr();
}
@ -12399,40 +12395,25 @@ ErrorPtr Field::InitializeStatic() const {
ASSERT(IsOriginal());
ASSERT(is_static());
if (StaticValue() == Object::sentinel().ptr()) {
ASSERT(is_late());
auto& value = Object::Handle();
if (is_late()) {
if (!has_initializer()) {
Exceptions::ThrowLateFieldNotInitialized(String::Handle(name()));
UNREACHABLE();
}
value = EvaluateInitializer();
if (value.IsError()) {
return Error::Cast(value).ptr();
}
if (is_final() && (StaticValue() != Object::sentinel().ptr())) {
Exceptions::ThrowLateFieldAssignedDuringInitialization(
String::Handle(name()));
UNREACHABLE();
}
} else {
SetStaticValue(Object::transition_sentinel());
value = EvaluateInitializer();
if (value.IsError()) {
SetStaticValue(Object::null_instance());
return Error::Cast(value).ptr();
}
if (!has_initializer()) {
Exceptions::ThrowLateFieldNotInitialized(String::Handle(name()));
UNREACHABLE();
}
value = EvaluateInitializer();
if (value.IsError()) {
return Error::Cast(value).ptr();
}
if (is_final() && (StaticValue() != Object::sentinel().ptr())) {
Exceptions::ThrowLateFieldAssignedDuringInitialization(
String::Handle(name()));
UNREACHABLE();
}
ASSERT(value.IsNull() || value.IsInstance());
SetStaticValue(value.IsNull() ? Instance::null_instance()
: Instance::Cast(value));
return Error::null();
} else if (StaticValue() == Object::transition_sentinel().ptr()) {
ASSERT(!is_late());
const Array& ctor_args = Array::Handle(Array::New(1));
const String& field_name = String::Handle(name());
ctor_args.SetAt(0, field_name);
Exceptions::ThrowByType(Exceptions::kCyclicInitializationError, ctor_args);
UNREACHABLE();
}
return Error::null();
}
@ -12824,7 +12805,6 @@ StaticTypeExactnessState StaticTypeExactnessState::Compute(
bool print_trace /* = false */) {
ASSERT(!value.IsNull()); // Should be handled by the caller.
ASSERT(value.ptr() != Object::sentinel().ptr());
ASSERT(value.ptr() != Object::transition_sentinel().ptr());
Thread* thread = Thread::Current();
Zone* const zone = thread->zone();
@ -18754,8 +18734,6 @@ SentinelPtr Sentinel::New() {
const char* Sentinel::ToCString() const {
if (ptr() == Object::sentinel().ptr()) {
return "sentinel";
} else if (ptr() == Object::transition_sentinel().ptr()) {
return "transition_sentinel";
} else if (ptr() == Object::unknown_constant().ptr()) {
return "unknown_constant";
} else if (ptr() == Object::non_constant().ptr()) {

View file

@ -447,9 +447,6 @@ class Object {
//
// - sentinel is a value that cannot be produced by Dart code. It can be used
// to mark special values, for example to distinguish "uninitialized" fields.
// - transition_sentinel is a value marking that we are transitioning from
// sentinel, e.g., computing a field value. Used to detect circular
// initialization.
// - unknown_constant and non_constant are optimizing compiler's constant
// propagation constants.
// - optimized_out results from deopt environment pruning or failure to
@ -480,7 +477,6 @@ class Object {
V(Array, synthetic_getter_parameter_types) \
V(Array, synthetic_getter_parameter_names) \
V(Sentinel, sentinel) \
V(Sentinel, transition_sentinel) \
V(Sentinel, unknown_constant) \
V(Sentinel, non_constant) \
V(Sentinel, optimized_out) \
@ -7580,9 +7576,6 @@ class ContextScope : public Object {
// - Object::sentinel() is a value that cannot be produced by Dart code.
// It can be used to mark special values, for example to distinguish
// "uninitialized" fields.
// - Object::transition_sentinel() is a value marking that we are transitioning
// from sentinel, e.g., computing a field value. Used to detect circular
// initialization of static fields.
// - Object::unknown_constant() and Object::non_constant() are optimizing
// compiler's constant propagation constants.
// - Object::optimized_out() result from deopt environment pruning or failure

View file

@ -1137,9 +1137,6 @@ class Pass2Visitor : public ObjectVisitor,
if (obj == Object::sentinel().ptr()) {
writer_->WriteUnsigned(kNameData);
writer_->WriteUtf8("uninitialized");
} else if (obj == Object::transition_sentinel().ptr()) {
writer_->WriteUnsigned(kNameData);
writer_->WriteUtf8("initializing");
} else {
writer_->WriteUnsigned(kNoData);
}

View file

@ -1129,12 +1129,6 @@ void Sentinel::PrintJSONImpl(JSONStream* stream, bool ref) const {
jsobj.AddProperty("kind", "NotInitialized");
jsobj.AddProperty("valueAsString", "<not initialized>");
return;
} else if (ptr() == Object::transition_sentinel().ptr()) {
JSONObject jsobj(stream);
jsobj.AddProperty("type", "Sentinel");
jsobj.AddProperty("kind", "BeingInitialized");
jsobj.AddProperty("valueAsString", "<being initialized>");
return;
} else if (ptr() == Object::optimized_out().ptr()) {
JSONObject jsobj(stream);
jsobj.AddProperty("type", "Sentinel");

View file

@ -6367,16 +6367,6 @@ ISOLATE_UNIT_TEST_CASE(PrintJSONPrimitives) {
"\"valueAsString\":\"<not initialized>\"}",
js.ToCString());
}
// Transition sentinel reference
{
JSONStream js;
Object::transition_sentinel().PrintJSON(&js, true);
EXPECT_STREQ(
"{\"type\":\"Sentinel\","
"\"kind\":\"BeingInitialized\","
"\"valueAsString\":\"<being initialized>\"}",
js.ToCString());
}
}
#endif // !PRODUCT

View file

@ -3826,8 +3826,7 @@ DEFINE_RUNTIME_ENTRY(InitInstanceField, 2) {
Object& result = Object::Handle(zone, field.InitializeInstance(instance));
ThrowIfError(result);
result = instance.GetField(field);
ASSERT((result.ptr() != Object::sentinel().ptr()) &&
(result.ptr() != Object::transition_sentinel().ptr()));
ASSERT(result.ptr() != Object::sentinel().ptr());
arguments.SetReturn(result);
}
@ -3836,8 +3835,7 @@ DEFINE_RUNTIME_ENTRY(InitStaticField, 1) {
Object& result = Object::Handle(zone, field.InitializeStatic());
ThrowIfError(result);
result = field.StaticValue();
ASSERT((result.ptr() != Object::sentinel().ptr()) &&
(result.ptr() != Object::transition_sentinel().ptr()));
ASSERT(result.ptr() != Object::sentinel().ptr());
arguments.SetReturn(result);
}

View file

@ -1891,9 +1891,6 @@ _Instance_.
If the field is uninitialized, the _value_ will be the
_NotInitialized_ [Sentinel](#sentinel).
If the field is being initialized, the _value_ will be the
_BeingInitialized_ [Sentinel](#sentinel).
### BoundVariable
```
@ -1918,9 +1915,6 @@ in a _Frame_.
If the variable is uninitialized, the _value_ will be the
_NotInitialized_ [Sentinel](#sentinel).
If the variable is being initialized, the _value_ will be the
_BeingInitialized_ [Sentinel](#sentinel).
If the variable has been optimized out by the compiler, the _value_
will be the _OptimizedOut_ [Sentinel](#sentinel).
@ -4188,7 +4182,7 @@ enum SentinelKind {
// Indicates that a variable or field has not been initialized.
NotInitialized,
// Indicates that a variable or field is in the process of being initialized.
// Deprecated, no longer used.
BeingInitialized,
// Indicates that a variable has been eliminated by the optimizing compiler.

View file

@ -291,7 +291,6 @@ class ObjectPointerVisitor;
V(_ConstMap, "_ConstMap") \
V(_ConstSet, "_ConstSet") \
V(_ControllerSubscription, "_ControllerSubscription") \
V(_CyclicInitializationError, "_CyclicInitializationError") \
V(_DeletedEnumPrefix, "Deleted enum value from ") \
V(_DeletedEnumSentinel, "_deleted_enum_sentinel") \
V(_Double, "_Double") \

View file

@ -132,30 +132,6 @@ class StateError {
}
}
/// Error thrown when a lazily initialized variable cannot be initialized.
///
/// Cyclic dependencies are no longer detected at runtime in null safe code.
/// Such code will fail in other ways instead,
/// possibly with a [StackOverflowError].
///
/// Will be removed when support for non-null-safe code is discontinued.
@Deprecated("Remove when no longer supporting non-null-safe code.")
class _CyclicInitializationError extends Error {
final String? variableName;
@pragma("vm:entry-point")
_CyclicInitializationError([this.variableName]);
String toString() {
var variableName = this.variableName;
return variableName == null
? "Reading static variable during its initialization"
: "Reading static variable '$variableName' during its initialization";
}
static _throwNew(String variableName) {
throw new _CyclicInitializationError(variableName);
}
}
@patch
class NoSuchMethodError {
final Object? _receiver;