/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- * vim: set ts=8 sts=2 et sw=2 tw=80: * 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 frontend_NameCollections_h #define frontend_NameCollections_h #include "mozilla/Assertions.h" // MOZ_ASSERT #include "mozilla/Attributes.h" // MOZ_IMPLICIT #include // size_t #include // uint32_t, uint64_t #include // std::{true_type, false_type, is_trivial_v, is_trivially_copyable_v, is_trivially_destructible_v} #include // std::forward #include "ds/InlineTable.h" // InlineMap, DefaultKeyPolicy #include "frontend/NameAnalysisTypes.h" // AtomVector, FunctionBoxVector #include "frontend/ParserAtom.h" // TaggedParserAtomIndex, TrivialTaggedParserAtomIndex #include "frontend/TaggedParserAtomIndexHasher.h" // TrivialTaggedParserAtomIndexHasher #include "js/AllocPolicy.h" // SystemAllocPolicy, ReportOutOfMemory #include "js/Utility.h" // js_new, js_delete #include "js/Vector.h" // Vector namespace js { namespace detail { // For InlineMap. // See DefaultKeyPolicy definition in InlineTable.h for more details. template <> class DefaultKeyPolicy { public: DefaultKeyPolicy() = delete; DefaultKeyPolicy(const frontend::TrivialTaggedParserAtomIndex&) = delete; static bool isTombstone(const frontend::TrivialTaggedParserAtomIndex& atom) { return atom.isNull(); } static void setToTombstone(frontend::TrivialTaggedParserAtomIndex& atom) { atom = frontend::TrivialTaggedParserAtomIndex::null(); } }; } // namespace detail namespace frontend { class FunctionBox; // A pool of recyclable containers for use in the frontend. The Parser and // BytecodeEmitter create many maps for name analysis that are short-lived // (i.e., for the duration of parsing or emitting a lexical scope). Making // them recyclable cuts down significantly on allocator churn. template class CollectionPool { using RecyclableCollections = Vector; RecyclableCollections all_; RecyclableCollections recyclable_; static RepresentativeCollection* asRepresentative(void* p) { return reinterpret_cast(p); } RepresentativeCollection* allocate() { size_t newAllLength = all_.length() + 1; if (!all_.reserve(newAllLength) || !recyclable_.reserve(newAllLength)) { return nullptr; } RepresentativeCollection* collection = js_new(); if (collection) { all_.infallibleAppend(collection); } return collection; } public: ~CollectionPool() { purgeAll(); } void purgeAll() { void** end = all_.end(); for (void** it = all_.begin(); it != end; ++it) { js_delete(asRepresentative(*it)); } all_.clearAndFree(); recyclable_.clearAndFree(); } // Fallibly aquire one of the supported collection types from the pool. template Collection* acquire(FrontendContext* fc) { ConcreteCollectionPool::template assertInvariants(); RepresentativeCollection* collection; if (recyclable_.empty()) { collection = allocate(); if (!collection) { ReportOutOfMemory(fc); } } else { collection = asRepresentative(recyclable_.popCopy()); collection->clear(); } return reinterpret_cast(collection); } // Release a collection back to the pool. template void release(Collection** collection) { ConcreteCollectionPool::template assertInvariants(); MOZ_ASSERT(*collection); #ifdef DEBUG bool ok = false; // Make sure the collection is in |all_| but not already in |recyclable_|. for (void** it = all_.begin(); it != all_.end(); ++it) { if (*it == *collection) { ok = true; break; } } MOZ_ASSERT(ok); for (void** it = recyclable_.begin(); it != recyclable_.end(); ++it) { MOZ_ASSERT(*it != *collection); } #endif MOZ_ASSERT(recyclable_.length() < all_.length()); // Reserved in allocateFresh. recyclable_.infallibleAppend(*collection); *collection = nullptr; } }; template struct RecyclableAtomMapValueWrapper { using WrappedType = Wrapped; union { Wrapped wrapped; uint64_t dummy; }; static void assertInvariant() { static_assert(sizeof(Wrapped) <= sizeof(uint64_t), "Can only recycle atom maps with values smaller than uint64"); } RecyclableAtomMapValueWrapper() : dummy(0) { assertInvariant(); } MOZ_IMPLICIT RecyclableAtomMapValueWrapper(Wrapped w) : wrapped(w) { assertInvariant(); } MOZ_IMPLICIT operator Wrapped&() { return wrapped; } MOZ_IMPLICIT operator Wrapped&() const { return wrapped; } Wrapped* operator->() { return &wrapped; } const Wrapped* operator->() const { return &wrapped; } }; template using RecyclableNameMapBase = InlineMap, 24, TrivialTaggedParserAtomIndexHasher, SystemAllocPolicy>; // Define wrapper methods to accept TaggedParserAtomIndex. template class RecyclableNameMap : public RecyclableNameMapBase { using Base = RecyclableNameMapBase; public: template [[nodiscard]] MOZ_ALWAYS_INLINE bool add(typename Base::AddPtr& p, const TaggedParserAtomIndex& key, Args&&... args) { return Base::add(p, TrivialTaggedParserAtomIndex::from(key), std::forward(args)...); } MOZ_ALWAYS_INLINE typename Base::Ptr lookup(const TaggedParserAtomIndex& l) { return Base::lookup(TrivialTaggedParserAtomIndex::from(l)); } MOZ_ALWAYS_INLINE typename Base::AddPtr lookupForAdd(const TaggedParserAtomIndex& l) { return Base::lookupForAdd(TrivialTaggedParserAtomIndex::from(l)); } }; using DeclaredNameMap = RecyclableNameMap; using NameLocationMap = RecyclableNameMap; // Cannot use GCThingIndex here because it's not trivial type. using AtomIndexMap = RecyclableNameMap; template class InlineTablePool : public CollectionPool> { template struct IsRecyclableAtomMapValueWrapper : std::false_type {}; template struct IsRecyclableAtomMapValueWrapper> : std::true_type {}; public: template static void assertInvariants() { static_assert( Table::SizeOfInlineEntries == RepresentativeTable::SizeOfInlineEntries, "Only tables with the same size for inline entries are usable in the " "pool."); using EntryType = typename Table::Table::Entry; using KeyType = typename EntryType::KeyType; using ValueType = typename EntryType::ValueType; static_assert(IsRecyclableAtomMapValueWrapper::value, "Please adjust the static assertions below if you need to " "support other types than RecyclableAtomMapValueWrapper"); using WrappedType = typename ValueType::WrappedType; // We can't directly check |std::is_trivial|, because neither // mozilla::HashMapEntry nor IsRecyclableAtomMapValueWrapper are trivially // default constructible. Instead we check that the key and the unwrapped // value are trivial and additionally ensure that the entry itself is // trivially copyable and destructible. static_assert(std::is_trivial_v, "Only tables with trivial keys are usable in the pool."); static_assert(std::is_trivial_v, "Only tables with trivial values are usable in the pool."); static_assert( std::is_trivially_copyable_v, "Only tables with trivially copyable entries are usable in the pool."); static_assert(std::is_trivially_destructible_v, "Only tables with trivially destructible entries are usable " "in the pool."); } }; template class VectorPool : public CollectionPool> { public: template static void assertInvariants() { static_assert( Vector::sMaxInlineStorage == RepresentativeVector::sMaxInlineStorage, "Only vectors with the same size for inline entries are usable in the " "pool."); using ElementType = typename Vector::ElementType; static_assert(std::is_trivial_v, "Only vectors of trivial values are usable in the pool."); static_assert(std::is_trivially_destructible_v, "Only vectors of trivially destructible values are usable in " "the pool."); static_assert( sizeof(ElementType) == sizeof(typename RepresentativeVector::ElementType), "Only vectors with same-sized elements are usable in the pool."); } }; using AtomVector = Vector; using FunctionBoxVector = Vector; class NameCollectionPool { InlineTablePool mapPool_; VectorPool atomVectorPool_; VectorPool functionBoxVectorPool_; uint32_t activeCompilations_; public: NameCollectionPool() : activeCompilations_(0) {} bool hasActiveCompilation() const { return activeCompilations_ != 0; } void addActiveCompilation() { activeCompilations_++; } void removeActiveCompilation() { MOZ_ASSERT(hasActiveCompilation()); activeCompilations_--; } template Map* acquireMap(FrontendContext* fc) { MOZ_ASSERT(hasActiveCompilation()); return mapPool_.acquire(fc); } template void releaseMap(Map** map) { MOZ_ASSERT(hasActiveCompilation()); MOZ_ASSERT(map); if (*map) { mapPool_.release(map); } } template inline Vector* acquireVector(FrontendContext* fc); template inline void releaseVector(Vector** vec); void purge() { if (!hasActiveCompilation()) { mapPool_.purgeAll(); atomVectorPool_.purgeAll(); functionBoxVectorPool_.purgeAll(); } } }; template <> inline AtomVector* NameCollectionPool::acquireVector( FrontendContext* fc) { MOZ_ASSERT(hasActiveCompilation()); return atomVectorPool_.acquire(fc); } template <> inline void NameCollectionPool::releaseVector(AtomVector** vec) { MOZ_ASSERT(hasActiveCompilation()); MOZ_ASSERT(vec); if (*vec) { atomVectorPool_.release(vec); } } template <> inline FunctionBoxVector* NameCollectionPool::acquireVector( FrontendContext* fc) { MOZ_ASSERT(hasActiveCompilation()); return functionBoxVectorPool_.acquire(fc); } template <> inline void NameCollectionPool::releaseVector( FunctionBoxVector** vec) { MOZ_ASSERT(hasActiveCompilation()); MOZ_ASSERT(vec); if (*vec) { functionBoxVectorPool_.release(vec); } } template typename Impl> class PooledCollectionPtr { NameCollectionPool& pool_; T* collection_ = nullptr; protected: ~PooledCollectionPtr() { Impl::releaseCollection(pool_, &collection_); } T& collection() { MOZ_ASSERT(collection_); return *collection_; } const T& collection() const { MOZ_ASSERT(collection_); return *collection_; } public: explicit PooledCollectionPtr(NameCollectionPool& pool) : pool_(pool) {} bool acquire(FrontendContext* fc) { MOZ_ASSERT(!collection_); collection_ = Impl::acquireCollection(fc, pool_); return !!collection_; } explicit operator bool() const { return !!collection_; } T* operator->() { return &collection(); } const T* operator->() const { return &collection(); } T& operator*() { return collection(); } const T& operator*() const { return collection(); } }; template class PooledMapPtr : public PooledCollectionPtr { friend class PooledCollectionPtr; static Map* acquireCollection(FrontendContext* fc, NameCollectionPool& pool) { return pool.acquireMap(fc); } static void releaseCollection(NameCollectionPool& pool, Map** ptr) { pool.releaseMap(ptr); } using Base = PooledCollectionPtr; public: using Base::Base; ~PooledMapPtr() = default; }; template class PooledVectorPtr : public PooledCollectionPtr { friend class PooledCollectionPtr; static Vector* acquireCollection(FrontendContext* fc, NameCollectionPool& pool) { return pool.acquireVector(fc); } static void releaseCollection(NameCollectionPool& pool, Vector** ptr) { pool.releaseVector(ptr); } using Base = PooledCollectionPtr; using Base::collection; public: using Base::Base; ~PooledVectorPtr() = default; typename Vector::ElementType& operator[](size_t index) { return collection()[index]; } const typename Vector::ElementType& operator[](size_t index) const { return collection()[index]; } }; } // namespace frontend } // namespace js #endif // frontend_NameCollections_h