blob: 20d172d19ff349d86a42527e8f354d4593926d30 [file]
// Copyright 2023 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_DIALECT_HAL_PASSES
#define IREE_DIALECT_HAL_PASSES
include "mlir/Pass/PassBase.td"
//===----------------------------------------------------------------------===//
// Conversion
//===----------------------------------------------------------------------===//
def ConvertToHALPass :
Pass<"iree-hal-conversion", "mlir::ModuleOp"> {
let summary = "Converts from stream and other intermediate dialects into the hal dialect.";
let description = [{
Converts supported intermediate dialects (`stream`, `util`, and various
upstream dialects like `cf`/`scf`) into the hal dialect. After conversion
host code scheduling work and allocations will act on `!hal.device` queues
and `!hal.buffer` (and other) resources.
It's expected that executable interface materialization has been performed
so that the information required to marshal buffers and operands to the
device is available for conversion.
}];
let dependentDialects = [
"mlir::arith::ArithDialect",
"mlir::func::FuncDialect",
"mlir::scf::SCFDialect",
"IREE::HAL::HALDialect",
"IREE::Stream::StreamDialect",
"IREE::Util::UtilDialect",
// TODO(benvanik): add a registration system for extra dialects?
"IREE::IO::Parameters::IOParametersDialect",
];
}
//===----------------------------------------------------------------------===//
// Device management
//===----------------------------------------------------------------------===//
def AssignLegacyTargetDevicesPass :
Pass<"iree-hal-assign-legacy-target-devices", "mlir::ModuleOp"> {
let summary = "Assigns the HAL devices the module will target to the given list of targets.";
let description = [{
Assigns target HAL devices to the module based on the given list.
}];
let options = [
Option<
"targetRegistry", "target-registry",
"llvm::cl::TargetRegistryRef", "",
"Target registry containing the list of available devices and backends."
>,
ListOption<
"targetBackends", "targetBackends",
"std::string",
"List of target backends to assign as device targets."
>,
];
let dependentDialects = [
"IREE::HAL::HALDialect",
];
}
def AssignTargetDevicesPass :
Pass<"iree-hal-assign-target-devices", "mlir::ModuleOp"> {
let summary = "Assigns the HAL devices the module will target to the given list of target specifications.";
let description = [{
Assigns target HAL devices to the module based on the given list of target
specifications.
Targets can be specified in several ways depending on whether there are
multiple devices, named devices, or devices imported from external files.
Human-friendly device aliases can be used as shorthand for
`IREE::HAL::TargetDevice` implementations providing their own configuration.
The aliases are identical to those used by `#hal.device.alias<>`.
If multiple targets are specified they will be available as multiple
distinct devices. A single device may select from one or more targets such
that the first enumerated that matches at runtime will be selected. For
example a `gpu` device may select between CUDA, HIP, or Vulkan at runtime
based on what kind of device the user has and what HAL implementations were
compiled into the runtime.
Examples using the canonical flag:
```mlir
// Two devices, one the local host device and the other a Vulkan device:
--iree-hal-target-device=local
--iree-hal-target-device=vulkan
// One device selecting between Vulkan if available and otherwise use the
// local host device:
--iree-hal-target-device=vulkan,local
// Two CUDA devices selected by runtime ordinal; at runtime two --device=
// flags are required to configure both devices:
--iree-hal-target-device=cuda[0]
--iree-hal-target-device=cuda[1]
// A fully-defined target specification:
--iree-hal-target-device=#hal.device.target<"cuda", {...}, [#hal.executable.target<...>]>
// Named device for defining a reference by #hal.device.promise<@some_name>:
--iree-hal-target-device=some_name=vulkan
```
}];
let options = [
ListOption<
"targetDevices", "targetDevices",
"std::string",
"List of target device specifications."
>,
];
let dependentDialects = [
"IREE::HAL::HALDialect",
];
}
def MaterializeTargetDevicesPass :
Pass<"iree-hal-materialize-target-devices", "mlir::ModuleOp"> {
let summary = "Materializes global device handles based on a `hal.device.targets` spec.";
let description = [{
Materializes global `!hal.device` ops for the devices specified by the
`hal.device.targets` attribute on the module. An optional default device can
be specified to assign to ops that do not have a default device specified.
}];
let options = [
Option<
"defaultDevice", "defaultDevice",
"std::string", "",
"Which device is considered the default when no device affinity is specified."
>,
];
let dependentDialects = [
"IREE::HAL::HALDialect",
"IREE::Util::UtilDialect",
];
}
def ResolveDevicePromisesPass :
Pass<"iree-hal-resolve-device-promises", "mlir::ModuleOp"> {
let summary = "Resolves `#hal.device.promise` attributes to their devices.";
let description = [{
Resolves promised device affinities to the materialized device globals that
were promised. Verifies that all promises are resolved.
}];
let dependentDialects = [
"IREE::HAL::HALDialect",
];
}
def ResolveDeviceAliasesPass :
Pass<"iree-hal-resolve-device-aliases", "mlir::ModuleOp"> {
let summary = "Resolves `#hal.device.alias` attributes to their expanded configurations.";
let description = [{
Resolves device aliases to the concrete targets using defaults, flags, and
registered device configurations.
}];
let options = [
Option<
"targetRegistry", "target-registry",
"llvm::cl::TargetRegistryRef", "",
"Target registry containing the list of available devices and backends."
>,
];
let dependentDialects = [
"IREE::HAL::HALDialect",
];
}
def VerifyDevicesPass :
Pass<"iree-hal-verify-devices", "mlir::ModuleOp"> {
let summary = "Verifies that all devices can be targeted with the available compiler plugins.";
let description = [{
Verifies that `#hal.device.target` and `#hal.executable.target` attributes
reference targets that are registered with the compiler.
}];
let options = [
Option<
"targetRegistry", "target-registry",
"llvm::cl::TargetRegistryRef", "",
"Target registry containing the list of available devices and backends."
>,
];
}
def FixupLegacySyncPass :
Pass<"iree-hal-fixup-legacy-sync", "mlir::ModuleOp"> {
let summary = "Applies fixups to the program for when using legacy HAL devices.";
let description = [{
Applies fixups to the program for when using legacy HAL devices that only
support synchronous execution. Once all devices support async this will be
removed.
NOTE: this pass only exists for backwards compatibility with legacy HAL
drivers. It will be removed once all have migrated to the modern async APIs.
}];
let dependentDialects = [
"mlir::arith::ArithDialect",
"IREE::HAL::HALDialect",
"IREE::Util::UtilDialect",
];
}
def InlineMemoizeRegionsPass :
Pass<"iree-hal-inline-memoize-regions", ""> {
let summary = "Inlines `hal.device.memoize` regions into their parent region.";
let description = [{
Inlines any `hal.device.memoize` ops into their parent region and removes
the op. This prevents memoization and has the same behavior as having never
formed the memoization regions.
}];
let dependentDialects = [
"mlir::cf::ControlFlowDialect",
"IREE::HAL::HALDialect",
];
}
def OutlineMemoizeRegionsPass :
Pass<"iree-hal-outline-memoize-regions", "mlir::ModuleOp"> {
let summary = "Outlines `hal.device.memoize` regions and creates global resources.";
let description = [{
Outlines any `hal.device.memoize` ops in the module by creating functions
and per-device globals with initializers.
}];
let dependentDialects = [
"mlir::arith::ArithDialect",
"mlir::cf::ControlFlowDialect",
"mlir::scf::SCFDialect",
"IREE::HAL::HALDialect",
"IREE::Util::UtilDialect",
];
}
def MemoizeDeviceQueriesPass :
Pass<"iree-hal-memoize-device-queries", "mlir::ModuleOp"> {
let summary = "Finds hal.device.query ops and creates variables initialized on startup.";
let description = [{
Finds all `hal.device.query`-related ops that are hoistable and moves them
into globals that are initialized on startup. This prevents repeated queries
at runtime and allows for optimization as queries are CSEd across the entire
program.
}];
let dependentDialects = [
"mlir::arith::ArithDialect",
"IREE::HAL::HALDialect",
"IREE::Util::UtilDialect",
];
}
//===----------------------------------------------------------------------===//
// Executable translation
//===----------------------------------------------------------------------===//
def MaterializeInterfacesPass :
Pass<"iree-hal-materialize-interfaces", "mlir::ModuleOp"> {
let summary = "Defines hal.executable variants for stream.executable ops.";
let description = [{
Defines hal.executables and one hal.variant for each required target. The
interfaces required to marshal buffers and operands across the host-device
boundary are declared on the executables and annotated on the dispatch
sites so that subsequent conversion can consume them.
}];
let dependentDialects = [
"mlir::arith::ArithDialect",
"IREE::HAL::HALDialect",
];
}
def SubstituteExecutablesPass :
Pass<"iree-hal-substitute-executables", "mlir::ModuleOp"> {
let summary = "Substitutes hal.executable ops with files on disk.";
let description = [{
Substitutes hal.executable ops with externally referenced MLIR files or
target-specific object files. When provided a .mlir/.mlirbc file with a
top-level hal.executable the entire executable will be replaced including
all variants contained with. All other files such as .o, .bc, and .spv will
be set as external object files on the original executable variants and the
original contents will be dropped.
Substitutions can be specified by providing a file system path where there
exists files matching the executable names in one of the supported formats
or by specifying the file each executable name maps to directly.
}];
let options = [
ListOption<
"substitutions", "substitutions",
"std::string",
"Substitution `executable_name=file.xxx` key-value pairs."
>,
Option<
"searchPath", "search-path",
"std::string", "",
"Path to source executable substitutions from."
>,
];
let dependentDialects = [
// NOTE: we may need more for anything we load from MLIR files.
"IREE::HAL::HALDialect",
];
}
def PreprocessExecutablesWithPipelinePass :
Pass<"iree-hal-preprocess-executables-with-pipeline", "IREE::HAL::ExecutableOp"> {
let summary = "Preprocess each executable with an MLIR pass pipeline.";
let description = [{
Runs the given MLIR pass pipeline as parsed by the `--pass-pipeline=` flag
on each hal.executable in the program. The passes must be linked into the
compiler to be discovered.
}];
let options = [
Option<
"pipeline", "pipeline",
"std::string", "",
"MLIR pass pipeline description to run on each executable."
>,
];
}
def PreprocessExecutablesWithToolPass :
Pass<"iree-hal-preprocess-executables-with-tool", "IREE::HAL::ExecutableOp"> {
let summary = "Preprocess each executable with an external command line tool.";
let description = [{
Passes each hal.executable in the program to the given command line tool
as stdin and parses the resulting MLIR from stdout to replace them. This
is equivalent to `iree-hal-preprocess-executables-with-pipeline` but allows
for an external `mlir-opt`/`iree-opt`-like tool to be used containing the
pipelines instead of requiring the passes to be linked into the compiler.
}];
let options = [
Option<
"command", "command",
"std::string", "",
"stdin->stdout command to run on each hal.executable MLIR op."
>,
];
}
def ConfigureExecutablesPass :
Pass<"iree-hal-configure-executables", "IREE::HAL::ExecutableOp"> {
let summary = "Configures hal.executable ops via a nested translation pipeline.";
let description = [{
Runs a nested pipeline on each executable to attach target-specific
configuration information to variants.
}];
let options = [
Option<
"targetRegistry", "target-registry",
"llvm::cl::TargetRegistryRef", "",
"Target registry containing the list of available devices and backends."
>,
];
}
def ConfigureTargetExecutableVariantsPass :
Pass<"iree-hal-configure-target-executable-variants", "IREE::HAL::ExecutableVariantOp"> {
let summary = "Configures hal.executable.variant ops for the specified target backend.";
let description = [{
Attaches target-specific configuration information to a variant controlling
how code generation operates.
}];
let options = [
Option<
"targetRegistry", "target-registry",
"llvm::cl::TargetRegistryRef", "",
"Target registry containing the list of available devices and backends."
>,
Option<
"target", "target",
"std::string", "",
"Target backend name whose executable variants will be configured by this pass."
>,
];
}
def TranslateAllExecutablesPass :
Pass<"iree-hal-translate-all-executables", "IREE::HAL::ExecutableOp"> {
let summary = "Translates hal.executable ops via a nested translation pipeline.";
let description = [{
Runs a nested pipeline on each executable to translate its variants from
their generic MLIR dialects (such as `linalg`) to their target-specific
dialects (`llvm`, `spirv`, etc).
}];
let options = [
Option<
"targetRegistry", "target-registry",
"llvm::cl::TargetRegistryRef", "",
"Target registry containing the list of available devices and backends."
>,
];
}
def TranslateTargetExecutableVariantsPass :
Pass<"iree-hal-translate-target-executable-variants", "IREE::HAL::ExecutableVariantOp"> {
let summary = "Translates hal.executable.variant ops for the specified target backend.";
let description = [{
Translates an executable variant for a specific target from its generic
MLIR dialects (such as `linalg`) to the target-specific dialects (`llvm`,
`spirv`, etc).
}];
let options = [
Option<
"targetRegistry", "target-registry",
"llvm::cl::TargetRegistryRef", "",
"Target registry containing the list of available devices and backends."
>,
Option<
"target", "target",
"std::string", "",
"Target backend name whose executable variants will be translated by this pass."
>,
];
}
def HoistExecutableObjectsPass :
Pass<"iree-hal-hoist-executable-objects", "IREE::HAL::ExecutableVariantOp"> {
let summary = "Hoists local executable object annotations to the parent `hal.executable.variant`.";
let description = [{
Finds all `hal.executable.objects` attrs on all ops within an executable
inner module and moves them to the parent `hal.executable.variant` op.
}];
}
def PruneExecutablesPass :
Pass<"iree-hal-prune-executables", "mlir::ModuleOp"> {
let summary = "Prunes executable variants and exports that are not referenced.";
let description = [{
Prunes executable variants and exports that are not referenced in the
module. This is intended to be run late in the pipeline where no new
dispatches will be inserted that may require the variants or exports that it
removes.
}];
}
def LinkAllExecutablesPass :
Pass<"iree-hal-link-all-executables", "mlir::ModuleOp"> {
let summary = "Links hal.executable ops into one or more hal.executable ops.";
let description = [{
Runs a nested pipeline to link multiple `hal.executable` ops together if the
target backend the executables are used with desires.
}];
let options = [
Option<
"targetRegistry", "target-registry",
"llvm::cl::TargetRegistryRef", "",
"Target registry containing the list of available devices and backends."
>,
];
}
def LinkTargetExecutablesPass :
Pass<"iree-hal-link-target-executables", "mlir::ModuleOp"> {
let summary = "Links executables for the specified target backend.";
let description = [{
Links together multiple `hal.executable` ops for the given target backend if
desired. Linking allows for intra-module deduplication and amortization of
startup time, code size, and runtime overheads that come from managing
multiple hundreds/thousands of executables.
}];
let options = [
Option<
"targetRegistry", "target-registry",
"llvm::cl::TargetRegistryRef", "",
"Target registry containing the list of available devices and backends."
>,
Option<
"target", "target",
"std::string", "",
"Target backend name whose executables will be linked by this pass."
>,
];
}
def ResolveExportOrdinalsPass :
Pass<"iree-hal-resolve-export-ordinals", "mlir::ModuleOp"> {
let summary = "Resolves symbolic hal.executable.export references to ordinals.";
let description = [{
Severs symbolic references to hal.executable.export ops from dispatch sites
by replacing them with the ordinal assigned to the exports. This allows for
subsequent passes to collapse the executables into opaque blobs.
}];
let dependentDialects = [
"arith::ArithDialect",
"IREE::HAL::HALDialect",
];
}
def SerializeAllExecutablesPass :
Pass<"iree-hal-serialize-all-executables", "IREE::HAL::ExecutableOp"> {
let summary = "Converts hal.executable.variants to one or more hal.executable.binary ops.";
let description = [{
Runs a nested pipeline on each executable to serialize its variants from
their low-level MLIR dialects (such as `llvm`, `spirv`, etc) to their
target-specific object format (static/shared libraries, SPIR-V, etc).
}];
let options = [
Option<
"targetRegistry", "target-registry",
"llvm::cl::TargetRegistryRef", "",
"Target registry containing the list of available devices and backends."
>,
Option<
"debugLevel", "debug-level",
"int", "2",
"Debug level for serialization (0 (no information) to 3 (all information))."
>,
Option<
"dumpIntermediatesPath", "dump-intermediates-path",
"std::string", "",
"Path to write translated executable intermediates (.bc, .o, etc) into for debugging."
>,
Option<
"dumpBinariesPath", "dump-binaries-path",
"std::string", "",
"Path to write translated and serialized executable binaries into for debugging."
>,
];
}
def SerializeTargetExecutablesPass :
Pass<"iree-hal-serialize-target-executables", "IREE::HAL::ExecutableOp"> {
let summary = "Serializes executables for the specified target backend.";
let description = [{
Serializes variants for the target backend from their low-level MLIR
dialects (such as `llvm`, `spirv`, etc) to their target-specific object
format (static/shared libraries, SPIR-V, etc).
}];
let options = [
Option<
"targetRegistry", "target-registry",
"llvm::cl::TargetRegistryRef", "",
"Target registry containing the list of available devices and backends."
>,
Option<
"target", "target",
"std::string", "",
"Target backend name whose executables will be serialized by this pass."
>,
Option<
"debugLevel", "debug-level",
"int", "2",
"Debug level for serialization (0 (no information) to 3 (all information))."
>,
Option<
"dumpIntermediatesPath", "dump-intermediates-path",
"std::string", "",
"Path to write translated executable intermediates (.bc, .o, etc) into for debugging."
>,
Option<
"dumpBinariesPath", "dump-binaries-path",
"std::string", "",
"Path to write translated and serialized executable binaries into for debugging."
>,
];
}
//===----------------------------------------------------------------------===//
// Resource initialization, caching, and optimization
//===----------------------------------------------------------------------===//
def MaterializeDispatchInstrumentationPass :
Pass<"iree-hal-materialize-dispatch-instrumentation", "mlir::ModuleOp"> {
let summary = "Materializes host and device dispatch instrumentation resources on stream IR.";
let description = [{
Adds dispatch instrumentation for both host and device prior to
materializing interfaces so that the higher-level stream dialect can be used
to easily mutate the dispatch sites, executable exports, and resources used
for instrumentation storage.
}];
let options = [
Option<
"bufferSize", "buffer-size",
"llvm::cl::PowerOf2ByteSize", "llvm::cl::PowerOf2ByteSize(64 * 1024 * 1024)",
"Power-of-two byte size of the instrumentation buffer."
>,
];
let dependentDialects = [
"mlir::arith::ArithDialect",
"IREE::HAL::HALDialect",
"IREE::Stream::StreamDialect",
"IREE::Util::UtilDialect",
];
}
def InitializeDevicesPass :
Pass<"iree-hal-initialize-devices", "mlir::ModuleOp"> {
let summary = "Initializes global device handles based on their specification.";
let description = [{
Initializes each global `!hal.device` based on the specification attribute
by building initializers that enumerate and select the appropriate device.
}];
let options = [
Option<
"targetRegistry", "target-registry",
"llvm::cl::TargetRegistryRef", "",
"Target registry containing the list of available devices and backends."
>,
];
let dependentDialects = [
"mlir::arith::ArithDialect",
"mlir::scf::SCFDialect",
"IREE::HAL::HALDialect",
"IREE::Util::UtilDialect",
];
}
def MaterializeResourceCachesPass :
Pass<"iree-hal-materialize-resource-caches", "mlir::ModuleOp"> {
let summary = "Materializes cached globals for device resources.";
let description = [{
Scans the program for resource lookups such as `hal.executable.lookup` and
materializes globals initialized on startup. The original lookup ops are
replaced with global loads of the cached resources.
}];
let dependentDialects = [
"mlir::arith::ArithDialect",
"mlir::scf::SCFDialect",
"IREE::HAL::HALDialect",
"IREE::Util::UtilDialect",
];
}
def ElideRedundantCommandsPass :
Pass<"iree-hal-elide-redundant-commands", ""> {
let summary = "Elides stateful command buffer ops that set redundant state.";
let description = [{
Identifies sequences of stateful command buffer operations such as
`hal.command_buffer.push_descriptor_set` that set redundant state that arise
from trivial conversion from the stateless stream dialect and removes them
to reduce binary size and runtime overhead.
}];
let dependentDialects = [
"IREE::HAL::HALDialect",
];
}
//===----------------------------------------------------------------------===//
// Benchmarking and debugging utilities
//===----------------------------------------------------------------------===//
def CaptureExecutableSourcesPass :
Pass<"iree-hal-capture-executable-sources", "mlir::ModuleOp"> {
let summary = "Captures individual hal.executable.variant source listings and embeds them in the IR.";
let description = [{
Captures a source listing of each hal.executable.variant and attaches the
source to the variant embedded in the IR. Entry points are assigned
locations in the IR relative to the captured source.
}];
let options = [
Option<
"stage", "stage",
"std::string", "\"source\"",
"Name used to indicate what stage of compilation is captured."
>,
];
}
def DumpExecutableSourcesPass :
Pass<"iree-hal-dump-executable-sources", "mlir::ModuleOp"> {
let summary = "Dumps individual hal.executable source listings to the provided path.";
let description = [{
Dumps a source listing of each hal.executable and updates the source
locations in the IR to point at the produced files. This allows for easy
inspection of each executable prior to translation and gives downstream
tools that can display source information (Tracy, perf, etc) something more
useful than the entire original source program.
}];
let options = [
Option<
"path", "path",
"std::string", "",
"File system path to write each executable source MLIR file."
>,
Option<
"prefix", "prefix",
"std::string", "",
"String to prefix the written file names with."
>,
];
}
def DumpExecutableBenchmarksPass :
Pass<"iree-hal-dump-executable-benchmarks", "mlir::ModuleOp"> {
let summary = "Dumps standalone hal.executable benchmarks to the provided path.";
let description = [{
Dumps one MLIR file per hal.executable containing the executable contents
and the host code required to dispatch them with fake buffers and operands.
These benchmarks can be run with the `iree-benchmark-module` tool to
microbenchmark individual dispatches outside of the whole program context.
The pass can only be run after executable translation but before host code
conversion as the original stream dialect ops are required to synthesize
the benchmarks.
There are many caveats with this approach and it will fail to generate
benchmarks in many cases such as dynamic shapes, dynamic operands, or
stateful data dependencies. Users should always prefer to build dedicated
benchmarks in their origin framework that can be guaranteed to match their
expectations and use appropriate test data. For example some dispatches may
produce NaNs or out-of-bounds accesses with the fake data generated by this
pass and either crash or result in unrepresentative performance.
In other words: don't blindly expect this pass to do anything but act as a
starting point for microbenchmarking. Verify the outputs, the benchmarking
methodology for the particular dispatch, and prepare to do more work. Or
just author proper benchmarks in the original framework!
}];
let options = [
Option<
"path", "path",
"std::string", "",
"File system path to write each executable benchmark MLIR file."
>,
];
let dependentDialects = [
"mlir::arith::ArithDialect",
"mlir::scf::SCFDialect",
"IREE::HAL::HALDialect",
"IREE::Util::UtilDialect",
];
}
def StripExecutableContentsPass :
Pass<"iree-hal-strip-executable-contents", "mlir::ModuleOp"> {
let summary = "Strips executable module contents for reducing IR size during debugging.";
let description = [{
A debugging pass for stripping translated executable contents (LLVM dialect,
SPIR-V dialect, etc) to reduce IR size and noise from the device-only code.
}];
}
def RepeatDispatchesPass :
Pass<"iree-hal-repeat-dispatches", ""> {
let summary = "Repeats each hal.command_buffer.dispatch op one or more times.";
let description = [{
Finds all hal.command_buffer.dispatch ops and repeats them the specified
number of times by cloning them and inserting a barrier. This is extremely
unreliable and nearly always creates incorrect programs that have wildly
incorrect end-to-end execution timings. It must only be used when trying to
profile (via sampling or performance counters) specific dispatches in-situ
with the additional caveat that cache behavior and dispatch overhead are
invalid. Do not trust any numbers produced by this method of benchmarking
without verifying via external tooling.
This should rarely be used. Prefer instead to build real benchmarks in
origin frameworks that, for example, use independent data and ensure correct
execution results (as if you're benchmarking known-incorrect results, are
you really benchmarking something useful?). Any benchmarking of memory-bound
operations using this approach will be questionable (such as matmuls, which
we use this for today... heh ;).
}];
let options = [
Option<
"repeatCount", "count",
"unsigned", "1",
"Number of times to repeat each dispatch (including the original)."
>,
];
let dependentDialects = [
"IREE::HAL::HALDialect",
];
}
#endif // IREE_DIALECT_HAL_PASSES