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/Bytecode/BytecodeReaderConfig.h" 18 #include "mlir/IR/OperationSupport.h" 19 #include "mlir/Support/LLVM.h" 20 #include "llvm/ADT/MapVector.h" 21 #include "llvm/ADT/StringMap.h" 22 23 #include <memory> 24 #include <variant> 25 26 namespace mlir { 27 class AsmResourcePrinter; 28 class AsmDialectResourceHandle; 29 class Operation; 30 31 namespace detail { 32 class AsmStateImpl; 33 } // namespace detail 34 35 //===----------------------------------------------------------------------===// 36 // Resources 37 //===----------------------------------------------------------------------===// 38 39 /// The following classes enable support for parsing and printing resources 40 /// within MLIR assembly formats. Resources are a mechanism by which dialects, 41 /// and external clients, may attach additional information when parsing or 42 /// printing IR without that information being encoded in the IR itself. 43 /// Resources are not uniqued within the MLIR context, are not attached directly 44 /// to any operation, and are solely intended to live and be processed outside 45 /// of the immediate IR. 46 /// 47 /// Resources are encoded using a key-value pair nested within dictionaries 48 /// anchored either on a dialect, or an externally registered entity. 49 /// Dictionaries anchored on dialects use the dialect namespace directly, and 50 /// dictionaries anchored on external entities use a provided unique identifier. 51 /// The resource key is an identifier used to disambiguate the data. The 52 /// resource value may be stored in various limited forms, but general encodings 53 /// use a string (human readable) or blob format (binary). Within the textual 54 /// format, an example may be of the form: 55 /// 56 /// {-# 57 /// // The `dialect_resources` section within the file-level metadata 58 /// // dictionary is used to contain any dialect resource entries. 59 /// dialect_resources: { 60 /// // Here is a dictionary anchored on "foo_dialect", which is a dialect 61 /// // namespace. 62 /// foo_dialect: { 63 /// // `some_dialect_resource` is a key to be interpreted by the dialect, 64 /// // and used to initialize/configure/etc. 65 /// some_dialect_resource: "Some important resource value" 66 /// } 67 /// }, 68 /// // The `external_resources` section within the file-level metadata 69 /// // dictionary is used to contain any non-dialect resource entries. 70 /// external_resources: { 71 /// // Here is a dictionary anchored on "mlir_reproducer", which is an 72 /// // external entity representing MLIR's crash reproducer functionality. 73 /// mlir_reproducer: { 74 /// // `pipeline` is an entry that holds a crash reproducer pipeline 75 /// // resource. 76 /// pipeline: "func.func(canonicalize,cse)" 77 /// } 78 /// } 79 /// #-} 80 /// 81 82 //===----------------------------------------------------------------------===// 83 // Resource Entry 84 85 class HeapAsmResourceBlob; 86 87 /// This class represents a processed binary blob of data. A resource blob is 88 /// essentially a collection of data, potentially mutable, with an associated 89 /// deleter function (used if the data needs to be destroyed). 90 class AsmResourceBlob { 91 public: 92 /// A deleter function that frees a blob given the data, allocation size, and 93 /// allocation aligment. 94 using DeleterFn = 95 llvm::unique_function<void(void *data, size_t size, size_t align)>; 96 97 //===--------------------------------------------------------------------===// 98 // Construction 99 //===--------------------------------------------------------------------===// 100 101 AsmResourceBlob() = default; 102 AsmResourceBlob(ArrayRef<char> data, size_t dataAlignment, DeleterFn deleter, 103 bool dataIsMutable) 104 : data(data), dataAlignment(dataAlignment), deleter(std::move(deleter)), 105 dataIsMutable(dataIsMutable) {} 106 /// Utility constructor that initializes a blob with a non-char type T. 107 template <typename T, typename DelT> 108 AsmResourceBlob(ArrayRef<T> data, DelT &&deleteFn, bool dataIsMutable) 109 : data((const char *)data.data(), data.size() * sizeof(T)), 110 dataAlignment(alignof(T)), 111 deleter([deleteFn = std::forward<DelT>(deleteFn)]( 112 void *data, size_t size, size_t align) { 113 return deleteFn((T *)data, size, align); 114 }), 115 dataIsMutable(dataIsMutable) {} 116 AsmResourceBlob(AsmResourceBlob &&) = default; 117 AsmResourceBlob &operator=(AsmResourceBlob &&rhs) { 118 // Delete the current blob if necessary. 119 if (deleter) 120 deleter(const_cast<char *>(data.data()), data.size(), dataAlignment); 121 122 // Take the data entries from rhs. 123 data = rhs.data; 124 dataAlignment = rhs.dataAlignment; 125 deleter = std::move(rhs.deleter); 126 dataIsMutable = rhs.dataIsMutable; 127 return *this; 128 } 129 AsmResourceBlob(const AsmResourceBlob &) = delete; 130 AsmResourceBlob &operator=(const AsmResourceBlob &) = delete; 131 ~AsmResourceBlob() { 132 if (deleter) 133 deleter(const_cast<char *>(data.data()), data.size(), dataAlignment); 134 } 135 136 //===--------------------------------------------------------------------===// 137 // Data Access 138 //===--------------------------------------------------------------------===// 139 140 /// Return the alignment of the underlying data. 141 size_t getDataAlignment() const { return dataAlignment; } 142 143 /// Return the raw underlying data of this blob. 144 ArrayRef<char> getData() const { return data; } 145 146 /// Return the underlying data as an array of the given type. This is an 147 /// inherrently unsafe operation, and should only be used when the data is 148 /// known to be of the correct type. 149 template <typename T> 150 ArrayRef<T> getDataAs() const { 151 return llvm::ArrayRef<T>((const T *)data.data(), data.size() / sizeof(T)); 152 } 153 154 /// Return a mutable reference to the raw underlying data of this blob. 155 /// Asserts that the blob `isMutable`. 156 MutableArrayRef<char> getMutableData() { 157 assert(isMutable() && 158 "cannot access mutable reference to non-mutable data"); 159 return MutableArrayRef<char>(const_cast<char *>(data.data()), data.size()); 160 } 161 162 /// Return if the data of this blob is mutable. 163 bool isMutable() const { return dataIsMutable; } 164 165 /// Return the deleter function of this blob. 166 DeleterFn &getDeleter() { return deleter; } 167 const DeleterFn &getDeleter() const { return deleter; } 168 169 private: 170 /// The raw, properly aligned, blob data. 171 ArrayRef<char> data; 172 173 /// The alignment of the data. 174 size_t dataAlignment = 0; 175 176 /// An optional deleter function used to deallocate the underlying data when 177 /// necessary. 178 DeleterFn deleter; 179 180 /// Whether the data is mutable. 181 bool dataIsMutable; 182 183 friend class HeapAsmResourceBlob; 184 }; 185 186 /// This class provides a simple utility wrapper for creating heap allocated 187 /// AsmResourceBlobs. 188 class HeapAsmResourceBlob { 189 public: 190 /// Create a new heap allocated blob with the given size and alignment. 191 /// `dataIsMutable` indicates if the allocated data can be mutated. By 192 /// default, we treat heap allocated blobs as mutable. 193 static AsmResourceBlob allocate(size_t size, size_t align, 194 bool dataIsMutable = true) { 195 return AsmResourceBlob( 196 ArrayRef<char>((char *)llvm::allocate_buffer(size, align), size), align, 197 llvm::deallocate_buffer, dataIsMutable); 198 } 199 /// Create a new heap allocated blob and copy the provided data into it. 200 static AsmResourceBlob allocateAndCopyWithAlign(ArrayRef<char> data, 201 size_t align, 202 bool dataIsMutable = true) { 203 // This sets the blob to be mutable initially to allow writing 204 // (getMutableData) below. 205 AsmResourceBlob blob = allocate(data.size(), align, /*dataIsMutable=*/true); 206 std::memcpy(blob.getMutableData().data(), data.data(), data.size()); 207 blob.dataIsMutable = dataIsMutable; 208 return blob; 209 } 210 template <typename T> 211 static AsmResourceBlob allocateAndCopyInferAlign(ArrayRef<T> data, 212 bool dataIsMutable = true) { 213 return allocateAndCopyWithAlign( 214 ArrayRef<char>((const char *)data.data(), data.size() * sizeof(T)), 215 alignof(T), dataIsMutable); 216 } 217 }; 218 /// This class provides a simple utility wrapper for creating "unmanaged" 219 /// AsmResourceBlobs. The lifetime of the data provided to these blobs is 220 /// guaranteed to persist beyond the lifetime of this reference. 221 class UnmanagedAsmResourceBlob { 222 public: 223 /// Create a new unmanaged resource directly referencing the provided data. 224 /// `dataIsMutable` indicates if the allocated data can be mutated. By 225 /// default, we treat unmanaged blobs as immutable. 226 static AsmResourceBlob 227 allocateWithAlign(ArrayRef<char> data, size_t align, 228 AsmResourceBlob::DeleterFn deleter = {}, 229 bool dataIsMutable = false) { 230 return AsmResourceBlob(data, align, std::move(deleter), dataIsMutable); 231 } 232 template <typename T> 233 static AsmResourceBlob 234 allocateInferAlign(ArrayRef<T> data, AsmResourceBlob::DeleterFn deleter = {}, 235 bool dataIsMutable = false) { 236 return allocateWithAlign( 237 ArrayRef<char>((const char *)data.data(), data.size() * sizeof(T)), 238 alignof(T), std::move(deleter), dataIsMutable); 239 } 240 }; 241 242 /// This class is used to build resource entries for use by the printer. Each 243 /// resource entry is represented using a key/value pair. The provided key must 244 /// be unique within the current context, which allows for a client to provide 245 /// resource entries without worrying about overlap with other clients. 246 class AsmResourceBuilder { 247 public: 248 virtual ~AsmResourceBuilder(); 249 250 /// Build a resource entry represented by the given bool. 251 virtual void buildBool(StringRef key, bool data) = 0; 252 253 /// Build a resource entry represented by the given human-readable string 254 /// value. 255 virtual void buildString(StringRef key, StringRef data) = 0; 256 257 /// Build an resource entry represented by the given binary blob data. 258 virtual void buildBlob(StringRef key, ArrayRef<char> data, 259 uint32_t dataAlignment) = 0; 260 /// Build an resource entry represented by the given binary blob data. This is 261 /// a useful overload if the data type is known. Note that this does not 262 /// support `char` element types to avoid accidentally not providing the 263 /// expected alignment of data in situations that treat blobs generically. 264 template <typename T> 265 std::enable_if_t<!std::is_same<T, char>::value> buildBlob(StringRef key, 266 ArrayRef<T> data) { 267 buildBlob( 268 key, ArrayRef<char>((const char *)data.data(), data.size() * sizeof(T)), 269 alignof(T)); 270 } 271 /// Build an resource entry represented by the given resource blob. This is 272 /// a useful overload if a blob already exists in-memory. 273 void buildBlob(StringRef key, const AsmResourceBlob &blob) { 274 buildBlob(key, blob.getData(), blob.getDataAlignment()); 275 } 276 }; 277 278 /// This enum represents the different kinds of resource values. 279 enum class AsmResourceEntryKind { 280 /// A blob of data with an accompanying alignment. 281 Blob, 282 /// A boolean value. 283 Bool, 284 /// A string value. 285 String, 286 }; 287 StringRef toString(AsmResourceEntryKind kind); 288 289 /// This class represents a single parsed resource entry. 290 class AsmParsedResourceEntry { 291 public: 292 virtual ~AsmParsedResourceEntry(); 293 294 /// Return the key of the resource entry. 295 virtual StringRef getKey() const = 0; 296 297 /// Emit an error at the location of this entry. 298 virtual InFlightDiagnostic emitError() const = 0; 299 300 /// Return the kind of this value. 301 virtual AsmResourceEntryKind getKind() const = 0; 302 303 /// Parse the resource entry represented by a boolean. Returns failure if the 304 /// entry does not correspond to a bool. 305 virtual FailureOr<bool> parseAsBool() const = 0; 306 307 /// Parse the resource entry represented by a human-readable string. Returns 308 /// failure if the entry does not correspond to a string. 309 virtual FailureOr<std::string> parseAsString() const = 0; 310 311 /// An allocator function used to allocate memory for a blob when required. 312 /// The function is provided a size and alignment, and should return an 313 /// aligned allocation buffer. 314 using BlobAllocatorFn = 315 function_ref<AsmResourceBlob(size_t size, size_t align)>; 316 317 /// Parse the resource entry represented by a binary blob. Returns failure if 318 /// the entry does not correspond to a blob. If the blob needed to be 319 /// allocated, the given allocator function is invoked. 320 virtual FailureOr<AsmResourceBlob> 321 parseAsBlob(BlobAllocatorFn allocator) const = 0; 322 /// Parse the resource entry represented by a binary blob using heap 323 /// allocation. 324 FailureOr<AsmResourceBlob> parseAsBlob() const { 325 return parseAsBlob([](size_t size, size_t align) { 326 return HeapAsmResourceBlob::allocate(size, align); 327 }); 328 } 329 }; 330 331 //===----------------------------------------------------------------------===// 332 // Resource Parser/Printer 333 334 /// This class represents an instance of a resource parser. This class should be 335 /// implemented by non-dialect clients that want to inject additional resources 336 /// into MLIR assembly formats. 337 class AsmResourceParser { 338 public: 339 /// Create a new parser with the given identifying name. This name uniquely 340 /// identifies the entries of this parser, and differentiates them from other 341 /// contexts. 342 AsmResourceParser(StringRef name) : name(name.str()) {} 343 virtual ~AsmResourceParser(); 344 345 /// Return the name of this parser. 346 StringRef getName() const { return name; } 347 348 /// Parse the given resource entry. Returns failure if the key/data were not 349 /// valid, or could otherwise not be processed correctly. Any necessary errors 350 /// should be emitted with the provided entry. 351 virtual LogicalResult parseResource(AsmParsedResourceEntry &entry) = 0; 352 353 /// Return a resource parser implemented via the given callable, whose form 354 /// should match that of `parseResource` above. 355 template <typename CallableT> 356 static std::unique_ptr<AsmResourceParser> fromCallable(StringRef name, 357 CallableT &&parseFn) { 358 struct Processor : public AsmResourceParser { 359 Processor(StringRef name, CallableT &&parseFn) 360 : AsmResourceParser(name), parseFn(std::move(parseFn)) {} 361 LogicalResult parseResource(AsmParsedResourceEntry &entry) override { 362 return parseFn(entry); 363 } 364 365 std::decay_t<CallableT> parseFn; 366 }; 367 return std::make_unique<Processor>(name, std::forward<CallableT>(parseFn)); 368 } 369 370 private: 371 std::string name; 372 }; 373 374 /// This class represents an instance of a resource printer. This class should 375 /// be implemented by non-dialect clients that want to inject additional 376 /// resources into MLIR assembly formats. 377 class AsmResourcePrinter { 378 public: 379 /// Create a new printer with the given identifying name. This name uniquely 380 /// identifies the entries of this printer, and differentiates them from 381 /// other contexts. 382 AsmResourcePrinter(StringRef name) : name(name.str()) {} 383 virtual ~AsmResourcePrinter(); 384 385 /// Return the name of this printer. 386 StringRef getName() const { return name; } 387 388 /// Build any resources to include during printing, utilizing the given 389 /// top-level root operation to help determine what information to include. 390 /// Provided data should be registered in the form of a key/data pair, to the 391 /// given builder. 392 virtual void buildResources(Operation *op, 393 AsmResourceBuilder &builder) const = 0; 394 395 /// Return a resource printer implemented via the given callable, whose form 396 /// should match that of `buildResources` above. 397 template <typename CallableT> 398 static std::unique_ptr<AsmResourcePrinter> fromCallable(StringRef name, 399 CallableT &&printFn) { 400 struct Printer : public AsmResourcePrinter { 401 Printer(StringRef name, CallableT &&printFn) 402 : AsmResourcePrinter(name), printFn(std::move(printFn)) {} 403 void buildResources(Operation *op, 404 AsmResourceBuilder &builder) const override { 405 printFn(op, builder); 406 } 407 408 std::decay_t<CallableT> printFn; 409 }; 410 return std::make_unique<Printer>(name, std::forward<CallableT>(printFn)); 411 } 412 413 private: 414 std::string name; 415 }; 416 417 /// A fallback map containing external resources not explicitly handled by 418 /// another parser/printer. 419 class FallbackAsmResourceMap { 420 public: 421 /// This class represents an opaque resource. 422 struct OpaqueAsmResource { 423 OpaqueAsmResource(StringRef key, 424 std::variant<AsmResourceBlob, bool, std::string> value) 425 : key(key.str()), value(std::move(value)) {} 426 427 /// The key identifying the resource. 428 std::string key; 429 /// An opaque value for the resource, whose variant values align 1-1 with 430 /// the kinds defined in AsmResourceEntryKind. 431 std::variant<AsmResourceBlob, bool, std::string> value; 432 }; 433 434 /// Return a parser than can be used for parsing entries for the given 435 /// identifier key. 436 AsmResourceParser &getParserFor(StringRef key); 437 438 /// Build a set of resource printers to print the resources within this map. 439 std::vector<std::unique_ptr<AsmResourcePrinter>> getPrinters(); 440 441 private: 442 struct ResourceCollection : public AsmResourceParser { 443 ResourceCollection(StringRef name) : AsmResourceParser(name) {} 444 445 /// Parse a resource into this collection. 446 LogicalResult parseResource(AsmParsedResourceEntry &entry) final; 447 448 /// Build the resources held by this collection. 449 void buildResources(Operation *op, AsmResourceBuilder &builder) const; 450 451 /// The set of resources parsed into this collection. 452 SmallVector<OpaqueAsmResource> resources; 453 }; 454 455 /// The set of opaque resources. 456 llvm::MapVector<std::string, std::unique_ptr<ResourceCollection>, 457 llvm::StringMap<unsigned>> 458 keyToResources; 459 }; 460 461 //===----------------------------------------------------------------------===// 462 // ParserConfig 463 //===----------------------------------------------------------------------===// 464 465 /// This class represents a configuration for the MLIR assembly parser. It 466 /// contains all of the necessary state to parse a MLIR source file. 467 class ParserConfig { 468 public: 469 /// Construct a parser configuration with the given context. 470 /// `verifyAfterParse` indicates if the IR should be verified after parsing. 471 /// `fallbackResourceMap` is an optional fallback handler that can be used to 472 /// parse external resources not explicitly handled by another parser. 473 ParserConfig(MLIRContext *context, bool verifyAfterParse = true, 474 FallbackAsmResourceMap *fallbackResourceMap = nullptr) 475 : context(context), verifyAfterParse(verifyAfterParse), 476 fallbackResourceMap(fallbackResourceMap) { 477 assert(context && "expected valid MLIR context"); 478 } 479 480 /// Return the MLIRContext to be used when parsing. 481 MLIRContext *getContext() const { return context; } 482 483 /// Returns if the parser should verify the IR after parsing. 484 bool shouldVerifyAfterParse() const { return verifyAfterParse; } 485 486 /// Returns the parsing configurations associated to the bytecode read. 487 BytecodeReaderConfig &getBytecodeReaderConfig() const { 488 return const_cast<BytecodeReaderConfig &>(bytecodeReaderConfig); 489 } 490 491 /// Return the resource parser registered to the given name, or nullptr if no 492 /// parser with `name` is registered. 493 AsmResourceParser *getResourceParser(StringRef name) const { 494 auto it = resourceParsers.find(name); 495 if (it != resourceParsers.end()) 496 return it->second.get(); 497 if (fallbackResourceMap) 498 return &fallbackResourceMap->getParserFor(name); 499 return nullptr; 500 } 501 502 /// Attach the given resource parser. 503 void attachResourceParser(std::unique_ptr<AsmResourceParser> parser) { 504 StringRef name = parser->getName(); 505 auto it = resourceParsers.try_emplace(name, std::move(parser)); 506 (void)it; 507 assert(it.second && 508 "resource parser already registered with the given name"); 509 } 510 511 /// Attach the given callable resource parser with the given name. 512 template <typename CallableT> 513 std::enable_if_t<std::is_convertible< 514 CallableT, function_ref<LogicalResult(AsmParsedResourceEntry &)>>::value> 515 attachResourceParser(StringRef name, CallableT &&parserFn) { 516 attachResourceParser(AsmResourceParser::fromCallable( 517 name, std::forward<CallableT>(parserFn))); 518 } 519 520 private: 521 MLIRContext *context; 522 bool verifyAfterParse; 523 DenseMap<StringRef, std::unique_ptr<AsmResourceParser>> resourceParsers; 524 FallbackAsmResourceMap *fallbackResourceMap; 525 BytecodeReaderConfig bytecodeReaderConfig; 526 }; 527 528 //===----------------------------------------------------------------------===// 529 // AsmState 530 //===----------------------------------------------------------------------===// 531 532 /// This class provides management for the lifetime of the state used when 533 /// printing the IR. It allows for alleviating the cost of recomputing the 534 /// internal state of the asm printer. 535 /// 536 /// The IR should not be mutated in-between invocations using this state, and 537 /// the IR being printed must not be an parent of the IR originally used to 538 /// initialize this state. This means that if a child operation is provided, a 539 /// parent operation cannot reuse this state. 540 class AsmState { 541 public: 542 /// This map represents the raw locations of operations within the output 543 /// stream. This maps the original pointer to the operation, to a pair of line 544 /// and column in the output stream. 545 using LocationMap = DenseMap<Operation *, std::pair<unsigned, unsigned>>; 546 547 /// Initialize the asm state at the level of the given operation. A location 548 /// map may optionally be provided to be populated when printing. `map` is an 549 /// optional fallback resource map, which when provided will attach resource 550 /// printers for the fallback resources within the map. 551 AsmState(Operation *op, 552 const OpPrintingFlags &printerFlags = OpPrintingFlags(), 553 LocationMap *locationMap = nullptr, 554 FallbackAsmResourceMap *map = nullptr); 555 AsmState(MLIRContext *ctx, 556 const OpPrintingFlags &printerFlags = OpPrintingFlags(), 557 LocationMap *locationMap = nullptr, 558 FallbackAsmResourceMap *map = nullptr); 559 ~AsmState(); 560 561 /// Get the printer flags. 562 const OpPrintingFlags &getPrinterFlags() const; 563 564 /// Return an instance of the internal implementation. Returns nullptr if the 565 /// state has not been initialized. 566 detail::AsmStateImpl &getImpl() { return *impl; } 567 568 //===--------------------------------------------------------------------===// 569 // Resources 570 //===--------------------------------------------------------------------===// 571 572 /// Attach the given resource printer to the AsmState. 573 void attachResourcePrinter(std::unique_ptr<AsmResourcePrinter> printer); 574 575 /// Attach an resource printer, in the form of a callable, to the AsmState. 576 template <typename CallableT> 577 std::enable_if_t<std::is_convertible< 578 CallableT, function_ref<void(Operation *, AsmResourceBuilder &)>>::value> 579 attachResourcePrinter(StringRef name, CallableT &&printFn) { 580 attachResourcePrinter(AsmResourcePrinter::fromCallable( 581 name, std::forward<CallableT>(printFn))); 582 } 583 584 /// Attach resource printers to the AsmState for the fallback resources 585 /// in the given map. 586 void attachFallbackResourcePrinter(FallbackAsmResourceMap &map) { 587 for (auto &printer : map.getPrinters()) 588 attachResourcePrinter(std::move(printer)); 589 } 590 591 /// Returns a map of dialect resources that were referenced when using this 592 /// state to print IR. 593 DenseMap<Dialect *, SetVector<AsmDialectResourceHandle>> & 594 getDialectResources() const; 595 596 private: 597 AsmState() = delete; 598 599 /// A pointer to allocated storage for the impl state. 600 std::unique_ptr<detail::AsmStateImpl> impl; 601 }; 602 603 //===----------------------------------------------------------------------===// 604 // AsmPrinter CommandLine Options 605 //===----------------------------------------------------------------------===// 606 607 /// Register a set of useful command-line options that can be used to configure 608 /// various flags within the AsmPrinter. 609 void registerAsmPrinterCLOptions(); 610 611 } // namespace mlir 612 613 #endif // MLIR_IR_ASMSTATE_H_ 614