14#include "llvm/Support/RWMutex.h"
25class ParametricStorageUniquer {
27 using BaseStorage = StorageUniquer::BaseStorage;
28 using StorageAllocator = StorageUniquer::StorageAllocator;
42 struct HashedStorage {
43 HashedStorage(
unsigned hashValue = 0, BaseStorage *storage =
nullptr)
44 : hashValue(hashValue), storage(storage) {}
50 struct StorageKeyInfo {
51 static inline HashedStorage getEmptyKey() {
52 return HashedStorage(0, DenseMapInfo<BaseStorage *>::getEmptyKey());
55 static inline unsigned getHashValue(
const HashedStorage &key) {
58 static inline unsigned getHashValue(
const LookupKey &key) {
62 static inline bool isEqual(
const HashedStorage &
lhs,
63 const HashedStorage &
rhs) {
64 return lhs.storage ==
rhs.storage;
66 static inline bool isEqual(
const LookupKey &
lhs,
const HashedStorage &
rhs) {
67 if (isEqual(
rhs, getEmptyKey()))
70 return lhs.isEqual(
rhs.storage);
73 using StorageTypeSet = DenseSet<HashedStorage, StorageKeyInfo>;
80 StorageTypeSet instances;
82#if LLVM_ENABLE_THREADS != 0
84 llvm::sys::SmartRWMutex<true> mutex;
90 BaseStorage *getOrCreateUnsafe(Shard &shard, LookupKey &key,
92 auto existing = shard.instances.insert_as({key.hashValue}, key);
93 BaseStorage *&storage = existing.first->storage;
100 void destroyShardInstances(Shard &shard) {
103 for (HashedStorage &instance : shard.instances)
104 destructorFn(instance.storage);
108#if LLVM_ENABLE_THREADS != 0
112 ParametricStorageUniquer(
function_ref<
void(BaseStorage *)> destructorFn,
113 size_t numShards = 8)
114 : shards(new std::atomic<Shard *>[numShards]), numShards(numShards),
115 destructorFn(destructorFn) {
116 assert(llvm::isPowerOf2_64(numShards) &&
117 "the number of shards is required to be a power of 2");
118 for (
size_t i = 0; i < numShards; i++)
119 shards[i].store(
nullptr, std::memory_order_relaxed);
121 ~ParametricStorageUniquer() {
123 for (
size_t i = 0; i != numShards; ++i) {
124 if (Shard *shard = shards[i].
load()) {
125 destroyShardInstances(*shard);
131 BaseStorage *getOrCreate(
bool threadingIsEnabled,
unsigned hashValue,
134 Shard &shard = getShard(hashValue);
135 ParametricStorageUniquer::LookupKey lookupKey{hashValue, isEqual};
136 if (!threadingIsEnabled)
137 return getOrCreateUnsafe(shard, lookupKey, ctorFn);
140 auto localIt = localCache->insert_as({hashValue}, lookupKey);
141 BaseStorage *&localInst = localIt.first->storage;
147 llvm::sys::SmartScopedReader<true> typeLock(shard.mutex);
148 auto it = shard.instances.find_as(lookupKey);
149 if (it != shard.instances.end())
150 return localInst = it->storage;
155 llvm::sys::SmartScopedWriter<true> typeLock(shard.mutex);
156 return localInst = getOrCreateUnsafe(shard, lookupKey, ctorFn);
161 LogicalResult mutate(
bool threadingIsEnabled, BaseStorage *storage,
163 if (!threadingIsEnabled)
169 Shard &shard = getShard(llvm::hash_value(storage));
170 llvm::sys::SmartScopedWriter<true> lock(shard.mutex);
176 Shard &getShard(
unsigned hashValue) {
178 unsigned shardNum = hashValue & (numShards - 1);
181 Shard *shard = shards[shardNum].load(std::memory_order_acquire);
186 Shard *newShard =
new Shard();
187 if (shards[shardNum].compare_exchange_strong(shard, newShard))
197 ThreadLocalCache<StorageTypeSet> localCache;
202 std::unique_ptr<std::atomic<Shard *>[]> shards;
214 ParametricStorageUniquer(
function_ref<
void(BaseStorage *)> destructorFn,
215 size_t numShards = 0)
216 : destructorFn(destructorFn) {}
217 ~ParametricStorageUniquer() { destroyShardInstances(shard); }
221 getOrCreate(
bool threadingIsEnabled,
unsigned hashValue,
224 ParametricStorageUniquer::LookupKey lookupKey{hashValue, isEqual};
225 return getOrCreateUnsafe(shard, lookupKey, ctorFn);
230 mutate(
bool threadingIsEnabled, BaseStorage *storage,
265 "creating unregistered storage instance");
267 return storageUniquer.getOrCreate(
278 "mutating unregistered storage instance");
288#if LLVM_ENABLE_THREADS != 0
294 if (!threadAllocator) {
299 llvm::sys::SmartScopedLock<true> lock(threadAllocatorMutex);
300 threadAllocators.push_back(
301 std::unique_ptr<StorageAllocator>(threadAllocator));
304 return *threadAllocator;
317 assert(singletonInstance &&
"expected singleton instance to exist");
318 return singletonInstance;
328#if LLVM_ENABLE_THREADS != 0
334 std::vector<std::unique_ptr<StorageAllocator>> threadAllocators;
363 impl->threadingIsEnabled = !disable;
368auto StorageUniquer::getParametricStorageTypeImpl(
369 TypeID id,
unsigned hashValue,
371 function_ref<BaseStorage *(StorageAllocator &)> ctorFn) -> BaseStorage * {
372 return impl->getOrCreate(
id, hashValue, isEqual, ctorFn);
377void StorageUniquer::registerParametricStorageTypeImpl(
379 impl->parametricUniquers.try_emplace(
380 id, std::make_unique<ParametricStorageUniquer>(destructorFn));
385auto StorageUniquer::getSingletonImpl(
TypeID id) -> BaseStorage * {
386 return impl->getSingleton(
id);
391 return impl->hasSingleton(
id);
396 return impl->hasParametricStorage(
id);
401void StorageUniquer::registerSingletonImpl(
403 assert(!
impl->singletonInstances.count(
id) &&
404 "storage class already registered");
405 impl->singletonInstances.try_emplace(
id, ctorFn(
impl->allocator));
409LogicalResult StorageUniquer::mutateImpl(
410 TypeID id, BaseStorage *storage,
411 function_ref<LogicalResult(StorageAllocator &)> mutationFn) {
412 return impl->mutate(
id, storage, mutationFn);
This class acts as the base storage that all storage classes must derived from.
This is a utility allocator used to allocate memory for instances of derived types.
void disableMultithreading(bool disable=true)
Set the flag specifying if multi-threading is disabled within the uniquer.
bool isSingletonStorageInitialized(TypeID id)
Test if there is a singleton storage uniquer initialized for the provided TypeID.
bool isParametricStorageInitialized(TypeID id)
Test if there is a parametric storage uniquer initialized for the provided TypeID.
This class provides support for defining a thread local object with non static storage duration.
This class provides an efficient unique identifier for a specific C++ type.
Attribute collections provide a dictionary-like interface.
Include the generated interface declarations.
llvm::DenseMap< KeyT, ValueT, KeyInfoT, BucketT > DenseMap
llvm::function_ref< Fn > function_ref
This is the implementation of the StorageUniquer class.
BaseStorage * getOrCreate(TypeID id, unsigned hashValue, function_ref< bool(const BaseStorage *)> isEqual, function_ref< BaseStorage *(StorageAllocator &)> ctorFn)
Get or create an instance of a parametric type.
bool hasSingleton(TypeID id) const
Check if an instance of a singleton storage class exists.
DenseMap< TypeID, std::unique_ptr< ParametricStorageUniquer > > parametricUniquers
Map of type ids to the storage uniquer to use for registered objects.
BaseStorage * getSingleton(TypeID id)
Get or create an instance of a singleton storage class.
StorageAllocator & getThreadSafeAllocator()
Return an allocator that can be used to safely allocate instances on the current thread.
StorageAllocator allocator
Main allocator used for uniquing singleton instances, and other state when thread safety is guarantee...
StorageUniquer::StorageAllocator StorageAllocator
bool threadingIsEnabled
Flag specifying if multi-threading is enabled within the uniquer.
LogicalResult mutate(TypeID id, BaseStorage *storage, function_ref< LogicalResult(StorageAllocator &)> mutationFn)
Run a mutation function on the provided storage object in a thread-safe way.
StorageUniquer::BaseStorage BaseStorage
DenseMap< TypeID, BaseStorage * > singletonInstances
Map of type ids to a singleton instance when the storage class is a singleton.
bool hasParametricStorage(TypeID id)
Check if an instance of a parametric storage class exists.