[vm/shared] Perform deeply-immutable initialization runtime check.
When an initial value is assigned into a class tagged as deeply-immutable, perform runtime check of that value. This is needed to support proper initialization of the closures as part of deeply-immutable classes. BUG=https://github.com/dart-lang/sdk/issues/61962 TEST=run_isolate_group_run_test Change-Id: I550746c0d22ca06ffb89959e8384cc9e6d28d590 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/468200 Commit-Queue: Alexander Aprelev <aam@google.com> Reviewed-by: Slava Egorov <vegorov@google.com>
This commit is contained in:
committed by
Commit Queue
parent
fc3ce5a24e
commit
eb75c53d95
@@ -9,7 +9,7 @@ Deeply immutable instances can be shared across isolates within the same group.
|
||||
A deeply immutable type is a type for which all instances that have this type are deeply immutable.
|
||||
|
||||
This is useful for static checks on classes annotated `@pragma('vm:deeply-immutable')`.
|
||||
All the instance fields of such classes must have a deeply immutable type.
|
||||
If an instance field has a deeply immutable type, it doesn't require any runtime checks for the values it gets. Instance fields of function types are also allowed in `@pragma('vm:deeply-immutable')` classes, but runtime checks will ensure that only immutable values are captured by the inititalizing values.
|
||||
|
||||
A list of immutable types:
|
||||
|
||||
@@ -36,8 +36,9 @@ instances can also be deeply immutable while their type is not deeply immutable:
|
||||
* `StackTrace` (can be implemented externally, not `final`)
|
||||
* `Type` (can be implemented externally, not `final`)
|
||||
* const object (the class can be deeply immutable)
|
||||
* function types (closures can capture arbritary contents)
|
||||
|
||||
This means users cannot mark classes with fields typed with these types as `@pragma('vm:deeply-immutable')`.
|
||||
This means users cannot mark classes with fields typed with these types as `@pragma('vm:deeply-immutable')`. Only exception is the function types, which are still allowed, but incur runtime-check.
|
||||
|
||||
## Shallowly immutable instances
|
||||
|
||||
@@ -50,8 +51,8 @@ The VM also has shallow immutability.
|
||||
|
||||
### Deeply and shallowly immutable instances
|
||||
|
||||
The `UntaggedObject::ImmutableBit` tracks whether an instance is deeply or shallowly immutable at runtime.
|
||||
For shallow immutable objects, the VM needs to know the layout and what to check when to check for to check deep immutability at runtime.
|
||||
The `UntaggedObject::ShallowImmutableBit` and `UntaggedObject::DeeplyImmutableBit` track whether an instance is shallowly or deeply immutable at runtime.
|
||||
For shallow immutable objects, the VM needs to know the layout and what to check when to check for to check deep immutability at runtime. During objects lifecycle if the object is inspected, it can get it shallow-immutable bit upgraded to deeply-immutable bit.
|
||||
|
||||
### Deeply immutable types
|
||||
|
||||
@@ -64,10 +65,10 @@ This bit can be set in two ways:
|
||||
|
||||
The `vm:deeply-immutable` pragma is added to classes of which their _type_ is deeply immutable.
|
||||
|
||||
This puts the following restrictions on these classes:
|
||||
This puts the following compile-time restrictions on these classes:
|
||||
|
||||
1. All instance fields must
|
||||
1. have a deeply immutable type,
|
||||
1. either have a deeply immutable or function type,
|
||||
2. be final, and
|
||||
3. be non-late.
|
||||
2. The class must be `final` or `sealed`.
|
||||
@@ -75,5 +76,9 @@ This puts the following restrictions on these classes:
|
||||
3. All subtypes must be deeply immutable.
|
||||
This ensures 1.1. can be trusted.
|
||||
4. The super type must be deeply immutable (except for Object).
|
||||
5. Context captured by closures must be deeply-immutable.
|
||||
|
||||
Compile-time restructions are enforced by [DeeplyImmutableValidator](../../pkg/vm/lib/transformations/ffi/deeply_immutable.dart).
|
||||
|
||||
Run-time checks are inserted to ensure that function-type instance fields are initialized with closure that only capture deeply-immutable values.
|
||||
|
||||
These restructions are enforced by [DeeplyImmutableValidator](../../pkg/vm/lib/transformations/ffi/deeply_immutable.dart).
|
||||
|
||||
@@ -127,8 +127,9 @@ DEFINE_FFI_NATIVE_ENTRY(IsolateGroup_runSync,
|
||||
|
||||
{
|
||||
DARTSCOPE(current_thread);
|
||||
FfiCallbackMetadata::EnsureTriviallyImmutable(
|
||||
current_thread->zone(), Object::Handle(Api::UnwrapHandle(closure)));
|
||||
auto& object =
|
||||
Object::Handle(current_thread->zone(), Api::UnwrapHandle(closure));
|
||||
object.EnsureDeeplyImmutable(current_thread->zone());
|
||||
}
|
||||
|
||||
Isolate* saved_isolate = current_thread->isolate();
|
||||
|
||||
@@ -619,6 +619,12 @@ DEFINE_NATIVE_ENTRY(Internal_loadDynamicModule, 0, 1) {
|
||||
#endif // defined(DART_DYNAMIC_MODULES)
|
||||
}
|
||||
|
||||
DEFINE_NATIVE_ENTRY(Internal_ensureDeeplyImmutable, 0, 1) {
|
||||
GET_NATIVE_ARGUMENT(Instance, value, arguments->NativeArgAt(0));
|
||||
value.EnsureDeeplyImmutable(zone);
|
||||
return value.ptr();
|
||||
}
|
||||
|
||||
DEFINE_NATIVE_ENTRY(InvocationMirror_unpackTypeArguments, 0, 2) {
|
||||
const TypeArguments& type_arguments =
|
||||
TypeArguments::CheckedHandle(zone, arguments->NativeArgAt(0));
|
||||
|
||||
@@ -30,6 +30,8 @@ class A<T> {
|
||||
dynamic m<H>(T a, H b) => this;
|
||||
}
|
||||
|
||||
List<String> list = <String>["Hello", "world"];
|
||||
|
||||
// When running with isolate groups enabled, we can share all of the following
|
||||
// objects.
|
||||
final sharableObjects = [
|
||||
@@ -123,8 +125,27 @@ final sharableObjects = [
|
||||
someFloat64x2: Float64x2(4.4, 5.5),
|
||||
someDeeplyImmutable: null,
|
||||
somePointer: Pointer.fromAddress(0x8badf00d),
|
||||
someClosure: () => 31,
|
||||
),
|
||||
somePointer: Pointer.fromAddress(0xdeadbeef),
|
||||
someClosure: () => 42,
|
||||
),
|
||||
DeeplyImmutable(
|
||||
someString: 'someString',
|
||||
someNullableString: 'someString',
|
||||
someInt: 3,
|
||||
someDouble: 3.3,
|
||||
someBool: false,
|
||||
someNull: null,
|
||||
someInt32x4: Int32x4(0, 1, 2, 3),
|
||||
someFloat32x4: Float32x4(0.0, 1.1, 2.2, 3.3),
|
||||
someFloat64x2: Float64x2(4.4, 5.5),
|
||||
someDeeplyImmutable: null,
|
||||
somePointer: Pointer.fromAddress(0xdeadbeef),
|
||||
someClosure: () {
|
||||
list.add("kuka");
|
||||
return list;
|
||||
},
|
||||
),
|
||||
];
|
||||
|
||||
@@ -282,6 +303,7 @@ final class DeeplyImmutable {
|
||||
final Float64x2 someFloat64x2;
|
||||
final DeeplyImmutable? someDeeplyImmutable;
|
||||
final Pointer somePointer;
|
||||
final Function() someClosure;
|
||||
|
||||
DeeplyImmutable({
|
||||
required this.someString,
|
||||
@@ -295,5 +317,6 @@ final class DeeplyImmutable {
|
||||
required this.someFloat64x2,
|
||||
this.someDeeplyImmutable,
|
||||
required this.somePointer,
|
||||
required this.someClosure,
|
||||
});
|
||||
}
|
||||
|
||||
+64
-61
@@ -168,7 +168,7 @@ class SerializationCluster : public ZoneAllocated {
|
||||
cid_(cid),
|
||||
target_instance_size_(target_instance_size),
|
||||
is_canonical_(is_canonical),
|
||||
is_immutable_(Object::ShouldHaveImmutabilityBitSet(cid)) {
|
||||
is_deeply_immutable_(Object::ShouldHaveDeeplyImmutabilityBitSet(cid)) {
|
||||
ASSERT(target_instance_size == kSizeVaries || target_instance_size >= 0);
|
||||
}
|
||||
virtual ~SerializationCluster() {}
|
||||
@@ -190,7 +190,7 @@ class SerializationCluster : public ZoneAllocated {
|
||||
const char* name() const { return name_; }
|
||||
intptr_t cid() const { return cid_; }
|
||||
bool is_canonical() const { return is_canonical_; }
|
||||
bool is_immutable() const { return is_immutable_; }
|
||||
bool is_deeply_immutable() const { return is_deeply_immutable_; }
|
||||
intptr_t size() const { return size_; }
|
||||
intptr_t num_objects() const { return num_objects_; }
|
||||
|
||||
@@ -208,7 +208,7 @@ class SerializationCluster : public ZoneAllocated {
|
||||
const intptr_t cid_;
|
||||
const intptr_t target_instance_size_;
|
||||
const bool is_canonical_;
|
||||
const bool is_immutable_;
|
||||
const bool is_deeply_immutable_;
|
||||
intptr_t size_ = 0;
|
||||
intptr_t num_objects_ = 0;
|
||||
intptr_t target_memory_size_ = 0;
|
||||
@@ -218,10 +218,10 @@ class DeserializationCluster : public ZoneAllocated {
|
||||
public:
|
||||
explicit DeserializationCluster(const char* name,
|
||||
bool is_canonical = false,
|
||||
bool is_immutable = false)
|
||||
bool is_deeply_immutable = false)
|
||||
: name_(name),
|
||||
is_canonical_(is_canonical),
|
||||
is_immutable_(is_immutable),
|
||||
is_deeply_immutable_(is_deeply_immutable),
|
||||
start_index_(-1),
|
||||
stop_index_(-1) {}
|
||||
virtual ~DeserializationCluster() {}
|
||||
@@ -248,14 +248,14 @@ class DeserializationCluster : public ZoneAllocated {
|
||||
|
||||
const char* name() const { return name_; }
|
||||
bool is_canonical() const { return is_canonical_; }
|
||||
bool is_immutable() const { return is_immutable_; }
|
||||
bool is_deeply_immutable() const { return is_deeply_immutable_; }
|
||||
|
||||
protected:
|
||||
void ReadAllocFixedSize(Deserializer* deserializer, intptr_t instance_size);
|
||||
|
||||
const char* const name_;
|
||||
const bool is_canonical_;
|
||||
const bool is_immutable_;
|
||||
const bool is_deeply_immutable_;
|
||||
// The range of the ref array that belongs to this cluster.
|
||||
intptr_t start_index_;
|
||||
intptr_t stop_index_;
|
||||
@@ -687,13 +687,13 @@ class Deserializer : public ThreadStackResource {
|
||||
intptr_t size,
|
||||
bool is_canonical = false) {
|
||||
InitializeHeader(raw, cid, size, is_canonical,
|
||||
ShouldHaveImmutabilityBitSetCid(cid));
|
||||
Object::ShouldHaveDeeplyImmutabilityBitSet(cid));
|
||||
}
|
||||
static void InitializeHeader(ObjectPtr raw,
|
||||
intptr_t cid,
|
||||
intptr_t size,
|
||||
bool is_canonical,
|
||||
bool is_immutable);
|
||||
bool is_deeply_immutable);
|
||||
|
||||
// Reads raw data (for basic types).
|
||||
// sizeof(T) must be in {1,2,4,8}.
|
||||
@@ -887,7 +887,7 @@ void Deserializer::InitializeHeader(ObjectPtr raw,
|
||||
intptr_t class_id,
|
||||
intptr_t size,
|
||||
bool is_canonical,
|
||||
bool is_immutable) {
|
||||
bool is_deeply_immutable) {
|
||||
ASSERT(Utils::IsAligned(size, kObjectAlignment));
|
||||
uword tags = 0;
|
||||
tags = UntaggedObject::ClassIdTag::update(class_id, tags);
|
||||
@@ -897,7 +897,9 @@ void Deserializer::InitializeHeader(ObjectPtr raw,
|
||||
tags = UntaggedObject::NotMarkedBit::update(true, tags);
|
||||
tags = UntaggedObject::OldAndNotRememberedBit::update(true, tags);
|
||||
tags = UntaggedObject::NewOrEvacuationCandidateBit::update(false, tags);
|
||||
tags = UntaggedObject::ImmutableBit::update(is_immutable, tags);
|
||||
tags = UntaggedObject::ShallowImmutableBit::update(
|
||||
Object::ShouldHaveShallowImmutabilityBitSet(class_id), tags);
|
||||
tags = UntaggedObject::DeeplyImmutableBit::update(is_deeply_immutable, tags);
|
||||
raw->untag()->tags_ = tags;
|
||||
}
|
||||
|
||||
@@ -906,9 +908,10 @@ void SerializationCluster::WriteAndMeasureAlloc(Serializer* serializer) {
|
||||
intptr_t start_size = serializer->bytes_written();
|
||||
intptr_t start_data = serializer->GetDataSize();
|
||||
intptr_t start_objects = serializer->next_ref_index();
|
||||
uint32_t tags = UntaggedObject::ClassIdTag::encode(cid_) |
|
||||
UntaggedObject::CanonicalBit::encode(is_canonical()) |
|
||||
UntaggedObject::ImmutableBit::encode(is_immutable());
|
||||
uint32_t tags =
|
||||
UntaggedObject::ClassIdTag::encode(cid_) |
|
||||
UntaggedObject::CanonicalBit::encode(is_canonical()) |
|
||||
UntaggedObject::DeeplyImmutableBit::encode(is_deeply_immutable());
|
||||
serializer->Write<uint32_t>(tags);
|
||||
WriteAlloc(serializer);
|
||||
intptr_t stop_size = serializer->bytes_written();
|
||||
@@ -4555,9 +4558,9 @@ class AbstractInstanceDeserializationCluster : public DeserializationCluster {
|
||||
protected:
|
||||
explicit AbstractInstanceDeserializationCluster(const char* name,
|
||||
bool is_canonical,
|
||||
bool is_immutable,
|
||||
bool is_deeply_immutable,
|
||||
bool is_root_unit)
|
||||
: DeserializationCluster(name, is_canonical, is_immutable),
|
||||
: DeserializationCluster(name, is_canonical, is_deeply_immutable),
|
||||
is_root_unit_(is_root_unit) {}
|
||||
|
||||
const bool is_root_unit_;
|
||||
@@ -4584,11 +4587,11 @@ class InstanceDeserializationCluster
|
||||
public:
|
||||
explicit InstanceDeserializationCluster(intptr_t cid,
|
||||
bool is_canonical,
|
||||
bool is_immutable,
|
||||
bool is_deeply_immutable,
|
||||
bool is_root_unit)
|
||||
: AbstractInstanceDeserializationCluster("Instance",
|
||||
is_canonical,
|
||||
is_immutable,
|
||||
is_deeply_immutable,
|
||||
is_root_unit),
|
||||
cid_(cid) {}
|
||||
~InstanceDeserializationCluster() {}
|
||||
@@ -4611,7 +4614,7 @@ class InstanceDeserializationCluster
|
||||
|
||||
const intptr_t cid = cid_;
|
||||
const bool mark_canonical = is_root_unit_ && is_canonical();
|
||||
const bool is_immutable = is_immutable_;
|
||||
const bool is_deeply_immutable = is_deeply_immutable_;
|
||||
intptr_t next_field_offset = next_field_offset_in_words_
|
||||
<< kCompressedWordSizeLog2;
|
||||
intptr_t instance_size = Object::RoundedAllocationSize(
|
||||
@@ -4621,7 +4624,7 @@ class InstanceDeserializationCluster
|
||||
for (intptr_t id = start_index_, n = stop_index_; id < n; id++) {
|
||||
InstancePtr instance = static_cast<InstancePtr>(d.Ref(id));
|
||||
Deserializer::InitializeHeader(instance, cid, instance_size,
|
||||
mark_canonical, is_immutable);
|
||||
mark_canonical, is_deeply_immutable);
|
||||
intptr_t offset = Instance::NextFieldOffset();
|
||||
while (offset < next_field_offset) {
|
||||
if (unboxed_fields_bitmap.Get(offset / kCompressedWordSize)) {
|
||||
@@ -5248,11 +5251,11 @@ class ClosureDeserializationCluster
|
||||
: public AbstractInstanceDeserializationCluster {
|
||||
public:
|
||||
explicit ClosureDeserializationCluster(bool is_canonical,
|
||||
bool is_immutable,
|
||||
bool is_deeply_immutable,
|
||||
bool is_root_unit)
|
||||
: AbstractInstanceDeserializationCluster("Closure",
|
||||
is_canonical,
|
||||
is_immutable,
|
||||
is_deeply_immutable,
|
||||
is_root_unit) {}
|
||||
~ClosureDeserializationCluster() {}
|
||||
|
||||
@@ -5345,12 +5348,10 @@ class MintSerializationCluster : public SerializationCluster {
|
||||
class MintDeserializationCluster
|
||||
: public AbstractInstanceDeserializationCluster {
|
||||
public:
|
||||
explicit MintDeserializationCluster(bool is_canonical,
|
||||
bool is_immutable,
|
||||
bool is_root_unit)
|
||||
explicit MintDeserializationCluster(bool is_canonical, bool is_root_unit)
|
||||
: AbstractInstanceDeserializationCluster("int",
|
||||
is_canonical,
|
||||
is_immutable,
|
||||
/*is_deeply_immutable=*/true,
|
||||
is_root_unit) {}
|
||||
~MintDeserializationCluster() {}
|
||||
|
||||
@@ -5417,14 +5418,12 @@ class DoubleSerializationCluster : public SerializationCluster {
|
||||
class DoubleDeserializationCluster
|
||||
: public AbstractInstanceDeserializationCluster {
|
||||
public:
|
||||
explicit DoubleDeserializationCluster(bool is_canonical,
|
||||
bool is_immutable,
|
||||
bool is_root_unit)
|
||||
explicit DoubleDeserializationCluster(bool is_canonical, bool is_root_unit)
|
||||
: AbstractInstanceDeserializationCluster("double",
|
||||
is_canonical,
|
||||
is_immutable,
|
||||
/*is_deeply_immutable=*/true,
|
||||
is_root_unit) {
|
||||
ASSERT(Object::ShouldHaveImmutabilityBitSet(kDoubleCid));
|
||||
ASSERT(Object::ShouldHaveDeeplyImmutabilityBitSet(kDoubleCid));
|
||||
}
|
||||
~DoubleDeserializationCluster() {}
|
||||
|
||||
@@ -5492,11 +5491,10 @@ class Simd128DeserializationCluster
|
||||
public:
|
||||
explicit Simd128DeserializationCluster(intptr_t cid,
|
||||
bool is_canonical,
|
||||
bool is_immutable,
|
||||
bool is_root_unit)
|
||||
: AbstractInstanceDeserializationCluster("Simd128",
|
||||
is_canonical,
|
||||
is_immutable,
|
||||
/*is_deeply_immutable=*/true,
|
||||
is_root_unit),
|
||||
cid_(cid) {}
|
||||
~Simd128DeserializationCluster() {}
|
||||
@@ -5511,7 +5509,7 @@ class Simd128DeserializationCluster
|
||||
Deserializer::Local d(d_);
|
||||
const intptr_t cid = cid_;
|
||||
const bool mark_canonical = is_root_unit_ && is_canonical();
|
||||
const bool is_immutable = ShouldHaveImmutabilityBitSetCid(cid);
|
||||
const bool is_immutable = Object::ShouldHaveDeeplyImmutabilityBitSet(cid);
|
||||
for (intptr_t id = start_index_, n = stop_index_; id < n; id++) {
|
||||
ObjectPtr vector = d.Ref(id);
|
||||
Deserializer::InitializeHeader(vector, cid, Int32x4::InstanceSize(),
|
||||
@@ -5641,11 +5639,11 @@ class RecordDeserializationCluster
|
||||
: public AbstractInstanceDeserializationCluster {
|
||||
public:
|
||||
explicit RecordDeserializationCluster(bool is_canonical,
|
||||
bool is_immutable,
|
||||
bool is_deeply_immutable,
|
||||
bool is_root_unit)
|
||||
: AbstractInstanceDeserializationCluster("Record",
|
||||
is_canonical,
|
||||
is_immutable,
|
||||
is_deeply_immutable,
|
||||
is_root_unit) {}
|
||||
~RecordDeserializationCluster() {}
|
||||
|
||||
@@ -6278,11 +6276,11 @@ class MapDeserializationCluster
|
||||
public:
|
||||
explicit MapDeserializationCluster(intptr_t cid,
|
||||
bool is_canonical,
|
||||
bool is_immutable,
|
||||
bool is_deeply_immutable,
|
||||
bool is_root_unit)
|
||||
: AbstractInstanceDeserializationCluster("Map",
|
||||
is_canonical,
|
||||
is_immutable,
|
||||
is_deeply_immutable,
|
||||
is_root_unit),
|
||||
cid_(cid) {}
|
||||
~MapDeserializationCluster() {}
|
||||
@@ -6355,11 +6353,11 @@ class SetDeserializationCluster
|
||||
public:
|
||||
explicit SetDeserializationCluster(intptr_t cid,
|
||||
bool is_canonical,
|
||||
bool is_immutable,
|
||||
bool is_deeply_immutable,
|
||||
bool is_root_unit)
|
||||
: AbstractInstanceDeserializationCluster("Set",
|
||||
is_canonical,
|
||||
is_immutable,
|
||||
is_deeply_immutable,
|
||||
is_root_unit),
|
||||
cid_(cid) {}
|
||||
~SetDeserializationCluster() {}
|
||||
@@ -6497,11 +6495,11 @@ class ArrayDeserializationCluster
|
||||
public:
|
||||
explicit ArrayDeserializationCluster(intptr_t cid,
|
||||
bool is_canonical,
|
||||
bool is_immutable,
|
||||
bool is_deeply_immutable,
|
||||
bool is_root_unit)
|
||||
: AbstractInstanceDeserializationCluster("Array",
|
||||
is_canonical,
|
||||
is_immutable,
|
||||
is_deeply_immutable,
|
||||
is_root_unit),
|
||||
cid_(cid) {}
|
||||
~ArrayDeserializationCluster() {}
|
||||
@@ -6521,7 +6519,7 @@ class ArrayDeserializationCluster
|
||||
|
||||
const intptr_t cid = cid_;
|
||||
const bool stamp_canonical = is_root_unit_ && is_canonical();
|
||||
const bool is_immutable = ShouldHaveImmutabilityBitSetCid(cid);
|
||||
const bool is_immutable = Object::ShouldHaveDeeplyImmutabilityBitSet(cid);
|
||||
for (intptr_t id = start_index_, n = stop_index_; id < n; id++) {
|
||||
ArrayPtr array = static_cast<ArrayPtr>(d.Ref(id));
|
||||
const intptr_t length = d.ReadUnsigned();
|
||||
@@ -9013,11 +9011,12 @@ DeserializationCluster* Deserializer::ReadCluster() {
|
||||
const uint32_t tags = Read<uint32_t>();
|
||||
const intptr_t cid = UntaggedObject::ClassIdTag::decode(tags);
|
||||
const bool is_canonical = UntaggedObject::CanonicalBit::decode(tags);
|
||||
const bool is_immutable = UntaggedObject::ImmutableBit::decode(tags);
|
||||
const bool is_deeply_immutable =
|
||||
UntaggedObject::DeeplyImmutableBit::decode(tags);
|
||||
Zone* Z = zone_;
|
||||
if (cid >= kNumPredefinedCids || cid == kInstanceCid) {
|
||||
return new (Z) InstanceDeserializationCluster(
|
||||
cid, is_canonical, is_immutable, !is_non_root_unit_);
|
||||
cid, is_canonical, is_deeply_immutable, !is_non_root_unit_);
|
||||
}
|
||||
if (IsTypedDataViewClassId(cid)) {
|
||||
ASSERT(!is_canonical);
|
||||
@@ -9151,25 +9150,28 @@ DeserializationCluster* Deserializer::ReadCluster() {
|
||||
return new (Z)
|
||||
TypeParameterDeserializationCluster(is_canonical, !is_non_root_unit_);
|
||||
case kClosureCid:
|
||||
return new (Z) ClosureDeserializationCluster(is_canonical, is_immutable,
|
||||
!is_non_root_unit_);
|
||||
return new (Z) ClosureDeserializationCluster(
|
||||
is_canonical, is_deeply_immutable, !is_non_root_unit_);
|
||||
case kMintCid:
|
||||
return new (Z) MintDeserializationCluster(is_canonical, is_immutable,
|
||||
!is_non_root_unit_);
|
||||
RELEASE_ASSERT(is_deeply_immutable);
|
||||
return new (Z)
|
||||
MintDeserializationCluster(is_canonical, !is_non_root_unit_);
|
||||
case kDoubleCid:
|
||||
return new (Z) DoubleDeserializationCluster(is_canonical, is_immutable,
|
||||
!is_non_root_unit_);
|
||||
RELEASE_ASSERT(is_deeply_immutable);
|
||||
return new (Z)
|
||||
DoubleDeserializationCluster(is_canonical, !is_non_root_unit_);
|
||||
case kInt32x4Cid:
|
||||
case kFloat32x4Cid:
|
||||
case kFloat64x2Cid:
|
||||
return new (Z) Simd128DeserializationCluster(
|
||||
cid, is_canonical, is_immutable, !is_non_root_unit_);
|
||||
RELEASE_ASSERT(is_deeply_immutable);
|
||||
return new (Z)
|
||||
Simd128DeserializationCluster(cid, is_canonical, !is_non_root_unit_);
|
||||
case kGrowableObjectArrayCid:
|
||||
ASSERT(!is_canonical);
|
||||
return new (Z) GrowableObjectArrayDeserializationCluster();
|
||||
case kRecordCid:
|
||||
return new (Z) RecordDeserializationCluster(is_canonical, is_immutable,
|
||||
!is_non_root_unit_);
|
||||
return new (Z) RecordDeserializationCluster(
|
||||
is_canonical, is_deeply_immutable, !is_non_root_unit_);
|
||||
case kStackTraceCid:
|
||||
ASSERT(!is_canonical);
|
||||
return new (Z) StackTraceDeserializationCluster();
|
||||
@@ -9184,19 +9186,20 @@ DeserializationCluster* Deserializer::ReadCluster() {
|
||||
UNREACHABLE();
|
||||
case kConstMapCid:
|
||||
return new (Z) MapDeserializationCluster(
|
||||
kConstMapCid, is_canonical, is_immutable, !is_non_root_unit_);
|
||||
kConstMapCid, is_canonical, is_deeply_immutable, !is_non_root_unit_);
|
||||
case kSetCid:
|
||||
// We do not have mutable hash sets in snapshots.
|
||||
UNREACHABLE();
|
||||
case kConstSetCid:
|
||||
return new (Z) SetDeserializationCluster(
|
||||
kConstSetCid, is_canonical, is_immutable, !is_non_root_unit_);
|
||||
kConstSetCid, is_canonical, is_deeply_immutable, !is_non_root_unit_);
|
||||
case kArrayCid:
|
||||
return new (Z) ArrayDeserializationCluster(
|
||||
kArrayCid, is_canonical, is_immutable, !is_non_root_unit_);
|
||||
kArrayCid, is_canonical, is_deeply_immutable, !is_non_root_unit_);
|
||||
case kImmutableArrayCid:
|
||||
return new (Z) ArrayDeserializationCluster(
|
||||
kImmutableArrayCid, is_canonical, is_immutable, !is_non_root_unit_);
|
||||
return new (Z)
|
||||
ArrayDeserializationCluster(kImmutableArrayCid, is_canonical,
|
||||
is_deeply_immutable, !is_non_root_unit_);
|
||||
case kWeakArrayCid:
|
||||
return new (Z) WeakArrayDeserializationCluster();
|
||||
case kStringCid:
|
||||
@@ -9207,7 +9210,7 @@ DeserializationCluster* Deserializer::ReadCluster() {
|
||||
CLASS_LIST_FFI_TYPE_MARKER(CASE_FFI_CID)
|
||||
#undef CASE_FFI_CID
|
||||
return new (Z) InstanceDeserializationCluster(
|
||||
cid, is_canonical, is_immutable, !is_non_root_unit_);
|
||||
cid, is_canonical, is_deeply_immutable, !is_non_root_unit_);
|
||||
case kDeltaEncodedTypedDataCid:
|
||||
return new (Z) DeltaEncodedTypedDataDeserializationCluster();
|
||||
default:
|
||||
|
||||
@@ -279,6 +279,7 @@ namespace dart {
|
||||
V(Internal_deoptimizeFunctionsOnStack, 0) \
|
||||
V(Internal_allocateObjectInstructionsStart, 0) \
|
||||
V(Internal_allocateObjectInstructionsEnd, 0) \
|
||||
V(Internal_ensureDeeplyImmutable, 1) \
|
||||
V(InvocationMirror_unpackTypeArguments, 2) \
|
||||
V(NoSuchMethodError_existingMethodSignature, 3) \
|
||||
V(ThreadLocal_allocateId, 0) \
|
||||
|
||||
@@ -2311,6 +2311,7 @@ void BytecodeReaderHelper::ReadClassDeclaration(const Class& cls) {
|
||||
const int kIsBaseClassFlag = 1 << 11;
|
||||
const int kIsInterfaceFlag = 1 << 12;
|
||||
const int kIsFinalFlag = 1 << 13;
|
||||
const int kIsDeeplyImmutableFlag = 1 << 14;
|
||||
|
||||
// Class is allocated when reading library declaration in
|
||||
// BytecodeReaderHelper::ReadLibraryDeclaration.
|
||||
@@ -2370,6 +2371,9 @@ void BytecodeReaderHelper::ReadClassDeclaration(const Class& cls) {
|
||||
if ((flags & kIsFinalFlag) != 0) {
|
||||
cls.set_is_final();
|
||||
}
|
||||
if ((flags & kIsDeeplyImmutableFlag) != 0) {
|
||||
cls.set_is_deeply_immutable(true);
|
||||
}
|
||||
|
||||
intptr_t num_type_arguments = 0;
|
||||
if ((flags & kHasTypeArgumentsFlag) != 0) {
|
||||
|
||||
@@ -599,13 +599,6 @@ inline bool IsShallowlyImmutableCid(intptr_t predefined_cid) {
|
||||
IsUnmodifiableTypedDataViewClassId(predefined_cid);
|
||||
}
|
||||
|
||||
// See documentation on ImmutableBit in raw_object.h
|
||||
inline bool ShouldHaveImmutabilityBitSetCid(intptr_t predefined_cid) {
|
||||
ASSERT(predefined_cid < kNumPredefinedCids);
|
||||
return IsDeeplyImmutableCid(predefined_cid) ||
|
||||
IsShallowlyImmutableCid(predefined_cid);
|
||||
}
|
||||
|
||||
inline bool IsFfiTypeClassId(intptr_t index) {
|
||||
switch (index) {
|
||||
case kPointerCid:
|
||||
|
||||
@@ -287,6 +287,9 @@ void ConstantPropagator::VisitGuardFieldLength(GuardFieldLengthInstr* instr) {}
|
||||
|
||||
void ConstantPropagator::VisitGuardFieldType(GuardFieldTypeInstr* instr) {}
|
||||
|
||||
void ConstantPropagator::VisitCheckFieldImmutability(
|
||||
CheckFieldImmutabilityInstr* instr) {}
|
||||
|
||||
void ConstantPropagator::VisitCheckSmi(CheckSmiInstr* instr) {}
|
||||
|
||||
void ConstantPropagator::VisitTailCall(TailCallInstr* instr) {}
|
||||
|
||||
@@ -359,8 +359,8 @@ class FieldAccessErrorSlowPath : public ThrowErrorSlowPathCode {
|
||||
class CheckedStoreIntoSharedSlowPath
|
||||
: public TemplateSlowPathCode<StoreStaticFieldInstr> {
|
||||
public:
|
||||
explicit CheckedStoreIntoSharedSlowPath(StoreStaticFieldInstr* instruction,
|
||||
Register value)
|
||||
CheckedStoreIntoSharedSlowPath(StoreStaticFieldInstr* instruction,
|
||||
Register value)
|
||||
: TemplateSlowPathCode(instruction), value_(value) {}
|
||||
|
||||
virtual void EmitNativeCode(FlowGraphCompiler* compiler);
|
||||
@@ -375,6 +375,20 @@ class CheckedStoreIntoSharedSlowPath
|
||||
Register value() const { return value_; }
|
||||
};
|
||||
|
||||
class EnsureDeeplyImmutableSlowPath : public TemplateSlowPathCode<Instruction> {
|
||||
public:
|
||||
EnsureDeeplyImmutableSlowPath(CheckFieldImmutabilityInstr* instruction,
|
||||
Register value)
|
||||
: TemplateSlowPathCode(instruction), value_(value) {}
|
||||
|
||||
virtual void EmitNativeCode(FlowGraphCompiler* compiler);
|
||||
|
||||
private:
|
||||
Register value_;
|
||||
|
||||
Register value() const { return value_; }
|
||||
};
|
||||
|
||||
enum class TypeTestOutcome { kConclusive, kNotConclusive };
|
||||
|
||||
class FlowGraphCompiler : public ValueObject {
|
||||
|
||||
@@ -1057,6 +1057,11 @@ bool GuardFieldTypeInstr::AttributesEqual(const Instruction& other) const {
|
||||
return field().ptr() == other.AsGuardFieldType()->field().ptr();
|
||||
}
|
||||
|
||||
bool CheckFieldImmutabilityInstr::AttributesEqual(
|
||||
const Instruction& other) const {
|
||||
return field().ptr() == other.AsCheckFieldImmutability()->field().ptr();
|
||||
}
|
||||
|
||||
Instruction* AssertSubtypeInstr::Canonicalize(FlowGraph* flow_graph) {
|
||||
// If all inputs needed to check instantiation are constant, instantiate the
|
||||
// sub and super type and remove the instruction if the subtype test succeeds.
|
||||
@@ -3944,6 +3949,10 @@ Instruction* GuardFieldTypeInstr::Canonicalize(FlowGraph* flow_graph) {
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
Instruction* CheckFieldImmutabilityInstr::Canonicalize(FlowGraph* flow_graph) {
|
||||
return this;
|
||||
}
|
||||
|
||||
Instruction* CheckSmiInstr::Canonicalize(FlowGraph* flow_graph) {
|
||||
return (value()->Type()->ToCid() == kSmiCid) ? nullptr : this;
|
||||
}
|
||||
@@ -6437,6 +6446,34 @@ void CheckedStoreIntoSharedSlowPath::EmitNativeCode(
|
||||
__ Jump(exit_label());
|
||||
}
|
||||
|
||||
void EnsureDeeplyImmutableSlowPath::EmitNativeCode(
|
||||
FlowGraphCompiler* compiler) {
|
||||
__ Comment("EnsureDeeplyImmutableSlowPath");
|
||||
__ Bind(entry_label());
|
||||
|
||||
LocationSummary* locs = instruction()->locs();
|
||||
locs->live_registers()->Remove(locs->out(0));
|
||||
|
||||
compiler->SaveLiveRegisters(locs);
|
||||
|
||||
auto slow_path_env =
|
||||
compiler->SlowPathEnvironmentFor(instruction(), /*num_slow_path_args=*/0);
|
||||
|
||||
#if defined(TARGET_ARCH_IA32)
|
||||
__ MoveRegister(EnsureDeeplyImmutableStubABI::kValueReg, value());
|
||||
#else
|
||||
ASSERT(value() == EnsureDeeplyImmutableStubABI::kValueReg);
|
||||
#endif
|
||||
|
||||
compiler->GenerateStubCall(instruction()->source(),
|
||||
StubCode::EnsureDeeplyImmutable(),
|
||||
UntaggedPcDescriptors::kOther, locs,
|
||||
instruction()->deopt_id(), slow_path_env);
|
||||
|
||||
compiler->RestoreLiveRegisters(instruction()->locs());
|
||||
__ Jump(exit_label());
|
||||
}
|
||||
|
||||
void UnboxInstr::EmitLoadFromBoxWithDeopt(FlowGraphCompiler* compiler) {
|
||||
const intptr_t box_cid = BoxCid();
|
||||
ASSERT(box_cid != kSmiCid); // Should never reach here with Smi-able ints.
|
||||
|
||||
@@ -520,6 +520,7 @@ struct InstrAttrs {
|
||||
M(GuardFieldClass, _) \
|
||||
M(GuardFieldLength, _) \
|
||||
M(GuardFieldType, _) \
|
||||
M(CheckFieldImmutability, _) \
|
||||
M(IfThenElse, kNoGC) \
|
||||
M(MaterializeObject, _) \
|
||||
M(TestInt, kNoGC) \
|
||||
@@ -6608,6 +6609,55 @@ class GuardFieldTypeInstr : public GuardFieldInstr {
|
||||
DISALLOW_COPY_AND_ASSIGN(GuardFieldTypeInstr);
|
||||
};
|
||||
|
||||
// Throws if [value] is not immutable.
|
||||
//
|
||||
// This is used when it's impossible to conclude from the variable static type
|
||||
// whether value is immutable(in case of closures, for example).
|
||||
class CheckFieldImmutabilityInstr : public TemplateInstruction<1, Throws> {
|
||||
public:
|
||||
CheckFieldImmutabilityInstr(Value* value,
|
||||
const Field& field,
|
||||
intptr_t deopt_id)
|
||||
: TemplateInstruction(deopt_id), field_(field) {
|
||||
SetInputAt(0, value);
|
||||
CheckField(field);
|
||||
}
|
||||
|
||||
Value* value() const { return inputs_[0]; }
|
||||
enum {
|
||||
kValue = 0,
|
||||
};
|
||||
|
||||
const Field& field() const { return field_; }
|
||||
|
||||
DECLARE_INSTRUCTION(CheckFieldImmutability)
|
||||
|
||||
virtual bool ComputeCanDeoptimize() const { return false; }
|
||||
virtual bool ComputeCanDeoptimizeAfterCall() const {
|
||||
return !CompilerState::Current().is_aot();
|
||||
}
|
||||
virtual bool CanBecomeDeoptimizationTarget() const { return true; }
|
||||
|
||||
virtual bool AllowsCSE() const { return true; }
|
||||
virtual bool HasUnknownSideEffects() const { return false; }
|
||||
|
||||
virtual Instruction* Canonicalize(FlowGraph* flow_graph);
|
||||
|
||||
virtual bool AttributesEqual(const Instruction& other) const;
|
||||
|
||||
PRINT_OPERANDS_TO_SUPPORT
|
||||
|
||||
#define FIELD_LIST(F) F(const Field&, field_)
|
||||
|
||||
DECLARE_INSTRUCTION_SERIALIZABLE_FIELDS(CheckFieldImmutabilityInstr,
|
||||
TemplateInstruction,
|
||||
FIELD_LIST)
|
||||
#undef FIELD_LIST
|
||||
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(CheckFieldImmutabilityInstr);
|
||||
};
|
||||
|
||||
enum class SlowPathOnSentinelValue {
|
||||
kDoNothing,
|
||||
kThrowAccessError, // This is part of shared field implementation.
|
||||
|
||||
@@ -2776,6 +2776,39 @@ void GuardFieldLengthInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
|
||||
|
||||
DEFINE_UNIMPLEMENTED_INSTRUCTION(GuardFieldTypeInstr)
|
||||
|
||||
LocationSummary* CheckFieldImmutabilityInstr::MakeLocationSummary(
|
||||
Zone* zone,
|
||||
bool opt) const {
|
||||
const intptr_t kNumInputs = 1;
|
||||
const intptr_t kNumTemps = 1;
|
||||
LocationSummary* summary = new (zone) LocationSummary(
|
||||
zone, kNumInputs, kNumTemps, LocationSummary::kCallOnSlowPath);
|
||||
summary->set_in(
|
||||
0, Location::RegisterLocation(EnsureDeeplyImmutableStubABI::kValueReg));
|
||||
summary->set_temp(
|
||||
0, Location::RegisterLocation(EnsureDeeplyImmutableStubABI::kTempReg));
|
||||
return summary;
|
||||
}
|
||||
|
||||
void CheckFieldImmutabilityInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
|
||||
const Register value = locs()->in(0).reg();
|
||||
const Register temp = locs()->temp(0).reg();
|
||||
|
||||
auto slow_path = new EnsureDeeplyImmutableSlowPath(this, value);
|
||||
compiler->AddSlowPathCode(slow_path);
|
||||
|
||||
__ BranchIfSmi(value, slow_path->exit_label(),
|
||||
compiler::Assembler::kNearJump);
|
||||
__ ldrb(temp, compiler::FieldAddress(
|
||||
value, compiler::target::Object::tags_offset()));
|
||||
__ TestImmediate(temp,
|
||||
1 << compiler::target::UntaggedObject::kDeeplyImmutableBit);
|
||||
// If immutability bit is not set, go to runtime.
|
||||
__ b(slow_path->entry_label(), ZERO);
|
||||
|
||||
__ Bind(slow_path->exit_label());
|
||||
}
|
||||
|
||||
LocationSummary* LoadCodeUnitsInstr::MakeLocationSummary(Zone* zone,
|
||||
bool opt) const {
|
||||
const bool might_box = (representation() == kTagged) && !can_pack_into_smi();
|
||||
@@ -2946,17 +2979,11 @@ void StoreStaticFieldInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
|
||||
1 << compiler::target::UntaggedObject::kCanonicalBit);
|
||||
// If canonical bit is set, no need for runtime check.
|
||||
__ b(&allow_store, NOT_ZERO);
|
||||
__ TestImmediate(temp,
|
||||
1 << compiler::target::UntaggedObject::kImmutableBit);
|
||||
__ TestImmediate(
|
||||
temp, 1 << compiler::target::UntaggedObject::kDeeplyImmutableBit);
|
||||
// If immutability bit is not set, go to runtime.
|
||||
__ b(checked_store_into_shared_slow_path->entry_label(), ZERO);
|
||||
|
||||
// If immutability bit is set, skip runtime unless it's a Closure
|
||||
// (see raw_object.h ImmutableBit description for deep vs shallow).
|
||||
__ LoadClassId(temp, value);
|
||||
__ CompareImmediate(temp, kClosureCid);
|
||||
__ b(checked_store_into_shared_slow_path->entry_label(), EQ);
|
||||
|
||||
__ Bind(&allow_store);
|
||||
}
|
||||
}
|
||||
@@ -6339,8 +6366,11 @@ void CheckWritableInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
|
||||
__ ldr(TMP, compiler::FieldAddress(locs()->in(0).reg(),
|
||||
compiler::target::Object::tags_offset()));
|
||||
// In the first byte.
|
||||
ASSERT(compiler::target::UntaggedObject::kImmutableBit < 8);
|
||||
__ TestImmediate(TMP, 1 << compiler::target::UntaggedObject::kImmutableBit);
|
||||
ASSERT(compiler::target::UntaggedObject::kDeeplyImmutableBit < 8);
|
||||
ASSERT(compiler::target::UntaggedObject::kShallowImmutableBit < 8);
|
||||
__ TestImmediate(
|
||||
TMP, 1 << compiler::target::UntaggedObject::kDeeplyImmutableBit |
|
||||
1 << compiler::target::UntaggedObject::kShallowImmutableBit);
|
||||
__ b(slow_path->entry_label(), NOT_ZERO);
|
||||
}
|
||||
|
||||
|
||||
@@ -2559,13 +2559,7 @@ void StoreStaticFieldInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
|
||||
compiler::target::UntaggedObject::kCanonicalBit);
|
||||
// If immutability bit is not set, go to runtime.
|
||||
__ tbz(checked_store_into_shared_slow_path->entry_label(), temp,
|
||||
compiler::target::UntaggedObject::kImmutableBit);
|
||||
|
||||
// If immutability bit is set, skip runtime unless it's a Closure
|
||||
// (see raw_object.h ImmutableBit description for deep vs shallow).
|
||||
__ LoadClassId(temp, value);
|
||||
__ CompareImmediate(temp, kClosureCid);
|
||||
__ b(checked_store_into_shared_slow_path->entry_label(), EQ);
|
||||
compiler::target::UntaggedObject::kDeeplyImmutableBit);
|
||||
|
||||
__ Bind(&allow_store);
|
||||
}
|
||||
@@ -2591,6 +2585,39 @@ void StoreStaticFieldInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
|
||||
}
|
||||
}
|
||||
|
||||
LocationSummary* CheckFieldImmutabilityInstr::MakeLocationSummary(
|
||||
Zone* zone,
|
||||
bool opt) const {
|
||||
const intptr_t kNumInputs = 1;
|
||||
const intptr_t kNumTemps = 1;
|
||||
LocationSummary* summary = new (zone) LocationSummary(
|
||||
zone, kNumInputs, kNumTemps, LocationSummary::kCallOnSlowPath);
|
||||
summary->set_in(
|
||||
0, Location::RegisterLocation(EnsureDeeplyImmutableStubABI::kValueReg));
|
||||
summary->set_temp(
|
||||
0, Location::RegisterLocation(EnsureDeeplyImmutableStubABI::kTempReg));
|
||||
return summary;
|
||||
}
|
||||
|
||||
void CheckFieldImmutabilityInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
|
||||
const Register value = locs()->in(0).reg();
|
||||
const Register temp = locs()->temp(0).reg();
|
||||
|
||||
auto slow_path = new EnsureDeeplyImmutableSlowPath(this, value);
|
||||
compiler->AddSlowPathCode(slow_path);
|
||||
|
||||
__ BranchIfSmi(value, slow_path->exit_label(),
|
||||
compiler::Assembler::kNearJump);
|
||||
__ ldr(temp,
|
||||
compiler::FieldAddress(value, compiler::target::Object::tags_offset()),
|
||||
compiler::kUnsignedByte);
|
||||
// If immutability bit is not set, go to runtime.
|
||||
__ tbz(slow_path->entry_label(), temp,
|
||||
compiler::target::UntaggedObject::kDeeplyImmutableBit);
|
||||
|
||||
__ Bind(slow_path->exit_label());
|
||||
}
|
||||
|
||||
LocationSummary* InstanceOfInstr::MakeLocationSummary(Zone* zone,
|
||||
bool opt) const {
|
||||
const intptr_t kNumInputs = 3;
|
||||
@@ -5337,9 +5364,12 @@ void CheckWritableInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
|
||||
compiler::target::Object::tags_offset()),
|
||||
compiler::kUnsignedByte);
|
||||
// In the first byte.
|
||||
ASSERT(compiler::target::UntaggedObject::kImmutableBit < 8);
|
||||
__ tbnz(slow_path->entry_label(), TMP,
|
||||
compiler::target::UntaggedObject::kImmutableBit);
|
||||
ASSERT(compiler::target::UntaggedObject::kDeeplyImmutableBit < 8);
|
||||
ASSERT(compiler::target::UntaggedObject::kShallowImmutableBit < 8);
|
||||
__ TestImmediate(
|
||||
TMP, 1 << compiler::target::UntaggedObject::kDeeplyImmutableBit |
|
||||
1 << compiler::target::UntaggedObject::kShallowImmutableBit);
|
||||
__ b(slow_path->entry_label(), NOT_ZERO);
|
||||
}
|
||||
|
||||
class Int64DivideSlowPath : public ThrowErrorSlowPathCode {
|
||||
|
||||
@@ -2102,6 +2102,41 @@ void GuardFieldLengthInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
|
||||
}
|
||||
}
|
||||
|
||||
LocationSummary* CheckFieldImmutabilityInstr::MakeLocationSummary(
|
||||
Zone* zone,
|
||||
bool opt) const {
|
||||
const intptr_t kNumInputs = 1;
|
||||
const intptr_t kNumTemps = 1;
|
||||
LocationSummary* summary = new (zone) LocationSummary(
|
||||
zone, kNumInputs, kNumTemps, LocationSummary::kCallOnSlowPath);
|
||||
summary->set_in(
|
||||
0, Location::RegisterLocation(EnsureDeeplyImmutableStubABI::kValueReg));
|
||||
summary->set_temp(
|
||||
0, Location::RegisterLocation(EnsureDeeplyImmutableStubABI::kTempReg));
|
||||
return summary;
|
||||
}
|
||||
|
||||
void CheckFieldImmutabilityInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
|
||||
const Register value = locs()->in(0).reg();
|
||||
const Register temp = locs()->temp(0).reg();
|
||||
|
||||
auto slow_path = new EnsureDeeplyImmutableSlowPath(this, value);
|
||||
compiler->AddSlowPathCode(slow_path);
|
||||
|
||||
__ BranchIfSmi(value, slow_path->exit_label(),
|
||||
compiler::Assembler::kNearJump);
|
||||
|
||||
__ movl(temp, compiler::FieldAddress(
|
||||
value, compiler::target::Object::tags_offset()));
|
||||
__ testl(temp,
|
||||
compiler::Immediate(
|
||||
1 << compiler::target::UntaggedObject::kDeeplyImmutableBit));
|
||||
// If immutability bit is not set, go to runtime.
|
||||
__ j(ZERO, slow_path->entry_label(), compiler::Assembler::kNearJump);
|
||||
|
||||
__ Bind(slow_path->exit_label());
|
||||
}
|
||||
|
||||
LocationSummary* StoreStaticFieldInstr::MakeLocationSummary(Zone* zone,
|
||||
bool opt) const {
|
||||
const intptr_t kNumTemps =
|
||||
@@ -2157,17 +2192,12 @@ void StoreStaticFieldInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
|
||||
1 << compiler::target::UntaggedObject::kCanonicalBit));
|
||||
// If canonical bit is set, no need for runtime check.
|
||||
__ j(NOT_ZERO, &allow_store);
|
||||
__ testl(temp, compiler::Immediate(
|
||||
1 << compiler::target::UntaggedObject::kImmutableBit));
|
||||
__ testl(temp,
|
||||
compiler::Immediate(
|
||||
1 << compiler::target::UntaggedObject::kDeeplyImmutableBit));
|
||||
// If immutability bit is not set, go to runtime.
|
||||
__ j(ZERO, checked_store_into_shared_slow_path->entry_label());
|
||||
|
||||
// If immutability bit is set, skip runtime unless it's a Closure
|
||||
// (see raw_object.h ImmutableBit description for deep vs shallow).
|
||||
__ LoadClassId(temp, in);
|
||||
__ CompareImmediate(temp, kClosureCid);
|
||||
__ BranchIf(EQUAL, checked_store_into_shared_slow_path->entry_label());
|
||||
|
||||
__ Bind(&allow_store);
|
||||
}
|
||||
}
|
||||
@@ -5460,8 +5490,10 @@ void CheckWritableInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
|
||||
__ movl(temp,
|
||||
compiler::FieldAddress(locs()->in(0).reg(),
|
||||
compiler::target::Object::tags_offset()));
|
||||
__ testl(temp, compiler::Immediate(
|
||||
1 << compiler::target::UntaggedObject::kImmutableBit));
|
||||
__ testl(temp,
|
||||
compiler::Immediate(
|
||||
1 << compiler::target::UntaggedObject::kDeeplyImmutableBit |
|
||||
1 << compiler::target::UntaggedObject::kShallowImmutableBit));
|
||||
__ j(NOT_ZERO, slow_path->entry_label());
|
||||
}
|
||||
|
||||
|
||||
@@ -959,6 +959,11 @@ void GuardFieldInstr::PrintOperandsTo(BaseTextBuffer* f) const {
|
||||
value()->PrintTo(f);
|
||||
}
|
||||
|
||||
void CheckFieldImmutabilityInstr::PrintOperandsTo(BaseTextBuffer* f) const {
|
||||
f->Printf("%s , ", String::Handle(field().name()).ToCString());
|
||||
value()->PrintTo(f);
|
||||
}
|
||||
|
||||
void StoreFieldInstr::PrintOperandsTo(BaseTextBuffer* f) const {
|
||||
instance()->PrintTo(f);
|
||||
f->Printf(" . %s = ", slot().Name());
|
||||
|
||||
@@ -2449,6 +2449,39 @@ static void LoadValueCid(FlowGraphCompiler* compiler,
|
||||
|
||||
DEFINE_UNIMPLEMENTED_INSTRUCTION(GuardFieldTypeInstr)
|
||||
|
||||
LocationSummary* CheckFieldImmutabilityInstr::MakeLocationSummary(
|
||||
Zone* zone,
|
||||
bool opt) const {
|
||||
const intptr_t kNumInputs = 1;
|
||||
const intptr_t kNumTemps = 1;
|
||||
LocationSummary* summary = new (zone) LocationSummary(
|
||||
zone, kNumInputs, kNumTemps, LocationSummary::kCallOnSlowPath);
|
||||
summary->set_in(
|
||||
0, Location::RegisterLocation(EnsureDeeplyImmutableStubABI::kValueReg));
|
||||
summary->set_temp(
|
||||
0, Location::RegisterLocation(EnsureDeeplyImmutableStubABI::kTempReg));
|
||||
return summary;
|
||||
}
|
||||
|
||||
void CheckFieldImmutabilityInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
|
||||
const Register value = locs()->in(0).reg();
|
||||
const Register temp = locs()->temp(0).reg();
|
||||
|
||||
auto slow_path = new EnsureDeeplyImmutableSlowPath(this, value);
|
||||
compiler->AddSlowPathCode(slow_path);
|
||||
|
||||
__ BranchIfSmi(value, slow_path->exit_label(),
|
||||
compiler::Assembler::kNearJump);
|
||||
__ lbu(temp, compiler::FieldAddress(value,
|
||||
compiler::target::Object::tags_offset()));
|
||||
__ andi(temp, temp,
|
||||
1 << compiler::target::UntaggedObject::kDeeplyImmutableBit);
|
||||
// If immutability bit is not set, go to runtime.
|
||||
__ beqz(temp, slow_path->entry_label());
|
||||
|
||||
__ Bind(slow_path->exit_label());
|
||||
}
|
||||
|
||||
LocationSummary* GuardFieldClassInstr::MakeLocationSummary(Zone* zone,
|
||||
bool opt) const {
|
||||
const intptr_t kNumInputs = 1;
|
||||
@@ -2754,16 +2787,11 @@ void StoreStaticFieldInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
|
||||
__ andi(temp, TMP, 1 << compiler::target::UntaggedObject::kCanonicalBit);
|
||||
// If canonical bit is set, no need for runtime check.
|
||||
__ bnez(temp, &allow_store);
|
||||
__ andi(temp, TMP, 1 << compiler::target::UntaggedObject::kImmutableBit);
|
||||
// If immutability bit is not set, go to runtime.
|
||||
__ andi(temp, TMP,
|
||||
1 << compiler::target::UntaggedObject::kDeeplyImmutableBit);
|
||||
// If deeply immutability bit is not set, go to runtime.
|
||||
__ beqz(temp, checked_store_into_shared_slow_path->entry_label());
|
||||
|
||||
// If immutability bit is set, skip runtime unless it's a Closure
|
||||
// (see raw_object.h ImmutableBit description for deep vs shallow).
|
||||
__ LoadClassId(TMP, value);
|
||||
__ CompareImmediate(TMP, kClosureCid);
|
||||
__ BranchIf(EQ, checked_store_into_shared_slow_path->entry_label());
|
||||
|
||||
__ Bind(&allow_store);
|
||||
}
|
||||
}
|
||||
@@ -5486,8 +5514,11 @@ void CheckWritableInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
|
||||
__ lbu(TMP, compiler::FieldAddress(locs()->in(0).reg(),
|
||||
compiler::target::Object::tags_offset()));
|
||||
// In the first byte.
|
||||
ASSERT(compiler::target::UntaggedObject::kImmutableBit < 8);
|
||||
__ andi(TMP, TMP, 1 << compiler::target::UntaggedObject::kImmutableBit);
|
||||
ASSERT(compiler::target::UntaggedObject::kDeeplyImmutableBit < 8);
|
||||
ASSERT(compiler::target::UntaggedObject::kShallowImmutableBit < 8);
|
||||
__ andi(TMP, TMP,
|
||||
1 << compiler::target::UntaggedObject::kDeeplyImmutableBit |
|
||||
1 << compiler::target::UntaggedObject::kShallowImmutableBit);
|
||||
__ bnez(TMP, slow_path->entry_label());
|
||||
}
|
||||
|
||||
|
||||
@@ -2477,6 +2477,40 @@ void GuardFieldTypeInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
|
||||
__ Bind(&ok);
|
||||
}
|
||||
|
||||
LocationSummary* CheckFieldImmutabilityInstr::MakeLocationSummary(
|
||||
Zone* zone,
|
||||
bool opt) const {
|
||||
const intptr_t kNumInputs = 1;
|
||||
const intptr_t kNumTemps = 1;
|
||||
LocationSummary* summary = new (zone) LocationSummary(
|
||||
zone, kNumInputs, kNumTemps, LocationSummary::kCallOnSlowPath);
|
||||
summary->set_in(
|
||||
0, Location::RegisterLocation(EnsureDeeplyImmutableStubABI::kValueReg));
|
||||
summary->set_temp(
|
||||
0, Location::RegisterLocation(EnsureDeeplyImmutableStubABI::kTempReg));
|
||||
return summary;
|
||||
}
|
||||
|
||||
void CheckFieldImmutabilityInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
|
||||
const Register value = locs()->in(0).reg();
|
||||
const Register temp = locs()->temp(0).reg();
|
||||
|
||||
auto slow_path = new EnsureDeeplyImmutableSlowPath(this, value);
|
||||
compiler->AddSlowPathCode(slow_path);
|
||||
|
||||
__ BranchIfSmi(value, slow_path->exit_label(),
|
||||
compiler::Assembler::kNearJump);
|
||||
__ movq(temp, compiler::FieldAddress(
|
||||
value, compiler::target::Object::tags_offset()));
|
||||
__ testq(temp,
|
||||
compiler::Immediate(
|
||||
1 << compiler::target::UntaggedObject::kDeeplyImmutableBit));
|
||||
// If deep immutability bit is not set, go to runtime.
|
||||
__ j(ZERO, slow_path->entry_label());
|
||||
|
||||
__ Bind(slow_path->exit_label());
|
||||
}
|
||||
|
||||
LocationSummary* StoreStaticFieldInstr::MakeLocationSummary(Zone* zone,
|
||||
bool opt) const {
|
||||
const intptr_t kNumInputs = 1;
|
||||
@@ -2527,17 +2561,12 @@ void StoreStaticFieldInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
|
||||
1 << compiler::target::UntaggedObject::kCanonicalBit));
|
||||
// If canonical bit is set, no need for runtime check.
|
||||
__ j(NOT_ZERO, &allow_store);
|
||||
__ testq(temp, compiler::Immediate(
|
||||
1 << compiler::target::UntaggedObject::kImmutableBit));
|
||||
// If immutability bit is not set, go to runtime.
|
||||
__ testq(temp,
|
||||
compiler::Immediate(
|
||||
1 << compiler::target::UntaggedObject::kDeeplyImmutableBit));
|
||||
// If deep immutability bit is not set, go to runtime.
|
||||
__ j(ZERO, checked_store_into_shared_slow_path->entry_label());
|
||||
|
||||
// If immutability bit is set, skip runtime unless it's a Closure
|
||||
// (see raw_object.h ImmutableBit description for deep vs shallow).
|
||||
__ LoadClassId(temp, value);
|
||||
__ CompareImmediate(temp, kClosureCid);
|
||||
__ BranchIf(EQUAL, checked_store_into_shared_slow_path->entry_label());
|
||||
|
||||
__ Bind(&allow_store);
|
||||
}
|
||||
}
|
||||
@@ -5649,8 +5678,10 @@ void CheckWritableInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
|
||||
compiler->AddSlowPathCode(slow_path);
|
||||
__ movq(TMP, compiler::FieldAddress(locs()->in(0).reg(),
|
||||
compiler::target::Object::tags_offset()));
|
||||
__ testq(TMP, compiler::Immediate(
|
||||
1 << compiler::target::UntaggedObject::kImmutableBit));
|
||||
__ testq(TMP,
|
||||
compiler::Immediate(
|
||||
1 << compiler::target::UntaggedObject::kDeeplyImmutableBit |
|
||||
1 << compiler::target::UntaggedObject::kShallowImmutableBit));
|
||||
__ j(NOT_ZERO, slow_path->entry_label());
|
||||
}
|
||||
|
||||
|
||||
@@ -597,7 +597,8 @@ Fragment BaseFlowGraphBuilder::StoreField(
|
||||
|
||||
Fragment BaseFlowGraphBuilder::StoreFieldGuarded(
|
||||
const Field& field,
|
||||
StoreFieldInstr::Kind kind /* = StoreFieldInstr::Kind::kOther */) {
|
||||
StoreFieldInstr::Kind kind /* = StoreFieldInstr::Kind::kOther */,
|
||||
bool requires_immutability_check /* = false */) {
|
||||
Fragment instructions;
|
||||
const Field& field_clone = MayCloneField(Z, field);
|
||||
if (IG->use_field_guards()) {
|
||||
@@ -632,6 +633,13 @@ Fragment BaseFlowGraphBuilder::StoreFieldGuarded(
|
||||
new (Z) GuardFieldTypeInstr(Pop(), field_clone, GetNextDeoptId());
|
||||
}
|
||||
}
|
||||
if (requires_immutability_check) {
|
||||
ASSERT(kind == StoreFieldInstr::Kind::kInitializing);
|
||||
LocalVariable* store_expression = MakeTemporary();
|
||||
instructions += LoadLocal(store_expression);
|
||||
instructions <<= new (Z)
|
||||
CheckFieldImmutabilityInstr(Pop(), field_clone, GetNextDeoptId());
|
||||
}
|
||||
instructions +=
|
||||
StoreNativeField(Slot::Get(field_clone, parsed_function_), kind);
|
||||
return instructions;
|
||||
|
||||
@@ -263,7 +263,8 @@ class BaseFlowGraphBuilder {
|
||||
StoreBarrierType emit_store_barrier = kEmitStoreBarrier);
|
||||
Fragment StoreFieldGuarded(
|
||||
const Field& field,
|
||||
StoreFieldInstr::Kind kind = StoreFieldInstr::Kind::kOther);
|
||||
StoreFieldInstr::Kind kind = StoreFieldInstr::Kind::kOther,
|
||||
bool requires_immutability_check = false);
|
||||
Fragment LoadStaticField(const Field& field, bool calls_initializer);
|
||||
Fragment RedefinitionWithType(const AbstractType& type);
|
||||
Fragment ReachabilityFence();
|
||||
|
||||
@@ -178,8 +178,11 @@ Fragment StreamingFlowGraphBuilder::BuildFieldInitializer(
|
||||
if (only_for_side_effects) {
|
||||
instructions += Drop();
|
||||
} else {
|
||||
const auto& klass = Class::Handle(field.Owner());
|
||||
// TODO(dartbug.com/61078): Use static type to avoid runtime check.
|
||||
instructions += flow_graph_builder_->StoreFieldGuarded(
|
||||
field, StoreFieldInstr::Kind::kInitializing);
|
||||
field, StoreFieldInstr::Kind::kInitializing,
|
||||
klass.is_deeply_immutable());
|
||||
}
|
||||
return instructions;
|
||||
}
|
||||
|
||||
@@ -368,8 +368,10 @@ uword MakeTagWordForNewSpaceObject(classid_t cid, uword instance_size) {
|
||||
dart::UntaggedObject::NewOrEvacuationCandidateBit::encode(true) |
|
||||
dart::UntaggedObject::AlwaysSetBit::encode(true) |
|
||||
dart::UntaggedObject::NotMarkedBit::encode(true) |
|
||||
dart::UntaggedObject::ImmutableBit::encode(
|
||||
dart::Object::ShouldHaveImmutabilityBitSet(cid));
|
||||
dart::UntaggedObject::ShallowImmutableBit::encode(
|
||||
dart::Object::ShouldHaveShallowImmutabilityBitSet(cid)) |
|
||||
dart::UntaggedObject::DeeplyImmutableBit::encode(
|
||||
dart::Object::ShouldHaveDeeplyImmutabilityBitSet(cid));
|
||||
}
|
||||
|
||||
word Object::tags_offset() {
|
||||
@@ -391,8 +393,11 @@ const word UntaggedObject::kOldAndNotRememberedBit =
|
||||
const word UntaggedObject::kNotMarkedBit =
|
||||
dart::UntaggedObject::NotMarkedBit::shift();
|
||||
|
||||
const word UntaggedObject::kImmutableBit =
|
||||
dart::UntaggedObject::ImmutableBit::shift();
|
||||
const word UntaggedObject::kShallowImmutableBit =
|
||||
dart::UntaggedObject::ShallowImmutableBit::shift();
|
||||
|
||||
const word UntaggedObject::kDeeplyImmutableBit =
|
||||
dart::UntaggedObject::DeeplyImmutableBit::shift();
|
||||
|
||||
const word UntaggedObject::kSizeTagPos =
|
||||
dart::UntaggedObject::SizeTagBits::shift();
|
||||
|
||||
@@ -422,7 +422,8 @@ class UntaggedObject : public AllStatic {
|
||||
static const word kNewOrEvacuationCandidateBit;
|
||||
static const word kOldAndNotRememberedBit;
|
||||
static const word kNotMarkedBit;
|
||||
static const word kImmutableBit;
|
||||
static const word kShallowImmutableBit;
|
||||
static const word kDeeplyImmutableBit;
|
||||
static const word kSizeTagPos;
|
||||
static const word kSizeTagSize;
|
||||
static const word kClassIdTagPos;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1881,6 +1881,15 @@ void StubCodeCompiler::GenerateCheckedStoreIntoSharedStub() {
|
||||
__ Ret();
|
||||
}
|
||||
|
||||
void StubCodeCompiler::GenerateEnsureDeeplyImmutableStub() {
|
||||
__ EnterStubFrame();
|
||||
__ PushRegister(EnsureDeeplyImmutableStubABI::kValueReg);
|
||||
__ CallRuntime(kEnsureDeeplyImmutableRuntimeEntry, /*argument_count=*/1);
|
||||
__ Drop(1);
|
||||
__ LeaveStubFrame();
|
||||
__ Ret();
|
||||
}
|
||||
|
||||
static intptr_t SuspendStateFpOffset() {
|
||||
return compiler::target::frame_layout.FrameSlotForVariableIndex(
|
||||
SuspendState::kSuspendStateVarIndex) *
|
||||
|
||||
@@ -573,6 +573,12 @@ struct CheckedStoreIntoSharedStubABI {
|
||||
static constexpr Register kResultReg = R0;
|
||||
};
|
||||
|
||||
// ABI for EnsureDeeplyImmutableStub.
|
||||
struct EnsureDeeplyImmutableStubABI {
|
||||
static constexpr Register kValueReg = R0;
|
||||
static constexpr Register kTempReg = R1;
|
||||
};
|
||||
|
||||
// ABI for SuspendStub (AwaitStub, AwaitWithTypeCheckStub, YieldAsyncStarStub,
|
||||
// SuspendSyncStarAtStartStub, SuspendSyncStarAtYieldStub).
|
||||
struct SuspendStubABI {
|
||||
|
||||
@@ -411,6 +411,12 @@ struct CheckedStoreIntoSharedStubABI {
|
||||
static constexpr Register kResultReg = R0;
|
||||
};
|
||||
|
||||
// ABI for EnsureDeeplyImmutableStub.
|
||||
struct EnsureDeeplyImmutableStubABI {
|
||||
static constexpr Register kValueReg = R0;
|
||||
static constexpr Register kTempReg = R1;
|
||||
};
|
||||
|
||||
// ABI for SuspendStub (AwaitStub, AwaitWithTypeCheckStub, YieldAsyncStarStub,
|
||||
// SuspendSyncStarAtStartStub, SuspendSyncStarAtYieldStub).
|
||||
struct SuspendStubABI {
|
||||
|
||||
@@ -301,6 +301,12 @@ struct CheckedStoreIntoSharedStubABI {
|
||||
static constexpr Register kResultReg = EAX;
|
||||
};
|
||||
|
||||
// ABI for EnsureDeeplyImmutableStub.
|
||||
struct EnsureDeeplyImmutableStubABI {
|
||||
static constexpr Register kValueReg = EAX;
|
||||
static constexpr Register kTempReg = ECX;
|
||||
};
|
||||
|
||||
// ABI for SuspendStub (AwaitStub, AwaitWithTypeCheckStub, YieldAsyncStarStub,
|
||||
// SuspendSyncStarAtStartStub, SuspendSyncStarAtYieldStub).
|
||||
struct SuspendStubABI {
|
||||
|
||||
@@ -455,6 +455,12 @@ struct CheckedStoreIntoSharedStubABI {
|
||||
static constexpr Register kResultReg = A0;
|
||||
};
|
||||
|
||||
// ABI for EnsureDeeplyImmutableStub.
|
||||
struct EnsureDeeplyImmutableStubABI {
|
||||
static constexpr Register kValueReg = A0;
|
||||
static constexpr Register kTempReg = T1;
|
||||
};
|
||||
|
||||
// ABI for SuspendStub (AwaitStub, AwaitWithTypeCheckStub, YieldAsyncStarStub,
|
||||
// SuspendSyncStarAtStartStub, SuspendSyncStarAtYieldStub).
|
||||
struct SuspendStubABI {
|
||||
|
||||
@@ -377,6 +377,12 @@ struct CheckedStoreIntoSharedStubABI {
|
||||
static constexpr Register kResultReg = RAX;
|
||||
};
|
||||
|
||||
// ABI for EnsureDeeplyImmutableStub.
|
||||
struct EnsureDeeplyImmutableStubABI {
|
||||
static constexpr Register kValueReg = RAX;
|
||||
static constexpr Register kTempReg = R10;
|
||||
};
|
||||
|
||||
// ABI for SuspendStub (AwaitStub, AwaitWithTypeCheckStub, YieldAsyncStarStub,
|
||||
// SuspendSyncStarAtStartStub, SuspendSyncStarAtYieldStub).
|
||||
struct SuspendStubABI {
|
||||
|
||||
@@ -3896,7 +3896,7 @@ static Dart_Handle NewExternalTypedData(Thread* thread,
|
||||
callback);
|
||||
}
|
||||
if (unmodifiable) {
|
||||
result.SetImmutable(); // Can pass by reference.
|
||||
result.SetDeeplyImmutable(); // Can pass by reference.
|
||||
const intptr_t view_cid = cid - kTypedDataCidRemainderExternal +
|
||||
kTypedDataCidRemainderUnmodifiable;
|
||||
result = TypedDataView::New(view_cid, ExternalTypedData::Cast(result), 0,
|
||||
@@ -3922,7 +3922,7 @@ static Dart_Handle NewExternalByteData(Thread* thread,
|
||||
const ExternalTypedData& array =
|
||||
Api::UnwrapExternalTypedDataHandle(zone, ext_data);
|
||||
if (unmodifiable) {
|
||||
array.SetImmutable(); // Can pass by reference.
|
||||
array.SetDeeplyImmutable(); // Can pass by reference.
|
||||
}
|
||||
return Api::NewHandle(
|
||||
thread, TypedDataView::New(unmodifiable ? kUnmodifiableByteDataViewCid
|
||||
|
||||
@@ -352,96 +352,6 @@ PersistentHandle* FfiCallbackMetadata::CreatePersistentHandle(
|
||||
return handle;
|
||||
}
|
||||
|
||||
class WorkSet : public StackResource {
|
||||
public:
|
||||
explicit WorkSet(Thread* thread, Zone* zone)
|
||||
: StackResource(thread),
|
||||
thread_(thread),
|
||||
list_(GrowableObjectArray::Handle(zone)) {
|
||||
ASSERT(thread->forward_table_new() == nullptr);
|
||||
set_ = new WeakTable();
|
||||
list_ = GrowableObjectArray::New(16);
|
||||
list_pos_ = 0;
|
||||
thread->set_forward_table_new(set_);
|
||||
}
|
||||
|
||||
~WorkSet() { thread_->set_forward_table_new(nullptr); }
|
||||
|
||||
void Add(const Object& object) {
|
||||
if (!IsMarked(object.ptr())) {
|
||||
Mark(object.ptr());
|
||||
list_.Add(object);
|
||||
}
|
||||
}
|
||||
|
||||
bool Take(Object* object) {
|
||||
if (list_pos_ >= list_.Length()) {
|
||||
return false;
|
||||
}
|
||||
*object = list_.At(list_pos_++);
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
bool IsMarked(ObjectPtr object) {
|
||||
return set_->GetValueExclusive(object) != 0;
|
||||
}
|
||||
void Mark(ObjectPtr object) { set_->SetValueExclusive(object, 1); }
|
||||
|
||||
Thread* thread_;
|
||||
WeakTable* set_;
|
||||
GrowableObjectArray& list_;
|
||||
intptr_t list_pos_;
|
||||
};
|
||||
|
||||
void FfiCallbackMetadata::EnsureTriviallyImmutable(Zone* zone,
|
||||
const Object& object) {
|
||||
WorkSet workset(Thread::Current(), zone);
|
||||
workset.Add(object);
|
||||
|
||||
Object& current = Object::Handle(zone);
|
||||
Function& function = Function::Handle(zone);
|
||||
Object& obj = Object::Handle(zone);
|
||||
while (workset.Take(¤t)) {
|
||||
if (current.IsSmi() || current.IsNull() || current.IsCanonical()) {
|
||||
continue;
|
||||
}
|
||||
if (current.IsClosure()) {
|
||||
const Closure& closure = Closure::Cast(current);
|
||||
function = closure.function();
|
||||
if (!function.IsImplicitClosureFunction() &&
|
||||
!function.captures_only_final_not_late_vars()) {
|
||||
Exceptions::ThrowArgumentError(String::Handle(String::New(
|
||||
"Only final not-late variables can be captured by isolate "
|
||||
"group callbacks.")));
|
||||
UNREACHABLE();
|
||||
}
|
||||
obj = closure.RawContext();
|
||||
workset.Add(obj);
|
||||
continue;
|
||||
}
|
||||
if (current.IsImmutable()) {
|
||||
continue;
|
||||
}
|
||||
if (IsTypedDataBaseClassId(current.GetClassId())) {
|
||||
continue;
|
||||
}
|
||||
if (current.IsContext()) {
|
||||
const Context& context = Context::Cast(current);
|
||||
// Iterate through all elements of the context.
|
||||
for (intptr_t i = 0; i < context.num_variables(); i++) {
|
||||
obj = context.At(i);
|
||||
workset.Add(obj);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
Exceptions::ThrowArgumentError(String::Handle(
|
||||
String::NewFormatted("Only trivially-immutable values are allowed: %s.",
|
||||
current.ToCString())));
|
||||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
|
||||
FfiCallbackMetadata::Trampoline FfiCallbackMetadata::CreateLocalFfiCallback(
|
||||
Isolate* isolate,
|
||||
IsolateGroup* isolate_group,
|
||||
@@ -469,7 +379,7 @@ FfiCallbackMetadata::Trampoline FfiCallbackMetadata::CreateLocalFfiCallback(
|
||||
|
||||
if (function.GetFfiCallbackKind() ==
|
||||
FfiCallbackKind::kIsolateGroupBoundClosureCallback) {
|
||||
EnsureTriviallyImmutable(zone, closure);
|
||||
closure.EnsureDeeplyImmutable(zone);
|
||||
}
|
||||
|
||||
handle = CreatePersistentHandle(
|
||||
|
||||
@@ -343,8 +343,6 @@ class FfiCallbackMetadata {
|
||||
#error What architecture?
|
||||
#endif
|
||||
|
||||
static void EnsureTriviallyImmutable(Zone* zone, const Object& object);
|
||||
|
||||
// Visible for testing.
|
||||
#if defined(TESTING)
|
||||
|
||||
|
||||
@@ -661,7 +661,8 @@ static constexpr uword kReadOnlyGCBits =
|
||||
uword ImageWriter::GetMarkedTags(classid_t cid,
|
||||
intptr_t size,
|
||||
bool is_canonical /* = false */,
|
||||
bool is_immutable /* = false */) {
|
||||
bool is_shallow_immutable /* = false */,
|
||||
bool is_deeply_immutable /* = false */) {
|
||||
// UntaggedObject::SizeTag expects a size divisible by kObjectAlignment and
|
||||
// checks this in debug mode, but the size on the target machine may not be
|
||||
// divisible by the host machine's object alignment if they differ.
|
||||
@@ -682,7 +683,8 @@ uword ImageWriter::GetMarkedTags(classid_t cid,
|
||||
return kReadOnlyGCBits | UntaggedObject::ClassIdTag::encode(cid) |
|
||||
UntaggedObject::SizeTag::encode(adjusted_size) |
|
||||
UntaggedObject::CanonicalBit::encode(is_canonical) |
|
||||
UntaggedObject::ImmutableBit::encode(is_immutable);
|
||||
UntaggedObject::ShallowImmutableBit::encode(is_shallow_immutable) |
|
||||
UntaggedObject::DeeplyImmutableBit::encode(is_deeply_immutable);
|
||||
}
|
||||
|
||||
uword ImageWriter::GetMarkedTags(const Object& obj) {
|
||||
|
||||
@@ -516,7 +516,8 @@ class ImageWriter : public ValueObject {
|
||||
static uword GetMarkedTags(classid_t cid,
|
||||
intptr_t size,
|
||||
bool is_canonical = false,
|
||||
bool is_immutable = false);
|
||||
bool is_shallow_immutable = false,
|
||||
bool is_deeply_immutable = false);
|
||||
static uword GetMarkedTags(const Object& obj);
|
||||
|
||||
void DumpInstructionStats();
|
||||
|
||||
@@ -243,8 +243,10 @@ DART_FORCE_INLINE static ObjectPtr InitializeHeader(uword addr,
|
||||
tags = UntaggedObject::NotMarkedBit::update(true, tags);
|
||||
tags = UntaggedObject::OldAndNotRememberedBit::update(is_old, tags);
|
||||
tags = UntaggedObject::NewOrEvacuationCandidateBit::update(!is_old, tags);
|
||||
tags = UntaggedObject::ImmutableBit::update(
|
||||
Object::ShouldHaveImmutabilityBitSet(class_id), tags);
|
||||
tags = UntaggedObject::ShallowImmutableBit::update(
|
||||
Object::ShouldHaveShallowImmutabilityBitSet(class_id), tags);
|
||||
tags = UntaggedObject::DeeplyImmutableBit::update(
|
||||
Object::ShouldHaveDeeplyImmutabilityBitSet(class_id), tags);
|
||||
#if defined(HASH_IN_OBJECT_HEADER)
|
||||
tags = UntaggedObject::HashTag::update(0, tags);
|
||||
#endif
|
||||
|
||||
@@ -1591,7 +1591,7 @@ class TypedDataViewMessageDeserializationCluster
|
||||
data = ExternalTypedData::New(
|
||||
backing_cid, reinterpret_cast<uint8_t*>(finalizable_data.data),
|
||||
length);
|
||||
data.SetImmutable(); // Can pass by reference.
|
||||
data.SetDeeplyImmutable(); // Can pass by reference.
|
||||
intptr_t external_size = length * element_size;
|
||||
data.AddFinalizer(finalizable_data.peer, finalizable_data.callback,
|
||||
external_size);
|
||||
|
||||
@@ -118,14 +118,15 @@ class Deserializer : public ThreadStackResource {
|
||||
intptr_t cid,
|
||||
intptr_t size,
|
||||
bool is_canonical = false) {
|
||||
InitializeHeader(raw, cid, size, is_canonical,
|
||||
ShouldHaveImmutabilityBitSetCid(cid));
|
||||
InitializeHeader(raw, cid, size, is_canonical, IsShallowlyImmutableCid(cid),
|
||||
IsDeeplyImmutableCid(cid));
|
||||
}
|
||||
static void InitializeHeader(ObjectPtr raw,
|
||||
intptr_t cid,
|
||||
intptr_t size,
|
||||
bool is_canonical,
|
||||
bool is_immutable);
|
||||
bool is_shallow_immutable,
|
||||
bool is_deeply_immutable);
|
||||
|
||||
// Reads raw data (for basic types).
|
||||
// sizeof(T) must be in {1,2,4,8}.
|
||||
@@ -240,7 +241,8 @@ void Deserializer::InitializeHeader(ObjectPtr raw,
|
||||
intptr_t class_id,
|
||||
intptr_t size,
|
||||
bool is_canonical,
|
||||
bool is_immutable) {
|
||||
bool is_shallow_immutable,
|
||||
bool is_deeply_immutable) {
|
||||
ASSERT(Utils::IsAligned(size, kObjectAlignment));
|
||||
uword tags = 0;
|
||||
tags = UntaggedObject::ClassIdTag::update(class_id, tags);
|
||||
@@ -250,7 +252,9 @@ void Deserializer::InitializeHeader(ObjectPtr raw,
|
||||
tags = UntaggedObject::NotMarkedBit::update(true, tags);
|
||||
tags = UntaggedObject::OldAndNotRememberedBit::update(true, tags);
|
||||
tags = UntaggedObject::NewOrEvacuationCandidateBit::update(false, tags);
|
||||
tags = UntaggedObject::ImmutableBit::update(is_immutable, tags);
|
||||
tags =
|
||||
UntaggedObject::ShallowImmutableBit::update(is_shallow_immutable, tags);
|
||||
tags = UntaggedObject::DeeplyImmutableBit::update(is_deeply_immutable, tags);
|
||||
raw->untag()->tags_ = tags;
|
||||
}
|
||||
|
||||
|
||||
+129
-5
@@ -1767,6 +1767,121 @@ void Object::RegisterPrivateClass(const Class& cls,
|
||||
lib.AddClass(cls);
|
||||
}
|
||||
|
||||
class WorkSet : public StackResource {
|
||||
public:
|
||||
explicit WorkSet(Thread* thread, Zone* zone)
|
||||
: StackResource(thread),
|
||||
thread_(thread),
|
||||
list_(GrowableObjectArray::Handle(zone)),
|
||||
stack_(GrowableObjectArray::Handle(zone)) {
|
||||
ASSERT(thread->forward_table_new() == nullptr);
|
||||
set_ = new WeakTable();
|
||||
list_ = GrowableObjectArray::New(16);
|
||||
stack_ = GrowableObjectArray::New(16);
|
||||
thread->set_forward_table_new(set_);
|
||||
}
|
||||
|
||||
~WorkSet() {
|
||||
thread_->set_forward_table_new(nullptr);
|
||||
set_ = nullptr;
|
||||
}
|
||||
|
||||
void Add(const Object& object) {
|
||||
if (!IsMarked(object.ptr())) {
|
||||
Mark(object.ptr());
|
||||
list_.Add(object);
|
||||
}
|
||||
}
|
||||
|
||||
bool TakeAndPush(Object* object) {
|
||||
if (list_.Length() == 0) {
|
||||
return false;
|
||||
}
|
||||
*object = list_.RemoveLast();
|
||||
stack_.Add(*object); // Push
|
||||
return true;
|
||||
}
|
||||
|
||||
void PopAndProcessCompletedClosuresAndContexts(Object* object) {
|
||||
if (stack_.Length() > 0) {
|
||||
*object = stack_.RemoveLast(); // Pop
|
||||
// Are we done processing nested context or closures?
|
||||
while (stack_.Length() > 0) {
|
||||
*object = stack_.At(stack_.Length() - 1);
|
||||
if (object->IsContext()) {
|
||||
stack_.RemoveLast();
|
||||
} else if (object->IsClosure()) {
|
||||
object->SetDeeplyImmutable();
|
||||
stack_.RemoveLast();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
intptr_t StackLength() { return stack_.Length(); }
|
||||
|
||||
private:
|
||||
bool IsMarked(ObjectPtr object) {
|
||||
return set_->GetValueExclusive(object) != 0;
|
||||
}
|
||||
void Mark(ObjectPtr object) { set_->SetValueExclusive(object, 1); }
|
||||
|
||||
Thread* thread_;
|
||||
WeakTable* set_;
|
||||
GrowableObjectArray& list_;
|
||||
// Stack of objects that are currently being processed
|
||||
GrowableObjectArray& stack_;
|
||||
};
|
||||
|
||||
void Object::EnsureDeeplyImmutable(Zone* zone) const {
|
||||
WorkSet workset(Thread::Current(), zone);
|
||||
workset.Add(*this);
|
||||
|
||||
Object& current = Object::Handle(zone);
|
||||
Function& function = Function::Handle(zone);
|
||||
Object& obj = Object::Handle(zone);
|
||||
|
||||
while (workset.TakeAndPush(¤t)) {
|
||||
if (current.IsSmi() || current.IsNull() || current.IsCanonical() ||
|
||||
current.IsDeeplyImmutable() ||
|
||||
IsTypedDataBaseClassId(current.GetClassId())) {
|
||||
workset.PopAndProcessCompletedClosuresAndContexts(&obj);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (current.IsClosure()) {
|
||||
const Closure& closure = Closure::Cast(current);
|
||||
function = closure.function();
|
||||
if (!function.IsImplicitClosureFunction() &&
|
||||
!function.captures_only_final_not_late_vars()) {
|
||||
Exceptions::ThrowArgumentError(String::Handle(String::New(
|
||||
"Only final not-late variables can be captured here.")));
|
||||
UNREACHABLE();
|
||||
}
|
||||
obj = closure.RawContext();
|
||||
workset.Add(obj);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (current.IsContext()) {
|
||||
const Context& context = Context::Cast(current);
|
||||
// Iterate through all elements of the context.
|
||||
for (intptr_t i = 0; i < context.num_variables(); i++) {
|
||||
obj = context.At(i);
|
||||
workset.Add(obj);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
Exceptions::ThrowArgumentError(String::Handle(
|
||||
String::NewFormatted("Only trivially-immutable values are allowed: %s.",
|
||||
current.ToCString())));
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
RELEASE_ASSERT(workset.StackLength() == 0);
|
||||
}
|
||||
|
||||
// Initialize a new isolate from source or from a snapshot.
|
||||
//
|
||||
// There are three possibilities:
|
||||
@@ -2741,9 +2856,16 @@ StringPtr Object::DictionaryName() const {
|
||||
return String::null();
|
||||
}
|
||||
|
||||
bool Object::ShouldHaveImmutabilityBitSet(classid_t class_id) {
|
||||
bool Object::ShouldHaveShallowImmutabilityBitSet(classid_t class_id) {
|
||||
if (class_id < kNumPredefinedCids) {
|
||||
return ShouldHaveImmutabilityBitSetCid(class_id);
|
||||
return IsShallowlyImmutableCid(class_id);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Object::ShouldHaveDeeplyImmutabilityBitSet(classid_t class_id) {
|
||||
if (class_id < kNumPredefinedCids) {
|
||||
return IsDeeplyImmutableCid(class_id);
|
||||
} else {
|
||||
return Class::IsDeeplyImmutable(
|
||||
IsolateGroup::Current()->class_table()->At(class_id));
|
||||
@@ -2852,8 +2974,10 @@ void Object::InitializeObject(uword address,
|
||||
tags = UntaggedObject::NotMarkedBit::update(true, tags);
|
||||
tags = UntaggedObject::OldAndNotRememberedBit::update(is_old, tags);
|
||||
tags = UntaggedObject::NewOrEvacuationCandidateBit::update(!is_old, tags);
|
||||
tags = UntaggedObject::ImmutableBit::update(
|
||||
Object::ShouldHaveImmutabilityBitSet(class_id), tags);
|
||||
tags = UntaggedObject::ShallowImmutableBit::update(
|
||||
Object::ShouldHaveShallowImmutabilityBitSet(class_id), tags);
|
||||
tags = UntaggedObject::DeeplyImmutableBit::update(
|
||||
Object::ShouldHaveDeeplyImmutabilityBitSet(class_id), tags);
|
||||
#if defined(HASH_IN_OBJECT_HEADER)
|
||||
tags = UntaggedObject::HashTag::update(0, tags);
|
||||
#endif
|
||||
@@ -13497,7 +13621,7 @@ void Field::SetStaticValue(const Object& value) const {
|
||||
ASSERT(id >= 0);
|
||||
|
||||
if (FLAG_experimental_shared_data && is_shared()) {
|
||||
FfiCallbackMetadata::EnsureTriviallyImmutable(thread->zone(), value);
|
||||
value.EnsureDeeplyImmutable(thread->zone());
|
||||
}
|
||||
SafepointReadRwLocker ml(thread, thread->isolate_group()->program_lock());
|
||||
if (is_shared()) {
|
||||
|
||||
+17
-4
@@ -352,9 +352,19 @@ class Object {
|
||||
bool IsCanonical() const { return ptr()->untag()->IsCanonical(); }
|
||||
void SetCanonical() const { ptr()->untag()->SetCanonical(); }
|
||||
void ClearCanonical() const { ptr()->untag()->ClearCanonical(); }
|
||||
bool IsImmutable() const { return ptr()->untag()->IsImmutable(); }
|
||||
void SetImmutable() const { ptr()->untag()->SetImmutable(); }
|
||||
void ClearImmutable() const { ptr()->untag()->ClearImmutable(); }
|
||||
bool IsShallowImmutable() const {
|
||||
return ptr()->untag()->IsShallowImmutable();
|
||||
}
|
||||
void SetShallowImmutable() const { ptr()->untag()->SetShallowImmutable(); }
|
||||
void ClearShallowImmutable() const {
|
||||
ptr()->untag()->ClearShallowImmutable();
|
||||
}
|
||||
bool IsDeeplyImmutable() const { return ptr()->untag()->IsDeeplyImmutable(); }
|
||||
void SetDeeplyImmutable() const { ptr()->untag()->SetDeeplyImmutable(); }
|
||||
void ClearDeeplyImmutable() const { ptr()->untag()->ClearDeeplyImmutable(); }
|
||||
bool IsImmutable() const {
|
||||
return IsShallowImmutable() || IsDeeplyImmutable();
|
||||
}
|
||||
intptr_t GetClassId() const { return ptr()->GetClassId(); }
|
||||
inline ClassPtr clazz() const;
|
||||
static intptr_t tags_offset() { return OFFSET_OF(UntaggedObject, tags_); }
|
||||
@@ -722,7 +732,10 @@ class Object {
|
||||
kNo,
|
||||
};
|
||||
|
||||
static bool ShouldHaveImmutabilityBitSet(classid_t class_id);
|
||||
static bool ShouldHaveShallowImmutabilityBitSet(classid_t class_id);
|
||||
static bool ShouldHaveDeeplyImmutabilityBitSet(classid_t class_id);
|
||||
|
||||
void EnsureDeeplyImmutable(Zone* zone) const;
|
||||
|
||||
protected:
|
||||
friend ObjectPtr AllocateObject(intptr_t, intptr_t, intptr_t);
|
||||
|
||||
@@ -151,8 +151,11 @@ static bool CanShareObject(ObjectPtr obj, uword tags) {
|
||||
if ((tags & UntaggedObject::CanonicalBit::mask_in_place()) != 0) {
|
||||
return true;
|
||||
}
|
||||
const auto cid = UntaggedObject::ClassIdTag::decode(tags);
|
||||
if ((tags & UntaggedObject::ImmutableBit::mask_in_place()) != 0) {
|
||||
if ((tags & UntaggedObject::DeeplyImmutableBit::mask_in_place()) != 0) {
|
||||
return true;
|
||||
}
|
||||
if ((tags & UntaggedObject::ShallowImmutableBit::mask_in_place()) != 0) {
|
||||
const auto cid = UntaggedObject::ClassIdTag::decode(tags);
|
||||
if (IsUnmodifiableTypedDataViewClassId(cid)) {
|
||||
// Unmodifiable typed data views may have mutable backing stores.
|
||||
return TypedDataView::RawCast(obj)
|
||||
@@ -235,8 +238,10 @@ void SetNewSpaceTaggingWord(ObjectPtr to, classid_t cid, uint32_t size) {
|
||||
tags = UntaggedObject::OldAndNotRememberedBit::update(false, tags);
|
||||
tags = UntaggedObject::CanonicalBit::update(false, tags);
|
||||
tags = UntaggedObject::NewOrEvacuationCandidateBit::update(true, tags);
|
||||
tags = UntaggedObject::ImmutableBit::update(
|
||||
Object::ShouldHaveImmutabilityBitSet(cid), tags);
|
||||
tags = UntaggedObject::ShallowImmutableBit::update(
|
||||
Object::ShouldHaveShallowImmutabilityBitSet(cid), tags);
|
||||
tags = UntaggedObject::DeeplyImmutableBit::update(
|
||||
Object::ShouldHaveDeeplyImmutabilityBitSet(cid), tags);
|
||||
#if defined(HASH_IN_OBJECT_HEADER)
|
||||
tags = UntaggedObject::HashTag::update(0, tags);
|
||||
#endif
|
||||
|
||||
+16
-5
@@ -226,12 +226,15 @@ class UntaggedObject {
|
||||
// The bit is also used to make typed data stores efficient. 2.a.
|
||||
//
|
||||
// See also Class::kIsDeeplyImmutableBit.
|
||||
using ImmutableBit =
|
||||
using ShallowImmutableBit =
|
||||
BitField<decltype(tags_), bool, OldAndNotRememberedBit::kNextBit>;
|
||||
|
||||
using DeeplyImmutableBit =
|
||||
BitField<decltype(tags_), bool, ShallowImmutableBit::kNextBit>;
|
||||
|
||||
// The rest of the initial byte is currently reserved, so the next bitfield
|
||||
// starts at the byte boundary.
|
||||
COMPILE_ASSERT(ImmutableBit::kNextBit <= kBitsPerInt8);
|
||||
COMPILE_ASSERT(DeeplyImmutableBit::kNextBit <= kBitsPerInt8);
|
||||
using SizeTagBits = BitField<decltype(tags_), intptr_t, kBitsPerInt8, 4>;
|
||||
|
||||
// Encodes the object size in the tag in units of object alignment.
|
||||
@@ -357,9 +360,17 @@ class UntaggedObject {
|
||||
void SetCanonical() { tags_.UpdateBool<CanonicalBit>(true); }
|
||||
void ClearCanonical() { tags_.UpdateBool<CanonicalBit>(false); }
|
||||
|
||||
bool IsImmutable() const { return tags_.Read<ImmutableBit>(); }
|
||||
void SetImmutable() { tags_.UpdateBool<ImmutableBit>(true); }
|
||||
void ClearImmutable() { tags_.UpdateBool<ImmutableBit>(false); }
|
||||
bool IsShallowImmutable() const { return tags_.Read<ShallowImmutableBit>(); }
|
||||
void SetShallowImmutable() { tags_.UpdateBool<ShallowImmutableBit>(true); }
|
||||
void ClearShallowImmutable() { tags_.UpdateBool<ShallowImmutableBit>(false); }
|
||||
|
||||
bool IsDeeplyImmutable() const { return tags_.Read<DeeplyImmutableBit>(); }
|
||||
void SetDeeplyImmutable() { tags_.UpdateBool<DeeplyImmutableBit>(true); }
|
||||
void ClearDeeplyImmutable() { tags_.UpdateBool<DeeplyImmutableBit>(false); }
|
||||
|
||||
bool IsImmutable() const {
|
||||
return IsShallowImmutable() || IsDeeplyImmutable();
|
||||
}
|
||||
|
||||
bool InVMIsolateHeap() const;
|
||||
|
||||
|
||||
@@ -4687,7 +4687,7 @@ DEFINE_RUNTIME_ENTRY(CheckedStoreIntoShared, 2) {
|
||||
const Field& field = Field::CheckedHandle(zone, arguments.ArgAt(0));
|
||||
const Instance& value = Instance::CheckedHandle(zone, arguments.ArgAt(1));
|
||||
|
||||
FfiCallbackMetadata::EnsureTriviallyImmutable(zone, value);
|
||||
value.EnsureDeeplyImmutable(zone);
|
||||
|
||||
field.SetStaticValue(value);
|
||||
arguments.SetReturn(field);
|
||||
@@ -5245,6 +5245,13 @@ DEFINE_RUNTIME_ENTRY(InitializeSharedField, 1) {
|
||||
arguments.SetReturn(result);
|
||||
}
|
||||
|
||||
// Throw if the value is not immutable.
|
||||
// Arg0: Value to check.
|
||||
DEFINE_RUNTIME_ENTRY(EnsureDeeplyImmutable, 1) {
|
||||
const Instance& value = Instance::CheckedHandle(zone, arguments.ArgAt(0));
|
||||
value.EnsureDeeplyImmutable(zone);
|
||||
}
|
||||
|
||||
#if defined(USING_MEMORY_SANITIZER)
|
||||
extern "C" void dart_msan_read1(void* addr) {
|
||||
__msan_check_mem_is_initialized(addr, 1);
|
||||
|
||||
@@ -89,7 +89,8 @@ namespace dart {
|
||||
V(InvokeNoSuchMethod) \
|
||||
V(ResumeInterpreter) \
|
||||
V(InitializeSharedField) \
|
||||
V(FatalError)
|
||||
V(FatalError) \
|
||||
V(EnsureDeeplyImmutable)
|
||||
|
||||
// Note: Leaf runtime function have C linkage, so they cannot pass C++ struct
|
||||
// values like ObjectPtr.
|
||||
|
||||
@@ -193,6 +193,7 @@ namespace dart {
|
||||
V(FfiAsyncCallbackSend) \
|
||||
V(CheckIsolateFieldAccess) \
|
||||
V(CheckedStoreIntoShared) \
|
||||
V(EnsureDeeplyImmutable) \
|
||||
V(UnknownDartCode)
|
||||
|
||||
} // namespace dart
|
||||
|
||||
Reference in New Issue
Block a user