Chromium Code Reviews| Index: src/objects.cc |
| diff --git a/src/objects.cc b/src/objects.cc |
| index 0941e792f04e98f3391c5a8757da74902d74e21e..c4f87c4b91a738edb00d54fd7e64e1650b404293 100644 |
| --- a/src/objects.cc |
| +++ b/src/objects.cc |
| @@ -4935,75 +4935,190 @@ void Map::RemoveFromCodeCache(String* name, Code* code, int index) { |
| } |
| -void Map::TraverseTransitionTree(TraverseCallback callback, void* data) { |
| - // Traverse the transition tree without using a stack. We do this by |
| - // reversing the pointers in the maps and descriptor arrays. |
| - Map* current = this; |
| - Map* meta_map = GetHeap()->meta_map(); |
| - Object** map_or_index_field = NULL; |
| - while (current != meta_map) { |
| - DescriptorArray* d = reinterpret_cast<DescriptorArray*>( |
| - *RawField(current, Map::kInstanceDescriptorsOrBitField3Offset)); |
| - if (!d->IsEmpty()) { |
| - FixedArray* contents = reinterpret_cast<FixedArray*>( |
| - d->get(DescriptorArray::kContentArrayIndex)); |
| - map_or_index_field = RawField(contents, HeapObject::kMapOffset); |
| - Object* map_or_index = *map_or_index_field; |
| - bool map_done = true; // Controls a nested continue statement. |
| - for (int i = map_or_index->IsSmi() ? Smi::cast(map_or_index)->value() : 0; |
| - i < contents->length(); |
| - i += 2) { |
| - PropertyDetails details(Smi::cast(contents->get(i + 1))); |
| - if (details.IsTransition()) { |
| - // Found a map in the transition array. We record our progress in |
| - // the transition array by recording the current map in the map field |
| - // of the next map and recording the index in the transition array in |
| - // the map field of the array. |
| - Map* next = Map::cast(contents->get(i)); |
| - next->set_map_no_write_barrier(current); |
| - *map_or_index_field = Smi::FromInt(i + 2); |
| - current = next; |
| - map_done = false; |
| - break; |
| - } |
| - } |
| - if (!map_done) continue; |
| - } else { |
| - map_or_index_field = NULL; |
| - } |
| - // That was the regular transitions, now for the prototype transitions. |
| - FixedArray* prototype_transitions = |
| - current->unchecked_prototype_transitions(); |
| - Object** proto_map_or_index_field = |
| - RawField(prototype_transitions, HeapObject::kMapOffset); |
| - Object* map_or_index = *proto_map_or_index_field; |
| - const int start = kProtoTransitionHeaderSize + kProtoTransitionMapOffset; |
| - int i = map_or_index->IsSmi() ? Smi::cast(map_or_index)->value() : start; |
| - if (i < prototype_transitions->length()) { |
| - // Found a map in the prototype transition array. Record progress in |
| - // an analogous way to the regular transitions array above. |
| - Object* perhaps_map = prototype_transitions->get(i); |
| - if (perhaps_map->IsMap()) { |
| - Map* next = Map::cast(perhaps_map); |
| - next->set_map_no_write_barrier(current); |
| - *proto_map_or_index_field = |
| - Smi::FromInt(i + kProtoTransitionElementsPerEntry); |
| - current = next; |
| - continue; |
| +// An iterator over all map transitions in an descriptor array, reusing the map |
| +// field of the contens array while it is running. |
| +class IterableDescriptorArray : public DescriptorArray { |
| + public: |
| + void Start() { |
| + ASSERT(!IsRunning()); |
| + if (HasContentArray()) *ContentHeader() = Smi::FromInt(0); |
| + } |
| + |
| + bool IsRunning() { |
|
Vyacheslav Egorov (Chromium)
2012/01/20 12:04:54
I find the name confusing because descriptor itsel
Sven Panne
2012/01/20 13:15:33
If we really wanted to be clean, we should introdu
Vyacheslav Egorov (Chromium)
2012/01/20 13:59:50
IsIterating sounds fine.
|
| + return HasContentArray() && (*ContentHeader())->IsSmi(); |
| + } |
| + |
| + Map* Next() { |
| + ASSERT(IsRunning()); |
| + FixedArray* contents = ContentArray(); |
| + int index = Smi::cast(*ContentHeader())->value(); |
| + while (index < contents->length()) { |
| + int next_index = index + 2; |
| + PropertyDetails details(Smi::cast(contents->get(index + 1))); |
| + if (details.IsTransition()) { |
| + *ContentHeader() = Smi::FromInt(next_index); |
| + return static_cast<Map*>(contents->get(index)); |
| } |
| + index = next_index; |
| + } |
| + *ContentHeader() = GetHeap()->fixed_array_map(); |
| + return NULL; |
| + } |
| + |
| + private: |
| + bool HasContentArray() { return length() > kContentArrayIndex; } |
| + |
| + FixedArray* ContentArray() { |
| + return static_cast<FixedArray*>(get(kContentArrayIndex)); |
| + } |
| + |
| + Object** ContentHeader() { |
| + return RawField(ContentArray(), kMapOffset); |
| + } |
| +}; |
| + |
| + |
| +// An iterator over all prototype transitions, reusing the map field of the |
| +// underlying array while it is running. |
| +class IterablePrototypeTransitions : public FixedArray { |
|
Vyacheslav Egorov (Chromium)
2012/01/20 12:04:54
I think parts IterableDescriptorArray & IterablePr
Sven Panne
2012/01/20 13:15:33
There are several reasons why I would prefer to av
Vyacheslav Egorov (Chromium)
2012/01/20 13:59:50
* Yes, it is a pure accident that both are fixed a
Sven Panne
2012/01/20 14:54:50
What code would actually be shared? The structure
Vyacheslav Egorov (Chromium)
2012/01/20 16:30:10
I outlined what I think can be shared in my intrus
|
| + public: |
| + void Start() { |
| + ASSERT(!IsRunning()); |
| + if (HasTransitions()) *Header() = Smi::FromInt(0); |
| + } |
| + |
| + bool IsRunning() { |
| + return HasTransitions() && (*Header())->IsSmi(); |
| + } |
| + |
| + Map* Next() { |
| + ASSERT(IsRunning()); |
| + int index = Smi::cast(*Header())->value(); |
| + if (index < NumberOfTransitions()) { |
| + *Header() = Smi::FromInt(index + 1); |
| + return GetTransition(index); |
| + } |
| + *Header() = GetHeap()->fixed_array_map(); |
| + return NULL; |
| + } |
| + |
| + private: |
| + bool HasTransitions() { return length() >= Map::kProtoTransitionHeaderSize; } |
| + |
| + Object** Header() { |
| + return RawField(this, kMapOffset); |
| + } |
| + |
| + int NumberOfTransitions() { |
| + return Smi::cast(get(Map::kProtoTransitionNumberOfEntriesOffset))->value(); |
| + } |
| + |
| + Map* GetTransition(int transitionNumber) { |
| + return Map::cast( |
| + get(Map::kProtoTransitionHeaderSize + |
| + Map::kProtoTransitionMapOffset + |
| + transitionNumber * Map::kProtoTransitionElementsPerEntry)); |
| + } |
| +}; |
| + |
| + |
| +// To traverse the transition tree iteratively, we have to store two kinds of |
| +// information in a map: The parent map in the traversal and which children of a |
| +// node have already been visited. To do this without additional memory, we |
| +// temporarily reuse two maps with known values: |
| +// |
| +// (1) The map of the map temporarily holds the parent, and is restored to the |
| +// meta map afterwards. |
| +// |
| +// (2) The info which children have already been visited depends on which part |
| +// of the map we currently iterate: |
| +// |
| +// (a) If we currently follow normal map transitions, we temporarily store |
| +// the current index in the map of the FixedArray of the desciptor |
| +// array's contents, and restore it to the fixed array map afterwards. |
| +// Note that a single descriptor can have 0, 1, or 2 transitions. |
| +// |
| +// (b) If we currently follow prototype transitions, we temporarily store |
| +// the current index in the map of the FixedArray holding the prototype |
| +// transitions, and restore it to the fixed array map afterwards. |
| +// |
| +// Note that the child iterator is just a concatenation of two iterators: One |
| +// iterating over map transitions and one iterating over prototype transisitons. |
| +class IterableMap : public Map { |
| + public: |
| + // Record the parent in the traversal within this map. Note that this destroys |
| + // this map's map! |
| + void setParent(IterableMap* parent) { set_map_no_write_barrier(parent); } |
|
Vyacheslav Egorov (Chromium)
2012/01/20 16:30:10
naming convention: first letter capital
|
| + |
| + // Reset the current map's map, returning the parent previously stored in it. |
| + IterableMap* getAndResetParent() { |
|
Vyacheslav Egorov (Chromium)
2012/01/20 16:30:10
naming convention: first letter capital
|
| + IterableMap* old_parent = static_cast<IterableMap*>(map()); |
| + set_map_no_write_barrier(GetHeap()->meta_map()); |
| + return old_parent; |
| + } |
| + |
| + // Start iterating over this map's children, possibly destroying a FixedArray |
| + // map (see explanation above). |
| + void childIteratorStart() { |
|
Vyacheslav Egorov (Chromium)
2012/01/20 16:30:10
naming convention: first letter capital
|
| + iterable_descriptor_array()->Start(); |
| + iterable_prototype_transitions()->Start(); |
| + } |
| + |
| + // If we have an unvisited child map, return that one and advance. If we have |
| + // none, return NULL and reset any destroyed FixedArray maps. |
| + IterableMap* childIteratorNext() { |
|
Vyacheslav Egorov (Chromium)
2012/01/20 16:30:10
naming convention: first letter capital
|
| + if (iterable_descriptor_array()->IsRunning()) { |
| + Map* next = iterable_descriptor_array()->Next(); |
| + if (next != NULL) return static_cast<IterableMap*>(next); |
| } |
| - *proto_map_or_index_field = GetHeap()->fixed_array_map(); |
| - if (map_or_index_field != NULL) { |
| - *map_or_index_field = GetHeap()->fixed_array_map(); |
| + if (iterable_prototype_transitions()->IsRunning()) { |
| + Map* next = iterable_prototype_transitions()->Next(); |
| + if (next != NULL) return static_cast<IterableMap*>(next); |
| } |
| + return NULL; |
| + } |
| + |
| + private: |
| + IterableDescriptorArray* iterable_descriptor_array() { |
| + return static_cast<IterableDescriptorArray*>(instance_descriptors()); |
| + } |
| + |
| + IterablePrototypeTransitions* iterable_prototype_transitions() { |
| + return static_cast<IterablePrototypeTransitions*>( |
| + unchecked_prototype_transitions()); |
| + } |
| +}; |
| + |
| - // The callback expects a map to have a real map as its map, so we save |
| - // the map field, which is being used to track the traversal and put the |
| - // correct map (the meta_map) in place while we do the callback. |
| - Map* prev = current->map(); |
| - current->set_map_no_write_barrier(meta_map); |
| - callback(current, data); |
| - current = prev; |
| +// Traverse the transition tree in postorder without using the C++ stack. We can |
| +// deduce the algorithm below using the following steps: |
| +// |
| +// (1) Implement the recursive textbook algorithm for visiting a tree. |
|
Vyacheslav Egorov (Chromium)
2012/01/20 12:04:54
I don't think these 4 steps make algorithm any cle
Sven Panne
2012/01/20 13:15:33
I can easily live without the comment, but this wo
Vyacheslav Egorov (Chromium)
2012/01/20 13:59:50
I agree that this comment is absolutely correct, a
Sven Panne
2012/01/20 14:54:50
Understanding the derivation of a non-trivial algo
|
| +// |
| +// (2) Transform that into an iterative version using an explicit stack, where |
| +// each stack entry contains a pair of values: Which tree node do we |
| +// currently visit and info about which children of this node have already |
| +// been visited. |
| +// |
| +// (3) Use local variables for the top of stack pair. |
| +// |
| +// (4) Put the stack entry pair directly into the nodes, correctly resetting |
| +// any overwritten parts after the visit. |
| +void Map::TraverseTransitionTree(TraverseCallback callback, void* data) { |
| + IterableMap* current = static_cast<IterableMap*>(this); |
| + current->childIteratorStart(); |
| + while (true) { |
|
Vyacheslav Egorov (Chromium)
2012/01/20 12:04:54
can it be changed into do { } while (current != me
Sven Panne
2012/01/20 13:15:33
I would prefer to leave it as it is, because using
Vyacheslav Egorov (Chromium)
2012/01/20 13:59:50
It would streamline control-flow (I would read thi
Sven Panne
2012/01/20 14:54:50
HasNext would complicate the currently easy iterat
Vyacheslav Egorov (Chromium)
2012/01/20 16:30:10
I consider loops with no special exits easier to r
|
| + IterableMap* child = current->childIteratorNext(); |
|
Vyacheslav Egorov (Chromium)
2012/01/20 12:04:54
I think it might be more clear to have descendInto
Sven Panne
2012/01/20 13:15:33
I think merging those calls together will make thi
Vyacheslav Egorov (Chromium)
2012/01/20 13:59:50
I have to agree that merging things together might
|
| + if (child != NULL) { |
| + child->childIteratorStart(); |
| + child->setParent(current); |
| + current = child; |
| + } else { |
| + IterableMap* parent = current->getAndResetParent(); |
| + callback(current, data); |
| + if (current == this) break; |
| + current = parent; |
| + } |
| } |
| } |