 Chromium Code Reviews
 Chromium Code Reviews Issue 10575032:
  In-place shrinking of descriptor arrays with non-live transitions.  (Closed) 
  Base URL: https://v8.googlecode.com/svn/branches/bleeding_edge
    
  
    Issue 10575032:
  In-place shrinking of descriptor arrays with non-live transitions.  (Closed) 
  Base URL: https://v8.googlecode.com/svn/branches/bleeding_edge| Index: src/objects.cc | 
| diff --git a/src/objects.cc b/src/objects.cc | 
| index 9dbe849b0c726b99ff53efcdd189b2f0b62b37ce..cb7231a22736d0dceabfc2bba5e56127efcca624 100644 | 
| --- a/src/objects.cc | 
| +++ b/src/objects.cc | 
| @@ -640,7 +640,9 @@ MaybeObject* Object::GetProperty(Object* receiver, | 
| } | 
| case MAP_TRANSITION: | 
| case CONSTANT_TRANSITION: | 
| - case NULL_DESCRIPTOR: | 
| + break; | 
| + case NONEXISTENT: | 
| + UNREACHABLE(); | 
| break; | 
| } | 
| UNREACHABLE(); | 
| @@ -2153,7 +2155,9 @@ MaybeObject* JSObject::SetPropertyViaPrototypes( | 
| } | 
| case MAP_TRANSITION: | 
| case CONSTANT_TRANSITION: | 
| - case NULL_DESCRIPTOR: | 
| + break; | 
| + case NONEXISTENT: | 
| + UNREACHABLE(); | 
| break; | 
| } | 
| } | 
| @@ -2935,9 +2939,8 @@ MaybeObject* JSObject::SetPropertyForResult(LookupResult* result, | 
| // FIELD, even if the value is a constant function. | 
| return ConvertDescriptorToFieldAndMapTransition(name, value, attributes); | 
| } | 
| - case NULL_DESCRIPTOR: | 
| - return ConvertDescriptorToFieldAndMapTransition(name, value, attributes); | 
| case HANDLER: | 
| + case NONEXISTENT: | 
| UNREACHABLE(); | 
| return value; | 
| } | 
| @@ -3033,9 +3036,9 @@ MaybeObject* JSObject::SetLocalPropertyIgnoreAttributes( | 
| case CONSTANT_TRANSITION: | 
| // Replace with a MAP_TRANSITION to a new map with a FIELD, even | 
| // if the value is a function. | 
| - case NULL_DESCRIPTOR: | 
| return ConvertDescriptorToFieldAndMapTransition(name, value, attributes); | 
| case HANDLER: | 
| + case NONEXISTENT: | 
| UNREACHABLE(); | 
| } | 
| UNREACHABLE(); // keep the compiler happy | 
| @@ -3328,11 +3331,11 @@ MaybeObject* JSObject::NormalizeProperties(PropertyNormalizationMode mode, | 
| } | 
| case MAP_TRANSITION: | 
| case CONSTANT_TRANSITION: | 
| - case NULL_DESCRIPTOR: | 
| case INTERCEPTOR: | 
| break; | 
| case HANDLER: | 
| case NORMAL: | 
| + case NONEXISTENT: | 
| UNREACHABLE(); | 
| break; | 
| } | 
| @@ -3676,8 +3679,7 @@ MaybeObject* JSObject::GetHiddenPropertiesDictionary(bool create_if_absent) { | 
| this->FastPropertyAt(descriptors->GetFieldIndex(0)); | 
| return StringDictionary::cast(hidden_store); | 
| } else { | 
| - ASSERT(descriptors->GetType(0) == NULL_DESCRIPTOR || | 
| - descriptors->GetType(0) == MAP_TRANSITION); | 
| + ASSERT(descriptors->GetType(0) == MAP_TRANSITION); | 
| } | 
| } | 
| } else { | 
| @@ -3725,8 +3727,7 @@ MaybeObject* JSObject::SetHiddenPropertiesDictionary( | 
| this->FastPropertyAtPut(descriptors->GetFieldIndex(0), dictionary); | 
| return this; | 
| } else { | 
| - ASSERT(descriptors->GetType(0) == NULL_DESCRIPTOR || | 
| - descriptors->GetType(0) == MAP_TRANSITION); | 
| + ASSERT(descriptors->GetType(0) == MAP_TRANSITION); | 
| } | 
| } | 
| } | 
| @@ -4173,9 +4174,7 @@ int Map::NumberOfDescribedProperties(PropertyAttributes filter) { | 
| int Map::PropertyIndexFor(String* name) { | 
| DescriptorArray* descs = instance_descriptors(); | 
| for (int i = 0; i < descs->number_of_descriptors(); i++) { | 
| - if (name->Equals(descs->GetKey(i)) && !descs->IsNullDescriptor(i)) { | 
| - return descs->GetFieldIndex(i); | 
| - } | 
| + if (name->Equals(descs->GetKey(i))) return descs->GetFieldIndex(i); | 
| } | 
| return -1; | 
| } | 
| @@ -5082,11 +5081,13 @@ class IntrusiveMapTransitionIterator { | 
| case CONSTANT_FUNCTION: | 
| case HANDLER: | 
| case INTERCEPTOR: | 
| - case NULL_DESCRIPTOR: | 
| // We definitely have no map transition. | 
| raw_index += 2; | 
| ++index; | 
| break; | 
| + case NONEXISTENT: | 
| + UNREACHABLE(); | 
| + break; | 
| } | 
| } | 
| if (index == descriptor_array_->number_of_descriptors()) { | 
| @@ -5876,7 +5877,6 @@ MaybeObject* DescriptorArray::CopyInsert(Descriptor* descriptor, | 
| // removing all other transitions is not supported. | 
| bool remove_transitions = transition_flag == REMOVE_TRANSITIONS; | 
| ASSERT(remove_transitions == !descriptor->ContainsTransition()); | 
| - ASSERT(descriptor->GetDetails().type() != NULL_DESCRIPTOR); | 
| // Ensure the key is a symbol. | 
| { MaybeObject* maybe_result = descriptor->KeyToSymbol(); | 
| @@ -5885,7 +5885,6 @@ MaybeObject* DescriptorArray::CopyInsert(Descriptor* descriptor, | 
| int new_size = 0; | 
| for (int i = 0; i < number_of_descriptors(); i++) { | 
| - if (IsNullDescriptor(i)) continue; | 
| if (remove_transitions && IsTransitionOnly(i)) continue; | 
| new_size++; | 
| } | 
| @@ -5943,8 +5942,7 @@ MaybeObject* DescriptorArray::CopyInsert(Descriptor* descriptor, | 
| insertion_index = to_index++; | 
| if (replacing) from_index++; | 
| } else { | 
| - if (!(IsNullDescriptor(from_index) || | 
| - (remove_transitions && IsTransitionOnly(from_index)))) { | 
| + if (!(remove_transitions && IsTransitionOnly(from_index))) { | 
| MaybeObject* copy_result = | 
| new_descriptors->CopyFrom(to_index++, this, from_index, witness); | 
| if (copy_result->IsFailure()) return copy_result; | 
| @@ -6074,8 +6072,7 @@ int DescriptorArray::BinarySearch(String* name, int low, int high) { | 
| } | 
| for (; low <= limit && GetKey(low)->Hash() == hash; ++low) { | 
| - if (GetKey(low)->Equals(name) && !IsNullDescriptor(low)) | 
| - return low; | 
| + if (GetKey(low)->Equals(name)) return low; | 
| } | 
| return kNotFound; | 
| @@ -6087,9 +6084,7 @@ int DescriptorArray::LinearSearch(SearchMode mode, String* name, int len) { | 
| for (int number = 0; number < len; number++) { | 
| String* entry = GetKey(number); | 
| if (mode == EXPECT_SORTED && entry->Hash() > hash) break; | 
| - if (name->Equals(entry) && !IsNullDescriptor(number)) { | 
| - return number; | 
| - } | 
| + if (name->Equals(entry)) return number; | 
| } | 
| return kNotFound; | 
| } | 
| @@ -7348,75 +7343,149 @@ static bool ClearBackPointer(Heap* heap, Object* target) { | 
| } | 
| -void Map::ClearNonLiveTransitions(Heap* heap) { | 
| - DescriptorArray* d = DescriptorArray::cast( | 
| - *RawField(this, Map::kInstanceDescriptorsOrBitField3Offset)); | 
| - if (d->IsEmpty()) return; | 
| - Map* elements_transition = d->elements_transition_map(); | 
| - if (elements_transition != NULL && | 
| - ClearBackPointer(heap, elements_transition)) { | 
| - d->ClearElementsTransition(); | 
| +// This function should only be called from within the GC, since it uses | 
| +// IncrementLiveBytesFromGC. If called from anywhere else, this results in an | 
| +// inconsistent live-bytes count. | 
| +static void RightTrimFixedArray(Heap* heap, FixedArray* elms, int to_trim) { | 
| + ASSERT(elms->map() != HEAP->fixed_cow_array_map()); | 
| + // For now this trick is only applied to fixed arrays in new and paged space. | 
| + // In large object space the object's start must coincide with chunk | 
| + // and thus the trick is just not applicable. | 
| + ASSERT(!HEAP->lo_space()->Contains(elms)); | 
| + | 
| + const int len = elms->length(); | 
| + | 
| + ASSERT(to_trim < len); | 
| + | 
| + Address new_end = elms->address() + FixedArray::SizeFor(len - to_trim); | 
| + | 
| +#ifdef DEBUG | 
| + if (to_trim > FixedArray::kHeaderSize / kPointerSize && | 
| + !heap->InNewSpace(elms)) { | 
| 
Michael Starzinger
2012/06/25 12:48:38
Since it's debug code now, I think we should just
 | 
| + // If we are doing a big trim in old space then we zap the space. | 
| + Object** zap = reinterpret_cast<Object**>(new_end); | 
| + zap++; // Header of filler must be at least one word so skip that. | 
| 
Michael Starzinger
2012/06/25 12:48:38
Using the same reasoning, I think we should also j
 | 
| + for (int i = 1; i < to_trim; i++) { | 
| + *zap++ = Smi::FromInt(0); | 
| + } | 
| } | 
| - Smi* NullDescriptorDetails = | 
| - PropertyDetails(NONE, NULL_DESCRIPTOR).AsSmi(); | 
| - for (int i = 0; i < d->number_of_descriptors(); ++i) { | 
| - // If the pair (value, details) is a map transition, check if the target is | 
| - // live. If not, null the descriptor. Also drop the back pointer for that | 
| - // map transition, so that this map is not reached again by following a back | 
| - // pointer from that non-live map. | 
| - bool keep_entry = false; | 
| - PropertyDetails details(d->GetDetails(i)); | 
| - switch (details.type()) { | 
| - case MAP_TRANSITION: | 
| - case CONSTANT_TRANSITION: | 
| - keep_entry = !ClearBackPointer(heap, d->GetValue(i)); | 
| - break; | 
| - case CALLBACKS: { | 
| - Object* object = d->GetValue(i); | 
| - if (object->IsAccessorPair()) { | 
| - AccessorPair* accessors = AccessorPair::cast(object); | 
| - Object* getter = accessors->getter(); | 
| - if (getter->IsMap()) { | 
| - if (ClearBackPointer(heap, getter)) { | 
| - accessors->set_getter(heap->the_hole_value()); | 
| - } else { | 
| - keep_entry = true; | 
| - } | 
| - } else if (!getter->IsTheHole()) { | 
| - keep_entry = true; | 
| +#endif | 
| + | 
| + int size_delta = to_trim * kPointerSize; | 
| + | 
| + // Technically in new space this write might be omitted (except for | 
| + // debug mode which iterates through the heap), but to play safer | 
| + // we still do it. | 
| + heap->CreateFillerObjectAt(new_end, size_delta); | 
| + | 
| + elms->set_length(len - to_trim); | 
| + | 
| + // Maintain marking consistency for IncrementalMarking. | 
| + if (Marking::IsBlack(Marking::MarkBitFrom(elms))) { | 
| + MemoryChunk::IncrementLiveBytesFromGC(elms->address(), -size_delta); | 
| + } | 
| +} | 
| + | 
| + | 
| +static bool ClearNonLiveTransitionsFromDescriptor(Heap* heap, | 
| 
Michael Starzinger
2012/06/25 12:48:38
Add a comment describing the semantics of the retu
 | 
| + DescriptorArray* d, | 
| + int descriptor_index) { | 
| + // If the pair (value, details) is a map transition, check if the target is | 
| + // live. If not, null the descriptor. Also drop the back pointer for that | 
| + // map transition, so that this map is not reached again by following a back | 
| + // pointer from that non-live map. | 
| + PropertyDetails details(d->GetDetails(descriptor_index)); | 
| + switch (details.type()) { | 
| + case MAP_TRANSITION: | 
| + case CONSTANT_TRANSITION: | 
| + return ClearBackPointer(heap, d->GetValue(descriptor_index)); | 
| + case CALLBACKS: { | 
| + Object* object = d->GetValue(descriptor_index); | 
| + if (object->IsAccessorPair()) { | 
| + bool cleared = true; | 
| + AccessorPair* accessors = AccessorPair::cast(object); | 
| + Object* getter = accessors->getter(); | 
| + if (getter->IsMap()) { | 
| + if (ClearBackPointer(heap, getter)) { | 
| + accessors->set_getter(heap->the_hole_value()); | 
| + } else { | 
| + cleared = false; | 
| } | 
| - Object* setter = accessors->setter(); | 
| - if (setter->IsMap()) { | 
| - if (ClearBackPointer(heap, setter)) { | 
| - accessors->set_setter(heap->the_hole_value()); | 
| - } else { | 
| - keep_entry = true; | 
| - } | 
| - } else if (!setter->IsTheHole()) { | 
| - keep_entry = true; | 
| + } else if (!getter->IsTheHole()) { | 
| + cleared = false; | 
| + } | 
| + Object* setter = accessors->setter(); | 
| + if (setter->IsMap()) { | 
| + if (ClearBackPointer(heap, setter)) { | 
| + accessors->set_setter(heap->the_hole_value()); | 
| + } else { | 
| + cleared = false; | 
| } | 
| - } else { | 
| - keep_entry = true; | 
| + } else if (!setter->IsTheHole()) { | 
| + cleared = false; | 
| } | 
| - break; | 
| + return cleared; | 
| } | 
| - case NORMAL: | 
| - case FIELD: | 
| - case CONSTANT_FUNCTION: | 
| - case HANDLER: | 
| - case INTERCEPTOR: | 
| - case NULL_DESCRIPTOR: | 
| - keep_entry = true; | 
| - break; | 
| + return false; | 
| } | 
| - // Make sure that an entry containing only dead transitions gets collected. | 
| - // What we *really* want to do here is removing this entry completely, but | 
| - // for technical reasons we can't do this, so we zero it out instead. | 
| - if (!keep_entry) { | 
| - d->SetDetailsUnchecked(i, NullDescriptorDetails); | 
| - d->SetNullValueUnchecked(i, heap); | 
| + case NORMAL: | 
| + case FIELD: | 
| + case CONSTANT_FUNCTION: | 
| + case HANDLER: | 
| + case INTERCEPTOR: | 
| + return false; | 
| + case NONEXISTENT: | 
| + break; | 
| + } | 
| + UNREACHABLE(); | 
| + return true; | 
| +} | 
| + | 
| + | 
| +void Map::ClearNonLiveTransitions(Heap* heap) { | 
| + Object* array = *RawField(this, Map::kInstanceDescriptorsOrBitField3Offset); | 
| + // If there are no descriptors to be cleared, return. | 
| + // TODO(verwaest) Should be an assert, otherwise back pointers are not | 
| + // properly cleared. | 
| + if (array->IsSmi()) return; | 
| + DescriptorArray* d = DescriptorArray::cast(array); | 
| + | 
| + int descriptor_index = 0; | 
| + // Compact all live descriptors to the left. | 
| + for (int i = 0; i < d->number_of_descriptors(); ++i) { | 
| + if (!ClearNonLiveTransitionsFromDescriptor(heap, d, i)) { | 
| + if (i != descriptor_index) { | 
| + d->SetKeyUnchecked(heap, descriptor_index, d->GetKey(i)); | 
| + d->SetDetailsUnchecked(descriptor_index, d->GetDetails(i).AsSmi()); | 
| + d->SetValueUnchecked(heap, descriptor_index, d->GetValue(i)); | 
| + } | 
| + descriptor_index++; | 
| } | 
| } | 
| + | 
| + Map* elements_transition = d->elements_transition_map(); | 
| + if (elements_transition != NULL && | 
| + ClearBackPointer(heap, elements_transition)) { | 
| + elements_transition = NULL; | 
| + d->ClearElementsTransition(); | 
| + } else { | 
| + // If there are no descriptors to be cleared, return. | 
| + // TODO(verwaest) Should be an assert, otherwise back pointers are not | 
| + // properly cleared. | 
| + if (descriptor_index == d->number_of_descriptors()) return; | 
| + } | 
| + | 
| + // If the final descriptor array does not contain any live descriptors, remove | 
| + // the descriptor array from the map. | 
| + if (descriptor_index == 0 && elements_transition == NULL) { | 
| + ClearDescriptorArray(); | 
| + return; | 
| + } | 
| + | 
| + int trim = d->number_of_descriptors() - descriptor_index; | 
| + if (trim > 0) { | 
| + RightTrimFixedArray(heap, d, trim * DescriptorArray::kDescriptorSize); | 
| + } | 
| } | 
| @@ -8544,7 +8613,9 @@ const char* Code::PropertyType2String(PropertyType type) { | 
| case INTERCEPTOR: return "INTERCEPTOR"; | 
| case MAP_TRANSITION: return "MAP_TRANSITION"; | 
| case CONSTANT_TRANSITION: return "CONSTANT_TRANSITION"; | 
| - case NULL_DESCRIPTOR: return "NULL_DESCRIPTOR"; | 
| + case NONEXISTENT: | 
| + UNREACHABLE(); | 
| + break; | 
| } | 
| UNREACHABLE(); // keep the compiler happy | 
| return NULL; | 
| @@ -11243,8 +11314,10 @@ bool StringDictionary::ContainsTransition(int entry) { | 
| case CONSTANT_FUNCTION: | 
| case HANDLER: | 
| case INTERCEPTOR: | 
| - case NULL_DESCRIPTOR: | 
| return false; | 
| + case NONEXISTENT: | 
| + UNREACHABLE(); | 
| + break; | 
| } | 
| UNREACHABLE(); // Keep the compiler happy. | 
| return false; |