Skip to content

Commit c0f5473

Browse files
author
git apple-llvm automerger
committed
Merge commit 'f3df3b58e7dd' from llvm.org/master into apple/main
2 parents 78e4313 + f3df3b5 commit c0f5473

File tree

4 files changed

+166
-77
lines changed

4 files changed

+166
-77
lines changed

mlir/include/mlir/Support/StorageUniquer.h

Lines changed: 0 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -231,28 +231,6 @@ class StorageUniquer {
231231
return mutateImpl(id, mutationFn);
232232
}
233233

234-
/// Erases a uniqued instance of 'Storage'. This function is used for derived
235-
/// types that have complex storage or uniquing constraints.
236-
template <typename Storage, typename Arg, typename... Args>
237-
void erase(TypeID id, Arg &&arg, Args &&...args) {
238-
// Construct a value of the derived key type.
239-
auto derivedKey =
240-
getKey<Storage>(std::forward<Arg>(arg), std::forward<Args>(args)...);
241-
242-
// Create a hash of the derived key.
243-
unsigned hashValue = getHash<Storage>(derivedKey);
244-
245-
// Generate an equality function for the derived storage.
246-
auto isEqual = [&derivedKey](const BaseStorage *existing) {
247-
return static_cast<const Storage &>(*existing) == derivedKey;
248-
};
249-
250-
// Attempt to erase the storage instance.
251-
eraseImpl(id, hashValue, isEqual, [](BaseStorage *storage) {
252-
static_cast<Storage *>(storage)->cleanup();
253-
});
254-
}
255-
256234
private:
257235
/// Implementation for getting/creating an instance of a derived type with
258236
/// parametric storage.
@@ -275,12 +253,6 @@ class StorageUniquer {
275253
registerSingletonImpl(TypeID id,
276254
function_ref<BaseStorage *(StorageAllocator &)> ctorFn);
277255

278-
/// Implementation for erasing an instance of a derived type with complex
279-
/// storage.
280-
void eraseImpl(TypeID id, unsigned hashValue,
281-
function_ref<bool(const BaseStorage *)> isEqual,
282-
function_ref<void(BaseStorage *)> cleanupFn);
283-
284256
/// Implementation for mutating an instance of a derived storage.
285257
LogicalResult
286258
mutateImpl(TypeID id,
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
//===- ThreadLocalCache.h - ThreadLocalCache class --------------*- C++ -*-===//
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+
// This file contains a definition of the ThreadLocalCache class. This class
10+
// provides support for defining thread local objects with non-static duration.
11+
//
12+
//===----------------------------------------------------------------------===//
13+
14+
#ifndef MLIR_SUPPORT_THREADLOCALCACHE_H
15+
#define MLIR_SUPPORT_THREADLOCALCACHE_H
16+
17+
#include "mlir/Support/LLVM.h"
18+
#include "llvm/ADT/DenseMap.h"
19+
#include "llvm/Support/ManagedStatic.h"
20+
#include "llvm/Support/Mutex.h"
21+
#include "llvm/Support/ThreadLocal.h"
22+
23+
namespace mlir {
24+
/// This class provides support for defining a thread local object with non
25+
/// static storage duration. This is very useful for situations in which a data
26+
/// cache has very large lock contention.
27+
template <typename ValueT>
28+
class ThreadLocalCache {
29+
/// The type used for the static thread_local cache. This is a map between an
30+
/// instance of the non-static cache and a weak reference to an instance of
31+
/// ValueT. We use a weak reference here so that the object can be destroyed
32+
/// without needing to lock access to the cache itself.
33+
struct CacheType : public llvm::SmallDenseMap<ThreadLocalCache<ValueT> *,
34+
std::weak_ptr<ValueT>> {
35+
~CacheType() {
36+
// Remove the values of this cache that haven't already expired.
37+
for (auto &it : *this)
38+
if (std::shared_ptr<ValueT> value = it.second.lock())
39+
it.first->remove(value.get());
40+
}
41+
42+
/// Clear out any unused entries within the map. This method is not
43+
/// thread-safe, and should only be called by the same thread as the cache.
44+
void clearExpiredEntries() {
45+
for (auto it = this->begin(), e = this->end(); it != e;) {
46+
auto curIt = it++;
47+
if (curIt->second.expired())
48+
this->erase(curIt);
49+
}
50+
}
51+
};
52+
53+
public:
54+
ThreadLocalCache() = default;
55+
~ThreadLocalCache() {
56+
// No cleanup is necessary here as the shared_pointer memory will go out of
57+
// scope and invalidate the weak pointers held by the thread_local caches.
58+
}
59+
60+
/// Return an instance of the value type for the current thread.
61+
ValueT &get() {
62+
// Check for an already existing instance for this thread.
63+
CacheType &staticCache = getStaticCache();
64+
std::weak_ptr<ValueT> &threadInstance = staticCache[this];
65+
if (std::shared_ptr<ValueT> value = threadInstance.lock())
66+
return *value;
67+
68+
// Otherwise, create a new instance for this thread.
69+
llvm::sys::SmartScopedLock<true> threadInstanceLock(instanceMutex);
70+
instances.push_back(std::make_shared<ValueT>());
71+
std::shared_ptr<ValueT> &instance = instances.back();
72+
threadInstance = instance;
73+
74+
// Before returning the new instance, take the chance to clear out any used
75+
// entries in the static map. The cache is only cleared within the same
76+
// thread to remove the need to lock the cache itself.
77+
staticCache.clearExpiredEntries();
78+
return *instance;
79+
}
80+
ValueT &operator*() { return get(); }
81+
ValueT *operator->() { return &get(); }
82+
83+
private:
84+
ThreadLocalCache(ThreadLocalCache &&) = delete;
85+
ThreadLocalCache(const ThreadLocalCache &) = delete;
86+
ThreadLocalCache &operator=(const ThreadLocalCache &) = delete;
87+
88+
/// Return the static thread local instance of the cache type.
89+
static CacheType &getStaticCache() {
90+
static LLVM_THREAD_LOCAL CacheType cache;
91+
return cache;
92+
}
93+
94+
/// Remove the given value entry. This is generally called when a thread local
95+
/// cache is destructing.
96+
void remove(ValueT *value) {
97+
// Erase the found value directly, because it is guaranteed to be in the
98+
// list.
99+
llvm::sys::SmartScopedLock<true> threadInstanceLock(instanceMutex);
100+
auto it = llvm::find_if(instances, [&](std::shared_ptr<ValueT> &instance) {
101+
return instance.get() == value;
102+
});
103+
assert(it != instances.end() && "expected value to exist in cache");
104+
instances.erase(it);
105+
}
106+
107+
/// Owning pointers to all of the values that have been constructed for this
108+
/// object in the static cache.
109+
SmallVector<std::shared_ptr<ValueT>, 1> instances;
110+
111+
/// A mutex used when a new thread instance has been added to the cache for
112+
/// this object.
113+
llvm::sys::SmartMutex<true> instanceMutex;
114+
};
115+
} // end namespace mlir
116+
117+
#endif // MLIR_SUPPORT_THREADLOCALCACHE_H

mlir/lib/IR/MLIRContext.cpp

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include "mlir/IR/Location.h"
2525
#include "mlir/IR/Module.h"
2626
#include "mlir/IR/Types.h"
27+
#include "mlir/Support/ThreadLocalCache.h"
2728
#include "llvm/ADT/DenseMap.h"
2829
#include "llvm/ADT/DenseSet.h"
2930
#include "llvm/ADT/SetVector.h"
@@ -291,8 +292,12 @@ class MLIRContextImpl {
291292
/// operations.
292293
llvm::StringMap<AbstractOperation> registeredOperations;
293294

294-
/// These are identifiers uniqued into this MLIRContext.
295+
/// Identifers are uniqued by string value and use the internal string set for
296+
/// storage.
295297
llvm::StringSet<llvm::BumpPtrAllocator &> identifiers;
298+
/// A thread local cache of identifiers to reduce lock contention.
299+
ThreadLocalCache<llvm::StringMap<llvm::StringMapEntry<llvm::NoneType> *>>
300+
localIdentifierCache;
296301

297302
/// An allocator used for AbstractAttribute and AbstractType objects.
298303
llvm::BumpPtrAllocator abstractDialectSymbolAllocator;
@@ -703,27 +708,37 @@ const AbstractType &AbstractType::lookup(TypeID typeID, MLIRContext *context) {
703708

704709
/// Return an identifier for the specified string.
705710
Identifier Identifier::get(StringRef str, MLIRContext *context) {
706-
auto &impl = context->getImpl();
707-
708-
// Check for an existing identifier in read-only mode.
709-
if (context->isMultithreadingEnabled()) {
710-
llvm::sys::SmartScopedReader<true> contextLock(impl.identifierMutex);
711-
auto it = impl.identifiers.find(str);
712-
if (it != impl.identifiers.end())
713-
return Identifier(&*it);
714-
}
715-
716711
// Check invariants after seeing if we already have something in the
717712
// identifier table - if we already had it in the table, then it already
718713
// passed invariant checks.
719714
assert(!str.empty() && "Cannot create an empty identifier");
720715
assert(str.find('\0') == StringRef::npos &&
721716
"Cannot create an identifier with a nul character");
722717

718+
auto &impl = context->getImpl();
719+
if (!context->isMultithreadingEnabled())
720+
return Identifier(&*impl.identifiers.insert(str).first);
721+
722+
// Check for an existing instance in the local cache.
723+
auto *&localEntry = (*impl.localIdentifierCache)[str];
724+
if (localEntry)
725+
return Identifier(localEntry);
726+
727+
// Check for an existing identifier in read-only mode.
728+
{
729+
llvm::sys::SmartScopedReader<true> contextLock(impl.identifierMutex);
730+
auto it = impl.identifiers.find(str);
731+
if (it != impl.identifiers.end()) {
732+
localEntry = &*it;
733+
return Identifier(localEntry);
734+
}
735+
}
736+
723737
// Acquire a writer-lock so that we can safely create the new instance.
724-
ScopedWriterLock contextLock(impl.identifierMutex, impl.threadingIsEnabled);
738+
llvm::sys::SmartScopedWriter<true> contextLock(impl.identifierMutex);
725739
auto it = impl.identifiers.insert(str).first;
726-
return Identifier(&*it);
740+
localEntry = &*it;
741+
return Identifier(localEntry);
727742
}
728743

729744
//===----------------------------------------------------------------------===//

mlir/lib/Support/StorageUniquer.cpp

Lines changed: 21 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include "mlir/Support/StorageUniquer.h"
1010

1111
#include "mlir/Support/LLVM.h"
12+
#include "mlir/Support/ThreadLocalCache.h"
1213
#include "mlir/Support/TypeID.h"
1314
#include "llvm/Support/RWMutex.h"
1415

@@ -37,17 +38,19 @@ struct ParametricStorageUniquer {
3738
/// A utility wrapper object representing a hashed storage object. This class
3839
/// contains a storage object and an existing computed hash value.
3940
struct HashedStorage {
41+
HashedStorage(unsigned hashValue = 0, BaseStorage *storage = nullptr)
42+
: hashValue(hashValue), storage(storage) {}
4043
unsigned hashValue;
4144
BaseStorage *storage;
4245
};
4346

4447
/// Storage info for derived TypeStorage objects.
4548
struct StorageKeyInfo : DenseMapInfo<HashedStorage> {
4649
static HashedStorage getEmptyKey() {
47-
return HashedStorage{0, DenseMapInfo<BaseStorage *>::getEmptyKey()};
50+
return HashedStorage(0, DenseMapInfo<BaseStorage *>::getEmptyKey());
4851
}
4952
static HashedStorage getTombstoneKey() {
50-
return HashedStorage{0, DenseMapInfo<BaseStorage *>::getTombstoneKey()};
53+
return HashedStorage(0, DenseMapInfo<BaseStorage *>::getTombstoneKey());
5154
}
5255

5356
static unsigned getHashValue(const HashedStorage &key) {
@@ -70,6 +73,10 @@ struct ParametricStorageUniquer {
7073
using StorageTypeSet = DenseSet<HashedStorage, StorageKeyInfo>;
7174
StorageTypeSet instances;
7275

76+
/// A thread local cache for storage objects. This helps to reduce the lock
77+
/// contention when an object already existing in the cache.
78+
ThreadLocalCache<StorageTypeSet> localCache;
79+
7380
/// Allocator to use when constructing derived instances.
7481
StorageAllocator allocator;
7582

@@ -104,56 +111,42 @@ struct StorageUniquerImpl {
104111
if (!threadingIsEnabled)
105112
return getOrCreateUnsafe(storageUniquer, lookupKey, ctorFn);
106113

114+
// Check for a instance of this object in the local cache.
115+
auto localIt = storageUniquer.localCache->insert_as({hashValue}, lookupKey);
116+
BaseStorage *&localInst = localIt.first->storage;
117+
if (localInst)
118+
return localInst;
119+
107120
// Check for an existing instance in read-only mode.
108121
{
109122
llvm::sys::SmartScopedReader<true> typeLock(storageUniquer.mutex);
110123
auto it = storageUniquer.instances.find_as(lookupKey);
111124
if (it != storageUniquer.instances.end())
112-
return it->storage;
125+
return localInst = it->storage;
113126
}
114127

115128
// Acquire a writer-lock so that we can safely create the new type instance.
116129
llvm::sys::SmartScopedWriter<true> typeLock(storageUniquer.mutex);
117-
return getOrCreateUnsafe(storageUniquer, lookupKey, ctorFn);
130+
return localInst = getOrCreateUnsafe(storageUniquer, lookupKey, ctorFn);
118131
}
119-
/// Get or create an instance of a complex derived type in an thread-unsafe
132+
/// Get or create an instance of a param derived type in an thread-unsafe
120133
/// fashion.
121134
BaseStorage *
122135
getOrCreateUnsafe(ParametricStorageUniquer &storageUniquer,
123-
ParametricStorageUniquer::LookupKey &lookupKey,
136+
ParametricStorageUniquer::LookupKey &key,
124137
function_ref<BaseStorage *(StorageAllocator &)> ctorFn) {
125-
auto existing = storageUniquer.instances.insert_as({}, lookupKey);
138+
auto existing = storageUniquer.instances.insert_as({key.hashValue}, key);
126139
if (!existing.second)
127140
return existing.first->storage;
128141

129142
// Otherwise, construct and initialize the derived storage for this type
130143
// instance.
131144
BaseStorage *storage = ctorFn(storageUniquer.allocator);
132145
*existing.first =
133-
ParametricStorageUniquer::HashedStorage{lookupKey.hashValue, storage};
146+
ParametricStorageUniquer::HashedStorage{key.hashValue, storage};
134147
return storage;
135148
}
136149

137-
/// Erase an instance of a parametric derived type.
138-
void erase(TypeID id, unsigned hashValue,
139-
function_ref<bool(const BaseStorage *)> isEqual,
140-
function_ref<void(BaseStorage *)> cleanupFn) {
141-
assert(parametricUniquers.count(id) &&
142-
"erasing unregistered storage instance");
143-
ParametricStorageUniquer &storageUniquer = *parametricUniquers[id];
144-
ParametricStorageUniquer::LookupKey lookupKey{hashValue, isEqual};
145-
146-
// Acquire a writer-lock so that we can safely erase the type instance.
147-
llvm::sys::SmartScopedWriter<true> lock(storageUniquer.mutex);
148-
auto existing = storageUniquer.instances.find_as(lookupKey);
149-
if (existing == storageUniquer.instances.end())
150-
return;
151-
152-
// Cleanup the storage and remove it from the map.
153-
cleanupFn(existing->storage);
154-
storageUniquer.instances.erase(existing);
155-
}
156-
157150
/// Mutates an instance of a derived storage in a thread-safe way.
158151
LogicalResult
159152
mutate(TypeID id,
@@ -252,14 +245,6 @@ void StorageUniquer::registerSingletonImpl(
252245
impl->singletonInstances.try_emplace(id, ctorFn(impl->singletonAllocator));
253246
}
254247

255-
/// Implementation for erasing an instance of a derived type with parametric
256-
/// storage.
257-
void StorageUniquer::eraseImpl(TypeID id, unsigned hashValue,
258-
function_ref<bool(const BaseStorage *)> isEqual,
259-
function_ref<void(BaseStorage *)> cleanupFn) {
260-
impl->erase(id, hashValue, isEqual, cleanupFn);
261-
}
262-
263248
/// Implementation for mutating an instance of a derived storage.
264249
LogicalResult StorageUniquer::mutateImpl(
265250
TypeID id, function_ref<LogicalResult(StorageAllocator &)> mutationFn) {

0 commit comments

Comments
 (0)