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 unsigned getHashValue(const HashedStorage &key) {
52 return key.hashValue;
53 }
54 static inline unsigned getHashValue(const LookupKey &key) {
55 return key.hashValue;
56 }
57
58 static inline bool isEqual(const HashedStorage &lhs,
59 const HashedStorage &rhs) {
60 return lhs.storage == rhs.storage;
61 }
62 static inline bool isEqual(const LookupKey &lhs, const HashedStorage &rhs) {
63 // Invoke the equality function on the lookup key.
64 return lhs.isEqual(rhs.storage);
65 }
66 };
67 using StorageTypeSet = DenseSet<HashedStorage, StorageKeyInfo>;
68
69 /// This class represents a single shard of the uniquer. The uniquer uses a
70 /// set of shards to allow for multiple threads to create instances with less
71 /// lock contention.
72 struct Shard {
73 /// The set containing the allocated storage instances.
74 StorageTypeSet instances;
75
76#if LLVM_ENABLE_THREADS != 0
77 /// A mutex to keep uniquing thread-safe.
78 llvm::sys::SmartRWMutex<true> mutex;
79#endif
80 };
81
82 /// Get or create an instance of a param derived type in an thread-unsafe
83 /// fashion.
84 BaseStorage *getOrCreateUnsafe(Shard &shard, LookupKey &key,
85 function_ref<BaseStorage *()> ctorFn) {
86 auto existing = shard.instances.insert_as({key.hashValue}, key);
87 BaseStorage *&storage = existing.first->storage;
88 if (existing.second)
89 storage = ctorFn();
90 return storage;
91 }
92
93 /// Destroy all of the storage instances within the given shard.
94 void destroyShardInstances(Shard &shard) {
95 if (!destructorFn)
96 return;
97 for (HashedStorage &instance : shard.instances)
98 destructorFn(instance.storage);
99 }
100
101public:
102#if LLVM_ENABLE_THREADS != 0
103 /// Initialize the storage uniquer with a given number of storage shards to
104 /// use. The provided shard number is required to be a valid power of 2. The
105 /// destructor function is used to destroy any allocated storage instances.
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);
114 }
115 ~ParametricStorageUniquer() {
116 // Free all of the allocated shards.
117 for (size_t i = 0; i != numShards; ++i) {
118 if (Shard *shard = shards[i].load()) {
119 destroyShardInstances(*shard);
120 delete shard;
121 }
122 }
123 }
124 /// Get or create an instance of a parametric type.
125 BaseStorage *getOrCreate(bool threadingIsEnabled, unsigned hashValue,
126 function_ref<bool(const BaseStorage *)> isEqual,
127 function_ref<BaseStorage *()> ctorFn) {
128 Shard &shard = getShard(hashValue);
129 ParametricStorageUniquer::LookupKey lookupKey{hashValue, isEqual};
130 if (!threadingIsEnabled)
131 return getOrCreateUnsafe(shard, lookupKey, ctorFn);
132
133 // Check for a instance of this object in the local cache.
134 auto localIt = localCache->insert_as({hashValue}, lookupKey);
135 BaseStorage *&localInst = localIt.first->storage;
136 if (localInst)
137 return localInst;
138
139 // Check for an existing instance in read-only mode.
140 {
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;
145 }
146
147 // Acquire a writer-lock so that we can safely create the new storage
148 // instance.
149 llvm::sys::SmartScopedWriter<true> typeLock(shard.mutex);
150 return localInst = getOrCreateUnsafe(shard, lookupKey, ctorFn);
151 }
152
153 /// Run a mutation function on the provided storage object in a thread-safe
154 /// way.
155 LogicalResult mutate(bool threadingIsEnabled, BaseStorage *storage,
156 function_ref<LogicalResult()> mutationFn) {
157 if (!threadingIsEnabled)
158 return mutationFn();
159
160 // Get a shard to use for mutating this storage instance. It doesn't need to
161 // be the same shard as the original allocation, but does need to be
162 // deterministic.
163 Shard &shard = getShard(llvm::hash_value(storage));
164 llvm::sys::SmartScopedWriter<true> lock(shard.mutex);
165 return mutationFn();
166 }
167
168private:
169 /// Return the shard used for the given hash value.
170 Shard &getShard(unsigned hashValue) {
171 // Get a shard number from the provided hashvalue.
172 unsigned shardNum = hashValue & (numShards - 1);
173
174 // Try to acquire an already initialized shard.
175 Shard *shard = shards[shardNum].load(std::memory_order_acquire);
176 if (shard)
177 return *shard;
178
179 // Otherwise, try to allocate a new shard.
180 Shard *newShard = new Shard();
181 if (shards[shardNum].compare_exchange_strong(shard, newShard))
182 return *newShard;
183
184 // If one was allocated before we can initialize ours, delete ours.
185 delete newShard;
186 return *shard;
187 }
188
189 /// A thread local cache for storage objects. This helps to reduce the lock
190 /// contention when an object already existing in the cache.
191 ThreadLocalCache<StorageTypeSet> localCache;
192
193 /// A set of uniquer shards to allow for further bucketing accesses for
194 /// instances of this storage type. Each shard is lazily initialized to reduce
195 /// the overhead when only a small amount of shards are in use.
196 std::unique_ptr<std::atomic<Shard *>[]> shards;
197
198 /// The number of available shards.
199 size_t numShards;
200
201 /// Function to used to destruct any allocated storage instances.
202 function_ref<void(BaseStorage *)> destructorFn;
203
204#else
205 /// If multi-threading is disabled, ignore the shard parameter as we will
206 /// always use one shard. The destructor function is used to destroy any
207 /// allocated storage instances.
208 ParametricStorageUniquer(function_ref<void(BaseStorage *)> destructorFn,
209 size_t numShards = 0)
210 : destructorFn(destructorFn) {}
211 ~ParametricStorageUniquer() { destroyShardInstances(shard); }
212
213 /// Get or create an instance of a parametric type.
214 BaseStorage *
215 getOrCreate(bool threadingIsEnabled, unsigned hashValue,
216 function_ref<bool(const BaseStorage *)> isEqual,
217 function_ref<BaseStorage *()> ctorFn) {
218 ParametricStorageUniquer::LookupKey lookupKey{hashValue, isEqual};
219 return getOrCreateUnsafe(shard, lookupKey, ctorFn);
220 }
221 /// Run a mutation function on the provided storage object in a thread-safe
222 /// way.
223 LogicalResult
224 mutate(bool threadingIsEnabled, BaseStorage *storage,
225 function_ref<LogicalResult()> mutationFn) {
226 return mutationFn();
227 }
228
229private:
230 /// The main uniquer shard that is used for allocating storage instances.
231 Shard shard;
232
233 /// Function to used to destruct any allocated storage instances.
234 function_ref<void(BaseStorage *)> destructorFn;
235#endif
236};
237} // namespace
238
239namespace mlir {
240namespace detail {
241/// This is the implementation of the StorageUniquer class.
245
246 //===--------------------------------------------------------------------===//
247 // Parametric Storage
248 //===--------------------------------------------------------------------===//
249
250 /// Check if an instance of a parametric storage class exists.
251 bool hasParametricStorage(TypeID id) { return parametricUniquers.count(id); }
252
253 /// Get or create an instance of a parametric type.
255 getOrCreate(TypeID id, unsigned hashValue,
256 function_ref<bool(const BaseStorage *)> isEqual,
258 assert(parametricUniquers.count(id) &&
259 "creating unregistered storage instance");
260 ParametricStorageUniquer &storageUniquer = *parametricUniquers[id];
261 return storageUniquer.getOrCreate(
262 threadingIsEnabled, hashValue, isEqual,
263 [&] { return ctorFn(getThreadSafeAllocator()); });
264 }
265
266 /// Run a mutation function on the provided storage object in a thread-safe
267 /// way.
268 LogicalResult
270 function_ref<LogicalResult(StorageAllocator &)> mutationFn) {
271 assert(parametricUniquers.count(id) &&
272 "mutating unregistered storage instance");
273 ParametricStorageUniquer &storageUniquer = *parametricUniquers[id];
274 return storageUniquer.mutate(threadingIsEnabled, storage, [&] {
275 return mutationFn(getThreadSafeAllocator());
276 });
277 }
278
279 /// Return an allocator that can be used to safely allocate instances on the
280 /// current thread.
282#if LLVM_ENABLE_THREADS != 0
284 return allocator;
285
286 // If the allocator has not been initialized, create a new one.
287 StorageAllocator *&threadAllocator = threadSafeAllocator.get();
288 if (!threadAllocator) {
289 threadAllocator = new StorageAllocator();
290
291 // Record this allocator, given that we don't want it to be destroyed when
292 // the thread dies.
293 llvm::sys::SmartScopedLock<true> lock(threadAllocatorMutex);
294 threadAllocators.push_back(
295 std::unique_ptr<StorageAllocator>(threadAllocator));
296 }
297
298 return *threadAllocator;
299#else
300 return allocator;
301#endif
302 }
303
304 //===--------------------------------------------------------------------===//
305 // Singleton Storage
306 //===--------------------------------------------------------------------===//
307
308 /// Get or create an instance of a singleton storage class.
310 BaseStorage *singletonInstance = singletonInstances[id];
311 assert(singletonInstance && "expected singleton instance to exist");
312 return singletonInstance;
313 }
314
315 /// Check if an instance of a singleton storage class exists.
316 bool hasSingleton(TypeID id) const { return singletonInstances.count(id); }
317
318 //===--------------------------------------------------------------------===//
319 // Instance Storage
320 //===--------------------------------------------------------------------===//
321
322#if LLVM_ENABLE_THREADS != 0
323 /// A thread local set of allocators used for uniquing parametric instances,
324 /// or other data allocated in thread volatile situations.
325 ThreadLocalCache<StorageAllocator *> threadSafeAllocator;
326
327 /// All of the allocators that have been created for thread based allocation.
328 std::vector<std::unique_ptr<StorageAllocator>> threadAllocators;
329
330 /// A mutex used for safely adding a new thread allocator.
331 llvm::sys::SmartMutex<true> threadAllocatorMutex;
332#endif
333
334 /// Main allocator used for uniquing singleton instances, and other state when
335 /// thread safety is guaranteed.
337
338 /// Map of type ids to the storage uniquer to use for registered objects.
341
342 /// Map of type ids to a singleton instance when the storage class is a
343 /// singleton.
345
346 /// Flag specifying if multi-threading is enabled within the uniquer.
348};
349} // namespace detail
350} // namespace mlir
351
354
355/// Set the flag specifying if multi-threading is disabled within the uniquer.
357 impl->threadingIsEnabled = !disable;
358}
359
360/// Implementation for getting/creating an instance of a derived type with
361/// parametric storage.
362auto StorageUniquer::getParametricStorageTypeImpl(
363 TypeID id, unsigned hashValue,
364 function_ref<bool(const BaseStorage *)> isEqual,
365 function_ref<BaseStorage *(StorageAllocator &)> ctorFn) -> BaseStorage * {
366 return impl->getOrCreate(id, hashValue, isEqual, ctorFn);
367}
368
369/// Implementation for registering an instance of a derived type with
370/// parametric storage.
371void StorageUniquer::registerParametricStorageTypeImpl(
372 TypeID id, function_ref<void(BaseStorage *)> destructorFn) {
373 impl->parametricUniquers.try_emplace(
374 id, std::make_unique<ParametricStorageUniquer>(destructorFn));
375}
376
377/// Implementation for getting an instance of a derived type with default
378/// storage.
379auto StorageUniquer::getSingletonImpl(TypeID id) -> BaseStorage * {
380 return impl->getSingleton(id);
381}
382
383/// Test is the storage singleton is initialized.
385 return impl->hasSingleton(id);
386}
387
388/// Test is the parametric storage is initialized.
390 return impl->hasParametricStorage(id);
391}
392
393/// Implementation for registering an instance of a derived type with default
394/// storage.
395void StorageUniquer::registerSingletonImpl(
396 TypeID id, function_ref<BaseStorage *(StorageAllocator &)> ctorFn) {
397 assert(!impl->singletonInstances.count(id) &&
398 "storage class already registered");
399 impl->singletonInstances.try_emplace(id, ctorFn(impl->allocator));
400}
401
402/// Implementation for mutating an instance of a derived storage.
403LogicalResult StorageUniquer::mutateImpl(
404 TypeID id, BaseStorage *storage,
405 function_ref<LogicalResult(StorageAllocator &)> mutationFn) {
406 return impl->mutate(id, storage, mutationFn);
407}
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.