[gardening] Always copy ICData prior to write.

To do this, we create a new Truncate() methods for Arrays, truncate
the ICData array to either 1 (just sentinel) or 2 (SMI + sentinel)
entries in Reset(), and change FindFreeIndex() to instead be
GrowFreeIndex() (which always copies).

Closes #35010.

Change-Id: Ia30f4b490cf9420caef843d7fcde9f461d15a206
Reviewed-on: https://dart-review.googlesource.com/c/86563
Commit-Queue: Stevie Strickland <sstrickl@google.com>
Reviewed-by: Vyacheslav Egorov <vegorov@google.com>
This commit is contained in:
Stevie Strickland 2018-12-10 17:09:21 +00:00 committed by commit-bot@chromium.org
parent 644d924978
commit 40be306fb0
3 changed files with 91 additions and 100 deletions

View file

@ -13560,45 +13560,6 @@ void ICData::ClearCountAt(intptr_t index) const {
SetCountAt(index, 0);
}
void ICData::ClearWithSentinel() const {
if (IsImmutable()) {
return;
}
// Write the sentinel value into all entries except the first one.
const intptr_t len = Length();
if (len == 0) {
return;
}
// The final entry is always the sentinel.
ASSERT(IsSentinelAt(len - 1));
for (intptr_t i = len - 1; i > 0; i--) {
WriteSentinelAt(i);
}
if (NumArgsTested() != 2) {
// Not the smi fast path case, write sentinel to first one and exit.
WriteSentinelAt(0);
return;
}
if (IsSentinelAt(0)) {
return;
}
Zone* zone = Thread::Current()->zone();
const String& name = String::Handle(target_name());
const Class& smi_class = Class::Handle(Smi::Class());
const Function& smi_op_target =
Function::Handle(Resolver::ResolveDynamicAnyArgs(zone, smi_class, name));
GrowableArray<intptr_t> class_ids(2);
Function& target = Function::Handle();
GetCheckAt(0, &class_ids, &target);
if ((target.raw() == smi_op_target.raw()) && (class_ids[0] == kSmiCid) &&
(class_ids[1] == kSmiCid)) {
// The smi fast path case, preserve the initial entry but reset the count.
ClearCountAt(0);
return;
}
WriteSentinelAt(0);
}
void ICData::ClearAndSetStaticTarget(const Function& func) const {
if (IsImmutable()) {
return;
@ -13770,7 +13731,7 @@ void ICData::AddCheck(const GrowableArray<intptr_t>& class_ids,
}
}
intptr_t index = -1;
data = FindFreeIndex(&index);
data = Grow(&index);
ASSERT(!data.IsNull());
intptr_t data_pos = index * TestEntryLength();
Smi& value = Smi::Handle();
@ -13789,21 +13750,14 @@ void ICData::AddCheck(const GrowableArray<intptr_t>& class_ids,
set_ic_data_array(data);
}
RawArray* ICData::FindFreeIndex(intptr_t* index) const {
// The final entry is always the sentinel value, don't consider it
// when searching.
const intptr_t len = Length() - 1;
RawArray* ICData::Grow(intptr_t* index) const {
Array& data = Array::Handle(ic_data());
for (intptr_t i = 0; i < len; i++) {
if (IsSentinelAt(i)) {
*index = i;
return data.raw();
}
}
// Append case.
*index = len;
// Last entry in array should be a sentinel and will be the new entry
// that can be updated after growing.
*index = Length() - 1;
ASSERT(*index >= 0);
// Grow array.
ASSERT(IsSentinelAt(*index));
// Grow the array and write the new final sentinel into place.
const intptr_t new_len = data.Length() + TestEntryLength();
data = Array::Grow(data, new_len, Heap::kOld);
WriteSentinel(data, TestEntryLength());
@ -13843,7 +13797,7 @@ void ICData::AddReceiverCheck(intptr_t receiver_class_id,
ASSERT(receiver_class_id != kIllegalCid);
intptr_t index = -1;
Array& data = Array::Handle(FindFreeIndex(&index));
Array& data = Array::Handle(Grow(&index));
intptr_t data_pos = index * TestEntryLength();
if ((receiver_class_id == kSmiCid) && (data_pos > 0)) {
ASSERT(GetReceiverClassIdAt(0) != kSmiCid);
@ -21255,6 +21209,47 @@ RawArray* Array::Grow(const Array& source,
return result.raw();
}
void Array::Truncate(intptr_t new_len) const {
if (IsNull()) {
return;
}
Thread* thread = Thread::Current();
Zone* zone = thread->zone();
const Array& array = Array::Handle(zone, this->raw());
intptr_t old_len = array.Length();
ASSERT(new_len <= old_len);
intptr_t old_size = Array::InstanceSize(old_len);
intptr_t new_size = Array::InstanceSize(new_len);
NoSafepointScope no_safepoint;
// If there is any left over space fill it with either an Array object or
// just a plain object (depending on the amount of left over space) so
// that it can be traversed over successfully during garbage collection.
Object::MakeUnusedSpaceTraversable(array, old_size, new_size);
// Update the size in the header field and length of the array object.
uword tags = array.raw_ptr()->tags_;
ASSERT(kArrayCid == RawObject::ClassIdTag::decode(tags));
uint32_t old_tags;
do {
old_tags = tags;
uint32_t new_tags = RawObject::SizeTag::update(new_size, old_tags);
tags = CompareAndSwapTags(old_tags, new_tags);
} while (tags != old_tags);
// TODO(22501): For the heap to remain walkable by the sweeper, it must
// observe the creation of the filler object no later than the new length
// of the array. This assumption holds on ia32/x64 or if the CAS above is a
// full memory barrier.
//
// Also, between the CAS of the header above and the SetLength below,
// the array is temporarily in an inconsistent state. The header is considered
// the overriding source of object size by RawObject::Size, but the ASSERTs
// in RawObject::SizeFromClass must handle this special case.
array.SetLength(new_len);
}
RawArray* Array::MakeFixedLength(const GrowableObjectArray& growable_array,
bool unique) {
ASSERT(!growable_array.IsNull());
@ -21278,43 +21273,16 @@ RawArray* Array::MakeFixedLength(const GrowableObjectArray& growable_array,
array.SetTypeArguments(type_arguments);
return array.raw();
}
intptr_t capacity_len = growable_array.Capacity();
const Array& array = Array::Handle(zone, growable_array.data());
ASSERT(array.IsArray());
array.SetTypeArguments(type_arguments);
intptr_t capacity_size = Array::InstanceSize(capacity_len);
intptr_t used_size = Array::InstanceSize(used_len);
NoSafepointScope no_safepoint;
// If there is any left over space fill it with either an Array object or
// just a plain object (depending on the amount of left over space) so
// that it can be traversed over successfully during garbage collection.
Object::MakeUnusedSpaceTraversable(array, capacity_size, used_size);
// Update the size in the header field and length of the array object.
uword tags = array.raw_ptr()->tags_;
ASSERT(kArrayCid == RawObject::ClassIdTag::decode(tags));
uint32_t old_tags;
do {
old_tags = tags;
uint32_t new_tags = RawObject::SizeTag::update(used_size, old_tags);
tags = array.CompareAndSwapTags(old_tags, new_tags);
} while (tags != old_tags);
// TODO(22501): For the heap to remain walkable by the sweeper, it must
// observe the creation of the filler object no later than the new length
// of the array. This assumption holds on ia32/x64 or if the CAS above is a
// full memory barrier.
//
// Also, between the CAS of the header above and the SetLength below,
// the array is temporarily in an inconsistent state. The header is considered
// the overriding source of object size by RawObject::Size, but the ASSERTs
// in RawObject::SizeFromClass must handle this special case.
array.SetLength(used_len);
// Null the GrowableObjectArray, we are removing its backing array.
growable_array.SetLength(0);
growable_array.SetData(Object::empty_array());
// Truncate the old backing array and return it.
array.Truncate(used_len);
return array.raw();
}

View file

@ -1905,18 +1905,10 @@ class ICData : public Object {
// Clears the count for entry |index|.
void ClearCountAt(intptr_t index) const;
// Clear all entries with the sentinel value (but will preserve initial
// smi smi checks).
void ClearWithSentinel() const;
// Clear all entries with the sentinel value and reset the first entry
// with the dummy target entry.
void ClearAndSetStaticTarget(const Function& func) const;
// Returns the first index that should be used to for a new entry. Will
// grow the array if necessary.
RawArray* FindFreeIndex(intptr_t* index) const;
void DebugDump() const;
// Returns true if this is a two arg smi operation.
@ -2069,6 +2061,10 @@ class ICData : public Object {
return AtomicOperations::LoadAcquire(&raw_ptr()->ic_data_);
}
// Grows the array and also sets the argument to the index that should be used
// for the new entry.
RawArray* Grow(intptr_t* index) const;
void set_owner(const Function& value) const;
void set_target_name(const String& value) const;
void set_arguments_descriptor(const Array& value) const;
@ -8213,13 +8209,17 @@ class Array : public Instance {
intptr_t new_length,
Heap::Space space = Heap::kNew);
// Truncates the array to a given length. 'new_length' must be less than
// or equal to 'source.Length()'. The remaining unused part of the array is
// marked as an Array object or a regular Object so that it can be traversed
// during garbage collection.
void Truncate(intptr_t new_length) const;
// Return an Array object that contains all the elements currently present
// in the specified Growable Object Array. This is done by first truncating
// the Growable Object Array's backing array to the currently used size and
// returning the truncated backing array.
// The remaining unused part of the backing array is marked as an Array
// object or a regular Object so that it can be traversed during garbage
// collection. The backing array of the original Growable Object Array is
// The backing array of the original Growable Object Array is
// set to an empty array.
// If the unique parameter is false, the function is allowed to return
// a shared Array instance.

View file

@ -704,13 +704,36 @@ void ICData::Reset(Zone* zone) const {
if (rule == kInstance) {
const intptr_t num_args = NumArgsTested();
const bool tracking_exactness = IsTrackingExactness();
if (num_args == 2) {
ClearWithSentinel();
} else {
const Array& data_array = Array::Handle(
zone, CachedEmptyICDataArray(num_args, tracking_exactness));
set_ic_data_array(data_array);
const intptr_t len = Length();
// We need at least one non-sentinel entry to require a check
// for the smi fast path case.
if (num_args == 2 && len >= 2) {
if (IsImmutable()) {
return;
}
Zone* zone = Thread::Current()->zone();
const String& name = String::Handle(target_name());
const Class& smi_class = Class::Handle(Smi::Class());
const Function& smi_op_target = Function::Handle(
Resolver::ResolveDynamicAnyArgs(zone, smi_class, name));
GrowableArray<intptr_t> class_ids(2);
Function& target = Function::Handle();
GetCheckAt(0, &class_ids, &target);
if ((target.raw() == smi_op_target.raw()) && (class_ids[0] == kSmiCid) &&
(class_ids[1] == kSmiCid)) {
// The smi fast path case, preserve the initial entry but reset the
// count.
ClearCountAt(0);
WriteSentinelAt(1);
const Array& array = Array::Handle(ic_data());
array.Truncate(2 * TestEntryLength());
return;
}
// Fall back to the normal behavior with cached empty ICData arrays.
}
const Array& data_array = Array::Handle(
zone, CachedEmptyICDataArray(num_args, tracking_exactness));
set_ic_data_array(data_array);
return;
} else if (rule == kNoRebind || rule == kNSMDispatch) {
// TODO(30877) we should account for addition/removal of NSM.