[vm, gc] Use FreeListElement as the filler object for array truncation.

This is consistent with new-space sweeping using FreeListElement as the filler object, and allows for stronger asserts in incremental compaction.

TEST=ci
Change-Id: I43a87a46fb1211a88589cf4a349ada7b556e4c11
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/366885
Reviewed-by: Alexander Aprelev <aam@google.com>
Commit-Queue: Ryan Macnak <rmacnak@google.com>
This commit is contained in:
Ryan Macnak
2024-05-16 19:30:58 +00:00
committed by Commit Queue
parent 8b53d26a09
commit 565bc48302
2 changed files with 27 additions and 89 deletions
+12 -55
View File
@@ -1605,8 +1605,8 @@ void Object::set_vm_isolate_snapshot_object_table(const Array& table) {
// Make unused space in an object whose type has been transformed safe
// for traversing during GC.
// The unused part of the transformed object is marked as an TypedDataInt8Array
// object.
// The unused part of the transformed object is marked as a FreeListElement
// object that is not inserted into to the freelist.
void Object::MakeUnusedSpaceTraversable(const Object& obj,
intptr_t original_size,
intptr_t used_size) {
@@ -1615,62 +1615,19 @@ void Object::MakeUnusedSpaceTraversable(const Object& obj,
ASSERT(original_size >= used_size);
if (original_size > used_size) {
intptr_t leftover_size = original_size - used_size;
uword addr = UntaggedObject::ToAddr(obj.ptr()) + used_size;
if (leftover_size >= TypedData::InstanceSize(0)) {
// Update the leftover space as a TypedDataInt8Array object.
TypedDataPtr raw =
static_cast<TypedDataPtr>(UntaggedObject::FromAddr(addr));
uword new_tags =
UntaggedObject::ClassIdTag::update(kTypedDataInt8ArrayCid, 0);
new_tags = UntaggedObject::SizeTag::update(leftover_size, new_tags);
const bool is_old = obj.ptr()->IsOldObject();
new_tags = UntaggedObject::AlwaysSetBit::update(true, new_tags);
new_tags = UntaggedObject::NotMarkedBit::update(true, new_tags);
new_tags =
UntaggedObject::OldAndNotRememberedBit::update(is_old, new_tags);
new_tags = UntaggedObject::NewBit::update(!is_old, new_tags);
// On architectures with a relaxed memory model, the concurrent marker may
// observe the write of the filler object's header before observing the
// new array length, and so treat it as a pointer. Ensure it is a Smi so
// the marker won't dereference it.
ASSERT((new_tags & kSmiTagMask) == kSmiTag);
intptr_t leftover_len = (leftover_size - TypedData::InstanceSize(0));
ASSERT(TypedData::InstanceSize(leftover_len) == leftover_size);
raw->untag()->set_length<std::memory_order_release>(
Smi::New(leftover_len));
raw->untag()->tags_ = new_tags;
raw->untag()->RecomputeDataField();
if (obj.ptr()->IsNewObject()) {
FreeListElement::AsElementNew(addr, leftover_size);
} else {
// Update the leftover space as a basic object.
ASSERT(leftover_size == Object::InstanceSize());
ObjectPtr raw = static_cast<ObjectPtr>(UntaggedObject::FromAddr(addr));
uword new_tags = UntaggedObject::ClassIdTag::update(kInstanceCid, 0);
new_tags = UntaggedObject::SizeTag::update(leftover_size, new_tags);
const bool is_old = obj.ptr()->IsOldObject();
new_tags = UntaggedObject::AlwaysSetBit::update(true, new_tags);
new_tags = UntaggedObject::NotMarkedBit::update(true, new_tags);
new_tags =
UntaggedObject::OldAndNotRememberedBit::update(is_old, new_tags);
new_tags = UntaggedObject::NewBit::update(!is_old, new_tags);
// On architectures with a relaxed memory model, the concurrent marker may
// observe the write of the filler object's header before observing the
// new array length, and so treat it as a pointer. Ensure it is a Smi so
// the marker won't dereference it.
ASSERT((new_tags & kSmiTagMask) == kSmiTag);
// The array might have an uninitialized alignment gap since the visitors
// for Arrays are precise based on element count, but the visitors for
// Instance are based on the size rounded to the allocation unit, so we
// need to ensure the alignment gap is initialized.
for (intptr_t offset = Instance::UnroundedSize();
offset < Instance::InstanceSize(); offset += sizeof(uword)) {
reinterpret_cast<std::atomic<uword>*>(addr + offset)
->store(0, std::memory_order_release);
}
raw->untag()->tags_ = new_tags;
FreeListElement::AsElement(addr, leftover_size);
}
// On architectures with a relaxed memory model, the concurrent marker may
// observe the write of the filler object's header before observing the
// new array length, and so treat it as a pointer. Ensure it is a Smi so
// the marker won't dereference it.
ASSERT((*reinterpret_cast<uword*>(addr) & kSmiTagMask) == kSmiTag);
ASSERT((*reinterpret_cast<uword*>(addr + kWordSize) & kSmiTagMask) ==
kSmiTag);
}
}
+15 -34
View File
@@ -2095,9 +2095,8 @@ ISOLATE_UNIT_TEST_CASE(GrowableObjectArray) {
// Test the MakeFixedLength functionality to make sure the resulting array
// object is properly setup.
// 1. Should produce an array of length 2 and a left over int8 array.
// 1. Should produce an array of length 2 and a filler of minimal size.
Array& new_array = Array::Handle();
TypedData& left_over_array = TypedData::Handle();
Object& obj = Object::Handle();
uword addr = 0;
intptr_t used_size = 0;
@@ -2117,19 +2116,12 @@ ISOLATE_UNIT_TEST_CASE(GrowableObjectArray) {
new_array ^= obj.ptr();
EXPECT_EQ(2, new_array.Length());
addr += used_size;
obj = UntaggedObject::FromAddr(addr);
#if defined(DART_COMPRESSED_POINTERS)
// In compressed pointer mode, the TypedData doesn't fit.
EXPECT(obj.IsInstance());
#else
EXPECT(obj.IsTypedData());
left_over_array ^= obj.ptr();
EXPECT_EQ(4 * kWordSize - TypedData::InstanceSize(0),
left_over_array.Length());
#endif
ObjectPtr filler = UntaggedObject::FromAddr(addr);
EXPECT(filler->IsFreeListElement());
EXPECT_EQ(filler->untag()->HeapSize(),
Array::InstanceSize(kArrayLen + 1) - used_size);
// 2. Should produce an array of length 3 and a left over int8 array or
// instance.
// 2. Should produce an array of length 3 and a filler object.
array = GrowableObjectArray::New(kArrayLen);
EXPECT_EQ(kArrayLen, array.Capacity());
EXPECT_EQ(0, array.Length());
@@ -2145,17 +2137,12 @@ ISOLATE_UNIT_TEST_CASE(GrowableObjectArray) {
new_array ^= obj.ptr();
EXPECT_EQ(3, new_array.Length());
addr += used_size;
obj = UntaggedObject::FromAddr(addr);
if (TypedData::InstanceSize(0) <= 2 * kCompressedWordSize) {
EXPECT(obj.IsTypedData());
left_over_array ^= obj.ptr();
EXPECT_EQ(2 * kCompressedWordSize - TypedData::InstanceSize(0),
left_over_array.Length());
} else {
EXPECT(obj.IsInstance());
}
filler = UntaggedObject::FromAddr(addr);
EXPECT(filler->IsFreeListElement());
EXPECT_EQ(filler->untag()->HeapSize(),
Array::InstanceSize(kArrayLen) - used_size);
// 3. Should produce an array of length 1 and a left over int8 array.
// 3. Should produce an array of length 1 and a filler object.
array = GrowableObjectArray::New(kArrayLen + 3);
EXPECT_EQ((kArrayLen + 3), array.Capacity());
EXPECT_EQ(0, array.Length());
@@ -2171,16 +2158,10 @@ ISOLATE_UNIT_TEST_CASE(GrowableObjectArray) {
new_array ^= obj.ptr();
EXPECT_EQ(1, new_array.Length());
addr += used_size;
obj = UntaggedObject::FromAddr(addr);
#if defined(DART_COMPRESSED_POINTERS)
// In compressed pointer mode, the TypedData doesn't fit.
EXPECT(obj.IsInstance());
#else
EXPECT(obj.IsTypedData());
left_over_array ^= obj.ptr();
EXPECT_EQ(8 * kWordSize - TypedData::InstanceSize(0),
left_over_array.Length());
#endif
filler = UntaggedObject::FromAddr(addr);
EXPECT(filler->IsFreeListElement());
EXPECT_EQ(filler->untag()->HeapSize(),
Array::InstanceSize(kArrayLen + 3) - used_size);
// 4. Verify that GC can handle the filler object for a large array.
array = GrowableObjectArray::New((1 * MB) >> kWordSizeLog2);