14 #include "llvm/Support/RWMutex.h"
25 class ParametricStorageUniquer {
42 struct HashedStorage {
43 HashedStorage(
unsigned hashValue = 0, BaseStorage *storage =
nullptr)
44 : hashValue(hashValue), storage(storage) {}
50 struct StorageKeyInfo {
51 static inline HashedStorage getEmptyKey() {
54 static inline HashedStorage 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);
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)
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))
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,
267 assert(parametricUniquers.count(
id) &&
268 "creating unregistered storage instance");
269 ParametricStorageUniquer &storageUniquer = *parametricUniquers[id];
270 return storageUniquer.getOrCreate(
271 threadingIsEnabled, hashValue, isEqual,
272 [&] {
return ctorFn(getThreadSafeAllocator()); });
280 assert(parametricUniquers.count(
id) &&
281 "mutating unregistered storage instance");
282 ParametricStorageUniquer &storageUniquer = *parametricUniquers[id];
283 return storageUniquer.mutate(threadingIsEnabled, storage, [&] {
284 return mutationFn(getThreadSafeAllocator());
291 #if LLVM_ENABLE_THREADS != 0
292 if (!threadingIsEnabled)
297 if (!threadAllocator) {
302 llvm::sys::SmartScopedLock<true> lock(threadAllocatorMutex);
303 threadAllocators.push_back(
304 std::unique_ptr<StorageAllocator>(threadAllocator));
307 return *threadAllocator;
319 BaseStorage *singletonInstance = singletonInstances[id];
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;
356 bool threadingIsEnabled =
true;
366 impl->threadingIsEnabled = !disable;
371 auto StorageUniquer::getParametricStorageTypeImpl(
372 TypeID id,
unsigned hashValue,
374 function_ref<BaseStorage *(StorageAllocator &)> ctorFn) -> BaseStorage * {
375 return impl->getOrCreate(
id, hashValue, isEqual, ctorFn);
380 void StorageUniquer::registerParametricStorageTypeImpl(
382 impl->parametricUniquers.try_emplace(
383 id, std::make_unique<ParametricStorageUniquer>(destructorFn));
388 auto StorageUniquer::getSingletonImpl(
TypeID id) -> BaseStorage * {
389 return impl->getSingleton(
id);
394 return impl->hasSingleton(
id);
399 return impl->hasParametricStorage(
id);
404 void StorageUniquer::registerSingletonImpl(
406 assert(!
impl->singletonInstances.count(
id) &&
407 "storage class already registered");
408 impl->singletonInstances.try_emplace(
id, ctorFn(
impl->allocator));
412 LogicalResult 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.
inline ::llvm::hash_code hash_value(const PolynomialBase< D, T > &arg)
Include the generated interface declarations.
This is the implementation of the StorageUniquer class.
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.
StorageAllocator allocator
Main allocator used for uniquing singleton instances, and other state when thread safety is guarantee...
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.
StorageAllocator & getThreadSafeAllocator()
Return an allocator that can be used to safely allocate instances on the current thread.
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.
BaseStorage * getSingleton(TypeID id)
Get or create an instance of a singleton storage class.
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.