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 unsigned getHashValue(
const HashedStorage &key) {
54 static inline unsigned getHashValue(
const LookupKey &key) {
58 static inline bool isEqual(
const HashedStorage &
lhs,
59 const HashedStorage &
rhs) {
60 return lhs.storage ==
rhs.storage;
62 static inline bool isEqual(
const LookupKey &
lhs,
const HashedStorage &
rhs) {
64 return lhs.isEqual(
rhs.storage);
67 using StorageTypeSet = DenseSet<HashedStorage, StorageKeyInfo>;
74 StorageTypeSet instances;
76#if LLVM_ENABLE_THREADS != 0
78 llvm::sys::SmartRWMutex<true> mutex;
84 BaseStorage *getOrCreateUnsafe(Shard &shard, LookupKey &key,
86 auto existing = shard.instances.insert_as({key.hashValue}, key);
87 BaseStorage *&storage = existing.first->storage;
94 void destroyShardInstances(Shard &shard) {
97 for (HashedStorage &instance : shard.instances)
98 destructorFn(instance.storage);
102#if LLVM_ENABLE_THREADS != 0
106 ParametricStorageUniquer(
function_ref<
void(BaseStorage *)> destructorFn,
107 size_t numShards = 8)
108 : shards(new std::atomic<Shard *>[numShards]), numShards(numShards),
109 destructorFn(destructorFn) {
110 assert(llvm::isPowerOf2_64(numShards) &&
111 "the number of shards is required to be a power of 2");
112 for (
size_t i = 0; i < numShards; i++)
113 shards[i].store(
nullptr, std::memory_order_relaxed);
115 ~ParametricStorageUniquer() {
117 for (
size_t i = 0; i != numShards; ++i) {
118 if (Shard *shard = shards[i].
load()) {
119 destroyShardInstances(*shard);
125 BaseStorage *getOrCreate(
bool threadingIsEnabled,
unsigned hashValue,
128 Shard &shard = getShard(hashValue);
129 ParametricStorageUniquer::LookupKey lookupKey{hashValue, isEqual};
130 if (!threadingIsEnabled)
131 return getOrCreateUnsafe(shard, lookupKey, ctorFn);
134 auto localIt = localCache->insert_as({hashValue}, lookupKey);
135 BaseStorage *&localInst = localIt.first->storage;
141 llvm::sys::SmartScopedReader<true> typeLock(shard.mutex);
142 auto it = shard.instances.find_as(lookupKey);
143 if (it != shard.instances.end())
144 return localInst = it->storage;
149 llvm::sys::SmartScopedWriter<true> typeLock(shard.mutex);
150 return localInst = getOrCreateUnsafe(shard, lookupKey, ctorFn);
155 LogicalResult mutate(
bool threadingIsEnabled, BaseStorage *storage,
157 if (!threadingIsEnabled)
163 Shard &shard = getShard(llvm::hash_value(storage));
164 llvm::sys::SmartScopedWriter<true> lock(shard.mutex);
170 Shard &getShard(
unsigned hashValue) {
172 unsigned shardNum = hashValue & (numShards - 1);
175 Shard *shard = shards[shardNum].load(std::memory_order_acquire);
180 Shard *newShard =
new Shard();
181 if (shards[shardNum].compare_exchange_strong(shard, newShard))
191 ThreadLocalCache<StorageTypeSet> localCache;
196 std::unique_ptr<std::atomic<Shard *>[]> shards;
208 ParametricStorageUniquer(
function_ref<
void(BaseStorage *)> destructorFn,
209 size_t numShards = 0)
210 : destructorFn(destructorFn) {}
211 ~ParametricStorageUniquer() { destroyShardInstances(shard); }
215 getOrCreate(
bool threadingIsEnabled,
unsigned hashValue,
218 ParametricStorageUniquer::LookupKey lookupKey{hashValue, isEqual};
219 return getOrCreateUnsafe(shard, lookupKey, ctorFn);
224 mutate(
bool threadingIsEnabled, BaseStorage *storage,
259 "creating unregistered storage instance");
261 return storageUniquer.getOrCreate(
272 "mutating unregistered storage instance");
282#if LLVM_ENABLE_THREADS != 0
288 if (!threadAllocator) {
293 llvm::sys::SmartScopedLock<true> lock(threadAllocatorMutex);
294 threadAllocators.push_back(
295 std::unique_ptr<StorageAllocator>(threadAllocator));
298 return *threadAllocator;
311 assert(singletonInstance &&
"expected singleton instance to exist");
312 return singletonInstance;
322#if LLVM_ENABLE_THREADS != 0
328 std::vector<std::unique_ptr<StorageAllocator>> threadAllocators;
357 impl->threadingIsEnabled = !disable;
362auto StorageUniquer::getParametricStorageTypeImpl(
363 TypeID id,
unsigned hashValue,
365 function_ref<BaseStorage *(StorageAllocator &)> ctorFn) -> BaseStorage * {
366 return impl->getOrCreate(
id, hashValue, isEqual, ctorFn);
371void StorageUniquer::registerParametricStorageTypeImpl(
373 impl->parametricUniquers.try_emplace(
374 id, std::make_unique<ParametricStorageUniquer>(destructorFn));
379auto StorageUniquer::getSingletonImpl(
TypeID id) -> BaseStorage * {
380 return impl->getSingleton(
id);
385 return impl->hasSingleton(
id);
390 return impl->hasParametricStorage(
id);
395void StorageUniquer::registerSingletonImpl(
397 assert(!
impl->singletonInstances.count(
id) &&
398 "storage class already registered");
399 impl->singletonInstances.try_emplace(
id, ctorFn(
impl->allocator));
403LogicalResult StorageUniquer::mutateImpl(
404 TypeID id, BaseStorage *storage,
405 function_ref<LogicalResult(StorageAllocator &)> mutationFn) {
406 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.