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());
54 static inline HashedStorage getTombstoneKey() {
55 return HashedStorage(0, DenseMapInfo<BaseStorage *>::getTombstoneKey());
58 static inline unsigned getHashValue(
const HashedStorage &key) {
61 static inline unsigned getHashValue(
const LookupKey &key) {
65 static inline bool isEqual(
const HashedStorage &
lhs,
66 const HashedStorage &
rhs) {
67 return lhs.storage ==
rhs.storage;
69 static inline bool isEqual(
const LookupKey &
lhs,
const HashedStorage &
rhs) {
70 if (isEqual(
rhs, getEmptyKey()) || isEqual(
rhs, getTombstoneKey()))
73 return lhs.isEqual(
rhs.storage);
76 using StorageTypeSet = DenseSet<HashedStorage, StorageKeyInfo>;
83 StorageTypeSet instances;
85#if LLVM_ENABLE_THREADS != 0
87 llvm::sys::SmartRWMutex<true> mutex;
93 BaseStorage *getOrCreateUnsafe(Shard &shard, LookupKey &key,
95 auto existing = shard.instances.insert_as({key.hashValue}, key);
96 BaseStorage *&storage = existing.first->storage;
103 void destroyShardInstances(Shard &shard) {
106 for (HashedStorage &instance : shard.instances)
107 destructorFn(instance.storage);
111#if LLVM_ENABLE_THREADS != 0
115 ParametricStorageUniquer(
function_ref<
void(BaseStorage *)> destructorFn,
116 size_t numShards = 8)
117 : shards(new std::atomic<Shard *>[numShards]), numShards(numShards),
118 destructorFn(destructorFn) {
119 assert(llvm::isPowerOf2_64(numShards) &&
120 "the number of shards is required to be a power of 2");
121 for (
size_t i = 0; i < numShards; i++)
122 shards[i].store(
nullptr, std::memory_order_relaxed);
124 ~ParametricStorageUniquer() {
126 for (
size_t i = 0; i != numShards; ++i) {
127 if (Shard *shard = shards[i].
load()) {
128 destroyShardInstances(*shard);
134 BaseStorage *getOrCreate(
bool threadingIsEnabled,
unsigned hashValue,
137 Shard &shard = getShard(hashValue);
138 ParametricStorageUniquer::LookupKey lookupKey{hashValue, isEqual};
139 if (!threadingIsEnabled)
140 return getOrCreateUnsafe(shard, lookupKey, ctorFn);
143 auto localIt = localCache->insert_as({hashValue}, lookupKey);
144 BaseStorage *&localInst = localIt.first->storage;
150 llvm::sys::SmartScopedReader<true> typeLock(shard.mutex);
151 auto it = shard.instances.find_as(lookupKey);
152 if (it != shard.instances.end())
153 return localInst = it->storage;
158 llvm::sys::SmartScopedWriter<true> typeLock(shard.mutex);
159 return localInst = getOrCreateUnsafe(shard, lookupKey, ctorFn);
164 LogicalResult mutate(
bool threadingIsEnabled, BaseStorage *storage,
166 if (!threadingIsEnabled)
172 Shard &shard = getShard(llvm::hash_value(storage));
173 llvm::sys::SmartScopedWriter<true> lock(shard.mutex);
179 Shard &getShard(
unsigned hashValue) {
181 unsigned shardNum = hashValue & (numShards - 1);
184 Shard *shard = shards[shardNum].load(std::memory_order_acquire);
189 Shard *newShard =
new Shard();
190 if (shards[shardNum].compare_exchange_strong(shard, newShard))
200 ThreadLocalCache<StorageTypeSet> localCache;
205 std::unique_ptr<std::atomic<Shard *>[]> shards;
217 ParametricStorageUniquer(
function_ref<
void(BaseStorage *)> destructorFn,
218 size_t numShards = 0)
219 : destructorFn(destructorFn) {}
220 ~ParametricStorageUniquer() { destroyShardInstances(shard); }
224 getOrCreate(
bool threadingIsEnabled,
unsigned hashValue,
227 ParametricStorageUniquer::LookupKey lookupKey{hashValue, isEqual};
228 return getOrCreateUnsafe(shard, lookupKey, ctorFn);
233 mutate(
bool threadingIsEnabled, BaseStorage *storage,
268 "creating unregistered storage instance");
270 return storageUniquer.getOrCreate(
281 "mutating unregistered storage instance");
291#if LLVM_ENABLE_THREADS != 0
297 if (!threadAllocator) {
302 llvm::sys::SmartScopedLock<true> lock(threadAllocatorMutex);
303 threadAllocators.push_back(
304 std::unique_ptr<StorageAllocator>(threadAllocator));
307 return *threadAllocator;
320 assert(singletonInstance &&
"expected singleton instance to exist");
321 return singletonInstance;
331#if LLVM_ENABLE_THREADS != 0
337 std::vector<std::unique_ptr<StorageAllocator>> threadAllocators;
366 impl->threadingIsEnabled = !disable;
371auto StorageUniquer::getParametricStorageTypeImpl(
372 TypeID id,
unsigned hashValue,
374 function_ref<BaseStorage *(StorageAllocator &)> ctorFn) -> BaseStorage * {
375 return impl->getOrCreate(
id, hashValue, isEqual, ctorFn);
380void StorageUniquer::registerParametricStorageTypeImpl(
382 impl->parametricUniquers.try_emplace(
383 id, std::make_unique<ParametricStorageUniquer>(destructorFn));
388auto StorageUniquer::getSingletonImpl(
TypeID id) -> BaseStorage * {
389 return impl->getSingleton(
id);
394 return impl->hasSingleton(
id);
399 return impl->hasParametricStorage(
id);
404void StorageUniquer::registerSingletonImpl(
406 assert(!
impl->singletonInstances.count(
id) &&
407 "storage class already registered");
408 impl->singletonInstances.try_emplace(
id, ctorFn(
impl->allocator));
412LogicalResult StorageUniquer::mutateImpl(
413 TypeID id, BaseStorage *storage,
414 function_ref<LogicalResult(StorageAllocator &)> mutationFn) {
415 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.