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;
78 std::unique_ptr<StorageTypeSet> transientInstances;
80#if LLVM_ENABLE_THREADS != 0
82 llvm::sys::SmartRWMutex<true> mutex;
88 BaseStorage *getOrCreateUnsafe(Shard &shard, LookupKey &key,
90 if (isInTransientScope()) {
98 if (shard.transientInstances) {
99 auto transientIt = shard.transientInstances->find_as(key);
100 if (transientIt != shard.transientInstances->end())
101 return transientIt->storage;
103 auto baseIt = shard.instances.find_as(key);
104 if (baseIt != shard.instances.end())
105 return baseIt->storage;
106 if (!shard.transientInstances)
107 shard.transientInstances = std::make_unique<StorageTypeSet>();
108 auto existing = shard.transientInstances->insert_as({key.hashValue}, key);
109 BaseStorage *&storage = existing.first->storage;
115 auto existing = shard.instances.insert_as({key.hashValue}, key);
116 BaseStorage *&storage = existing.first->storage;
123 void destroyShardInstances(Shard &shard) {
126 for (HashedStorage &instance : shard.instances)
127 destructorFn(instance.storage);
128 if (shard.transientInstances) {
129 for (HashedStorage &instance : *shard.transientInstances)
130 destructorFn(instance.storage);
135#if LLVM_ENABLE_THREADS != 0
139 ParametricStorageUniquer(
function_ref<
void(BaseStorage *)> destructorFn,
140 size_t numShards = 8)
141 : shards(new std::atomic<Shard *>[numShards]),
142 destructorFn(destructorFn) {
143 assert(llvm::isPowerOf2_64(numShards) &&
144 "the number of shards is required to be a power of 2");
145 this->numShards = numShards;
146 this->inTransientScope =
false;
147 for (
size_t i = 0; i < numShards; i++)
148 shards[i].store(
nullptr, std::memory_order_relaxed);
150 ~ParametricStorageUniquer() {
152 for (
size_t i = 0; i != numShards; ++i) {
153 if (Shard *shard = shards[i].
load()) {
154 destroyShardInstances(*shard);
160 BaseStorage *getOrCreate(
bool threadingIsEnabled,
unsigned hashValue,
163 Shard &shard = getShard(hashValue);
164 ParametricStorageUniquer::LookupKey lookupKey{hashValue, isEqual};
165 if (!threadingIsEnabled)
166 return getOrCreateUnsafe(shard, lookupKey, ctorFn);
169 auto localIt = localCache->insert_as({hashValue}, lookupKey);
170 BaseStorage *&localInst = localIt.first->storage;
176 llvm::sys::SmartScopedReader<true> typeLock(shard.mutex);
177 if (isInTransientScope() && shard.transientInstances) {
178 auto it = shard.transientInstances->find_as(lookupKey);
179 if (it != shard.transientInstances->end())
180 return localInst = it->storage;
182 auto it = shard.instances.find_as(lookupKey);
183 if (it != shard.instances.end())
184 return localInst = it->storage;
189 llvm::sys::SmartScopedWriter<true> typeLock(shard.mutex);
190 return localInst = getOrCreateUnsafe(shard, lookupKey, ctorFn);
195 LogicalResult mutate(
bool threadingIsEnabled, BaseStorage *storage,
197 if (!threadingIsEnabled)
203 Shard &shard = getShard(llvm::hash_value(storage));
204 llvm::sys::SmartScopedWriter<true> lock(shard.mutex);
208 void beginTransientScope() {
209 assert(!isInTransientScope() &&
210 "parametric storage uniquer is already in a transient scope");
211 inTransientScope =
true;
214 void endTransientScope() {
215 assert(isInTransientScope() &&
216 "parametric storage uniquer is not in a transient scope");
217 if (!isInTransientScope())
220 for (
size_t i = 0; i != numShards; ++i) {
221 if (Shard *shard = shards[i].
load()) {
222 llvm::sys::SmartScopedWriter<true> typeLock(shard->mutex);
223 if (shard->transientInstances) {
225 for (HashedStorage &instance : *shard->transientInstances)
226 destructorFn(instance.storage);
228 shard->transientInstances.reset();
233 inTransientScope =
false;
236 bool isInTransientScope()
const {
return inTransientScope; }
238 size_t getNumShards()
const {
return numShards; }
242 Shard &getShard(
unsigned hashValue) {
244 unsigned shardNum = hashValue & (numShards - 1);
247 Shard *shard = shards[shardNum].load(std::memory_order_acquire);
252 Shard *newShard =
new Shard();
253 if (shards[shardNum].compare_exchange_strong(shard, newShard))
263 ThreadLocalCache<StorageTypeSet> localCache;
268 std::unique_ptr<std::atomic<Shard *>[]> shards;
271 unsigned numShards : 31;
274 unsigned inTransientScope : 1;
283 ParametricStorageUniquer(
function_ref<
void(BaseStorage *)> destructorFn,
284 size_t numShards = 0)
285 : destructorFn(destructorFn) {}
286 ~ParametricStorageUniquer() { destroyShardInstances(shard); }
290 getOrCreate(
bool threadingIsEnabled,
unsigned hashValue,
293 ParametricStorageUniquer::LookupKey lookupKey{hashValue, isEqual};
294 return getOrCreateUnsafe(shard, lookupKey, ctorFn);
299 mutate(
bool threadingIsEnabled, BaseStorage *storage,
304 void beginTransientScope() {
305 assert(!inTransientScope &&
306 "parametric storage uniquer is already in a transient scope");
307 inTransientScope =
true;
310 void endTransientScope() {
311 assert(inTransientScope &&
312 "parametric storage uniquer is not in a transient scope");
313 if (!inTransientScope)
315 if (shard.transientInstances) {
317 for (HashedStorage &instance : *shard.transientInstances)
318 destructorFn(instance.storage);
320 shard.transientInstances.reset();
322 inTransientScope =
false;
325 bool isInTransientScope()
const {
return inTransientScope; }
335 bool inTransientScope =
false;
349#if LLVM_ENABLE_THREADS != 0
354 std::vector<std::unique_ptr<StorageAllocator>> threadAllocators;
380 "creating unregistered storage instance");
382 return storageUniquer.getOrCreate(
393 "mutating unregistered storage instance");
403#if LLVM_ENABLE_THREADS != 0
416 if (!threadAllocator) {
418 llvm::sys::SmartScopedLock<true> lock(
421 std::unique_ptr<StorageAllocator>(threadAllocator));
423 return *threadAllocator;
428 if (!threadAllocator) {
433 llvm::sys::SmartScopedLock<true> lock(threadAllocatorMutex);
434 threadAllocators.push_back(
435 std::unique_ptr<StorageAllocator>(threadAllocator));
438 return *threadAllocator;
451 "storage uniquer is already in a transient scope");
454 entry.second->beginTransientScope();
458 assert(
transientState &&
"storage uniquer is not in a transient scope");
462 entry.second->endTransientScope();
480 assert(singletonInstance &&
"expected singleton instance to exist");
481 return singletonInstance;
495#if LLVM_ENABLE_THREADS != 0
501 std::vector<std::unique_ptr<StorageAllocator>> threadAllocators;
533 impl->threadingIsEnabled = !disable;
541 return impl->isInTransientScope();
546auto StorageUniquer::getParametricStorageTypeImpl(
547 TypeID id,
unsigned hashValue,
549 function_ref<BaseStorage *(StorageAllocator &)> ctorFn) -> BaseStorage * {
550 return impl->getOrCreate(
id, hashValue, isEqual, ctorFn);
555void StorageUniquer::registerParametricStorageTypeImpl(
557 auto uniquer = std::make_unique<ParametricStorageUniquer>(destructorFn);
558 if (
impl->isInTransientScope())
559 uniquer->beginTransientScope();
560 impl->parametricUniquers.try_emplace(
id, std::move(uniquer));
565auto StorageUniquer::getSingletonImpl(
TypeID id) -> BaseStorage * {
566 return impl->getSingleton(
id);
571 return impl->hasSingleton(
id);
576 return impl->hasParametricStorage(
id);
581void StorageUniquer::registerSingletonImpl(
583 if (
impl->transientState) {
584 assert(!
impl->transientState->singletonInstances.count(
id) &&
585 !
impl->singletonInstances.count(
id) &&
586 "storage class already registered");
587 impl->transientState->singletonInstances.try_emplace(
588 id, ctorFn(
impl->getThreadSafeAllocator()));
591 assert(!
impl->singletonInstances.count(
id) &&
592 "storage class already registered");
593 impl->singletonInstances.try_emplace(
id, ctorFn(
impl->allocator));
597LogicalResult StorageUniquer::mutateImpl(
598 TypeID id, BaseStorage *storage,
599 function_ref<LogicalResult(StorageAllocator &)> mutationFn) {
600 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.
bool isInTransientScope() const
Returns true if the uniquer is currently in a transient scope.
void disableMultithreading(bool disable=true)
Set the flag specifying if multi-threading is disabled within the uniquer.
void beginTransientScope()
Begins a transient scope.
void endTransientScope()
Ends the transient scope and resets back to the base state, freeing all transiently allocated storage...
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
Bundled state dynamically allocated when entering a transient scope.
DenseMap< TypeID, BaseStorage * > singletonInstances
Transient singleton instances registered during transient scope.
std::unique_ptr< StorageAllocator > allocator
Single-threaded allocator used during transient scope.
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.
std::unique_ptr< TransientState > transientState
Transient state bundled into a unique pointer (nullptr when inactive).
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
void beginTransientScope()
bool isInTransientScope() const
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.