MLIR 23.0.0git
StorageUniquer.cpp
Go to the documentation of this file.
1//===- StorageUniquer.cpp - Common Storage Class Uniquer ------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
10
11#include "mlir/Support/LLVM.h"
13#include "mlir/Support/TypeID.h"
14#include "llvm/Support/RWMutex.h"
15
16using namespace mlir;
17using namespace mlir::detail;
18
19namespace {
20/// This class represents a uniquer for storage instances of a specific type
21/// that has parametric storage. It contains all of the necessary data to unique
22/// storage instances in a thread safe way. This allows for the main uniquer to
23/// bucket each of the individual sub-types removing the need to lock the main
24/// uniquer itself.
25class ParametricStorageUniquer {
26public:
27 using BaseStorage = StorageUniquer::BaseStorage;
28 using StorageAllocator = StorageUniquer::StorageAllocator;
29
30 /// A lookup key for derived instances of storage objects.
31 struct LookupKey {
32 /// The known hash value of the key.
33 unsigned hashValue;
34
35 /// An equality function for comparing with an existing storage instance.
36 function_ref<bool(const BaseStorage *)> isEqual;
37 };
38
39private:
40 /// A utility wrapper object representing a hashed storage object. This class
41 /// contains a storage object and an existing computed hash value.
42 struct HashedStorage {
43 HashedStorage(unsigned hashValue = 0, BaseStorage *storage = nullptr)
44 : hashValue(hashValue), storage(storage) {}
45 unsigned hashValue;
46 BaseStorage *storage;
47 };
48
49 /// Storage info for derived TypeStorage objects.
50 struct StorageKeyInfo {
51 static inline HashedStorage getEmptyKey() {
52 return HashedStorage(0, DenseMapInfo<BaseStorage *>::getEmptyKey());
53 }
54
55 static inline unsigned getHashValue(const HashedStorage &key) {
56 return key.hashValue;
57 }
58 static inline unsigned getHashValue(const LookupKey &key) {
59 return key.hashValue;
60 }
61
62 static inline bool isEqual(const HashedStorage &lhs,
63 const HashedStorage &rhs) {
64 return lhs.storage == rhs.storage;
65 }
66 static inline bool isEqual(const LookupKey &lhs, const HashedStorage &rhs) {
67 if (isEqual(rhs, getEmptyKey()))
68 return false;
69 // Invoke the equality function on the lookup key.
70 return lhs.isEqual(rhs.storage);
71 }
72 };
73 using StorageTypeSet = DenseSet<HashedStorage, StorageKeyInfo>;
74
75 /// This class represents a single shard of the uniquer. The uniquer uses a
76 /// set of shards to allow for multiple threads to create instances with less
77 /// lock contention.
78 struct Shard {
79 /// The set containing the allocated storage instances.
80 StorageTypeSet instances;
81
82#if LLVM_ENABLE_THREADS != 0
83 /// A mutex to keep uniquing thread-safe.
84 llvm::sys::SmartRWMutex<true> mutex;
85#endif
86 };
87
88 /// Get or create an instance of a param derived type in an thread-unsafe
89 /// fashion.
90 BaseStorage *getOrCreateUnsafe(Shard &shard, LookupKey &key,
91 function_ref<BaseStorage *()> ctorFn) {
92 auto existing = shard.instances.insert_as({key.hashValue}, key);
93 BaseStorage *&storage = existing.first->storage;
94 if (existing.second)
95 storage = ctorFn();
96 return storage;
97 }
98
99 /// Destroy all of the storage instances within the given shard.
100 void destroyShardInstances(Shard &shard) {
101 if (!destructorFn)
102 return;
103 for (HashedStorage &instance : shard.instances)
104 destructorFn(instance.storage);
105 }
106
107public:
108#if LLVM_ENABLE_THREADS != 0
109 /// Initialize the storage uniquer with a given number of storage shards to
110 /// use. The provided shard number is required to be a valid power of 2. The
111 /// destructor function is used to destroy any allocated storage instances.
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);
120 }
121 ~ParametricStorageUniquer() {
122 // Free all of the allocated shards.
123 for (size_t i = 0; i != numShards; ++i) {
124 if (Shard *shard = shards[i].load()) {
125 destroyShardInstances(*shard);
126 delete shard;
127 }
128 }
129 }
130 /// Get or create an instance of a parametric type.
131 BaseStorage *getOrCreate(bool threadingIsEnabled, unsigned hashValue,
132 function_ref<bool(const BaseStorage *)> isEqual,
133 function_ref<BaseStorage *()> ctorFn) {
134 Shard &shard = getShard(hashValue);
135 ParametricStorageUniquer::LookupKey lookupKey{hashValue, isEqual};
136 if (!threadingIsEnabled)
137 return getOrCreateUnsafe(shard, lookupKey, ctorFn);
138
139 // Check for a instance of this object in the local cache.
140 auto localIt = localCache->insert_as({hashValue}, lookupKey);
141 BaseStorage *&localInst = localIt.first->storage;
142 if (localInst)
143 return localInst;
144
145 // Check for an existing instance in read-only mode.
146 {
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;
151 }
152
153 // Acquire a writer-lock so that we can safely create the new storage
154 // instance.
155 llvm::sys::SmartScopedWriter<true> typeLock(shard.mutex);
156 return localInst = getOrCreateUnsafe(shard, lookupKey, ctorFn);
157 }
158
159 /// Run a mutation function on the provided storage object in a thread-safe
160 /// way.
161 LogicalResult mutate(bool threadingIsEnabled, BaseStorage *storage,
162 function_ref<LogicalResult()> mutationFn) {
163 if (!threadingIsEnabled)
164 return mutationFn();
165
166 // Get a shard to use for mutating this storage instance. It doesn't need to
167 // be the same shard as the original allocation, but does need to be
168 // deterministic.
169 Shard &shard = getShard(llvm::hash_value(storage));
170 llvm::sys::SmartScopedWriter<true> lock(shard.mutex);
171 return mutationFn();
172 }
173
174private:
175 /// Return the shard used for the given hash value.
176 Shard &getShard(unsigned hashValue) {
177 // Get a shard number from the provided hashvalue.
178 unsigned shardNum = hashValue & (numShards - 1);
179
180 // Try to acquire an already initialized shard.
181 Shard *shard = shards[shardNum].load(std::memory_order_acquire);
182 if (shard)
183 return *shard;
184
185 // Otherwise, try to allocate a new shard.
186 Shard *newShard = new Shard();
187 if (shards[shardNum].compare_exchange_strong(shard, newShard))
188 return *newShard;
189
190 // If one was allocated before we can initialize ours, delete ours.
191 delete newShard;
192 return *shard;
193 }
194
195 /// A thread local cache for storage objects. This helps to reduce the lock
196 /// contention when an object already existing in the cache.
197 ThreadLocalCache<StorageTypeSet> localCache;
198
199 /// A set of uniquer shards to allow for further bucketing accesses for
200 /// instances of this storage type. Each shard is lazily initialized to reduce
201 /// the overhead when only a small amount of shards are in use.
202 std::unique_ptr<std::atomic<Shard *>[]> shards;
203
204 /// The number of available shards.
205 size_t numShards;
206
207 /// Function to used to destruct any allocated storage instances.
208 function_ref<void(BaseStorage *)> destructorFn;
209
210#else
211 /// If multi-threading is disabled, ignore the shard parameter as we will
212 /// always use one shard. The destructor function is used to destroy any
213 /// allocated storage instances.
214 ParametricStorageUniquer(function_ref<void(BaseStorage *)> destructorFn,
215 size_t numShards = 0)
216 : destructorFn(destructorFn) {}
217 ~ParametricStorageUniquer() { destroyShardInstances(shard); }
218
219 /// Get or create an instance of a parametric type.
220 BaseStorage *
221 getOrCreate(bool threadingIsEnabled, unsigned hashValue,
222 function_ref<bool(const BaseStorage *)> isEqual,
223 function_ref<BaseStorage *()> ctorFn) {
224 ParametricStorageUniquer::LookupKey lookupKey{hashValue, isEqual};
225 return getOrCreateUnsafe(shard, lookupKey, ctorFn);
226 }
227 /// Run a mutation function on the provided storage object in a thread-safe
228 /// way.
229 LogicalResult
230 mutate(bool threadingIsEnabled, BaseStorage *storage,
231 function_ref<LogicalResult()> mutationFn) {
232 return mutationFn();
233 }
234
235private:
236 /// The main uniquer shard that is used for allocating storage instances.
237 Shard shard;
238
239 /// Function to used to destruct any allocated storage instances.
240 function_ref<void(BaseStorage *)> destructorFn;
241#endif
242};
243} // namespace
244
245namespace mlir {
246namespace detail {
247/// This is the implementation of the StorageUniquer class.
251
252 //===--------------------------------------------------------------------===//
253 // Parametric Storage
254 //===--------------------------------------------------------------------===//
255
256 /// Check if an instance of a parametric storage class exists.
257 bool hasParametricStorage(TypeID id) { return parametricUniquers.count(id); }
258
259 /// Get or create an instance of a parametric type.
261 getOrCreate(TypeID id, unsigned hashValue,
262 function_ref<bool(const BaseStorage *)> isEqual,
264 assert(parametricUniquers.count(id) &&
265 "creating unregistered storage instance");
266 ParametricStorageUniquer &storageUniquer = *parametricUniquers[id];
267 return storageUniquer.getOrCreate(
268 threadingIsEnabled, hashValue, isEqual,
269 [&] { return ctorFn(getThreadSafeAllocator()); });
270 }
271
272 /// Run a mutation function on the provided storage object in a thread-safe
273 /// way.
274 LogicalResult
276 function_ref<LogicalResult(StorageAllocator &)> mutationFn) {
277 assert(parametricUniquers.count(id) &&
278 "mutating unregistered storage instance");
279 ParametricStorageUniquer &storageUniquer = *parametricUniquers[id];
280 return storageUniquer.mutate(threadingIsEnabled, storage, [&] {
281 return mutationFn(getThreadSafeAllocator());
282 });
283 }
284
285 /// Return an allocator that can be used to safely allocate instances on the
286 /// current thread.
288#if LLVM_ENABLE_THREADS != 0
290 return allocator;
291
292 // If the allocator has not been initialized, create a new one.
293 StorageAllocator *&threadAllocator = threadSafeAllocator.get();
294 if (!threadAllocator) {
295 threadAllocator = new StorageAllocator();
296
297 // Record this allocator, given that we don't want it to be destroyed when
298 // the thread dies.
299 llvm::sys::SmartScopedLock<true> lock(threadAllocatorMutex);
300 threadAllocators.push_back(
301 std::unique_ptr<StorageAllocator>(threadAllocator));
302 }
303
304 return *threadAllocator;
305#else
306 return allocator;
307#endif
308 }
309
310 //===--------------------------------------------------------------------===//
311 // Singleton Storage
312 //===--------------------------------------------------------------------===//
313
314 /// Get or create an instance of a singleton storage class.
316 BaseStorage *singletonInstance = singletonInstances[id];
317 assert(singletonInstance && "expected singleton instance to exist");
318 return singletonInstance;
319 }
320
321 /// Check if an instance of a singleton storage class exists.
322 bool hasSingleton(TypeID id) const { return singletonInstances.count(id); }
323
324 //===--------------------------------------------------------------------===//
325 // Instance Storage
326 //===--------------------------------------------------------------------===//
327
328#if LLVM_ENABLE_THREADS != 0
329 /// A thread local set of allocators used for uniquing parametric instances,
330 /// or other data allocated in thread volatile situations.
331 ThreadLocalCache<StorageAllocator *> threadSafeAllocator;
332
333 /// All of the allocators that have been created for thread based allocation.
334 std::vector<std::unique_ptr<StorageAllocator>> threadAllocators;
335
336 /// A mutex used for safely adding a new thread allocator.
337 llvm::sys::SmartMutex<true> threadAllocatorMutex;
338#endif
339
340 /// Main allocator used for uniquing singleton instances, and other state when
341 /// thread safety is guaranteed.
343
344 /// Map of type ids to the storage uniquer to use for registered objects.
347
348 /// Map of type ids to a singleton instance when the storage class is a
349 /// singleton.
351
352 /// Flag specifying if multi-threading is enabled within the uniquer.
354};
355} // namespace detail
356} // namespace mlir
357
360
361/// Set the flag specifying if multi-threading is disabled within the uniquer.
363 impl->threadingIsEnabled = !disable;
364}
365
366/// Implementation for getting/creating an instance of a derived type with
367/// parametric storage.
368auto StorageUniquer::getParametricStorageTypeImpl(
369 TypeID id, unsigned hashValue,
370 function_ref<bool(const BaseStorage *)> isEqual,
371 function_ref<BaseStorage *(StorageAllocator &)> ctorFn) -> BaseStorage * {
372 return impl->getOrCreate(id, hashValue, isEqual, ctorFn);
373}
374
375/// Implementation for registering an instance of a derived type with
376/// parametric storage.
377void StorageUniquer::registerParametricStorageTypeImpl(
378 TypeID id, function_ref<void(BaseStorage *)> destructorFn) {
379 impl->parametricUniquers.try_emplace(
380 id, std::make_unique<ParametricStorageUniquer>(destructorFn));
381}
382
383/// Implementation for getting an instance of a derived type with default
384/// storage.
385auto StorageUniquer::getSingletonImpl(TypeID id) -> BaseStorage * {
386 return impl->getSingleton(id);
387}
388
389/// Test is the storage singleton is initialized.
391 return impl->hasSingleton(id);
392}
393
394/// Test is the parametric storage is initialized.
396 return impl->hasParametricStorage(id);
397}
398
399/// Implementation for registering an instance of a derived type with default
400/// storage.
401void StorageUniquer::registerSingletonImpl(
402 TypeID id, function_ref<BaseStorage *(StorageAllocator &)> ctorFn) {
403 assert(!impl->singletonInstances.count(id) &&
404 "storage class already registered");
405 impl->singletonInstances.try_emplace(id, ctorFn(impl->allocator));
406}
407
408/// Implementation for mutating an instance of a derived storage.
409LogicalResult StorageUniquer::mutateImpl(
410 TypeID id, BaseStorage *storage,
411 function_ref<LogicalResult(StorageAllocator &)> mutationFn) {
412 return impl->mutate(id, storage, mutationFn);
413}
lhs
auto load
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.
Definition TypeID.h:107
AttrTypeReplacer.
Attribute collections provide a dictionary-like interface.
Definition Traits.h:18
Include the generated interface declarations.
llvm::DenseMap< KeyT, ValueT, KeyInfoT, BucketT > DenseMap
Definition LLVM.h:120
llvm::function_ref< Fn > function_ref
Definition LLVM.h:147
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.