/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * vim: set ts=8 sts=4 et sw=4 tw=99: * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef gc_NurseryAwareHashMap_h #define gc_NurseryAwareHashMap_h namespace js { namespace detail { // This class only handles the incremental case and does not deal with nursery // pointers. The only users should be for NurseryAwareHashMap; it is defined // externally because we need a GCPolicy for its use in the contained map. template <typename T> class UnsafeBareReadBarriered : public ReadBarrieredBase<T> { public: UnsafeBareReadBarriered() : ReadBarrieredBase<T>(JS::GCPolicy<T>::initial()) {} MOZ_IMPLICIT UnsafeBareReadBarriered(const T& v) : ReadBarrieredBase<T>(v) {} explicit UnsafeBareReadBarriered(const UnsafeBareReadBarriered& v) : ReadBarrieredBase<T>(v) {} UnsafeBareReadBarriered(UnsafeBareReadBarriered&& v) : ReadBarrieredBase<T>(mozilla::Move(v)) {} UnsafeBareReadBarriered& operator=(const UnsafeBareReadBarriered& v) { this->value = v.value; return *this; } UnsafeBareReadBarriered& operator=(const T& v) { this->value = v; return *this; } const T get() const { if (!InternalBarrierMethods<T>::isMarkable(this->value)) return JS::GCPolicy<T>::initial(); this->read(); return this->value; } explicit operator bool() const { return bool(this->value); } const T unbarrieredGet() const { return this->value; } T* unsafeGet() { return &this->value; } T const* unsafeGet() const { return &this->value; } }; } // namespace detail // The "nursery aware" hash map is a special case of GCHashMap that is able to // treat nursery allocated members weakly during a minor GC: e.g. it allows for // nursery allocated objects to be collected during nursery GC where a normal // hash table treats such edges strongly. // // Doing this requires some strong constraints on what can be stored in this // table and how it can be accessed. At the moment, this table assumes that // all values contain a strong reference to the key. It also requires the // policy to contain an |isTenured| and |needsSweep| members, which is fairly // non-standard. This limits its usefulness to the CrossCompartmentMap at the // moment, but might serve as a useful base for other tables in future. template <typename Key, typename Value, typename HashPolicy = DefaultHasher<Key>, typename AllocPolicy = TempAllocPolicy> class NurseryAwareHashMap { using BarrieredValue = detail::UnsafeBareReadBarriered<Value>; using MapType = GCRekeyableHashMap<Key, BarrieredValue, HashPolicy, AllocPolicy>; MapType map; // Keep a list of all keys for which JS::GCPolicy<Key>::isTenured is false. // This lets us avoid a full traveral of the map on each minor GC, keeping // the minor GC times proportional to the nursery heap size. Vector<Key, 0, AllocPolicy> nurseryEntries; public: using Lookup = typename MapType::Lookup; using Ptr = typename MapType::Ptr; using Range = typename MapType::Range; explicit NurseryAwareHashMap(AllocPolicy a = AllocPolicy()) : map(a) {} MOZ_MUST_USE bool init(uint32_t len = 16) { return map.init(len); } bool empty() const { return map.empty(); } Ptr lookup(const Lookup& l) const { return map.lookup(l); } void remove(Ptr p) { map.remove(p); } Range all() const { return map.all(); } struct Enum : public MapType::Enum { explicit Enum(NurseryAwareHashMap& namap) : MapType::Enum(namap.map) {} }; size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const { return map.sizeOfExcludingThis(mallocSizeOf); } size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const { return map.sizeOfIncludingThis(mallocSizeOf); } MOZ_MUST_USE bool put(const Key& k, const Value& v) { auto p = map.lookupForAdd(k); if (p) { if (!JS::GCPolicy<Key>::isTenured(k) || !JS::GCPolicy<Value>::isTenured(v)) { if (!nurseryEntries.append(k)) return false; } p->value() = v; return true; } bool ok = map.add(p, k, v); if (!ok) return false; if (!JS::GCPolicy<Key>::isTenured(k) || !JS::GCPolicy<Value>::isTenured(v)) { if (!nurseryEntries.append(k)) { map.remove(k); return false; } } return true; } void sweepAfterMinorGC(JSTracer* trc) { for (auto& key : nurseryEntries) { auto p = map.lookup(key); if (!p) continue; // Drop the entry if the value is not marked. if (JS::GCPolicy<BarrieredValue>::needsSweep(&p->value())) { map.remove(key); continue; } // Update and relocate the key, if the value is still needed. // // Note that this currently assumes that all Value will contain a // strong reference to Key, as per its use as the // CrossCompartmentWrapperMap. We may need to make the following // behavior more dynamic if we use this map in other nursery-aware // contexts. Key copy(key); mozilla::DebugOnly<bool> sweepKey = JS::GCPolicy<Key>::needsSweep(©); MOZ_ASSERT(!sweepKey); map.rekeyIfMoved(key, copy); } nurseryEntries.clear(); } void sweep() { MOZ_ASSERT(nurseryEntries.empty()); map.sweep(); } }; } // namespace js namespace JS { template <typename T> struct GCPolicy<js::detail::UnsafeBareReadBarriered<T>> { static void trace(JSTracer* trc, js::detail::UnsafeBareReadBarriered<T>* thingp, const char* name) { js::TraceEdge(trc, thingp, name); } static bool needsSweep(js::detail::UnsafeBareReadBarriered<T>* thingp) { return js::gc::IsAboutToBeFinalized(thingp); } }; } // namespace JS #endif // gc_NurseryAwareHashMap_h