tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

NurseryAwareHashMap.h (8230B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
      2 * vim: set ts=8 sts=2 et sw=2 tw=80:
      3 * This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #ifndef gc_NurseryAwareHashMap_h
      8 #define gc_NurseryAwareHashMap_h
      9 
     10 #include "gc/Barrier.h"
     11 #include "gc/Tracer.h"
     12 #include "js/GCHashTable.h"
     13 #include "js/GCPolicyAPI.h"
     14 #include "js/GCVector.h"
     15 #include "js/Utility.h"
     16 
     17 namespace js {
     18 
     19 namespace detail {
     20 
     21 // This class only handles the incremental case and does not deal with nursery
     22 // pointers. The only users should be for NurseryAwareHashMap; it is defined
     23 // externally because we need a GCPolicy for its use in the contained map.
     24 template <typename T>
     25 class UnsafeBareWeakHeapPtr : public ReadBarriered<T> {
     26 public:
     27  UnsafeBareWeakHeapPtr()
     28      : ReadBarriered<T>(JS::SafelyInitialized<T>::create()) {}
     29  MOZ_IMPLICIT UnsafeBareWeakHeapPtr(const T& v) : ReadBarriered<T>(v) {}
     30  explicit UnsafeBareWeakHeapPtr(const UnsafeBareWeakHeapPtr& v)
     31      : ReadBarriered<T>(v) {}
     32  UnsafeBareWeakHeapPtr(UnsafeBareWeakHeapPtr&& v)
     33      : ReadBarriered<T>(std::move(v)) {}
     34 
     35  UnsafeBareWeakHeapPtr& operator=(const UnsafeBareWeakHeapPtr& v) {
     36    this->value = v.value;
     37    return *this;
     38  }
     39 
     40  UnsafeBareWeakHeapPtr& operator=(const T& v) {
     41    this->value = v;
     42    return *this;
     43  }
     44 
     45  const T get() const {
     46    if (!InternalBarrierMethods<T>::isMarkable(this->value)) {
     47      return JS::SafelyInitialized<T>::create();
     48    }
     49    this->read();
     50    return this->value;
     51  }
     52 
     53  explicit operator bool() const { return bool(this->value); }
     54 
     55  const T unbarrieredGet() const { return this->value; }
     56  T* unsafeGet() { return &this->value; }
     57  T const* unsafeGet() const { return &this->value; }
     58 };
     59 }  // namespace detail
     60 
     61 enum : bool { DuplicatesNotPossible, DuplicatesPossible };
     62 
     63 // The "nursery aware" hash map is a special case of GCHashMap that is able to
     64 // treat nursery allocated members weakly during a minor GC: e.g. it allows for
     65 // nursery allocated objects to be collected during nursery GC where a normal
     66 // hash table treats such edges strongly.
     67 //
     68 // Doing this requires some strong constraints on what can be stored in this
     69 // table and how it can be accessed. At the moment, this table assumes that all
     70 // values contain a strong reference to the key. This limits its usefulness to
     71 // the CrossCompartmentMap at the moment, but might serve as a useful base for
     72 // other tables in future.
     73 template <typename Key, typename Value, typename AllocPolicy = TempAllocPolicy,
     74          bool AllowDuplicates = DuplicatesNotPossible>
     75 class NurseryAwareHashMap {
     76  using MapKey = UnsafeBarePtr<Key>;
     77  using MapValue = detail::UnsafeBareWeakHeapPtr<Value>;
     78  using HashPolicy = DefaultHasher<MapKey>;
     79  using MapType = GCRekeyableHashMap<MapKey, MapValue, HashPolicy, AllocPolicy>;
     80  MapType map;
     81 
     82  // Keep a list of all keys for which key->isTenured() is false. This lets us
     83  // avoid a full traversal of the map on each minor GC, keeping the minor GC
     84  // times proportional to the nursery heap size.
     85  using KeyVector = GCVector<Key, 0, AllocPolicy>;
     86  KeyVector nurseryEntries;
     87 
     88 public:
     89  using Lookup = typename MapType::Lookup;
     90  using Ptr = typename MapType::Ptr;
     91  using Range = typename MapType::Range;
     92  using Entry = typename MapType::Entry;
     93 
     94  explicit NurseryAwareHashMap(AllocPolicy a = AllocPolicy())
     95      : map(a), nurseryEntries(std::move(a)) {}
     96  explicit NurseryAwareHashMap(size_t length) : map(length) {}
     97  NurseryAwareHashMap(AllocPolicy a, size_t length)
     98      : map(a, length), nurseryEntries(std::move(a)) {}
     99 
    100  bool empty() const { return map.empty(); }
    101  Ptr lookup(const Lookup& l) const { return map.lookup(l); }
    102  void remove(Ptr p) { map.remove(p); }
    103  Range all() const { return map.all(); }
    104  struct Enum : public MapType::Enum {
    105    explicit Enum(NurseryAwareHashMap& namap) : MapType::Enum(namap.map) {}
    106  };
    107  size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const {
    108    return map.shallowSizeOfExcludingThis(mallocSizeOf) +
    109           nurseryEntries.sizeOfExcludingThis(mallocSizeOf);
    110  }
    111  size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const {
    112    return mallocSizeOf(this) + sizeOfExcludingThis(mallocSizeOf);
    113  }
    114 
    115  [[nodiscard]] bool put(const Key& key, const Value& value) {
    116    if ((!key->isTenured() || !value->isTenured()) &&
    117        !nurseryEntries.append(key)) {
    118      return false;
    119    }
    120 
    121    auto p = map.lookupForAdd(key);
    122    if (p) {
    123      p->value() = value;
    124      return true;
    125    }
    126 
    127    return map.add(p, key, value);
    128  }
    129 
    130  void sweepAfterMinorGC(JSTracer* trc) {
    131    nurseryEntries.mutableEraseIf([this, trc](Key& key) {
    132      auto p = map.lookup(key);
    133      if (!p) {
    134        return true;
    135      }
    136 
    137      // Drop the entry if the value is not marked.
    138      if (!JS::GCPolicy<MapValue>::traceWeak(trc, &p->value())) {
    139        map.remove(p);
    140        return true;
    141      }
    142 
    143      // Update and relocate the key, if the value is still needed.
    144      //
    145      // Non-string Values will contain a strong reference to Key, as per its
    146      // use in the CrossCompartmentWrapperMap, so the key will never be dying
    147      // here. Strings do *not* have any sort of pointer from wrapper to
    148      // wrappee, as they are just copies. The wrapper map entry is merely used
    149      // as a cache to avoid re-copying the string, and currently that entire
    150      // cache is flushed on major GC.
    151      //
    152      // Since |key| is a reference, this updates the content of the
    153      // nurseryEntries vector.
    154      Key prior = key;
    155      if (!TraceManuallyBarrieredWeakEdge(trc, &key,
    156                                          "NurseryAwareHashMap key")) {
    157        map.remove(p);
    158        return true;
    159      }
    160 
    161      bool valueIsTenured = p->value().unbarrieredGet()->isTenured();
    162 
    163      if constexpr (AllowDuplicates) {
    164        // Drop duplicated keys.
    165        //
    166        // A key can be forwarded to another place. In this case, rekey the
    167        // item. If two or more different keys are forwarded to the same new
    168        // key, simply drop the later ones.
    169        if (key == prior) {
    170          // No rekey needed.
    171        } else if (map.has(key)) {
    172          // Key was forwarded to the same place that another key was already
    173          // forwarded to.
    174          map.remove(p);
    175          return true;
    176        } else {
    177          map.rekeyAs(prior, key, key);
    178        }
    179      } else {
    180        MOZ_ASSERT(key == prior || !map.has(key));
    181        map.rekeyIfMoved(prior, key);
    182      }
    183 
    184      return key->isTenured() && valueIsTenured;
    185    });
    186 
    187    checkNurseryEntries();
    188  }
    189 
    190  void checkNurseryEntries() const {
    191 #ifdef DEBUG
    192    AutoEnterOOMUnsafeRegion oomUnsafe;
    193    HashSet<Key, DefaultHasher<Key>, SystemAllocPolicy> set;
    194    for (const auto& key : nurseryEntries) {
    195      if (!set.put(key)) {
    196        oomUnsafe.crash("NurseryAwareHashMap::checkNurseryEntries");
    197      }
    198    }
    199 
    200    for (auto i = map.iter(); !i.done(); i.next()) {
    201      Key key = i.get().key().get();
    202      MOZ_ASSERT(gc::IsCellPointerValid(key));
    203      MOZ_ASSERT_IF(IsInsideNursery(key), set.has(key));
    204 
    205      Value value = i.get().value().unbarrieredGet();
    206      MOZ_ASSERT(gc::IsCellPointerValid(value));
    207      MOZ_ASSERT_IF(IsInsideNursery(value), set.has(key));
    208    }
    209 #endif
    210  }
    211 
    212  void traceWeak(JSTracer* trc) { map.traceWeak(trc); }
    213 
    214  void clear() {
    215    map.clear();
    216    nurseryEntries.clear();
    217  }
    218 
    219  bool hasNurseryEntries() const { return !nurseryEntries.empty(); }
    220 };
    221 
    222 }  // namespace js
    223 
    224 namespace JS {
    225 
    226 template <typename T>
    227 struct GCPolicy<js::detail::UnsafeBareWeakHeapPtr<T>>
    228    : public GCPolicyBase<js::detail::UnsafeBareWeakHeapPtr<T>> {
    229  static void trace(JSTracer* trc, js::detail::UnsafeBareWeakHeapPtr<T>* thingp,
    230                    const char* name) {
    231    js::TraceEdge(trc, thingp, name);
    232  }
    233  static bool traceWeak(JSTracer* trc,
    234                        js::detail::UnsafeBareWeakHeapPtr<T>* thingp) {
    235    return js::TraceWeakEdge(trc, thingp, "UnsafeBareWeakHeapPtr");
    236  }
    237 };
    238 
    239 }  // namespace JS
    240 
    241 namespace mozilla {}  // namespace mozilla
    242 
    243 #endif  // gc_NurseryAwareHashMap_h