Align Python/C compiler API with command line usage. (#7995)

* Align Python/C compiler API with command line usage.

* Normalizes flag/option handling for both to share the command line flag syntax, removing the partial existing support for individual options in the C/Python API.
* Rename iree.compiler.api.driver -> iree.compiler.transforms.ireec to better layer with upstream and represent that this is the programmatic analog to the ireec CL tool.
* Also adds CMake option to allow disabling `iree.jax` legacy API as it is getting in the way of its out of tree replacement and didn't belong in tree like this anyway.

Rework flag handling so it can be used from libraries as well.
diff --git a/CMakeLists.txt b/CMakeLists.txt
index ac03a37..3e67822 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -42,6 +42,7 @@
 option(IREE_BUILD_SAMPLES "Builds IREE sample projects." ON)
 option(IREE_BUILD_TRACY "Builds tracy server tools." OFF)
 
+option(IREE_BUILD_LEGACY_JAX "Builds the legacy JAX Python API" ON)
 option(IREE_BUILD_TENSORFLOW_ALL "Builds all TensorFlow compiler frontends." OFF)
 option(IREE_BUILD_TENSORFLOW_COMPILER "Builds TensorFlow compiler frontend." "${IREE_BUILD_TENSORFLOW_ALL}")
 option(IREE_BUILD_TFLITE_COMPILER "Builds the TFLite compiler frontend." "${IREE_BUILD_TENSORFLOW_ALL}")
diff --git a/bindings/python/CMakeLists.txt b/bindings/python/CMakeLists.txt
index f019d28..77bf1ce 100644
--- a/bindings/python/CMakeLists.txt
+++ b/bindings/python/CMakeLists.txt
@@ -11,4 +11,8 @@
 
 # Namespace packages.
 add_subdirectory(iree/runtime)
-add_subdirectory(iree/jax)
+
+if(IREE_BUILD_LEGACY_JAX)
+  message(STATUS "Building legacy JAX API")
+  add_subdirectory(iree/jax)
+endif()
diff --git a/iree/compiler/ConstEval/JitGlobals.cpp b/iree/compiler/ConstEval/JitGlobals.cpp
index 08c8998..21947c9 100644
--- a/iree/compiler/ConstEval/JitGlobals.cpp
+++ b/iree/compiler/ConstEval/JitGlobals.cpp
@@ -13,7 +13,7 @@
 #include "iree/compiler/Dialect/Stream/Transforms/Passes.h"
 #include "iree/compiler/Dialect/Util/IR/UtilOps.h"
 #include "iree/compiler/Dialect/Util/Transforms/Passes.h"
-#include "iree/compiler/Dialect/VM/Target/Bytecode/TranslationFlags.h"
+#include "iree/compiler/Dialect/VM/Target/Bytecode/BytecodeModuleTarget.h"
 #include "iree/compiler/Dialect/VM/Transforms/Passes.h"
 #include "iree/compiler/Utils/PassUtils.h"
 #include "llvm/ADT/DenseSet.h"
diff --git a/iree/compiler/Dialect/HAL/Conversion/HALToVM/ConvertHALToVM.cpp b/iree/compiler/Dialect/HAL/Conversion/HALToVM/ConvertHALToVM.cpp
index d37e1cf..3f406a7 100644
--- a/iree/compiler/Dialect/HAL/Conversion/HALToVM/ConvertHALToVM.cpp
+++ b/iree/compiler/Dialect/HAL/Conversion/HALToVM/ConvertHALToVM.cpp
@@ -138,7 +138,7 @@
 }
 
 static PassRegistration<ConvertHALToVMPass> pass([] {
-  auto options = IREE::VM::getTargetOptionsFromFlags();
+  auto options = IREE::VM::TargetOptions::FromFlags::get();
   return std::make_unique<ConvertHALToVMPass>(options);
 });
 
diff --git a/iree/compiler/Dialect/HAL/Conversion/Passes.h b/iree/compiler/Dialect/HAL/Conversion/Passes.h
index 8052a7f..92734e2 100644
--- a/iree/compiler/Dialect/HAL/Conversion/Passes.h
+++ b/iree/compiler/Dialect/HAL/Conversion/Passes.h
@@ -17,7 +17,7 @@
     IREE::VM::TargetOptions targetOptions);
 
 inline void registerHALConversionPasses() {
-  createConvertHALToVMPass(IREE::VM::getTargetOptionsFromFlags());
+  createConvertHALToVMPass(IREE::VM::TargetOptions::FromFlags::get());
 }
 
 }  // namespace iree_compiler
diff --git a/iree/compiler/Dialect/HAL/Target/BUILD b/iree/compiler/Dialect/HAL/Target/BUILD
index f804469..3cae4a0 100644
--- a/iree/compiler/Dialect/HAL/Target/BUILD
+++ b/iree/compiler/Dialect/HAL/Target/BUILD
@@ -25,6 +25,7 @@
         "//iree/compiler/Dialect/HAL/IR",
         "//iree/compiler/Dialect/HAL/Utils",
         "//iree/compiler/Dialect/Util/IR",
+        "//iree/compiler/Utils",
         "@llvm-project//llvm:Support",
         "@llvm-project//mlir:IR",
         "@llvm-project//mlir:Pass",
diff --git a/iree/compiler/Dialect/HAL/Target/CMakeLists.txt b/iree/compiler/Dialect/HAL/Target/CMakeLists.txt
index 2227647..658b678 100644
--- a/iree/compiler/Dialect/HAL/Target/CMakeLists.txt
+++ b/iree/compiler/Dialect/HAL/Target/CMakeLists.txt
@@ -29,6 +29,7 @@
     iree::compiler::Dialect::HAL::IR
     iree::compiler::Dialect::HAL::Utils
     iree::compiler::Dialect::Util::IR
+    iree::compiler::Utils
   PUBLIC
 )
 
diff --git a/iree/compiler/Dialect/HAL/Target/TargetBackend.cpp b/iree/compiler/Dialect/HAL/Target/TargetBackend.cpp
index 7774efc..cc7f5c8 100644
--- a/iree/compiler/Dialect/HAL/Target/TargetBackend.cpp
+++ b/iree/compiler/Dialect/HAL/Target/TargetBackend.cpp
@@ -17,7 +17,7 @@
 namespace IREE {
 namespace HAL {
 
-TargetOptions getTargetOptionsFromFlags() {
+void TargetOptions::bindOptions(OptionsBinder &binder) {
   static llvm::cl::OptionCategory halTargetOptionsCategory(
       "IREE HAL executable target options");
 
@@ -25,15 +25,10 @@
   // TranslateExecutablesPass. Pass registery is also staticly
   // initialized, so targetBackendsFlags needs to be here to be initialized
   // first.
-  static llvm::cl::list<std::string> *targetBackendsFlag =
-      new llvm::cl::list<std::string>{
-          "iree-hal-target-backends",
-          llvm::cl::desc("Target backends for executable compilation"),
-          llvm::cl::ZeroOrMore, llvm::cl::cat(halTargetOptionsCategory)};
-
-  TargetOptions targetOptions;
-  targetOptions.targets = *targetBackendsFlag;
-  return targetOptions;
+  binder.list<std::string>(
+      "iree-hal-target-backends", targets,
+      llvm::cl::desc("Target backends for executable compilation"),
+      llvm::cl::ZeroOrMore, llvm::cl::cat(halTargetOptionsCategory));
 }
 
 // Renames |op| within |moduleOp| with a new name that is unique within both
diff --git a/iree/compiler/Dialect/HAL/Target/TargetBackend.h b/iree/compiler/Dialect/HAL/Target/TargetBackend.h
index 6caf416..5e17d0e 100644
--- a/iree/compiler/Dialect/HAL/Target/TargetBackend.h
+++ b/iree/compiler/Dialect/HAL/Target/TargetBackend.h
@@ -14,6 +14,7 @@
 #include "iree/compiler/Dialect/Flow/IR/FlowOps.h"
 #include "iree/compiler/Dialect/HAL/IR/HALOps.h"
 #include "iree/compiler/Dialect/HAL/Utils/DeviceSwitchBuilder.h"
+#include "iree/compiler/Utils/OptionUtils.h"
 #include "llvm/ADT/StringMap.h"
 #include "llvm/ADT/StringRef.h"
 #include "mlir/IR/Dialect.h"
@@ -35,12 +36,10 @@
   // the best we can do is a coarse flag as to whether source maps should be
   // embedded, however we could be much better here on the TargetBackend
   // interface.
+  void bindOptions(OptionsBinder &binder);
+  using FromFlags = OptionsFromFlags<TargetOptions>;
 };
 
-// Returns a TargetOptions struct initialized with the
-// --iree-hal-target-* flags.
-TargetOptions getTargetOptionsFromFlags();
-
 // HAL executable target backend interface.
 // Multiple backends can be registered and targeted during a single compilation.
 // The flow->hal conversion process will use registered TargetBackend interfaces
diff --git a/iree/compiler/Dialect/HAL/Target/VMVX/VMVXTarget.cpp b/iree/compiler/Dialect/HAL/Target/VMVX/VMVXTarget.cpp
index 8b3fa52..5d99ca2 100644
--- a/iree/compiler/Dialect/HAL/Target/VMVX/VMVXTarget.cpp
+++ b/iree/compiler/Dialect/HAL/Target/VMVX/VMVXTarget.cpp
@@ -59,7 +59,7 @@
     OpPassManager &nestedModulePM = passManager.nest<ModuleOp>();
 
     // TODO(benvanik): derive these from a vm target triple.
-    auto vmOptions = IREE::VM::getTargetOptionsFromFlags();
+    auto vmOptions = IREE::VM::TargetOptions::FromFlags::get();
     vmOptions.f32Extension = true;
     vmOptions.optimizeForStackSize = false;
     IREE::VM::buildVMTransformPassPipeline(nestedModulePM, vmOptions);
diff --git a/iree/compiler/Dialect/HAL/Transforms/MaterializeResourceCaches.cpp b/iree/compiler/Dialect/HAL/Transforms/MaterializeResourceCaches.cpp
index 822e7fa..7ed1636 100644
--- a/iree/compiler/Dialect/HAL/Transforms/MaterializeResourceCaches.cpp
+++ b/iree/compiler/Dialect/HAL/Transforms/MaterializeResourceCaches.cpp
@@ -294,7 +294,7 @@
 }
 
 static PassRegistration<MaterializeResourceCachesPass> pass([] {
-  auto options = getTargetOptionsFromFlags();
+  auto options = TargetOptions::FromFlags::get();
   return std::make_unique<MaterializeResourceCachesPass>(options);
 });
 
diff --git a/iree/compiler/Dialect/HAL/Transforms/Passes.cpp b/iree/compiler/Dialect/HAL/Transforms/Passes.cpp
index 0d77197..52e4515 100644
--- a/iree/compiler/Dialect/HAL/Transforms/Passes.cpp
+++ b/iree/compiler/Dialect/HAL/Transforms/Passes.cpp
@@ -210,8 +210,8 @@
       "iree-hal-transformation-pipeline",
       "Runs the full IREE HAL dialect transformation pipeline",
       [](OpPassManager &passManager, const TransformOptions &transformOptions) {
-        buildHALTransformPassPipeline(passManager, getTargetOptionsFromFlags(),
-                                      transformOptions);
+        buildHALTransformPassPipeline(
+            passManager, TargetOptions::FromFlags::get(), transformOptions);
       });
 }
 
