//===- DialectRegistry.h - Dialect Registration and Extension ---*- 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 functionality for registring and extending dialects. // //===----------------------------------------------------------------------===// #ifndef MLIR_IR_DIALECTREGISTRY_H #define MLIR_IR_DIALECTREGISTRY_H #include "mlir/IR/MLIRContext.h" #include "mlir/Support/TypeID.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/MapVector.h" #include "llvm/ADT/SmallVector.h" #include "llvm/Support/LogicalResult.h" #include #include #include namespace mlir { class Dialect; using DialectAllocatorFunction = std::function; using DialectAllocatorFunctionRef = function_ref; using DynamicDialectPopulationFunction = std::function; //===----------------------------------------------------------------------===// // DialectExtension //===----------------------------------------------------------------------===// /// This class represents an opaque dialect extension. It contains a set of /// required dialects and an application function. The required dialects control /// when the extension is applied, i.e. the extension is applied when all /// required dialects are loaded. The application function can be used to attach /// additional functionality to attributes, dialects, operations, types, etc., /// and may also load additional necessary dialects. class DialectExtensionBase { public: virtual ~DialectExtensionBase(); /// Return the dialects that our required by this extension to be loaded /// before applying. If empty then the extension is invoked for every loaded /// dialect independently. ArrayRef getRequiredDialects() const { return dialectNames; } /// Apply this extension to the given context and the required dialects. virtual void apply(MLIRContext *context, MutableArrayRef dialects) const = 0; /// Return a copy of this extension. virtual std::unique_ptr clone() const = 0; protected: /// Initialize the extension with a set of required dialects. /// If the list is empty, the extension is invoked for every loaded dialect /// independently. DialectExtensionBase(ArrayRef dialectNames) : dialectNames(dialectNames) {} private: /// The names of the dialects affected by this extension. SmallVector dialectNames; }; /// This class represents a dialect extension anchored on the given set of /// dialects. When all of the specified dialects have been loaded, the /// application function of this extension will be executed. template class DialectExtension : public DialectExtensionBase { public: /// Applies this extension to the given context and set of required dialects. virtual void apply(MLIRContext *context, DialectsT *...dialects) const = 0; /// Return a copy of this extension. std::unique_ptr clone() const final { return std::make_unique(static_cast(*this)); } protected: DialectExtension() : DialectExtensionBase( ArrayRef({DialectsT::getDialectNamespace()...})) {} /// Override the base apply method to allow providing the exact dialect types. void apply(MLIRContext *context, MutableArrayRef dialects) const final { unsigned dialectIdx = 0; auto derivedDialects = std::tuple{ static_cast(dialects[dialectIdx++])...}; std::apply([&](DialectsT *...dialect) { apply(context, dialect...); }, derivedDialects); } }; namespace dialect_extension_detail { /// Checks if the given interface, which is attempting to be used, is a /// promised interface of this dialect that has yet to be implemented. If so, /// emits a fatal error. void handleUseOfUndefinedPromisedInterface(Dialect &dialect, TypeID interfaceRequestorID, TypeID interfaceID, StringRef interfaceName); /// Checks if the given interface, which is attempting to be attached, is a /// promised interface of this dialect that has yet to be implemented. If so, /// the promised interface is marked as resolved. void handleAdditionOfUndefinedPromisedInterface(Dialect &dialect, TypeID interfaceRequestorID, TypeID interfaceID); /// Checks if a promise has been made for the interface/requestor pair. bool hasPromisedInterface(Dialect &dialect, TypeID interfaceRequestorID, TypeID interfaceID); /// Checks if a promise has been made for the interface/requestor pair. template bool hasPromisedInterface(Dialect &dialect) { return hasPromisedInterface(dialect, TypeID::get(), InterfaceT::getInterfaceID()); } } // namespace dialect_extension_detail //===----------------------------------------------------------------------===// // DialectRegistry //===----------------------------------------------------------------------===// /// The DialectRegistry maps a dialect namespace to a constructor for the /// matching dialect. This allows for decoupling the list of dialects /// "available" from the dialects loaded in the Context. The parser in /// particular will lazily load dialects in the Context as operations are /// encountered. /// /// In addition to allocator-backed registrations, the registry can also carry /// a set of dialect *names* that some caller has asked to be preloaded into /// the context (see `addDialectToPreload(StringRef)`). The registry itself /// does not load those dialects — it merely records the request; the /// allocator is expected to live in the MLIRContext's own registry, and /// actually loading them is the caller's responsibility via /// `preloadSelectDialects(MLIRContext *)`. class DialectRegistry { using MapTy = std::map, std::less<>>; public: explicit DialectRegistry(); DialectRegistry(const DialectRegistry &) = delete; DialectRegistry &operator=(const DialectRegistry &other) = delete; DialectRegistry(DialectRegistry &&) = default; DialectRegistry &operator=(DialectRegistry &&other) = default; template void insert() { insert(TypeID::get(), ConcreteDialect::getDialectNamespace(), static_cast(([](MLIRContext *ctx) { // Just allocate the dialect, the context // takes ownership of it. return ctx->getOrLoadDialect(); }))); } template void insert() { insert(); insert(); } /// Add a new dialect constructor to the registry. The constructor must be /// calling MLIRContext::getOrLoadDialect in order for the context to take /// ownership of the dialect and for delayed interface registration to happen. void insert(TypeID typeID, StringRef name, const DialectAllocatorFunction &ctor); /// Request that the dialect with the given name be preloaded into the /// MLIRContext, without providing an allocator. Useful when a caller knows a /// dialect is required but expects its allocator to be available in the /// MLIRContext's own registry at load time (e.g. a pass learning dialect /// names from string-valued options). void addDialectToPreload(StringRef name); /// Add a new dynamic dialect constructor in the registry. The constructor /// provides as argument the created dynamic dialect, and is expected to /// register the dialect types, attributes, and ops, using the /// methods defined in ExtensibleDialect such as registerDynamicOperation. void insertDynamic(StringRef name, const DynamicDialectPopulationFunction &ctor); /// Return an allocation function for constructing the dialect identified /// by its namespace, or nullptr if the namespace is not in this registry. DialectAllocatorFunctionRef getDialectAllocator(StringRef name) const; // Register all dialects available in the current registry with the registry // in the provided context. void appendTo(DialectRegistry &destination) const { for (const auto &nameAndRegistrationIt : registry) destination.insert(nameAndRegistrationIt.second.first, nameAndRegistrationIt.first, nameAndRegistrationIt.second.second); for (const std::string &name : dialectsToPreload) destination.addDialectToPreload(StringRef(name)); // Merge the extensions. for (const auto &extension : extensions) destination.extensions.try_emplace(extension.first, extension.second->clone()); } /// Return the names of dialects registered in this registry with an /// allocator function. Does not include preload-only entries added via /// `addDialectToPreload(StringRef)` — use `getDialectsToPreload()` for those. SmallVector getRegisteredDialectNames() const { SmallVector names; names.reserve(registry.size()); for (const auto &item : registry) names.push_back(item.first); return names; } /// Return the names of dialects that should be preloaded into the context /// but whose allocator is expected to be resolved from the context's own /// registry (added via `addDialectToPreload(StringRef)`). ArrayRef getDialectsToPreload() const { return dialectsToPreload; } /// Load into `ctx` every dialect previously added via /// `addDialectToPreload(StringRef)`. The allocator is resolved from the /// context's own registry. On failure, if `emitError` is provided, it is /// invoked to produce a diagnostic naming the offending dialect; otherwise /// the failure is silent. LogicalResult preloadSelectDialects( MLIRContext *ctx, function_ref emitError = {}) const; /// Apply any held extensions that require the given dialect. Users are not /// expected to call this directly. void applyExtensions(Dialect *dialect) const; /// Apply any applicable extensions to the given context. Users are not /// expected to call this directly. void applyExtensions(MLIRContext *ctx) const; /// Add the given extension to the registry. bool addExtension(TypeID extensionID, std::unique_ptr extension) { return extensions.try_emplace(extensionID, std::move(extension)).second; } /// Add the given extensions to the registry. template void addExtensions() { (addExtension(TypeID::get(), std::make_unique()), ...); } /// Add an extension function that requires the given dialects. /// Note: This bare functor overload is provided in addition to the /// std::function variant to enable dialect type deduction, e.g.: /// registry.addExtension(+[](MLIRContext *ctx, MyDialect *dialect) { /// ... }) /// /// is equivalent to: /// registry.addExtension( /// [](MLIRContext *ctx, MyDialect *dialect){ ... } /// ) template bool addExtension(void (*extensionFn)(MLIRContext *, DialectsT *...)) { using ExtensionFnT = void (*)(MLIRContext *, DialectsT *...); struct Extension : public DialectExtension { Extension(const Extension &) = default; Extension(ExtensionFnT extensionFn) : DialectExtension(), extensionFn(extensionFn) {} ~Extension() override = default; void apply(MLIRContext *context, DialectsT *...dialects) const final { extensionFn(context, dialects...); } ExtensionFnT extensionFn; }; return addExtension(TypeID::getFromOpaquePointer( reinterpret_cast(extensionFn)), std::make_unique(extensionFn)); } /// Returns true if the current registry is a subset of 'rhs', i.e. if 'rhs' /// contains all of the components of this registry. bool isSubsetOf(const DialectRegistry &rhs) const; private: MapTy registry; /// Names of dialects that should be preloaded into the MLIRContext but for /// which no allocator has been registered here. The allocator is expected /// to be resolved from the MLIRContext's own registry when the dialect is /// loaded (e.g. via MLIRContext::getOrLoadDialect). SmallVector dialectsToPreload; llvm::MapVector> extensions; }; } // namespace mlir #endif // MLIR_IR_DIALECTREGISTRY_H