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 { 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 : DenseMapInfo<HashedStorage> { 51 static HashedStorage getEmptyKey() { 52 return HashedStorage(0, DenseMapInfo<BaseStorage *>::getEmptyKey()); 53 } 54 static HashedStorage getTombstoneKey() { 55 return HashedStorage(0, DenseMapInfo<BaseStorage *>::getTombstoneKey()); 56 } 57 58 static unsigned getHashValue(const HashedStorage &key) { 59 return key.hashValue; 60 } 61 static unsigned getHashValue(LookupKey key) { return key.hashValue; } 62 63 static bool isEqual(const HashedStorage &lhs, const HashedStorage &rhs) { 64 return lhs.storage == rhs.storage; 65 } 66 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 * 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 /// Destroy all of the storage instances within the given shard. 104 void destroyShardInstances(Shard &shard) { 105 if (!destructorFn) 106 return; 107 for (HashedStorage &instance : shard.instances) 108 destructorFn(instance.storage); 109 } 110 111 public: 112 #if LLVM_ENABLE_THREADS != 0 113 /// Initialize the storage uniquer with a given number of storage shards to 114 /// use. The provided shard number is required to be a valid power of 2. The 115 /// destructor function is used to destroy any allocated storage instances. 116 ParametricStorageUniquer(function_ref<void(BaseStorage *)> destructorFn, 117 size_t numShards = 8) 118 : shards(new std::atomic<Shard *>[numShards]), numShards(numShards), 119 destructorFn(destructorFn) { 120 assert(llvm::isPowerOf2_64(numShards) && 121 "the number of shards is required to be a power of 2"); 122 for (size_t i = 0; i < numShards; i++) 123 shards[i].store(nullptr, std::memory_order_relaxed); 124 } 125 ~ParametricStorageUniquer() { 126 // Free all of the allocated shards. 127 for (size_t i = 0; i != numShards; ++i) { 128 if (Shard *shard = shards[i].load()) { 129 destroyShardInstances(*shard); 130 delete shard; 131 } 132 } 133 } 134 /// Get or create an instance of a parametric type. 135 BaseStorage * 136 getOrCreate(bool threadingIsEnabled, unsigned hashValue, 137 function_ref<bool(const BaseStorage *)> isEqual, 138 function_ref<BaseStorage *(StorageAllocator &)> ctorFn) { 139 Shard &shard = getShard(hashValue); 140 ParametricStorageUniquer::LookupKey lookupKey{hashValue, isEqual}; 141 if (!threadingIsEnabled) 142 return getOrCreateUnsafe(shard, lookupKey, ctorFn); 143 144 // Check for a instance of this object in the local cache. 145 auto localIt = localCache->insert_as({hashValue}, lookupKey); 146 BaseStorage *&localInst = localIt.first->storage; 147 if (localInst) 148 return localInst; 149 150 // Check for an existing instance in read-only mode. 151 { 152 llvm::sys::SmartScopedReader<true> typeLock(shard.mutex); 153 auto it = shard.instances.find_as(lookupKey); 154 if (it != shard.instances.end()) 155 return localInst = it->storage; 156 } 157 158 // Acquire a writer-lock so that we can safely create the new storage 159 // instance. 160 llvm::sys::SmartScopedWriter<true> typeLock(shard.mutex); 161 return localInst = getOrCreateUnsafe(shard, lookupKey, ctorFn); 162 } 163 /// Run a mutation function on the provided storage object in a thread-safe 164 /// way. 165 LogicalResult 166 mutate(bool threadingIsEnabled, BaseStorage *storage, 167 function_ref<LogicalResult(StorageAllocator &)> mutationFn) { 168 Shard &shard = getShardFor(storage); 169 if (!threadingIsEnabled) 170 return mutationFn(shard.allocator); 171 172 llvm::sys::SmartScopedWriter<true> lock(shard.mutex); 173 return mutationFn(shard.allocator); 174 } 175 176 private: 177 /// Return the shard used for the given hash value. 178 Shard &getShard(unsigned hashValue) { 179 // Get a shard number from the provided hashvalue. 180 unsigned shardNum = hashValue & (numShards - 1); 181 182 // Try to acquire an already initialized shard. 183 Shard *shard = shards[shardNum].load(std::memory_order_acquire); 184 if (shard) 185 return *shard; 186 187 // Otherwise, try to allocate a new shard. 188 Shard *newShard = new Shard(); 189 if (shards[shardNum].compare_exchange_strong(shard, newShard)) 190 return *newShard; 191 192 // If one was allocated before we can initialize ours, delete ours. 193 delete newShard; 194 return *shard; 195 } 196 197 /// Return the shard that allocated the provided storage object. 198 Shard &getShardFor(BaseStorage *storage) { 199 for (size_t i = 0; i != numShards; ++i) { 200 if (Shard *shard = shards[i].load(std::memory_order_acquire)) { 201 llvm::sys::SmartScopedReader<true> lock(shard->mutex); 202 if (shard->allocator.allocated(storage)) 203 return *shard; 204 } 205 } 206 llvm_unreachable("expected storage object to have a valid shard"); 207 } 208 209 /// A thread local cache for storage objects. This helps to reduce the lock 210 /// contention when an object already existing in the cache. 211 ThreadLocalCache<StorageTypeSet> localCache; 212 213 /// A set of uniquer shards to allow for further bucketing accesses for 214 /// instances of this storage type. Each shard is lazily initialized to reduce 215 /// the overhead when only a small amount of shards are in use. 216 std::unique_ptr<std::atomic<Shard *>[]> shards; 217 218 /// The number of available shards. 219 size_t numShards; 220 221 /// Function to used to destruct any allocated storage instances. 222 function_ref<void(BaseStorage *)> destructorFn; 223 224 #else 225 /// If multi-threading is disabled, ignore the shard parameter as we will 226 /// always use one shard. The destructor function is used to destroy any 227 /// allocated storage instances. 228 ParametricStorageUniquer(function_ref<void(BaseStorage *)> destructorFn, 229 size_t numShards = 0) 230 : destructorFn(destructorFn) {} 231 ~ParametricStorageUniquer() { destroyShardInstances(shard); } 232 233 /// Get or create an instance of a parametric type. 234 BaseStorage * 235 getOrCreate(bool threadingIsEnabled, unsigned hashValue, 236 function_ref<bool(const BaseStorage *)> isEqual, 237 function_ref<BaseStorage *(StorageAllocator &)> ctorFn) { 238 ParametricStorageUniquer::LookupKey lookupKey{hashValue, isEqual}; 239 return getOrCreateUnsafe(shard, lookupKey, ctorFn); 240 } 241 /// Run a mutation function on the provided storage object in a thread-safe 242 /// way. 243 LogicalResult 244 mutate(bool threadingIsEnabled, BaseStorage *storage, 245 function_ref<LogicalResult(StorageAllocator &)> mutationFn) { 246 return mutationFn(shard.allocator); 247 } 248 249 private: 250 /// The main uniquer shard that is used for allocating storage instances. 251 Shard shard; 252 253 /// Function to used to destruct any allocated storage instances. 254 function_ref<void(BaseStorage *)> destructorFn; 255 #endif 256 }; 257 } // end anonymous namespace 258 259 namespace mlir { 260 namespace detail { 261 /// This is the implementation of the StorageUniquer class. 262 struct StorageUniquerImpl { 263 using BaseStorage = StorageUniquer::BaseStorage; 264 using StorageAllocator = StorageUniquer::StorageAllocator; 265 266 //===--------------------------------------------------------------------===// 267 // Parametric Storage 268 //===--------------------------------------------------------------------===// 269 270 /// Check if an instance of a parametric storage class exists. 271 bool hasParametricStorage(TypeID id) { return parametricUniquers.count(id); } 272 273 /// Get or create an instance of a parametric type. 274 BaseStorage * 275 getOrCreate(TypeID id, unsigned hashValue, 276 function_ref<bool(const BaseStorage *)> isEqual, 277 function_ref<BaseStorage *(StorageAllocator &)> ctorFn) { 278 assert(parametricUniquers.count(id) && 279 "creating unregistered storage instance"); 280 ParametricStorageUniquer &storageUniquer = *parametricUniquers[id]; 281 return storageUniquer.getOrCreate(threadingIsEnabled, hashValue, isEqual, 282 ctorFn); 283 } 284 285 /// Run a mutation function on the provided storage object in a thread-safe 286 /// way. 287 LogicalResult 288 mutate(TypeID id, BaseStorage *storage, 289 function_ref<LogicalResult(StorageAllocator &)> mutationFn) { 290 assert(parametricUniquers.count(id) && 291 "mutating unregistered storage instance"); 292 ParametricStorageUniquer &storageUniquer = *parametricUniquers[id]; 293 return storageUniquer.mutate(threadingIsEnabled, storage, mutationFn); 294 } 295 296 //===--------------------------------------------------------------------===// 297 // Singleton Storage 298 //===--------------------------------------------------------------------===// 299 300 /// Get or create an instance of a singleton storage class. 301 BaseStorage *getSingleton(TypeID id) { 302 BaseStorage *singletonInstance = singletonInstances[id]; 303 assert(singletonInstance && "expected singleton instance to exist"); 304 return singletonInstance; 305 } 306 307 /// Check if an instance of a singleton storage class exists. 308 bool hasSingleton(TypeID id) const { return singletonInstances.count(id); } 309 310 //===--------------------------------------------------------------------===// 311 // Instance Storage 312 //===--------------------------------------------------------------------===// 313 314 /// Map of type ids to the storage uniquer to use for registered objects. 315 DenseMap<TypeID, std::unique_ptr<ParametricStorageUniquer>> 316 parametricUniquers; 317 318 /// Map of type ids to a singleton instance when the storage class is a 319 /// singleton. 320 DenseMap<TypeID, BaseStorage *> singletonInstances; 321 322 /// Allocator used for uniquing singleton instances. 323 StorageAllocator singletonAllocator; 324 325 /// Flag specifying if multi-threading is enabled within the uniquer. 326 bool threadingIsEnabled = true; 327 }; 328 } // end namespace detail 329 } // namespace mlir 330 331 StorageUniquer::StorageUniquer() : impl(new StorageUniquerImpl()) {} 332 StorageUniquer::~StorageUniquer() {} 333 334 /// Set the flag specifying if multi-threading is disabled within the uniquer. 335 void StorageUniquer::disableMultithreading(bool disable) { 336 impl->threadingIsEnabled = !disable; 337 } 338 339 /// Implementation for getting/creating an instance of a derived type with 340 /// parametric storage. 341 auto StorageUniquer::getParametricStorageTypeImpl( 342 TypeID id, unsigned hashValue, 343 function_ref<bool(const BaseStorage *)> isEqual, 344 function_ref<BaseStorage *(StorageAllocator &)> ctorFn) -> BaseStorage * { 345 return impl->getOrCreate(id, hashValue, isEqual, ctorFn); 346 } 347 348 /// Implementation for registering an instance of a derived type with 349 /// parametric storage. 350 void StorageUniquer::registerParametricStorageTypeImpl( 351 TypeID id, function_ref<void(BaseStorage *)> destructorFn) { 352 impl->parametricUniquers.try_emplace( 353 id, std::make_unique<ParametricStorageUniquer>(destructorFn)); 354 } 355 356 /// Implementation for getting an instance of a derived type with default 357 /// storage. 358 auto StorageUniquer::getSingletonImpl(TypeID id) -> BaseStorage * { 359 return impl->getSingleton(id); 360 } 361 362 /// Test is the storage singleton is initialized. 363 bool StorageUniquer::isSingletonStorageInitialized(TypeID id) { 364 return impl->hasSingleton(id); 365 } 366 367 /// Test is the parametric storage is initialized. 368 bool StorageUniquer::isParametricStorageInitialized(TypeID id) { 369 return impl->hasParametricStorage(id); 370 } 371 372 /// Implementation for registering an instance of a derived type with default 373 /// storage. 374 void StorageUniquer::registerSingletonImpl( 375 TypeID id, function_ref<BaseStorage *(StorageAllocator &)> ctorFn) { 376 assert(!impl->singletonInstances.count(id) && 377 "storage class already registered"); 378 impl->singletonInstances.try_emplace(id, ctorFn(impl->singletonAllocator)); 379 } 380 381 /// Implementation for mutating an instance of a derived storage. 382 LogicalResult StorageUniquer::mutateImpl( 383 TypeID id, BaseStorage *storage, 384 function_ref<LogicalResult(StorageAllocator &)> mutationFn) { 385 return impl->mutate(id, storage, mutationFn); 386 } 387