diff --git a/iree/compiler/Dialect/HAL/Transforms/Passes.h b/iree/compiler/Dialect/HAL/Transforms/Passes.h
index 1b9eafd..8ab5e34 100644
--- a/iree/compiler/Dialect/HAL/Transforms/Passes.h
+++ b/iree/compiler/Dialect/HAL/Transforms/Passes.h
@@ -134,7 +134,7 @@
 
 inline void registerHALPasses() {
   registerHALTransformPassPipeline();
-  auto targetOptions = getTargetOptionsFromFlags();
+  auto targetOptions = TargetOptions::FromFlags::get();
   createAssignTargetDevicesPass({});
   createBenchmarkBatchDispatchesPass(/*repeatCount=*/1);
   createConvertToHALPass();
diff --git a/iree/compiler/Dialect/VM/Conversion/BUILD b/iree/compiler/Dialect/VM/Conversion/BUILD
index 4b90b33..6d49265 100644
--- a/iree/compiler/Dialect/VM/Conversion/BUILD
+++ b/iree/compiler/Dialect/VM/Conversion/BUILD
@@ -28,6 +28,7 @@
     deps = [
         "//iree/compiler/Dialect/Util/IR",
         "//iree/compiler/Dialect/VM/IR",
+        "//iree/compiler/Utils",
         "@llvm-project//llvm:Support",
         "@llvm-project//mlir:IR",
         "@llvm-project//mlir:Parser",
diff --git a/iree/compiler/Dialect/VM/Conversion/CMakeLists.txt b/iree/compiler/Dialect/VM/Conversion/CMakeLists.txt
index e0f872a..3cb1337 100644
--- a/iree/compiler/Dialect/VM/Conversion/CMakeLists.txt
+++ b/iree/compiler/Dialect/VM/Conversion/CMakeLists.txt
@@ -32,6 +32,7 @@
     MLIRTransforms
     iree::compiler::Dialect::Util::IR
     iree::compiler::Dialect::VM::IR
+    iree::compiler::Utils
   PUBLIC
 )
 
diff --git a/iree/compiler/Dialect/VM/Conversion/StandardToVM/ConvertStandardToVMTest.cpp b/iree/compiler/Dialect/VM/Conversion/StandardToVM/ConvertStandardToVMTest.cpp
index c3ee86c..dc755da 100644
--- a/iree/compiler/Dialect/VM/Conversion/StandardToVM/ConvertStandardToVMTest.cpp
+++ b/iree/compiler/Dialect/VM/Conversion/StandardToVM/ConvertStandardToVMTest.cpp
@@ -40,7 +40,7 @@
                              mlir::arith::ArithmeticDialect>();
 
     IREE::VM::TypeConverter typeConverter(
-        IREE::VM::getTargetOptionsFromFlags());
+        IREE::VM::TargetOptions::FromFlags::get());
 
     OwningRewritePatternList patterns(&getContext());
     populateStandardToVMPatterns(&getContext(), typeConverter, patterns);
diff --git a/iree/compiler/Dialect/VM/Conversion/TargetOptions.cpp b/iree/compiler/Dialect/VM/Conversion/TargetOptions.cpp
index 2f74c57..a27e3cd 100644
--- a/iree/compiler/Dialect/VM/Conversion/TargetOptions.cpp
+++ b/iree/compiler/Dialect/VM/Conversion/TargetOptions.cpp
@@ -13,61 +13,35 @@
 namespace IREE {
 namespace VM {
 
-TargetOptions getTargetOptionsFromFlags() {
+void TargetOptions::bindOptions(OptionsBinder &binder) {
   static llvm::cl::OptionCategory vmTargetOptionsCategory(
       "IREE VM target options");
 
-  static auto *indexBitsFlag = new llvm::cl::opt<int>{
-      "iree-vm-target-index-bits",
-      llvm::cl::init(32),
-      llvm::cl::desc("Bit width of index types."),
-      llvm::cl::cat(vmTargetOptionsCategory),
-  };
-  static auto *i64ExtensionFlag = new llvm::cl::opt<bool>{
-      "iree-vm-target-extension-i64",
-      llvm::cl::init(false),
-      llvm::cl::desc("Support i64 target opcode extensions."),
-      llvm::cl::cat(vmTargetOptionsCategory),
-  };
-  static auto *f32ExtensionFlag = new llvm::cl::opt<bool>{
-      "iree-vm-target-extension-f32",
-      llvm::cl::init(true),
-      llvm::cl::desc("Support f32 target opcode extensions."),
-      llvm::cl::cat(vmTargetOptionsCategory),
-  };
-  static auto *f64ExtensionFlag = new llvm::cl::opt<bool>{
-      "iree-vm-target-extension-f64",
-      llvm::cl::init(false),
-      llvm::cl::desc("Support f64 target opcode extensions."),
-      llvm::cl::cat(vmTargetOptionsCategory),
-  };
-  static auto *truncateUnsupportedIntegersFlag = new llvm::cl::opt<bool>{
-      "iree-vm-target-truncate-unsupported-integers",
-      llvm::cl::init(true),
-      llvm::cl::desc("Truncate i64 to i32 when unsupported."),
-      llvm::cl::cat(vmTargetOptionsCategory),
-  };
-  static auto *truncateUnsupportedFloatsFlag = new llvm::cl::opt<bool>{
-      "iree-vm-target-truncate-unsupported-floats",
-      llvm::cl::init(true),
-      llvm::cl::desc("Truncate f64 to f32 when unsupported."),
-      llvm::cl::cat(vmTargetOptionsCategory),
-  };
-
-  TargetOptions targetOptions;
-  targetOptions.indexBits = *indexBitsFlag;
-  if (*i64ExtensionFlag) {
-    targetOptions.i64Extension = true;
-  }
-  if (*f32ExtensionFlag) {
-    targetOptions.f32Extension = true;
-  }
-  if (*f64ExtensionFlag) {
-    targetOptions.f64Extension = true;
-  }
-  targetOptions.truncateUnsupportedIntegers = *truncateUnsupportedIntegersFlag;
-  targetOptions.truncateUnsupportedFloats = *truncateUnsupportedFloatsFlag;
-  return targetOptions;
+  binder.opt<int>("iree-vm-target-index-bits", indexBits,
+                  llvm::cl::desc("Bit width of index types."),
+                  llvm::cl::cat(vmTargetOptionsCategory));
+  binder.opt<bool>("iree-vm-target-extension-i64", i64Extension,
+                   llvm::cl::desc("Support i64 target opcode extensions."),
+                   llvm::cl::cat(vmTargetOptionsCategory));
+  binder.opt<bool>("iree-vm-target-extension-f32", f32Extension,
+                   llvm::cl::desc("Support f32 target opcode extensions."),
+                   llvm::cl::cat(vmTargetOptionsCategory));
+  binder.opt<bool>("iree-vm-target-extension-f64", f64Extension,
+                   llvm::cl::desc("Support f64 target opcode extensions."),
+                   llvm::cl::cat(vmTargetOptionsCategory));
+  binder.opt<bool>("iree-vm-target-truncate-unsupported-integers",
+                   truncateUnsupportedIntegers,
+                   llvm::cl::desc("Truncate i64 to i32 when unsupported."),
+                   llvm::cl::cat(vmTargetOptionsCategory));
+  binder.opt<bool>("iree-vm-target-truncate-unsupported-floats",
+                   truncateUnsupportedFloats,
+                   llvm::cl::desc("Truncate f64 to f32 when unsupported."),
+                   llvm::cl::cat(vmTargetOptionsCategory));
+  binder.opt<bool>(
+      "iree-vm-target-optimize-for-stack-size", optimizeForStackSize,
+      llvm::cl::desc(
+          "Prefer optimizations that reduce VM stack usage over performance."),
+      llvm::cl::cat(vmTargetOptionsCategory));
 }
 
 }  // namespace VM
diff --git a/iree/compiler/Dialect/VM/Conversion/TargetOptions.h b/iree/compiler/Dialect/VM/Conversion/TargetOptions.h
index 5e02b3f..82d3f97 100644
--- a/iree/compiler/Dialect/VM/Conversion/TargetOptions.h
+++ b/iree/compiler/Dialect/VM/Conversion/TargetOptions.h
@@ -7,6 +7,7 @@
 #ifndef IREE_COMPILER_DIALECT_VM_CONVERSION_TARGETOPTIONS_H_
 #define IREE_COMPILER_DIALECT_VM_CONVERSION_TARGETOPTIONS_H_
 
+#include "iree/compiler/Utils/OptionUtils.h"
 #include "mlir/Transforms/DialectConversion.h"
 
 namespace mlir {
@@ -32,7 +33,7 @@
   // Whether the i64 extension is enabled in the target VM.
   bool i64Extension = false;
   // Whether the f32 extension is enabled in the target VM.
-  bool f32Extension = false;
+  bool f32Extension = true;
   // Whether the f64 extension is enabled in the target VM.
   bool f64Extension = false;
 
@@ -45,11 +46,10 @@
 
   // Prefer optimizations that reduce VM stack usage over performance.
   bool optimizeForStackSize = true;
-};
 
-// Returns a TargetOptions struct initialized with the
-// --iree-vm-target-* flags.
-TargetOptions getTargetOptionsFromFlags();
+  void bindOptions(OptionsBinder &binder);
+  using FromFlags = OptionsFromFlags<TargetOptions>;
+};
 
 }  // namespace VM
 }  // namespace IREE
diff --git a/iree/compiler/Dialect/VM/Target/Bytecode/BUILD b/iree/compiler/Dialect/VM/Target/Bytecode/BUILD
index a62850a..3914d3c 100644
--- a/iree/compiler/Dialect/VM/Target/Bytecode/BUILD
+++ b/iree/compiler/Dialect/VM/Target/Bytecode/BUILD
@@ -12,12 +12,10 @@
         "BytecodeModuleTarget.cpp",
         "DebugDatabaseBuilder.cpp",
         "DebugDatabaseBuilder.h",
