Updating HAL passes to use tablegen. (#15952)

No functional changes, just boilerplate churn from C++ to tablegen and
other cleanup.
diff --git a/compiler/src/iree/compiler/Dialect/HAL/Target/TargetRegistry.cpp b/compiler/src/iree/compiler/Dialect/HAL/Target/TargetRegistry.cpp
index 4860b91..2bac06c 100644
--- a/compiler/src/iree/compiler/Dialect/HAL/Target/TargetRegistry.cpp
+++ b/compiler/src/iree/compiler/Dialect/HAL/Target/TargetRegistry.cpp
@@ -130,3 +130,34 @@
 }
 
 } // namespace mlir::iree_compiler::IREE::HAL
+
+namespace llvm::cl {
+template class basic_parser<TargetBackendRegistryRef>;
+} // namespace llvm::cl
+
+using TargetBackendRegistryRef = llvm::cl::TargetBackendRegistryRef;
+
+// Return true on error.
+bool llvm::cl::parser<TargetBackendRegistryRef>::parse(
+    Option &O, StringRef ArgName, StringRef Arg,
+    TargetBackendRegistryRef &Val) {
+  // We ignore Arg here and just use the global registry. We could parse a list
+  // of target backends and create a new registry with just that subset but
+  // ownership gets tricky.
+  if (Arg != "global")
+    return true;
+  Val.value =
+      &mlir::iree_compiler::IREE::HAL::TargetBackendRegistry::getGlobal();
+  return false;
+}
+
+void llvm::cl::parser<TargetBackendRegistryRef>::printOptionDiff(
+    const Option &O, TargetBackendRegistryRef V, const OptVal &Default,
+    size_t GlobalWidth) const {
+  printOptionName(O, GlobalWidth);
+  std::string Str = "global";
+  outs() << "= " << Str;
+  outs().indent(2) << " (default: global)\n";
+}
+
+void llvm::cl::parser<TargetBackendRegistryRef>::anchor() {}
diff --git a/compiler/src/iree/compiler/Dialect/HAL/Target/TargetRegistry.h b/compiler/src/iree/compiler/Dialect/HAL/Target/TargetRegistry.h
index 6927c13..07f933d 100644
--- a/compiler/src/iree/compiler/Dialect/HAL/Target/TargetRegistry.h
+++ b/compiler/src/iree/compiler/Dialect/HAL/Target/TargetRegistry.h
@@ -15,6 +15,7 @@
 #include "llvm/ADT/ArrayRef.h"
 #include "llvm/ADT/StringMap.h"
 #include "llvm/ADT/StringRef.h"
+#include "llvm/Support/CommandLine.h"
 
 namespace mlir::iree_compiler::IREE::HAL {
 
@@ -96,4 +97,44 @@
 
 } // namespace mlir::iree_compiler::IREE::HAL
 
+namespace llvm::cl {
+
+struct TargetBackendRegistryRef {
+  const mlir::iree_compiler::IREE::HAL::TargetBackendRegistry *value =
+      &mlir::iree_compiler::IREE::HAL::TargetBackendRegistry::getGlobal();
+  TargetBackendRegistryRef() = default;
+  TargetBackendRegistryRef(
+      const mlir::iree_compiler::IREE::HAL::TargetBackendRegistry &value)
+      : value(&value) {}
+  TargetBackendRegistryRef(
+      const mlir::iree_compiler::IREE::HAL::TargetBackendRegistry *value)
+      : value(value) {}
+  operator bool() const noexcept {
+    return value->getRegisteredTargetBackends() !=
+           mlir::iree_compiler::IREE::HAL::TargetBackendRegistry::getGlobal()
+               .getRegisteredTargetBackends();
+  }
+  const mlir::iree_compiler::IREE::HAL::TargetBackendRegistry *
+  operator->() const {
+    return value;
+  }
+};
+
+extern template class basic_parser<TargetBackendRegistryRef>;
+
+template <>
+class parser<TargetBackendRegistryRef>
+    : public basic_parser<TargetBackendRegistryRef> {
+public:
+  parser(Option &O) : basic_parser(O) {}
+  bool parse(Option &O, StringRef ArgName, StringRef Arg,
+             TargetBackendRegistryRef &Val);
+  StringRef getValueName() const override { return "target backend registry"; }
+  void printOptionDiff(const Option &O, TargetBackendRegistryRef V,
+                       const OptVal &Default, size_t GlobalWidth) const;
+  void anchor() override;
+};
+
+} // namespace llvm::cl
+
 #endif // IREE_COMPILER_DIALECT_HAL_TARGET_TARGETREGISTRY_H_
