MLIR 24.0.0git
StorageUniquer.h
Go to the documentation of this file.
1//===- StorageUniquer.h - Common Storage Class Uniquer ----------*- 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#ifndef MLIR_SUPPORT_STORAGEUNIQUER_H
10#define MLIR_SUPPORT_STORAGEUNIQUER_H
11
12#include "mlir/Support/LLVM.h"
13#include "mlir/Support/TypeID.h"
14#include "llvm/ADT/ArrayRef.h"
15#include "llvm/ADT/DenseSet.h"
16#include "llvm/ADT/StringRef.h"
17#include "llvm/Support/Allocator.h"
18#include <utility>
19
20namespace mlir {
21namespace detail {
23
24/// Trait to check if ImplTy provides a 'getKey' method with types 'Args'.
25template <typename ImplTy, typename... Args>
26using has_impltype_getkey_t = decltype(ImplTy::getKey(std::declval<Args>()...));
27
28/// Trait to check if ImplTy provides a 'hashKey' method for 'T'.
29template <typename ImplTy, typename T>
30using has_impltype_hash_t = decltype(ImplTy::hashKey(std::declval<T>()));
31} // namespace detail
32
33/// A utility class to get or create instances of "storage classes". These
34/// storage classes must derive from 'StorageUniquer::BaseStorage'.
35///
36/// For non-parametric storage classes, i.e. singleton classes, nothing else is
37/// needed. Instances of these classes can be created by calling `get` without
38/// trailing arguments.
39///
40/// Otherwise, the parametric storage classes may be created with `get`,
41/// and must respect the following:
42/// - Define a type alias, KeyTy, to a type that uniquely identifies the
43/// instance of the storage class.
44/// * The key type must be constructible from the values passed into the
45/// getComplex call.
46/// * If the KeyTy does not have an llvm::DenseMapInfo specialization, the
47/// storage class must define a hashing method:
48/// 'static unsigned hashKey(const KeyTy &)'
49///
50/// - Provide a method, 'bool operator==(const KeyTy &) const', to
51/// compare the storage instance against an instance of the key type.
52///
53/// - Provide a static construction method:
54/// 'DerivedStorage *construct(StorageAllocator &, const KeyTy &key)'
55/// that builds a unique instance of the derived storage. The arguments to
56/// this function are an allocator to store any uniqued data and the key
57/// type for this storage.
58///
59/// - Provide a cleanup method:
60/// 'void cleanup()'
61/// that is called when erasing a storage instance. This should cleanup any
62/// fields of the storage as necessary and not attempt to free the memory
63/// of the storage itself.
64///
65/// Storage classes may have an optional mutable component, which must not take
66/// part in the unique immutable key. In this case, storage classes may be
67/// mutated with `mutate` and must additionally respect the following:
68/// - Provide a mutation method:
69/// 'LogicalResult mutate(StorageAllocator &, <...>)'
70/// that is called when mutating a storage instance. The first argument is
71/// an allocator to store any mutable data, and the remaining arguments are
72/// forwarded from the call site. The storage can be mutated at any time
73/// after creation. Care must be taken to avoid excessive mutation since
74/// the allocated storage can keep containing previous states. The return
75/// value of the function is used to indicate whether the mutation was
76/// successful, e.g., to limit the number of mutations or enable deferred
77/// one-time assignment of the mutable component.
78///
79/// All storage classes must be registered with the uniquer via
80/// `registerParametricStorageType` or `registerSingletonStorageType`
81/// using an appropriate unique `TypeID` for the storage class.
83public:
84 /// This class acts as the base storage that all storage classes must derived
85 /// from.
86 class alignas(8) BaseStorage {
87 protected:
88 BaseStorage() = default;
89 };
90
91 /// This is a utility allocator used to allocate memory for instances of
92 /// derived types.
94 public:
95 /// Copy the specified array of elements into memory managed by our bump
96 /// pointer allocator. This assumes the elements are all PODs.
97 template <typename T>
99 if (elements.empty())
100 return {};
101 auto result = allocator.Allocate<T>(elements.size());
102 llvm::uninitialized_copy(elements, result);
103 return ArrayRef<T>(result, elements.size());
104 }
105
106 /// Copy the provided string into memory managed by our bump pointer
107 /// allocator.
108 StringRef copyInto(StringRef str) {
109 if (str.empty())
110 return StringRef();
111
112 char *result = allocator.Allocate<char>(str.size() + 1);
113 llvm::uninitialized_copy(str, result);
114 result[str.size()] = 0;
115 return StringRef(result, str.size());
116 }
117
118 /// Allocate an instance of the provided type.
119 template <typename T>
120 T *allocate() {
121 return allocator.Allocate<T>();
122 }
123
124 /// Allocate 'size' bytes of 'alignment' aligned memory.
125 void *allocate(size_t size, size_t alignment) {
126 return allocator.Allocate(size, alignment);
127 }
128
129 /// Returns true if this allocator allocated the provided object pointer.
130 bool allocated(const void *ptr) {
131 return allocator.identifyObject(ptr).has_value();
132 }
133
134 private:
135 /// The raw allocator for type storage objects.
136 llvm::BumpPtrAllocator allocator;
137 };
138
141
142 /// Set the flag specifying if multi-threading is disabled within the uniquer.
143 void disableMultithreading(bool disable = true);
144
145 /// Register a new parametric storage class, this is necessary to create
146 /// instances of this class type. `id` is the type identifier that will be
147 /// used to identify this type when creating instances of it via 'get'.
148 template <typename Storage>
150 // If the storage is trivially destructible, we don't need a destructor
151 // function.
152 if constexpr (std::is_trivially_destructible_v<Storage>)
153 return registerParametricStorageTypeImpl(id, nullptr);
154 registerParametricStorageTypeImpl(id, [](BaseStorage *storage) {
155 static_cast<Storage *>(storage)->~Storage();
156 });
157 }
158 /// Utility override when the storage type represents the type id.
159 template <typename Storage>
163 /// Register a new singleton storage class, this is necessary to get the
164 /// singletone instance. `id` is the type identifier that will be used to
165 /// access the singleton instance via 'get'. An optional initialization
166 /// function may also be provided to initialize the newly created storage
167 /// instance, and used when the singleton instance is created.
168 template <typename Storage>
170 function_ref<void(Storage *)> initFn) {
171 auto ctorFn = [&](StorageAllocator &allocator) {
172 auto *storage = new (allocator.allocate<Storage>()) Storage();
173 if (initFn)
174 initFn(storage);
175 return storage;
176 };
177 registerSingletonImpl(id, ctorFn);
178 }
179 template <typename Storage>
183 /// Utility override when the storage type represents the type id.
184 template <typename Storage>
188
189 /// Gets a uniqued instance of 'Storage'. 'id' is the type id used when
190 /// registering the storage instance. 'initFn' is an optional parameter that
191 /// can be used to initialize a newly inserted storage instance. This function
192 /// is used for derived types that have complex storage or uniquing
193 /// constraints.
194 template <typename Storage, typename... Args>
195 Storage *get(function_ref<void(Storage *)> initFn, TypeID id,
196 Args &&...args) {
197 // Construct a value of the derived key type.
198 auto derivedKey = getKey<Storage>(std::forward<Args>(args)...);
199
200 // Create a hash of the derived key.
201 unsigned hashValue = getHash<Storage>(derivedKey);
202
203 // Generate an equality function for the derived storage.
204 auto isEqual = [&derivedKey](const BaseStorage *existing) {
205 return static_cast<const Storage &>(*existing) == derivedKey;
206 };
207
208 // Generate a constructor function for the derived storage.
209 auto ctorFn = [&](StorageAllocator &allocator) {
210 auto *storage = Storage::construct(allocator, std::move(derivedKey));
211 if (initFn)
212 initFn(storage);
213 return storage;
214 };
215
216 // Get an instance for the derived storage.
217 return static_cast<Storage *>(
218 getParametricStorageTypeImpl(id, hashValue, isEqual, ctorFn));
219 }
220 /// Utility override when the storage type represents the type id.
221 template <typename Storage, typename... Args>
222 Storage *get(function_ref<void(Storage *)> initFn, Args &&...args) {
223 return get<Storage>(initFn, TypeID::get<Storage>(),
224 std::forward<Args>(args)...);
225 }
226
227 /// Gets a uniqued instance of 'Storage' which is a singleton storage type.
228 /// 'id' is the type id used when registering the storage instance.
229 template <typename Storage>
230 Storage *get(TypeID id) {
231 return static_cast<Storage *>(getSingletonImpl(id));
232 }
233 /// Utility override when the storage type represents the type id.
234 template <typename Storage>
235 Storage *get() {
237 }
238
239 /// Test if there is a singleton storage uniquer initialized for the provided
240 /// TypeID. This is only useful for debugging/diagnostic purpose: the uniquer
241 /// is initialized when a dialect is loaded.
243
244 /// Test if there is a parametric storage uniquer initialized for the provided
245 /// TypeID. This is only useful for debugging/diagnostic purpose: the uniquer
246 /// is initialized when a dialect is loaded.
248
249 /// Begins a transient scope. All subsequent allocations and uniquing
250 /// registrations will be recorded in a transient layer.
251 /// Precondition: The uniquer must not already be in a transient scope.
252 void beginTransientScope();
253
254 /// Ends the transient scope and resets back to the base state, freeing
255 /// all transiently allocated storage instances and clearing transient lookup
256 /// tables.
257 /// Precondition: The uniquer must be in a transient scope.
258 void endTransientScope();
259
260 /// Returns true if the uniquer is currently in a transient scope.
261 bool isInTransientScope() const;
262
263 /// Changes the mutable component of 'storage' by forwarding the trailing
264 /// arguments to the 'mutate' function of the derived class.
265 /// Note: When in a transient scope, any storage memory allocated during
266 /// mutation will come from the transient allocator. Mutating base storage
267 /// instances (allocated before the transient scope) while in a transient
268 /// scope is not supported as that transient memory will be freed on scope
269 /// exit.
270 template <typename Storage, typename... Args>
271 LogicalResult mutate(TypeID id, Storage *storage, Args &&...args) {
272 auto mutationFn = [&](StorageAllocator &allocator) -> LogicalResult {
273 return static_cast<Storage &>(*storage).mutate(
274 allocator, std::forward<Args>(args)...);
275 };
276 return mutateImpl(id, storage, mutationFn);
277 }
278
279private:
280 /// Implementation for getting/creating an instance of a derived type with
281 /// parametric storage.
282 BaseStorage *getParametricStorageTypeImpl(
283 TypeID id, unsigned hashValue,
284 function_ref<bool(const BaseStorage *)> isEqual,
285 function_ref<BaseStorage *(StorageAllocator &)> ctorFn);
286
287 /// Implementation for registering an instance of a derived type with
288 /// parametric storage. This method takes an optional destructor function that
289 /// destructs storage instances when necessary.
290 void registerParametricStorageTypeImpl(
291 TypeID id, function_ref<void(BaseStorage *)> destructorFn);
292
293 /// Implementation for getting an instance of a derived type with default
294 /// storage.
295 BaseStorage *getSingletonImpl(TypeID id);
296
297 /// Implementation for registering an instance of a derived type with default
298 /// storage.
299 void
300 registerSingletonImpl(TypeID id,
301 function_ref<BaseStorage *(StorageAllocator &)> ctorFn);
302
303 /// Implementation for mutating an instance of a derived storage.
304 LogicalResult
305 mutateImpl(TypeID id, BaseStorage *storage,
306 function_ref<LogicalResult(StorageAllocator &)> mutationFn);
307
308 /// The internal implementation class.
309 std::unique_ptr<detail::StorageUniquerImpl> impl;
310
311 //===--------------------------------------------------------------------===//
312 // Key Construction
313 //===--------------------------------------------------------------------===//
314
315 /// Used to construct an instance of 'ImplTy::KeyTy' if there is an
316 /// 'ImplTy::getKey' function for the provided arguments. Otherwise, then we
317 /// try to directly construct the 'ImplTy::KeyTy' with the provided arguments.
318 template <typename ImplTy, typename... Args>
319 static typename ImplTy::KeyTy getKey(Args &&...args) {
320 if constexpr (llvm::is_detected<detail::has_impltype_getkey_t, ImplTy,
321 Args...>::value)
322 return ImplTy::getKey(std::forward<Args>(args)...);
323 else
324 return typename ImplTy::KeyTy(std::forward<Args>(args)...);
325 }
326
327 //===--------------------------------------------------------------------===//
328 // Key Hashing
329 //===--------------------------------------------------------------------===//
330
331 /// Used to generate a hash for the `ImplTy` of a storage instance if
332 /// there is a `ImplTy::hashKey. Otherwise, if there is no `ImplTy::hashKey`
333 /// then default to using the 'llvm::DenseMapInfo' definition for
334 /// 'DerivedKey' for generating a hash.
335 template <typename ImplTy, typename DerivedKey>
336 static ::llvm::hash_code getHash(const DerivedKey &derivedKey) {
337 if constexpr (llvm::is_detected<detail::has_impltype_hash_t, ImplTy,
338 DerivedKey>::value)
339 return ImplTy::hashKey(derivedKey);
340 else
341 return DenseMapInfo<DerivedKey>::getHashValue(derivedKey);
342 }
343};
344} // namespace mlir
345
346#endif
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.
ArrayRef< T > copyInto(ArrayRef< T > elements)
Copy the specified array of elements into memory managed by our bump pointer allocator.
T * allocate()
Allocate an instance of the provided type.
StringRef copyInto(StringRef str)
Copy the provided string into memory managed by our bump pointer allocator.
void * allocate(size_t size, size_t alignment)
Allocate 'size' bytes of 'alignment' aligned memory.
bool allocated(const void *ptr)
Returns true if this allocator allocated the provided object pointer.
bool isInTransientScope() const
Returns true if the uniquer is currently in a transient scope.
Storage * get(TypeID id)
Gets a uniqued instance of 'Storage' which is a singleton storage type.
void disableMultithreading(bool disable=true)
Set the flag specifying if multi-threading is disabled within the uniquer.
LogicalResult mutate(TypeID id, Storage *storage, Args &&...args)
Changes the mutable component of 'storage' by forwarding the trailing arguments to the 'mutate' funct...
void registerSingletonStorageType(TypeID id, function_ref< void(Storage *)> initFn)
Register a new singleton storage class, this is necessary to get the singletone instance.
void beginTransientScope()
Begins a transient scope.
Storage * get(function_ref< void(Storage *)> initFn, Args &&...args)
Utility override when the storage type represents the type id.
Storage * get(function_ref< void(Storage *)> initFn, TypeID id, Args &&...args)
Gets a uniqued instance of 'Storage'.
Storage * get()
Utility override when the storage type represents the type id.
void endTransientScope()
Ends the transient scope and resets back to the base state, freeing all transiently allocated storage...
bool isSingletonStorageInitialized(TypeID id)
Test if there is a singleton storage uniquer initialized for the provided TypeID.
void registerParametricStorageType()
Utility override when the storage type represents the type id.
bool isParametricStorageInitialized(TypeID id)
Test if there is a parametric storage uniquer initialized for the provided TypeID.
void registerSingletonStorageType(function_ref< void(Storage *)> initFn={})
Utility override when the storage type represents the type id.
void registerSingletonStorageType(TypeID id)
void registerParametricStorageType(TypeID id)
Register a new parametric storage class, this is necessary to create instances of this class type.
This class provides an efficient unique identifier for a specific C++ type.
Definition TypeID.h:107
static TypeID get()
Construct a type info object for the given type T.
Definition TypeID.h:245
AttrTypeReplacer.
decltype(ImplTy::getKey(std::declval< Args >()...)) has_impltype_getkey_t
Trait to check if ImplTy provides a 'getKey' method with types 'Args'.
decltype(ImplTy::hashKey(std::declval< T >())) has_impltype_hash_t
Trait to check if ImplTy provides a 'hashKey' method for 'T'.
Include the generated interface declarations.
llvm::function_ref< Fn > function_ref
Definition LLVM.h:147
This is the implementation of the StorageUniquer class.