-        "TranslationFlags.cpp",
         "TranslationRegistration.cpp",
     ],
     hdrs = [
         "BytecodeModuleTarget.h",
-        "TranslationFlags.h",
     ],
     deps = [
         "//iree/compiler/Dialect/Util/IR",
diff --git a/iree/compiler/Dialect/VM/Target/Bytecode/BytecodeModuleTarget.cpp b/iree/compiler/Dialect/VM/Target/Bytecode/BytecodeModuleTarget.cpp
index a4ee999..0f2cc96 100644
--- a/iree/compiler/Dialect/VM/Target/Bytecode/BytecodeModuleTarget.cpp
+++ b/iree/compiler/Dialect/VM/Target/Bytecode/BytecodeModuleTarget.cpp
@@ -543,6 +543,7 @@
 static LogicalResult buildFlatBufferModule(BytecodeTargetOptions targetOptions,
                                            IREE::VM::ModuleOp moduleOp,
                                            SmallVector<ZIPFileRef> &zipFileRefs,
+                                           bool emitPolyglotZip,
                                            FlatbufferBuilder &fbb) {
   // Start the buffer so that we can begin recording data prior to the root
   // table (which we do at the very end). This does not change the layout of the
@@ -603,8 +604,7 @@
   for (auto rodataOp : llvm::reverse(rodataOps)) {
     // Only include rodata entries in the ZIP if they are file-like. This
     // prevents all of our string tables from getting included.
-    bool includeInZIP =
-        targetOptions.emitPolyglotZip && rodataOp.mime_type().hasValue();
+    bool includeInZIP = emitPolyglotZip && rodataOp.mime_type().hasValue();
 
     // Embed the rodata contents.
     size_t alignment =
@@ -774,6 +774,9 @@
 LogicalResult translateModuleToBytecode(IREE::VM::ModuleOp moduleOp,
                                         BytecodeTargetOptions targetOptions,
                                         llvm::raw_ostream &output) {
+  bool emitPolyglotZip =
+      targetOptions.emitPolyglotZip &&
+      targetOptions.outputFormat == BytecodeOutputFormat::kFlatBufferBinary;
   moduleOp.getContext()->getOrLoadDialect<IREE::Util::UtilDialect>();
 
   uint64_t startOffset = output.tell();
@@ -821,8 +824,8 @@
   // can be large bulk data.
   FlatbufferBuilder fbb;
   SmallVector<ZIPFileRef> zipFileRefs;
-  if (failed(
-          buildFlatBufferModule(targetOptions, moduleOp, zipFileRefs, fbb))) {
+  if (failed(buildFlatBufferModule(targetOptions, moduleOp, zipFileRefs,
+                                   emitPolyglotZip, fbb))) {
     return moduleOp.emitError()
            << "failed to build FlatBuffer BytecodeModuleDef";
   }
@@ -852,7 +855,7 @@
   }
   output.flush();
 
-  if (targetOptions.emitPolyglotZip) {
+  if (emitPolyglotZip) {
     // Append the ZIP central directory to the end of the output.
     // We have to do this here as we need to have flushed the flatbuffer
     // contents to the output so that we have their final absolute addresses.
@@ -875,6 +878,48 @@
   return translateModuleToBytecode(*moduleOps.begin(), targetOptions, output);
 }
 
+void BytecodeTargetOptions::bindOptions(OptionsBinder &binder) {
+  static llvm::cl::OptionCategory vmBytecodeOptionsCategory(
+      "IREE VM bytecode options");
+
+  binder.opt<BytecodeOutputFormat>(
+      "iree-vm-bytecode-module-output-format", outputFormat,
+      llvm::cl::cat(vmBytecodeOptionsCategory),
+      llvm::cl::desc("Output format the bytecode module is written in"),
+      llvm::cl::values(
+          clEnumValN(BytecodeOutputFormat::kFlatBufferBinary,
+                     "flatbuffer-binary", "Binary FlatBuffer file"),
+          clEnumValN(BytecodeOutputFormat::kFlatBufferText, "flatbuffer-text",
+                     "Text FlatBuffer file, debug-only"),
+          clEnumValN(BytecodeOutputFormat::kMlirText, "mlir-text",
+                     "MLIR module file in the VM dialect"),
+          clEnumValN(BytecodeOutputFormat::kAnnotatedMlirText,
+                     "annotated-mlir-text",
+                     "MLIR module file in the VM dialect with annotations")));
+  binder.opt<bool>(
+      "iree-vm-bytecode-module-optimize", optimize,
+      llvm::cl::cat(vmBytecodeOptionsCategory),
+      llvm::cl::desc("Optimizes the VM module with CSE/inlining/etc prior to "
+                     "serialization"));
+  binder.opt<std::string>(
+      "iree-vm-bytecode-source-listing", sourceListing,
+      llvm::cl::cat(vmBytecodeOptionsCategory),
+      llvm::cl::desc(
+          "Dump a VM MLIR file and annotate source locations with it"));
+  binder.opt<bool>("iree-vm-bytecode-module-strip-source-map", stripSourceMap,
+                   llvm::cl::cat(vmBytecodeOptionsCategory),
+                   llvm::cl::desc("Strips the source map from the module"));
+  binder.opt<bool>("iree-vm-bytecode-module-strip-debug-ops", stripDebugOps,
+                   llvm::cl::cat(vmBytecodeOptionsCategory),
+                   llvm::cl::desc("Strips debug-only ops from the module"));
+  binder.opt<bool>(
+      "iree-vm-emit-polyglot-zip", emitPolyglotZip,
+      llvm::cl::cat(vmBytecodeOptionsCategory),
+      llvm::cl::desc(
+          "Enables output files to be viewed as zip files for debugging "
+          "(only applies to binary targets)"));
+}
+
 }  // namespace VM
 }  // namespace IREE
 }  // namespace iree_compiler
diff --git a/iree/compiler/Dialect/VM/Target/Bytecode/BytecodeModuleTarget.h b/iree/compiler/Dialect/VM/Target/Bytecode/BytecodeModuleTarget.h
index 57b31dd..8ef8a92 100644
--- a/iree/compiler/Dialect/VM/Target/Bytecode/BytecodeModuleTarget.h
+++ b/iree/compiler/Dialect/VM/Target/Bytecode/BytecodeModuleTarget.h
@@ -8,6 +8,7 @@
 #define IREE_COMPILER_DIALECT_VM_TARGET_BYTECODE_BYTECODEMODULETARGET_H_
 
 #include "iree/compiler/Dialect/VM/IR/VMOps.h"
+#include "iree/compiler/Utils/OptionUtils.h"
 #include "llvm/Support/raw_ostream.h"
 #include "mlir/IR/BuiltinOps.h"
 #include "mlir/Support/LogicalResult.h"
@@ -52,6 +53,9 @@
   // Enables the output .vmfb to be inspected as a ZIP file.
   // This is only useful for debugging and should be disabled otherwise.
   bool emitPolyglotZip = false;
+
+  void bindOptions(OptionsBinder &binder);
+  using FromFlags = OptionsFromFlags<BytecodeTargetOptions>;
 };
 
 // Translates a vm.module to a bytecode module flatbuffer.
diff --git a/iree/compiler/Dialect/VM/Target/Bytecode/CMakeLists.txt b/iree/compiler/Dialect/VM/Target/Bytecode/CMakeLists.txt
index 1315acf..fcd6fb7 100644
--- a/iree/compiler/Dialect/VM/Target/Bytecode/CMakeLists.txt
+++ b/iree/compiler/Dialect/VM/Target/Bytecode/CMakeLists.txt
@@ -15,14 +15,12 @@
     Bytecode
   HDRS
     "BytecodeModuleTarget.h"
-    "TranslationFlags.h"
   SRCS
     "BytecodeEncoder.cpp"
     "BytecodeEncoder.h"
     "BytecodeModuleTarget.cpp"
     "DebugDatabaseBuilder.cpp"
     "DebugDatabaseBuilder.h"
-    "TranslationFlags.cpp"
     "TranslationRegistration.cpp"
   DEPS
     LLVMSupport
diff --git a/iree/compiler/Dialect/VM/Target/Bytecode/TranslationFlags.cpp b/iree/compiler/Dialect/VM/Target/Bytecode/TranslationFlags.cpp
deleted file mode 100644
index 815bfc9..0000000
--- a/iree/compiler/Dialect/VM/Target/Bytecode/TranslationFlags.cpp
+++ /dev/null
@@ -1,86 +0,0 @@
-// Copyright 2019 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/VM/Target/Bytecode/TranslationFlags.h"
-
-#include "llvm/Support/CommandLine.h"
-
-namespace mlir {
-namespace iree_compiler {
-namespace IREE {
-namespace VM {
-
-// TODO(b/145140345): fix LLVM category registration with ASAN.
-// static llvm::cl::OptionCategory vmBytecodeOptionsCategory(
-//     "IREE VM bytecode options");
-
-static llvm::cl::opt<BytecodeOutputFormat> outputFormatFlag{
-    "iree-vm-bytecode-module-output-format",
-    llvm::cl::desc("Output format the bytecode module is written in"),
-    llvm::cl::init(BytecodeOutputFormat::kFlatBufferBinary),
-    llvm::cl::values(
-        clEnumValN(BytecodeOutputFormat::kFlatBufferBinary, "flatbuffer-binary",
-                   "Binary FlatBuffer file"),
-        clEnumValN(BytecodeOutputFormat::kFlatBufferText, "flatbuffer-text",
-                   "Text FlatBuffer file, debug-only"),
-        clEnumValN(BytecodeOutputFormat::kMlirText, "mlir-text",
-                   "MLIR module file in the VM dialect"),
-        clEnumValN(BytecodeOutputFormat::kAnnotatedMlirText,
-                   "annotated-mlir-text",
-                   "MLIR module file in the VM dialect with annotations")),
-};
-
-static llvm::cl::opt<bool> optimizeFlag{
-    "iree-vm-bytecode-module-optimize",
-    llvm::cl::desc(
-        "Optimizes the VM module with CSE/inlining/etc prior to serialization"),
-    llvm::cl::init(true),
-};
-
-static llvm::cl::opt<std::string> sourceListingFlag{
-    "iree-vm-bytecode-source-listing",
-    llvm::cl::desc("Dump a VM MLIR file and annotate source locations with it"),
-    llvm::cl::init(""),
-};
-
-static llvm::cl::opt<bool> stripSourceMapFlag{
-    "iree-vm-bytecode-module-strip-source-map",
-    llvm::cl::desc("Strips the source map from the module"),
-    llvm::cl::init(false),
-};
-
-static llvm::cl::opt<bool> stripDebugOpsFlag{
-    "iree-vm-bytecode-module-strip-debug-ops",
-    llvm::cl::desc("Strips debug-only ops from the module"),
-    llvm::cl::init(false),
-};
-
-static llvm::cl::opt<bool> emitPolyglotZipFlag{
-    "iree-vm-emit-polyglot-zip",
-    llvm::cl::desc(
-        "Enables output files to be viewed as zip files for debugging"),
-    llvm::cl::init(true),
-};
-
-BytecodeTargetOptions getBytecodeTargetOptionsFromFlags() {
-  BytecodeTargetOptions targetOptions;
-  targetOptions.outputFormat = outputFormatFlag;
-  targetOptions.optimize = optimizeFlag;
-  targetOptions.sourceListing = sourceListingFlag;
-  targetOptions.stripSourceMap = stripSourceMapFlag;
-  targetOptions.stripDebugOps = stripDebugOpsFlag;
-  targetOptions.emitPolyglotZip = emitPolyglotZipFlag;
-  if (outputFormatFlag != BytecodeOutputFormat::kFlatBufferBinary) {
-    // Only allow binary output formats to also be .zip files.
-    targetOptions.emitPolyglotZip = false;
-  }
-  return targetOptions;
-}
-
-}  // namespace VM
-}  // namespace IREE
-}  // namespace iree_compiler
-}  // namespace mlir
diff --git a/iree/compiler/Dialect/VM/Target/Bytecode/TranslationFlags.h b/iree/compiler/Dialect/VM/Target/Bytecode/TranslationFlags.h
deleted file mode 100644
index 1835dab..0000000
--- a/iree/compiler/Dialect/VM/Target/Bytecode/TranslationFlags.h
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright 2019 The IREE Authors
-//
-// Licensed under the Apache License v2.0 with LLVM Exceptions.
-// See https://llvm.org/LICENSE.txt for license information.
-// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-
-#ifndef IREE_COMPILER_DIALECT_VM_TARGET_BYTECODE_TRANSLATIONFLAGS_H_
-#define IREE_COMPILER_DIALECT_VM_TARGET_BYTECODE_TRANSLATIONFLAGS_H_
-
-#include "iree/compiler/Dialect/VM/Target/Bytecode/BytecodeModuleTarget.h"
-
-namespace mlir {
-namespace iree_compiler {
-namespace IREE {
-namespace VM {
-
-// Returns a BytecodeTargetOptions struct initialized with the
-// --iree-vm-bytecode-* flags.
-BytecodeTargetOptions getBytecodeTargetOptionsFromFlags();
-
-}  // namespace VM
-}  // namespace IREE
-}  // namespace iree_compiler
-}  // namespace mlir
-
-#endif  // IREE_COMPILER_DIALECT_VM_TARGET_BYTECODE_TRANSLATIONFLAGS_H_
diff --git a/iree/compiler/Dialect/VM/Target/Bytecode/TranslationRegistration.cpp b/iree/compiler/Dialect/VM/Target/Bytecode/TranslationRegistration.cpp
index 77568f1..88367c4 100644
--- a/iree/compiler/Dialect/VM/Target/Bytecode/TranslationRegistration.cpp
+++ b/iree/compiler/Dialect/VM/Target/Bytecode/TranslationRegistration.cpp
@@ -5,7 +5,6 @@
 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 
 #include "iree/compiler/Dialect/VM/Target/Bytecode/BytecodeModuleTarget.h"
-#include "iree/compiler/Dialect/VM/Target/Bytecode/TranslationFlags.h"
 #include "mlir/IR/BuiltinOps.h"
 #include "mlir/IR/Visitors.h"
 #include "mlir/Translation.h"
@@ -20,7 +19,7 @@
       "iree-vm-ir-to-bytecode-module",
       [](mlir::ModuleOp moduleOp, llvm::raw_ostream &output) {
         return translateModuleToBytecode(
-            moduleOp, getBytecodeTargetOptionsFromFlags(), output);
+            moduleOp, BytecodeTargetOptions::FromFlags::get(), output);
       });
 }
 
