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
9 #include "mlir/Support/StorageUniquer.h"
10
11 #include "mlir/Support/LLVM.h"
12 #include "mlir/Support/ThreadLocalCache.h"
13 #include "mlir/Support/TypeID.h"
14 #include "llvm/Support/RWMutex.h"
15
16 using namespace mlir;
17 using namespace mlir::detail;
18
19 namespace {
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.
25 class ParametricStorageUniquer {
26 public:
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
39 private:
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 {
HashedStorage__anonb14bee2a0111::ParametricStorageUniquer::HashedStorage43 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 : DenseMapInfo<HashedStorage> {
getEmptyKey__anonb14bee2a0111::ParametricStorageUniquer::StorageKeyInfo51 static HashedStorage getEmptyKey() {
52 return HashedStorage(0, DenseMapInfo<BaseStorage *>::getEmptyKey());
53 }
getTombstoneKey__anonb14bee2a0111::ParametricStorageUniquer::StorageKeyInfo54 static HashedStorage getTombstoneKey() {
55 return HashedStorage(0, DenseMapInfo<BaseStorage *>::getTombstoneKey());
56 }
57
getHashValue__anonb14bee2a0111::ParametricStorageUniquer::StorageKeyInfo58 static unsigned getHashValue(const HashedStorage &key) {
59 return key.hashValue;
60 }
getHashValue__anonb14bee2a0111::ParametricStorageUniquer::StorageKeyInfo61 static unsigned getHashValue(LookupKey key) { return key.hashValue; }
62
isEqual__anonb14bee2a0111::ParametricStorageUniquer::StorageKeyInfo63 static bool isEqual(const HashedStorage &lhs, const HashedStorage &rhs) {
64 return lhs.storage == rhs.storage;
65 }
isEqual__anonb14bee2a0111::ParametricStorageUniquer::StorageKeyInfo66 static bool isEqual(const LookupKey &lhs, const HashedStorage &rhs) {
67 if (isEqual(rhs, getEmptyKey()) || isEqual(rhs, getTombstoneKey()))
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 /// Allocator to use when constructing derived instances.
83 StorageAllocator allocator;
84
85 #if LLVM_ENABLE_THREADS != 0
86 /// A mutex to keep uniquing thread-safe.
87 llvm::sys::SmartRWMutex<true> mutex;
88 #endif
89 };
90
91 /// Get or create an instance of a param derived type in an thread-unsafe
92 /// fashion.
93 BaseStorage *
getOrCreateUnsafe(Shard & shard,LookupKey & key,function_ref<BaseStorage * (StorageAllocator &)> ctorFn)94 getOrCreateUnsafe(Shard &shard, LookupKey &key,
95 function_ref<BaseStorage *(StorageAllocator &)> ctorFn) {
96 auto existing = shard.instances.insert_as({key.hashValue}, key);
97 BaseStorage *&storage = existing.first->storage;
98 if (existing.second)
99 storage = ctorFn(shard.allocator);
100 return storage;
101 }
102
103 public:
104 #if LLVM_ENABLE_THREADS != 0
105 /// Initialize the storage uniquer with a given number of storage shards to
106 /// use. The provided shard number is required to be a valid power of 2.
ParametricStorageUniquer(size_t numShards=8)107 ParametricStorageUniquer(size_t numShards = 8)
108 : shards(new std::atomic<Shard *>[numShards]), numShards(numShards) {
109 assert(llvm::isPowerOf2_64(numShards) &&
110 "the number of shards is required to be a power of 2");
111 for (size_t i = 0; i < numShards; i++)
112 shards[i].store(nullptr, std::memory_order_relaxed);
113 }
~ParametricStorageUniquer()114 ~ParametricStorageUniquer() {
115 // Free all of the allocated shards.
116 for (size_t i = 0; i != numShards; ++i)
117 if (Shard *shard = shards[i].load())
118 delete shard;
119 }
120 /// Get or create an instance of a parametric type.
121 BaseStorage *
getOrCreate(bool threadingIsEnabled,unsigned hashValue,function_ref<bool (const BaseStorage *)> isEqual,function_ref<BaseStorage * (StorageAllocator &)> ctorFn)122 getOrCreate(bool threadingIsEnabled, unsigned hashValue,
123 function_ref<bool(const BaseStorage *)> isEqual,
124 function_ref<BaseStorage *(StorageAllocator &)> ctorFn) {
125 Shard &shard = getShard(hashValue);
126 ParametricStorageUniquer::LookupKey lookupKey{hashValue, isEqual};
127 if (!threadingIsEnabled)
128 return getOrCreateUnsafe(shard, lookupKey, ctorFn);
129
130 // Check for a instance of this object in the local cache.
131 auto localIt = localCache->insert_as({hashValue}, lookupKey);
132 BaseStorage *&localInst = localIt.first->storage;
133 if (localInst)
134 return localInst;
135
136 // Check for an existing instance in read-only mode.
137 {
138 llvm::sys::SmartScopedReader<true> typeLock(shard.mutex);
139 auto it = shard.instances.find_as(lookupKey);
140 if (it != shard.instances.end())
141 return localInst = it->storage;
142 }
143
144 // Acquire a writer-lock so that we can safely create the new storage
145 // instance.
146 llvm::sys::SmartScopedWriter<true> typeLock(shard.mutex);
147 return localInst = getOrCreateUnsafe(shard, lookupKey, ctorFn);
148 }
149 /// Run a mutation function on the provided storage object in a thread-safe
150 /// way.
151 LogicalResult
mutate(bool threadingIsEnabled,BaseStorage * storage,function_ref<LogicalResult (StorageAllocator &)> mutationFn)152 mutate(bool threadingIsEnabled, BaseStorage *storage,
153 function_ref<LogicalResult(StorageAllocator &)> mutationFn) {
154 Shard &shard = getShardFor(storage);
155 if (!threadingIsEnabled)
156 return mutationFn(shard.allocator);
157
158 llvm::sys::SmartScopedWriter<true> lock(shard.mutex);
159 return mutationFn(shard.allocator);
160 }
161
162 private:
163 /// Return the shard used for the given hash value.
getShard(unsigned hashValue)164 Shard &getShard(unsigned hashValue) {
165 // Get a shard number from the provided hashvalue.
166 unsigned shardNum = hashValue & (numShards - 1);
167
168 // Try to acquire an already initialized shard.
169 Shard *shard = shards[shardNum].load(std::memory_order_acquire);
170 if (shard)
171 return *shard;
172
173 // Otherwise, try to allocate a new shard.
174 Shard *newShard = new Shard();
175 if (shards[shardNum].compare_exchange_strong(shard, newShard))
176 return *newShard;
177
178 // If one was allocated before we can initialize ours, delete ours.
179 delete newShard;
180 return *shard;
181 }
182
183 /// Return the shard that allocated the provided storage object.
getShardFor(BaseStorage * storage)184 Shard &getShardFor(BaseStorage *storage) {
185 for (size_t i = 0; i != numShards; ++i) {
186 if (Shard *shard = shards[i].load(std::memory_order_acquire)) {
187 llvm::sys::SmartScopedReader<true> lock(shard->mutex);
188 if (shard->allocator.allocated(storage))
189 return *shard;
190 }
191 }
192 llvm_unreachable("expected storage object to have a valid 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 #else
208 /// If multi-threading is disabled, ignore the shard parameter as we will
209 /// always use one shard.
210 ParametricStorageUniquer(size_t numShards = 0) {}
211
212 /// Get or create an instance of a parametric type.
213 BaseStorage *
214 getOrCreate(bool threadingIsEnabled, unsigned hashValue,
215 function_ref<bool(const BaseStorage *)> isEqual,
216 function_ref<BaseStorage *(StorageAllocator &)> ctorFn) {
217 ParametricStorageUniquer::LookupKey lookupKey{hashValue, isEqual};
218 return getOrCreateUnsafe(shard, lookupKey, ctorFn);
219 }
220 /// Run a mutation function on the provided storage object in a thread-safe
221 /// way.
222 LogicalResult
223 mutate(bool threadingIsEnabled, BaseStorage *storage,
224 function_ref<LogicalResult(StorageAllocator &)> mutationFn) {
225 return mutationFn(shard.allocator);
226 }
227
228 private:
229 /// The main uniquer shard that is used for allocating storage instances.
230 Shard shard;
231 #endif
232 };
233 } // end anonymous namespace
234
235 namespace mlir {
236 namespace detail {
237 /// This is the implementation of the StorageUniquer class.
238 struct StorageUniquerImpl {
239 using BaseStorage = StorageUniquer::BaseStorage;
240 using StorageAllocator = StorageUniquer::StorageAllocator;
241
242 //===--------------------------------------------------------------------===//
243 // Parametric Storage
244 //===--------------------------------------------------------------------===//
245
246 /// Check if an instance of a parametric storage class exists.
hasParametricStoragemlir::detail::StorageUniquerImpl247 bool hasParametricStorage(TypeID id) { return parametricUniquers.count(id); }
248
249 /// Get or create an instance of a parametric type.
250 BaseStorage *
getOrCreatemlir::detail::StorageUniquerImpl251 getOrCreate(TypeID id, unsigned hashValue,
252 function_ref<bool(const BaseStorage *)> isEqual,
253 function_ref<BaseStorage *(StorageAllocator &)> ctorFn) {
254 assert(parametricUniquers.count(id) &&
255 "creating unregistered storage instance");
256 ParametricStorageUniquer &storageUniquer = *parametricUniquers[id];
257 return storageUniquer.getOrCreate(threadingIsEnabled, hashValue, isEqual,
258 ctorFn);
259 }
260
261 /// Run a mutation function on the provided storage object in a thread-safe
262 /// way.
263 LogicalResult
mutatemlir::detail::StorageUniquerImpl264 mutate(TypeID id, BaseStorage *storage,
265 function_ref<LogicalResult(StorageAllocator &)> mutationFn) {
266 assert(parametricUniquers.count(id) &&
267 "mutating unregistered storage instance");
268 ParametricStorageUniquer &storageUniquer = *parametricUniquers[id];
269 return storageUniquer.mutate(threadingIsEnabled, storage, mutationFn);
270 }
271
272 //===--------------------------------------------------------------------===//
273 // Singleton Storage
274 //===--------------------------------------------------------------------===//
275
276 /// Get or create an instance of a singleton storage class.
getSingletonmlir::detail::StorageUniquerImpl277 BaseStorage *getSingleton(TypeID id) {
278 BaseStorage *singletonInstance = singletonInstances[id];
279 assert(singletonInstance && "expected singleton instance to exist");
280 return singletonInstance;
281 }
282
283 /// Check if an instance of a singleton storage class exists.
hasSingletonmlir::detail::StorageUniquerImpl284 bool hasSingleton(TypeID id) const { return singletonInstances.count(id); }
285
286 //===--------------------------------------------------------------------===//
287 // Instance Storage
288 //===--------------------------------------------------------------------===//
289
290 /// Map of type ids to the storage uniquer to use for registered objects.
291 DenseMap<TypeID, std::unique_ptr<ParametricStorageUniquer>>
292 parametricUniquers;
293
294 /// Map of type ids to a singleton instance when the storage class is a
295 /// singleton.
296 DenseMap<TypeID, BaseStorage *> singletonInstances;
297
298 /// Allocator used for uniquing singleton instances.
299 StorageAllocator singletonAllocator;
300
301 /// Flag specifying if multi-threading is enabled within the uniquer.
302 bool threadingIsEnabled = true;
303 };
304 } // end namespace detail
305 } // namespace mlir
306
StorageUniquer()307 StorageUniquer::StorageUniquer() : impl(new StorageUniquerImpl()) {}
~StorageUniquer()308 StorageUniquer::~StorageUniquer() {}
309
310 /// Set the flag specifying if multi-threading is disabled within the uniquer.
disableMultithreading(bool disable)311 void StorageUniquer::disableMultithreading(bool disable) {
312 impl->threadingIsEnabled = !disable;
313 }
314
315 /// Implementation for getting/creating an instance of a derived type with
316 /// parametric storage.
getParametricStorageTypeImpl(TypeID id,unsigned hashValue,function_ref<bool (const BaseStorage *)> isEqual,function_ref<BaseStorage * (StorageAllocator &)> ctorFn)317 auto StorageUniquer::getParametricStorageTypeImpl(
318 TypeID id, unsigned hashValue,
319 function_ref<bool(const BaseStorage *)> isEqual,
320 function_ref<BaseStorage *(StorageAllocator &)> ctorFn) -> BaseStorage * {
321 return impl->getOrCreate(id, hashValue, isEqual, ctorFn);
322 }
323
324 /// Implementation for registering an instance of a derived type with
325 /// parametric storage.
registerParametricStorageTypeImpl(TypeID id)326 void StorageUniquer::registerParametricStorageTypeImpl(TypeID id) {
327 impl->parametricUniquers.try_emplace(
328 id, std::make_unique<ParametricStorageUniquer>());
329 }
330
331 /// Implementation for getting an instance of a derived type with default
332 /// storage.
getSingletonImpl(TypeID id)333 auto StorageUniquer::getSingletonImpl(TypeID id) -> BaseStorage * {
334 return impl->getSingleton(id);
335 }
336
337 /// Test is the storage singleton is initialized.
isSingletonStorageInitialized(TypeID id)338 bool StorageUniquer::isSingletonStorageInitialized(TypeID id) {
339 return impl->hasSingleton(id);
340 }
341
342 /// Test is the parametric storage is initialized.
isParametricStorageInitialized(TypeID id)343 bool StorageUniquer::isParametricStorageInitialized(TypeID id) {
344 return impl->hasParametricStorage(id);
345 }
346
347 /// Implementation for registering an instance of a derived type with default
348 /// storage.
registerSingletonImpl(TypeID id,function_ref<BaseStorage * (StorageAllocator &)> ctorFn)349 void StorageUniquer::registerSingletonImpl(
350 TypeID id, function_ref<BaseStorage *(StorageAllocator &)> ctorFn) {
351 assert(!impl->singletonInstances.count(id) &&
352 "storage class already registered");
353 impl->singletonInstances.try_emplace(id, ctorFn(impl->singletonAllocator));
354 }
355
356 /// Implementation for mutating an instance of a derived storage.
mutateImpl(TypeID id,BaseStorage * storage,function_ref<LogicalResult (StorageAllocator &)> mutationFn)357 LogicalResult StorageUniquer::mutateImpl(
358 TypeID id, BaseStorage *storage,
359 function_ref<LogicalResult(StorageAllocator &)> mutationFn) {
360 return impl->mutate(id, storage, mutationFn);
361 }
362