Home | History | Annotate | Download | only in heap
      1 // Copyright 2016 the V8 project authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #ifndef V8_SLOT_SET_H
      6 #define V8_SLOT_SET_H
      7 
      8 #include <map>
      9 #include <stack>
     10 
     11 #include "src/allocation.h"
     12 #include "src/base/atomic-utils.h"
     13 #include "src/base/bits.h"
     14 #include "src/utils.h"
     15 
     16 namespace v8 {
     17 namespace internal {
     18 
     19 enum SlotCallbackResult { KEEP_SLOT, REMOVE_SLOT };
     20 
     21 // Data structure for maintaining a set of slots in a standard (non-large)
     22 // page. The base address of the page must be set with SetPageStart before any
     23 // operation.
     24 // The data structure assumes that the slots are pointer size aligned and
     25 // splits the valid slot offset range into kBuckets buckets.
     26 // Each bucket is a bitmap with a bit corresponding to a single slot offset.
     27 class SlotSet : public Malloced {
     28  public:
     29   enum EmptyBucketMode {
     30     FREE_EMPTY_BUCKETS,     // An empty bucket will be deallocated immediately.
     31     PREFREE_EMPTY_BUCKETS,  // An empty bucket will be unlinked from the slot
     32                             // set, but deallocated on demand by a sweeper
     33                             // thread.
     34     KEEP_EMPTY_BUCKETS      // An empty bucket will be kept.
     35   };
     36 
     37   SlotSet() {
     38     for (int i = 0; i < kBuckets; i++) {
     39       bucket[i].SetValue(nullptr);
     40     }
     41   }
     42 
     43   ~SlotSet() {
     44     for (int i = 0; i < kBuckets; i++) {
     45       ReleaseBucket(i);
     46     }
     47     FreeToBeFreedBuckets();
     48   }
     49 
     50   void SetPageStart(Address page_start) { page_start_ = page_start; }
     51 
     52   // The slot offset specifies a slot at address page_start_ + slot_offset.
     53   // This method should only be called on the main thread because concurrent
     54   // allocation of the bucket is not thread-safe.
     55   void Insert(int slot_offset) {
     56     int bucket_index, cell_index, bit_index;
     57     SlotToIndices(slot_offset, &bucket_index, &cell_index, &bit_index);
     58     base::AtomicValue<uint32_t>* current_bucket = bucket[bucket_index].Value();
     59     if (current_bucket == nullptr) {
     60       current_bucket = AllocateBucket();
     61       bucket[bucket_index].SetValue(current_bucket);
     62     }
     63     if (!(current_bucket[cell_index].Value() & (1u << bit_index))) {
     64       current_bucket[cell_index].SetBit(bit_index);
     65     }
     66   }
     67 
     68   // The slot offset specifies a slot at address page_start_ + slot_offset.
     69   void Remove(int slot_offset) {
     70     int bucket_index, cell_index, bit_index;
     71     SlotToIndices(slot_offset, &bucket_index, &cell_index, &bit_index);
     72     base::AtomicValue<uint32_t>* current_bucket = bucket[bucket_index].Value();
     73     if (current_bucket != nullptr) {
     74       uint32_t cell = current_bucket[cell_index].Value();
     75       if (cell) {
     76         uint32_t bit_mask = 1u << bit_index;
     77         if (cell & bit_mask) {
     78           current_bucket[cell_index].ClearBit(bit_index);
     79         }
     80       }
     81     }
     82   }
     83 
     84   // The slot offsets specify a range of slots at addresses:
     85   // [page_start_ + start_offset ... page_start_ + end_offset).
     86   void RemoveRange(int start_offset, int end_offset, EmptyBucketMode mode) {
     87     CHECK_LE(end_offset, 1 << kPageSizeBits);
     88     DCHECK_LE(start_offset, end_offset);
     89     int start_bucket, start_cell, start_bit;
     90     SlotToIndices(start_offset, &start_bucket, &start_cell, &start_bit);
     91     int end_bucket, end_cell, end_bit;
     92     SlotToIndices(end_offset, &end_bucket, &end_cell, &end_bit);
     93     uint32_t start_mask = (1u << start_bit) - 1;
     94     uint32_t end_mask = ~((1u << end_bit) - 1);
     95     if (start_bucket == end_bucket && start_cell == end_cell) {
     96       ClearCell(start_bucket, start_cell, ~(start_mask | end_mask));
     97       return;
     98     }
     99     int current_bucket = start_bucket;
    100     int current_cell = start_cell;
    101     ClearCell(current_bucket, current_cell, ~start_mask);
    102     current_cell++;
    103     base::AtomicValue<uint32_t>* bucket_ptr = bucket[current_bucket].Value();
    104     if (current_bucket < end_bucket) {
    105       if (bucket_ptr != nullptr) {
    106         ClearBucket(bucket_ptr, current_cell, kCellsPerBucket);
    107       }
    108       // The rest of the current bucket is cleared.
    109       // Move on to the next bucket.
    110       current_bucket++;
    111       current_cell = 0;
    112     }
    113     DCHECK(current_bucket == end_bucket ||
    114            (current_bucket < end_bucket && current_cell == 0));
    115     while (current_bucket < end_bucket) {
    116       if (mode == PREFREE_EMPTY_BUCKETS) {
    117         PreFreeEmptyBucket(current_bucket);
    118       } else if (mode == FREE_EMPTY_BUCKETS) {
    119         ReleaseBucket(current_bucket);
    120       } else {
    121         DCHECK(mode == KEEP_EMPTY_BUCKETS);
    122         bucket_ptr = bucket[current_bucket].Value();
    123         if (bucket_ptr) {
    124           ClearBucket(bucket_ptr, 0, kCellsPerBucket);
    125         }
    126       }
    127       current_bucket++;
    128     }
    129     // All buckets between start_bucket and end_bucket are cleared.
    130     bucket_ptr = bucket[current_bucket].Value();
    131     DCHECK(current_bucket == end_bucket && current_cell <= end_cell);
    132     if (current_bucket == kBuckets || bucket_ptr == nullptr) {
    133       return;
    134     }
    135     while (current_cell < end_cell) {
    136       bucket_ptr[current_cell].SetValue(0);
    137       current_cell++;
    138     }
    139     // All cells between start_cell and end_cell are cleared.
    140     DCHECK(current_bucket == end_bucket && current_cell == end_cell);
    141     ClearCell(end_bucket, end_cell, ~end_mask);
    142   }
    143 
    144   // The slot offset specifies a slot at address page_start_ + slot_offset.
    145   bool Lookup(int slot_offset) {
    146     int bucket_index, cell_index, bit_index;
    147     SlotToIndices(slot_offset, &bucket_index, &cell_index, &bit_index);
    148     if (bucket[bucket_index].Value() != nullptr) {
    149       uint32_t cell = bucket[bucket_index].Value()[cell_index].Value();
    150       return (cell & (1u << bit_index)) != 0;
    151     }
    152     return false;
    153   }
    154 
    155   // Iterate over all slots in the set and for each slot invoke the callback.
    156   // If the callback returns REMOVE_SLOT then the slot is removed from the set.
    157   // Returns the new number of slots.
    158   // This method should only be called on the main thread.
    159   //
    160   // Sample usage:
    161   // Iterate([](Address slot_address) {
    162   //    if (good(slot_address)) return KEEP_SLOT;
    163   //    else return REMOVE_SLOT;
    164   // });
    165   template <typename Callback>
    166   int Iterate(Callback callback, EmptyBucketMode mode) {
    167     int new_count = 0;
    168     for (int bucket_index = 0; bucket_index < kBuckets; bucket_index++) {
    169       base::AtomicValue<uint32_t>* current_bucket =
    170           bucket[bucket_index].Value();
    171       if (current_bucket != nullptr) {
    172         int in_bucket_count = 0;
    173         int cell_offset = bucket_index * kBitsPerBucket;
    174         for (int i = 0; i < kCellsPerBucket; i++, cell_offset += kBitsPerCell) {
    175           if (current_bucket[i].Value()) {
    176             uint32_t cell = current_bucket[i].Value();
    177             uint32_t old_cell = cell;
    178             uint32_t mask = 0;
    179             while (cell) {
    180               int bit_offset = base::bits::CountTrailingZeros32(cell);
    181               uint32_t bit_mask = 1u << bit_offset;
    182               uint32_t slot = (cell_offset + bit_offset) << kPointerSizeLog2;
    183               if (callback(page_start_ + slot) == KEEP_SLOT) {
    184                 ++in_bucket_count;
    185               } else {
    186                 mask |= bit_mask;
    187               }
    188               cell ^= bit_mask;
    189             }
    190             uint32_t new_cell = old_cell & ~mask;
    191             if (old_cell != new_cell) {
    192               while (!current_bucket[i].TrySetValue(old_cell, new_cell)) {
    193                 // If TrySetValue fails, the cell must have changed. We just
    194                 // have to read the current value of the cell, & it with the
    195                 // computed value, and retry. We can do this, because this
    196                 // method will only be called on the main thread and filtering
    197                 // threads will only remove slots.
    198                 old_cell = current_bucket[i].Value();
    199                 new_cell = old_cell & ~mask;
    200               }
    201             }
    202           }
    203         }
    204         if (mode == PREFREE_EMPTY_BUCKETS && in_bucket_count == 0) {
    205           PreFreeEmptyBucket(bucket_index);
    206         }
    207         new_count += in_bucket_count;
    208       }
    209     }
    210     return new_count;
    211   }
    212 
    213   void FreeToBeFreedBuckets() {
    214     base::LockGuard<base::Mutex> guard(&to_be_freed_buckets_mutex_);
    215     while (!to_be_freed_buckets_.empty()) {
    216       base::AtomicValue<uint32_t>* top = to_be_freed_buckets_.top();
    217       to_be_freed_buckets_.pop();
    218       DeleteArray<base::AtomicValue<uint32_t>>(top);
    219     }
    220   }
    221 
    222  private:
    223   static const int kMaxSlots = (1 << kPageSizeBits) / kPointerSize;
    224   static const int kCellsPerBucket = 32;
    225   static const int kCellsPerBucketLog2 = 5;
    226   static const int kBitsPerCell = 32;
    227   static const int kBitsPerCellLog2 = 5;
    228   static const int kBitsPerBucket = kCellsPerBucket * kBitsPerCell;
    229   static const int kBitsPerBucketLog2 = kCellsPerBucketLog2 + kBitsPerCellLog2;
    230   static const int kBuckets = kMaxSlots / kCellsPerBucket / kBitsPerCell;
    231 
    232   base::AtomicValue<uint32_t>* AllocateBucket() {
    233     base::AtomicValue<uint32_t>* result =
    234         NewArray<base::AtomicValue<uint32_t>>(kCellsPerBucket);
    235     for (int i = 0; i < kCellsPerBucket; i++) {
    236       result[i].SetValue(0);
    237     }
    238     return result;
    239   }
    240 
    241   void ClearBucket(base::AtomicValue<uint32_t>* bucket, int start_cell,
    242                    int end_cell) {
    243     DCHECK_GE(start_cell, 0);
    244     DCHECK_LE(end_cell, kCellsPerBucket);
    245     int current_cell = start_cell;
    246     while (current_cell < kCellsPerBucket) {
    247       bucket[current_cell].SetValue(0);
    248       current_cell++;
    249     }
    250   }
    251 
    252   void PreFreeEmptyBucket(int bucket_index) {
    253     base::AtomicValue<uint32_t>* bucket_ptr = bucket[bucket_index].Value();
    254     if (bucket_ptr != nullptr) {
    255       base::LockGuard<base::Mutex> guard(&to_be_freed_buckets_mutex_);
    256       to_be_freed_buckets_.push(bucket_ptr);
    257       bucket[bucket_index].SetValue(nullptr);
    258     }
    259   }
    260 
    261   void ReleaseBucket(int bucket_index) {
    262     DeleteArray<base::AtomicValue<uint32_t>>(bucket[bucket_index].Value());
    263     bucket[bucket_index].SetValue(nullptr);
    264   }
    265 
    266   void ClearCell(int bucket_index, int cell_index, uint32_t mask) {
    267     if (bucket_index < kBuckets) {
    268       base::AtomicValue<uint32_t>* cells = bucket[bucket_index].Value();
    269       if (cells != nullptr) {
    270         uint32_t cell = cells[cell_index].Value();
    271         if (cell) cells[cell_index].SetBits(0, mask);
    272       }
    273     } else {
    274       // GCC bug 59124: Emits wrong warnings
    275       // "array subscript is above array bounds"
    276       UNREACHABLE();
    277     }
    278   }
    279 
    280   // Converts the slot offset into bucket/cell/bit index.
    281   void SlotToIndices(int slot_offset, int* bucket_index, int* cell_index,
    282                      int* bit_index) {
    283     DCHECK_EQ(slot_offset % kPointerSize, 0);
    284     int slot = slot_offset >> kPointerSizeLog2;
    285     DCHECK(slot >= 0 && slot <= kMaxSlots);
    286     *bucket_index = slot >> kBitsPerBucketLog2;
    287     *cell_index = (slot >> kBitsPerCellLog2) & (kCellsPerBucket - 1);
    288     *bit_index = slot & (kBitsPerCell - 1);
    289   }
    290 
    291   base::AtomicValue<base::AtomicValue<uint32_t>*> bucket[kBuckets];
    292   Address page_start_;
    293   base::Mutex to_be_freed_buckets_mutex_;
    294   std::stack<base::AtomicValue<uint32_t>*> to_be_freed_buckets_;
    295 };
    296 
    297 enum SlotType {
    298   EMBEDDED_OBJECT_SLOT,
    299   OBJECT_SLOT,
    300   CELL_TARGET_SLOT,
    301   CODE_TARGET_SLOT,
    302   CODE_ENTRY_SLOT,
    303   DEBUG_TARGET_SLOT,
    304   CLEARED_SLOT
    305 };
    306 
    307 // Data structure for maintaining a multiset of typed slots in a page.
    308 // Typed slots can only appear in Code and JSFunction objects, so
    309 // the maximum possible offset is limited by the LargePage::kMaxCodePageSize.
    310 // The implementation is a chain of chunks, where each chunks is an array of
    311 // encoded (slot type, slot offset) pairs.
    312 // There is no duplicate detection and we do not expect many duplicates because
    313 // typed slots contain V8 internal pointers that are not directly exposed to JS.
    314 class TypedSlotSet {
    315  public:
    316   enum IterationMode { PREFREE_EMPTY_CHUNKS, KEEP_EMPTY_CHUNKS };
    317 
    318   typedef std::pair<SlotType, uint32_t> TypeAndOffset;
    319 
    320   struct TypedSlot {
    321     TypedSlot() {
    322       type_and_offset_.SetValue(0);
    323       host_offset_.SetValue(0);
    324     }
    325 
    326     TypedSlot(SlotType type, uint32_t host_offset, uint32_t offset) {
    327       type_and_offset_.SetValue(TypeField::encode(type) |
    328                                 OffsetField::encode(offset));
    329       host_offset_.SetValue(host_offset);
    330     }
    331 
    332     bool operator==(const TypedSlot other) {
    333       return type_and_offset_.Value() == other.type_and_offset_.Value() &&
    334              host_offset_.Value() == other.host_offset_.Value();
    335     }
    336 
    337     bool operator!=(const TypedSlot other) { return !(*this == other); }
    338 
    339     SlotType type() { return TypeField::decode(type_and_offset_.Value()); }
    340 
    341     uint32_t offset() { return OffsetField::decode(type_and_offset_.Value()); }
    342 
    343     TypeAndOffset GetTypeAndOffset() {
    344       uint32_t type_and_offset = type_and_offset_.Value();
    345       return std::make_pair(TypeField::decode(type_and_offset),
    346                             OffsetField::decode(type_and_offset));
    347     }
    348 
    349     uint32_t host_offset() { return host_offset_.Value(); }
    350 
    351     void Set(TypedSlot slot) {
    352       type_and_offset_.SetValue(slot.type_and_offset_.Value());
    353       host_offset_.SetValue(slot.host_offset_.Value());
    354     }
    355 
    356     void Clear() {
    357       type_and_offset_.SetValue(TypeField::encode(CLEARED_SLOT) |
    358                                 OffsetField::encode(0));
    359       host_offset_.SetValue(0);
    360     }
    361 
    362     base::AtomicValue<uint32_t> type_and_offset_;
    363     base::AtomicValue<uint32_t> host_offset_;
    364   };
    365   static const int kMaxOffset = 1 << 29;
    366 
    367   explicit TypedSlotSet(Address page_start) : page_start_(page_start) {
    368     chunk_.SetValue(new Chunk(nullptr, kInitialBufferSize));
    369   }
    370 
    371   ~TypedSlotSet() {
    372     Chunk* chunk = chunk_.Value();
    373     while (chunk != nullptr) {
    374       Chunk* next = chunk->next.Value();
    375       delete chunk;
    376       chunk = next;
    377     }
    378     FreeToBeFreedChunks();
    379   }
    380 
    381   // The slot offset specifies a slot at address page_start_ + offset.
    382   // This method can only be called on the main thread.
    383   void Insert(SlotType type, uint32_t host_offset, uint32_t offset) {
    384     TypedSlot slot(type, host_offset, offset);
    385     Chunk* top_chunk = chunk_.Value();
    386     if (!top_chunk) {
    387       top_chunk = new Chunk(nullptr, kInitialBufferSize);
    388       chunk_.SetValue(top_chunk);
    389     }
    390     if (!top_chunk->AddSlot(slot)) {
    391       Chunk* new_top_chunk =
    392           new Chunk(top_chunk, NextCapacity(top_chunk->capacity.Value()));
    393       bool added = new_top_chunk->AddSlot(slot);
    394       chunk_.SetValue(new_top_chunk);
    395       DCHECK(added);
    396       USE(added);
    397     }
    398   }
    399 
    400   // Iterate over all slots in the set and for each slot invoke the callback.
    401   // If the callback returns REMOVE_SLOT then the slot is removed from the set.
    402   // Returns the new number of slots.
    403   //
    404   // Sample usage:
    405   // Iterate([](SlotType slot_type, Address slot_address) {
    406   //    if (good(slot_type, slot_address)) return KEEP_SLOT;
    407   //    else return REMOVE_SLOT;
    408   // });
    409   template <typename Callback>
    410   int Iterate(Callback callback, IterationMode mode) {
    411     STATIC_ASSERT(CLEARED_SLOT < 8);
    412     Chunk* chunk = chunk_.Value();
    413     Chunk* previous = nullptr;
    414     int new_count = 0;
    415     while (chunk != nullptr) {
    416       TypedSlot* buffer = chunk->buffer.Value();
    417       int count = chunk->count.Value();
    418       bool empty = true;
    419       for (int i = 0; i < count; i++) {
    420         // Order is important here. We have to read out the slot type last to
    421         // observe the concurrent removal case consistently.
    422         Address host_addr = page_start_ + buffer[i].host_offset();
    423         TypeAndOffset type_and_offset = buffer[i].GetTypeAndOffset();
    424         SlotType type = type_and_offset.first;
    425         if (type != CLEARED_SLOT) {
    426           Address addr = page_start_ + type_and_offset.second;
    427           if (callback(type, host_addr, addr) == KEEP_SLOT) {
    428             new_count++;
    429             empty = false;
    430           } else {
    431             buffer[i].Clear();
    432           }
    433         }
    434       }
    435 
    436       Chunk* next = chunk->next.Value();
    437       if (mode == PREFREE_EMPTY_CHUNKS && empty) {
    438         // We remove the chunk from the list but let it still point its next
    439         // chunk to allow concurrent iteration.
    440         if (previous) {
    441           previous->next.SetValue(next);
    442         } else {
    443           chunk_.SetValue(next);
    444         }
    445         base::LockGuard<base::Mutex> guard(&to_be_freed_chunks_mutex_);
    446         to_be_freed_chunks_.push(chunk);
    447       } else {
    448         previous = chunk;
    449       }
    450       chunk = next;
    451     }
    452     return new_count;
    453   }
    454 
    455   void FreeToBeFreedChunks() {
    456     base::LockGuard<base::Mutex> guard(&to_be_freed_chunks_mutex_);
    457     while (!to_be_freed_chunks_.empty()) {
    458       Chunk* top = to_be_freed_chunks_.top();
    459       to_be_freed_chunks_.pop();
    460       delete top;
    461     }
    462   }
    463 
    464   void RemoveInvaldSlots(std::map<uint32_t, uint32_t>& invalid_ranges) {
    465     Chunk* chunk = chunk_.Value();
    466     while (chunk != nullptr) {
    467       TypedSlot* buffer = chunk->buffer.Value();
    468       int count = chunk->count.Value();
    469       for (int i = 0; i < count; i++) {
    470         uint32_t host_offset = buffer[i].host_offset();
    471         std::map<uint32_t, uint32_t>::iterator upper_bound =
    472             invalid_ranges.upper_bound(host_offset);
    473         if (upper_bound == invalid_ranges.begin()) continue;
    474         // upper_bounds points to the invalid range after the given slot. Hence,
    475         // we have to go to the previous element.
    476         upper_bound--;
    477         DCHECK_LE(upper_bound->first, host_offset);
    478         if (upper_bound->second > host_offset) {
    479           buffer[i].Clear();
    480         }
    481       }
    482       chunk = chunk->next.Value();
    483     }
    484   }
    485 
    486  private:
    487   static const int kInitialBufferSize = 100;
    488   static const int kMaxBufferSize = 16 * KB;
    489 
    490   static int NextCapacity(int capacity) {
    491     return Min(kMaxBufferSize, capacity * 2);
    492   }
    493 
    494   class OffsetField : public BitField<int, 0, 29> {};
    495   class TypeField : public BitField<SlotType, 29, 3> {};
    496 
    497   struct Chunk : Malloced {
    498     explicit Chunk(Chunk* next_chunk, int chunk_capacity) {
    499       count.SetValue(0);
    500       capacity.SetValue(chunk_capacity);
    501       buffer.SetValue(NewArray<TypedSlot>(chunk_capacity));
    502       next.SetValue(next_chunk);
    503     }
    504     bool AddSlot(TypedSlot slot) {
    505       int current_count = count.Value();
    506       if (current_count == capacity.Value()) return false;
    507       TypedSlot* current_buffer = buffer.Value();
    508       // Order is important here. We have to write the slot first before
    509       // increasing the counter to guarantee that a consistent state is
    510       // observed by concurrent threads.
    511       current_buffer[current_count].Set(slot);
    512       count.SetValue(current_count + 1);
    513       return true;
    514     }
    515     ~Chunk() { DeleteArray(buffer.Value()); }
    516     base::AtomicValue<Chunk*> next;
    517     base::AtomicValue<int> count;
    518     base::AtomicValue<int> capacity;
    519     base::AtomicValue<TypedSlot*> buffer;
    520   };
    521 
    522   Address page_start_;
    523   base::AtomicValue<Chunk*> chunk_;
    524   base::Mutex to_be_freed_chunks_mutex_;
    525   std::stack<Chunk*> to_be_freed_chunks_;
    526 };
    527 
    528 }  // namespace internal
    529 }  // namespace v8
    530 
    531 #endif  // V8_SLOT_SET_H
    532