diff --git a/iree/compiler/Dialect/VM/Transforms/Conversion.cpp b/iree/compiler/Dialect/VM/Transforms/Conversion.cpp
index c190deb..cc55193 100644
--- a/iree/compiler/Dialect/VM/Transforms/Conversion.cpp
+++ b/iree/compiler/Dialect/VM/Transforms/Conversion.cpp
@@ -158,7 +158,7 @@
 static PassRegistration<ConversionPass> pass(
 
     [] {
-      auto options = getTargetOptionsFromFlags();
+      auto options = TargetOptions::FromFlags::get();
       return std::make_unique<ConversionPass>(options);
     });
 
diff --git a/iree/compiler/Dialect/VM/Transforms/Passes.cpp b/iree/compiler/Dialect/VM/Transforms/Passes.cpp
index 8dd1981..db2720a 100644
--- a/iree/compiler/Dialect/VM/Transforms/Passes.cpp
+++ b/iree/compiler/Dialect/VM/Transforms/Passes.cpp
@@ -53,7 +53,8 @@
       "iree-vm-transformation-pipeline",
       "Runs the full IREE VM dialect transformation pipeline",
       [](OpPassManager &passManager) {
-        buildVMTransformPassPipeline(passManager, getTargetOptionsFromFlags());
+        buildVMTransformPassPipeline(passManager,
+                                     TargetOptions::FromFlags::get());
       });
 }
 
diff --git a/iree/compiler/Dialect/VM/Transforms/Passes.h b/iree/compiler/Dialect/VM/Transforms/Passes.h
index 83bd4c9..eff235a 100644
--- a/iree/compiler/Dialect/VM/Transforms/Passes.h
+++ b/iree/compiler/Dialect/VM/Transforms/Passes.h
@@ -93,7 +93,7 @@
 //===----------------------------------------------------------------------===//
 
 inline void registerVMPasses() {
-  auto targetOptions = getTargetOptionsFromFlags();
+  auto targetOptions = TargetOptions::FromFlags::get();
   registerVMTransformPassPipeline();
   createConversionPass(targetOptions);
   createHoistInlinedRodataPass();
@@ -104,7 +104,7 @@
 }
 
 inline void registerVMTestPasses() {
-  getTargetOptionsFromFlags();
+  TargetOptions::FromFlags::get();
   createConvertStandardToVMTestPass();
 }
 
diff --git a/iree/compiler/Translation/HALExecutable.cpp b/iree/compiler/Translation/HALExecutable.cpp
index 1b9fc71..957515c 100644
--- a/iree/compiler/Translation/HALExecutable.cpp
+++ b/iree/compiler/Translation/HALExecutable.cpp
@@ -52,7 +52,7 @@
   mlir::registerPassManagerCLOptions();
 
   // Convert into the final target-specific module definition and serialize it.