diff --git a/compiler/src/iree/compiler/Dialect/HAL/Transforms/AssignTargetDevices.cpp b/compiler/src/iree/compiler/Dialect/HAL/Transforms/AssignTargetDevices.cpp
index 4ff28f6..e108e0b 100644
--- a/compiler/src/iree/compiler/Dialect/HAL/Transforms/AssignTargetDevices.cpp
+++ b/compiler/src/iree/compiler/Dialect/HAL/Transforms/AssignTargetDevices.cpp
@@ -23,36 +23,28 @@
 
 namespace mlir::iree_compiler::IREE::HAL {
 
-class AssignTargetDevicesPass
-    : public PassWrapper<AssignTargetDevicesPass, OperationPass<ModuleOp>> {
-public:
-  AssignTargetDevicesPass()
-      : targetRegistry(TargetBackendRegistry::getGlobal()) {}
-  AssignTargetDevicesPass(const AssignTargetDevicesPass &pass)
-      : targetRegistry(pass.targetRegistry) {}
-  AssignTargetDevicesPass(const TargetBackendRegistry &targetRegistry,
-                          ArrayRef<std::string> targets)
-      : targetRegistry(targetRegistry) {
-    this->targets = targets;
-  }
+#define GEN_PASS_DEF_ASSIGNTARGETDEVICESPASS
+#include "iree/compiler/Dialect/HAL/Transforms/Passes.h.inc"
 
+namespace {
+
+//===----------------------------------------------------------------------===//
+// --iree-hal-assign-target-devices
+//===----------------------------------------------------------------------===//
+
+struct AssignTargetDevicesPass
+    : public IREE::HAL::impl::AssignTargetDevicesPassBase<
+          AssignTargetDevicesPass> {
+  using IREE::HAL::impl::AssignTargetDevicesPassBase<
+      AssignTargetDevicesPass>::AssignTargetDevicesPassBase;
   void getDependentDialects(DialectRegistry &registry) const override {
     registry.insert<IREE::HAL::HALDialect>();
-    for (auto &targetBackend : targetRegistry.getTargetBackends(
-             targetRegistry.getRegisteredTargetBackends())) {
+    for (auto &targetBackend : targetRegistry->getTargetBackends(
+             targetRegistry->getRegisteredTargetBackends())) {
       targetBackend->getDependentDialects(registry);
     }
   }
 
-  StringRef getArgument() const override {
-    return "iree-hal-assign-target-devices";
-  }
-
-  StringRef getDescription() const override {
-    return "Assigns the HAL devices the module will target to the given list "
-           "of targets.";
-  }
-
   void runOnOperation() override {
     auto moduleOp = getOperation();
 
@@ -76,13 +68,13 @@
     llvm::SmallDenseSet<Attribute> targetAttrSet;
     SmallVector<Attribute> targetAttrs;
     for (const auto &targetName : targets) {
-      auto targetBackend = targetRegistry.getTargetBackend(targetName);
+      auto targetBackend = targetRegistry->getTargetBackend(targetName);
       if (!targetBackend) {
         std::string backends;
         llvm::raw_string_ostream os(backends);
         llvm::interleaveComma(
-            targetRegistry.getTargetBackends(
-                targetRegistry.getRegisteredTargetBackends()),
+            targetRegistry->getTargetBackends(
+                targetRegistry->getRegisteredTargetBackends()),
             os,
             [&os](const std::shared_ptr<
                   mlir::iree_compiler::IREE::HAL::TargetBackend>
@@ -106,23 +98,8 @@
     moduleOp->setAttr("hal.device.targets",
                       ArrayAttr::get(moduleOp.getContext(), targetAttrs));
   }
-
-private:
-  ListOption<std::string> targets{*this, "targets",
-                                  llvm::cl::desc("List of devices to target."),
-                                  llvm::cl::ZeroOrMore};
-
-  const TargetBackendRegistry &targetRegistry;
 };
 
-std::unique_ptr<OperationPass<ModuleOp>>
-createAssignTargetDevicesPass(const TargetBackendRegistry &targetRegistry,
-                              ArrayRef<std::string> targets) {
-  return std::make_unique<AssignTargetDevicesPass>(targetRegistry, targets);
-}
-
-static PassRegistration<AssignTargetDevicesPass> pass([] {
-  return std::make_unique<AssignTargetDevicesPass>();
-});
+} // namespace
 
 } // namespace mlir::iree_compiler::IREE::HAL
diff --git a/compiler/src/iree/compiler/Dialect/HAL/Transforms/BUILD.bazel b/compiler/src/iree/compiler/Dialect/HAL/Transforms/BUILD.bazel
index 174c42f..a1d4f13 100644
--- a/compiler/src/iree/compiler/Dialect/HAL/Transforms/BUILD.bazel
+++ b/compiler/src/iree/compiler/Dialect/HAL/Transforms/BUILD.bazel
@@ -4,7 +4,7 @@
 # See https://llvm.org/LICENSE.txt for license information.
 # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 
-load("//build_tools/bazel:build_defs.oss.bzl", "iree_compiler_cc_library")
+load("//build_tools/bazel:build_defs.oss.bzl", "iree_compiler_cc_library", "iree_gentbl_cc_library")
 
 package(
     default_visibility = ["//visibility:public"],
@@ -16,7 +16,6 @@
     name = "Transforms",
     srcs = [
         "AssignTargetDevices.cpp",
-        "BenchmarkBatchDispatches.cpp",
         "ConfigureExecutables.cpp",
         "ConvertToHAL.cpp",
         "DumpExecutableBenchmarks.cpp",
@@ -29,7 +28,9 @@
         "MaterializeResourceCaches.cpp",
         "MemoizeDeviceQueries.cpp",
         "Passes.cpp",
+        "Passes.h.inc",
         "PreprocessExecutables.cpp",
+        "RepeatDispatches.cpp",
         "ResolveExportOrdinals.cpp",
         "SerializeExecutables.cpp",
         "StripExecutableContents.cpp",
@@ -41,6 +42,7 @@
         "Passes.h",
     ],
     deps = [
+        ":PassesIncGen",
         "//compiler/src/iree/compiler/Codegen/Common/CPU:CommonCPUPasses",
         "//compiler/src/iree/compiler/Codegen/Dialect:IREECodegenDialect",
         "//compiler/src/iree/compiler/Dialect/Flow/IR",
@@ -78,3 +80,18 @@
         "@llvm-project//mlir:Transforms",
     ],
 )
+
+iree_gentbl_cc_library(
+    name = "PassesIncGen",
+    tbl_outs = [
+        (
+            ["--gen-pass-decls"],
+            "Passes.h.inc",
+        ),
+    ],
+    tblgen = "@llvm-project//mlir:mlir-tblgen",
+    td_file = "Passes.td",
+    deps = [
+        "@llvm-project//mlir:PassBaseTdFiles",
+    ],
+)
diff --git a/compiler/src/iree/compiler/Dialect/HAL/Transforms/BenchmarkBatchDispatches.cpp b/compiler/src/iree/compiler/Dialect/HAL/Transforms/BenchmarkBatchDispatches.cpp
deleted file mode 100644
index 098f5dc..0000000
--- a/compiler/src/iree/compiler/Dialect/HAL/Transforms/BenchmarkBatchDispatches.cpp
+++ /dev/null
@@ -1,79 +0,0 @@
-// Copyright 2021 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
-
-#include "iree/compiler/Dialect/HAL/IR/HALDialect.h"
-#include "iree/compiler/Dialect/HAL/IR/HALOps.h"
-#include "iree/compiler/Dialect/HAL/IR/HALTypes.h"
-#include "mlir/Dialect/Func/IR/FuncOps.h"
-#include "mlir/Pass/Pass.h"
-
-namespace mlir::iree_compiler::IREE::HAL {
-namespace {
-
-// Repeats dispatches a specified number of times.
-class BenchmarkBatchDispatchesPass
-    : public PassWrapper<BenchmarkBatchDispatchesPass,
-                         OperationPass<func::FuncOp>> {
-public:
-  MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(BenchmarkBatchDispatchesPass)
-
-  explicit BenchmarkBatchDispatchesPass(unsigned repeatCount)
-      : repeatCount_(repeatCount) {}
-
-  void getDependentDialects(DialectRegistry &registry) const override {
-    registry.insert<func::FuncDialect, IREE::HAL::HALDialect>();
-  }
-
-  StringRef getArgument() const override {
-    return "test-iree-hal-benchmark-batch-dispatches-2-times";
-  }
-
-  StringRef getDescription() const override {
-    return "Test pass used for benchmarking batch dispatches analysis";
-  }
-
-  void runOnOperation() override {
-    // Collect all (nested) command buffer dispatch ops.
-    std::vector<IREE::HAL::CommandBufferDispatchOp> ops;
-    getOperation().walk(
-        [&ops](IREE::HAL::CommandBufferDispatchOp op) { ops.push_back(op); });
-    for (auto op : ops) {
-      OpBuilder builder(op);
-      for (unsigned i = 1; i < repeatCount_; ++i) {
-        builder.clone(*op.getOperation());
-        // Add a barrier after each clone. If the original dispatch has a small
-        // problem size, simply duplicating without barrier will increase the
-        // number of subgroups and thus "help" filling the GPU. In the end we
-        // will have an over optimistic result. Inserting barriers avoids that,
-        // but it assumes that the command buffer has a linear dispatch
-        // structure.
-        builder.create<IREE::HAL::CommandBufferExecutionBarrierOp>(
-            op.getLoc(), op.getCommandBuffer(),
-            IREE::HAL::ExecutionStageBitfield::CommandRetire |
-                IREE::HAL::ExecutionStageBitfield::Dispatch,
-            IREE::HAL::ExecutionStageBitfield::CommandIssue |
-                IREE::HAL::ExecutionStageBitfield::Dispatch,
-            IREE::HAL::ExecutionBarrierFlagBitfield::None);
-      }
-    }
-  }
-
-private:
-  unsigned repeatCount_;
-};
-
-} // namespace
-
-std::unique_ptr<OperationPass<func::FuncOp>>
-createBenchmarkBatchDispatchesPass(unsigned repeatCount) {
-  return std::make_unique<BenchmarkBatchDispatchesPass>(repeatCount);
-}
-
-static PassRegistration<BenchmarkBatchDispatchesPass> pass([] {
-  return std::make_unique<BenchmarkBatchDispatchesPass>(2);
-});
-
-} // namespace mlir::iree_compiler::IREE::HAL
diff --git a/compiler/src/iree/compiler/Dialect/HAL/Transforms/CMakeLists.txt b/compiler/src/iree/compiler/Dialect/HAL/Transforms/CMakeLists.txt
index b24af6a..d88c4fb 100644
--- a/compiler/src/iree/compiler/Dialect/HAL/Transforms/CMakeLists.txt
+++ b/compiler/src/iree/compiler/Dialect/HAL/Transforms/CMakeLists.txt
@@ -17,7 +17,6 @@
     "Passes.h"
   SRCS
     "AssignTargetDevices.cpp"
-    "BenchmarkBatchDispatches.cpp"
     "ConfigureExecutables.cpp"
     "ConvertToHAL.cpp"
     "DumpExecutableBenchmarks.cpp"
@@ -30,7 +29,9 @@
     "MaterializeResourceCaches.cpp"
     "MemoizeDeviceQueries.cpp"
     "Passes.cpp"
+    "Passes.h.inc"
     "PreprocessExecutables.cpp"
+    "RepeatDispatches.cpp"
     "ResolveExportOrdinals.cpp"
     "SerializeExecutables.cpp"
     "StripExecutableContents.cpp"
@@ -38,6 +39,7 @@
     "TranslateExecutables.cpp"
     "VerifyTargetEnvironment.cpp"
   DEPS
+    ::PassesIncGen
     LLVMSupport
     MLIRAffineToStandard
     MLIRArithDialect
@@ -76,4 +78,13 @@
   PUBLIC
 )
 
+iree_tablegen_library(
+  NAME
+    PassesIncGen
+  TD_FILE
+    "Passes.td"
+  OUTS
+    --gen-pass-decls Passes.h.inc
+)
+
 ### BAZEL_TO_CMAKE_PRESERVES_ALL_CONTENT_BELOW_THIS_LINE ###
diff --git a/compiler/src/iree/compiler/Dialect/HAL/Transforms/ConfigureExecutables.cpp b/compiler/src/iree/compiler/Dialect/HAL/Transforms/ConfigureExecutables.cpp
index ae42202..9ee5f73 100644
--- a/compiler/src/iree/compiler/Dialect/HAL/Transforms/ConfigureExecutables.cpp
+++ b/compiler/src/iree/compiler/Dialect/HAL/Transforms/ConfigureExecutables.cpp
@@ -11,6 +11,7 @@
 #include "iree/compiler/Dialect/HAL/IR/HALOps.h"
 #include "iree/compiler/Dialect/HAL/Target/TargetBackend.h"
 #include "iree/compiler/Dialect/HAL/Target/TargetRegistry.h"
+#include "iree/compiler/Dialect/HAL/Transforms/Passes.h"
 #include "iree/compiler/Utils/TracingUtils.h"
 #include "llvm/ADT/StringSet.h"
 #include "mlir/IR/Attributes.h"
@@ -21,42 +22,37 @@
 
 namespace mlir::iree_compiler::IREE::HAL {
 
+#define GEN_PASS_DEF_CONFIGUREEXECUTABLESPASS
+#define GEN_PASS_DEF_CONFIGURETARGETEXECUTABLEVARIANTSPASS
+#include "iree/compiler/Dialect/HAL/Transforms/Passes.h.inc"
+
+namespace {
+
+//===----------------------------------------------------------------------===//
+// --iree-hal-configure-target-executable-variants
+//===----------------------------------------------------------------------===//
+
 class ConfigureTargetExecutableVariantsPass
-    : public PassWrapper<ConfigureTargetExecutableVariantsPass,
-                         OperationPass<IREE::HAL::ExecutableVariantOp>> {
-public:
-  ConfigureTargetExecutableVariantsPass()
-      : targetRegistry(TargetBackendRegistry::getGlobal()) {}
-  ConfigureTargetExecutableVariantsPass(
-      const ConfigureTargetExecutableVariantsPass &pass)
-      : targetRegistry(pass.targetRegistry) {}
-  ConfigureTargetExecutableVariantsPass(
-      const TargetBackendRegistry &targetRegistry, StringRef target)
-      : targetRegistry(targetRegistry) {
-    this->target = target.str();
-  }
-
-  StringRef getArgument() const override {
-    return "iree-hal-configure-target-executable-variants";
-  }
-
-  StringRef getDescription() const override {
-    return "Configures a hal.executable.variant op for translation";
-  }
+    : public IREE::HAL::impl::ConfigureTargetExecutableVariantsPassBase<
+          ConfigureTargetExecutableVariantsPass> {
+  using IREE::HAL::impl::ConfigureTargetExecutableVariantsPassBase<
+      ConfigureTargetExecutableVariantsPass>::
+      ConfigureTargetExecutableVariantsPassBase;
 
   void getDependentDialects(DialectRegistry &registry) const override {
     registry.insert<IREE::HAL::HALDialect>();
-    auto targetBackend = targetRegistry.getTargetBackend(target);
+    auto targetBackend = targetRegistry->getTargetBackend(target);
     if (targetBackend) {
       targetBackend->getDependentDialects(registry);
     }
   }
+
   void runOnOperation() override {
     auto variantOp = getOperation();
     if (variantOp.getTarget().getBackend().getValue() != target)
       return;
 
-    auto targetBackend = targetRegistry.getTargetBackend(target);
+    auto targetBackend = targetRegistry->getTargetBackend(target);
     if (!targetBackend) {
       variantOp.emitError() << "unregistered target backend '" << target << "'";
       return signalPassFailure();
@@ -78,51 +74,22 @@
       return signalPassFailure();
     }
   }
-
-private:
-  Option<std::string> target{
-      *this, "target",
-      llvm::cl::desc(
-          "Target backend name whose executables will be configured by "
-          "this pass.")};
-
-  const TargetBackendRegistry &targetRegistry;
 };
 
-std::unique_ptr<OperationPass<IREE::HAL::ExecutableVariantOp>>
-createConfigureTargetExecutableVariantsPass(
-    const TargetBackendRegistry &targetRegistry, StringRef target) {
-  return std::make_unique<ConfigureTargetExecutableVariantsPass>(targetRegistry,
-                                                                 target);
-}
+//===----------------------------------------------------------------------===//
+// --iree-hal-configure-executables
+//===----------------------------------------------------------------------===//
 
-static PassRegistration<ConfigureTargetExecutableVariantsPass> linkTargetPass(
-    [] { return std::make_unique<ConfigureTargetExecutableVariantsPass>(); });
-
-class ConfigureExecutablesPass
-    : public PassWrapper<ConfigureExecutablesPass,
-                         OperationPass<IREE::HAL::ExecutableOp>> {
-public:
-  ConfigureExecutablesPass()
-      : targetRegistry(TargetBackendRegistry::getGlobal()) {}
-  ConfigureExecutablesPass(const ConfigureExecutablesPass &pass)
-      : targetRegistry(pass.targetRegistry) {}
-  ConfigureExecutablesPass(const TargetBackendRegistry &targetRegistry)
-      : targetRegistry(targetRegistry) {}
-
-  StringRef getArgument() const override {
-    return "iree-hal-configure-executables";
-  }
-
-  StringRef getDescription() const override {
-    return "Configures hal.executable.variant ops for translation to "
-           "hal.executable.binary ops";
-  }
+struct ConfigureExecutablesPass
+    : public IREE::HAL::impl::ConfigureExecutablesPassBase<
+          ConfigureExecutablesPass> {
+  using IREE::HAL::impl::ConfigureExecutablesPassBase<
+      ConfigureExecutablesPass>::ConfigureExecutablesPassBase;
 
   void getDependentDialects(DialectRegistry &registry) const override {
     registry.insert<IREE::HAL::HALDialect>();
-    auto targetBackends = targetRegistry.getTargetBackends(
-        targetRegistry.getRegisteredTargetBackends());
+    auto targetBackends = targetRegistry->getTargetBackends(
+        targetRegistry->getRegisteredTargetBackends());
     for (auto &targetBackend : targetBackends) {
       targetBackend->getDependentDialects(registry);
     }
@@ -133,8 +100,8 @@
     OpPassManager passManager(executableOp.getOperationName());
     for (const auto &targetName : gatherExecutableTargetNames(executableOp)) {
       passManager.addNestedPass<IREE::HAL::ExecutableVariantOp>(
-          createConfigureTargetExecutableVariantsPass(targetRegistry,
-                                                      targetName));
+          IREE::HAL::createConfigureTargetExecutableVariantsPass(
+              {targetRegistry, targetName}));
     }
 
     IREE_COMPILER_TRACE_MESSAGE_DYNAMIC(INFO, executableOp.getSymName().str());
@@ -144,18 +111,8 @@
       return signalPassFailure();
     }
   }
-
-private:
-  const TargetBackendRegistry &targetRegistry;
 };
 
-std::unique_ptr<OperationPass<IREE::HAL::ExecutableOp>>
-createConfigureExecutablesPass(const TargetBackendRegistry &targetRegistry) {
-  return std::make_unique<ConfigureExecutablesPass>(targetRegistry);
-}
-
-static PassRegistration<ConfigureExecutablesPass> translatePass([] {
-  return std::make_unique<ConfigureExecutablesPass>();
-});
+} // namespace
 
 } // namespace mlir::iree_compiler::IREE::HAL
diff --git a/compiler/src/iree/compiler/Dialect/HAL/Transforms/ConvertToHAL.cpp b/compiler/src/iree/compiler/Dialect/HAL/Transforms/ConvertToHAL.cpp
index b140cde..6269438 100644
--- a/compiler/src/iree/compiler/Dialect/HAL/Transforms/ConvertToHAL.cpp
+++ b/compiler/src/iree/compiler/Dialect/HAL/Transforms/ConvertToHAL.cpp
@@ -34,32 +34,18 @@
 #include "mlir/Transforms/DialectConversion.h"
 
 namespace mlir::iree_compiler::IREE::HAL {
+
+#define GEN_PASS_DEF_CONVERTTOHALPASS
+#include "iree/compiler/Dialect/HAL/Transforms/Passes.h.inc"
+
 namespace {
 
-// A pass converting the IREE flow dialect into the IREE HAL dialect.
-class ConvertToHALPass
-    : public PassWrapper<ConvertToHALPass, OperationPass<ModuleOp>> {
-public:
-  MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(ConvertToHALPass)
+//===----------------------------------------------------------------------===//
+// --iree-hal-conversion
+//===----------------------------------------------------------------------===//
 
-  void getDependentDialects(DialectRegistry &registry) const override {
-    registry.insert<mlir::arith::ArithDialect>();
-    registry.insert<mlir::func::FuncDialect>();
-    registry.insert<mlir::scf::SCFDialect>();
-    registry.insert<IREE::HAL::HALDialect>();
-    registry.insert<IREE::Stream::StreamDialect>();
-    registry.insert<IREE::Util::UtilDialect>();
-
-    // TODO(benvanik): add a registration system for extra dialects?
-    registry.insert<IREE::IO::Parameters::IOParametersDialect>();
-  }
-
-  StringRef getArgument() const override { return "iree-hal-conversion"; }
-
-  StringRef getDescription() const override {
-    return "Convert input stream/std/etc dialects to the IREE HAL dialect.";
-  }
-
+struct ConvertToHALPass
+    : public IREE::HAL::impl::ConvertToHALPassBase<ConvertToHALPass> {
   void runOnOperation() override {
     auto *context = &getContext();
 
@@ -106,10 +92,4 @@
 
 } // namespace
 
-std::unique_ptr<OperationPass<ModuleOp>> createConvertToHALPass() {
-  return std::make_unique<ConvertToHALPass>();
-}
-
-static PassRegistration<ConvertToHALPass> pass;
-
 } // namespace mlir::iree_compiler::IREE::HAL
diff --git a/compiler/src/iree/compiler/Dialect/HAL/Transforms/DumpExecutableBenchmarks.cpp b/compiler/src/iree/compiler/Dialect/HAL/Transforms/DumpExecutableBenchmarks.cpp
index 5fc02a5..473c392 100644
--- a/compiler/src/iree/compiler/Dialect/HAL/Transforms/DumpExecutableBenchmarks.cpp
+++ b/compiler/src/iree/compiler/Dialect/HAL/Transforms/DumpExecutableBenchmarks.cpp
@@ -11,6 +11,7 @@
 #include "iree/compiler/Dialect/HAL/IR/HALOps.h"
 #include "iree/compiler/Dialect/HAL/Transforms/Passes.h"
 #include "iree/compiler/Dialect/Stream/IR/StreamOps.h"
+#include "iree/compiler/Dialect/Util/IR/UtilDialect.h"
 #include "iree/compiler/Utils/IndexSet.h"
 #include "llvm/Support/FileSystem.h"
 #include "llvm/Support/Path.h"
@@ -28,6 +29,11 @@
 
 namespace mlir::iree_compiler::IREE::HAL {
 
+#define GEN_PASS_DEF_DUMPEXECUTABLEBENCHMARKSPASS
+#include "iree/compiler/Dialect/HAL/Transforms/Passes.h.inc"
+
+namespace {
+
 // We could use the resource constraints in the module when we have them.
 static const int64_t kBufferAlignment = 256;
 
@@ -446,28 +452,15 @@
   os << "\n"; // newline at end of file
 }
 
-class DumpExecutableBenchmarksPass
-    : public PassWrapper<DumpExecutableBenchmarksPass,
-                         OperationPass<ModuleOp>> {
-public:
-  DumpExecutableBenchmarksPass() = default;
-  DumpExecutableBenchmarksPass(const DumpExecutableBenchmarksPass &pass) {}
-  DumpExecutableBenchmarksPass(StringRef path) { this->path = path.str(); }
+//===----------------------------------------------------------------------===//
+// --iree-hal-dump-executable-benchmarks
+//===----------------------------------------------------------------------===//
 
-  void getDependentDialects(DialectRegistry &registry) const override {
-    registry.insert<IREE::HAL::HALDialect>();
-    registry.insert<arith::ArithDialect>();
-    registry.insert<scf::SCFDialect>();
-  }
-
-  StringRef getArgument() const override {
-    return "iree-hal-dump-executable-benchmarks";
-  }
-
-  StringRef getDescription() const override {
-    return "Dumps standalone hal.executable benchmarks to a path.";
-  }
-
+struct DumpExecutableBenchmarksPass
+    : public IREE::HAL::impl::DumpExecutableBenchmarksPassBase<
+          DumpExecutableBenchmarksPass> {
+  using IREE::HAL::impl::DumpExecutableBenchmarksPassBase<
+      DumpExecutableBenchmarksPass>::DumpExecutableBenchmarksPassBase;
   void runOnOperation() override {
     auto moduleOp = getOperation();
     auto moduleName = moduleOp.getName().value_or("module");
@@ -519,20 +512,8 @@
       }
     }
   }
-
-private:
-  Option<std::string> path{
-      *this, "path",
-      llvm::cl::desc("Path to write hal.executable benchmarks into.")};
 };
 
-std::unique_ptr<OperationPass<ModuleOp>>
-createDumpExecutableBenchmarksPass(StringRef path) {
-  return std::make_unique<DumpExecutableBenchmarksPass>(path);
-}
-
-static PassRegistration<DumpExecutableBenchmarksPass> pass([] {
-  return std::make_unique<DumpExecutableBenchmarksPass>();
-});
+} // namespace
 
 } // namespace mlir::iree_compiler::IREE::HAL
diff --git a/compiler/src/iree/compiler/Dialect/HAL/Transforms/DumpExecutableSources.cpp b/compiler/src/iree/compiler/Dialect/HAL/Transforms/DumpExecutableSources.cpp
index 6ba9c54..defca30 100644
--- a/compiler/src/iree/compiler/Dialect/HAL/Transforms/DumpExecutableSources.cpp
+++ b/compiler/src/iree/compiler/Dialect/HAL/Transforms/DumpExecutableSources.cpp
@@ -19,6 +19,11 @@
 
 namespace mlir::iree_compiler::IREE::HAL {
 
+#define GEN_PASS_DEF_DUMPEXECUTABLESOURCESPASS
+#include "iree/compiler/Dialect/HAL/Transforms/Passes.h.inc"
+
+namespace {
+
 static void dumpExecutableToStream(IREE::HAL::ExecutableOp executableOp,
                                    StringRef filePath, llvm::raw_ostream &os) {
   OpPrintingFlags flags;
@@ -27,28 +32,15 @@
   os << "\n"; // newline at end of file
 }
 
-class DumpExecutableSourcesPass
-    : public PassWrapper<DumpExecutableSourcesPass, OperationPass<ModuleOp>> {
-public:
-  DumpExecutableSourcesPass() = default;
-  DumpExecutableSourcesPass(const DumpExecutableSourcesPass &pass) {}
-  DumpExecutableSourcesPass(StringRef path, StringRef prefix) {
-    this->path = path.str();
-    this->prefix = prefix.str();
-  }
+//===----------------------------------------------------------------------===//
+// --iree-hal-dump-executable-sources
+//===----------------------------------------------------------------------===//
 
-  void getDependentDialects(DialectRegistry &registry) const override {
-    registry.insert<IREE::HAL::HALDialect>();
-  }
-
-  StringRef getArgument() const override {
-    return "iree-hal-dump-executable-sources";
-  }
-
-  StringRef getDescription() const override {
-    return "Dumps individual hal.executable source listings to a path.";
-  }
-
+struct DumpExecutableSourcesPass
+    : public IREE::HAL::impl::DumpExecutableSourcesPassBase<
+          DumpExecutableSourcesPass> {
+  using IREE::HAL::impl::DumpExecutableSourcesPassBase<
+      DumpExecutableSourcesPass>::DumpExecutableSourcesPassBase;
   void runOnOperation() override {
     auto moduleOp = getOperation();
     auto moduleName = moduleOp.getName().value_or("module");
@@ -88,24 +80,8 @@
       executableOp.setVisibility(originalVisibility);
     }
   }
-
-private:
-  Option<std::string> path{
-      *this, "path",
-      llvm::cl::desc("Path to write hal.executable source files into.")};
-
-  Option<std::string> prefix{
-      *this, "prefix",
-      llvm::cl::desc("String to prefix the written files with.")};
 };
 
-std::unique_ptr<OperationPass<ModuleOp>>
-createDumpExecutableSourcesPass(StringRef path, StringRef prefix) {
-  return std::make_unique<DumpExecutableSourcesPass>(path, prefix);
-}
-
-static PassRegistration<DumpExecutableSourcesPass> pass([] {
-  return std::make_unique<DumpExecutableSourcesPass>();
-});
+} // namespace
 
 } // namespace mlir::iree_compiler::IREE::HAL
diff --git a/compiler/src/iree/compiler/Dialect/HAL/Transforms/ElideRedundantCommands.cpp b/compiler/src/iree/compiler/Dialect/HAL/Transforms/ElideRedundantCommands.cpp
index 2e9cc48..77eb5bc 100644
--- a/compiler/src/iree/compiler/Dialect/HAL/Transforms/ElideRedundantCommands.cpp
+++ b/compiler/src/iree/compiler/Dialect/HAL/Transforms/ElideRedundantCommands.cpp
@@ -19,6 +19,10 @@
 #include "mlir/Pass/Pass.h"
 
 namespace mlir::iree_compiler::IREE::HAL {
+
+#define GEN_PASS_DEF_ELIDEREDUNDANTCOMMANDSPASS
+#include "iree/compiler/Dialect/HAL/Transforms/Passes.h.inc"
+
 namespace {
 
 struct DescriptorState {
@@ -82,8 +86,6 @@
 
 using CommandBufferStateMap = DenseMap<Value, CommandBufferState>;
 
-} // namespace
-
 static void processOp(IREE::HAL::CommandBufferExecutionBarrierOp op,
                       CommandBufferState &state) {
   if (state.previousFullBarrier) {
@@ -201,21 +203,13 @@
   return success();
 }
 
-class ElideRedundantCommandsPass
-    : public PassWrapper<ElideRedundantCommandsPass, OperationPass<void>> {
-public:
-  void getDependentDialects(DialectRegistry &registry) const override {
-    registry.insert<IREE::HAL::HALDialect>();
-  }
+//===----------------------------------------------------------------------===//
+// --iree-hal-elide-redundant-commands
+//===----------------------------------------------------------------------===//
 
-  StringRef getArgument() const override {
-    return "iree-hal-elide-redundant-commands";
-  }
-
-  StringRef getDescription() const override {
-    return "Elides stateful command buffer ops that set redundant state.";
-  }
-
+struct ElideRedundantCommandsPass
+    : public IREE::HAL::impl::ElideRedundantCommandsPassBase<
+          ElideRedundantCommandsPass> {
   void runOnOperation() override {
     auto parentOp = getOperation();
 
@@ -287,10 +281,6 @@
   }
 };
 
-std::unique_ptr<OperationPass<void>> createElideRedundantCommandsPass() {
-  return std::make_unique<ElideRedundantCommandsPass>();
-}
-
-static PassRegistration<ElideRedundantCommandsPass> pass;
+} // namespace
 
 } // namespace mlir::iree_compiler::IREE::HAL
diff --git a/compiler/src/iree/compiler/Dialect/HAL/Transforms/FixupLegacySync.cpp b/compiler/src/iree/compiler/Dialect/HAL/Transforms/FixupLegacySync.cpp
index 47cfe10..0a082b7 100644
--- a/compiler/src/iree/compiler/Dialect/HAL/Transforms/FixupLegacySync.cpp
+++ b/compiler/src/iree/compiler/Dialect/HAL/Transforms/FixupLegacySync.cpp
@@ -4,8 +4,10 @@
 // See https://llvm.org/LICENSE.txt for license information.
 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 
+#include "iree/compiler/Dialect/HAL/IR/HALDialect.h"
 #include "iree/compiler/Dialect/HAL/IR/HALOps.h"
 #include "iree/compiler/Dialect/HAL/Transforms/Passes.h"
+#include "iree/compiler/Dialect/Util/IR/UtilDialect.h"
 #include "mlir/Dialect/Arith/IR/Arith.h"
 #include "mlir/Dialect/Func/IR/FuncOps.h"
 #include "mlir/IR/Attributes.h"
@@ -16,6 +18,11 @@
 
 namespace mlir::iree_compiler::IREE::HAL {
 
+#define GEN_PASS_DEF_FIXUPLEGACYSYNCPASS
+#include "iree/compiler/Dialect/HAL/Transforms/Passes.h.inc"
+
+namespace {
+
 // Marks a command buffer as being executable inline during recording.
 // This is only possible because we generate our command buffer code without
 // caching today and know that all are executable inline so long as we have
@@ -134,19 +141,12 @@
   }
 }
 
-// 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.
+//===----------------------------------------------------------------------===//
+// --iree-hal-fixup-legacy-sync
+//===----------------------------------------------------------------------===//
+
 struct FixupLegacySyncPass
-    : public PassWrapper<FixupLegacySyncPass, OperationPass<mlir::ModuleOp>> {
-  StringRef getArgument() const override {
-    return "iree-hal-fixup-legacy-sync";
-  }
-
-  StringRef getDescription() const override {
-    return "Applies fixups to the program for when using legacy HAL devices "
-           "that only support synchronous execution";
-  }
-
+    : public IREE::HAL::impl::FixupLegacySyncPassBase<FixupLegacySyncPass> {
   void runOnOperation() override {
     auto moduleOp = getOperation();
 
@@ -182,10 +182,6 @@
   }
 };
 
-std::unique_ptr<OperationPass<mlir::ModuleOp>> createFixupLegacySyncPass() {
-  return std::make_unique<FixupLegacySyncPass>();
-}
-
-static PassRegistration<FixupLegacySyncPass> pass;
+} // namespace
 
 } // namespace mlir::iree_compiler::IREE::HAL
diff --git a/compiler/src/iree/compiler/Dialect/HAL/Transforms/LinkExecutables.cpp b/compiler/src/iree/compiler/Dialect/HAL/Transforms/LinkExecutables.cpp
index 8e828b6..8951945 100644
--- a/compiler/src/iree/compiler/Dialect/HAL/Transforms/LinkExecutables.cpp
+++ b/compiler/src/iree/compiler/Dialect/HAL/Transforms/LinkExecutables.cpp
@@ -11,6 +11,7 @@
 #include "iree/compiler/Dialect/HAL/IR/HALOps.h"
 #include "iree/compiler/Dialect/HAL/Target/TargetBackend.h"
 #include "iree/compiler/Dialect/HAL/Target/TargetRegistry.h"
+#include "iree/compiler/Dialect/HAL/Transforms/Passes.h"
 #include "llvm/ADT/StringSet.h"
 #include "mlir/IR/Attributes.h"
 #include "mlir/IR/Builders.h"
@@ -21,31 +22,25 @@
 
 namespace mlir::iree_compiler::IREE::HAL {
 
-class LinkTargetExecutablesPass
-    : public PassWrapper<LinkTargetExecutablesPass,
-                         OperationPass<mlir::ModuleOp>> {
-public:
-  LinkTargetExecutablesPass()
-      : targetRegistry(TargetBackendRegistry::getGlobal()) {}
-  LinkTargetExecutablesPass(const LinkTargetExecutablesPass &pass)
-      : targetRegistry(pass.targetRegistry) {}
-  LinkTargetExecutablesPass(const TargetBackendRegistry &targetRegistry,
-                            StringRef target)
-      : targetRegistry(targetRegistry) {
-    this->target = target.str();
-  }
+#define GEN_PASS_DEF_LINKEXECUTABLESPASS
+#define GEN_PASS_DEF_LINKTARGETEXECUTABLESPASS
+#include "iree/compiler/Dialect/HAL/Transforms/Passes.h.inc"
 
-  StringRef getArgument() const override {
-    return "iree-hal-link-target-executables";
-  }
+namespace {
 
-  StringRef getDescription() const override {
-    return "Links together hal.executables for the specified target.";
-  }
+//===----------------------------------------------------------------------===//
+// --iree-hal-link-target-executables
+//===----------------------------------------------------------------------===//
+
+struct LinkTargetExecutablesPass
+    : public IREE::HAL::impl::LinkTargetExecutablesPassBase<
+          LinkTargetExecutablesPass> {
+  using IREE::HAL::impl::LinkTargetExecutablesPassBase<
+      LinkTargetExecutablesPass>::LinkTargetExecutablesPassBase;
 
   void getDependentDialects(DialectRegistry &registry) const override {
     registry.insert<IREE::HAL::HALDialect>();
-    auto targetBackend = targetRegistry.getTargetBackend(target);
+    auto targetBackend = targetRegistry->getTargetBackend(target);
     if (targetBackend) {
       targetBackend->getDependentDialects(registry);
     }
@@ -53,7 +48,7 @@
 
   void runOnOperation() override {
     auto moduleOp = getOperation();
-    auto targetBackend = targetRegistry.getTargetBackend(target);
+    auto targetBackend = targetRegistry->getTargetBackend(target);
     if (!targetBackend) {
       moduleOp.emitError() << "unregistered target backend '" << target << "'";
       return signalPassFailure();
@@ -68,38 +63,16 @@
       return signalPassFailure();
     }
   }
-
-private:
-  Option<std::string> target{
-      *this, "target",
-      llvm::cl::desc("Target backend name whose executables will be linked by "
-                     "this pass.")};
-
-  const TargetBackendRegistry &targetRegistry;
 };
 
-std::unique_ptr<OperationPass<mlir::ModuleOp>>
-createLinkTargetExecutablesPass(const TargetBackendRegistry &targetRegistry,
-                                StringRef target) {
-  return std::make_unique<LinkTargetExecutablesPass>(targetRegistry, target);
-}
+//===----------------------------------------------------------------------===//
+// --iree-hal-link-executables
+//===----------------------------------------------------------------------===//
 
-static PassRegistration<LinkTargetExecutablesPass> linkTargetPass([] {
-  return std::make_unique<LinkTargetExecutablesPass>();
-});
-
-class LinkExecutablesPass
-    : public PassWrapper<LinkExecutablesPass, OperationPass<mlir::ModuleOp>> {
-public:
-  LinkExecutablesPass(const TargetBackendRegistry &targetRegistry)
-      : targetRegistry(targetRegistry) {}
-
-  StringRef getArgument() const override { return "iree-hal-link-executables"; }
-
-  StringRef getDescription() const override {
-    return "Links together hal.executables depending on target backend rules";
-  }
-
+struct LinkExecutablesPass
+    : public IREE::HAL::impl::LinkExecutablesPassBase<LinkExecutablesPass> {
+  using IREE::HAL::impl::LinkExecutablesPassBase<
+      LinkExecutablesPass>::LinkExecutablesPassBase;
   void runOnOperation() override {
     auto moduleOp = getOperation();
 
@@ -107,8 +80,8 @@
     // These will create/rearrange executables.
     OpPassManager passManager(moduleOp.getOperationName());
     for (const auto &targetName : gatherExecutableTargetNames(moduleOp)) {
-      passManager.addPass(
-          createLinkTargetExecutablesPass(targetRegistry, targetName));
+      passManager.addPass(IREE::HAL::createLinkTargetExecutablesPass(
+          {targetRegistry, targetName}));
     }
 
     // Cleanup any remaining empty executables after each pipeline has run.
@@ -121,18 +94,8 @@
       return signalPassFailure();
     }
   }
-
-  const TargetBackendRegistry &targetRegistry;
 };
 
-std::unique_ptr<OperationPass<mlir::ModuleOp>>
-createLinkExecutablesPass(const TargetBackendRegistry &targetRegistry) {
-  return std::make_unique<LinkExecutablesPass>(targetRegistry);
-}
-
-static PassRegistration<LinkExecutablesPass> linkPass([] {
-  return std::make_unique<LinkExecutablesPass>(
-      TargetBackendRegistry::getGlobal());
-});
+} // namespace
 
 } // namespace mlir::iree_compiler::IREE::HAL
diff --git a/compiler/src/iree/compiler/Dialect/HAL/Transforms/MaterializeDispatchInstrumentation.cpp b/compiler/src/iree/compiler/Dialect/HAL/Transforms/MaterializeDispatchInstrumentation.cpp
index 8c1244c..b48f7c9 100644
--- a/compiler/src/iree/compiler/Dialect/HAL/Transforms/MaterializeDispatchInstrumentation.cpp
+++ b/compiler/src/iree/compiler/Dialect/HAL/Transforms/MaterializeDispatchInstrumentation.cpp
@@ -27,6 +27,11 @@
 
 namespace mlir::iree_compiler::IREE::HAL {
 
+#define GEN_PASS_DEF_MATERIALIZEDISPATCHINSTRUMENTATIONPASS
+#include "iree/compiler/Dialect/HAL/Transforms/Passes.h.inc"
+
+namespace {
+
 static std::string getAttrStr(Attribute attr) {
   if (!attr)
     return "";
@@ -94,32 +99,16 @@
   }
 }
 
-class MaterializeDispatchInstrumentationPass
-    : public PassWrapper<MaterializeDispatchInstrumentationPass,
-                         OperationPass<mlir::ModuleOp>> {
-public:
-  MaterializeDispatchInstrumentationPass() = default;
-  MaterializeDispatchInstrumentationPass(
-      const MaterializeDispatchInstrumentationPass &pass) {}
-  explicit MaterializeDispatchInstrumentationPass(int64_t bufferSize) {
-    this->bufferSize = bufferSize;
-  }
+//===----------------------------------------------------------------------===//
+// --iree-hal-materialize-dispatch-instrumentation
+//===----------------------------------------------------------------------===//
 
-  StringRef getArgument() const override {
-    return "iree-hal-materialize-dispatch-instrumentation";
-  }
-
-  StringRef getDescription() const override {
-    return "Materializes dispatch instrumentation resources.";
-  }
-
-  void getDependentDialects(DialectRegistry &registry) const override {
-    registry.insert<mlir::arith::ArithDialect>();
-    registry.insert<IREE::HAL::HALDialect>();
-    registry.insert<IREE::Stream::StreamDialect>();
-    registry.insert<IREE::Util::UtilDialect>();
-  }
-
+struct MaterializeDispatchInstrumentationPass
+    : public IREE::HAL::impl::MaterializeDispatchInstrumentationPassBase<
+          MaterializeDispatchInstrumentationPass> {
+  using IREE::HAL::impl::MaterializeDispatchInstrumentationPassBase<
+      MaterializeDispatchInstrumentationPass>::
+      MaterializeDispatchInstrumentationPassBase;
   void runOnOperation() override {
     auto moduleOp = getOperation();
     if (moduleOp.getBody()->empty())
@@ -378,23 +367,8 @@
       queryBuilder.create<func::ReturnOp>(loc);
     }
   }
-
-private:
-  Option<llvm::cl::PowerOf2ByteSize> bufferSize{
-      *this,
-      "bufferSize",
-      llvm::cl::desc("Power-of-two byte size of the instrumentation buffer."),
-      llvm::cl::init(llvm::cl::PowerOf2ByteSize(64 * 1024 * 1024)),
-  };
 };
 
-std::unique_ptr<OperationPass<mlir::ModuleOp>>
-createMaterializeDispatchInstrumentationPass(int64_t bufferSize) {
-  return std::make_unique<MaterializeDispatchInstrumentationPass>(bufferSize);
-}
-
-static PassRegistration<MaterializeDispatchInstrumentationPass> pass([] {
-  return std::make_unique<MaterializeDispatchInstrumentationPass>();
-});
+} // namespace
 
 } // namespace mlir::iree_compiler::IREE::HAL
diff --git a/compiler/src/iree/compiler/Dialect/HAL/Transforms/MaterializeInterfaces.cpp b/compiler/src/iree/compiler/Dialect/HAL/Transforms/MaterializeInterfaces.cpp
index 0b0ba53..2953df0 100644
--- a/compiler/src/iree/compiler/Dialect/HAL/Transforms/MaterializeInterfaces.cpp
+++ b/compiler/src/iree/compiler/Dialect/HAL/Transforms/MaterializeInterfaces.cpp
@@ -29,6 +29,10 @@
 #define DEBUG_TYPE "iree-hal-materialize-interfaces"
 
 namespace mlir::iree_compiler::IREE::HAL {
+
+#define GEN_PASS_DEF_MATERIALIZEINTERFACESPASS
+#include "iree/compiler/Dialect/HAL/Transforms/Passes.h.inc"
+
 namespace {
 
 // Map of original SymbolRefAttr to a list of SymbolRefAttrs in variants.
@@ -510,28 +514,12 @@
 }
 
 //===----------------------------------------------------------------------===//
-// -iree-hal-materialize-interfaces
+// --iree-hal-materialize-interfaces
 //===----------------------------------------------------------------------===//
 
-class MaterializeInterfacesPass
-    : public PassWrapper<MaterializeInterfacesPass, OperationPass<ModuleOp>> {
-public:
-  MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(MaterializeInterfacesPass)
-
-  MaterializeInterfacesPass() = default;
-
-  void getDependentDialects(DialectRegistry &registry) const override {
-    registry.insert<IREE::HAL::HALDialect>();
-  }
-
-  StringRef getArgument() const override {
-    return "iree-hal-materialize-interfaces";
-  }
-
-  StringRef getDescription() const override {
-    return "Materializes hal.executable ops from stream.executable ops";
-  }
-
+struct MaterializeInterfacesPass
+    : public IREE::HAL::impl::MaterializeInterfacesPassBase<
+          MaterializeInterfacesPass> {
   void runOnOperation() override {
     SymbolTable symbolTable(getOperation());
 
@@ -656,12 +644,4 @@
 
 } // namespace
 
-std::unique_ptr<OperationPass<ModuleOp>> createMaterializeInterfacesPass() {
-  return std::make_unique<MaterializeInterfacesPass>();
-}
-
-static PassRegistration<MaterializeInterfacesPass> pass([] {
-  return std::make_unique<MaterializeInterfacesPass>();
-});
-
 } // namespace mlir::iree_compiler::IREE::HAL
diff --git a/compiler/src/iree/compiler/Dialect/HAL/Transforms/MaterializeResourceCaches.cpp b/compiler/src/iree/compiler/Dialect/HAL/Transforms/MaterializeResourceCaches.cpp
index 3b0bb79..3620e52 100644
--- a/compiler/src/iree/compiler/Dialect/HAL/Transforms/MaterializeResourceCaches.cpp
+++ b/compiler/src/iree/compiler/Dialect/HAL/Transforms/MaterializeResourceCaches.cpp
@@ -10,6 +10,7 @@
 #include "iree/compiler/Dialect/HAL/IR/HALDialect.h"
 #include "iree/compiler/Dialect/HAL/IR/HALOps.h"
 #include "iree/compiler/Dialect/HAL/Transforms/Passes.h"
+#include "iree/compiler/Dialect/Util/IR/UtilDialect.h"
 #include "iree/compiler/Dialect/Util/IR/UtilOps.h"
 #include "mlir/Dialect/Arith/IR/Arith.h"
 #include "mlir/Dialect/Func/IR/FuncOps.h"
@@ -22,27 +23,15 @@
 
 namespace mlir::iree_compiler::IREE::HAL {
 
-class MaterializeResourceCachesPass
-    : public PassWrapper<MaterializeResourceCachesPass,
-                         OperationPass<ModuleOp>> {
-public:
-  explicit MaterializeResourceCachesPass(TargetOptions targetOptions)
-      : targetOptions_(targetOptions) {}
+#define GEN_PASS_DEF_MATERIALIZERESOURCECACHESPASS
+#include "iree/compiler/Dialect/HAL/Transforms/Passes.h.inc"
 
-  StringRef getArgument() const override {
-    return "iree-hal-materialize-resource-caches";
-  }
+namespace {
 
-  StringRef getDescription() const override {
-    return "Materializes hal.executable resource caches and rewrites lookups.";
-  }
-
-  void getDependentDialects(DialectRegistry &registry) const override {
-    registry.insert<mlir::arith::ArithDialect>();
-    registry.insert<mlir::scf::SCFDialect>();
-    registry.insert<IREE::HAL::HALDialect>();
-  }
-
+// TODO(multi-device): rewrite this to shard resources per device.
+struct MaterializeResourceCachesPass
+    : public IREE::HAL::impl::MaterializeResourceCachesPassBase<
+          MaterializeResourceCachesPass> {
   void runOnOperation() override {
     auto moduleOp = getOperation();
     if (moduleOp.getBody()->empty())
@@ -368,8 +357,6 @@
     lookupOp.erase();
   }
 
-  TargetOptions targetOptions_;
-
   OpBuilder moduleBuilder{static_cast<MLIRContext *>(nullptr)};
   DenseMap<std::pair<Attribute, IREE::HAL::DescriptorSetLayoutFlags>,
            IREE::Util::GlobalOp>
@@ -382,14 +369,6 @@
   int nextUniqueDescriptorSetLayoutId = 0;
 };
 
-std::unique_ptr<OperationPass<ModuleOp>>
-createMaterializeResourceCachesPass(TargetOptions targetOptions) {
-  return std::make_unique<MaterializeResourceCachesPass>(targetOptions);
-}
-
-static PassRegistration<MaterializeResourceCachesPass> pass([] {
-  auto options = TargetOptions::FromFlags::get();
-  return std::make_unique<MaterializeResourceCachesPass>(options);
-});
+} // namespace
 
 } // namespace mlir::iree_compiler::IREE::HAL
diff --git a/compiler/src/iree/compiler/Dialect/HAL/Transforms/MemoizeDeviceQueries.cpp b/compiler/src/iree/compiler/Dialect/HAL/Transforms/MemoizeDeviceQueries.cpp
index 80845d3..1bc5ae0 100644
--- a/compiler/src/iree/compiler/Dialect/HAL/Transforms/MemoizeDeviceQueries.cpp
+++ b/compiler/src/iree/compiler/Dialect/HAL/Transforms/MemoizeDeviceQueries.cpp
@@ -6,32 +6,32 @@
 
 #include <utility>
 
+#include "iree/compiler/Dialect/HAL/IR/HALDialect.h"
 #include "iree/compiler/Dialect/HAL/IR/HALOps.h"
 #include "iree/compiler/Dialect/HAL/Transforms/Passes.h"
-#include "llvm/ADT/StringSet.h"
-#include "mlir/Dialect/Func/IR/FuncOps.h"
+#include "iree/compiler/Dialect/Util/IR/UtilDialect.h"
 #include "mlir/IR/Attributes.h"
 #include "mlir/IR/Builders.h"
 #include "mlir/IR/BuiltinTypes.h"
 #include "mlir/IR/Diagnostics.h"
 #include "mlir/Pass/Pass.h"
-#include "mlir/Transforms/GreedyPatternRewriteDriver.h"
 
 namespace mlir::iree_compiler::IREE::HAL {
 
+#define GEN_PASS_DEF_MEMOIZEDEVICEQUERIESPASS
+#include "iree/compiler/Dialect/HAL/Transforms/Passes.h.inc"
+
+namespace {
+
+//===----------------------------------------------------------------------===//
+// --iree-hal-memoize-device-queries
+//===----------------------------------------------------------------------===//
+
 // NOTE: this implementation is just for a single active device. As we start to
 // support multiple devices we'll need to change this to be per-device.
-class MemoizeDeviceQueriesPass
-    : public PassWrapper<MemoizeDeviceQueriesPass, OperationPass<ModuleOp>> {
-public:
-  StringRef getArgument() const override {
-    return "iree-hal-memoize-device-queries";
-  }
-
-  StringRef getDescription() const override {
-    return "Caches hal.device.query results for use across the entire module";
-  }
-
+struct MemoizeDeviceQueriesPass
+    : public IREE::HAL::impl::MemoizeDeviceQueriesPassBase<
+          MemoizeDeviceQueriesPass> {
   void runOnOperation() override {
     auto moduleOp = getOperation();
 
@@ -119,10 +119,6 @@
   }
 };
 
-std::unique_ptr<OperationPass<ModuleOp>> createMemoizeDeviceQueriesPass() {
-  return std::make_unique<MemoizeDeviceQueriesPass>();
-}
-
-static PassRegistration<MemoizeDeviceQueriesPass> pass;
+} // namespace
 
 } // namespace mlir::iree_compiler::IREE::HAL
diff --git a/compiler/src/iree/compiler/Dialect/HAL/Transforms/Passes.cpp b/compiler/src/iree/compiler/Dialect/HAL/Transforms/Passes.cpp
index ebdb4f3..1073e71 100644
--- a/compiler/src/iree/compiler/Dialect/HAL/Transforms/Passes.cpp
+++ b/compiler/src/iree/compiler/Dialect/HAL/Transforms/Passes.cpp
@@ -138,6 +138,10 @@
 
 using FunctionLikeNest = MultiOpNest<func::FuncOp, IREE::Util::InitializerOp>;
 
+//===----------------------------------------------------------------------===//
+// Utilities
+//===----------------------------------------------------------------------===//
+
 static void addCleanupPatterns(OpPassManager &passManager) {
   // Standard MLIR cleanup.
   passManager.addPass(mlir::createCSEPass());
@@ -155,6 +159,27 @@
   passManager.addPass(IREE::Util::createFuseGlobalsPass());
 }
 
+static void addExecutableSubstitutionPasses(OpPassManager &passManager,
+                                            ArrayRef<std::string> substitutions,
+                                            StringRef fromPath) {
+  if (!fromPath.empty()) {
+    SubstituteExecutablesPassOptions substituteOptions;
+    substituteOptions.searchPath = fromPath;
+    passManager.addPass(
+        IREE::HAL::createSubstituteExecutablesPass(substituteOptions));
+  }
+  if (!substitutions.empty()) {
+    SubstituteExecutablesPassOptions substituteOptions;
+    substituteOptions.substitutions = substitutions;
+    passManager.addPass(
+        IREE::HAL::createSubstituteExecutablesPass(substituteOptions));
+  }
+}
+
+//===----------------------------------------------------------------------===//
+// --iree-hal-configuration-pipeline
+//===----------------------------------------------------------------------===//
+
 void buildHALConfigurationPassPipeline(
     OpPassManager &passManager, const TargetBackendRegistry &targetRegistry,
     const TargetOptions &targetOptions) {
@@ -178,29 +203,30 @@
     // Today we just assign devices from parameters but we should instead be
     // performing analysis at the flow level and then doing magic device
     // database lookups here.
-    passManager.addPass(
-        createAssignTargetDevicesPass(targetRegistry, targetOptions.targets));
+    passManager.addPass(IREE::HAL::createAssignTargetDevicesPass(
+        {&targetRegistry, targetOptions.targets}));
   }
-  passManager.addPass(createVerifyTargetEnvironmentPass(targetRegistry));
+  passManager.addPass(
+      IREE::HAL::createVerifyTargetEnvironmentPass({targetRegistry}));
 
   // Add dispatch instrumentation prior to materializing interfaces so we can
   // more easily mutate the stream dispatch ops and exports.
   if (auto bufferSize = clInstrumentDispatchBufferSize.getValue()) {
-    passManager.addPass(
-        createMaterializeDispatchInstrumentationPass(bufferSize.value));
+    passManager.addPass(IREE::HAL::createMaterializeDispatchInstrumentationPass(
+        {bufferSize.value}));
   }
 
   // Each executable needs a hal.interface to specify how the host and
   // device communicate across the ABI boundary.
-  passManager.addPass(createMaterializeInterfacesPass());
+  passManager.addPass(IREE::HAL::createMaterializeInterfacesPass());
 
   // Dump a source listing of each hal.executable and update the source
   // locations in the IR. This will allow us to easily inspect each executable
   // and give downstream tools that can display source information something
   // more useful and slim than the entire original source model.
   if (!targetOptions.executableSourcesPath.empty()) {
-    passManager.addPass(
-        createDumpExecutableSourcesPass(targetOptions.executableSourcesPath));
+    passManager.addPass(IREE::HAL::createDumpExecutableSourcesPass(
+        {targetOptions.executableSourcesPath}));
   }
 
   // Substitute hal.executables we've generated from earlier phases of
@@ -209,15 +235,12 @@
   // in various forms without modifying the end-to-end compiler. Note that we do
   // this prior to dumping benchmarks in order to allow generating new
   // benchmarks using the substituted executables.
-  if (!clSubstituteExecutableSourcesFrom.empty()) {
-    passManager.addPass(createSubstituteExecutablesPass(
-        clSubstituteExecutableSourcesFrom.getValue()));
-  }
-  if (!clSubstituteExecutableSource.empty()) {
-    passManager.addPass(
-        createSubstituteExecutablesPass(clSubstituteExecutableSource));
-  }
+  addExecutableSubstitutionPasses(passManager, clSubstituteExecutableSource,
+                                  clSubstituteExecutableSourcesFrom);
 }
+//===----------------------------------------------------------------------===//
+// --iree-hal-transformation-pipeline
+//===----------------------------------------------------------------------===//
 
 void buildHALTransformPassPipeline(OpPassManager &passManager,
                                    const TargetBackendRegistry &targetRegistry,
@@ -241,7 +264,7 @@
     // more variants and even insert or remove variants.
     for (auto command : clPreprocessExecutablesWith) {
       passManager.addNestedPass<IREE::HAL::ExecutableOp>(
-          createPreprocessExecutablesPass(command));
+          IREE::HAL::createPreprocessExecutablesPass(command));
     }
   }
 
@@ -259,30 +282,21 @@
     // selected translation strategies and the features each translation
     // strategy are known to require or not require.
     passManager.addNestedPass<IREE::HAL::ExecutableOp>(
-        createConfigureExecutablesPass(targetRegistry));
+        IREE::HAL::createConfigureExecutablesPass({targetRegistry}));
 
     // Dump a second listing of each hal.executable after preprocessing and
     // configuration of executables, as well as update locations in the IR.
     if (!targetOptions.executableConfigurationsPath.empty()) {
-      passManager.addPass(createDumpExecutableSourcesPass(
-          targetOptions.executableConfigurationsPath, "configured"));
+      passManager.addPass(IREE::HAL::createDumpExecutableSourcesPass(
+          {targetOptions.executableConfigurationsPath, "configured"}));
     }
 
-    if (!clSubstituteExecutableConfiguration.empty()) {
-      passManager.addPass(
-          createSubstituteExecutablesPass(clSubstituteExecutableConfiguration));
-    }
     // Substitute hal.executables we've configured with those specified on the
     // command line. This developer feature allows for hand editing the
     // configured executable with different lowering parameters.
-    if (!clSubstituteExecutableConfigurationsFrom.empty()) {
-      passManager.addPass(createSubstituteExecutablesPass(
-          clSubstituteExecutableConfigurationsFrom.getValue()));
-    }
-    if (!clSubstituteExecutableConfiguration.empty()) {
-      passManager.addPass(
-          createSubstituteExecutablesPass(clSubstituteExecutableConfiguration));
-    }
+    addExecutableSubstitutionPasses(passManager,
+                                    clSubstituteExecutableConfiguration,
+                                    clSubstituteExecutableConfigurationsFrom);
 
     // Dump standalone hal.executable benchmark modules.
     // Today this only works for executables that have static dispatch
@@ -290,8 +304,8 @@
     // after configuration to make it easy to tweak configurations directly
     // from the benchmark.
     if (!targetOptions.executableBenchmarksPath.empty()) {
-      passManager.addPass(createDumpExecutableBenchmarksPass(
-          targetOptions.executableBenchmarksPath));
+      passManager.addPass(IREE::HAL::createDumpExecutableBenchmarksPass(
+          {targetOptions.executableBenchmarksPath}));
     }
   }
 
@@ -311,7 +325,7 @@
 
   if (compileFrom < PipelinePhase::ExecutableTargets) {
     passManager.addNestedPass<IREE::HAL::ExecutableOp>(
-        createTranslateExecutablesPass(targetRegistry));
+        IREE::HAL::createTranslateExecutablesPass({targetRegistry}));
   }
 
   if (compileTo == PipelinePhase::ExecutableTargets)
@@ -324,25 +338,19 @@
   // but sometimes translation is required to produce the host code required
   // for specialization and workgroup counts and we need to perform the
   // substitution later.
-  if (!clSubstituteExecutableObjectsFrom.empty()) {
-    passManager.addPass(createSubstituteExecutablesPass(
-        clSubstituteExecutableObjectsFrom.getValue()));
-  }
-  if (!clSubstituteExecutableObject.empty()) {
-    passManager.addPass(
-        createSubstituteExecutablesPass(clSubstituteExecutableObject));
-  }
+  addExecutableSubstitutionPasses(passManager, clSubstituteExecutableObject,
+                                  clSubstituteExecutableObjectsFrom);
 
   //----------------------------------------------------------------------------
   // Host program conversion
   //----------------------------------------------------------------------------
 
   // Convert supported input dialects (std, stream, etc) into the HAL dialect.
-  passManager.addPass(createConvertToHALPass());
+  passManager.addPass(IREE::HAL::createConvertToHALPass());
 
   // If any devices require the legacy synchronous execution behavior then
   // make all async operations blocking.
-  passManager.addPass(createFixupLegacySyncPass());
+  passManager.addPass(IREE::HAL::createFixupLegacySyncPass());
 
   addCleanupPatterns(passManager);
 
@@ -358,17 +366,17 @@
   // same architecture into a single executable and link it as a shared
   // library.
   if (transformOptions.linkExecutables) {
-    passManager.addPass(createLinkExecutablesPass(targetRegistry));
+    passManager.addPass(IREE::HAL::createLinkExecutablesPass({targetRegistry}));
   }
 
   // Resolve export ordinals from nested symbol references prior to
   // serialization. As this pass creates lookup ops it should run before
   // MaterializeResourceCachesPass.
-  passManager.addPass(createResolveExportOrdinalsPass());
+  passManager.addPass(IREE::HAL::createResolveExportOrdinalsPass());
 
   // Gather cacheable resources such as executables and descriptor sets and
   // cache them at initialization-time.
-  passManager.addPass(createMaterializeResourceCachesPass(targetOptions));
+  passManager.addPass(IREE::HAL::createMaterializeResourceCachesPass());
 
   //----------------------------------------------------------------------------
   // Device management and specialization
@@ -376,19 +384,24 @@
 
   // Memoize device queries such that we don't need to repeatedly ask the same
   // information at runtime.
-  passManager.addPass(createMemoizeDeviceQueriesPass());
+  passManager.addPass(IREE::HAL::createMemoizeDeviceQueriesPass());
 
   // Big cleanup after all our conversion and materialization.
   addCleanupPatterns(passManager);
 
-  // HACK: repeat dispatch ops for benchmarks.
+  // Benchmarking only: repeat dispatch ops a certain number of times.
+  // This is guaranteed to invalidate program output and may introduce crashes
+  // if there are in-place dispatches that expect specific input data.
   if (clBenchmarkDispatchRepeatCount != 1) {
-    passManager.addNestedPass<mlir::func::FuncOp>(
-        createBenchmarkBatchDispatchesPass(clBenchmarkDispatchRepeatCount));
+    FunctionLikeNest(passManager).addPass([&]() {
+      return IREE::HAL::createRepeatDispatchesPass(
+          {clBenchmarkDispatchRepeatCount});
+    });
   }
 
   // Elide redundant command buffer state ops created during conversion.
-  FunctionLikeNest(passManager).addPass(createElideRedundantCommandsPass);
+  FunctionLikeNest(passManager)
+      .addPass(IREE::HAL::createElideRedundantCommandsPass);
 
   // Fixup workgroup count calculations that may have used the affine dialect.
   // Kind of random here but can happen if the benchmarking code does things.
@@ -410,10 +423,10 @@
   // contents not turned into a big base64 string.
   if (transformOptions.serializeExecutables) {
     passManager.addNestedPass<IREE::HAL::ExecutableOp>(
-        createSerializeExecutablesPass(
-            targetRegistry, targetOptions.debugLevel,
-            targetOptions.executableIntermediatesPath,
-            targetOptions.executableBinariesPath));
+        IREE::HAL::createSerializeExecutablesPass(
+            {&targetRegistry, targetOptions.debugLevel,
+             targetOptions.executableIntermediatesPath,
+             targetOptions.executableBinariesPath}));
 
     // NOTE: symbol DCE will destroy executable target contents, so only run
     // it if we serialized things.
@@ -457,21 +470,35 @@
                                 transformOptions, compileFrom, compileTo);
 }
 
-void registerHALConfigurationPassPipeline() {
+//===----------------------------------------------------------------------===//
+// Registration
+//===----------------------------------------------------------------------===//
+
+namespace {
+#define GEN_PASS_REGISTRATION
+#include "iree/compiler/Dialect/HAL/Transforms/Passes.h.inc" // IWYU pragma: export
+} // namespace
+
+void registerHALPasses() {
+  // Force the flags to be bound.
+  // TODO(benvanik): remove the global flags and only rely on pipeline flags.
+  (void)IREE::HAL::TargetOptions::FromFlags::get();
+
+  // Generated.
+  registerPasses();
+
+  // Pipelines.
   PassPipelineRegistration<>("iree-hal-configuration-pipeline",
-                             "Runs the IREE HAL dialect configuration pipeline",
+                             "Runs HAL target configuration pipeline.",
                              [](OpPassManager &passManager) {
                                buildHALConfigurationPassPipeline(
                                    passManager,
                                    TargetBackendRegistry::getGlobal(),
                                    TargetOptions::FromFlags::get());
                              });
-}
-
-void registerHALTransformPassPipeline() {
   PassPipelineRegistration<TransformOptions>(
       "iree-hal-transformation-pipeline",
-      "Runs the full IREE HAL dialect transformation pipeline",
+      "Runs the full IREE HAL conversion/lowering pipeline.",
       [](OpPassManager &passManager, const TransformOptions &transformOptions) {
         buildHALTransformPassPipeline(
             passManager, TargetBackendRegistry::getGlobal(),
diff --git a/compiler/src/iree/compiler/Dialect/HAL/Transforms/Passes.h b/compiler/src/iree/compiler/Dialect/HAL/Transforms/Passes.h
index 97778d1..18050b9 100644
--- a/compiler/src/iree/compiler/Dialect/HAL/Transforms/Passes.h
+++ b/compiler/src/iree/compiler/Dialect/HAL/Transforms/Passes.h
@@ -20,7 +20,7 @@
 namespace mlir::iree_compiler::IREE::HAL {
 
 //===----------------------------------------------------------------------===//
-// Helpers
+// Pipelines
 //===----------------------------------------------------------------------===//
 
 enum class PipelinePhase {
@@ -37,6 +37,13 @@
   End,
 };
 
+// Adds a set of passes to the given pass manager that run the head of the HAL
+// pipeline to assign devices, materialize interfaces, and translate
+// executables. The host portion of the program is annotated but not modified.
+void buildHALConfigurationPassPipeline(
+    OpPassManager &passManager, const TargetBackendRegistry &targetRegistry,
+    const TargetOptions &targetOptions);
+
 // Adds a set of passes to the given pass manager that run the required HAL
 // transforms in the canonical order.
 //
@@ -53,193 +60,22 @@
     PipelinePhase compileFrom = PipelinePhase::Start,
     PipelinePhase compileTo = PipelinePhase::End);
 
-// Adds a set of passes to the given pass manager that run the head of the HAL
-// pipeline to assign devices, materialize interfaces, and translate
-// executables. The host portion of the program is annotated but not modified.
-void buildHALConfigurationPassPipeline(
-    OpPassManager &passManager, const TargetBackendRegistry &targetRegistry,
-    const TargetOptions &targetOptions);
-
-void registerHALTransformPassPipeline();
-void registerHALConfigurationPassPipeline();
-
 //===----------------------------------------------------------------------===//
-// Conversion
+// Passes
 //===----------------------------------------------------------------------===//
 
-// Converts input flow/std/etc dialects to the IREE HAL dialect.
-std::unique_ptr<OperationPass<mlir::ModuleOp>> createConvertToHALPass();
-
-//===----------------------------------------------------------------------===//
-// Device management
-//===----------------------------------------------------------------------===//
-
-// Verifies that the target execution environment is valid.
-// #hal.device.target and #hal.executable.target attribute placement and
-// definition will be checked as well along with other structural requirements.
-std::unique_ptr<OperationPass<mlir::ModuleOp>>
-createVerifyTargetEnvironmentPass(const TargetBackendRegistry &targetRegistry);
-
-// Assigns the HAL devices the module will target to the given list of targets.
-std::unique_ptr<OperationPass<mlir::ModuleOp>>
-createAssignTargetDevicesPass(const TargetBackendRegistry &targetRegistry,
-                              ArrayRef<std::string> targets);
-
-// 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.
-std::unique_ptr<OperationPass<mlir::ModuleOp>> createFixupLegacySyncPass();
-
-// Finds hal.device.query ops and creates variables initialized on startup.
-std::unique_ptr<OperationPass<mlir::ModuleOp>> createMemoizeDeviceQueriesPass();
-
-//===----------------------------------------------------------------------===//
-// Executable translation
-//===----------------------------------------------------------------------===//
-
-// Defines hal.executables and hal.interfaces for flow.executable ops based on
-// usage within the module. Target backends are queried to check for support and
-// device placements are made.
-std::unique_ptr<OperationPass<mlir::ModuleOp>>
-createMaterializeInterfacesPass();
-
-// Dumps individual hal.executable source listings to |path|.
-std::unique_ptr<OperationPass<mlir::ModuleOp>>
-createDumpExecutableSourcesPass(StringRef path, StringRef prefix = "");
-
-// Dumps standalone hal.executable benchmarks to |path|.
-std::unique_ptr<OperationPass<mlir::ModuleOp>>
-createDumpExecutableBenchmarksPass(StringRef path);
-
-// Strips executable module contents for reducing IR size during debugging.
-std::unique_ptr<OperationPass<mlir::ModuleOp>>
-createStripExecutableContentsPass();
-
-// Substitutes hal.executable ops by parsing |substitutions| in
-// `executable_name=file.xxx` strings. File paths may be absolute or relative to
-// the paths specified on `--iree-hal-executable-object-search-path=`.
-std::unique_ptr<OperationPass<mlir::ModuleOp>>
-createSubstituteExecutablesPass(ArrayRef<std::string> substitutions = {});
-// Substitutes hal.executable ops with files in the given |searchPath| matching
-// the symbol name.
-std::unique_ptr<OperationPass<mlir::ModuleOp>>
-createSubstituteExecutablesPass(std::string searchPath);
-
-// Preprocess each executable with either a pass pipeline or external tool.
-std::unique_ptr<OperationPass<IREE::HAL::ExecutableOp>>
-createPreprocessExecutablesPass(std::string command);
-// Preprocesses each executable with a pass pipeline.
-std::unique_ptr<OperationPass<IREE::HAL::ExecutableOp>>
-createPreprocessExecutablesWithPipelinePass(std::string pipeline);
-// Preprocesses each executable with an external tool.
-std::unique_ptr<OperationPass<IREE::HAL::ExecutableOp>>
-createPreprocessExecutablesWithToolPass(std::string command);
-
-// Configures hal.executable.variant ops in all hal.executable ops via a nested
-// translation pipeline.
-std::unique_ptr<OperationPass<IREE::HAL::ExecutableOp>>
-createConfigureExecutablesPass(const TargetBackendRegistry &targetRegistry);
-
-// Configures hal.executable.variant ops for the specified |target| backend.
-std::unique_ptr<OperationPass<IREE::HAL::ExecutableVariantOp>>
-createConfigureTargetExecutableVariantsPass(
-    const TargetBackendRegistry &targetRegistry, StringRef target);
-
-// Translates hal.executable.variant ops via a nested translation pipeline.
-std::unique_ptr<OperationPass<IREE::HAL::ExecutableOp>>
-createTranslateExecutablesPass(const TargetBackendRegistry &targetRegistry);
-
-// Translates hal.executable.variant ops for the specified |target| backend.
-std::unique_ptr<OperationPass<IREE::HAL::ExecutableVariantOp>>
-createTranslateTargetExecutableVariantsPass(
-    const TargetBackendRegistry &targetRegistry, StringRef target);
-
-// Calls into each target backend to have it link multiple hal.executables
-// together (if that makes sense). For example, the LLVM AOT backend may combine
-// all executable targets for the same architecture into a single executable and
-// link it as a shared library.
-std::unique_ptr<OperationPass<mlir::ModuleOp>>
-createLinkExecutablesPass(const TargetBackendRegistry &targetRegistry);
-
-// Links executables for the specified |target| backend.
-std::unique_ptr<OperationPass<mlir::ModuleOp>>
-createLinkTargetExecutablesPass(const TargetBackendRegistry &targetRegistry,
-                                StringRef target);
-
-// Resolves hal.executable.export references to ordinals.
-std::unique_ptr<OperationPass<mlir::ModuleOp>>
-createResolveExportOrdinalsPass();
-
-// Converts hal.executable.variants to one or more hal.executable.binary ops.
-std::unique_ptr<OperationPass<IREE::HAL::ExecutableOp>>
-createSerializeExecutablesPass(const TargetBackendRegistry &targetRegistry,
-                               int debugLevel = 2,
-                               std::string dumpIntermediatesPath = "",
-                               std::string dumpBinariesPath = "");
-
-// Serializes executables for the specified |target| backend.
-std::unique_ptr<OperationPass<IREE::HAL::ExecutableOp>>
-createSerializeTargetExecutablesPass(
-    const TargetBackendRegistry &targetRegistry, StringRef target,
-    int debugLevel = 2, std::string dumpIntermediatesPath = "",
-    std::string dumpBinariesPath = "");
-
-//===----------------------------------------------------------------------===//
-// Resource initialization, caching, and optimization
-//===----------------------------------------------------------------------===//
-
-// Materializes host and device dispatch instrumentation resources on stream IR.
-std::unique_ptr<OperationPass<mlir::ModuleOp>>
-createMaterializeDispatchInstrumentationPass(int64_t bufferSize);
-
-// Finds all resource lookups (such as hal.executable.lookup), materializes
-// their cache storage and initialization, and rewrites the lookups to
-// references.
-std::unique_ptr<OperationPass<mlir::ModuleOp>>
-createMaterializeResourceCachesPass(TargetOptions targetOptions);
-
-// Elides stateful command buffer ops that set redundant state.
-std::unique_ptr<OperationPass<void>> createElideRedundantCommandsPass();
-
-// Repeats dispatches `iree-hal-repeat-dispatch-num` times, which is 1 by
-// default.
-std::unique_ptr<OperationPass<func::FuncOp>>
-createBenchmarkBatchDispatchesPass(unsigned repeatCount);
+// Preprocesses each executable with an MLIR pass pipeline or external command
+// line tool.
+std::unique_ptr<Pass> createPreprocessExecutablesPass(std::string command = "");
 
 //===----------------------------------------------------------------------===//
 // Register all Passes
 //===----------------------------------------------------------------------===//
 
-inline void registerHALPasses() {
-  registerHALTransformPassPipeline();
-  registerHALConfigurationPassPipeline();
-  auto targetOptions = TargetOptions::FromFlags::get();
-  createAssignTargetDevicesPass(TargetBackendRegistry::getGlobal(), {});
-  createBenchmarkBatchDispatchesPass(/*repeatCount=*/1);
-  createConfigureExecutablesPass(TargetBackendRegistry::getGlobal());
-  createConfigureTargetExecutableVariantsPass(
-      TargetBackendRegistry::getGlobal(), "");
-  createConvertToHALPass();
-  createDumpExecutableSourcesPass("");
-  createElideRedundantCommandsPass();
-  createFixupLegacySyncPass();
-  createLinkExecutablesPass(TargetBackendRegistry::getGlobal());
-  createLinkTargetExecutablesPass(TargetBackendRegistry::getGlobal(), "");
-  createMaterializeDispatchInstrumentationPass(0);
-  createMaterializeInterfacesPass();
-  createMaterializeResourceCachesPass(targetOptions);
-  createMemoizeDeviceQueriesPass();
-  createPreprocessExecutablesPass("");
-  createResolveExportOrdinalsPass();
-  createSerializeExecutablesPass(TargetBackendRegistry::getGlobal());
-  createSerializeTargetExecutablesPass(TargetBackendRegistry::getGlobal(), "");
-  createStripExecutableContentsPass();
-  createSubstituteExecutablesPass();
-  createTranslateExecutablesPass(TargetBackendRegistry::getGlobal());
-  createTranslateTargetExecutableVariantsPass(
-      TargetBackendRegistry::getGlobal(), "");
-  createVerifyTargetEnvironmentPass(TargetBackendRegistry::getGlobal());
-}
+#define GEN_PASS_DECL
+#include "iree/compiler/Dialect/HAL/Transforms/Passes.h.inc" // IWYU pragma: keep
+
+void registerHALPasses();
 
 } // namespace mlir::iree_compiler::IREE::HAL
 
diff --git a/compiler/src/iree/compiler/Dialect/HAL/Transforms/Passes.td b/compiler/src/iree/compiler/Dialect/HAL/Transforms/Passes.td
new file mode 100644
index 0000000..0c8f376
--- /dev/null
+++ b/compiler/src/iree/compiler/Dialect/HAL/Transforms/Passes.td
@@ -0,0 +1,562 @@
+// 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 VerifyTargetEnvironmentPass :
+    Pass<"iree-hal-verify-target-environment", "mlir::ModuleOp"> {
+  let summary = "Verifies that the target execution environment is valid.";
+  let description = [{
+    Verifies that the target execution environment is valid.
+    `#hal.device.target` and `#hal.executable.target` attribute placement and
+    definition will be checked that they reference the available target backends
+    and that they are structurally valid.
+  }];
+  let options = [
+    Option<
+      "targetRegistry", "target-registry",
+      "llvm::cl::TargetBackendRegistryRef", "",
+      "Target backend registry containing the list of available backends."
+    >,
+  ];
+}
+
+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 targets.";
+  let description = [{
+    DO NOT SUBMIT
+  }];
+  let options = [
+    Option<
+      "targetRegistry", "target-registry",
+      "llvm::cl::TargetBackendRegistryRef", "",
+      "Target backend registry containing the list of available backends."
+    >,
+    ListOption<
+      "targets", "targets",
+      "std::string",
+      "List of devices to assign as targets."
+    >,
+  ];
+}
+
+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 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 = [{
+    DO NOT SUBMIT
+  }];
+  let options = [
+    Option<
+      "targetRegistry", "target-registry",
+      "llvm::cl::TargetBackendRegistryRef", "",
+      "Target backend registry containing the list of available 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 = [{
+    DO NOT SUBMIT
+  }];
+  let options = [
+    Option<
+      "targetRegistry", "target-registry",
+      "llvm::cl::TargetBackendRegistryRef", "",
+      "Target backend registry containing the list of available backends."
+    >,
+    Option<
+      "target", "target",
+      "std::string", "",
+      "Target backend name whose executable variants will be configured by this pass."
+    >,
+  ];
+}
+
+def TranslateExecutablesPass :
+    Pass<"iree-hal-translate-executables", "IREE::HAL::ExecutableOp"> {
+  let summary = "Translates hal.executable ops via a nested translation pipeline.";
+  let description = [{
+    DO NOT SUBMIT
+  }];
+  let options = [
+    Option<
+      "targetRegistry", "target-registry",
+      "llvm::cl::TargetBackendRegistryRef", "",
+      "Target backend registry containing the list of available 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 = [{
+    DO NOT SUBMIT
+  }];
+  let options = [
+    Option<
+      "targetRegistry", "target-registry",
+      "llvm::cl::TargetBackendRegistryRef", "",
+      "Target backend registry containing the list of available backends."
+    >,
+    Option<
+      "target", "target",
+      "std::string", "",
+      "Target backend name whose executable variants will be translated by this pass."
+    >,
+  ];
+}
+
+def LinkExecutablesPass :
+    Pass<"iree-hal-link-executables", "mlir::ModuleOp"> {
+  let summary = "Links hal.executable ops into one or more hal.executable ops.";
+  let description = [{
+    Calls into each target backend to have it link multiple `hal.executable` ops
+    together (if the backend desires).
+
+    DO NOT SUBMIT
+  }];
+  let options = [
+    Option<
+      "targetRegistry", "target-registry",
+      "llvm::cl::TargetBackendRegistryRef", "",
+      "Target backend registry containing the list of available backends."
+    >,
+  ];
+}
+
+def LinkTargetExecutablesPass :
+    Pass<"iree-hal-link-target-executables", "mlir::ModuleOp"> {
+  let summary = "Links executables for the specified target backend.";
+  let description = [{
+    DO NOT SUBMIT
+  }];
+  let options = [
+    Option<
+      "targetRegistry", "target-registry",
+      "llvm::cl::TargetBackendRegistryRef", "",
+      "Target backend registry containing the list of available 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 = [
+    "IREE::HAL::HALDialect",
+  ];
+}
+
+def SerializeExecutablesPass :
+    Pass<"iree-hal-serialize-executables", "IREE::HAL::ExecutableOp"> {
+  let summary = "Converts hal.executable.variants to one or more hal.executable.binary ops.";
+  let description = [{
+    DO NOT SUBMIT
+  }];
+  let options = [
+    Option<
+      "targetRegistry", "target-registry",
+      "llvm::cl::TargetBackendRegistryRef", "",
+      "Target backend registry containing the list of available 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 = [{
+    DO NOT SUBMIT
+  }];
+  let options = [
+    Option<
+      "targetRegistry", "target-registry",
+      "llvm::cl::TargetBackendRegistryRef", "",
+      "Target backend registry containing the list of available 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",
+    "mlir::func::FuncDialect",
+    "IREE::HAL::HALDialect",
+    "IREE::Stream::StreamDialect",
+    "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::func::FuncDialect",
+    "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 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::func::FuncDialect",
+    "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
diff --git a/compiler/src/iree/compiler/Dialect/HAL/Transforms/PreprocessExecutables.cpp b/compiler/src/iree/compiler/Dialect/HAL/Transforms/PreprocessExecutables.cpp
index b668c25..b1c96ba 100644
--- a/compiler/src/iree/compiler/Dialect/HAL/Transforms/PreprocessExecutables.cpp
+++ b/compiler/src/iree/compiler/Dialect/HAL/Transforms/PreprocessExecutables.cpp
@@ -27,6 +27,12 @@
 
 namespace mlir::iree_compiler::IREE::HAL {
 
+#define GEN_PASS_DEF_PREPROCESSEXECUTABLESWITHPIPELINEPASS
+#define GEN_PASS_DEF_PREPROCESSEXECUTABLESWITHTOOLPASS
+#include "iree/compiler/Dialect/HAL/Transforms/Passes.h.inc"
+
+namespace {
+
 static StringRef fixupArg(StringRef arg) {
   // HACK: pass pipeline parsing doesn't handle strings with spaces and the only
   // way to get them through (I could find) is to double quote them. This
@@ -212,20 +218,16 @@
   return success();
 }
 
-class PreprocessExecutablesPass
-    : public PassWrapper<PreprocessExecutablesPass,
-                         OperationPass<IREE::HAL::ExecutableOp>> {
-public:
-  PreprocessExecutablesPass() = default;
-  PreprocessExecutablesPass(const PreprocessExecutablesPass &pass) {}
-  PreprocessExecutablesPass(std::optional<std::string> pipeline,
-                            std::optional<std::string> command) {
-    if (pipeline.has_value()) {
-      this->pipeline = std::move(pipeline).value();
-    } else if (command.has_value()) {
-      this->command = std::move(command).value();
-    }
-  }
+//===----------------------------------------------------------------------===//
+// --iree-hal-preprocess-executables-with-pipeline
+//===----------------------------------------------------------------------===//
+
+struct PreprocessExecutablesWithPipelinePass
+    : public IREE::HAL::impl::PreprocessExecutablesWithPipelinePassBase<
+          PreprocessExecutablesWithPipelinePass> {
+  using IREE::HAL::impl::PreprocessExecutablesWithPipelinePassBase<
+      PreprocessExecutablesWithPipelinePass>::
+      PreprocessExecutablesWithPipelinePassBase;
 
   void getDependentDialects(DialectRegistry &registry) const override {
     registry.insert<IREE::HAL::HALDialect>();
@@ -239,79 +241,56 @@
     }
   }
 
-  StringRef getArgument() const override {
-    return "iree-hal-preprocess-executables";
-  }
-
-  StringRef getDescription() const override {
-    return "Preprocesses each executable with a pass pipeline or external "
-           "tool.";
-  }
-
   void runOnOperation() override {
+    if (!pipeline.hasValue())
+      return;
     auto executableOp = getOperation();
-    if (pipeline.hasValue()) {
-      OpPassManager passManager(executableOp.getOperationName());
-      if (failed(buildPassPipeline(pipeline, passManager))) {
-        llvm::errs() << "ERROR: failed to parse preprocessing pipeline `"
-                     << pipeline << "`\n";
-        return signalPassFailure();
-      }
-      if (failed(runPipeline(passManager, executableOp))) {
-        llvm::errs() << "ERROR: failed to preprocess executable `"
-                     << executableOp.getName() << "` using pipeline `"
-                     << pipeline << "`\n";
-        return signalPassFailure();
-      }
-    } else if (command.hasValue()) {
-      if (failed(preprocessWithCommand(executableOp, command))) {
-        llvm::errs() << "ERROR: failed to preprocess executable `"
-                     << executableOp.getName() << "` using command `" << command
-                     << "`\n";
-        return signalPassFailure();
-      }
+    OpPassManager passManager(executableOp.getOperationName());
+    if (failed(buildPassPipeline(pipeline, passManager))) {
+      llvm::errs() << "ERROR: failed to parse preprocessing pipeline `"
+                   << pipeline << "`\n";
+      return signalPassFailure();
+    }
+    if (failed(runPipeline(passManager, executableOp))) {
+      llvm::errs() << "ERROR: failed to preprocess executable `"
+                   << executableOp.getName() << "` using pipeline `" << pipeline
+                   << "`\n";
+      return signalPassFailure();
     }
   }
-
-private:
-  Option<std::string> pipeline{
-      *this,
-      "pipeline",
-      llvm::cl::desc("Pass pipeline used to preprocess the executable."),
-      llvm::cl::init(""),
-  };
-  Option<std::string> command{
-      *this,
-      "command",
-      llvm::cl::desc("Shell command used to preprocess the executable."),
-      llvm::cl::init(""),
-  };
 };
 
-std::unique_ptr<OperationPass<IREE::HAL::ExecutableOp>>
-createPreprocessExecutablesPass(std::string rawCommand) {
+//===----------------------------------------------------------------------===//
+// --iree-hal-preprocess-executables-with-tool
+//===----------------------------------------------------------------------===//
+
+struct PreprocessExecutablesWithToolPass
+    : public IREE::HAL::impl::PreprocessExecutablesWithToolPassBase<
+          PreprocessExecutablesWithToolPass> {
+  using IREE::HAL::impl::PreprocessExecutablesWithToolPassBase<
+      PreprocessExecutablesWithToolPass>::PreprocessExecutablesWithToolPassBase;
+  void runOnOperation() override {
+    if (!command.hasValue())
+      return;
+    auto executableOp = getOperation();
+    if (failed(preprocessWithCommand(executableOp, command))) {
+      llvm::errs() << "ERROR: failed to preprocess executable `"
+                   << executableOp.getName() << "` using command `" << command
+                   << "`\n";
+      return signalPassFailure();
+    }
+  }
+};
+
+} // namespace
+
+std::unique_ptr<Pass> createPreprocessExecutablesPass(std::string rawCommand) {
   auto command = fixupArg(rawCommand);
   if (command.starts_with("builtin.module")) {
-    return createPreprocessExecutablesWithPipelinePass(command.str());
+    return createPreprocessExecutablesWithPipelinePass({command.str()});
   } else {
-    return createPreprocessExecutablesWithToolPass(command.str());
+    return createPreprocessExecutablesWithToolPass({command.str()});
   }
 }
 
-std::unique_ptr<OperationPass<IREE::HAL::ExecutableOp>>
-createPreprocessExecutablesWithPipelinePass(std::string pipeline) {
-  return std::make_unique<PreprocessExecutablesPass>(std::move(pipeline),
-                                                     std::nullopt);
-}
-
-std::unique_ptr<OperationPass<IREE::HAL::ExecutableOp>>
-createPreprocessExecutablesWithToolPass(std::string command) {
-  return std::make_unique<PreprocessExecutablesPass>(std::nullopt,
-                                                     std::move(command));
-}
-
-static PassRegistration<PreprocessExecutablesPass> pass([] {
-  return std::make_unique<PreprocessExecutablesPass>();
-});
-
 } // namespace mlir::iree_compiler::IREE::HAL
diff --git a/compiler/src/iree/compiler/Dialect/HAL/Transforms/RepeatDispatches.cpp b/compiler/src/iree/compiler/Dialect/HAL/Transforms/RepeatDispatches.cpp
new file mode 100644
index 0000000..d7ee923
--- /dev/null
+++ b/compiler/src/iree/compiler/Dialect/HAL/Transforms/RepeatDispatches.cpp
@@ -0,0 +1,58 @@
+// Copyright 2021 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
+
+#include "iree/compiler/Dialect/HAL/IR/HALDialect.h"
+#include "iree/compiler/Dialect/HAL/IR/HALOps.h"
+#include "iree/compiler/Dialect/HAL/IR/HALTypes.h"
+#include "iree/compiler/Dialect/HAL/Transforms/Passes.h"
+#include "mlir/Pass/Pass.h"
+
+namespace mlir::iree_compiler::IREE::HAL {
+
+#define GEN_PASS_DEF_REPEATDISPATCHESPASS
+#include "iree/compiler/Dialect/HAL/Transforms/Passes.h.inc"
+
+namespace {
+
+//===----------------------------------------------------------------------===//
+// --iree-hal-repeat-dispatches
+//===----------------------------------------------------------------------===//
+
+struct RepeatDispatchesPass
+    : public IREE::HAL::impl::RepeatDispatchesPassBase<RepeatDispatchesPass> {
+  using IREE::HAL::impl::RepeatDispatchesPassBase<
+      RepeatDispatchesPass>::RepeatDispatchesPassBase;
+  void runOnOperation() override {
+    // Collect all (nested) command buffer dispatch ops.
+    SmallVector<IREE::HAL::CommandBufferDispatchOp> ops;
+    getOperation()->walk(
+        [&ops](IREE::HAL::CommandBufferDispatchOp op) { ops.push_back(op); });
+    for (auto op : ops) {
+      OpBuilder builder(op);
+      for (unsigned i = 1; i < repeatCount; ++i) {
+        // Clone the op at its original location in the IR.
+        builder.clone(*op.getOperation());
+        // Add a barrier after each clone. If the original dispatch has a small
+        // problem size, simply duplicating without barrier will increase the
+        // number of subgroups and thus "help" filling the GPU. In the end we
+        // will have an over optimistic result. Inserting barriers avoids that,
+        // but it assumes that the command buffer has a linear dispatch
+        // structure.
+        builder.create<IREE::HAL::CommandBufferExecutionBarrierOp>(
+            op.getLoc(), op.getCommandBuffer(),
+            IREE::HAL::ExecutionStageBitfield::CommandRetire |
+                IREE::HAL::ExecutionStageBitfield::Dispatch,
+            IREE::HAL::ExecutionStageBitfield::CommandIssue |
+                IREE::HAL::ExecutionStageBitfield::Dispatch,
+            IREE::HAL::ExecutionBarrierFlagBitfield::None);
+      }
+    }
+  }
+};
+
+} // namespace
+
+} // namespace mlir::iree_compiler::IREE::HAL
diff --git a/compiler/src/iree/compiler/Dialect/HAL/Transforms/ResolveExportOrdinals.cpp b/compiler/src/iree/compiler/Dialect/HAL/Transforms/ResolveExportOrdinals.cpp
index c96819a..7d30bb1 100644
--- a/compiler/src/iree/compiler/Dialect/HAL/Transforms/ResolveExportOrdinals.cpp
+++ b/compiler/src/iree/compiler/Dialect/HAL/Transforms/ResolveExportOrdinals.cpp
@@ -4,6 +4,7 @@
 // See https://llvm.org/LICENSE.txt for license information.
 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 
+#include "iree/compiler/Dialect/HAL/IR/HALDialect.h"
 #include "iree/compiler/Dialect/HAL/IR/HALOps.h"
 #include "iree/compiler/Dialect/HAL/Transforms/Passes.h"
 #include "mlir/Pass/Pass.h"
@@ -11,9 +12,13 @@
 
 namespace mlir::iree_compiler::IREE::HAL {
 
-class ResolveCommandBufferDispatchOrdinals
+#define GEN_PASS_DEF_RESOLVEEXPORTORDINALSPASS
+#include "iree/compiler/Dialect/HAL/Transforms/Passes.h.inc"
+
+namespace {
+
+struct ResolveCommandBufferDispatchOrdinals
     : public OpRewritePattern<IREE::HAL::CommandBufferDispatchSymbolOp> {
-public:
   using OpRewritePattern<
       IREE::HAL::CommandBufferDispatchSymbolOp>::OpRewritePattern;
   LogicalResult matchAndRewrite(IREE::HAL::CommandBufferDispatchSymbolOp op,
@@ -39,10 +44,9 @@
   }
 };
 
-class ResolveCommandBufferDispatchIndirectOrdinals
+struct ResolveCommandBufferDispatchIndirectOrdinals
     : public OpRewritePattern<
           IREE::HAL::CommandBufferDispatchIndirectSymbolOp> {
-public:
   using OpRewritePattern<
       IREE::HAL::CommandBufferDispatchIndirectSymbolOp>::OpRewritePattern;
   LogicalResult
@@ -69,17 +73,13 @@
   }
 };
 
-class ResolveExportOrdinalsPass
-    : public PassWrapper<ResolveExportOrdinalsPass, OperationPass<ModuleOp>> {
-public:
-  StringRef getArgument() const override {
-    return "iree-hal-resolve-export-ordinals";
-  }
+//===----------------------------------------------------------------------===//
+// --iree-hal-resolve-export-ordinals
+//===----------------------------------------------------------------------===//
 
-  StringRef getDescription() const override {
-    return "Resolves hal.executable.export references to ordinals";
-  }
-
+struct ResolveExportOrdinalsPass
+    : public IREE::HAL::impl::ResolveExportOrdinalsPassBase<
+          ResolveExportOrdinalsPass> {
   void runOnOperation() override {
     MLIRContext *context = &getContext();
     RewritePatternSet patterns(&getContext());
@@ -92,10 +92,6 @@
   }
 };
 
-std::unique_ptr<OperationPass<ModuleOp>> createResolveExportOrdinalsPass() {
-  return std::make_unique<ResolveExportOrdinalsPass>();
-}
-
-static PassRegistration<ResolveExportOrdinalsPass> pass;
+} // namespace
 
 } // namespace mlir::iree_compiler::IREE::HAL
diff --git a/compiler/src/iree/compiler/Dialect/HAL/Transforms/SerializeExecutables.cpp b/compiler/src/iree/compiler/Dialect/HAL/Transforms/SerializeExecutables.cpp
index 4b2b5c2..99a7df0 100644
--- a/compiler/src/iree/compiler/Dialect/HAL/Transforms/SerializeExecutables.cpp
+++ b/compiler/src/iree/compiler/Dialect/HAL/Transforms/SerializeExecutables.cpp
@@ -11,6 +11,8 @@
 #include "iree/compiler/Dialect/HAL/IR/HALOps.h"
 #include "iree/compiler/Dialect/HAL/Target/TargetBackend.h"
 #include "iree/compiler/Dialect/HAL/Target/TargetRegistry.h"
+#include "iree/compiler/Dialect/HAL/Transforms/Passes.h"
+#include "iree/compiler/Utils/TracingUtils.h"
 #include "llvm/ADT/StringSet.h"
 #include "llvm/Support/FileSystem.h"
 #include "mlir/IR/Attributes.h"
@@ -21,36 +23,25 @@
 
 namespace mlir::iree_compiler::IREE::HAL {
 
-class SerializeTargetExecutablesPass
-    : public PassWrapper<SerializeTargetExecutablesPass,
-                         OperationPass<IREE::HAL::ExecutableOp>> {
-public:
-  SerializeTargetExecutablesPass()
-      : targetRegistry(TargetBackendRegistry::getGlobal()) {}
-  SerializeTargetExecutablesPass(const SerializeTargetExecutablesPass &pass)
-      : targetRegistry(pass.targetRegistry) {}
-  SerializeTargetExecutablesPass(const TargetBackendRegistry &targetRegistry,
-                                 StringRef target, int debugLevel,
-                                 std::string dumpIntermediatesPath,
-                                 std::string dumpBinariesPath)
-      : targetRegistry(targetRegistry) {
-    this->target = target.str();
-    this->debugLevel = debugLevel;
-    this->dumpIntermediatesPath = dumpIntermediatesPath;
-    this->dumpBinariesPath = dumpBinariesPath;
-  }
+#define GEN_PASS_DEF_SERIALIZEEXECUTABLESPASS
+#define GEN_PASS_DEF_SERIALIZETARGETEXECUTABLESPASS
+#include "iree/compiler/Dialect/HAL/Transforms/Passes.h.inc"
 
-  StringRef getArgument() const override {
-    return "iree-hal-serialize-target-executables";
-  }
+namespace {
 
-  StringRef getDescription() const override {
-    return "Serializes hal.executable.variant ops to hal.executable.binary ops";
-  }
+//===----------------------------------------------------------------------===//
+// --iree-hal-serialize-target-executables
+//===----------------------------------------------------------------------===//
+
+struct SerializeTargetExecutablesPass
+    : public IREE::HAL::impl::SerializeTargetExecutablesPassBase<
+          SerializeTargetExecutablesPass> {
+  using IREE::HAL::impl::SerializeTargetExecutablesPassBase<
+      SerializeTargetExecutablesPass>::SerializeTargetExecutablesPassBase;
 
   void getDependentDialects(DialectRegistry &registry) const override {
     registry.insert<IREE::HAL::HALDialect>();
-    auto targetBackend = targetRegistry.getTargetBackend(target);
+    auto targetBackend = targetRegistry->getTargetBackend(target);
     if (targetBackend) {
       targetBackend->getDependentDialects(registry);
     }
@@ -60,7 +51,7 @@
     auto executableOp = getOperation();
     auto moduleOp = executableOp->getParentOfType<mlir::ModuleOp>();
 
-    auto targetBackend = targetRegistry.getTargetBackend(target);
+    auto targetBackend = targetRegistry->getTargetBackend(target);
     if (!targetBackend) {
       executableOp.emitError()
           << "unregistered target backend '" << target << "'";
@@ -102,96 +93,35 @@
       variantOp.erase();
     }
   }
-
-private:
-  Option<std::string> target{
-      *this, "target",
-      llvm::cl::desc(
-          "Target backend name whose executables will be serialized by "
-          "this pass.")};
-
-  Option<int> debugLevel{*this, "debug-level",
-                         llvm::cl::desc("Debug level for serialization (0-3)"),
-                         llvm::cl::init(2)};
-  Option<std::string> dumpIntermediatesPath{
-      *this, "dump-intermediates-path",
-      llvm::cl::desc("Path to write translated executable intermediates (.bc, "
-                     ".o, etc) into for debugging.")};
-  Option<std::string> dumpBinariesPath{
-      *this, "dump-binaries-path",
-      llvm::cl::desc("Path to write translated and serialized executable "
-                     "binaries into for debugging.")};
-
-  const TargetBackendRegistry &targetRegistry;
 };
 
-std::unique_ptr<OperationPass<IREE::HAL::ExecutableOp>>
-createSerializeTargetExecutablesPass(
-    const TargetBackendRegistry &targetRegistry, StringRef target,
-    int debugLevel, std::string dumpIntermediatesPath,
-    std::string dumpBinariesPath) {
-  return std::make_unique<SerializeTargetExecutablesPass>(
-      targetRegistry, target, debugLevel, dumpIntermediatesPath,
-      dumpBinariesPath);
-}
+//===----------------------------------------------------------------------===//
+// --iree-hal-serialize-executables
+//===----------------------------------------------------------------------===//
 
-static PassRegistration<SerializeTargetExecutablesPass> linkTargetPass([] {
-  return std::make_unique<SerializeTargetExecutablesPass>();
-});
-
-class SerializeExecutablesPass
-    : public PassWrapper<SerializeExecutablesPass,
-                         OperationPass<IREE::HAL::ExecutableOp>> {
-public:
-  SerializeExecutablesPass()
-      : targetRegistry(TargetBackendRegistry::getGlobal()) {}
-  SerializeExecutablesPass(const TargetBackendRegistry &targetRegistry,
-                           int debugLevel, std::string dumpIntermediatesPath,
-                           std::string dumpBinariesPath)
-      : targetRegistry(targetRegistry), debugLevel(debugLevel),
-        dumpIntermediatesPath(dumpIntermediatesPath),
-        dumpBinariesPath(dumpBinariesPath) {}
-
-  StringRef getArgument() const override {
-    return "iree-hal-serialize-executables";
-  }
-
-  StringRef getDescription() const override {
-    return "Serializes hal.executable.variant ops to hal.executable.binary ops";
-  }
-
+struct SerializeExecutablesPass
+    : public IREE::HAL::impl::SerializeExecutablesPassBase<
+          SerializeExecutablesPass> {
+  using IREE::HAL::impl::SerializeExecutablesPassBase<
+      SerializeExecutablesPass>::SerializeExecutablesPassBase;
   void runOnOperation() override {
     auto executableOp = getOperation();
     OpPassManager passManager(executableOp.getOperationName());
     for (const auto &targetName : gatherExecutableTargetNames(executableOp)) {
-      passManager.addPass(createSerializeTargetExecutablesPass(
-          targetRegistry, targetName, debugLevel, dumpIntermediatesPath,
-          dumpBinariesPath));
+      passManager.addPass(IREE::HAL::createSerializeTargetExecutablesPass(
+          {targetRegistry, targetName, debugLevel, dumpIntermediatesPath,
+           dumpBinariesPath}));
     }
+
+    IREE_COMPILER_TRACE_MESSAGE_DYNAMIC(INFO, executableOp.getSymName().str());
+
     if (failed(runPipeline(passManager, executableOp))) {
       executableOp.emitError() << "failed to serialize executables";
       return signalPassFailure();
     }
   }
-
-private:
-  const TargetBackendRegistry &targetRegistry;
-  int debugLevel;
-  std::string dumpIntermediatesPath;
-  std::string dumpBinariesPath;
 };
 
-std::unique_ptr<OperationPass<IREE::HAL::ExecutableOp>>
-createSerializeExecutablesPass(const TargetBackendRegistry &targetRegistry,
-                               int debugLevel,
-                               std::string dumpIntermediatesPath,
-                               std::string dumpBinariesPath) {
-  return std::make_unique<SerializeExecutablesPass>(
-      targetRegistry, debugLevel, dumpIntermediatesPath, dumpBinariesPath);
-}
-
-static PassRegistration<SerializeExecutablesPass> linkPass([] {
-  return std::make_unique<SerializeExecutablesPass>();
-});
+} // namespace
 
 } // namespace mlir::iree_compiler::IREE::HAL
diff --git a/compiler/src/iree/compiler/Dialect/HAL/Transforms/StripExecutableContents.cpp b/compiler/src/iree/compiler/Dialect/HAL/Transforms/StripExecutableContents.cpp
index a4d8600..bb52756 100644
--- a/compiler/src/iree/compiler/Dialect/HAL/Transforms/StripExecutableContents.cpp
+++ b/compiler/src/iree/compiler/Dialect/HAL/Transforms/StripExecutableContents.cpp
@@ -14,18 +14,18 @@
 
 namespace mlir::iree_compiler::IREE::HAL {
 
+#define GEN_PASS_DEF_STRIPEXECUTABLECONTENTSPASS
+#include "iree/compiler/Dialect/HAL/Transforms/Passes.h.inc"
+
+namespace {
+
+//===----------------------------------------------------------------------===//
+// --iree-hal-strip-executable-contents
+//===----------------------------------------------------------------------===//
+
 struct StripExecutableContentsPass
-    : public PassWrapper<StripExecutableContentsPass,
-                         OperationPass<mlir::ModuleOp>> {
-  StringRef getArgument() const override {
-    return "iree-hal-strip-executable-contents";
-  }
-
-  StringRef getDescription() const override {
-    return "Strips executable module contents for reducing IR size during "
-           "debugging.";
-  }
-
+    : public IREE::HAL::impl::StripExecutableContentsPassBase<
+          StripExecutableContentsPass> {
   void runOnOperation() override {
     for (auto executableOp : getOperation().getOps<IREE::HAL::ExecutableOp>()) {
       for (auto variantOp :
@@ -38,11 +38,6 @@
   }
 };
 
-std::unique_ptr<OperationPass<mlir::ModuleOp>>
-createStripExecutableContentsPass() {
-  return std::make_unique<StripExecutableContentsPass>();
-}
-
-static PassRegistration<StripExecutableContentsPass> pass;
+} // namespace
 
 } // namespace mlir::iree_compiler::IREE::HAL
diff --git a/compiler/src/iree/compiler/Dialect/HAL/Transforms/SubstituteExecutables.cpp b/compiler/src/iree/compiler/Dialect/HAL/Transforms/SubstituteExecutables.cpp
index 2e695a6..1623a27 100644
--- a/compiler/src/iree/compiler/Dialect/HAL/Transforms/SubstituteExecutables.cpp
+++ b/compiler/src/iree/compiler/Dialect/HAL/Transforms/SubstituteExecutables.cpp
@@ -18,6 +18,11 @@
 
 namespace mlir::iree_compiler::IREE::HAL {
 
+#define GEN_PASS_DEF_SUBSTITUTEEXECUTABLESPASS
+#include "iree/compiler/Dialect/HAL/Transforms/Passes.h.inc"
+
+namespace {
+
 // Scans |searchPath| for all child files and appends them to |substitutions|.
 // The file basename will be treated as an executable name and the path will be
 // absolute such that no object path resolution occurs later on.
@@ -200,31 +205,15 @@
   }
 }
 
-class SubstituteExecutablesPass
-    : public PassWrapper<SubstituteExecutablesPass, OperationPass<ModuleOp>> {
-public:
-  SubstituteExecutablesPass() = default;
-  SubstituteExecutablesPass(const SubstituteExecutablesPass &pass) {}
-  SubstituteExecutablesPass(ArrayRef<std::string> substitutions) {
-    this->substitutions = substitutions;
-  }
-  SubstituteExecutablesPass(std::string searchPath) {
-    this->searchPath = std::move(searchPath);
-  }
+//===----------------------------------------------------------------------===//
+// --iree-hal-substitute-executables
+//===----------------------------------------------------------------------===//
 
-  void getDependentDialects(DialectRegistry &registry) const override {
-    registry.insert<IREE::HAL::HALDialect>();
-  }
-
-  StringRef getArgument() const override {
-    return "iree-hal-substitute-executables";
-  }
-
-  StringRef getDescription() const override {
-    return "Substitutes hal.executable ops by parsing |substitutions| in "
-           "`executable_name=file.xxx` strings.";
-  }
-
+struct SubstituteExecutablesPass
+    : public IREE::HAL::impl::SubstituteExecutablesPassBase<
+          SubstituteExecutablesPass> {
+  using IREE::HAL::impl::SubstituteExecutablesPassBase<
+      SubstituteExecutablesPass>::SubstituteExecutablesPassBase;
   void runOnOperation() override {
     auto moduleOp = getOperation();
     auto moduleName = moduleOp.getName().value_or("module");
@@ -282,29 +271,8 @@
       }
     }
   }
-
-private:
-  ListOption<std::string> substitutions{
-      *this, "substitutions",
-      llvm::cl::desc(
-          "Substitution `executable_name=file.xxx` key-value pairs.")};
-  Option<std::string> searchPath{
-      *this, "search-path",
-      llvm::cl::desc("Path to source executable substitutions from.")};
 };
 
-std::unique_ptr<OperationPass<mlir::ModuleOp>>
-createSubstituteExecutablesPass(ArrayRef<std::string> substitutions) {
-  return std::make_unique<SubstituteExecutablesPass>(substitutions);
-}
-
-std::unique_ptr<OperationPass<mlir::ModuleOp>>
-createSubstituteExecutablesPass(std::string searchPath) {
-  return std::make_unique<SubstituteExecutablesPass>(std::move(searchPath));
-}
-
-static PassRegistration<SubstituteExecutablesPass> pass([] {
-  return std::make_unique<SubstituteExecutablesPass>();
-});
+} // namespace
 
 } // namespace mlir::iree_compiler::IREE::HAL
diff --git a/compiler/src/iree/compiler/Dialect/HAL/Transforms/TranslateExecutables.cpp b/compiler/src/iree/compiler/Dialect/HAL/Transforms/TranslateExecutables.cpp
index 060468d..a215884 100644
--- a/compiler/src/iree/compiler/Dialect/HAL/Transforms/TranslateExecutables.cpp
+++ b/compiler/src/iree/compiler/Dialect/HAL/Transforms/TranslateExecutables.cpp
@@ -11,6 +11,7 @@
 #include "iree/compiler/Dialect/HAL/IR/HALOps.h"
 #include "iree/compiler/Dialect/HAL/Target/TargetBackend.h"
 #include "iree/compiler/Dialect/HAL/Target/TargetRegistry.h"
+#include "iree/compiler/Dialect/HAL/Transforms/Passes.h"
 #include "iree/compiler/Utils/TracingUtils.h"
 #include "llvm/ADT/StringSet.h"
 #include "mlir/Dialect/Bufferization/IR/Bufferization.h"
@@ -22,33 +23,27 @@
 
 namespace mlir::iree_compiler::IREE::HAL {
 
-class TranslateTargetExecutableVariantsPass
-    : public PassWrapper<TranslateTargetExecutableVariantsPass,
-                         OperationPass<IREE::HAL::ExecutableVariantOp>> {
-public:
-  TranslateTargetExecutableVariantsPass()
-      : targetRegistry(TargetBackendRegistry::getGlobal()) {}
-  TranslateTargetExecutableVariantsPass(
-      const TranslateTargetExecutableVariantsPass &pass)
-      : targetRegistry(pass.targetRegistry) {}
-  TranslateTargetExecutableVariantsPass(
-      const TargetBackendRegistry &targetRegistry, StringRef target)
-      : targetRegistry(targetRegistry) {
-    this->target = target.str();
-  }
+#define GEN_PASS_DEF_TRANSLATEEXECUTABLESPASS
+#define GEN_PASS_DEF_TRANSLATETARGETEXECUTABLEVARIANTSPASS
+#include "iree/compiler/Dialect/HAL/Transforms/Passes.h.inc"
 
-  StringRef getArgument() const override {
-    return "iree-hal-translate-target-executable-variants";
-  }
+namespace {
 
-  StringRef getDescription() const override {
-    return "Serializes hal.executable.variant ops to hal.executable.binary ops";
-  }
+//===----------------------------------------------------------------------===//
+// --iree-hal-translate-target-executable-variants
+//===----------------------------------------------------------------------===//
+
+struct TranslateTargetExecutableVariantsPass
+    : public IREE::HAL::impl::TranslateTargetExecutableVariantsPassBase<
+          TranslateTargetExecutableVariantsPass> {
+  using IREE::HAL::impl::TranslateTargetExecutableVariantsPassBase<
+      TranslateTargetExecutableVariantsPass>::
+      TranslateTargetExecutableVariantsPassBase;
 
   void getDependentDialects(DialectRegistry &registry) const override {
     registry.insert<IREE::HAL::HALDialect>();
     registry.insert<bufferization::BufferizationDialect>();
-    auto targetBackend = targetRegistry.getTargetBackend(target);
+    auto targetBackend = targetRegistry->getTargetBackend(target);
     if (targetBackend) {
       targetBackend->getDependentDialects(registry);
     }
@@ -59,7 +54,7 @@
     if (variantOp.getTarget().getBackend().getValue() != target)
       return;
 
-    auto targetBackend = targetRegistry.getTargetBackend(target);
+    auto targetBackend = targetRegistry->getTargetBackend(target);
     if (!targetBackend) {
       variantOp.emitError() << "unregistered target backend '" << target << "'";
       return signalPassFailure();
@@ -74,51 +69,23 @@
       return signalPassFailure();
     }
   }
-
-private:
-  Option<std::string> target{
-      *this, "target",
-      llvm::cl::desc(
-          "Target backend name whose executables will be translated by "
-          "this pass.")};
-
-  const TargetBackendRegistry &targetRegistry;
 };
 
-std::unique_ptr<OperationPass<IREE::HAL::ExecutableVariantOp>>
-createTranslateTargetExecutableVariantsPass(
-    const TargetBackendRegistry &targetRegistry, StringRef target) {
-  return std::make_unique<TranslateTargetExecutableVariantsPass>(targetRegistry,
-                                                                 target);
-}
+//===----------------------------------------------------------------------===//
+// --iree-hal-translate-executables
+//===----------------------------------------------------------------------===//
 
-static PassRegistration<TranslateTargetExecutableVariantsPass> linkTargetPass(
-    [] { return std::make_unique<TranslateTargetExecutableVariantsPass>(); });
-
-class TranslateExecutablesPass
-    : public PassWrapper<TranslateExecutablesPass,
-                         OperationPass<IREE::HAL::ExecutableOp>> {
-public:
-  TranslateExecutablesPass()
-      : targetRegistry(TargetBackendRegistry::getGlobal()) {}
-  TranslateExecutablesPass(const TranslateExecutablesPass &pass)
-      : targetRegistry(pass.targetRegistry) {}
-  TranslateExecutablesPass(const TargetBackendRegistry &targetRegistry)
-      : targetRegistry(targetRegistry) {}
-
-  StringRef getArgument() const override {
-    return "iree-hal-translate-executables";
-  }
-
-  StringRef getDescription() const override {
-    return "Serializes hal.executable.variant ops to hal.executable.binary ops";
-  }
+struct TranslateExecutablesPass
+    : public IREE::HAL::impl::TranslateExecutablesPassBase<
+          TranslateExecutablesPass> {
+  using IREE::HAL::impl::TranslateExecutablesPassBase<
+      TranslateExecutablesPass>::TranslateExecutablesPassBase;
 
   void getDependentDialects(DialectRegistry &registry) const override {
     registry.insert<IREE::HAL::HALDialect>();
     registry.insert<bufferization::BufferizationDialect>();
-    auto targetBackends = targetRegistry.getTargetBackends(
-        targetRegistry.getRegisteredTargetBackends());
+    auto targetBackends = targetRegistry->getTargetBackends(
+        targetRegistry->getRegisteredTargetBackends());
     for (auto &targetBackend : targetBackends) {
       targetBackend->getDependentDialects(registry);
     }
@@ -129,28 +96,19 @@
     OpPassManager passManager(executableOp.getOperationName());
     for (const auto &targetName : gatherExecutableTargetNames(executableOp)) {
       passManager.addNestedPass<IREE::HAL::ExecutableVariantOp>(
-          createTranslateTargetExecutableVariantsPass(targetRegistry,
-                                                      targetName));
+          IREE::HAL::createTranslateTargetExecutableVariantsPass(
+              {targetRegistry, targetName}));
     }
 
     IREE_COMPILER_TRACE_MESSAGE_DYNAMIC(INFO, executableOp.getSymName().str());
 
     if (failed(runPipeline(passManager, executableOp))) {
-      executableOp.emitError() << "failed to serialize executables";
+      executableOp.emitError() << "failed to translate executables";
       return signalPassFailure();
     }
   }
-
-  const TargetBackendRegistry &targetRegistry;
 };
 
-std::unique_ptr<OperationPass<IREE::HAL::ExecutableOp>>
-createTranslateExecutablesPass(const TargetBackendRegistry &targetRegistry) {
-  return std::make_unique<TranslateExecutablesPass>(targetRegistry);
-}
-
-static PassRegistration<TranslateExecutablesPass> translatePass([] {
-  return std::make_unique<TranslateExecutablesPass>();
-});
+} // namespace
 
 } // namespace mlir::iree_compiler::IREE::HAL
diff --git a/compiler/src/iree/compiler/Dialect/HAL/Transforms/VerifyTargetEnvironment.cpp b/compiler/src/iree/compiler/Dialect/HAL/Transforms/VerifyTargetEnvironment.cpp
index 39f378b..64a003d 100644
--- a/compiler/src/iree/compiler/Dialect/HAL/Transforms/VerifyTargetEnvironment.cpp
+++ b/compiler/src/iree/compiler/Dialect/HAL/Transforms/VerifyTargetEnvironment.cpp
@@ -12,7 +12,6 @@
 #include "iree/compiler/Dialect/HAL/Target/TargetBackend.h"
 #include "iree/compiler/Dialect/HAL/Target/TargetRegistry.h"
 #include "iree/compiler/Dialect/HAL/Transforms/Passes.h"
-#include "mlir/Dialect/Func/IR/FuncOps.h"
 #include "mlir/IR/Attributes.h"
 #include "mlir/IR/Builders.h"
 #include "mlir/IR/BuiltinTypes.h"
@@ -21,24 +20,20 @@
 
 namespace mlir::iree_compiler::IREE::HAL {
 
-class VerifyTargetEnvironmentPass
-    : public PassWrapper<VerifyTargetEnvironmentPass, OperationPass<ModuleOp>> {
-public:
-  VerifyTargetEnvironmentPass(const TargetBackendRegistry &targetRegistry)
-      : targetRegistry(targetRegistry) {}
+#define GEN_PASS_DEF_VERIFYTARGETENVIRONMENTPASS
+#include "iree/compiler/Dialect/HAL/Transforms/Passes.h.inc"
 
-  void getDependentDialects(DialectRegistry &registry) const override {
-    registry.insert<IREE::HAL::HALDialect>();
-  }
+namespace {
 
-  StringRef getArgument() const override {
-    return "iree-hal-verify-target-environment";
-  }
+//===----------------------------------------------------------------------===//
+// --iree-hal-verify-target-environment
+//===----------------------------------------------------------------------===//
 
-  StringRef getDescription() const override {
-    return "Verifies that the target execution environment is valid.";
-  }
-
+struct VerifyTargetEnvironmentPass
+    : public IREE::HAL::impl::VerifyTargetEnvironmentPassBase<
+          VerifyTargetEnvironmentPass> {
+  using IREE::HAL::impl::VerifyTargetEnvironmentPassBase<
+      VerifyTargetEnvironmentPass>::VerifyTargetEnvironmentPassBase;
   void runOnOperation() override {
     auto moduleOp = getOperation();
 
@@ -64,7 +59,7 @@
       diagnostic
           << "no HAL target devices specified on the module (available = [ ";
       for (const auto &targetName :
-           targetRegistry.getRegisteredTargetBackends()) {
+           targetRegistry->getRegisteredTargetBackends()) {
         diagnostic << "'" << targetName << "' ";
       }
       diagnostic << "])";
@@ -82,14 +77,14 @@
       }
 
       auto targetBackend =
-          targetRegistry.getTargetBackend(targetAttr.getDeviceID().getValue());
+          targetRegistry->getTargetBackend(targetAttr.getDeviceID().getValue());
       if (!targetBackend) {
         auto diagnostic = moduleOp.emitError();
         diagnostic
             << "unregistered target backend " << targetAttr.getDeviceID()
             << "; ensure it is linked in to the compiler (available = [ ";
         for (const auto &targetName :
-             targetRegistry.getRegisteredTargetBackends()) {
+             targetRegistry->getRegisteredTargetBackends()) {
           diagnostic << "'" << targetName << "' ";
         }
         diagnostic << "])";
@@ -98,18 +93,8 @@
       }
     }
   }
-
-  const TargetBackendRegistry &targetRegistry;
 };
 
-std::unique_ptr<OperationPass<ModuleOp>>
-createVerifyTargetEnvironmentPass(const TargetBackendRegistry &targetRegistry) {
-  return std::make_unique<VerifyTargetEnvironmentPass>(targetRegistry);
-}
-
-static PassRegistration<VerifyTargetEnvironmentPass> pass([] {
-  return std::make_unique<VerifyTargetEnvironmentPass>(
-      TargetBackendRegistry::getGlobal());
-});
+} // namespace
 
 } // namespace mlir::iree_compiler::IREE::HAL
diff --git a/compiler/src/iree/compiler/Dialect/HAL/Transforms/test/BUILD.bazel b/compiler/src/iree/compiler/Dialect/HAL/Transforms/test/BUILD.bazel
index 778254c..3c930d0 100644
--- a/compiler/src/iree/compiler/Dialect/HAL/Transforms/test/BUILD.bazel
+++ b/compiler/src/iree/compiler/Dialect/HAL/Transforms/test/BUILD.bazel
@@ -17,7 +17,6 @@
     srcs = enforce_glob(
         [
             "assign_target_devices.mlir",
-            "benchmark_batch_dispatches.mlir",
             "convert_to_hal.mlir",
             "dump_executable_benchmarks.mlir",
             "dump_executable_sources.mlir",
@@ -28,6 +27,7 @@
             "materialize_resource_caches.mlir",
             "memoize_device_queries.mlir",
             "preprocess_executables.mlir",
+            "repeat_dispatches.mlir",
             "resolve_export_ordinals.mlir",
             "strip_executable_contents.mlir",
             "substitute_executables.mlir",
diff --git a/compiler/src/iree/compiler/Dialect/HAL/Transforms/test/CMakeLists.txt b/compiler/src/iree/compiler/Dialect/HAL/Transforms/test/CMakeLists.txt
index 2f5b0e9..b4be816 100644
--- a/compiler/src/iree/compiler/Dialect/HAL/Transforms/test/CMakeLists.txt
+++ b/compiler/src/iree/compiler/Dialect/HAL/Transforms/test/CMakeLists.txt
@@ -15,7 +15,6 @@
     lit
   SRCS
     "assign_target_devices.mlir"
-    "benchmark_batch_dispatches.mlir"
     "convert_to_hal.mlir"
     "dump_executable_benchmarks.mlir"
     "dump_executable_sources.mlir"
@@ -26,6 +25,7 @@
     "materialize_resource_caches.mlir"
     "memoize_device_queries.mlir"
     "preprocess_executables.mlir"
+    "repeat_dispatches.mlir"
     "resolve_export_ordinals.mlir"
     "strip_executable_contents.mlir"
     "substitute_executables.mlir"
diff --git a/compiler/src/iree/compiler/Dialect/HAL/Transforms/test/materialize_dispatch_instrumentation.mlir b/compiler/src/iree/compiler/Dialect/HAL/Transforms/test/materialize_dispatch_instrumentation.mlir
index 3ea7bf2..fcb1a75 100644
--- a/compiler/src/iree/compiler/Dialect/HAL/Transforms/test/materialize_dispatch_instrumentation.mlir
+++ b/compiler/src/iree/compiler/Dialect/HAL/Transforms/test/materialize_dispatch_instrumentation.mlir
@@ -1,4 +1,4 @@
-// RUN: iree-opt --split-input-file --pass-pipeline='builtin.module(iree-hal-materialize-dispatch-instrumentation{bufferSize=64mib})' %s | FileCheck %s
+// RUN: iree-opt --split-input-file --pass-pipeline='builtin.module(iree-hal-materialize-dispatch-instrumentation{buffer-size=64mib})' %s | FileCheck %s
 
 module attributes {hal.device.targets = [
   #hal.device.target<"llvm-cpu", {
diff --git a/compiler/src/iree/compiler/Dialect/HAL/Transforms/test/preprocess_executables.mlir b/compiler/src/iree/compiler/Dialect/HAL/Transforms/test/preprocess_executables.mlir
index 670a326..6742b24 100644
--- a/compiler/src/iree/compiler/Dialect/HAL/Transforms/test/preprocess_executables.mlir
+++ b/compiler/src/iree/compiler/Dialect/HAL/Transforms/test/preprocess_executables.mlir
@@ -1,9 +1,9 @@
 // RUN: iree-opt --split-input-file %s \
-// RUN:   --pass-pipeline="builtin.module(hal.executable(iree-hal-preprocess-executables{pipeline=\"builtin.module(iree-codegen-test-executable-preprocessing)\"}))" | \
+// RUN:   --pass-pipeline="builtin.module(hal.executable(iree-hal-preprocess-executables-with-pipeline{pipeline=\"builtin.module(iree-codegen-test-executable-preprocessing)\"}))" | \
 // RUN: FileCheck %s
 
 // RUN: iree-opt --split-input-file %s \
-// RUN:   --pass-pipeline="builtin.module(hal.executable(iree-hal-preprocess-executables{command=\"iree-opt --iree-codegen-test-executable-preprocessing\"}))" | \
+// RUN:   --pass-pipeline="builtin.module(hal.executable(iree-hal-preprocess-executables-with-tool{command=\"iree-opt --iree-codegen-test-executable-preprocessing\"}))" | \
 // RUN: FileCheck %s
 
 // Uses a test pass to simulate an external user pipeline or tool that
diff --git a/compiler/src/iree/compiler/Dialect/HAL/Transforms/test/benchmark_batch_dispatches.mlir b/compiler/src/iree/compiler/Dialect/HAL/Transforms/test/repeat_dispatches.mlir
similarity index 96%
rename from compiler/src/iree/compiler/Dialect/HAL/Transforms/test/benchmark_batch_dispatches.mlir
rename to compiler/src/iree/compiler/Dialect/HAL/Transforms/test/repeat_dispatches.mlir
index 54fe8b1..7e60dbc 100644
--- a/compiler/src/iree/compiler/Dialect/HAL/Transforms/test/benchmark_batch_dispatches.mlir
+++ b/compiler/src/iree/compiler/Dialect/HAL/Transforms/test/repeat_dispatches.mlir
@@ -1,4 +1,4 @@
-// RUN: iree-opt --split-input-file -test-iree-hal-benchmark-batch-dispatches-2-times %s | FileCheck %s
+// RUN: iree-opt --split-input-file --pass-pipeline='builtin.module(func.func(iree-hal-repeat-dispatches{count=2}))' %s | FileCheck %s
 
 util.global @_executable : !hal.executable
 
diff --git a/compiler/src/iree/compiler/Dialect/Stream/Transforms/BUILD.bazel b/compiler/src/iree/compiler/Dialect/Stream/Transforms/BUILD.bazel
index 1c96994..278e82e 100644
--- a/compiler/src/iree/compiler/Dialect/Stream/Transforms/BUILD.bazel
+++ b/compiler/src/iree/compiler/Dialect/Stream/Transforms/BUILD.bazel
@@ -30,6 +30,7 @@
         "PackConstants.cpp",
         "PackDispatchOperands.cpp",
         "Passes.cpp",
+        "Passes.h.inc",
         "PropagateTimepoints.cpp",
         "RefineUsage.cpp",
         "ScheduleAllocation.cpp",
@@ -41,12 +42,10 @@
     ],
     hdrs = [
         "Passes.h",
-        "Passes.h.inc",
     ],
     deps = [
         ":PassesIncGen",
         "//compiler/src/iree/compiler/Dialect/Flow/IR",
-        "//compiler/src/iree/compiler/Dialect/Flow/Transforms",
         "//compiler/src/iree/compiler/Dialect/HAL/IR",
         "//compiler/src/iree/compiler/Dialect/Stream/Analysis",
         "//compiler/src/iree/compiler/Dialect/Stream/Builtins",
diff --git a/compiler/src/iree/compiler/Dialect/Stream/Transforms/CMakeLists.txt b/compiler/src/iree/compiler/Dialect/Stream/Transforms/CMakeLists.txt
index c6c2d51..34ea128 100644
--- a/compiler/src/iree/compiler/Dialect/Stream/Transforms/CMakeLists.txt
+++ b/compiler/src/iree/compiler/Dialect/Stream/Transforms/CMakeLists.txt
@@ -15,7 +15,6 @@
     Transforms
   HDRS
     "Passes.h"
-    "Passes.h.inc"
   SRCS
     "AnnotateDispatchArguments.cpp"
     "ConvertToStream.cpp"
@@ -32,6 +31,7 @@
     "PackConstants.cpp"
     "PackDispatchOperands.cpp"
     "Passes.cpp"
+    "Passes.h.inc"
     "PropagateTimepoints.cpp"
     "RefineUsage.cpp"
     "ScheduleAllocation.cpp"
@@ -62,7 +62,6 @@
     MLIRTransforms
     MLIRVectorDialect
     iree::compiler::Dialect::Flow::IR
-    iree::compiler::Dialect::Flow::Transforms
     iree::compiler::Dialect::HAL::IR
     iree::compiler::Dialect::Stream::Analysis
     iree::compiler::Dialect::Stream::Builtins
diff --git a/compiler/src/iree/compiler/Dialect/Stream/Transforms/Passes.cpp b/compiler/src/iree/compiler/Dialect/Stream/Transforms/Passes.cpp
index a9a4856..aa1a2d6 100644
--- a/compiler/src/iree/compiler/Dialect/Stream/Transforms/Passes.cpp
+++ b/compiler/src/iree/compiler/Dialect/Stream/Transforms/Passes.cpp
@@ -8,7 +8,6 @@
 
 #include <memory>
 
-#include "iree/compiler/Dialect/Flow/Transforms/Passes.h"
 #include "iree/compiler/Dialect/Util/Transforms/Passes.h"
 #include "iree/compiler/Utils/PassUtils.h"
 #include "mlir/Conversion/SCFToControlFlow/SCFToControlFlow.h"
@@ -47,7 +46,7 @@
 }
 
 //===----------------------------------------------------------------------===//
-// -iree-stream-tensor-transformation-pipeline
+// --iree-stream-tensor-transformation-pipeline
 //===----------------------------------------------------------------------===//
 
 void buildStreamTensorPassPipeline(OpPassManager &passManager,
@@ -106,7 +105,7 @@
 }
 
 //===----------------------------------------------------------------------===//
-// -iree-stream-async-transformation-pipeline
+// --iree-stream-async-transformation-pipeline
 //===----------------------------------------------------------------------===//
 
 void buildStreamAsyncPassPipeline(OpPassManager &passManager,
@@ -181,7 +180,7 @@
 }
 
 //===----------------------------------------------------------------------===//
-// -iree-stream-cmd-transformation-pipeline
+// --iree-stream-cmd-transformation-pipeline
 //===----------------------------------------------------------------------===//
 
 void buildStreamCmdPassPipeline(OpPassManager &passManager,
@@ -220,7 +219,7 @@
 }
 
 //===----------------------------------------------------------------------===//
-// -iree-stream-optimization-pipeline
+// --iree-stream-optimization-pipeline
 //===----------------------------------------------------------------------===//
 
 void buildStreamOptimizationPassPipeline(
@@ -304,7 +303,7 @@
 }
 
 //===----------------------------------------------------------------------===//
-// -iree-stream-transformation-pipeline
+// --iree-stream-transformation-pipeline
 //===----------------------------------------------------------------------===//
 
 void buildStreamTransformPassPipeline(
@@ -350,7 +349,16 @@
 // Registration
 //===----------------------------------------------------------------------===//
 
-void registerStreamTransformPassPipelines() {
+namespace {
+#define GEN_PASS_REGISTRATION
+#include "iree/compiler/Dialect/Stream/Transforms/Passes.h.inc" // IWYU pragma: export
+} // namespace
+
+void registerStreamPasses() {
+  // Generated.
+  registerPasses();
+
+  // Pipelines.
   PassPipelineRegistration<TransformOptions> tensorPassPipeline(
       "iree-stream-tensor-transformation-pipeline",
       "Lowers source dialects into stream.tensor.* IR.",
@@ -384,17 +392,4 @@
       });
 }
 
-namespace {
-#define GEN_PASS_REGISTRATION
-#include "iree/compiler/Dialect/Stream/Transforms/Passes.h.inc" // IWYU pragma: export
-} // namespace
-
-void registerStreamPasses() {
-  // Generated.
-  registerPasses();
-
-  // Pipelines.
-  registerStreamTransformPassPipelines();
-}
-
 } // namespace mlir::iree_compiler::IREE::Stream
diff --git a/compiler/src/iree/compiler/Dialect/Vulkan/Utils/test/multiple_target_env_conversion.mlir b/compiler/src/iree/compiler/Dialect/Vulkan/Utils/test/multiple_target_env_conversion.mlir
index 7c6cb29..e2e3e87 100644
--- a/compiler/src/iree/compiler/Dialect/Vulkan/Utils/test/multiple_target_env_conversion.mlir
+++ b/compiler/src/iree/compiler/Dialect/Vulkan/Utils/test/multiple_target_env_conversion.mlir
@@ -1,5 +1,4 @@
-// RUN: iree-opt --pass-pipeline='builtin.module(iree-hal-transformation-pipeline{serialize-executables=false})' \
-// RUN:   --iree-hal-target-backends=vulkan-spirv \
+// RUN: iree-opt --pass-pipeline='builtin.module(iree-hal-assign-target-devices{targets=vulkan-spirv},iree-hal-transformation-pipeline{serialize-executables=false})' \
 // RUN:   --iree-vulkan-target-triple=rdna3-7900xtx-windows \
 // RUN:   --iree-vulkan-target-env="#vk.target_env<v1.1, r(120), [VK_KHR_spirv_1_4, VK_KHR_storage_buffer_storage_class], AMD:DiscreteGPU, #vk.caps<maxComputeSharedMemorySize = 16384, maxComputeWorkGroupInvocations = 1024, maxComputeWorkGroupSize = dense<[128, 8, 4]>: vector<3xi32>, subgroupFeatures = 63 : i32, subgroupSize = 4 >>" \
 // RUN:   --iree-vulkan-target-triple=valhall-unknown-android31 \
diff --git a/compiler/src/iree/compiler/Dialect/Vulkan/Utils/test/target_env_conversion.mlir b/compiler/src/iree/compiler/Dialect/Vulkan/Utils/test/target_env_conversion.mlir
index ae43012..62d531b 100644
--- a/compiler/src/iree/compiler/Dialect/Vulkan/Utils/test/target_env_conversion.mlir
+++ b/compiler/src/iree/compiler/Dialect/Vulkan/Utils/test/target_env_conversion.mlir
@@ -1,12 +1,12 @@
-// RUN: iree-opt --pass-pipeline='builtin.module(iree-hal-transformation-pipeline{serialize-executables=false})' --iree-hal-target-backends=vulkan-spirv %s | FileCheck %s --check-prefix=DEFAULT
-// RUN: iree-opt --pass-pipeline='builtin.module(iree-hal-transformation-pipeline{serialize-executables=false})' --iree-hal-target-backends=vulkan-spirv --iree-vulkan-target-triple=adreno-a650-android30 %s | FileCheck %s --check-prefix=ADRENO
-// RUN: iree-opt --pass-pipeline='builtin.module(iree-hal-transformation-pipeline{serialize-executables=false})' --iree-hal-target-backends=vulkan-spirv --iree-vulkan-target-triple=valhall-unknown-android31 %s | FileCheck %s --check-prefix=VALHALL
-// RUN: iree-opt --pass-pipeline='builtin.module(iree-hal-transformation-pipeline{serialize-executables=false})' --iree-hal-target-backends=vulkan-spirv --iree-vulkan-target-triple=turing-t4-linux %s | FileCheck %s --check-prefix=TURING
-// RUN: iree-opt --pass-pipeline='builtin.module(iree-hal-transformation-pipeline{serialize-executables=false})' --iree-hal-target-backends=vulkan-spirv --iree-vulkan-target-triple=rdna1-5700xt-windows %s | FileCheck %s --check-prefix=RDNA1
-// RUN: iree-opt --pass-pipeline='builtin.module(iree-hal-transformation-pipeline{serialize-executables=false})' --iree-hal-target-backends=vulkan-spirv --iree-vulkan-target-triple=rdna3-6900xtx-windows %s | FileCheck %s --check-prefix=RDNA3
-// RUN: iree-opt --pass-pipeline='builtin.module(iree-hal-transformation-pipeline{serialize-executables=false})' --iree-hal-target-backends=vulkan-spirv --iree-vulkan-target-triple=m1-moltenvk-macos %s | FileCheck %s --check-prefix=M1
-// RUN: iree-opt --pass-pipeline='builtin.module(iree-hal-transformation-pipeline{serialize-executables=false})' --iree-hal-target-backends=vulkan-spirv --iree-vulkan-target-triple=arc-770-windows %s | FileCheck %s --check-prefix=ARC
-// RUN: iree-opt --pass-pipeline='builtin.module(iree-hal-transformation-pipeline{serialize-executables=false})' --iree-hal-target-backends=vulkan-spirv --iree-vulkan-target-triple=pascal-1080-windows %s | FileCheck %s --check-prefix=PASCAL
+// RUN: iree-opt --pass-pipeline='builtin.module(iree-hal-assign-target-devices{targets=vulkan-spirv},iree-hal-transformation-pipeline{serialize-executables=false})' %s | FileCheck %s --check-prefix=DEFAULT
+// RUN: iree-opt --pass-pipeline='builtin.module(iree-hal-assign-target-devices{targets=vulkan-spirv},iree-hal-transformation-pipeline{serialize-executables=false})' --iree-vulkan-target-triple=adreno-a650-android30 %s | FileCheck %s --check-prefix=ADRENO
+// RUN: iree-opt --pass-pipeline='builtin.module(iree-hal-assign-target-devices{targets=vulkan-spirv},iree-hal-transformation-pipeline{serialize-executables=false})' --iree-vulkan-target-triple=valhall-unknown-android31 %s | FileCheck %s --check-prefix=VALHALL
+// RUN: iree-opt --pass-pipeline='builtin.module(iree-hal-assign-target-devices{targets=vulkan-spirv},iree-hal-transformation-pipeline{serialize-executables=false})' --iree-vulkan-target-triple=turing-t4-linux %s | FileCheck %s --check-prefix=TURING
+// RUN: iree-opt --pass-pipeline='builtin.module(iree-hal-assign-target-devices{targets=vulkan-spirv},iree-hal-transformation-pipeline{serialize-executables=false})' --iree-vulkan-target-triple=rdna1-5700xt-windows %s | FileCheck %s --check-prefix=RDNA1
+// RUN: iree-opt --pass-pipeline='builtin.module(iree-hal-assign-target-devices{targets=vulkan-spirv},iree-hal-transformation-pipeline{serialize-executables=false})' --iree-vulkan-target-triple=rdna3-6900xtx-windows %s | FileCheck %s --check-prefix=RDNA3
+// RUN: iree-opt --pass-pipeline='builtin.module(iree-hal-assign-target-devices{targets=vulkan-spirv},iree-hal-transformation-pipeline{serialize-executables=false})' --iree-vulkan-target-triple=m1-moltenvk-macos %s | FileCheck %s --check-prefix=M1
+// RUN: iree-opt --pass-pipeline='builtin.module(iree-hal-assign-target-devices{targets=vulkan-spirv},iree-hal-transformation-pipeline{serialize-executables=false})' --iree-vulkan-target-triple=arc-770-windows %s | FileCheck %s --check-prefix=ARC
+// RUN: iree-opt --pass-pipeline='builtin.module(iree-hal-assign-target-devices{targets=vulkan-spirv},iree-hal-transformation-pipeline{serialize-executables=false})' --iree-vulkan-target-triple=pascal-1080-windows %s | FileCheck %s --check-prefix=PASCAL
 
 // TODO(antiagainst): Passing in lenghty strings as command-line options is not
 // optimal. We should consider creating a dedicated test pass to pick up
diff --git a/compiler/src/iree/compiler/Modules/HAL/Inline/Transforms/Passes.cpp b/compiler/src/iree/compiler/Modules/HAL/Inline/Transforms/Passes.cpp
index 0ad371a..7c02c39 100644
--- a/compiler/src/iree/compiler/Modules/HAL/Inline/Transforms/Passes.cpp
+++ b/compiler/src/iree/compiler/Modules/HAL/Inline/Transforms/Passes.cpp
@@ -61,9 +61,9 @@
 
   // Translate each executable down to common MLIR dialects.
   passManager.addNestedPass<IREE::HAL::ExecutableOp>(
-      IREE::HAL::createConfigureExecutablesPass(targetRegistry));
+      IREE::HAL::createConfigureExecutablesPass({targetRegistry}));
   passManager.addNestedPass<IREE::HAL::ExecutableOp>(
-      IREE::HAL::createTranslateExecutablesPass(targetRegistry));
+      IREE::HAL::createTranslateExecutablesPass({targetRegistry}));
 
   // Inline the translated executable functions.
   // We preserve the executables for their metadata used during conversion.
diff --git a/compiler/src/iree/compiler/Modules/HAL/Loader/Transforms/Passes.cpp b/compiler/src/iree/compiler/Modules/HAL/Loader/Transforms/Passes.cpp
index b675e1d..9b58d42 100644
--- a/compiler/src/iree/compiler/Modules/HAL/Loader/Transforms/Passes.cpp
+++ b/compiler/src/iree/compiler/Modules/HAL/Loader/Transforms/Passes.cpp
@@ -66,9 +66,9 @@
   // After this point the executables are opaque blobs and we cannot change
   // their interfaces.
   passManager.addNestedPass<IREE::HAL::ExecutableOp>(
-      IREE::HAL::createConfigureExecutablesPass(targetRegistry));
+      IREE::HAL::createConfigureExecutablesPass({targetRegistry}));
   passManager.addNestedPass<IREE::HAL::ExecutableOp>(
-      IREE::HAL::createTranslateExecutablesPass(targetRegistry));
+      IREE::HAL::createTranslateExecutablesPass({targetRegistry}));
 
   //----------------------------------------------------------------------------
   // Conversion
@@ -82,7 +82,7 @@
   //----------------------------------------------------------------------------
 
   // Link executables together.
-  passManager.addPass(IREE::HAL::createLinkExecutablesPass(targetRegistry));
+  passManager.addPass(IREE::HAL::createLinkExecutablesPass({targetRegistry}));
 
   // Resolve export ordinals from nested symbol references prior to
   // serialization.
@@ -91,9 +91,9 @@
   // Serialize executables to their binary forms.
   passManager.addNestedPass<IREE::HAL::ExecutableOp>(
       IREE::HAL::createSerializeExecutablesPass(
-          targetRegistry, targetOptions.debugLevel,
-          targetOptions.executableIntermediatesPath,
-          targetOptions.executableBinariesPath));
+          {&targetRegistry, targetOptions.debugLevel,
+           targetOptions.executableIntermediatesPath,
+           targetOptions.executableBinariesPath}));
 
   // NOTE: symbol DCE will destroy executable target contents.
   passManager.addPass(mlir::createSymbolDCEPass());
diff --git a/compiler/src/iree/compiler/Pipelines/Pipelines.cpp b/compiler/src/iree/compiler/Pipelines/Pipelines.cpp
index 40a5499..f8ebd27 100644
--- a/compiler/src/iree/compiler/Pipelines/Pipelines.cpp
+++ b/compiler/src/iree/compiler/Pipelines/Pipelines.cpp
@@ -37,7 +37,7 @@
   // specifying targets.
   if (!executableOptions.targets.empty()) {
     passManager.addPass(IREE::HAL::createAssignTargetDevicesPass(
-        targetRegistry, executableOptions.targets));
+        {&targetRegistry, executableOptions.targets}));
   }
 
   // Input pipelines can result in changes to the exported functions and types
diff --git a/compiler/src/iree/compiler/Utils/OptionUtils.cpp b/compiler/src/iree/compiler/Utils/OptionUtils.cpp
index 015bf26..e8d9491 100644
--- a/compiler/src/iree/compiler/Utils/OptionUtils.cpp
+++ b/compiler/src/iree/compiler/Utils/OptionUtils.cpp
@@ -122,12 +122,10 @@
   return size * scale;
 }
 
-namespace llvm {
-namespace cl {
+namespace llvm::cl {
 template class basic_parser<ByteSize>;
 template class basic_parser<PowerOf2ByteSize>;
-} // namespace cl
-} // namespace llvm
+} // namespace llvm::cl
 
 using ByteSize = llvm::cl::ByteSize;
 using PowerOf2ByteSize = llvm::cl::PowerOf2ByteSize;
diff --git a/compiler/src/iree/compiler/Utils/OptionUtils.h b/compiler/src/iree/compiler/Utils/OptionUtils.h
index f04f8c4..c2b5812 100644
--- a/compiler/src/iree/compiler/Utils/OptionUtils.h
+++ b/compiler/src/iree/compiler/Utils/OptionUtils.h
@@ -236,8 +236,7 @@
 
 } // namespace mlir::iree_compiler
 
-namespace llvm {
-namespace cl {
+namespace llvm::cl {
 
 struct ByteSize {
   int64_t value = 0;
@@ -276,7 +275,6 @@
   void anchor() override;
 };
 
-} // namespace cl
-} // namespace llvm
+} // namespace llvm::cl
 
 #endif // IREE_COMPILER_UTILS_FLAG_UTILS_H
diff --git a/tests/e2e/regression/libm_linking.mlir b/tests/e2e/regression/libm_linking.mlir
index d0b4afe..543ac86 100644
--- a/tests/e2e/regression/libm_linking.mlir
+++ b/tests/e2e/regression/libm_linking.mlir
@@ -1,5 +1,5 @@
-// RUN: iree-opt --split-input-file --iree-transformation-pipeline --iree-hal-target-backends=llvm-cpu %s | FileCheck %s
-// RUN: iree-opt --split-input-file --iree-transformation-pipeline --iree-hal-target-backends=llvm-cpu --iree-llvmcpu-link-embedded=false %s | FileCheck %s
+// RUN: iree-opt --split-input-file --pass-pipeline='builtin.module(iree-hal-assign-target-devices{targets=llvm-cpu},iree-transformation-pipeline)' %s | FileCheck %s
+// RUN: iree-opt --split-input-file --pass-pipeline='builtin.module(iree-hal-assign-target-devices{targets=llvm-cpu},iree-transformation-pipeline)' --iree-llvmcpu-link-embedded=false %s | FileCheck %s
 
 // When lowering to CPU code through LLVM, certain LLVM intrinsics require
 // linking against libm (the standard C library of math functions, `-lm`).
diff --git a/tools/test/executable_configurations.mlir b/tools/test/executable_configurations.mlir
index 67ca926..98554fc 100644
--- a/tools/test/executable_configurations.mlir
+++ b/tools/test/executable_configurations.mlir
@@ -33,12 +33,12 @@
 // for individual executables one or more `executable_name=file.mlir` pairs can
 // be repeated in `--iree-hal-substitute-executable-configuration=`.
 
-func.func @abs(%input : tensor<f32>) -> (tensor<f32>) {
+func.func @abs(%input : tensor<f32>) -> tensor<f32> {
   %result = math.absf %input : tensor<f32>
   return %result : tensor<f32>
 }
 
-// CHECK: IR Dump Before mlir::iree_compiler::IREE::HAL::SerializeExecutablesPass
+// CHECK: IR Dump Before SerializeExecutablesPass
 // CHECK: hal.executable public @abs_dispatch_0
 // CHECK:   hal.executable.variant public @vmvx_bytecode_fb
 // CHECK:     vm.func private @abs_dispatch_0_generic
diff --git a/tools/test/executable_sources.mlir b/tools/test/executable_sources.mlir
index 92df2e9..30e305e 100644
--- a/tools/test/executable_sources.mlir
+++ b/tools/test/executable_sources.mlir
@@ -36,7 +36,7 @@
   return %result : tensor<f32>
 }
 
-// CHECK: IR Dump Before mlir::iree_compiler::IREE::HAL::SerializeExecutablesPass
+// CHECK: IR Dump Before SerializeExecutablesPass
 // CHECK: hal.executable public @abs_dispatch_0
 // CHECK:   hal.executable.variant public @vmvx_bytecode_fb
 // CHECK:     vm.func private @abs_dispatch_0_generic