| // Copyright 2020 The IREE Authors |
| // |
| // Licensed 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 |
| |
| #ifndef IREE_COMPILER_DIALECT_HAL_TARGET_TARGETBACKEND_H_ |
| #define IREE_COMPILER_DIALECT_HAL_TARGET_TARGETBACKEND_H_ |
| |
| #include <functional> |
| #include <optional> |
| #include <string> |
| #include <vector> |
| |
| #include "iree/compiler/Dialect/HAL/IR/HALOps.h" |
| #include "llvm/ADT/StringMap.h" |
| #include "llvm/ADT/StringRef.h" |
| #include "mlir/IR/Dialect.h" |
| #include "mlir/Pass/PassManager.h" |
| |
| namespace mlir::iree_compiler::IREE::HAL { |
| |
| // HAL executable target backend interface. |
| // Multiple backends can be registered and targeted during a single compilation. |
| // The flow->hal conversion process will use registered TargetBackend interfaces |
| // to query for scheduling parameters (such as workgroup size), allow for |
| // backend-specific scheduling logic (such as custom command buffer dispatch |
| // recording), and to setup the transformation pipeline. |
| // |
| // During each phase of lowering the executable may be duplicated based on the |
| // target configuration. For example, a single input `flow.executable` will map |
| // to at least one `hal.executable.variant` for each unique target backend |
| // configuration, and for each of those target backends can emit one or more |
| // `hal.executable.variant` containing the translated contents. Finally, each |
| // executable target will be serialized into one or more binary formats. The |
| // exact contents of the `hal.executable.variant` ops is left to the backends |
| // and can contain backend-specific nested IR and attributes. |
| // |
| // Hypothetical example (Vulkan+SPIR-V): |
| // -> flow.executable @my_exe |
| // [[-iree-hal-materialize-interfaces]] |
| // -> hal.executable @my_exe |
| // + hal.executable.variant @spirv-v1.1-mobile filter="spirv-v1.1-mobile*" |
| // hal.executable.export @my_entry |
| // module { ... } |
| // + hal.executable.variant @spirv-v1.1-desktop |
| // filter="spirv-v1.1-desktop*" |
| // hal.executable.export @my_entry |
| // module { ... } |
| // + hal.executable.variant @spirv-v1.2-desktop |
| // filter="spirv-v1.2-desktop*" |
| // hal.executable.export @my_entry |
| // module { ... } |
| // [[-iree-hal-translate-all-executables]] |
| // -> hal.executable @my_exe |
| // + hal.executable.variant @spirv-v1.1-mobile filter="spirv-v1.1-mobile*" |
| // hal.executable.export @my_entry_1 |
| // hal.executable.export @my_entry_2 |
| // hal.executable.export @my_entry_3 |
| // module { spirv.module { ... } } |
| // + hal.executable.variant @spirv-v1.1-desktop |
| // filter="spirv-v1.1-desktop*" |
| // hal.executable.export @my_entry |
| // module { spirv.module { ... } } |
| // + hal.executable.variant @spirv-v1.2-desktop |
| // filter="spirv-v1.2-desktop*" |
| // hal.executable.export @my_entry |
| // module { spirv.module { ... } } |
| // [[-iree-hal-link-all-executables]] |
| // -> TODO(benvanik): linkage rules. |
| // [[-iree-hal-serialize-all-executables]] |
| // -> hal.executable @my_exe |
| // + hal.executable.binary attributes { ... } |
| // data blob... |
| // + hal.executable.binary attributes { ... } |
| // data blob... |
| // + hal.executable.binary attributes { ... } |
| // data blob... |
| // + hal.executable.binary attributes { ... } |
| // data blob... |
| class TargetBackend { |
| public: |
| virtual ~TargetBackend() = default; |
| |
| // Returns the ID of a DeviceTarget that can execute files produced by this |
| // backend. This is used to support the `--iree-hal-target-backends=` flag |
| // and will be removed in the future. |
| virtual std::string getLegacyDefaultDeviceID() const = 0; |
| |
| // Appends zero or more executable targets for a device with the given |
| // ID and configuration using flags/options that control target defaults. |
| virtual void getDefaultExecutableTargets( |
| MLIRContext *context, StringRef deviceID, DictionaryAttr deviceConfigAttr, |
| SmallVectorImpl<IREE::HAL::ExecutableTargetAttr> &executableTargetAttrs) |
| const = 0; |
| |
| // Appends zero or more executable targets for a device with the given |
| // ID and configuration that represents the hosting machine. |
| virtual void getHostExecutableTargets( |
| MLIRContext *context, StringRef deviceID, DictionaryAttr deviceConfigAttr, |
| SmallVectorImpl<IREE::HAL::ExecutableTargetAttr> &executableTargetAttrs) |
| const {} |
| |
| // Registers dependent dialects for the TargetBackend. |
| // Mirrors the method on mlir::Pass of the same name. A TargetBackend is |
| // expected to register the dialects it will create entities for (Operations, |
| // Types, Attributes). |
| virtual void getDependentDialects(DialectRegistry ®istry) const {} |
| |
| // Inserts passes used to configure the `hal.executable.variant` op contents |
| // for translation. The pass manager will be nested on `hal.executable` such |
| // that the pipeline will only run on executable contents. |
| // |
| // The primary purpose of this pipeline is to preprocess then annotate all |
| // `hal.executable.variant` ops with the information necessary for |
| // translation. This can include specifying the set of required and/or |
| // irrelevant target features, allowing for an additional deduplication step |
| // when the only difference between two variants is a set of irrelevant |
| // features. As a result, this pipeline is optional. |
| // |
| // The expected input to this pipeline might look like: |
| // hal.executable @some_executable { |
| // hal.interface @main_io { |
| // hal.interface.binding @arg0, set=0, binding=0, ... |
| // hal.interface.binding @arg1, set=0, binding=1, ... |
| // } |
| // hal.executable.variant @target, target="target-backend" { |
| // hal.executable.export @main interface(@main_io) { |
| // ordinal = 0 : index |
| // } |
| // module { |
| // func.func @main ... |
| // } |
| // } |
| // } |
| // |
| // As output, structurally the variant should be very similar: |
| // hal.executable @some_executable { |
| // hal.interface @main_io { |
| // hal.interface.binding @arg0, set=0, binding=0, ... |
| // hal.interface.binding @arg1, set=0, binding=1, ... |
| // } |
| // hal.executable.variant @target, target="target-backend" { |
| // hal.executable.export @main interface(@main_io) |
| // {attrs = #target_specific_translation_attr<...>} { |
| // ordinal = 0 : index |
| // } |
| // module { |
| // func.func @main ... |
| // } |
| // } |
| // } |
| virtual void |
| buildConfigurationPassPipeline(IREE::HAL::ExecutableTargetAttr targetAttr, |
| OpPassManager &passManager) {} |
| |
| // Inserts passes used to translate the `hal.executable.variant` op contents. |
| // The pass manager will be nested on `hal.executable` such that the pipeline |
| // will only run on executable contents. |
| // |
| // Backend transformation passes must check that the source op they receive |
| // is for them using the `target_backend` attribute. Backends may have |
| // multiple source ops in the same executable to transform such as when |
| // multiple target configurations are requested. |
| // |
| // For example, as input: |
| // hal.executable @some_executable { |
| // hal.interface @main_io { |
| // hal.interface.binding @arg0, set=0, binding=0, ... |
| // hal.interface.binding @arg1, set=0, binding=1, ... |
| // } |
| // hal.executable.variant @target, target="target-backend" { |
| // hal.executable.export @main interface(@main_io) { |
| // ordinal = 0 : index |
| // } |
| // module { ... (annotated for translation) } |
| // } |
| // } |
| // |
| // As output: |
| // hal.executable @some_executable { |
| // hal.interface @main_io ... |
| // hal.executable.variant @target, target="target-backend" { |
| // hal.executable.export @main ... |
| // module { spirv.module { ... } } |
| // } |
| // } |
| virtual void |
| buildTranslationPassPipeline(IREE::HAL::ExecutableTargetAttr targetAttr, |
| OpPassManager &passManager) = 0; |
| |
| // Inserts passes used to link `hal.executable.variant` ops together. |
| // The pass manager will be nested on the parent module of `hal.executable` |
| // ops and the pipeline will need to find relevant variant ops itself. |
| // |
| // Implementations should clone executable contents (including interfaces, |
| // entry points, and functions) into new executables and update any relevant |
| // references as they do so. |
| // |
| // Which executable variants to link together and how many new executables to |
| // produce are left to implementations to determine. For example, an |
| // implementation may choose to link all executables (even with different |
| // interfaces) into a single combined executable, or it could choose to limit |
| // the number linked together in order to shard binary size across multiple |
| // executables. |
| // |
| // For example, as input: |
| // hal.executable @some_executable_0 { |
| // hal.interface... |
| // hal.executable.variant @target_a, target="target-backend" { |
| // module { ... } |
| // } |
| // } |
| // hal.executable @some_executable_1 { |
| // hal.interface... |
| // hal.executable.variant @target_b, target="target-backend" { |
| // module { ... } |
| // } |
| // hal.executable.variant @target_c, target="other-backend" { |
| // module { ... } |
| // } |
| // } |
| // |
| // As output: |
| // hal.executable @some_executable_1 { // untouched, not relevant |
| // hal.interface... |
| // hal.executable.variant @target_c, target="other-backend" { |
| // module { ... } |
| // } |
| // } |
| // hal.executable @some_executable_linked { |
| // hal.interface... |
| // hal.executable.variant @target_a, target="target-backend" { |
| // module { ... } |
| // } |
| // hal.executable.variant @target_b, target="target-backend" { |
| // module { ... } |
| // } |
| // } |
| virtual void buildLinkingPassPipeline(OpPassManager &passManager) {} |
| |
| struct SerializationOptions { |
| // Debug level for serialization (0-3). |
| int debugLevel; |
| // File name prefix used when creating scratch files. |
| // This contains the module and executable name in canonical form. |
| // Example: some_module_executable_43 |
| std::string dumpBaseName; |
| // Optional path to write temporary/intermediate files into. |
| std::string dumpIntermediatesPath; |
| // Optional path to write serialized binary results into. |
| std::string dumpBinariesPath; |
| }; |
| |
| // Serializes the given |variantOp| executable produced by this backend to one |
| // or more binary byte buffer formats used for storage in the module file. |
| // Implementations should insert `hal.executable.binary` ops for each format |
| // (such as x64 and arm64 for compiled LLVM blobs, etc). |
| // |
| // If no serialization is provided then lowering the parent module into a |
| // binary format (such as to the IREE VM) will fail. |
| virtual LogicalResult |
| serializeExecutable(const SerializationOptions &options, |
| IREE::HAL::ExecutableVariantOp variantOp, |
| OpBuilder &executableBuilder) { |
| assert(false && "unimplemented serializeExecutable"); |
| return failure(); |
| } |
| }; |
| |
| // Returns a sorted uniqued set of target backends used in the executable. |
| SmallVector<std::string> |
| gatherExecutableTargetNames(IREE::HAL::ExecutableOp executableOp); |
| |
| // Returns a sorted uniqued set of target backends used in the entire module. |
| SmallVector<std::string> gatherExecutableTargetNames(mlir::ModuleOp moduleOp); |
| |
| // Dumps binary data to a file formed by joining the given path components: |
| // `path/baseName_suffix[extension]` |
| void dumpDataToPath(StringRef path, StringRef baseName, StringRef suffix, |
| StringRef extension, StringRef data); |
| template <typename T> |
| void dumpDataToPath(StringRef path, StringRef baseName, StringRef suffix, |
| StringRef extension, ArrayRef<T> data) { |
| dumpDataToPath(path, baseName, suffix, extension, |
| StringRef(reinterpret_cast<const char *>(data.data()), |
| data.size() * sizeof(T))); |
| } |
| |
| } // namespace mlir::iree_compiler::IREE::HAL |
| |
| #endif // IREE_COMPILER_DIALECT_HAL_TARGET_TARGETBACKEND_H_ |