1 //===- AsmState.h - Assembly State Utilities --------------------*- 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 defines various classes and utilites for interacting with the MLIR
10 // assembly formats.
11 //
12 //===----------------------------------------------------------------------===//
13 
14 #ifndef MLIR_IR_ASMSTATE_H_
15 #define MLIR_IR_ASMSTATE_H_
16 
17 #include "mlir/IR/OperationSupport.h"
18 #include "mlir/Support/LLVM.h"
19 
20 #include <memory>
21 
22 namespace mlir {
23 class AsmResourcePrinter;
24 class Operation;
25 
26 namespace detail {
27 class AsmStateImpl;
28 } // namespace detail
29 
30 //===----------------------------------------------------------------------===//
31 // Resources
32 //===----------------------------------------------------------------------===//
33 
34 /// The following classes enable support for parsing and printing resources
35 /// within MLIR assembly formats. Resources are a mechanism by which dialects,
36 /// and external clients, may attach additional information when parsing or
37 /// printing IR without that information being encoded in the IR itself.
38 /// Resources are not uniqued within the MLIR context, are not attached directly
39 /// to any operation, and are solely intended to live and be processed outside
40 /// of the immediate IR.
41 ///
42 /// Resources are encoded using a key-value pair nested within dictionaries
43 /// anchored either on a dialect, or an externally registered entity.
44 /// Dictionaries anchored on dialects use the dialect namespace directly, and
45 /// dictionaries anchored on external entities use a provided unique identifier.
46 /// The resource key is an identifier used to disambiguate the data. The
47 /// resource value may be stored in various limited forms, but general encodings
48 /// use a string (human readable) or blob format (binary). Within the textual
49 /// format, an example may be of the form:
50 ///
51 /// {-#
52 ///   // The `dialect_resources` section within the file-level metadata
53 ///   // dictionary is used to contain any dialect resource entries.
54 ///   dialect_resources: {
55 ///     // Here is a dictionary anchored on "foo_dialect", which is a dialect
56 ///     // namespace.
57 ///     foo_dialect: {
58 ///       // `some_dialect_resource` is a key to be interpreted by the dialect,
59 ///       // and used to initialize/configure/etc.
60 ///       some_dialect_resource: "Some important resource value"
61 ///     }
62 ///   },
63 ///   // The `external_resources` section within the file-level metadata
64 ///   // dictionary is used to contain any non-dialect resource entries.
65 ///   external_resources: {
66 ///     // Here is a dictionary anchored on "mlir_reproducer", which is an
67 ///     // external entity representing MLIR's crash reproducer functionality.
68 ///     mlir_reproducer: {
69 ///       // `pipeline` is an entry that holds a crash reproducer pipeline
70 ///       // resource.
71 ///       pipeline: "func.func(canonicalize,cse)"
72 ///     }
73 ///   }
74 /// #-}
75 ///
76 
77 //===----------------------------------------------------------------------===//
78 // Resource Entry
79 
80 /// This class is used to build resource entries for use by the printer. Each
81 /// resource entry is represented using a key/value pair. The provided key must
82 /// be unique within the current context, which allows for a client to provide
83 /// resource entries without worrying about overlap with other clients.
84 class AsmResourceBuilder {
85 public:
86   virtual ~AsmResourceBuilder();
87 
88   /// Build a resource entry represented by the given bool.
89   virtual void buildBool(StringRef key, bool data) = 0;
90 
91   /// Build a resource entry represented by the given human-readable string
92   /// value.
93   virtual void buildString(StringRef key, StringRef data) = 0;
94 
95   /// Build an resource entry represented by the given binary blob data.
96   virtual void buildBlob(StringRef key, ArrayRef<char> data,
97                          uint32_t dataAlignment) = 0;
98   /// Build an resource entry represented by the given binary blob data. This is
99   /// a useful overload if the data type is known. Note that this does not
100   /// support `char` element types to avoid accidentally not providing the
101   /// expected alignment of data in situations that treat blobs generically.
102   template <typename T>
buildBlob(StringRef key,ArrayRef<T> data)103   std::enable_if_t<!std::is_same<T, char>::value> buildBlob(StringRef key,
104                                                             ArrayRef<T> data) {
105     buildBlob(
106         key, ArrayRef<char>((const char *)data.data(), data.size() * sizeof(T)),
107         alignof(T));
108   }
109 };
110 
111 /// This class represents a processed binary blob of data. A resource blob is
112 /// essentially a collection of data, potentially mutable, with an associated
113 /// deleter function (used if the data needs to be destroyed).
114 class AsmResourceBlob {
115 public:
116   /// A deleter function that frees a blob given the data and allocation size.
117   using DeleterFn = llvm::unique_function<void(const void *data, size_t size)>;
118 
119   AsmResourceBlob() = default;
AsmResourceBlob(ArrayRef<char> data,DeleterFn deleter,bool dataIsMutable)120   AsmResourceBlob(ArrayRef<char> data, DeleterFn deleter, bool dataIsMutable)
121       : data(data), deleter(std::move(deleter)), dataIsMutable(dataIsMutable) {}
122   /// Utility constructor that initializes a blob with a non-char type T.
123   template <typename T, typename DelT>
AsmResourceBlob(ArrayRef<T> data,DelT && deleteFn,bool dataIsMutable)124   AsmResourceBlob(ArrayRef<T> data, DelT &&deleteFn, bool dataIsMutable)
125       : data((const char *)data.data(), data.size() * sizeof(T)),
126         deleter([deleteFn = std::forward<DelT>(deleteFn)](const void *data,
127                                                           size_t size) {
128           return deleteFn((const T *)data, size);
129         }),
130         dataIsMutable(dataIsMutable) {}
131   AsmResourceBlob(AsmResourceBlob &&) = default;
132   AsmResourceBlob &operator=(AsmResourceBlob &&) = default;
133   AsmResourceBlob(const AsmResourceBlob &) = delete;
134   AsmResourceBlob &operator=(const AsmResourceBlob &) = delete;
~AsmResourceBlob()135   ~AsmResourceBlob() {
136     if (deleter)
137       deleter(data.data(), data.size());
138   }
139 
140   /// Return the raw underlying data of this blob.
getData()141   ArrayRef<char> getData() const { return data; }
142 
143   /// Return a mutable reference to the raw underlying data of this blob.
144   /// Asserts that the blob `isMutable`.
getMutableData()145   MutableArrayRef<char> getMutableData() {
146     assert(isMutable() &&
147            "cannot access mutable reference to non-mutable data");
148     return MutableArrayRef<char>(const_cast<char *>(data.data()), data.size());
149   }
150 
151   /// Return if the data of this blob is mutable.
isMutable()152   bool isMutable() const { return dataIsMutable; }
153 
154   /// Return the deleter function of this blob.
getDeleter()155   DeleterFn &getDeleter() { return deleter; }
getDeleter()156   const DeleterFn &getDeleter() const { return deleter; }
157 
158 private:
159   /// The raw, properly aligned, blob data.
160   ArrayRef<char> data;
161 
162   /// An optional deleter function used to deallocate the underlying data when
163   /// necessary.
164   DeleterFn deleter;
165 
166   /// Whether the data is mutable.
167   bool dataIsMutable;
168 };
169 
170 /// This class represents a single parsed resource entry.
171 class AsmParsedResourceEntry {
172 public:
173   virtual ~AsmParsedResourceEntry();
174 
175   /// Return the key of the resource entry.
176   virtual StringRef getKey() const = 0;
177 
178   /// Emit an error at the location of this entry.
179   virtual InFlightDiagnostic emitError() const = 0;
180 
181   /// Parse the resource entry represented by a boolean. Returns failure if the
182   /// entry does not correspond to a bool.
183   virtual FailureOr<bool> parseAsBool() const = 0;
184 
185   /// Parse the resource entry represented by a human-readable string. Returns
186   /// failure if the entry does not correspond to a string.
187   virtual FailureOr<std::string> parseAsString() const = 0;
188 
189   /// The type of an allocator function used to allocate memory for a blob when
190   /// required. The function is provided a size and alignment, and should return
191   /// an aligned allocation buffer.
192   using BlobAllocatorFn =
193       function_ref<AsmResourceBlob(unsigned size, unsigned align)>;
194 
195   /// Parse the resource entry represented by a binary blob. Returns failure if
196   /// the entry does not correspond to a blob. If the blob needed to be
197   /// allocated, the given allocator function is invoked.
198   virtual FailureOr<AsmResourceBlob>
199   parseAsBlob(BlobAllocatorFn allocator) const = 0;
200 };
201 
202 //===----------------------------------------------------------------------===//
203 // Resource Parser/Printer
204 
205 /// This class represents an instance of a resource parser. This class should be
206 /// implemented by non-dialect clients that want to inject additional resources
207 /// into MLIR assembly formats.
208 class AsmResourceParser {
209 public:
210   /// Create a new parser with the given identifying name. This name uniquely
211   /// identifies the entries of this parser, and differentiates them from other
212   /// contexts.
AsmResourceParser(StringRef name)213   AsmResourceParser(StringRef name) : name(name.str()) {}
214   virtual ~AsmResourceParser();
215 
216   /// Return the name of this parser.
getName()217   StringRef getName() const { return name; }
218 
219   /// Parse the given resource entry. Returns failure if the key/data were not
220   /// valid, or could otherwise not be processed correctly. Any necessary errors
221   /// should be emitted with the provided entry.
222   virtual LogicalResult parseResource(AsmParsedResourceEntry &entry) = 0;
223 
224   /// Return a resource parser implemented via the given callable, whose form
225   /// should match that of `parseResource` above.
226   template <typename CallableT>
fromCallable(StringRef name,CallableT && parseFn)227   static std::unique_ptr<AsmResourceParser> fromCallable(StringRef name,
228                                                          CallableT &&parseFn) {
229     struct Processor : public AsmResourceParser {
230       Processor(StringRef name, CallableT &&parseFn)
231           : AsmResourceParser(name), parseFn(std::move(parseFn)) {}
232       LogicalResult parseResource(AsmParsedResourceEntry &entry) override {
233         return parseFn(entry);
234       }
235 
236       std::decay_t<CallableT> parseFn;
237     };
238     return std::make_unique<Processor>(name, std::forward<CallableT>(parseFn));
239   }
240 
241 private:
242   std::string name;
243 };
244 
245 /// This class represents an instance of a resource printer. This class should
246 /// be implemented by non-dialect clients that want to inject additional
247 /// resources into MLIR assembly formats.
248 class AsmResourcePrinter {
249 public:
250   /// Create a new printer with the given identifying name. This name uniquely
251   /// identifies the entries of this printer, and differentiates them from
252   /// other contexts.
AsmResourcePrinter(StringRef name)253   AsmResourcePrinter(StringRef name) : name(name.str()) {}
254   virtual ~AsmResourcePrinter();
255 
256   /// Return the name of this printer.
getName()257   StringRef getName() const { return name; }
258 
259   /// Build any resources to include during printing, utilizing the given
260   /// top-level root operation to help determine what information to include.
261   /// Provided data should be registered in the form of a key/data pair, to the
262   /// given builder.
263   virtual void buildResources(Operation *op,
264                               AsmResourceBuilder &builder) const = 0;
265 
266   /// Return a resource printer implemented via the given callable, whose form
267   /// should match that of `buildResources` above.
268   template <typename CallableT>
fromCallable(StringRef name,CallableT && printFn)269   static std::unique_ptr<AsmResourcePrinter> fromCallable(StringRef name,
270                                                           CallableT &&printFn) {
271     struct Printer : public AsmResourcePrinter {
272       Printer(StringRef name, CallableT &&printFn)
273           : AsmResourcePrinter(name), printFn(std::move(printFn)) {}
274       void buildResources(Operation *op,
275                           AsmResourceBuilder &builder) const override {
276         printFn(op, builder);
277       }
278 
279       std::decay_t<CallableT> printFn;
280     };
281     return std::make_unique<Printer>(name, std::forward<CallableT>(printFn));
282   }
283 
284 private:
285   std::string name;
286 };
287 
288 //===----------------------------------------------------------------------===//
289 // ParserConfig
290 //===----------------------------------------------------------------------===//
291 
292 /// This class represents a configuration for the MLIR assembly parser. It
293 /// contains all of the necessary state to parse a MLIR source file.
294 class ParserConfig {
295 public:
ParserConfig(MLIRContext * context)296   ParserConfig(MLIRContext *context) : context(context) {
297     assert(context && "expected valid MLIR context");
298   }
299 
300   /// Return the MLIRContext to be used when parsing.
getContext()301   MLIRContext *getContext() const { return context; }
302 
303   /// Return the resource parser registered to the given name, or nullptr if no
304   /// parser with `name` is registered.
getResourceParser(StringRef name)305   AsmResourceParser *getResourceParser(StringRef name) const {
306     auto it = resourceParsers.find(name);
307     return it == resourceParsers.end() ? nullptr : it->second.get();
308   }
309 
310   /// Attach the given resource parser.
attachResourceParser(std::unique_ptr<AsmResourceParser> parser)311   void attachResourceParser(std::unique_ptr<AsmResourceParser> parser) {
312     StringRef name = parser->getName();
313     auto it = resourceParsers.try_emplace(name, std::move(parser));
314     (void)it;
315     assert(it.second &&
316            "resource parser already registered with the given name");
317   }
318 
319   /// Attach the given callable resource parser with the given name.
320   template <typename CallableT>
321   std::enable_if_t<std::is_convertible<
322       CallableT, function_ref<LogicalResult(AsmParsedResourceEntry &)>>::value>
attachResourceParser(StringRef name,CallableT && parserFn)323   attachResourceParser(StringRef name, CallableT &&parserFn) {
324     attachResourceParser(AsmResourceParser::fromCallable(
325         name, std::forward<CallableT>(parserFn)));
326   }
327 
328 private:
329   MLIRContext *context;
330   DenseMap<StringRef, std::unique_ptr<AsmResourceParser>> resourceParsers;
331 };
332 
333 //===----------------------------------------------------------------------===//
334 // AsmState
335 //===----------------------------------------------------------------------===//
336 
337 /// This class provides management for the lifetime of the state used when
338 /// printing the IR. It allows for alleviating the cost of recomputing the
339 /// internal state of the asm printer.
340 ///
341 /// The IR should not be mutated in-between invocations using this state, and
342 /// the IR being printed must not be an parent of the IR originally used to
343 /// initialize this state. This means that if a child operation is provided, a
344 /// parent operation cannot reuse this state.
345 class AsmState {
346 public:
347   /// This map represents the raw locations of operations within the output
348   /// stream. This maps the original pointer to the operation, to a pair of line
349   /// and column in the output stream.
350   using LocationMap = DenseMap<Operation *, std::pair<unsigned, unsigned>>;
351 
352   /// Initialize the asm state at the level of the given operation. A location
353   /// map may optionally be provided to be populated when printing.
354   AsmState(Operation *op,
355            const OpPrintingFlags &printerFlags = OpPrintingFlags(),
356            LocationMap *locationMap = nullptr);
357   ~AsmState();
358 
359   /// Get the printer flags.
360   const OpPrintingFlags &getPrinterFlags() const;
361 
362   /// Return an instance of the internal implementation. Returns nullptr if the
363   /// state has not been initialized.
getImpl()364   detail::AsmStateImpl &getImpl() { return *impl; }
365 
366   //===--------------------------------------------------------------------===//
367   // Resources
368   //===--------------------------------------------------------------------===//
369 
370   /// Attach the given resource printer to the AsmState.
371   void attachResourcePrinter(std::unique_ptr<AsmResourcePrinter> printer);
372 
373   /// Attach an resource printer, in the form of a callable, to the AsmState.
374   template <typename CallableT>
375   std::enable_if_t<std::is_convertible<
376       CallableT, function_ref<void(Operation *, AsmResourceBuilder &)>>::value>
attachResourcePrinter(StringRef name,CallableT && printFn)377   attachResourcePrinter(StringRef name, CallableT &&printFn) {
378     attachResourcePrinter(AsmResourcePrinter::fromCallable(
379         name, std::forward<CallableT>(printFn)));
380   }
381 
382 private:
383   AsmState() = delete;
384 
385   /// A pointer to allocated storage for the impl state.
386   std::unique_ptr<detail::AsmStateImpl> impl;
387 };
388 
389 //===----------------------------------------------------------------------===//
390 // AsmPrinter CommandLine Options
391 //===----------------------------------------------------------------------===//
392 
393 /// Register a set of useful command-line options that can be used to configure
394 /// various flags within the AsmPrinter.
395 void registerAsmPrinterCLOptions();
396 
397 } // namespace mlir
398 
399 #endif // MLIR_IR_ASMSTATE_H_
400