• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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