-  auto executableOptions = IREE::HAL::getTargetOptionsFromFlags();
+  auto executableOptions = IREE::HAL::TargetOptions::FromFlags::get();
   auto result = translateFromMLIRToHALExecutable(moduleOp, executableOptions);
   if (failed(result)) {
     return result;
diff --git a/iree/compiler/Translation/IREEVM.cpp b/iree/compiler/Translation/IREEVM.cpp
index 7e22013..a9eeb7f 100644
--- a/iree/compiler/Translation/IREEVM.cpp
+++ b/iree/compiler/Translation/IREEVM.cpp
@@ -13,7 +13,6 @@
 #include "iree/compiler/Dialect/HAL/Transforms/Passes.h"
 #include "iree/compiler/Dialect/Stream/Transforms/Passes.h"
 #include "iree/compiler/Dialect/Util/Transforms/Passes.h"
-#include "iree/compiler/Dialect/VM/Target/Bytecode/TranslationFlags.h"
 #include "iree/compiler/Dialect/VM/Transforms/Passes.h"
 #include "iree/compiler/InputConversion/Common/Passes.h"
 #include "iree/compiler/InputConversion/MHLO/Passes.h"
@@ -31,73 +30,55 @@
 namespace mlir {
 namespace iree_compiler {
 
-static BindingOptions getBindingOptionsFromFlags() {
+void BindingOptions::bindOptions(OptionsBinder &binder) {
   static llvm::cl::OptionCategory bindingOptionsCategory(
       "IREE translation binding support options");
-
-  static llvm::cl::opt<bool> *bindingsNativeFlag = new llvm::cl::opt<bool>{
-      "iree-native-bindings-support",
+  binder.opt<bool>(
+      "iree-native-bindings-support", native,
       llvm::cl::desc(
           "Include runtime support for native IREE ABI-compatible bindings"),
-      llvm::cl::init(true), llvm::cl::cat(bindingOptionsCategory)};
-
-  static llvm::cl::opt<bool> *bindingsTFLiteFlag = new llvm::cl::opt<bool>{
-      "iree-tflite-bindings-support",
+      llvm::cl::cat(bindingOptionsCategory));
+  binder.opt<bool>(
+      "iree-tflite-bindings-support", tflite,
       llvm::cl::desc(
           "Include runtime support for the IREE TFLite compatibility bindings"),
-      llvm::cl::init(false), llvm::cl::cat(bindingOptionsCategory)};
-
-  BindingOptions bindingOptions;
-  bindingOptions.native = *bindingsNativeFlag;
-  bindingOptions.tflite = *bindingsTFLiteFlag;
-  return bindingOptions;
+      llvm::cl::cat(bindingOptionsCategory));
 }
 
-static InputDialectOptions getInputDialectOptionsFromFlags() {
+void InputDialectOptions::bindOptions(OptionsBinder &binder) {
   static llvm::cl::OptionCategory inputDialectOptions(
       "IREE options for controlling the input transformations to apply");
 
-  static llvm::cl::opt<InputDialectOptions::Type> *typeFlag =
-      new llvm::cl::opt<InputDialectOptions::Type>{
-          "iree-input-type", llvm::cl::desc("IREE input type"),
-          llvm::cl::values(
-              clEnumValN(InputDialectOptions::Type::none, "none",
-                         "No input dialect transformation"),
-              clEnumValN(InputDialectOptions::Type::tosa, "tosa",
-                         "Legalize from TOSA ops"),
-              clEnumValN(InputDialectOptions::Type::mhlo, "mhlo",
-                         "Legalize from MHLO ops"),
-              clEnumValN(
-                  InputDialectOptions::Type::xla, "xla",
-                  "Legalize from MHLO ops (with XLA cleanup preprocessing)")),
-          llvm::cl::init(InputDialectOptions::Type::none),
-          llvm::cl::cat(inputDialectOptions)};
-
-  InputDialectOptions options;
-  options.type = *typeFlag;
-  return options;
+  binder.opt<InputDialectOptions::Type>(
+      "iree-input-type", type, llvm::cl::desc("IREE input type"),
+      llvm::cl::values(
+          clEnumValN(InputDialectOptions::Type::none, "none",
+                     "No input dialect transformation"),
+          clEnumValN(InputDialectOptions::Type::tosa, "tosa",
+                     "Legalize from TOSA ops"),
+          clEnumValN(InputDialectOptions::Type::mhlo, "mhlo",
+                     "Legalize from MHLO ops"),
+          clEnumValN(
+              InputDialectOptions::Type::xla, "xla",
+              "Legalize from MHLO ops (with XLA cleanup preprocessing)")),
+      llvm::cl::cat(inputDialectOptions));
 }
 
-static HighLevelOptimizationOptions getHighLevelOptimizationOptionsFromFlags() {
+void HighLevelOptimizationOptions::bindOptions(OptionsBinder &binder) {
   static llvm::cl::OptionCategory category(
       "IREE options for controlling high level optimizations");
 
-  static llvm::cl::opt<bool> *constEval = new llvm::cl::opt<bool>{
-      "iree-const-eval",
+  binder.opt<bool>(
+      "iree-const-eval", constEval,
       llvm::cl::desc("Enables eager evaluation of constants using the full "
                      "compiler and runtime"),
-      llvm::cl::init(false), llvm::cl::cat(category)};
-  static llvm::cl::opt<bool> *constExprHoisting = new llvm::cl::opt<bool>{
-      "iree-const-expr-hoisting",
+      llvm::cl::cat(category));
+  binder.opt<bool>(
+      "iree-const-expr-hoisting", constExprHoisting,
       llvm::cl::desc(
           "Hoists the results of latent constant expressions into immutable "
           "global initializers for evaluation at program load"),
-      llvm::cl::init(false), llvm::cl::cat(category)};
-
-  HighLevelOptimizationOptions options;
-  options.constEval = *constEval;
-  options.constExprHoisting = *constExprHoisting;
-  return options;
+      llvm::cl::cat(category));
 }
 
 void buildIREEVMTransformPassPipeline(
@@ -152,10 +133,10 @@
 
 void buildDefaultIREEVMTransformPassPipeline(OpPassManager &passManager) {
   buildIREEVMTransformPassPipeline(
-      getBindingOptionsFromFlags(), getInputDialectOptionsFromFlags(),
-      getHighLevelOptimizationOptionsFromFlags(),
-      IREE::HAL::getTargetOptionsFromFlags(),
-      IREE::VM::getTargetOptionsFromFlags(), passManager);
+      BindingOptions::FromFlags::get(), InputDialectOptions::FromFlags::get(),
+      HighLevelOptimizationOptions::FromFlags::get(),
+      IREE::HAL::TargetOptions::FromFlags::get(),
+      IREE::VM::TargetOptions::FromFlags::get(), passManager);
 }
 
 void registerIREEVMTransformPassPipeline() {
@@ -199,13 +180,14 @@
 static LogicalResult translateFromMLIRToVMBytecodeModuleWithFlags(
     ModuleOp moduleOp, llvm::raw_ostream &output) {
   mlir::registerPassManagerCLOptions();
-  auto bindingOptions = getBindingOptionsFromFlags();
-  auto inputOptions = getInputDialectOptionsFromFlags();
+  auto bindingOptions = BindingOptions::FromFlags::get();
+  auto inputOptions = InputDialectOptions::FromFlags::get();
   auto highLevelOptimizationOptions =
-      getHighLevelOptimizationOptionsFromFlags();
-  auto halTargetOptions = IREE::HAL::getTargetOptionsFromFlags();
-  auto vmTargetOptions = IREE::VM::getTargetOptionsFromFlags();
-  auto bytecodeTargetOptions = IREE::VM::getBytecodeTargetOptionsFromFlags();
+      HighLevelOptimizationOptions::FromFlags::get();
+  auto halTargetOptions = IREE::HAL::TargetOptions::FromFlags::get();
+  auto vmTargetOptions = IREE::VM::TargetOptions::FromFlags::get();
+  auto bytecodeTargetOptions =
+      IREE::VM::BytecodeTargetOptions::FromFlags::get();
   auto result = translateFromMLIRToVM(moduleOp, bindingOptions, inputOptions,
                                       highLevelOptimizationOptions,
                                       halTargetOptions, vmTargetOptions);
@@ -223,12 +205,12 @@
 static LogicalResult translateFromMLIRToVMCModuleWithFlags(
     ModuleOp moduleOp, llvm::raw_ostream &output) {
   mlir::registerPassManagerCLOptions();
-  auto bindingOptions = getBindingOptionsFromFlags();
-  auto inputOptions = getInputDialectOptionsFromFlags();
+  auto bindingOptions = BindingOptions::FromFlags::get();
+  auto inputOptions = InputDialectOptions::FromFlags::get();
   auto highLevelOptimizationOptions =
-      getHighLevelOptimizationOptionsFromFlags();
-  auto halTargetOptions = IREE::HAL::getTargetOptionsFromFlags();
-  auto vmTargetOptions = IREE::VM::getTargetOptionsFromFlags();
+      HighLevelOptimizationOptions::FromFlags::get();
+  auto halTargetOptions = IREE::HAL::TargetOptions::FromFlags::get();
+  auto vmTargetOptions = IREE::VM::TargetOptions::FromFlags::get();
   auto cTargetOptions = IREE::VM::getCTargetOptionsFromFlags();
   auto result = translateFromMLIRToVM(moduleOp, bindingOptions, inputOptions,
                                       highLevelOptimizationOptions,
@@ -243,9 +225,12 @@
 #endif  // IREE_HAVE_EMITC_DIALECT
 
 void registerIREEVMTranslationFlags() {
-  getBindingOptionsFromFlags();
-  getInputDialectOptionsFromFlags();
-  getHighLevelOptimizationOptionsFromFlags();
+  BindingOptions::FromFlags::get();
+  InputDialectOptions::FromFlags::get();
+  HighLevelOptimizationOptions::FromFlags::get();
+  IREE::HAL::TargetOptions::FromFlags::get();
+  IREE::VM::TargetOptions::FromFlags::get();
+  IREE::VM::BytecodeTargetOptions::FromFlags::get();
 }
 
 void registerIREEVMTranslation() {
diff --git a/iree/compiler/Translation/IREEVM.h b/iree/compiler/Translation/IREEVM.h
index 287dc6c..dbf601f 100644
--- a/iree/compiler/Translation/IREEVM.h
+++ b/iree/compiler/Translation/IREEVM.h
@@ -10,6 +10,7 @@
 #include "iree/compiler/Dialect/HAL/Target/TargetRegistry.h"
 #include "iree/compiler/Dialect/VM/Conversion/TargetOptions.h"
 #include "iree/compiler/Dialect/VM/Target/Bytecode/BytecodeModuleTarget.h"
+#include "iree/compiler/Utils/OptionUtils.h"
 #include "llvm/Support/raw_ostream.h"
 #include "mlir/IR/BuiltinOps.h"
 #include "mlir/Pass/PassManager.h"
@@ -29,6 +30,9 @@
   // Whether to include runtime support functions required for the IREE TFLite
   // API compatibility bindings.
   bool tflite = false;
+
+  void bindOptions(OptionsBinder &binder);
+  using FromFlags = OptionsFromFlags<BindingOptions>;
 };
 
 // The transformation to apply to the input prior to main compiler execution.
@@ -54,6 +58,9 @@
     xla,
   };
   Type type = Type::none;
+
+  void bindOptions(OptionsBinder &binder);
+  using FromFlags = OptionsFromFlags<InputDialectOptions>;
 };
 
 // Options controlling high level optimizations.
@@ -64,6 +71,9 @@
   // Enables recursive evaluation of immutable globals using the compiler
   // and runtime.
   bool constEval = false;
+
+  void bindOptions(OptionsBinder &binder);
+  using FromFlags = OptionsFromFlags<HighLevelOptimizationOptions>;
 };
 
 // Builds the translation pipeline with defaults.
diff --git a/iree/compiler/Utils/BUILD b/iree/compiler/Utils/BUILD
index 0406bcb..188bd41 100644
--- a/iree/compiler/Utils/BUILD
+++ b/iree/compiler/Utils/BUILD
@@ -18,6 +18,7 @@
         "ConversionUtils.cpp",
         "FlatbufferUtils.cpp",
         "GraphUtils.cpp",
+        "OptionUtils.cpp",
         "PassUtils.cpp",
         "TracingUtils.cpp",
     ],
@@ -26,6 +27,7 @@
         "FlatbufferUtils.h",
         "GraphUtils.h",
         "IndexSet.h",
+        "OptionUtils.h",
         "PassUtils.h",
         "PatternUtils.h",
         "TracingUtils.h",
diff --git a/iree/compiler/Utils/CMakeLists.txt b/iree/compiler/Utils/CMakeLists.txt
index a103507..56d7e2e 100644
--- a/iree/compiler/Utils/CMakeLists.txt
+++ b/iree/compiler/Utils/CMakeLists.txt
@@ -18,6 +18,7 @@
     "FlatbufferUtils.h"
     "GraphUtils.h"
     "IndexSet.h"
+    "OptionUtils.h"
     "PassUtils.h"
     "PatternUtils.h"
     "TracingUtils.h"
@@ -25,6 +26,7 @@
     "ConversionUtils.cpp"
     "FlatbufferUtils.cpp"
     "GraphUtils.cpp"
+    "OptionUtils.cpp"
     "PassUtils.cpp"
     "TracingUtils.cpp"
   DEPS
diff --git a/iree/compiler/Utils/OptionUtils.cpp b/iree/compiler/Utils/OptionUtils.cpp
new file mode 100644
index 0000000..2a2a7f2
--- /dev/null
+++ b/iree/compiler/Utils/OptionUtils.cpp
@@ -0,0 +1,94 @@
+// Copyright 2022 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/Utils/OptionUtils.h"
+
+#include "llvm/Support/ManagedStatic.h"
+
+namespace mlir {
+namespace iree_compiler {
+
+void OptionsBinder::addGlobalOption(std::unique_ptr<llvm::cl::Option> option) {
+  static llvm::ManagedStatic<std::vector<std::unique_ptr<llvm::cl::Option>>>
+      globalOptions;
+  globalOptions->push_back(std::move(option));
+}
+
+LogicalResult OptionsBinder::parseArguments(int argc, const char *const *argv,
+                                            ErrorCallback onError) {
+  assert(scope && "can only parse arguments for local scoped binder");
+  for (int i = 0; i < argc; ++i) {
+    llvm::StringRef arg(argv[i]);
+    llvm::StringRef nameVal;
+    if (arg.startswith("--")) {
+      nameVal = arg.drop_front(2);
+    } else if (arg.startswith("-")) {
+      nameVal = arg.drop_front(1);
+    } else {
+      // Pure positional options not supported.
+      if (onError) {
+        onError("pure positional arguments not supported (prefix with '--')");
+      }
+      return failure();
+    }
+
+    // Split name and value.
+    llvm::StringRef name;
+    llvm::StringRef value;
+    size_t eqPos = nameVal.find("=");
+    if (eqPos == llvm::StringRef::npos) {
+      name = nameVal;
+    } else {
+      name = nameVal.take_front(eqPos);
+      value = nameVal.drop_front(eqPos + 1);
+    }
+
+    // Find the option.
+    auto foundIt = scope->OptionsMap.find(name);
+    if (foundIt == scope->OptionsMap.end()) {
+      if (onError) {
+        std::string message("option not found: ");
+        message.append(name.begin(), name.end());
+        onError(message);
+      }
+      return failure();
+    }
+    llvm::cl::Option *option = foundIt->second;
+
+    if (llvm::cl::ProvidePositionalOption(option, value, argc)) {
+      // Error.
+      if (onError) {
+        std::string message("option parse error for: ");
+        message.append(name.begin(), name.end());
+        message.append("=");
+        message.append(value.begin(), value.end());
+        onError(message);
+      }
+      return failure();
+    }
+  }
+
+  return success();
+}
+
+llvm::SmallVector<std::string> OptionsBinder::printArguments(
+    bool nonDefaultOnly) {
+  llvm::SmallVector<std::string> values;
+  for (auto &info : localOptions) {
+    if (!info.print) continue;
+    if (nonDefaultOnly && !info.isChanged()) continue;
+
+    std::string s;
+    llvm::raw_string_ostream os(s);
+    info.print(os);
+    os.flush();
+    values.push_back(std::move(s));
+  }
+  return values;
+}
+
+}  // namespace iree_compiler
+}  // namespace mlir
diff --git a/iree/compiler/Utils/OptionUtils.h b/iree/compiler/Utils/OptionUtils.h
new file mode 100644
index 0000000..2e7fae5
--- /dev/null
+++ b/iree/compiler/Utils/OptionUtils.h
@@ -0,0 +1,230 @@
+// Copyright 2022 The IREE Authors
+//
+// Licensed under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#ifndef IREE_COMPILER_UTILS_FLAG_UTILS_H
+#define IREE_COMPILER_UTILS_FLAG_UTILS_H
+
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/CommandLine.h"
+#include "llvm/Support/raw_ostream.h"
+#include "mlir/Support/LogicalResult.h"
+
+namespace mlir {
+namespace iree_compiler {
+
+// Base class that can bind named options to fields of structs.
+//
+// Typically use by adding the following to your struct:
+//   void bindOptions(OptionsBinder &binder);
+//   using FromFlags = OptionsFromFlags<MyStruct>;
+//
+// Then you can get the struct as initialized from global CL options as:
+//   MyStruct::FromFlags::get()
+//
+// Such use is referred to as a "global binder". You can also create a
+// local binder, which does not interact with global flags by calling the
+// local() static factory. When in this mode, the lifetime of any bound
+// structures must exceed uses of the binder to parse or print (ie. via
+// parseArguments() or printArguments()).
+//
+// The underlying LLVM command line support is quite flexible and all esoteric
+// features are not supported here. Consider that supported structs can define
+// options of built-in scalar types (string, ints, bool, etc) and enums. Lists
+// of built-in scalar types are also supported.
+class OptionsBinder {
+ public:
+  static OptionsBinder global() { return OptionsBinder(); }
+
+  static OptionsBinder local() {
+    return OptionsBinder(std::make_unique<llvm::cl::SubCommand>());
+  }
+
+  template <typename T, typename V, typename... Mods>
+  void opt(llvm::StringRef name, V &value, Mods... Ms) {
+    if (!scope) {
+      // Bind global options.
+      auto opt = std::make_unique<llvm::cl::opt<T, /*ExternalStorage=*/true>>(
+          name, llvm::cl::location(value), llvm::cl::init(value),
+          std::forward<Mods>(Ms)...);
+      addGlobalOption(std::move(opt));
+    } else {
+      // Bind local options.
+      auto option =
+          std::make_unique<llvm::cl::opt<T, /*ExternalStorage=*/true>>(
+              name, llvm::cl::sub(*scope), llvm::cl::location(value),
+              llvm::cl::init(value), std::forward<Mods>(Ms)...);
+      auto printCallback =
+          makePrintCallback(option->ArgStr, option->getParser(), &value);
+      auto changedCallback = makeChangedCallback(&value);
+      localOptions.push_back(
+          LocalOptionInfo{std::move(option), printCallback, changedCallback});
+    }
+  }
+
+  template <typename T, typename V, typename... Mods>
+  void list(llvm::StringRef name, V &value, Mods... Ms) {
+    if (!scope) {
+      // Bind global options.
+      auto list =
+          std::make_unique<llvm::cl::list<T>>(name, std::forward<Mods>(Ms)...);
+      // Since list does not support external storage, hook the callback
+      // and use it to update.
+      list->setCallback(
+          [&value](const T &newElement) { value.push_back(newElement); });
+      addGlobalOption(std::move(list));
+    } else {
+      // Bind local options.
+      auto list = std::make_unique<llvm::cl::list<T>>(
+          name, llvm::cl::sub(*scope), std::forward<Mods>(Ms)...);
+      auto printCallback =
+          makeListPrintCallback(list->ArgStr, list->getParser(), &value);
+      auto changedCallback = makeListChangedCallback(&value);
+      // Since list does not support external storage, hook the callback
+      // and use it to update.
+      list->setCallback(
+          [&value](const T &newElement) { value.push_back(newElement); });
+
+      localOptions.push_back(
+          LocalOptionInfo{std::move(list), printCallback, changedCallback});
+    }
+  }
+
+  // For a local binder, parses a sequence of flags of the usual form on
+  // command lines.
+  using ErrorCallback = std::function<void(llvm::StringRef message)>;
+  LogicalResult parseArguments(int argc, const char *const *argv,
+                               ErrorCallback onError = nullptr);
+
+  // Prints any flag values that differ from their default.
+  // Flags print in the order declared, which preserves some notion of grouping
+  // and is stable.
+  llvm::SmallVector<std::string> printArguments(bool nonDefaultOnly = false);
+
+ private:
+  struct LocalOptionInfo {
+    using ChangedCallback = std::function<bool()>;
+    using PrintCallback = std::function<void(llvm::raw_ostream &)>;
+    std::unique_ptr<llvm::cl::Option> option;
+    PrintCallback print;
+    ChangedCallback isChanged;
+  };
+
+  OptionsBinder() = default;
+  OptionsBinder(std::unique_ptr<llvm::cl::SubCommand> scope)
+      : scope(std::move(scope)) {}
+  void addGlobalOption(std::unique_ptr<llvm::cl::Option> option);
+
+  // LLVM makes a half-hearted (i.e. "best effort" == "no effort") attempt to
+  // handle non-enumerated generic value based options, but the generic
+  // comparisons are not reliably implemented. Simplify our lives by only
+  // supporting int convertible values (i.e. enums), which we can restrict
+  // ourselves to.
+  // Scalar enum print specialization.
+  template <typename V, typename ParserTy>
+  static auto makePrintCallback(llvm::StringRef optionName, ParserTy &parser,
+                                V *value)
+      -> decltype(static_cast<llvm::cl::generic_parser_base &>(parser),
+                  static_cast<int>(*value), LocalOptionInfo::PrintCallback()) {
+    return [optionName, &parser, value](llvm::raw_ostream &os) {
+      StringRef valueName("<unknown>");
+      for (unsigned i = 0; i < parser.getNumOptions(); ++i) {
+        V cmpValue = static_cast<const llvm::cl::OptionValue<V> &>(
+                         parser.getOptionValue(i))
+                         .getValue();
+        if (cmpValue == *value) {
+          valueName = parser.getOption(i);
+          break;
+        }
+      }
+      os << "--" << optionName << "=" << valueName;
+    };
+  }
+
+  // Basic scalar print specialization.
+  template <typename V, typename ParserTy>
+  static auto makePrintCallback(llvm::StringRef optionName, ParserTy &parser,
+                                V *value)
+      -> decltype(static_cast<llvm::cl::basic_parser<V> &>(parser),
+                  LocalOptionInfo::PrintCallback()) {
+    return [optionName, value](llvm::raw_ostream &os) {
+      os << "--" << optionName << "=" << *value;
+    };
+  }
+
+  // Bool scalar print specialization.
+  template <typename ParserTy>
+  static auto makePrintCallback(llvm::StringRef optionName, ParserTy &parser,
+                                bool *value)
+      -> decltype(static_cast<llvm::cl::basic_parser<bool> &>(parser),
+                  LocalOptionInfo::PrintCallback()) {
+    return [optionName, value](llvm::raw_ostream &os) {
+      os << "--" << optionName << "=";
+      if (*value) {
+        os << "true";
+      } else {
+        os << "false";
+      }
+    };
+  }
+
+  // Scalar changed specialization.
+  template <typename V>
+  static LocalOptionInfo::ChangedCallback makeChangedCallback(V *currentValue) {
+    // Capture the current value as the initial value.
+    V initialValue = *currentValue;
+    return [currentValue, initialValue]() -> bool {
+      return *currentValue != initialValue;
+    };
+  }
+
+  // List changed specialization.
+  template <typename V>
+  static LocalOptionInfo::ChangedCallback makeListChangedCallback(
+      V *currentValue) {
+    return [currentValue]() -> bool { return !currentValue->empty(); };
+  }
+
+  // List basic print specialization. This is the only one we provide so far.
+  // Add others if the compiler tells you to.
+  template <typename ListTy, typename ParserTy,
+            typename V = typename ListTy::value_type>
+  static auto makeListPrintCallback(llvm::StringRef optionName,
+                                    ParserTy &parser, ListTy *values)
+      -> decltype(static_cast<llvm::cl::basic_parser<V> &>(parser),
+                  LocalOptionInfo::PrintCallback()) {
+    return [optionName, values](llvm::raw_ostream &os) {
+      os << "--" << optionName << "=";
+      for (auto it : llvm::enumerate(*values)) {
+        if (it.index() > 0) os << ",";
+        os << it.value();
+      }
+    };
+  }
+
+  std::unique_ptr<llvm::cl::SubCommand> scope;
+  llvm::SmallVector<LocalOptionInfo> localOptions;
+};
+
+template <typename DerivedTy>
+class OptionsFromFlags {
+ public:
+  static DerivedTy &get() {
+    struct InitializedTy : DerivedTy {
+      InitializedTy() {
+        OptionsBinder binder = OptionsBinder::global();
+        DerivedTy::bindOptions(binder);
+      }
+    };
+    static InitializedTy singleton;
+    return singleton;
+  }
+};
+
+}  // namespace iree_compiler
+}  // namespace mlir
+
+#endif  // IREE_COMPILER_UTILS_FLAG_UTILS_H
diff --git a/iree/samples/custom_modules/dialect/custom-translate-main.cc b/iree/samples/custom_modules/dialect/custom-translate-main.cc
index c32f145..6e2372a 100644
--- a/iree/samples/custom_modules/dialect/custom-translate-main.cc
+++ b/iree/samples/custom_modules/dialect/custom-translate-main.cc
@@ -61,7 +61,7 @@
   mlir::registerMlirTranslations();
   mlir::iree_compiler::registerIreeTranslations();
   // Make sure command line options are registered.
-  (void)mlir::iree_compiler::IREE::HAL::getTargetOptionsFromFlags();
+  (void)mlir::iree_compiler::IREE::HAL::TargetOptions::FromFlags::get();
 
   // Register MLIRContext command-line options like
   // -mlir-print-op-on-diagnostic.
diff --git a/iree/tools/iree-run-mlir-main.cc b/iree/tools/iree-run-mlir-main.cc
index 11e4841..44d44d4 100644
--- a/iree/tools/iree-run-mlir-main.cc
+++ b/iree/tools/iree-run-mlir-main.cc
@@ -45,7 +45,6 @@
 #include "iree/base/tracing.h"
 #include "iree/compiler/Dialect/HAL/Target/TargetBackend.h"
 #include "iree/compiler/Dialect/VM/Target/Bytecode/BytecodeModuleTarget.h"
-#include "iree/compiler/Dialect/VM/Target/Bytecode/TranslationFlags.h"
 #include "iree/compiler/Dialect/VM/Target/init_targets.h"
 #include "iree/compiler/Translation/IREEVM.h"
 #include "iree/hal/api.h"
@@ -163,7 +162,7 @@
   IREE_TRACE_SCOPE();
   out_target_backends->clear();
   auto target_backends =
-      mlir::iree_compiler::IREE::HAL::getTargetOptionsFromFlags().targets;
+      mlir::iree_compiler::IREE::HAL::TargetOptions::FromFlags::get().targets;
   if (target_backends.empty()) {
     iree_allocator_t host_allocator = iree_allocator_system();
     iree_hal_driver_info_t* driver_infos = NULL;
@@ -220,7 +219,7 @@
   }
 
   auto bytecode_options =
-      mlir::iree_compiler::IREE::VM::getBytecodeTargetOptionsFromFlags();
+      mlir::iree_compiler::IREE::VM::BytecodeTargetOptions::FromFlags::get();
   std::string binary_contents;
   llvm::raw_string_ostream binary_output(binary_contents);
   if (failed(mlir::iree_compiler::IREE::VM::translateModuleToBytecode(
@@ -502,7 +501,7 @@
   mlir::iree_compiler::registerIREEVMTranslationFlags();
   mlir::registerLLVMDialectTranslation(registry);
   // Make sure command line options are registered.
-  (void)mlir::iree_compiler::IREE::HAL::getTargetOptionsFromFlags();
+  (void)mlir::iree_compiler::IREE::HAL::TargetOptions::FromFlags::get();
 
   // Register MLIRContext command-line options like
   // -mlir-print-op-on-diagnostic.
diff --git a/iree/tools/iree_translate_lib.cc b/iree/tools/iree_translate_lib.cc
index c4ce48f..f12725f 100644
--- a/iree/tools/iree_translate_lib.cc
+++ b/iree/tools/iree_translate_lib.cc
@@ -53,7 +53,7 @@
   mlir::registerMlirTranslations();
   mlir::iree_compiler::registerIreeTranslations();
   // Make sure command line options are registered.
-  (void)mlir::iree_compiler::IREE::HAL::getTargetOptionsFromFlags();
+  (void)mlir::iree_compiler::IREE::HAL::TargetOptions::FromFlags::get();
 
   // Register MLIRContext command-line options like
   // -mlir-print-op-on-diagnostic.
diff --git a/llvm-external-projects/iree-compiler-api/BUILD.bazel b/llvm-external-projects/iree-compiler-api/BUILD.bazel
index 94afcee..f103ced 100644
--- a/llvm-external-projects/iree-compiler-api/BUILD.bazel
+++ b/llvm-external-projects/iree-compiler-api/BUILD.bazel
@@ -69,6 +69,7 @@
         "//iree/compiler/InputConversion/MHLO",
         "//iree/compiler/InputConversion/TOSA",
         "//iree/compiler/Translation:IREEVM",
+        "//iree/compiler/Utils",
         "//iree/tools:init_targets",
         "//iree/tools:iree_translate_lib",
         "@llvm-project//lld:COFF",
diff --git a/llvm-external-projects/iree-compiler-api/build_tools/smoketest.py b/llvm-external-projects/iree-compiler-api/build_tools/smoketest.py
index d5e407f..385451c 100644
--- a/llvm-external-projects/iree-compiler-api/build_tools/smoketest.py
+++ b/llvm-external-projects/iree-compiler-api/build_tools/smoketest.py
@@ -12,7 +12,7 @@
 from iree.compiler.dialects import arith
 from iree.compiler.dialects import chlo
 from iree.compiler.dialects import mhlo
-from iree.compiler.dialects import iree as iree_dialect
+from iree.compiler.dialects import iree_input
 from iree.compiler.dialects import builtin
 from iree.compiler.dialects import std
 from iree.compiler.dialects import linalg
@@ -24,13 +24,13 @@
 from iree.compiler.dialects import tosa
 from iree.compiler.dialects import vector
 
-from iree.compiler.api import driver
+from iree.compiler.transforms import ireec
 
 # Test the compiler API.
 with ir.Context() as ctx:
   chlo.register_chlo_dialect(ctx)
   mhlo.register_mhlo_dialect(ctx)
-  iree_dialect.register_dialect(ctx)
+  iree_input.register_dialect(ctx)
 
   input_module = ir.Module.parse(r"""
     builtin.module  {
@@ -42,16 +42,16 @@
     }
   """)
 
-  options = driver.CompilerOptions()
-  options.set_input_dialect_mhlo()
-  options.add_target_backend("cpu")
+  options = ireec.CompilerOptions("--iree-input-type=mhlo",
+                                  "--iree-hal-target-backends=cpu")
+  print(options)
   pm = passmanager.PassManager()
-  driver.build_iree_vm_pass_pipeline(options, pm)
+  ireec.build_iree_vm_pass_pipeline(options, pm)
   pm.run(input_module)
 
   print(input_module)
   bytecode_io = io.BytesIO()
-  driver.translate_module_to_vm_bytecode(options, input_module, bytecode_io)
+  ireec.translate_module_to_vm_bytecode(options, input_module, bytecode_io)
   print(f"Bytecode module len = {len(bytecode_io.getbuffer())}")
 
 # Check console scripts.
diff --git a/llvm-external-projects/iree-compiler-api/include/iree-compiler-c/Compiler.h b/llvm-external-projects/iree-compiler-api/include/iree-compiler-c/Compiler.h
index c1fd471..e4c0d3d 100644
--- a/llvm-external-projects/iree-compiler-api/include/iree-compiler-c/Compiler.h
+++ b/llvm-external-projects/iree-compiler-api/include/iree-compiler-c/Compiler.h
@@ -37,14 +37,15 @@
 MLIR_CAPI_EXPORTED IreeCompilerOptions ireeCompilerOptionsCreate();
 MLIR_CAPI_EXPORTED void ireeCompilerOptionsDestroy(IreeCompilerOptions options);
 
-MLIR_CAPI_EXPORTED void ireeCompilerOptionsSetInputDialectMHLO(
-    IreeCompilerOptions options);
-MLIR_CAPI_EXPORTED void ireeCompilerOptionsSetInputDialectTOSA(
-    IreeCompilerOptions options);
-MLIR_CAPI_EXPORTED void ireeCompilerOptionsSetInputDialectXLA(
-    IreeCompilerOptions options);
-MLIR_CAPI_EXPORTED void ireeCompilerOptionsAddTargetBackend(
-    IreeCompilerOptions options, const char *targetBackend);
+// Parses argv style arguments into a compiler options structure.
+MLIR_CAPI_EXPORTED MlirLogicalResult ireeCompilerOptionsSetFlags(
+    IreeCompilerOptions options, int argc, const char *const *argv,
+    void (*onError)(MlirStringRef, void *), void *userData);
+
+// Enumerates any non default flags and invokes the callback.
+MLIR_CAPI_EXPORTED void ireeCompilerOptionsGetFlags(
+    IreeCompilerOptions options, bool nonDefaultOnly,
+    void (*onFlag)(MlirStringRef, void *), void *userData);
 
 //===----------------------------------------------------------------------===//
 // Compiler stages.
diff --git a/llvm-external-projects/iree-compiler-api/lib/CAPI/Compiler.cpp b/llvm-external-projects/iree-compiler-api/lib/CAPI/Compiler.cpp
index bc7a7a6..27b658d 100644
--- a/llvm-external-projects/iree-compiler-api/lib/CAPI/Compiler.cpp
+++ b/llvm-external-projects/iree-compiler-api/lib/CAPI/Compiler.cpp
@@ -11,6 +11,7 @@
 #include "iree/compiler/InputConversion/MHLO/Passes.h"
 #include "iree/compiler/InputConversion/TOSA/Passes.h"
 #include "iree/compiler/Translation/IREEVM.h"
+#include "iree/compiler/Utils/OptionUtils.h"
 #include "iree/tools/init_targets.h"
 #include "mlir/CAPI/IR.h"
 #include "mlir/CAPI/Pass.h"
@@ -39,6 +40,17 @@
   HALTargetOptions executableOptions;
   VMTargetOptions vmTargetOptions;
   VMBytecodeTargetOptions vmBytecodeTargetOptions;
+
+  OptionsBinder binder;
+
+  CompilerOptions() : binder(OptionsBinder::local()) {
+    bindingOptions.bindOptions(binder);
+    inputDialectOptions.bindOptions(binder);
+    highLevelOptimizationOptions.bindOptions(binder);
+    executableOptions.bindOptions(binder);
+    vmTargetOptions.bindOptions(binder);
+    vmBytecodeTargetOptions.bindOptions(binder);
+  }
 };
 }  // namespace
 
@@ -53,6 +65,31 @@
   return wrap(options);
 }
 
+MlirLogicalResult ireeCompilerOptionsSetFlags(
+    IreeCompilerOptions options, int argc, const char *const *argv,
+    void (*onError)(MlirStringRef, void *), void *userData) {
+  CompilerOptions *optionsCpp = unwrap(options);
+  auto callback = [&](llvm::StringRef message) {
+    if (onError) {
+      onError(wrap(message), userData);
+    }
+  };
+  if (failed(optionsCpp->binder.parseArguments(argc, argv, callback))) {
+    return mlirLogicalResultFailure();
+  }
+  return mlirLogicalResultSuccess();
+}
+
+void ireeCompilerOptionsGetFlags(IreeCompilerOptions options,
+                                 bool nonDefaultOnly,
+                                 void (*onFlag)(MlirStringRef, void *),
+                                 void *userData) {
+  auto flagVector = unwrap(options)->binder.printArguments(nonDefaultOnly);
+  for (std::string &value : flagVector) {
+    onFlag(wrap(llvm::StringRef(value)), userData);
+  }
+}
+
 void ireeCompilerOptionsDestroy(IreeCompilerOptions options) {
   delete unwrap(options);
 }
diff --git a/llvm-external-projects/iree-compiler-api/python/CMakeLists.txt b/llvm-external-projects/iree-compiler-api/python/CMakeLists.txt
index cede62e..e1beabc 100644
--- a/llvm-external-projects/iree-compiler-api/python/CMakeLists.txt
+++ b/llvm-external-projects/iree-compiler-api/python/CMakeLists.txt
@@ -11,7 +11,7 @@
 declare_mlir_python_sources(IREECompilerAPIPythonSources
   ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/iree/compiler"
   SOURCES
-    api/driver.py
+    transforms/ireec.py
     version.py
 )
 declare_mlir_python_sources(IREECompilerAPIPythonExtensions)
@@ -31,11 +31,11 @@
 # Extensions
 ################################################################################
 
-declare_mlir_python_extension(IREECompilerAPIPythonExtensions.CompilerDriver
-  MODULE_NAME _ireeCompilerDriver
+declare_mlir_python_extension(IREECompilerAPIPythonExtensions.IREECTransforms
+  MODULE_NAME _ireecTransforms
   ADD_TO_PARENT IREECompilerAPIPythonExtensions
   SOURCES
-    CompilerModule.cpp
+    IREECTransforms.cpp
   EMBED_CAPI_LINK_LIBS
     IREECompilerAPICompilerCAPI
   PRIVATE_LINK_LIBS
diff --git a/llvm-external-projects/iree-compiler-api/python/CompilerModule.cpp b/llvm-external-projects/iree-compiler-api/python/IREECTransforms.cpp
similarity index 71%
rename from llvm-external-projects/iree-compiler-api/python/CompilerModule.cpp
rename to llvm-external-projects/iree-compiler-api/python/IREECTransforms.cpp
index 37256ce..c7c1d7a 100644
--- a/llvm-external-projects/iree-compiler-api/python/CompilerModule.cpp
+++ b/llvm-external-projects/iree-compiler-api/python/IREECTransforms.cpp
@@ -55,6 +55,40 @@
   bool binary;
 };
 
+void setOptionsFromArgs(IreeCompilerOptions &options, py::args &args) {
+  std::vector<std::string> allocedArgs;
+  std::vector<const char *> cArgs;
+  for (auto &argObject : args) {
+    allocedArgs.push_back(py::cast<std::string>(argObject));
+    cArgs.push_back(allocedArgs.back().c_str());
+  }
+
+  std::string errorMessage;
+  auto callback = +[](MlirStringRef msg, void *userData) {
+    std::string *innerErrorMessage = static_cast<std::string *>(userData);
+    if (!innerErrorMessage->empty()) innerErrorMessage->append("\n");
+    innerErrorMessage->append(msg.data, msg.length);
+  };
+  if (mlirLogicalResultIsFailure(ireeCompilerOptionsSetFlags(
+          options, cArgs.size(), cArgs.data(), callback,
+          static_cast<void *>(&errorMessage)))) {
+    throw std::invalid_argument(std::move(errorMessage));
+  }
+}
+
+py::list getFlagsFromOptions(IreeCompilerOptions &options,
+                             bool nonDefaultOnly) {
+  py::list flags;
+  auto callback = +[](MlirStringRef flag, void *userData) {
+    py::list *innerFlags = static_cast<py::list *>(userData);
+    py::str s(flag.data, flag.length);
+    innerFlags->append(std::move(s));
+  };
+  ireeCompilerOptionsGetFlags(options, nonDefaultOnly, callback,
+                              static_cast<void *>(&flags));
+  return flags;
+}
+
 }  // namespace
 
 static const char BUILD_MHLO_IMPORT_PASS_PIPELINE_DOCSTRING[] =
@@ -101,40 +135,39 @@
 binary data written to it.
 )";
 
-PYBIND11_MODULE(_ireeCompilerDriver, m) {
-  m.doc() = "iree-compiler driver api";
+PYBIND11_MODULE(_ireecTransforms, m) {
+  m.doc() = "ireec transforms API";
   ireeCompilerRegisterTargetBackends();
 
   py::class_<PyCompilerOptions>(m, "CompilerOptions",
                                 "Options for the IREE backend compiler.")
-      .def(py::init<>())
+      .def(py::init([](py::args args) {
+        PyCompilerOptions self;
+        if (!args.empty()) {
+          setOptionsFromArgs(self.options, args);
+        }
+        return self;
+      }))
       .def(
-          "set_input_dialect_mhlo",
-          [](PyCompilerOptions &self) {
-            ireeCompilerOptionsSetInputDialectMHLO(self.options);
+          "set",
+          [](PyCompilerOptions &self, py::args args) {
+            setOptionsFromArgs(self.options, args);
           },
-          "Sets the input type to the 'mhlo' dialect")
+          "Sets options from flag values in the usual form for command line "
+          "options")
       .def(
-          "set_input_dialect_tosa",
-          [](PyCompilerOptions &self) {
-            ireeCompilerOptionsSetInputDialectTOSA(self.options);
+          "get",
+          [](PyCompilerOptions &self, bool nonDefaultOnly) {
+            return getFlagsFromOptions(self.options, nonDefaultOnly);
           },
-          "Sets the input type to the 'tosa' dialect")
-      .def(
-          "set_input_dialect_xla",
-          [](PyCompilerOptions &self) {
-            ireeCompilerOptionsSetInputDialectTOSA(self.options);
-          },
-          "Sets the input type to the 'mhlo' dialect with XLA compatibility "
-          "cleanups")
-      .def(
-          "add_target_backend",
-          [](PyCompilerOptions &self, const std::string &targetBackend) {
-            ireeCompilerOptionsAddTargetBackend(self.options,
-                                                targetBackend.c_str());
-          },
-          py::arg("target_backend"),
-          "Adds a target backend (i.e. 'cpu', 'vulkan-spirv', etc)");
+          py::arg("non_default_only") = true,
+          "Gets a list of flag values for the options (by default only those "
+          "that have changed).")
+      .def("__repr__", [](PyCompilerOptions &self) {
+        py::list flags =
+            getFlagsFromOptions(self.options, /*nonDefaultOnly=*/true);
+        return py::str("<CompilerOptions:") + py::repr(flags) + py::str(">");
+      });
   m.def(
       "build_mhlo_import_pass_pipeline",
       [](MlirPassManager passManager) {
diff --git a/llvm-external-projects/iree-compiler-api/python/iree/compiler/api/driver.py b/llvm-external-projects/iree-compiler-api/python/iree/compiler/transforms/ireec.py
similarity index 81%
rename from llvm-external-projects/iree-compiler-api/python/iree/compiler/api/driver.py
rename to llvm-external-projects/iree-compiler-api/python/iree/compiler/transforms/ireec.py
index e8bcd96..571e6db 100644
--- a/llvm-external-projects/iree-compiler-api/python/iree/compiler/api/driver.py
+++ b/llvm-external-projects/iree-compiler-api/python/iree/compiler/transforms/ireec.py
@@ -4,4 +4,4 @@
 # See https://llvm.org/LICENSE.txt for license information.
 # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 
-from .._mlir_libs._ireeCompilerDriver import *
+from .._mlir_libs._ireecTransforms import *
diff --git a/llvm-external-projects/iree-compiler-api/setup.py b/llvm-external-projects/iree-compiler-api/setup.py
index 2612810..3b8b497 100644
--- a/llvm-external-projects/iree-compiler-api/setup.py
+++ b/llvm-external-projects/iree-compiler-api/setup.py
@@ -138,6 +138,7 @@
     ext_modules=[
         CMakeExtension("iree.compiler._mlir_libs._mlir"),
         CMakeExtension("iree.compiler._mlir_libs._ireeDialects"),
+        CMakeExtension("iree.compiler._mlir_libs._ireecTransforms"),
         CMakeExtension("iree.compiler._mlir_libs._mlirHlo"),
         CMakeExtension("iree.compiler._mlir_libs._mlirLinalgPasses"),
     ],
diff --git a/llvm-external-projects/iree-compiler-api/unittests/CMakeLists.txt b/llvm-external-projects/iree-compiler-api/unittests/CMakeLists.txt
index f7c6b56..1750864 100644
--- a/llvm-external-projects/iree-compiler-api/unittests/CMakeLists.txt
+++ b/llvm-external-projects/iree-compiler-api/unittests/CMakeLists.txt
@@ -1 +1,21 @@
+function(iree_compiler_api_py_test)
+  cmake_parse_arguments(
+    ARG
+    ""
+    "NAME;MAIN"
+    ""
+    ${ARGN}
+  )
+  set(TEST_NAME "iree-compiler-api-${ARG_NAME}")
+  add_test(
+    NAME
+      ${TEST_NAME}
+    WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}"
+    COMMAND "${Python3_EXECUTABLE}" "${CMAKE_CURRENT_SOURCE_DIR}/${ARG_MAIN}"
+  )
+  set_tests_properties(${TEST_NAME} PROPERTIES
+    ENVIRONMENT PYTHONPATH=${IREE_COMPILER_API_BINARY_DIR}/python_package)
+endfunction()
+
 add_subdirectory(tools)
+add_subdirectory(transforms/ireec)
diff --git a/llvm-external-projects/iree-compiler-api/unittests/tools/CMakeLists.txt b/llvm-external-projects/iree-compiler-api/unittests/tools/CMakeLists.txt
index 9233c3f..e276d13 100644
--- a/llvm-external-projects/iree-compiler-api/unittests/tools/CMakeLists.txt
+++ b/llvm-external-projects/iree-compiler-api/unittests/tools/CMakeLists.txt
@@ -4,25 +4,6 @@
 # See https://llvm.org/LICENSE.txt for license information.
 # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 
-function(iree_compiler_api_py_test)
-  cmake_parse_arguments(
-    ARG
-    ""
-    "NAME;MAIN"
-    ""
-    ${ARGN}
-  )
-  set(TEST_NAME "iree-compiler-api-${ARG_NAME}")
-  add_test(
-    NAME
-      ${TEST_NAME}
-    WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}"
-    COMMAND "${Python3_EXECUTABLE}" "${CMAKE_CURRENT_SOURCE_DIR}/${ARG_MAIN}"
-  )
-  set_tests_properties(${TEST_NAME} PROPERTIES
-    ENVIRONMENT PYTHONPATH=${IREE_COMPILER_API_BINARY_DIR}/python_package)
-endfunction()
-
 iree_compiler_api_py_test(
   NAME
     compiler_core_test
diff --git a/llvm-external-projects/iree-compiler-api/unittests/transforms/ireec/CMakeLists.txt b/llvm-external-projects/iree-compiler-api/unittests/transforms/ireec/CMakeLists.txt
new file mode 100644
index 0000000..e0a903d
--- /dev/null
+++ b/llvm-external-projects/iree-compiler-api/unittests/transforms/ireec/CMakeLists.txt
@@ -0,0 +1,12 @@
+# Copyright 2022 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
+
+iree_compiler_api_py_test(
+  NAME
+    compiler_options_test
+  MAIN
+    "compiler_options_test.py"
+)
diff --git a/llvm-external-projects/iree-compiler-api/unittests/transforms/ireec/compiler_options_test.py b/llvm-external-projects/iree-compiler-api/unittests/transforms/ireec/compiler_options_test.py
new file mode 100644
index 0000000..945db55
--- /dev/null
+++ b/llvm-external-projects/iree-compiler-api/unittests/transforms/ireec/compiler_options_test.py
@@ -0,0 +1,50 @@
+# Copyright 2022 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
+
+import logging
+import unittest
+
+from iree.compiler.transforms import ireec
+
+
+class CompilerTest(unittest.TestCase):
+
+  def testDefaultOptions(self):
+    options = ireec.CompilerOptions()
+    self.assertEqual(repr(options), "<CompilerOptions:[]>")
+
+  def testOptionsBadArg(self):
+    with self.assertRaisesRegex(ValueError, "option not found: foobar"):
+      options = ireec.CompilerOptions("--foobar")
+
+  def testOptionsBoolArgImplicit(self):
+    options = ireec.CompilerOptions("--iree-tflite-bindings-support")
+    self.assertEqual(
+        repr(options),
+        "<CompilerOptions:['--iree-tflite-bindings-support=true']>")
+
+  def testOptionsBoolArgExplicit(self):
+    options = ireec.CompilerOptions("--iree-tflite-bindings-support=true")
+    self.assertEqual(
+        repr(options),
+        "<CompilerOptions:['--iree-tflite-bindings-support=true']>")
+
+  def testOptionsEnumArg(self):
+    options = ireec.CompilerOptions("--iree-input-type=mhlo")
+    self.assertEqual(repr(options),
+                     "<CompilerOptions:['--iree-input-type=mhlo']>")
+
+  def testListOption(self):
+    options = ireec.CompilerOptions("--iree-hal-target-backends=cpu,vmvx")
+    self.assertEqual(
+        repr(options),
+        "<CompilerOptions:['--iree-hal-target-backends=cpu,vmvx']>")
+    print(options)
+
+
+if __name__ == "__main__":
+  logging.basicConfig(level=logging.DEBUG)
+  unittest.main()