//===- AsmState.h - Assembly State Utilities --------------------*- C++ -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// // // This file defines various classes and utilites for interacting with the MLIR // assembly formats. // //===----------------------------------------------------------------------===// #ifndef MLIR_IR_ASMSTATE_H_ #define MLIR_IR_ASMSTATE_H_ #include "mlir/Bytecode/BytecodeReaderConfig.h" #include "mlir/IR/OperationSupport.h" #include "mlir/Support/LLVM.h" #include "llvm/ADT/MapVector.h" #include "llvm/ADT/StringMap.h" #include #include namespace mlir { class AsmResourcePrinter; class AsmDialectResourceHandle; class Operation; namespace detail { class AsmStateImpl; } // namespace detail //===----------------------------------------------------------------------===// // Resources //===----------------------------------------------------------------------===// /// The following classes enable support for parsing and printing resources /// within MLIR assembly formats. Resources are a mechanism by which dialects, /// and external clients, may attach additional information when parsing or /// printing IR without that information being encoded in the IR itself. /// Resources are not uniqued within the MLIR context, are not attached directly /// to any operation, and are solely intended to live and be processed outside /// of the immediate IR. /// /// Resources are encoded using a key-value pair nested within dictionaries /// anchored either on a dialect, or an externally registered entity. /// Dictionaries anchored on dialects use the dialect namespace directly, and /// dictionaries anchored on external entities use a provided unique identifier. /// The resource key is an identifier used to disambiguate the data. The /// resource value may be stored in various limited forms, but general encodings /// use a string (human readable) or blob format (binary). Within the textual /// format, an example may be of the form: /// /// {-# /// // The `dialect_resources` section within the file-level metadata /// // dictionary is used to contain any dialect resource entries. /// dialect_resources: { /// // Here is a dictionary anchored on "foo_dialect", which is a dialect /// // namespace. /// foo_dialect: { /// // `some_dialect_resource` is a key to be interpreted by the dialect, /// // and used to initialize/configure/etc. /// some_dialect_resource: "Some important resource value" /// } /// }, /// // The `external_resources` section within the file-level metadata /// // dictionary is used to contain any non-dialect resource entries. /// external_resources: { /// // Here is a dictionary anchored on "mlir_reproducer", which is an /// // external entity representing MLIR's crash reproducer functionality. /// mlir_reproducer: { /// // `pipeline` is an entry that holds a crash reproducer pipeline /// // resource. /// pipeline: "func.func(canonicalize,cse)" /// } /// } /// #-} /// //===----------------------------------------------------------------------===// // Resource Entry //===----------------------------------------------------------------------===// class HeapAsmResourceBlob; /// This class represents a processed binary blob of data. A resource blob is /// essentially a collection of data, potentially mutable, with an associated /// deleter function (used if the data needs to be destroyed). class AsmResourceBlob { public: /// A deleter function that frees a blob given the data, allocation size, and /// allocation aligment. using DeleterFn = llvm::unique_function; //===--------------------------------------------------------------------===// // Construction //===--------------------------------------------------------------------===// AsmResourceBlob() = default; AsmResourceBlob(ArrayRef data, size_t dataAlignment, DeleterFn deleter, bool dataIsMutable) : data(data), dataAlignment(dataAlignment), deleter(std::move(deleter)), dataIsMutable(dataIsMutable) {} /// Utility constructor that initializes a blob with a non-char type T. template AsmResourceBlob(ArrayRef data, DelT &&deleteFn, bool dataIsMutable) : data((const char *)data.data(), data.size() * sizeof(T)), dataAlignment(alignof(T)), deleter([deleteFn = std::forward(deleteFn)]( void *data, size_t size, size_t align) { return deleteFn((T *)data, size, align); }), dataIsMutable(dataIsMutable) {} AsmResourceBlob(AsmResourceBlob &&) = default; AsmResourceBlob &operator=(AsmResourceBlob &&rhs) { // Delete the current blob if necessary. if (deleter) deleter(const_cast(data.data()), data.size(), dataAlignment); // Take the data entries from rhs. data = rhs.data; dataAlignment = rhs.dataAlignment; deleter = std::move(rhs.deleter); dataIsMutable = rhs.dataIsMutable; return *this; } AsmResourceBlob(const AsmResourceBlob &) = delete; AsmResourceBlob &operator=(const AsmResourceBlob &) = delete; ~AsmResourceBlob() { if (deleter) deleter(const_cast(data.data()), data.size(), dataAlignment); } //===--------------------------------------------------------------------===// // Data Access //===--------------------------------------------------------------------===// /// Return the alignment of the underlying data. size_t getDataAlignment() const { return dataAlignment; } /// Return the raw underlying data of this blob. ArrayRef getData() const { return data; } /// Return the underlying data as an array of the given type. This is an /// inherrently unsafe operation, and should only be used when the data is /// known to be of the correct type. template ArrayRef getDataAs() const { return llvm::ArrayRef((const T *)data.data(), data.size() / sizeof(T)); } /// Return a mutable reference to the raw underlying data of this blob. /// Asserts that the blob `isMutable`. MutableArrayRef getMutableData() { assert(isMutable() && "cannot access mutable reference to non-mutable data"); return MutableArrayRef(const_cast(data.data()), data.size()); } /// Return if the data of this blob is mutable. bool isMutable() const { return dataIsMutable; } /// Return the deleter function of this blob. DeleterFn &getDeleter() { return deleter; } const DeleterFn &getDeleter() const { return deleter; } private: /// The raw, properly aligned, blob data. ArrayRef data; /// The alignment of the data. size_t dataAlignment = 0; /// An optional deleter function used to deallocate the underlying data when /// necessary. DeleterFn deleter; /// Whether the data is mutable. bool dataIsMutable; friend class HeapAsmResourceBlob; }; /// This class provides a simple utility wrapper for creating heap allocated /// AsmResourceBlobs. class HeapAsmResourceBlob { public: /// Create a new heap allocated blob with the given size and alignment. /// `dataIsMutable` indicates if the allocated data can be mutated. By /// default, we treat heap allocated blobs as mutable. static AsmResourceBlob allocate(size_t size, size_t align, bool dataIsMutable = true) { return AsmResourceBlob( ArrayRef((char *)llvm::allocate_buffer(size, align), size), align, llvm::deallocate_buffer, dataIsMutable); } /// Create a new heap allocated blob and copy the provided data into it. static AsmResourceBlob allocateAndCopyWithAlign(ArrayRef data, size_t align, bool dataIsMutable = true) { // This sets the blob to be mutable initially to allow writing // (getMutableData) below. AsmResourceBlob blob = allocate(data.size(), align, /*dataIsMutable=*/true); std::memcpy(blob.getMutableData().data(), data.data(), data.size()); blob.dataIsMutable = dataIsMutable; return blob; } template static AsmResourceBlob allocateAndCopyInferAlign(ArrayRef data, bool dataIsMutable = true) { return allocateAndCopyWithAlign( ArrayRef((const char *)data.data(), data.size() * sizeof(T)), alignof(T), dataIsMutable); } }; /// This class provides a simple utility wrapper for creating "unmanaged" /// AsmResourceBlobs. The lifetime of the data provided to these blobs is /// guaranteed to persist beyond the lifetime of this reference. class UnmanagedAsmResourceBlob { public: /// Create a new unmanaged resource directly referencing the provided data. /// `dataIsMutable` indicates if the allocated data can be mutated. By /// default, we treat unmanaged blobs as immutable. static AsmResourceBlob allocateWithAlign(ArrayRef data, size_t align, AsmResourceBlob::DeleterFn deleter = {}, bool dataIsMutable = false) { return AsmResourceBlob(data, align, std::move(deleter), dataIsMutable); } template static AsmResourceBlob allocateInferAlign(ArrayRef data, AsmResourceBlob::DeleterFn deleter = {}, bool dataIsMutable = false) { return allocateWithAlign( ArrayRef((const char *)data.data(), data.size() * sizeof(T)), alignof(T), std::move(deleter), dataIsMutable); } }; /// This class is used to build resource entries for use by the printer. Each /// resource entry is represented using a key/value pair. The provided key must /// be unique within the current context, which allows for a client to provide /// resource entries without worrying about overlap with other clients. class AsmResourceBuilder { public: virtual ~AsmResourceBuilder(); /// Build a resource entry represented by the given bool. virtual void buildBool(StringRef key, bool data) = 0; /// Build a resource entry represented by the given human-readable string /// value. virtual void buildString(StringRef key, StringRef data) = 0; /// Build an resource entry represented by the given binary blob data. virtual void buildBlob(StringRef key, ArrayRef data, uint32_t dataAlignment) = 0; /// Build an resource entry represented by the given binary blob data. This is /// a useful overload if the data type is known. Note that this does not /// support `char` element types to avoid accidentally not providing the /// expected alignment of data in situations that treat blobs generically. template std::enable_if_t::value> buildBlob(StringRef key, ArrayRef data) { buildBlob( key, ArrayRef((const char *)data.data(), data.size() * sizeof(T)), alignof(T)); } /// Build an resource entry represented by the given resource blob. This is /// a useful overload if a blob already exists in-memory. void buildBlob(StringRef key, const AsmResourceBlob &blob) { buildBlob(key, blob.getData(), blob.getDataAlignment()); } }; /// This enum represents the different kinds of resource values. enum class AsmResourceEntryKind { /// A blob of data with an accompanying alignment. Blob, /// A boolean value. Bool, /// A string value. String, }; StringRef toString(AsmResourceEntryKind kind); /// This class represents a single parsed resource entry. class AsmParsedResourceEntry { public: virtual ~AsmParsedResourceEntry(); /// Return the key of the resource entry. virtual StringRef getKey() const = 0; /// Emit an error at the location of this entry. virtual InFlightDiagnostic emitError() const = 0; /// Return the kind of this value. virtual AsmResourceEntryKind getKind() const = 0; /// Parse the resource entry represented by a boolean. Returns failure if the /// entry does not correspond to a bool. virtual FailureOr parseAsBool() const = 0; /// Parse the resource entry represented by a human-readable string. Returns /// failure if the entry does not correspond to a string. virtual FailureOr parseAsString() const = 0; /// An allocator function used to allocate memory for a blob when required. /// The function is provided a size and alignment, and should return an /// aligned allocation buffer. using BlobAllocatorFn = function_ref; /// Parse the resource entry represented by a binary blob. Returns failure if /// the entry does not correspond to a blob. If the blob needed to be /// allocated, the given allocator function is invoked. virtual FailureOr parseAsBlob(BlobAllocatorFn allocator) const = 0; /// Parse the resource entry represented by a binary blob using heap /// allocation. FailureOr parseAsBlob() const { return parseAsBlob([](size_t size, size_t align) { return HeapAsmResourceBlob::allocate(size, align); }); } }; //===----------------------------------------------------------------------===// // Resource Parser/Printer //===----------------------------------------------------------------------===// /// This class represents an instance of a resource parser. This class should be /// implemented by non-dialect clients that want to inject additional resources /// into MLIR assembly formats. class AsmResourceParser { public: /// Create a new parser with the given identifying name. This name uniquely /// identifies the entries of this parser, and differentiates them from other /// contexts. AsmResourceParser(StringRef name) : name(name.str()) {} virtual ~AsmResourceParser(); /// Return the name of this parser. StringRef getName() const { return name; } /// Parse the given resource entry. Returns failure if the key/data were not /// valid, or could otherwise not be processed correctly. Any necessary errors /// should be emitted with the provided entry. virtual LogicalResult parseResource(AsmParsedResourceEntry &entry) = 0; /// Return a resource parser implemented via the given callable, whose form /// should match that of `parseResource` above. template static std::unique_ptr fromCallable(StringRef name, CallableT &&parseFn) { struct Processor : public AsmResourceParser { Processor(StringRef name, CallableT &&parseFn) : AsmResourceParser(name), parseFn(std::move(parseFn)) {} LogicalResult parseResource(AsmParsedResourceEntry &entry) override { return parseFn(entry); } std::decay_t parseFn; }; return std::make_unique(name, std::forward(parseFn)); } private: std::string name; }; /// This class represents an instance of a resource printer. This class should /// be implemented by non-dialect clients that want to inject additional /// resources into MLIR assembly formats. class AsmResourcePrinter { public: /// Create a new printer with the given identifying name. This name uniquely /// identifies the entries of this printer, and differentiates them from /// other contexts. AsmResourcePrinter(StringRef name) : name(name.str()) {} virtual ~AsmResourcePrinter(); /// Return the name of this printer. StringRef getName() const { return name; } /// Build any resources to include during printing, utilizing the given /// top-level root operation to help determine what information to include. /// Provided data should be registered in the form of a key/data pair, to the /// given builder. virtual void buildResources(Operation *op, AsmResourceBuilder &builder) const = 0; /// Return a resource printer implemented via the given callable, whose form /// should match that of `buildResources` above. template static std::unique_ptr fromCallable(StringRef name, CallableT &&printFn) { struct Printer : public AsmResourcePrinter { Printer(StringRef name, CallableT &&printFn) : AsmResourcePrinter(name), printFn(std::move(printFn)) {} void buildResources(Operation *op, AsmResourceBuilder &builder) const override { printFn(op, builder); } std::decay_t printFn; }; return std::make_unique(name, std::forward(printFn)); } private: std::string name; }; /// A fallback map containing external resources not explicitly handled by /// another parser/printer. class FallbackAsmResourceMap { public: /// This class represents an opaque resource. struct OpaqueAsmResource { OpaqueAsmResource(StringRef key, std::variant value) : key(key.str()), value(std::move(value)) {} /// The key identifying the resource. std::string key; /// An opaque value for the resource, whose variant values align 1-1 with /// the kinds defined in AsmResourceEntryKind. std::variant value; }; /// Return a parser than can be used for parsing entries for the given /// identifier key. AsmResourceParser &getParserFor(StringRef key); /// Build a set of resource printers to print the resources within this map. std::vector> getPrinters(); private: struct ResourceCollection : public AsmResourceParser { ResourceCollection(StringRef name) : AsmResourceParser(name) {} /// Parse a resource into this collection. LogicalResult parseResource(AsmParsedResourceEntry &entry) final; /// Build the resources held by this collection. void buildResources(Operation *op, AsmResourceBuilder &builder) const; /// The set of resources parsed into this collection. SmallVector resources; }; /// The set of opaque resources. llvm::MapVector, llvm::StringMap> keyToResources; }; //===----------------------------------------------------------------------===// // ParserConfig //===----------------------------------------------------------------------===// /// This class represents a configuration for the MLIR assembly parser. It /// contains all of the necessary state to parse a MLIR source file. class ParserConfig { public: /// Construct a parser configuration with the given context. /// `verifyAfterParse` indicates if the IR should be verified after parsing. /// `fallbackResourceMap` is an optional fallback handler that can be used to /// parse external resources not explicitly handled by another parser. ParserConfig(MLIRContext *context, bool verifyAfterParse = true, FallbackAsmResourceMap *fallbackResourceMap = nullptr) : context(context), verifyAfterParse(verifyAfterParse), fallbackResourceMap(fallbackResourceMap) { assert(context && "expected valid MLIR context"); } /// Return the MLIRContext to be used when parsing. MLIRContext *getContext() const { return context; } /// Returns if the parser should verify the IR after parsing. bool shouldVerifyAfterParse() const { return verifyAfterParse; } /// Returns the parsing configurations associated to the bytecode read. BytecodeReaderConfig &getBytecodeReaderConfig() const { return const_cast(bytecodeReaderConfig); } /// Return the resource parser registered to the given name, or nullptr if no /// parser with `name` is registered. AsmResourceParser *getResourceParser(StringRef name) const { auto it = resourceParsers.find(name); if (it != resourceParsers.end()) return it->second.get(); if (fallbackResourceMap) return &fallbackResourceMap->getParserFor(name); return nullptr; } /// Attach the given resource parser. void attachResourceParser(std::unique_ptr parser) { StringRef name = parser->getName(); auto it = resourceParsers.try_emplace(name, std::move(parser)); (void)it; assert(it.second && "resource parser already registered with the given name"); } /// Attach the given callable resource parser with the given name. template std::enable_if_t>::value> attachResourceParser(StringRef name, CallableT &&parserFn) { attachResourceParser(AsmResourceParser::fromCallable( name, std::forward(parserFn))); } private: MLIRContext *context; bool verifyAfterParse; DenseMap> resourceParsers; FallbackAsmResourceMap *fallbackResourceMap; BytecodeReaderConfig bytecodeReaderConfig; }; //===----------------------------------------------------------------------===// // AsmState //===----------------------------------------------------------------------===// /// This class provides management for the lifetime of the state used when /// printing the IR. It allows for alleviating the cost of recomputing the /// internal state of the asm printer. /// /// The IR should not be mutated in-between invocations using this state, and /// the IR being printed must not be an parent of the IR originally used to /// initialize this state. This means that if a child operation is provided, a /// parent operation cannot reuse this state. class AsmState { public: /// This map represents the raw locations of operations within the output /// stream. This maps the original pointer to the operation, to a pair of line /// and column in the output stream. using LocationMap = DenseMap>; /// Initialize the asm state at the level of the given operation. A location /// map may optionally be provided to be populated when printing. `map` is an /// optional fallback resource map, which when provided will attach resource /// printers for the fallback resources within the map. AsmState(Operation *op, const OpPrintingFlags &printerFlags = OpPrintingFlags(), LocationMap *locationMap = nullptr, FallbackAsmResourceMap *map = nullptr); AsmState(MLIRContext *ctx, const OpPrintingFlags &printerFlags = OpPrintingFlags(), LocationMap *locationMap = nullptr, FallbackAsmResourceMap *map = nullptr); ~AsmState(); /// Get the printer flags. const OpPrintingFlags &getPrinterFlags() const; /// Return an instance of the internal implementation. Returns nullptr if the /// state has not been initialized. detail::AsmStateImpl &getImpl() { return *impl; } //===--------------------------------------------------------------------===// // Resources //===--------------------------------------------------------------------===// /// Attach the given resource printer to the AsmState. void attachResourcePrinter(std::unique_ptr printer); /// Attach an resource printer, in the form of a callable, to the AsmState. template std::enable_if_t>::value> attachResourcePrinter(StringRef name, CallableT &&printFn) { attachResourcePrinter(AsmResourcePrinter::fromCallable( name, std::forward(printFn))); } /// Attach resource printers to the AsmState for the fallback resources /// in the given map. void attachFallbackResourcePrinter(FallbackAsmResourceMap &map) { for (auto &printer : map.getPrinters()) attachResourcePrinter(std::move(printer)); } /// Returns a map of dialect resources that were referenced when using this /// state to print IR. DenseMap> & getDialectResources() const; private: AsmState() = delete; /// A pointer to allocated storage for the impl state. std::unique_ptr impl; }; //===----------------------------------------------------------------------===// // AsmPrinter CommandLine Options //===----------------------------------------------------------------------===// /// Register a set of useful command-line options that can be used to configure /// various flags within the AsmPrinter. void registerAsmPrinterCLOptions(); } // namespace mlir #endif // MLIR_IR_ASMSTATE_H_